Work in progress

The content of this page was not yet updated for Godot 4.5 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 のようなエンジンは、高レベルの構造と機能により、使いやすさが向上しています。そのほとんどは シーン システム を介してアクセスおよび使用されます。ノードとリソースを使用すると、複雑なゲームのプロジェクトの構成とアセット管理が簡素化されます。

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

  • 複雑さがさらに増します。

  • 単純な API を直接使用するよりもパフォーマンスが低下します。

  • 複数のスレッドを使用してそれらを制御することはできません。

  • より多くのメモリが必要になります。

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

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

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

参考

Bullet Shower デモプロジェクト では、低レベルサーバーを使用すると実際にどのように機能するかを確認できます

サーバー

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

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

  • RenderingServer: グラフィックスに関連するすべてを処理します。

  • PhysicsServer3D: 3D物理演算に関連するすべてを処理します。

  • PhysicsServer2D: 2D物理演算に関連するすべてを処理します。

  • AudioServer: オーディオに関連するすべてを処理します。

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

RIDs

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

ほとんどの Godot ノードとリソースには、サーバーからのこれらの RID が内部的に含まれており、さまざまな関数を使用して取得できます。実際、Resource を継承するものはすべて、RID に直接キャストでき、サーバー API に渡すことができます。ただし、すべてのリソースに RID が含まれているわけではありません。その場合 RID は空になります。

警告

リソースは参照カウントされます (RefCounted を参照)。リソースがまだ使用中かどうかを判断するときに、リソースの RID への参照はカウントされません。リソースへの参照をサーバーの外部に保持するようにしてください。そうしないと、リソースとその RID の両方が消去されてしまいます。

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

  • CanvasItemの場合、CanvasItem.get_canvas_item() メソッドは、サーバー内のキャンバスアイテムRIDを返します。

  • CanvasLayerの場合、CanvasLayer.get_canvas() メソッドはサーバーのキャンバスRIDを返します。

  • ビューポートの場合、Viewport.get_viewport_rid() メソッドはサーバーのビューポートRIDを返します。

  • 3Dの場合 World3D リソース (Viewport および Node3D ノードで取得可能)には RenderingServer ScenarioPhysicsServer Space を取得する関数が含まれます。これにより、サーバーAPIを使用して3Dオブジェクトを直接作成し、使用することができます。

  • 2Dの場合 World2D リソース(Viewport および CanvasItem ノードで取得可能)には、 RenderingServer CanvasPhysics2DServer Space を取得する関数が含まれます 。これにより、サーバーAPIで2Dオブジェクトを直接作成して使用できます。

  • VisualInstance3D クラスは VisualInstance3D.get_instance() および VisualInstance3D.get_base() を介して、それぞれ scenario の instance および instance 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を使用すると、描画プリミティブを追加できます。追加した後は変更できません。アイテムをクリアし、プリミティブを再度追加する必要があります(これは、幾何学変換を設定する場合には当てはまりません。これは、必要な回数だけ実行できます)。

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

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

これは PhysicsServer2D 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)

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

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

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

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