Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

使用伺服器進行最佳化

Engines like Godot provide increased ease of use thanks to their high-level constructs and features. Most of them are accessed and used via the scene system. Using nodes and resources simplifies project organization and asset management in complex games.

There are several drawbacks to this:

  • 會增加一層額外的複雜度。

  • 效能會比直接使用底層 API 來得低。

  • It is not possible to use multiple threads to control them.

  • 需要消耗更多記憶體。

In most cases, this is not really a problem. Godot is well-optimized, and most operations are handled with signals, which means no polling is required. Still, sometimes, we want to extract better performance from the hardware when other avenues of optimization have been exhausted. For example, dealing with tens of thousands of instances for something that needs to be processed every frame can be a bottleneck.

這種情況有時會讓開發者懷念手寫底層程式碼的時光,甚至後悔選擇了遊戲引擎,希望能回到更手工、低階的遊戲實作方式。

不過,Godot 的設計其實能解決這類問題。

也參考

You can see how using low-level servers works in action using the Bullet Shower demo project.

伺服器

One of the most interesting design decisions for Godot is the fact that the whole scene system is optional. While it is not possible to compile it out, it can be completely bypassed.

At the core, Godot uses the concept of Servers. They are low-level APIs to control rendering, physics, sound, etc. The scene system is built on top of them and uses them directly. The most common servers are:

Explore their APIs, and you will realize that all the functions provided are low-level implementations of everything Godot allows you to do using nodes.

RID

要使用伺服器,最重要的是理解『Resource ID』(RID)物件。這些 RID 就是對伺服器內部資源的控制碼,需要手動分配與釋放。幾乎所有伺服器函式都需要透過 RID 來存取實際資源。

大多數 Godot 節點與資源內部都持有來自伺服器的 RID,可以透過不同函式取得。其實所有繼承自 Resource 的物件,都可以直接轉換為 RID。但並非所有資源都有 RID,這時 RID 會是空的。不過你仍然可以將這些資源作為 RID 傳給伺服器 API。

警告

Resources are reference-counted (see RefCounted), and references to a resource's RID are not counted when determining whether the resource is still in use. Make sure to keep a reference to the resource outside the server. Otherwise, both the resource and its RID will be erased.

對於節點,有許多函式可以取得相關 RID:

建議你多嘗試探索熟悉的節點與資源,看看能從哪些函式取得對應的伺服器 RID

不建議直接操作已經關聯節點的物件的 RID。要建立或控制新的資源,或與現有資源互動,應該都直接使用伺服器函式來處理。

建立 Sprite

This is an example of how to create a sprite from code and move it using the low-level CanvasItem API.

備註

When creating canvas items using the RenderingServer, you should reset physics interpolation on the first frame using RenderingServer.canvas_item_reset_physics_interpolation(). This ensures proper synchronization between the rendering and physics systems.

If this is not done, the canvas item may appear to teleport in when the scene is loaded, rather than appearing directly at its intended location.

extends Node2D


# RenderingServer expects references to be kept around.
var texture


func _ready():
    # Create a canvas item, child of this node.
    var ci_rid = RenderingServer.canvas_item_create()
    # Make this node the parent.
    RenderingServer.canvas_item_set_parent(ci_rid, get_canvas_item())
    # Draw a texture on it.
    # Remember to keep this reference.
    texture = load("res://my_texture.png")
    # Add it, centered.
    RenderingServer.canvas_item_add_texture_rect(ci_rid, Rect2(-texture.get_size() / 2, texture.get_size()), texture)
    # Add the item, rotated 45 degrees and translated.
    var xform = Transform2D().rotated(deg_to_rad(45)).translated(Vector2(20, 30))
    RenderingServer.canvas_item_set_transform(ci_rid, xform)
    # Reset physics interpolation for this item.
    RenderingServer.canvas_item_reset_physics_interpolation(ci_rid)

The Canvas Item API in the server allows you to add draw primitives to it. Once added, they can't be modified. The Item needs to be cleared and the primitives re-added. This is not the case for setting the transform, which can be done as many times as desired.

清除圖元方式如下:

RenderingServer.canvas_item_clear(ci_rid)

在 3D 空間中產生網格實體

3D 的 API 與 2D 不同,因此必須使用專屬的產生實體 API。

extends Node3D


# RenderingServer expects references to be kept around.
var mesh


func _ready():
    # Create a visual instance (for 3D).
    var instance = RenderingServer.instance_create()
    # Set the scenario from the world. This ensures it
    # appears with the same objects as the scene.
    var scenario = get_world_3d().scenario
    RenderingServer.instance_set_scenario(instance, scenario)
    # Add a mesh to it.
    # Remember to keep this reference.
    mesh = load("res://my_mesh.obj")
    RenderingServer.instance_set_base(instance, mesh)
    # Move the mesh around.
    var xform = Transform3D(Basis(), Vector3(2, 3, 0))
    RenderingServer.instance_set_transform(instance, xform)

建立 2D 剛體並用它移動精靈

This creates a RigidBody2D using the PhysicsServer2D API, and moves a CanvasItem when the body moves.

# PhysicsServer2D expects references to be kept around.
var body
var shape


func _body_moved(state, index):
    # Created your own canvas item; use it here.
    # `ci_rid` from the sprite example above needs to be moved to a
    # member variable (instead of within `_ready()`) so it can be referenced here.
    RenderingServer.canvas_item_set_transform(ci_rid, state.transform)


func _ready():
    # Create the body.
    body = PhysicsServer2D.body_create()
    PhysicsServer2D.body_set_mode(body, PhysicsServer2D.BODY_MODE_RIGID)
    # Add a shape.
    shape = PhysicsServer2D.rectangle_shape_create()
    # Set rectangle extents.
    PhysicsServer2D.shape_set_data(shape, Vector2(10, 10))
    # Make sure to keep the shape reference!
    PhysicsServer2D.body_add_shape(body, shape)
    # Set space, so it collides in the same space as current scene.
    PhysicsServer2D.body_set_space(body, get_world_2d().space)
    # Move initial position.
    PhysicsServer2D.body_set_state(body, PhysicsServer2D.BODY_STATE_TRANSFORM, Transform2D(0, Vector2(10, 20)))
    # Add the transform callback, when body moves
    # The last parameter is optional, can be used as index
    # if you have many bodies and a single callback.
    PhysicsServer2D.body_set_force_integration_callback(body, self, "_body_moved", 0)

    # Also create a sprite using RenderingServer here.
    # See the section above on creating a sprite.
    # ...

The 3D version should be very similar, as the 2D and 3D physics servers are identical (using RigidBody3D and PhysicsServer3D respectively).

從伺服器取得資料

Try to never request any information from RenderingServer, PhysicsServer2D, or PhysicsServer3D by calling functions unless you know what you are doing. These servers will often run asynchronously for performance and calling any function that returns a value will stall them and force them to process anything pending until the function is actually called. This will severely decrease performance if you call them every frame (and it won't be obvious why).

因此,這類伺服器的大多數 API 設計時,通常根本不允許直接回傳資訊,除非是要取得可儲存的實際資料。