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, обеспечивают повышенную простоту использования благодаря своим высокоуровневым конструкциям и функциям. Большинство из них доступны и используются через Scene System. Использование узлов и ресурсов упрощает организацию проекта и управление активами в сложных играх.

Недостатки, конечно, всегда есть:

  • There is an extra layer of complexity.

  • Performance is lower than when using simple APIs directly.

  • It is not possible to use multiple threads to control them.

  • Необходимо больше памяти.

Во многих случаях это не является проблемой (Godot очень оптимизирован, и большинство операций выполняется с помощью сигналов, поэтому опрос не требуется). Тем не менее, иногда это может быть проблемой. Например, работа с десятками тысяч экземпляров для чего-то, что должно обрабатываться каждый кадр, может стать узким местом.

Подобная ситуация заставляет программистов жалеть о том, что они используют игровой движок, и желать вернуться к более ручной, низкоуровневой реализации игрового кода.

Тем не менее, Godot разработан для того, чтобы обойти эту проблему.

См.также

You can see how using low-level servers works in action using the Bullet Shower demo project

Servers

Одним из самых интересных дизайнерских решений для Godot является тот факт, что вся система сцен необязательна. Хотя в настоящее время ее невозможно скомпилировать, ее можно полностью обойти.

В основе Godot лежит концепция серверов. Это очень низкоуровневые API для управления рендерингом, физикой, звуком и т.д. Система сцены построена поверх них и использует их напрямую. Наиболее распространенными серверами являются:

Изучите их API, и вы поймете, что все предоставляемые функции являются низкоуровневыми реализациями всего того, что Godot позволяет вам делать.

RІDs

Ключом к использованию серверов является понимание объектов Resource ID (RID). Это непрозрачные ручки реализации сервера. Они выделяются и освобождаются вручную. Почти каждая функция в серверах требует RID для доступа к реальному ресурсу.

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. The resource can then be passed to server APIs as an RID.

Предупреждение

Resources are reference-counted (see RefCounted), and references to a resource's RID are not counted when determining whether the resource is still in use. Make sure to keep a reference to the resource outside the server, or else both it and its RID will be erased.

Для узлов доступны множество функций:

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.

Creating a sprite

This is an example of how to create a sprite from code and move it using the low-level 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)

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:

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

Creating a 2D RigidBody and moving a sprite with it

This creates a RigidBody2D using the PhysicsServer2D 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.
    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)

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

Getting data from the servers

Try to never request any information from RenderingServer, PhysicsServer2D or PhysicsServer3D 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.