Optimierung mit Servern

Engines wie Godot bieten dank ihrer hohen Abstraktion von Techniken und Funktionen eine höhere Benutzerfreundlichkeit. Die meisten davon werden über das Szenensystem angesprochen. Die Verwendung von Nodes und Ressourcen vereinfacht die Projektorganisation und die Asset-Verwaltung in komplexen Spielen.

Wie immer, gibt es auch hier Nachteile:

  • Es ensteht eine zusätzliche Komplexitätsebene.

  • Die Leistung ist geringer als bei der Verwendung einfacher, direkter APIs.

  • Es ist nicht möglich, mehrere Threads zu verwenden, um sie zu steuern.

  • Mehr Speicher wird benötigt.

In vielen Fällen stellt dies kein wirkliches Problem dar (Godot ist sehr optimiert und die meisten Prozesse werden an Signale weitergegeben, sodass keine Abfrage erforderlich ist). Dennoch kann es manchmal zu einem Engpass führen, etwa, wenn zehntausende Instanzen in einem Frame verarbeitet werden müssen.

In solchen Situationen mag es so mancher Programmierer bereuen, eine Spiel-Engine zu verwenden und sich wünschen, nicht doch eine eigene, von Grund auf angepasste Engine benutzt zu haben.

Dennoch ist Godot darauf ausgelegt, dieses Problem zu umgehen.

Server

Eine der interessantesten Designentscheidungen für Godot ist die Tatsache, dass das gesamte Szenensystem optional ist. Während es derzeit nicht möglich ist, es heraus zu kompilieren, kann es vollständig umgangen werden.

Im Kern verwendet Godot das Konzept der Server. Es handelt sich um APIs auf sehr niedriger Ebene zur Steuerung von Rendering, Physik, Sound usw. Das Szenensystem ist darauf aufgebaut und verwendet sie direkt. Die gängigsten Server sind:

Wenn Sie nur die APIs untersuchen werden Sie feststellen, dass alle bereitgestellten Funktionen einfache Implementierungen von allem sind, was Godot Ihnen ermöglicht.

RIDs

Der Schlüssel zur Verwendung von Servern liegt im Verständnis von Resource-ID-Objekten (RID). Diese stellen Referenzen auf die jeweilige Server-Implementierung dar. Sie werden manuell zugewiesen und freigegeben. Fast jede Funktion in den Servern erfordert RIDs, um auf die tatsächliche Ressource zugreifen zu können.

Den meisten Godot-Nodes und -Ressourcen wird diese RIDs aus den Servern intern zugewiesen und sie können mit verschiedenen Funktionen abgerufen werden. Tatsächlich ist alles, was von Resource erbt, direkt in eine RID umgewandelt werden. Nicht alle Ressourcen enthalten jedoch eine RID; in solchen Fällen ist sie leer. Die Ressource kann dann per RID an Server-APIs übergeben werden.

Warnung

Ressourcen sind referenzzählerbasiert (siehe Reference), und Referenzen auf die RID einer Ressource werden nicht gezählt, wenn bestimmt wird, ob die Ressource noch in Benutzung ist. Eine Referenz auf die Ressource sollte außerhalb des Servers bestehen bleiben, da sonst sowohl die Ressource als auch ihre RID gelöscht werden.

Für Nodes stehen viele Funktionen zur Verfügung:

  • For CanvasItem, the CanvasItem.get_canvas_item() method will return the canvas item RID in the server.

  • Für CanvasLayer gibt die Methode CanvasLayer.get_canvas() die Canvas-RID auf dem Server zurück.

  • Bei Ansichtsfenstern gibt die Methode :ref:'Viewport.get_viewport_rid() <class_Viewport_method_get_viewport_rid>' die RID des Ansichtsfensters auf dem Server zurück.

  • For 3D, the World resource (obtainable in the Viewport and Spatial nodes) contains functions to get the VisualServer Scenario, and the PhysicsServer Space. This allows creating 3D objects directly with the server API and using them.

  • For 2D, the World2D resource (obtainable in the Viewport and CanvasItem nodes) contains functions to get the VisualServer Canvas, and the Physics2DServer Space. This allows creating 2D objects directly with the server API and using them.

  • The VisualInstance class, allows getting the scenario instance and instance base via the VisualInstance.get_instance() and VisualInstance.get_base() respectively.

Try exploring the nodes and resources you are familiar with and find the functions to obtain the server RIDs.

It is not advised to control RIDs from objects that already have a node associated. Instead, server functions should always be used for creating and controlling new ones and interacting with the existing ones.

Ein Sprite erstellen

This is a simple example of how to create a sprite from code and move it using the low-level CanvasItem API.

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)

The Canvas Item API in the server allows you to add draw primitives to it. Once added, they can't be modified. The Item needs to be cleared and the primitives re-added (this is not the case for setting the transform, which can be done as many times as desired).

Primitive werden folgendermaßen gelöscht:

VisualServer.canvas_item_clear(ci_rid)

Instanziieren eines Mesh in den 3D-Raum

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)

Erstellen eines 2D-RigidBody und Verschieben eines Sprites damit

This creates a RigidBody2D using the Physics2DServer API, and moves a CanvasItem when the body moves.

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

The 3D version should be very similar, as 2D and 3D physics servers are identical (using RigidBody and PhysicsServer respectively).

Daten bekommen von den Servern

Try to never request any information from VisualServer, PhysicsServer or Physics2DServer by calling functions unless you know what you are doing. These servers will often run asynchronously for performance and calling any function that returns a value will stall them and force them to process anything pending until the function is actually called. This will severely decrease performance if you call them every frame (and it won't be obvious why).

Aus diesem Grund sind die meisten APIs in solchen Servern so konzipiert, dass es nicht einmal möglich ist, Informationen zurückzufordern, bis die tatsächlichen Daten gespeichert werden können.