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.
Checking the stable version of the documentation...
使用服务器进行优化
像 Godot 这样的引擎借助其更高阶的构建和功能提供了更好的易用性。它们中的大多数都是通过场景系统来访问和使用的。使用节点和资源可以简化复杂游戏中的项目组织和资产管理。
这有几个缺点:
多了一层额外的复杂性。
性能比直接使用简单的 API 要低。
无法使用多线程来控制它们。
需要更多的内存。
在大多数情况下,这并不是一个真正的问题。Godot 经过了很好的优化,大多数操作都是通过信号处理的,所以不需要轮询。不过,有时候在其他优化途径都已用尽时,我们还是希望从硬件中榨取更好的性能。例如,对于每一帧都需要处理的东西来说,处理数以万计的实例可能是一个瓶颈。
这种情况会让程序员后悔自己使用的是游戏引擎,希望能回到更加手工、更加底层的游戏代码实现中去。
不过,Godot 的设计旨在绕过这个问题。
参见
你可以使用 Bullet Shower 演示项目 来了解如何使用底层服务器。
服务器
Godot 最有趣的设计决策之一就是整个场景系统都是可选的。虽然目前还不能在编译时去除,但你完全可以绕过它。
在核心层面,Godot 使用了服务器的概念。它们是非常底层的 API,用来控制渲染、物理、声音等。场景系统建立在它们之上,直接使用它们。最常见的服务器有:
RenderingServer:处理所有与图形相关的内容。
PhysicsServer3D:处理所有与 3D 物理相关的内容。
PhysicsServer2D:处理所有与 2D 物理相关的内容。
AudioServer:处理与音频相关的一切。
探索它们的 API,你就会意识到,它们所提供的函数全部都是 Godot 允许你使用节点进行的操作的底层实现。
RID
使用服务器的关键是理解资源 ID(Resource ID,即 RID)对象。它们是服务器实现的不透明句柄。它们是手动分配和释放的。几乎服务器中的每个函数都需要 RID 来访问实际的资源。
大多数 Godot 节点和资源在内部都包含这些来自服务器的 RID,它们可以通过不同的函数获得。事实上,任何继承 Resource 的类型都可以直接转换为 RID。不过并不是所有资源都包含 RID:在这种情况下,RID 为空。然后资源可以以 RID 的形式传递给服务器 API。
警告
资源是引用计数的(见 RefCounted),对资源 RID 的引用不会用于确定资源是否仍在使用。请确保在服务器外部对资源保持引用,否则资源及其 RID 都将被删除。
对于节点,有很多可用的函数:
对于 CanvasItem,CanvasItem.get_canvas_item() 方法将返回服务器中的画布项 RID。
对于 CanvasLayer,CanvasLayer.get_canvas() 方法将返回服务器中的画布 RID。
对于 Viewport,Viewport.get_viewport_rid() 方法将返回服务器中的视口 RID。
对于 2D,World2D 资源(可在 Viewport 和 CanvasItem 节点中获取)包含获取 RenderingServer 画布和 PhysicsServer2D 空间的函数。这允许直接使用服务器 API 创建 2D 对象并使用它们。
对于 3D,World3D 资源(可在 Viewport 和 Node3D 节点中获取)包含获取 RenderingServer 场景和 PhysicsServer 空间的函数。这允许直接使用服务器 API 创建 3D 对象并使用它们。
VisualInstance3D 类允许分别通过 VisualInstance3D.get_instance() 和 VisualInstance3D.get_base() 获取场景实例和实例基础。
请尝试探索你所熟悉的节点和资源,找到获取服务器 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)
public partial class MyNode2D : Node2D
{
// RenderingServer expects references to be kept around.
private Texture2D _texture;
public override void _Ready()
{
// Create a canvas item, child of this node.
Rid ciRid = RenderingServer.CanvasItemCreate();
// Make this node the parent.
RenderingServer.CanvasItemSetParent(ciRid, GetCanvasItem());
// Draw a texture on it.
// Remember to keep this reference.
_texture = ResourceLoader.Load<Texture2D>("res://my_texture.png");
// Add it, centered.
RenderingServer.CanvasItemAddTextureRect(ciRid, new Rect2(-_texture.GetSize() / 2, _texture.GetSize()), _texture.GetRid());
// Add the item, rotated 45 degrees and translated.
Transform2D xform = Transform2D.Identity.Rotated(Mathf.DegToRad(45)).Translated(new Vector2(20, 30));
RenderingServer.CanvasItemSetTransform(ciRid, xform);
// Reset physics interpolation for this item.
RenderingServer.CanvasItemResetPhysicsInterpolation(ciRid);
}
}
服务器中的 CanvasItem API 允许你向其添加绘制图元。一旦添加,它们就不能被修改。需要清除 Item,并重新添加图元(设置变换时则不然,变换可根据需要多次进行)。
图元按如下方式清除:
RenderingServer.canvas_item_clear(ci_rid)
RenderingServer.CanvasItemClear(ciRid);
将网格实例化到 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)
public partial class MyNode3D : Node3D
{
// RenderingServer expects references to be kept around.
private Mesh _mesh;
public override void _Ready()
{
// Create a visual instance (for 3D).
Rid instance = RenderingServer.InstanceCreate();
// Set the scenario from the world. This ensures it
// appears with the same objects as the scene.
Rid scenario = GetWorld3D().Scenario;
RenderingServer.InstanceSetScenario(instance, scenario);
// Add a mesh to it.
// Remember to keep this reference.
_mesh = ResourceLoader.Load<Mesh>("res://my_mesh.obj");
RenderingServer.InstanceSetBase(instance, _mesh.GetRid());
// Move the mesh around.
Transform3D xform = new Transform3D(Basis.Identity, new Vector3(2, 3, 0));
RenderingServer.InstanceSetTransform(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.
# ...
using Godot;
public partial class MyNode2D : Node2D
{
private Rid _canvasItem;
private void BodyMoved(PhysicsDirectBodyState2D state, int index)
{
// Created your own canvas item; use it here.
// `ciRid` from the sprite example above needs to be moved to a
// member variable (instead of within `_Ready()`) so it can be referenced here.
RenderingServer.CanvasItemSetTransform(_canvasItem, state.Transform);
}
public override void _Ready()
{
// Create the body.
var body = PhysicsServer2D.BodyCreate();
PhysicsServer2D.BodySetMode(body, PhysicsServer2D.BodyMode.Rigid);
// Add a shape.
var shape = PhysicsServer2D.RectangleShapeCreate();
// Set rectangle extents.
PhysicsServer2D.ShapeSetData(shape, new Vector2(10, 10));
// Make sure to keep the shape reference!
PhysicsServer2D.BodyAddShape(body, shape);
// Set space, so it collides in the same space as current scene.
PhysicsServer2D.BodySetSpace(body, GetWorld2D().Space);
// Move initial position.
PhysicsServer2D.BodySetState(body, PhysicsServer2D.BodyState.Transform, new Transform2D(0, new 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.BodySetForceIntegrationCallback(body, new Callable(this, MethodName.BodyMoved), 0);
// Also create a sprite using RenderingServer here.
// See the section above on creating a sprite.
// ...
}
}
3D 版本应该非常相似,因为 2D 和 3D 物理服务器是相同的(分别使用 RigidBody3D 和 PhysicsServer3D)。
从服务器获取数据
除非你知道自己在做什么,否则请尽量永远不要通过调用函数从 RenderingServer、PhysicsServer2D 或 PhysicsServer3D 请求任何信息。这些服务器通常会异步运行以提高性能,调用任何返回值的函数都会使它们停滞,并迫使它们处理任何待处理的内容,直到实际调用该函数。如果你每帧都调用它们,这将严重降低性能(而且原因并不明显)。
正因为如此,这类服务器中的大部分 API 都被设计成甚至无法请求回信息,除非是可以保存的实际数据。