Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

Оптимизация с помощью серверов

Движки, такие как Godot, обеспечивают простоту использования благодаря высокоуровневым конструкциям и функциям. Большинство из них доступны и используются через систему сцен. Использование узлов и ресурсов упрощает организацию проекта и управление активами в сложных играх.

У этого есть несколько недостатков:

  • Существует дополнительный уровень сложности.

  • Производительность ниже, чем при использовании простых API напрямую.

  • Невозможно использовать несколько потоков для управления ими.

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

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

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

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

См. также

Вы можете увидеть, как работают низкоуровневые серверы, на примере демонстрационного проекта Bullet Shower.

Серверы

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

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

  • RenderingServer: обрабатывает всё, что связано с графикой.

  • PhysicsServer3D: обрабатывает всё, что связано с 3D-физикой.

  • PhysicsServer2D: обрабатывает всё, что связано с 2D-физикой.

  • AudioServer: обрабатывает всё, что связано с аудио.

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

RІDs

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

Большинство узлов и ресурсов Godot содержат эти RID внутри серверов, и их можно получить с помощью различных функций. Фактически, всё, что наследует Resource, можно напрямую привести к RID. Однако не все ресурсы содержат RID: в таких случаях RID будет пустым. После этого ресурс можно передать серверным API-ам как RID.

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

Ресурсы подсчитываются по ссылкам (см. RefCounted), и ссылки на RID ресурса не учитываются при определении того, используется ли ресурс ещё. Обязательно сохраните ссылку на ресурс вне сервера, иначе и ресурс, и его RID будут удалены.

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

  • Для CanvasItem, метод CanvasItem.get_canvas_item() вернет RID элемента холста на сервере.

  • Для CanvasLayer, метод CanvasLayer.get_canvas() вернет RID холста на сервере.

  • Для Viewport, метод Viewport.get_viewport_rid() вернет RID области просмотра на сервере.

  • Для 2D ресурс World2D (доступный в узлах Viewport и CanvasItem) содержит функции для получения RenderingServer Canvas и PhysicsServer2D Space. Это позволяет создавать 2D-объекты напрямую с помощью серверного API и использовать их.

  • Для 3D ресурс World3D (доступный в узлах Viewport и Node3D) содержит функции для получения RenderingServer Scenario и PhysicsServer Space. Это позволяет создавать 3D-объекты напрямую с помощью серверного API и использовать их.

  • Класс VisualInstance3D позволяет получить экземпляр и базу экземпляра сценария через VisualInstance3D.get_instance() и VisualInstance3D.get_base() соответственно.

Попробуйте изучить знакомые вам узлы и ресурсы и найдите функции для получения RID-ов сервера.

Не рекомендуется управлять идентификаторами RID объектов, с которыми уже связан узел. Вместо этого следует использовать серверные функции для создания и управления новыми идентификаторами, а также для взаимодействия с существующими.

Создание спрайта

Это пример того, как создать спрайт из кода и переместить его с помощью низкоуровневого API CanvasItem.

Примечание

При создании элементов холста с помощью RenderingServer следует сбросить физическую интерполяцию на первом кадре, используя RenderingServer.canvas_item_reset_physics_interpolation(). Это обеспечивает правильную синхронизацию систем рендеринга и физики.

Если этого не сделать, элемент холста может телепортироваться при загрузке сцены, а не появиться непосредственно в нужном месте.

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 to 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)
    # Reset physics interpolation for this item.
    RenderingServer.canvas_item_reset_physics_interpolation(ci_rid)

API Canvas Item на сервере позволяет добавлять в него примитивы рисования. После добавления их нельзя изменить. Необходимо очистить элемент и заново добавить примитивы. Это не относится к установке преобразования, которую можно выполнять неограниченное количество раз.

Примитивы очищаются следующим образом:

RenderingServer.canvas_item_clear(ci_rid)

Создание экземпляра сетки в 3D пространстве

3D API-ы отличаются от 2D, поэтому необходимо использовать 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 to keep this reference.
    mesh = load("res://my_mesh.obj")
    RenderingServer.instance_set_base(instance, mesh)
    # Move the mesh around.
    var xform = Transform3D(Basis(), Vector3(2, 3, 0))
    RenderingServer.instance_set_transform(instance, xform)

Создание 2D RigidBody и перемещение спрайта с его помощью

Это создаёт RigidBody2D с помощью API PhysicsServer2D и перемещает CanvasItem при движении тела.

# PhysicsServer2D expects references to be kept around.
var body
var shape


func _body_moved(state, index):
    # Created your own canvas item; use it here.
    # `ci_rid` from the sprite example above needs to be moved to a
    # member variable (instead of within `_ready()`) so it can be referenced here.
    RenderingServer.canvas_item_set_transform(ci_rid, state.transform)


func _ready():
    # Create the body.
    body = PhysicsServer2D.body_create()
    PhysicsServer2D.body_set_mode(body, PhysicsServer2D.BODY_MODE_RIGID)
    # Add a shape.
    shape = PhysicsServer2D.rectangle_shape_create()
    # Set rectangle extents.
    PhysicsServer2D.shape_set_data(shape, Vector2(10, 10))
    # Make sure to keep the shape reference!
    PhysicsServer2D.body_add_shape(body, shape)
    # Set space, so it collides in the same space as current scene.
    PhysicsServer2D.body_set_space(body, get_world_2d().space)
    # Move initial position.
    PhysicsServer2D.body_set_state(body, PhysicsServer2D.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.
    PhysicsServer2D.body_set_force_integration_callback(body, self, "_body_moved", 0)

    # Also create a sprite using RenderingServer here.
    # See the section above on creating a sprite.
    # ...

3D-версия должна быть очень похожа, поскольку 2D- и 3D-физические серверы идентичны (используют RigidBody3D и PhysicsServer3D соответственно).

Получение данных с серверов

Старайтесь никогда не запрашивать информацию у RenderingServer, PhysicsServer2D или PhysicsServer3D с помощью вызовов функций, если вы не знаете, что делаете. Эти серверы часто работают асинхронно для повышения производительности, и вызов любой функции, возвращающей значение, остановит их и заставит обрабатывать всё ожидающее, пока функция не будет вызвана. Это значительно снизит производительность, если вы будете вызывать их каждый кадр (и причина будет неясна).

По этой причине большинство API-ов на таких серверах спроектированы таким образом, что запросить информацию обратно невозможно, пока не будут сохранены фактические данные.