Up to date

This page is up to date for Godot 4.2. If you still find outdated information, please open an issue.

Коли використовувати сцени, а коли скрипти

Ми вже висвітлювали, чим відрізняються сцени та скрипти. Скрипти визначають розширення класу движка з імперативним кодом, сцени з декларативним кодом.

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

Анонімні типи

Можна повністю визначити зміст сцен, використовуючи лише один скрипт. Це, по суті, те, що робить редактор Godot, тільки в конструкторі C++ зі своїх об'єктів.

Але вибір того, який із них використовувати, може бути дилемою. Створення примірників скриптів ідентично створенню внутрішніх класів движка, тоді як обробка сцен вимагає зміни в API:

const MyNode = preload("my_node.gd")
const MyScene = preload("my_scene.tscn")
var node = Node.new()
var my_node = MyNode.new() # Same method call.
var my_scene = MyScene.instantiate() # Different method call.
var my_inherited_scene = MyScene.instantiate(PackedScene.GEN_EDIT_STATE_MAIN) # Create scene inheriting from MyScene.

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

Названі типи

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

  1. Знати базовий тип скрипта, який мав би використовуватися.

  2. Створювати екземпляр цього базового типу.

  3. Додавати скрипт до вузла.

За допомогою зареєстрованого скрипту тип скрипту стає доступним для створення, як і інші вузли та ресурси у системі. У діалоговому вікні створення навіть є рядок пошуку для пошуку типу за назвою.

Існує дві системи реєстрації типів:

  • Користувацькі Типи

    • Лише для редактора. Назви типів недоступні під час виконання.

    • Не підтримує успадковані користувацькі типи.

    • Інструмент ініціалізації. Створює вузол зі скриптом. Більше нічого.

    • Редактор не знає про тип скрипта або його зв’язок з іншими типами механізмів чи скриптів.

    • Дозволяє користувачам визначати піктограму.

    • Працює для всіх скриптових мов, оскільки абстрактно стосується скриптових ресурсів.

    • Налаштовується за допомогою EditorPlugin.add_custom_type.

  • Класи Скриптів

    • Доступ до редактора та середовища виконання.

    • Показує відносини спадкування в повному обсязі.

    • Створює вузол зі скриптом, але також може змінювати типи або розширювати тип із редактора.

    • Редактору відомі відносини спадкування між скриптами, класами скриптів та C++ класами движка.

    • Дозволяє користувачам визначати піктограму.

    • Розробники движка повинні додавати підтримку мов вручну (як для розголошення імені, так і для доступності під час виконання).

    • Тільки Godot 3.1+.

    • Редактор сканує папки проєкту та реєструє будь-які відкриті імена для всіх мов скриптів. Кожна скриптова мова повинна реалізовувати власну підтримку для відкриття цієї інформації.

Обидві методології додають імена до діалогового вікна створення, але, зокрема, класи скриптів також дозволяють користувачам отримувати доступ до імені типу без завантаження ресурсу скрипта. Створення примірників та доступ до констант або статичних методів життєздатне з будь-якого місця.

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

З мінусів, це також означає необхідність використання, в основному, імперативного програмування.

Виконання Скрипта та PackedScene

Останній аспект, який слід враховувати при виборі сцен та скриптів, - це швидкість виконання.

Зі збільшенням розміру об’єктів необхідний розмір скрипта для їх створення та ініціалізації стає набагато більшим. Створення ієрархій вузлів це демонструє. Логіка кожного вузла може складати кілька сотень рядків коду.

Наведений нижче приклад коду створює новий Node, змінює його назву, призначає йому скрипт, встановлює майбутнього предка як власника, щоб він зберігався на диску разом з ним, і нарешті додає його в якості нащадка до вузла Main:

# main.gd
extends Node

func _init():
    var child = Node.new()
    child.name = "Child"
    child.script = preload("child.gd")
    add_child(child)
    child.owner = self

Код скрипта набагато повільніший, ніж C++ код движка. Кожна інструкція викликає API скриптів, що веде до багатьох прихованих "пошуків" логіки виконання.

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

Висновки

Зрештою, найкращим підходом є врахування наступного:

  • Якщо хтось хоче створити базовий інструмент, який буде повторно використаний у кількох різних проєктах і який, ймовірно, будуть використовувати люди всіх рівнів кваліфікації (включаючи тих, хто не називає себе "програмістами"), то, швидше за все, це повинен бути скрипт, і, навіть, із власним ім’ям/піктограмою.

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

  • Якщо ви хочете дати назву сцені, ви можете зробити це, оголосивши клас сценарію і передати йому сцену як константу. Сценарій стає, по суті, простором імен:

    # game.gd
    class_name Game # extends RefCounted, so it won't show up in the node creation dialog.
    extends RefCounted
    
    const MyScene = preload("my_scene.tscn")
    
    # main.gd
    extends Node
    func _ready():
        add_child(Game.MyScene.instantiate())