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

Обработка

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

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

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

Этот метод будет вызван каждый раз в момент прорисовки фрейма:

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

It’s important to bear in mind that the frequency with which _process() will be called depends on how many frames per second (FPS) your application is running at. This rate can vary over time and devices.

Параметр 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.
    }
}

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

Группы

Groups in Godot work like tags you might have come across in other software. A node can be added to as many groups as desired. This is a useful feature for organizing large scenes. There are two ways to add nodes to groups. The first is from the UI, using the Groups button under the Node panel:

../../_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.

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