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

Обработка

Некоторые действия в 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 раз в секунду по умолчанию. Вы можете изменить интервал в настройках проекта, в разделе "Physics -> Common -> Physics 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 работают как теги, с которыми вы могли столкнуться в других программах. Узел может быть добавлен в любое количество групп по желанию. Это полезная функция для организации больших сцен. Существует два способа добавления узлов в группы. Первый - из пользовательского интерфейса, с помощью кнопки "Groups" (Группы) под панелью "Node" (Узел):

../../_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, за которым следует имя класса. Вы можете добавить запятую и необязательный путь к изображению PNG или SVG для использования в качестве иконки (минимум 16×16, рекомендуется 32×32). Затем вы найдете свой новый тип в диалоге создания узла или ресурса. Обратите внимание, что значок появится только после перезапуска редактора.

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