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 demo project
Серверы
Одним из самых интересных дизайнерских решений для Godot является тот факт, что вся система сцен необязательна. Хотя в настоящее время ее невозможно скомпилировать, ее можно полностью обойти.
В основе Godot лежит концепция серверов. Это очень низкоуровневые API для управления рендерингом, физикой, звуком и т.д. Система сцены построена поверх них и использует их напрямую. Наиболее распространенными серверами являются:
RenderingServer: занимается всем, что связано с графикой.
PhysicsServer3D: занимается всем, что связано с трехмерной физикой.
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 области просмотра на сервере.
Для 3D-моделей ресурс World3D (доступный в узлах Viewport и Node3D) содержит функции для получения RenderingServer Scenario и PhysicsServer Space. Это позволяет создавать 3D-объекты непосредственно с помощью API сервера и использовать их.
Для 2D-моделей ресурс World2D (доступный в узлах Viewport и CanvasItem) содержит функции для получения RenderingServer Canvas и Physics2DServer Space. Это позволяет создавать 2D-объекты непосредственно с помощью API сервера и использовать их.
Класс VisualInstance3D позволяет получить экземпляр и базу экземпляра сценария через VisualInstance3D.get_instance() и VisualInstance3D.get_base() соответственно.
Попробуйте изучить знакомые вам узлы и ресурсы и найдите функции для получения RID-ов сервера.
Не рекомендуется управлять идентификаторами RID объектов, с которыми уже связан узел. Вместо этого следует использовать серверные функции для создания и управления новыми идентификаторами, а также для взаимодействия с существующими.
Создание спрайта
Это пример того, как создать спрайт из кода и переместить его с помощью низкоуровневого API CanvasItem.
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)
public partial class MyNode2D : Node2D
{
// RenderingServer expects references to be kept around.
private Texture2D _texture;
public override void _Ready()
{
// Create a canvas item, child of this node.
Rid ciRid = RenderingServer.CanvasItemCreate();
// Make this node the parent.
RenderingServer.CanvasItemSetParent(ciRid, GetCanvasItem());
// Draw a texture on it.
// Remember, keep this reference.
_texture = ResourceLoader.Load<Texture2D>("res://MyTexture.png");
// Add it, centered.
RenderingServer.CanvasItemAddTextureRect(ciRid, new Rect2(-_texture.GetSize() / 2, _texture.GetSize()), _texture.GetRid());
// Add the item, rotated 45 degrees and translated.
Transform2D xform = Transform2D.Identity.Rotated(Mathf.DegToRad(45)).Translated(new Vector2(20, 30));
RenderingServer.CanvasItemSetTransform(ciRid, xform);
}
}
API Canvas Item на сервере позволяет добавлять в него примитивы рисования. После добавления их нельзя изменить. Необходимо очистить элемент и заново добавить примитивы (это не относится к настройке преобразования, которую можно выполнять неограниченное количество раз).
Примитивы очищаются следующим образом:
RenderingServer.canvas_item_clear(ci_rid)
RenderingServer.CanvasItemClear(ciRid);
Создание экземпляра сетки в 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, 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)
public partial class MyNode3D : Node3D
{
// RenderingServer expects references to be kept around.
private Mesh _mesh;
public override void _Ready()
{
// Create a visual instance (for 3D).
Rid instance = RenderingServer.InstanceCreate();
// Set the scenario from the world, this ensures it
// appears with the same objects as the scene.
Rid scenario = GetWorld3D().Scenario;
RenderingServer.InstanceSetScenario(instance, scenario);
// Add a mesh to it.
// Remember, keep the reference.
_mesh = ResourceLoader.Load<Mesh>("res://MyMesh.obj");
RenderingServer.InstanceSetBase(instance, _mesh.GetRid());
// Move the mesh around.
Transform3D xform = new Transform3D(Basis.Identity, new Vector3(20, 100, 0));
RenderingServer.InstanceSetTransform(instance, xform);
}
}
Создание 2D RigidBody и перемещение спрайта с его помощью
Это создает RigidBody2D с помощью API PhysicsServer2D и перемещает 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)
using Godot;
public partial class MyNode2D : Node2D
{
private Rid _canvasItem;
private void BodyMoved(PhysicsDirectBodyState2D state, int index)
{
RenderingServer.CanvasItemSetTransform(_canvasItem, state.Transform);
}
public override void _Ready()
{
// Create the body.
var body = PhysicsServer2D.BodyCreate();
PhysicsServer2D.BodySetMode(body, PhysicsServer2D.BodyMode.Rigid);
// Add a shape.
var shape = PhysicsServer2D.RectangleShapeCreate();
// Set rectangle extents.
PhysicsServer2D.ShapeSetData(shape, new Vector2(10, 10));
// Make sure to keep the shape reference!
PhysicsServer2D.BodyAddShape(body, shape);
// Set space, so it collides in the same space as current scene.
PhysicsServer2D.BodySetSpace(body, GetWorld2D().Space);
// Move initial position.
PhysicsServer2D.BodySetState(body, PhysicsServer2D.BodyState.Transform, new Transform2D(0, new 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.BodySetForceIntegrationCallback(body, new Callable(this, MethodName.BodyMoved), 0);
}
}
3D-версия должна быть очень похожа, поскольку 2D- и 3D-физические серверы идентичны (используют RigidBody3D и PhysicsServer3D соответственно).
Получение данных с серверов
Старайтесь НИКОГДА не запрашивать информацию у RenderingServer, PhysicsServer2D или PhysicsServer3D, вызывая функции, если вы не знаете, что делаете. Эти серверы часто работают асинхронно для повышения производительности, и вызов любой функции, возвращающей значение, остановит их и заставит обрабатывать всё, что ожидает, до тех пор, пока функция не будет вызвана. Это значительно снизит производительность, если вы будете вызывать их в каждом кадре (и причина будет неясна).
По этой причине большинство API-ов на таких серверах спроектированы таким образом, что запросить информацию обратно невозможно, пока не будут сохранены фактические данные.