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.

使用服务器进行优化

像 Godot 这样的引擎借助其更高阶的构建和功能提供了更好的易用性。它们中的大多数都是通过场景系统来访问和使用的。使用节点和资源可以简化复杂游戏中的项目组织和资产管理。

这有几个缺点:

  • 多了一层额外的复杂性。

  • 性能比直接使用简单的 API 要低。

  • 无法使用多线程来控制它们。

  • 需要更多的内存。

在大多数情况下,这并不是一个真正的问题。Godot 经过了很好的优化,大多数操作都是通过信号处理的,所以不需要轮询。不过,有时候在其他优化途径都已用尽时,我们还是希望从硬件中榨取更好的性能。例如,对于每一帧都需要处理的东西来说,处理数以万计的实例可能是一个瓶颈。

这种情况会让程序员后悔自己使用的是游戏引擎,希望能回到更加手工、更加底层的游戏代码实现中去。

不过,Godot 的设计旨在绕过这个问题。

参见

你可以使用 Bullet Shower 演示项目 来了解如何使用底层服务器。

服务器

Godot 最有趣的设计决策之一就是整个场景系统都是可选的。虽然目前还不能在编译时去除,但你完全可以绕过它。

在核心层面,Godot 使用了服务器的概念。它们是非常底层的 API,用来控制渲染、物理、声音等。场景系统建立在它们之上,直接使用它们。最常见的服务器有:

探索它们的 API,你就会意识到,它们所提供的函数全部都是 Godot 允许你使用节点进行的操作的底层实现。

RID

使用服务器的关键是理解资源 ID(Resource ID,即 RID)对象。它们是服务器实现的不透明句柄。它们是手动分配和释放的。几乎服务器中的每个函数都需要 RID 来访问实际的资源。

大多数 Godot 节点和资源在内部都包含这些来自服务器的 RID,它们可以通过不同的函数获得。事实上,任何继承 Resource 的类型都可以直接转换为 RID。不过并不是所有资源都包含 RID:在这种情况下,RID 为空。然后资源可以以 RID 的形式传递给服务器 API。

警告

资源是引用计数的(见 RefCounted),对资源 RID 的引用不会用于确定资源是否仍在使用。请确保在服务器外部对资源保持引用,否则资源及其 RID 都将被删除。

对于节点,有很多可用的函数:

请尝试探索你所熟悉的节点和资源,找到获取服务器 RID 的函数。

不建议控制已有节点关联的对象的 RID。应始终使用服务器函数来创建和控制新对象,并与现有对象进行交互。

创建精灵

这是一个如何从代码创建精灵并使用底层 CanvasItem API 移动它的示例。

备注

使用 RenderingServer 创建画布项时,应在第一帧使用 RenderingServer.canvas_item_reset_physics_interpolation() 重置物理插值。这可确保渲染系统和物理系统之间的正确同步。

如果不这样做,画布项在场景加载时可能会看起来像是瞬间移动进来的,而不是直接出现在其预期位置。

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)

服务器中的 CanvasItem API 允许你向其添加绘制图元。一旦添加,它们就不能被修改。需要清除 Item,并重新添加图元(设置变换时则不然,变换可根据需要多次进行)。

图元按如下方式清除:

RenderingServer.canvas_item_clear(ci_rid)

将网格实例化到 3D 空间

3D API 与 2D API 不同,所以必须使用实例化 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 刚体并随其移动精灵

这段代码使用 PhysicsServer2D API 创建了一个 RigidBody2D,并在该物体移动时移动 CanvasItem

# 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.
    # ...

3D 版本应该非常相似,因为 2D 和 3D 物理服务器是相同的(分别使用 RigidBody3DPhysicsServer3D)。

从服务器获取数据

除非你知道自己在做什么,否则请尽量永远不要通过调用函数从 RenderingServerPhysicsServer2DPhysicsServer3D 请求任何信息。这些服务器通常会异步运行以提高性能,调用任何返回值的函数都会使它们停滞,并迫使它们处理任何待处理的内容,直到实际调用该函数。如果你每帧都调用它们,这将严重降低性能(而且原因并不明显)。

正因为如此,这类服务器中的大部分 API 都被设计成甚至无法请求回信息,除非是可以保存的实际数据。