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

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

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

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

Можна повністю визначити зміст сцен, використовуючи лише один скрипт. Це, по суті, те, що робить редактор 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.instance() # Different method call
var my_inherited_scene = MyScene.instance(PackedScene.GEN_EDIT_STATE_MAIN) # Create scene inheriting from MyScene
using System;
using Godot;

public class Game : Node
{
    public readonly Script MyNodeScr = (Script)ResourceLoader.Load("MyNode.cs");
    public readonly PackedScene MySceneScn = (PackedScene)ResourceLoader.Load("MyScene.tscn");
    public Node ANode;
    public Node MyNode;
    public Node MyScene;
    public Node MyInheritedScene;

    public Game()
    {
        ANode = new Node();
        MyNode = new MyNode(); // Same syntax
        MyScene = MySceneScn.Instance(); // Different. Instantiated from a PackedScene
        MyInheritedScene = MySceneScn.Instance(PackedScene.GenEditState.Main); // Create scene inheriting from MyScene
    }
}

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

Названі типи

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

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

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

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

    1. (Метод перетягування)

      1. Знайти скрипт на вкладці Файлова система.

      2. Перетягнути скрипт на вузол на вкладці Сцена.

    2. (Метод властивості)

      1. Прогорнути вниз Інспектора, щоб знайти властивість script і вибрати її.

      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")
    child.owner = self
    add_child(child)
using System;
using Godot;

public class Main : Resource
{
    public Node Child { get; set; }

    public Main()
    {
        Child = new Node();
        Child.Name = "Child";
        Child.Script = ResourceLoader.Load<Script>("child.gd");
        Child.Owner = this;
        AddChild(Child);
    }
}

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

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

Висновки

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

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

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

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

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