Optimierung mit Servern

Engines wie Godot bieten dank ihrer hochwertigen Konstruktionen und Funktionen eine höhere Benutzerfreundlichkeit. Auf die meisten von ihnen wird über das Szenensystem zugegriffen, bzw. verwendet . Die Verwendung von Nodes und Ressourcen vereinfacht die Projektorganisation und das Asset Management in komplexen Spielen.

Es gibt natürlich immer Nachteile:

  • Es gibt eine zusätzliche Komplexitätsebene
  • Die Leistung ist geringer als bei der direkten Verwendung einfacher APIs
  • Es ist nicht möglich, mehrere Threads zu verwenden, um sie zu steuern
  • Mehr Speicher wird benötigt.

In vielen Fällen ist dies kein wirkliches Problem (Godot ist sehr optimiert und die meisten Vorgänge werden mit Signalen behandelt, sodass keine Abfrage erforderlich ist). Trotzdem kann es manchmal dazu kommen: Der Umgang mit Zehntausenden von Instanzen für etwas, das in jedem Frame verarbeitet werden muss, kann beispielsweise einen Engpass darstellen.

In solche Situationen bereuen Programmierer, dass sie eine Spiel-Engine verwenden und wünschten sich, sie könnten zu einer handwerklicheren Implementierung von Spielcode auf niedriger Ebene zurückkehren.

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 häufigsten Server sind:

  • VisualServer: kümmert sich um alles, was mit Grafiken zu tun hat.
  • PhysicsServer: kümmert sich um alles rund um die 3D-Physik.
  • Physics2DServer: kümmert sich um alles, was mit 2D-Physik zu tun hat.
  • AudioServer: kümmert sich um alles, was mit Audio zu tun hat.

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

RIDs

The key to using servers is understanding Resource ID (RID) objects. These are opaque handles to the server implementation. They are allocated and freed manually. Almost every function in the servers requires RIDs to access the actual resource.

Most Godot nodes and resources contain these RIDs from the servers internally, and they can be obtained with different functions. In fact, anything that inherits Resource can be directly casted to an RID (not all resources contain an RID, though, in such cases the RID will be empty). In fact, resources can be passed to server APIs as RIDs. Just make sure to keep references to the resources outside the server, because if the resource is erased, the internal RID is erased too.

For nodes, there are many functions available:

Just explore 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 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)

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

Primitives are cleared this way:

VisualServer.canvas_item_clear(ci_rid)

Instantiating a Mesh into 3D space

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

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

Because of this, most APIs in such servers are designed so it's not even possible to request information back, until it's actual data that can be saved.