サーバーを使用した最適化

Godotのようなエンジンは、高レベルの構造と機能により、使いやすさが向上しています。それらのほとんどは、Scene System を介してアクセスおよび使用されます。ノードとリソースを使用すると、複雑なゲームのプロジェクト編成と資産管理が簡素化されます。

もちろん、常に欠点もあります:

  • (目に見える以上の)さらに複雑な層があります
  • 単純なAPIを直接使用するよりもパフォーマンスが低くなります
  • 複数のスレッドを使用してそれらを制御することはできません
  • より多くのメモリが必要になります。

多くの場合、これは実際には問題ではありません(Godotは非常に最適化されており、ほとんどの操作はシグナルで処理されるため、ポーリングは不要です)。それでも、時にはそれが必要です。たとえば、フレームごとに処理する必要がある何かのインスタンスを数万件処理することがボトルネックになる可能性があります。

このような状況では、プログラマはゲームエンジンを使用していることを後悔し、より手作りの低レベルのゲームコード実装に戻ることを望みます。

それでも、Godotはこの問題を回避するように設計されています。

サーバー

Godotの最も興味深い設計上の決定の1つは、シーンシステム全体が オプション であるという事実です。現在、その様にコンパイルすることはできませんが、完全にバイパスすることができます。

コアでは、Godotはサーバーの概念を使用します。これらは、レンダリング、物理演算、サウンドなどを制御するための非常に低レベルのAPIです。シーンシステムはそれらの上に構築され、それらを直接使用します。最も一般的なサーバーは次のとおりです:

  • VisualServer: グラフィックに関連するすべてを処理します。
  • PhysicsServer: 3D物理演算に関連するすべてを処理します。
  • Physics2DServer: 2D物理演算に関連するすべてを処理します。
  • AudioServer: オーディオに関連するすべてを処理します。

APIを調べるだけで、提供されるすべての機能がGodotで実行可能なすべての低レベル実装であることに気付くでしょう。

RIDs

サーバーを使用する鍵は、リソースID(RID)オブジェクトを理解することです。これらは、サーバー実装への不透明なハンドルです。それらは手動で割り当てられ、解放されます。サーバーのほぼすべての機能には、実際のリソースにアクセスするためのRIDが必要です。

ほとんどのGodotのノードとリソースには、サーバーからこれらのRIDが内部的に含まれており、さまざまな機能で取得できます。実際、Resource を継承するものはすべてRIDに直接キャストできます(ただし、すべてのリソースにRIDが含まれるわけではありませんが、そのような場合、RIDは空になります)。実際、リソースはRIDとしてサーバーAPIに渡すことができます。リソースが消去されると、内部RIDも消去されるため、サーバー外部のリソースへの参照を必ず保持してください。

ノードには、次の多くの機能が用意されています:

  • CanvasItemの場合、CanvasItem.get_canvas_item() メソッドは、サーバー内のキャンバスアイテムRIDを返します。
  • CanvasLayerの場合、CanvasLayer.get_canvas() メソッドはサーバーのキャンバスRIDを返します。
  • ビューポートの場合、Viewport.get_viewport_rid() メソッドはサーバーのビューポートRIDを返します。
  • 3Dの場合、World リソース(Viewport および Spatial ノードで取得可能)には VisualServer ScenarioPhysicsServer Space を取得する関数が含まれます。これにより、サーバーAPIを使用して3Dオブジェクトを直接作成し、使用することができます。
  • 2Dの場合、World2D リソース(Viewport および CanvasItem ノードで取得可能)には、VisualServer CanvasPhysics2DServer Space を取得する関数が含まれます 。これにより、サーバーAPIで2Dオブジェクトを直接作成して使用できます。
  • VisualInstance クラスは、VisualInstance.get_instance() および VisualInstance.get_base() を介して、それぞれscenarioの instance および instance base を取得できます。

使い慣れたノードとリソースを調べて、サーバー*RID*を取得する関数を見つけてください。

すでにノードが関連付けられているオブジェクトからRIDを制御することはお勧めしません。代わりに、サーバー関数を常に使用して、新しい関数を作成および制御し、既存の関数と対話する必要があります。

スプライトの作成

これは、コードからスプライトを作成し、低レベルの CanvasItem APIを使用して移動する方法の簡単な例です。

extends Node2D


# VisualServer expects references to be kept around.
var sprite


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 sprite on it.
    # Remember, keep this reference.
    sprite = load("res://mysprite.png")
    # Add it, centered.
    VisualServer.canvas_item_add_texture_rect(ci_rid, Rect2(sprite.get_size() / 2, sprite.get_size()), sprite)
    # 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を使用すると、描画プリミティブを追加できます。追加した後は変更できません。アイテムをクリアし、プリミティブを再度追加する必要があります(これは、幾何学変換を設定する場合には当てはまりません。これは、必要な回数だけ実行できます)。

プリミティブは次の方法でクリアされます:

VisualServer.canvas_item_clear(ci_rid)

メッシュを3D空間にインスタンス化する

The 3D APIs are different from the 2D ones, so the instantiation API must be used.

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 RigidBodyの作成とスプライトの移動

これは 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.
    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 = RectangleShape2D.new()
    shape.extents = 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)

2Dおよび3D物理サーバーは同一であるため(それぞれ RigidBody および PhysicsServer を使用)、3Dバージョンも非常に似ているはずです。

サーバーからデータを取得する

自分が何をしているのか理解していないのなら、絶対に関数を呼び出して VisualServerPhysicsServer、またはPhysics2DServer に情報を要求しないでください。これらのサーバーは、多くの場合、パフォーマンスのために非同期に実行され、値を返す関数を呼び出すと、関数が実際に呼び出されるまで、それらの関数が停止し、保留中の処理を強制します。これをフレームごとに呼び出すと、パフォーマンスが大幅に低下します(理由は明らかではありません)。

このため、このようなサーバーのほとんどのAPIは、保存可能な実際のデータができるまで、情報を要求することさえできないように設計されています。