Написання скриптів (продовження)

Обробка

Кілька дій у Godot викликаються зворотними викликами, або віртуальними функціями, тому не потрібно писати код, який працює весь час.

Однак, як правило, потрібен скрипт для обробки на кожному кадрі. Існує два типи обробки: обробка в режимі очікування та обробка фізики.

Обробка в режимі очікування активується, коли в скрипті знайдено метод Node._process(). Його можна вимкнути та увімкнути за допомогою функції Node.set_process().

Цей метод викликатиметься кожного разу, коли малюється кадр:

func _process(delta):
    # Do something...
    pass
public override void _Process(float delta)
{
    // Do something...
}

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

Для управління цією властивістю є параметр delta, який містить час у секундах, що минув від попереднього виклику _process().

Цей параметр може бути використаний для того, щоб дії завжди займали однаковий час, незалежно від FPS гри.

Наприклад, рух часто множать на дельту часу, щоб зробити швидкість руху постійною і незалежною від частоти кадрів.

Обробка фізики з _physics_process() є аналогічною, але її слід використовувати для процесів, які повинні відбуватися перед кожним етапом фізики, наприклад, керування персонажем. Вона завжди працює перед початком фізичного кроку і викликається через фіксований інтервал часу: 60 разів за секунду за замовчуванням. Інтервал можна змінити в Параметрах проекту, у розділі Фізика -> Загальні -> Фізика Fps.

Функція _process(), однак, не синхронізована з фізикою. Частота кадрів не є постійною і залежить від апаратної та ігрової оптимізації. В одно-поточних іграх її виконання відбувається після кроку фізики .

Простий спосіб побачити функцію _process() в роботі - це створити сцену з одного вузла Label із наступним скриптом:

extends Label

var accum = 0

func _process(delta):
    accum += delta
    text = str(accum) # 'text' is a built-in label property.
public class CustomLabel : Label
{
    private float _accum;

    public override void _Process(float delta)
    {
        _accum += delta;
        Text = _accum.ToString(); // 'Text' is a built-in label property.
    }
}

Який покаже лічильник, що збільшується кожен кадр.

Групи

Групи в Godot працюють як теги, які, можливо, ви зустрічали в інших програмах. Вузол можна додати до бажаної кількості груп. Це корисна функція для організації великих сцен. Є два способи додавання вузлів до груп. Перший - з користувацького інтерфейсу, використовуючи кнопку "Групи" на панелі "Вузол":

../../_images/groups_in_nodes.png

А другий шлях - з коду. Наступний скрипт додав би поточний вузол до групи enemies, як тільки вона з'явиться у дереві сцени.

func _ready():
    add_to_group("enemies")
public override void _Ready()
{
    base._Ready();

    AddToGroup("enemies");
}

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

func _on_discovered(): # This is a purely illustrative function.
    get_tree().call_group("enemies", "player_was_discovered")
public void _OnDiscovered() // This is a purely illustrative function.
{
    GetTree().CallGroup("enemies", "player_was_discovered");
}

Вищевказаний код викликає функцію player_was_discovered для кожного члена групи enemies.

Також можна отримати повний список вузлів enemies, викликавши SceneTree.get_nodes_in_group():

var enemies = get_tree().get_nodes_in_group("enemies")
var enemies = GetTree().GetNodesInGroup("enemies");

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

Сповіщення

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

func _notification(what):
    match what:
        NOTIFICATION_READY:
            print("This is the same as overriding _ready()...")
        NOTIFICATION_PROCESS:
            print("This is the same as overriding _process()...")
public override void _Notification(int what)
{
    base._Notification(what);

    switch (what)
    {
        case NotificationReady:
            GD.Print("This is the same as overriding _Ready()...");
            break;
        case NotificationProcess:
            var delta = GetProcessDeltaTime();
            GD.Print("This is the same as overriding _Process()...");
            break;
    }
}

Документація кожного класу у Class Reference показує сповіщення, які він може отримувати. Однак у більшості випадків GDScript надає більш прості функції для перезапису.

Переобумовленої функції

Функції, які описані нижче, можуть бути застосовані до вузлів:

func _enter_tree():
    # When the node enters the Scene Tree, it becomes active
    # and  this function is called. Children nodes have not entered
    # the active scene yet. In general, it's better to use _ready()
    # for most cases.
    pass

func _ready():
    # This function is called after _enter_tree, but it ensures
    # that all children nodes have also entered the Scene Tree,
    # and became active.
    pass

func _exit_tree():
    # When the node exits the Scene Tree, this function is called.
    # Children nodes have all exited the Scene Tree at this point
    # and all became inactive.
    pass

func _process(delta):
    # This function is called every frame.
    pass

func _physics_process(delta):
    # This is called every physics frame.
    pass
public override void _EnterTree()
{
    // When the node enters the Scene Tree, it becomes active
    // and  this function is called. Children nodes have not entered
    // the active scene yet. In general, it's better to use _ready()
    // for most cases.
    base._EnterTree();
}

public override void _Ready()
{
    // This function is called after _enter_tree, but it ensures
    // that all children nodes have also entered the Scene Tree,
    // and became active.
    base._Ready();
}

public override void _ExitTree()
{
    // When the node exits the Scene Tree, this function is called.
    // Children nodes have all exited the Scene Tree at this point
    // and all became inactive.
    base._ExitTree();
}

public override void _Process(float delta)
{
    // This function is called every frame.
    base._Process(delta);
}

public override void _PhysicsProcess(float delta)
{
    // This is called every physics frame.
    base._PhysicsProcess(delta);
}

Як було сказано раніше, краще використовувати ці функції замість системи сповіщень.

Створення вузлів

Щоб створити вузол з коду, викличте метод .new(), як і для будь-якого іншого типу даних на основі класу. Наприклад:

var s
func _ready():
    s = Sprite.new() # Create a new sprite!
    add_child(s) # Add it as a child of this node.
private Sprite _sprite;

public override void _Ready()
{
    base._Ready();

    _sprite = new Sprite(); // Create a new sprite!
    AddChild(_sprite); // Add it as a child of this node.
}

Щоб видалити вузол, чи то всередині, чи поза сценою, необхідно використовувати free():

func _someaction():
    s.free() # Immediately removes the node from the scene and frees it.
public void _SomeAction()
{
    _sprite.Free(); // Immediately removes the node from the scene and frees it.
}

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

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

Найбезпечніший спосіб видалити вузол - використати Node.queue_free(). Він безпечно стирає вузол під час роботи в режимі очікування.

func _someaction():
    s.queue_free() # Removes the node from the scene and frees it when it becomes safe to do so.
public void _SomeAction()
{
    _sprite.QueueFree(); // Removes the node from the scene and frees it when it becomes safe to do so.
}

Інстансування сцени

Інстансування сцени з коду робиться в два етапи. Перший - це завантаження сцени з вашого жорсткого диска:

var scene = load("res://myscene.tscn") # Will load when the script is instanced.
var scene = GD.Load<PackedScene>("res://myscene.tscn"); // Will load when the script is instanced.

Попереднє завантаження може бути зручнішим, так як відбувається під час розбору (parse time) (лише для GDScript):

var scene = preload("res://myscene.tscn") # Will load when parsing the script.

Але scene це ще не вузол. Він упакований у спеціальний ресурс під назвою PackedScene. Щоб створити справжній вузол, необхідно викликати функцію PackedScene.instance(). Це поверне дерево вузлів, які можна додати до активної сцени:

var node = scene.instance()
add_child(node)
var node = scene.Instance();
AddChild(node);

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

Реєстрація скриптів в якості класів

У Godot є функція "Script Class" для реєстрації окремих скриптів у Редакторі. За замовчуванням ви можете отримати доступ лише до безіменних скриптів, завантаживши файл безпосередньо.

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

extends Node

# Declare the class name here
class_name ScriptName, "res://path/to/optional/icon.svg"

func _ready():
    var this = ScriptName           # reference to the script
    var cppNode = MyCppNode.new()   # new instance of a class named MyCppNode

    cppNode.queue_free()
../../_images/script_class_nativescript_example.png

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

В Godot 3.1:

  • Лише GDScript і NativeScript, тобто C ++ та інші мови, що працюють на GDNative, можуть реєструвати скрипти.
  • Тільки GDScript створює глобальні змінні для кожного названого скрипта.