Work in progress

The content of this page was not yet updated for Godot 4.2 and may be outdated. If you know how to improve this page or you can confirm that it's up to date, feel free to open a pull request.

使用伺服器進行優化

像 Godot 這樣的引擎,由於其更高層次的建構和功能,提供了更多的易用性。它們中的大多數都是通過:ref:`場景系統 <doc_scene_tree>`來存取和使用的。使用節點和資源可以簡化複雜遊戲中的專案組織和素材管理。

當然, 總是有缺點的:

  • 那有一個額外複雜層。

  • 性能比直接使用簡單API要低。

  • 也可以有條件地複寫選項:

  • 需要更多的記憶體.

在很多情況下, 這並不是一個真正的問題(Godot是非常優化的, 大多數操作都是用訊號處理的, 所以不需要輪詢). 不過, 有時候還是會有這樣的情況. 例如, 對於每一影格都需要處理的東西來說, 處理數以萬計的實例可能是一個瓶頸.

這種情況會讓程式師後悔自己使用的是遊戲引擎, 希望能回到更加手動, 更加低層的遊戲程式碼實作中去.

當然,Godot的設計工作中還是可以解決這個問題.

也參考

除了這份說明文件,你可能也會想看看 Godot Demo 專案

伺服器

Godot 有許多非常有趣的設計決定,其中之一就是整個場景系統都是*可選的*。雖然目前還不能在編譯時去除,但你完全可以繞過它。

Godot 在核心中使用了“伺服器”的概念。它們是非常底層的 API,用來控制渲染、物理、聲音等。場景系統建立在它們之上,直接使用它們。最常見的伺服器有:

你只需研究它們的 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() 方法將返回伺服器中的canvas RID.

  • 對於視口, Viewport.get_viewport_rid() 方法將返回伺服器中的視口RID.

  • 對於3D, World 資源(可在 ViewportSpatial 節點中獲得)包含獲取 VisualServer ScenarioPhysicsServer Space 的函式. 這樣就可以直接用服務API建立3D物件並使用它們.

  • 對於2D, World2D 資源(可在 ViewportCanvasItem 節點中獲取)包含獲取 VisualServer CanvasPhysics2DServer Space 的函式. 這樣就可以直接用服務API建立2D物件並使用它們.

  • VisualInstance 類, 可以分別通過 VisualInstance.get_instance()VisualInstance.get_base() 來獲取場景 instanceinstance base .

請嘗試探索你所熟悉的節點和資源,找到獲得伺服器 RID 的功能。

不建議從已經有節點關聯的物件中控制RID. 相反, 服務函式應始終用於建立和控制新的以及與現有的互動.

建立內容

這是一個簡單的例子, 說明如何從程式碼中建立一個精靈, 並使用低級的 CanvasItem API移動它.

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, 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)

伺服器中的 Canvas Item 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, keep the reference.
    mesh = load("res://mymesh.obj")
    RenderingServer.instance_set_base(instance, mesh)
    # Move the mesh around.
    var xform = Transform3D(Basis(), Vector3(20, 100, 0))
    RenderingServer.instance_set_transform(instance, xform)

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

這將使用 Physics2DServer API建立一個 RigidBody2D, 並在物體移動時移動一個 CanvasItem.

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


func _body_moved(state, index):
    # Created your own canvas item, use it here.
    RenderingServer.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物理伺服器是相同的(分別使用 RigidBodyPhysicsServer ).

從伺服器獲取資料

除非你知道自己在做什麼,否則儘量**不要**通過呼叫函式向 VisualServerPhysicsServerPhysics2DServer 請求任何資訊。這些伺服器通常會為了性能而非同步運作, 呼叫任何返回值的函式都會使它們停滯, 並迫使它們處理任何待處理的東西, 直到函式被實際呼叫. 如果你每一影格都呼叫它們, 這將嚴重降低性能(而且原因不會很明顯).

正因為如此, 這類伺服器中的大部分API都被設計成連資訊都無法請求回來, 直到這是可以保存的實際資料.