Оптимізація за допомогою серверів

Такі рушії, як Godot, забезпечують підвищену простоту використання завдяки їх конструкціям і функціям високого рівня. Більшість із них доступні та використовуються через Систему Сцен. Використання вузлів і ресурсів спрощує організацію проекту та управління активами в складних іграх.

Звичайно, завжди є недоліки:

  • Є додатковий рівень складності

  • Продуктивність нижча, ніж безпосереднє використання простих API

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

  • Потрібно більше пам'яті.

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

Така ситуація змушує програмістів шкодувати , що вони використовують ігровий рушій, і бажати повернутися до більш ручної, низькорівневої реалізації ігрового коду.

Тим не менш, Godot спроектований так, щоб вирішити цю проблему.

Сервери

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

По суті, Godot використовує концепцію Серверів. Це API дуже низького рівня для керування рендерингом, фізикою, звуком тощо. Система сцен побудована на їх основі і використовує їх безпосередньо. Найпоширенішими серверами є:

  • VisualServer: обробляє все, що стосується графіки.

  • PhysicsServer: обробляє все, що стосується 3D фізики.

  • Physics2DServer: обробляє все, що стосується фізики 2D.

  • AudioServer: обробляє все, що стосується аудіо.

Дослідіть їхні API, і ви зрозумієте, що всі надані функції є низькорівневими реалізаціями всього, що дозволяє Godot.

RID-и

Ключем до використання серверів є розуміння ідентифікатора ресурсу (RID) об'єктів. Це непрозорі дескриптори реалізації сервера. Вони виділяються та звільняються вручну. Майже кожна функція на серверах вимагає RID для доступу до фактичного ресурсу.

Більшість вузлів і ресурсів Godot містять ці RID-и із внутрішніх серверів, і їх можна отримати за допомогою різних функцій. Насправді все, що успадковує Ресурс, може бути безпосередньо передано до RID. Однак не всі ресурси містять RID: у таких випадках RID буде порожнім. Тоді ресурс можна передати API сервера, як RID.

Попередження

Ресурси підраховуються за посиланнями (див. Reference), а посилання на RID ресурсу не враховуються, коли визначається, чи ресурс все ще використовується. Не забудьте зберегти посилання на ресурс за межами сервера, інакше він і його RID будуть стерті.

Для вузлів доступно багато функцій:

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

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

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

  • Для 3D, ресурс World (доступний у вузлах Viewport і Spatial) містить функції для отримання VisualServer Scenario та PhysicsServer Space. Це дозволяє створювати 3D-об’єкти безпосередньо за допомогою API сервера та використовувати їх.

  • Для 2D ресурс World2D (доступний у вузлах Viewport і CanvasItem) містить функції для отримання VisualServer Canvas і Physics2DServer Space. Це дозволяє створювати 2D-об’єкти безпосередньо за допомогою API сервера та використовувати їх.

  • Клас VisualInstance дозволяє отримати екземпляр сценарію та базу екземплярів через VisualInstance.get_instance() та VisualInstance.get_base() відповідно.

Спробуйте вивчити вузли та ресурси, з якими ви знайомі, і знайдіть функції для отримання RID сервера .

Не рекомендується керувати RID від об’єктів, які вже мають пов’язаний вузол. Натомість функції сервера завжди повинні використовуватися для створення та керування новими та взаємодії з наявними.

Створення спрайта

Це простий приклад того, як створити спрайт із коду та перемістити його за допомогою низькорівневого API CanvasItem.

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)

API Canvas Item (Елемента Полотна) на сервері дозволяє додавати до нього намальовані примітиви. Після додавання їх не можна змінити. Елемент потрібно очистити, а примітиви знову додати (це не стосується налаштування перетворення, яке можна виконувати скільки завгодно разів).

Примітиви очищаються таким чином:

VisualServer.canvas_item_clear(ci_rid)

Створення екземпляра Mesh у 3D просторі

3D API відрізняються від 2D, тому необхідно використовувати API для створення екземплярів.

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)

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

Тут створений RigidBody2D за допомогою API Physics2DServer, і його рух призводить до руху 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.
    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)

3D версія подібна на 2D, а 3D фізика серверів ідентична (використовує RigidBody та PhysicsServer відповідно).

Отримання даних із серверів

Намагайтеся ніколи не запитувати ніякої інформації від VisualServer, PhysicsServer, чи Physics2DServer через виклик функцій, якщо ви не знаєте, що робите. Ці сервери часто для продуктивності працюють асинхронно, і виклик будь-якої функції, яка повертає значення, зупинить їх і змусить обробляти все, на що очікує функція, доки вона буде актуальна. Це значно знизить продуктивність, якщо ви будете викликати такі функції кожен кадр (і не буде очевидно, чому).

Через це більшість API на таких серверах розроблені таким чином, що навіть неможливо надіслати запит на повернення інформації, поки це не будуть фактичні дані, які можна зберегти.