Запуск кода в редакторе

Что такое @tool?

@tool — это мощная строка кода, которая, будучи добавленной в начало скрипта, запускает его выполнение в редакторе. Вы также можете указать, какие части скрипта будут выполняться в редакторе, какие — в игре, а какие — и там, и там.

Вы можете использовать её для достижения многих вещей, но это наиболее полезно для дизайна уровней и визуальных представлений вещей которые сложно предсказать. Здесь показаны некоторые примеры использования:

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

  • Если у вас есть прыжковые платформы с различными высотами прыжка, для каждой из них вы сможете нарисовать максимальную высоту на которую игрок может подпрыгнуть, что также делает дизайн уровней лёгким.

  • Если ваш игрок не использует спрайт, но рисует его из кода, вы можете сделать эту отрисовку выполняемой в редакторе так что вы увидите вашего игрока.

Опасно

Скрипты @tool запускаются внутри редактора и позволяют получить доступ к дереву сцены, редактируемой в данный момент. Это мощная функция, которая, однако, имеет свои недостатки, поскольку редактор не предусматривает защиты от возможного несанкционированного использования скриптов @tool. Будьте крайне осторожны при работе с деревом сцены, особенно с помощью Node.queue_free, так как это может привести к сбоям, если вы освободите узел во время выполнения редактором связанной с ним логики.

Как использовать @tool

Чтобы превратить скрипт в инструмент, добавьте аннотацию @tool в начало кода.

Чтобы проверить, находитесь ли вы в данный момент в редакторе, используйте: Engine.is_editor_hint().

Например, если вы хотите выполнить какой-то код только в редакторе, используйте:

if Engine.is_editor_hint():
    # Code to execute when in editor.

С другой стороны, если вы хотите выполнить код только в игре, просто отмените тот же оператор:

if not Engine.is_editor_hint():
    # Code to execute when in game.

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

Вот как может выглядеть функция _process():

func _process(delta):
    if Engine.is_editor_hint():
        # Code to execute in editor.

    if not Engine.is_editor_hint():
        # Code to execute in game.

    # Code to execute both in editor and in game.

Важная информация

Общее правило заключается в том, что любой другой GDScript, который использует ваш скрипт инструмента, *также* должен быть инструментом. Редактор не может создавать экземпляры из файлов GDScript без @tool, что означает, что вы не можете вызывать методы или ссылаться на них переменные-члены иным образом. Однако, поскольку статические методы, константы и перечисления могут использоваться без создания экземпляра, их можно вызывать или ссылаться на них из скрипта @tool в других скриптах, не являющихся инструментами. Исключением являются static variables. Если вы попытаетесь прочитать значение статической переменной в скрипте, в котором нет @tool, он всегда вернет null, но при этом не выведет предупреждение или ошибку. Это ограничение не применяется к статическим методам, которые могут быть вызваны независимо от того, находится ли целевой скрипт в режиме инструмента.

Расширение скрипта @tool не делает расширяющий скрипт автоматически @tool. Отсутствие @tool в расширяющем скрипте отключит поведение инструмента из суперкласса. Поэтому расширяющий скрипт также должен содержать аннотацию @tool.

Изменения в редакторе являются постоянными, их невозможно отменить или повторить. Например, в следующем разделе, когда мы удалим скрипт, узел сохранит свое вращение. Будьте осторожны и не вносите нежелательных изменений. Рассмотрите возможность настройки version control, чтобы избежать потери работы в случае совершения ошибки.

Отладка

While the debugger and breakpoints cannot be used directly with tool scripts, it is possible to launch a new instance of the editor and debug from there. To do this, navigate to Debug > Customize Run Instances... and specify --editor in Main Run Args.

See Обзор инструментов отладки for more information.

Additionally, you can use print statements to display the contents of variables instead.

Попробуйте @tool

Добавьте узел Sprite2D в сцену и установите текстуру на иконку Godot. Присоедините и откройте скрипт, затем измените его следующим образом:

@tool
extends Sprite2D

func _process(delta):
    rotation += PI * delta

Сохраните скрипт и вернитесь в редактор. Вы должны увидеть как ваш объект вращается. Если вы запустите игру, он также будет вращаться.

Предупреждение

Вам может потребоваться перезагрузить редактор. Это известная ошибка, найденная во всех версиях Godot 4: GH-66381.

../../_images/rotating_in_editor.gif

Примечание

Если вы не увидели изменений, перезагрузите сцену (закройте и откройте снова).

Теперь давайте выберем когда и какой код запустится. Измените функцию _process() чтобы она выглядела вот так:

func _process(delta):
    if Engine.is_editor_hint():
        rotation += PI * delta
    else:
        rotation -= PI * delta

Сохраните скрипт. Теперь объект будет вращаться по часовой стрелке в редакторе, но если вы запустите игру он будет вращаться против часовой стрелки.

Редактирование переменных

Добавьте и экспортируйте переменную скорость в скрипт. Чтобы обновить скорость и сбросить угол поворота, добавьте сеттер set(new_speed), который выполняется при получении входных данных от инспектора. Измените _process(), чтобы включить скорость поворота.

@tool
extends Sprite2D


@export var speed = 1:
    # Update speed and reset the rotation.
    set(new_speed):
        speed = new_speed
        rotation = 0


func _process(delta):
    rotation += PI * delta * speed

Примечание

Код из других узлов не запускается в редакторе. Ваш доступ к другим узлам ограничен. Вы можете получить доступ к дереву, узлам и их свойствам по умолчанию, но не можете получить доступ к пользовательским переменным. Для этого другие узлы также должны быть запущены в редакторе.

Получение уведомлений об изменении ресурсов

Иногда требуется, чтобы ваш инструмент использовал ресурс. Однако при изменении свойства этого ресурса в редакторе метод set() вашего инструмента не будет вызван.

@tool
class_name MyTool
extends Node

@export var resource: MyResource:
    set(new_resource):
        resource = new_resource
        _on_resource_set()

# This will only be called when you create, delete, or paste a resource.
# You will not get an update when tweaking properties of it.
func _on_resource_set():
    print("My resource was set!")

Чтобы обойти эту проблему, вам сначала придется сделать свой ресурс инструментом и заставить его выдавать сигнал changed каждый раз, когда устанавливается свойство:

# Make Your Resource a tool.
@tool
class_name MyResource
extends Resource

@export var property = 1:
    set(new_setting):
        property = new_setting
        # Emit a signal when the property is changed.
        changed.emit()

Затем необходимо подключить сигнал при установке нового ресурса:

@tool
class_name MyTool
extends Node

@export var resource: MyResource:
    set(new_resource):
        resource = new_resource
        # Connect the changed signal as soon as a new resource is being added.
        if resource != null:
            resource.changed.connect(_on_resource_changed)

func _on_resource_changed():
    print("My resource just changed!")

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

@export var resource: MyResource:
    set(new_resource):
        # Disconnect the signal if the previous resource was not null.
        if resource != null:
            resource.changed.disconnect(_on_resource_changed)
        resource = new_resource
        if resource != null:
            resource.changed.connect(_on_resource_changed)

Предупреждения о конфигурации узла

Godot использует систему предупреждений о конфигурации узлов, чтобы предупреждать пользователей о неправильно настроенных узлах. Если узел настроен неправильно, рядом с его именем в доке сцен появляется жёлтый предупреждающий знак. При наведении курсора или щелчке по значку появляется предупреждающее сообщение. Вы можете использовать эту функцию в своих скриптах, чтобы избежать ошибок при настройке сцен.

При использовании предупреждений конфигурации узла, когда изменяется любое значение, которое должно повлиять на предупреждение или удалить его, необходимо вызвать update_configuration_warnings . По умолчанию предупреждение обновляется только при закрытии и повторном открытии сцены.

# Use setters to update the configuration warning automatically.
@export var title = "":
    set(p_title):
        if p_title != title:
            title = p_title
            update_configuration_warnings()

@export var description = "":
    set(p_description):
        if p_description != description:
            description = p_description
            update_configuration_warnings()


func _get_configuration_warnings():
    var warnings = []

    if title == "":
        warnings.append("Please set `title` to a non-empty value.")

    if description.length() >= 100:
        warnings.append("`description` should be less than 100 characters long.")

    # Returning an empty array means "no warning".
    return warnings

Запуск одноразовых скриптов с помощью EditorScript

Иногда вам нужно запустить код всего один раз, чтобы автоматизировать определённую задачу, которая изначально недоступна в редакторе. Вот несколько примеров:

  • Используйте как площадку для написания сценариев GDScript или C# без необходимости запуска проекта. Вывод print() отображается на панели вывода редактора.

  • Измените масштаб всех световых узлов в текущей редактируемой сцене, так как вы заметили, что ваш уровень становится слишком темным или слишком ярким после размещения источников света там, где это необходимо.

  • Замените скопированные и вставленные узлы экземплярами сцены, чтобы упростить их последующее изменение.

В Godot это доступно через расширение EditorScript в скрипте. Это позволяет запускать отдельные скрипты в редакторе без необходимости создания плагина для редактора.

Чтобы создать EditorScript, щёлкните правой кнопкой мыши по папке или пустому месту в доке файловой системы и выберите New > Script.... В диалоговом окне создания скрипта щёлкните по значку дерева, чтобы выбрать объект, который будет расширяться (или введите EditorScript непосредственно в поле слева, но учтите, что регистр учитывается):

Создание скрипта редактора в диалоговом окне создания редактора скриптов

Создание скрипта редактора в диалоговом окне создания редактора скриптов

Это автоматически выберет шаблон скрипта, подходящий для EditorScripts, с уже вставленным методом _run():

@tool
extends EditorScript

# Called when the script is executed (using File -> Run in Script Editor).
func _run():
    pass

Этот метод _run() выполняется при использовании команды File > Run или сочетания клавиш Ctrl + Shift + X, когда в редакторе скриптов открыт текущий скрипт EditorScript. Это сочетание клавиш действует только при активном редакторе скриптов.

Для работы скрипты, расширяющие EditorScript, должны быть скриптами @tool.

Примечание

EditorScripts можно запускать только из редактора скриптов Godot. Если вы используете внешний редактор, откройте скрипт в редакторе скриптов Godot, чтобы запустить его.

Опасно

В EditorScripts нет функций отмены/повтора действий, поэтому обязательно сохраните сцену перед ее запуском, если скрипт предназначен для изменения каких-либо данных.

Для доступа к узлам в текущей редактируемой сцене используйте метод EditorScript.get_scene, который возвращает корневой узел текущей редактируемой сцены. Вот пример, который рекурсивно получает все узлы в текущей редактируемой сцене и удваивает диапазон всех узлов OmniLight3D:

@tool
extends EditorScript

func _run():
    for node in get_all_children(get_scene()):
        if node is OmniLight3D:
            # Don't operate on instanced subscene children, as changes are lost
            # when reloading the scene.
            # See the "Instancing scenes" section below for a description of `owner`.
            var is_instanced_subscene_child = node != get_scene() and node.owner != get_scene()
            if not is_instanced_subscene_child:
                node.omni_range *= 2.0

# This function is recursive: it calls itself to get lower levels of child nodes as needed.
# `children_acc` is the accumulator parameter that allows this function to work.
# It should be left to its default value when you call this function directly.
func get_all_children(in_node, children_acc = []):
    children_acc.push_back(in_node)
    for child in in_node.get_children():
        children_acc = get_all_children(child, children_acc)

    return children_acc

Совет

Вы можете изменить текущую редактируемую сцену в верхней части редактора, даже если открыто окно Script. Это повлияет на возвращаемое значение EditorScript.get_scene, поэтому перед запуском скрипта убедитесь, что вы выбрали сцену, которую собираетесь перебирать.

Инстанцирование сцен

Вы можете создавать упакованные сцены обычным образом и добавлять их к сцене, открытой в данный момент в редакторе. По умолчанию узлы или сцены, добавленные с помощью Node.add_child(node), не видны в доке дерева сцены и не сохраняются на диск. Если вы хотите, чтобы узел или сцена были видны в доке дерева сцены и сохранялись на диск при сохранении сцены, вам нужно установить свойство дочернего узла owner на редактируемый корень сцены.

Если вы используете @tool:

func _ready():
    var node = Node3D.new()
    add_child(node) # Parent could be any node in the scene

    # The line below is required to make the node visible in the Scene tree dock
    # and persist changes made by the tool script to the saved scene file.
    node.owner = get_tree().edited_scene_root

Если вы используете EditorScript:

func _run():
    # `parent` could be any node in the scene.
    var parent = get_scene().get_node("Parent")
    var node = Node3D.new()
    parent.add_child(node)

    # The line below is required to make the node visible in the Scene tree dock
    # and persist changes made by the tool script to the saved scene file.
    node.owner = get_scene()

Предупреждение

Неправильное использование @tool может привести к множеству ошибок. Рекомендуется сначала написать код так, как вам нужно, и только затем добавлять аннотацию @tool в начало. Также обязательно разделяйте код, выполняемый в редакторе, от кода, выполняемого в игре. Это упростит обнаружение ошибок.