利用服务器进行优化
像 Godot 这样的引擎,由于其更高层次的构建和功能,提供了更多的易用性。它们中的大多数都是通过场景系统来访问和使用的。使用节点和资源可以简化复杂游戏中的项目组织和素材管理。
当然, 总是有缺点的:
那有一个额外复杂层
性能比直接使用简单API要低
无法使用多个线程来控制它们
需要更多的内存.
在很多情况下, 这并不是一个真正的问题(Godot是非常优化的, 大多数操作都是用信号处理的, 所以不需要轮询). 不过, 有时候还是会有这样的情况. 例如, 对于每一帧都需要处理的东西来说, 处理数以万计的实例可能是一个瓶颈.
这种情况会让程序员后悔自己使用的是游戏引擎, 希望能回到更加手动, 更加低层的游戏代码实现中去.
当然,Godot的设计工作中还是可以解决这个问题.
服务器
Godot 有许多非常有趣的设计决定,其中之一就是整个场景系统都是可选的。虽然目前还不能在编译时去除,但你完全可以绕过它。
Godot 在核心中使用了“服务器”的概念。它们是非常底层的 API,用来控制渲染、物理、声音等。场景系统建立在它们之上,直接使用它们。最常见的服务器有:
VisualServer: 处理与图形相关的一切.
PhysicsServer: 处理一切相关的3d物体.
Physics2DServer: 处理一切相关的2D物理.
AudioServer: 处理与音频相关的一切.
你只需研究它们的 API 就会意识到,它们所提供的函数全部都是 Godot 允许你所进行的操作的底层实现。
RID
使用服务器的关键是理解资源 ID(Resource ID,即 RID)对象。这些对象是服务器实现的非公开的句柄。它们是手动分配和释放的。几乎服务器中的每个功能都需要 RID 来访问实际的资源。
大多数 Godot 节点和资源都包含这些来自服务内部的 RID,它们可以通过不同的函数获得。事实上,任何继承 Resource 的东西都可以直接转换成 RID。不过并不是所有资源都包含 RID:在这种情况下,RID 为空。可以用 RID 的形式将资源传递给服务器 API。
警告
资源进行了引用计数(请参阅 Reference),但在确定是否仍在使用某个资源时,对资源 RID 的引用没有进行计数。请在服务器外保留资源的引用,并在清除资源时清除其 RID。
对于节点来说, 有很多函数功能可以使用:
对于 CanvasItem,CanvasItem.get_canvas_item() 方法将在服务器中返回该画布项目的 RID。
对于CanvasLayer来说, CanvasLayer.get_canvas() 方法将返回服务器中的canvas RID.
对于视口, Viewport.get_viewport_rid() 方法将返回服务器中的视口RID.
对于3D, World 资源(可在 Viewport 和 Spatial 节点中获得)包含获取 VisualServer Scenario 和 PhysicsServer Space 的函数. 这样就可以直接用服务API创建3D对象并使用它们.
对于2D, World2D 资源(可在 Viewport 和 CanvasItem 节点中获取)包含获取 VisualServer Canvas 和 Physics2DServer Space 的函数. 这样就可以直接用服务API创建2D对象并使用它们.
VisualInstance 类, 可以分别通过 VisualInstance.get_instance() 和 VisualInstance.get_base() 来获取场景 instance 和 instance base .
请尝试探索你所熟悉的节点和资源,找到获得服务器 RID 的功能。
不建议从已经有节点关联的对象中控制RID. 相反, 服务函数应始终用于创建和控制新的以及与现有的交互.
创建精灵
这是一个简单的例子, 说明如何从代码中创建一个精灵, 并使用低级的 CanvasItem API移动它.
GDScript
extends Node2D
# VisualServer expects references to be kept around.
var texture
func _ready():
# Create a canvas item, child of this node.
var ci_rid = VisualServer.canvas_item_create()
# Make this node the parent.
VisualServer.canvas_item_set_parent(ci_rid, get_canvas_item())
# Draw a texture on it.
# Remember, keep this reference.
texture = load("res://my_texture.png")
# Add it, centered.
VisualServer.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(deg2rad(45)).translated(Vector2(20, 30))
VisualServer.canvas_item_set_transform(ci_rid, xform)
服务器中的 Canvas Item API 允许您向其添加绘制图元。一旦添加,它们就不能被修改。需要清除 Item,并重新添加图元(设置变换时则不然,变换可根据需要多次进行)。
图元的清除方式为:
GDScript
VisualServer.canvas_item_clear(ci_rid)
将网格实例化到 3D 空间
3D API 与 2D API 不同,所以必须使用实例化 API。
GDScript
extends Spatial
# VisualServer expects references to be kept around.
var mesh
func _ready():
# Create a visual instance (for 3D).
var instance = VisualServer.instance_create()
# Set the scenario from the world, this ensures it
# appears with the same objects as the scene.
var scenario = get_world().scenario
VisualServer.instance_set_scenario(instance, scenario)
# Add a mesh to it.
# Remember, keep the reference.
mesh = load("res://mymesh.obj")
VisualServer.instance_set_base(instance, mesh)
# Move the mesh around.
var xform = Transform(Basis(), Vector3(20, 100, 0))
VisualServer.instance_set_transform(instance, xform)
创建 2D 刚体并使用它移动精灵
这将使用 Physics2DServer API创建一个 RigidBody2D, 并在物体移动时移动一个 CanvasItem.
GDScript
# Physics2DServer expects references to be kept around.
var body
var shape
func _body_moved(state, index):
# Created your own canvas item, use it here.
VisualServer.canvas_item_set_transform(canvas_item, state.transform)
func _ready():
# Create the body.
body = Physics2DServer.body_create()
Physics2DServer.body_set_mode(body, Physics2DServer.BODY_MODE_RIGID)
# Add a shape.
shape = Physics2DServer.rectangle_shape_create()
# Set rectangle extents.
Physics2DServer.shape_set_data(shape, Vector2(10, 10))
# Make sure to keep the shape reference!
Physics2DServer.body_add_shape(body, shape)
# Set space, so it collides in the same space as current scene.
Physics2DServer.body_set_space(body, get_world_2d().space)
# Move initial position.
Physics2DServer.body_set_state(body, Physics2DServer.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.
Physics2DServer.body_set_force_integration_callback(body, self, "_body_moved", 0)
3D版本应该非常相似, 因为2D和3D物理服务器是相同的(分别使用 RigidBody 和 PhysicsServer ).
从服务器获取数据
除非你知道自己在做什么,否则尽量不要通过调用函数向 VisualServer
、PhysicsServer
、Physics2DServer
请求任何信息。这些服务器通常会为了性能而异步运行, 调用任何返回值的函数都会使它们停滞, 并迫使它们处理任何待处理的东西, 直到函数被实际调用. 如果你每一帧都调用它们, 这将严重降低性能(而且原因不会很明显).
正因为如此, 这类服务器中的大部分API都被设计成连信息都无法请求回来, 直到这是可以保存的实际数据.