Написание сценариев (продолжение)

Обработка

Некоторые действия в Godot инициируются обратными вызовами или виртуальными функциями, поэтому нет необходимости писать код, который работает все время.

Однако, бывает необходимо, чтобы скрипт обрабатывался в каждом кадре. Существует два типа обработки: обработка простоя и обработка физики.

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

Этот метод будет вызываться каждый раз при отрисовке кадра, поэтому он полностью зависит от количества кадров в секунду (FPS), при котором работает приложение:

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

Параметр delta содержит время (в виде числа с плавающей запятой), прошедшее в секундах после предыдущего вызова функции _process().

Этот параметр можно использовать для того, чтобы различные операции всегда занимали одинаковое количество времени, независимо от FPS игры.

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

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

Однако, функция _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.
    }
}

Он будет показывать счетчик, который будет увеличиваться на каждом кадре.

Группы

Узлы могут быть объединены в группы, по сколько угодно узлов, которые являются полезной особенностью для организации больших сцен. Есть два способа сделать это. Первый - из пользовательского интерфейса, с помощью кнопки «Группы» под панелью «Узел»:

../../_images/groups_in_nodes.png

И второй способ - из кода. Одним из примеров это пометка сцен, которые являются врагами:

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.

Предварительная загрузка может быть более удобной, т.к. происходит во время парсинга (только для 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++ и другие) могут зарегистрировать такие скрипты.
  • Только GDScript создает глобальные переменные для каждого именованного скрипта.