Оглавление
Проблема
Cамая распространенная проблема, с которой сталкиваются пользователи Godot в каналах поддержки: недопустимая ссылка на узел. Чаще всего это выглядит как следующее сообщение об ошибке:
Invalid get index ‘position’ (on base: ‘null instance’).
Решение
Именно последняя часть, «null instance» (нулевой экземпляр), является источником этой проблемы и основным источником путаницы для новичков в Godot.
Путь к избежанию этой проблемы — понимание концепции путей к узлам.
Понимание путей к узлам
Дерево сцены состоит из узлов, которые соединены друг с другом в отношениях родитель-потомок. Путь к узлу — это путь, который нужно пройти от одного узла к другому, двигаясь через это дерево.
В качестве примера давайте рассмотрим простую сцену «Player»:
Скрипт для этой сцены находится на узле Player. Если сценарию нужно вызвать play() на узле AnimatedSprite, ему нужна ссылка на этот узел:
get_node("AnimatedSprite").play()
Аргумент функции get_node() — это строка, представляющая путь к нужному узлу. В данном случае это дочерний узел для узла, на котором находится сценарий. Если путь, который вы передаете, недопустим, вы получите ненавистное сообщение об ошибке «null instance» (а также «Node not found»).
Получение ссылки на узел с помощью get_node() — такая распространенная ситуация, что в GDScript есть сокращение для этого:
$AnimatedSprite.play()
get_node() возвращает ссылку на нужный узел.
Давайте рассмотрим более сложное дерево сцены:
Если сценарий на узле Main должен получить доступ к ScoreLabel, он может сделать это с использованием следующего пути:
get_node("HUD/ScoreLabel").text = "0"
# or using the shortcut:
$HUD/ScoreLabel.text = "0"
При использовании обозначения $, редактор Godot автоматически предложит вам варианты завершения пути. Вы также можете щелкнуть правой кнопкой мыши по узлу во вкладке Scene и выбрать «Copy Node Path» («Копировать путь к узлу»).
Что если узел, к которому вы хотите получить доступ, находится выше в дереве? Вы можете использовать get_parent() или «..» для ссылки на родительский узел. В приведенном выше примере дерева, чтобы получить узел Player из ScoreLabel:
get_node("../../Player")
Давайте разберем его. Путь «../../Player» означает «получить узел, который находится на один уровень выше (HUD), затем еще один уровень выше (Main), затем его дочерний узел Player».
Совет Это звучит знакомо? Пути к узлам работают точно так же, как пути к каталогам в вашей операционной системе. Символ / указывает на отношение родитель-потомок, а .. означает «на один уровень вверх».
Относительные vs абсолютные пути
Все приведенные выше примеры используют относительные пути, что означает, что они начинаются с текущего узла и следуют по пути к месту назначения. Пути к узлам также могут быть абсолютными, начиная от корневого узла сцены.
Например, абсолютный путь к узлу Player таков:
get_node("/root/Main/Player")
/root, который также можно получить с помощью get_tree().root, не является корневым узлом вашей сцены. Это узел Viewport, который всегда присутствует по умолчанию в дереве сцены.
Предупреждение
Хотя приведенные выше примеры работают нормально, есть несколько вещей, о которых стоит знать, и которые могут вызвать проблемы позже. Представьте следующую ситуацию: у узла Player есть свойство health, которое вы хотите отобразить в узле HealthBar где-то в вашем пользовательском интерфейсе. Вы можете написать что-то подобное в сценарии игрока:
func take_damage(amount):
health -= amount
get_node("../Main/UI/HealthBar").text = str(health)
Хотя это может работать нормально изначально, это хрупкое решение, что означает, что оно легко может сломаться. Существует две основные проблемы с такого рода устройством:
- Вы не можете провести независимое тестирование сцены игрока. Если вы запустите сцену игрока самостоятельно или в тестовой сцене без интерфейса пользователя, строка get_node() вызовет сбой.
- Вы не можете изменить свой пользовательский интерфейс. Если вы решите изменить структуру или переработать свой интерфейс, путь больше не будет действителен и вам придется его изменить.
По этой причине старайтесь избегать использования узловых путей, которые поднимаются вверх по дереву сцены. В вышеуказанной ситуации, если бы вместо этого игрок отправлял сигнал при изменении здоровья, пользовательский интерфейс мог бы прослушивать этот сигнал для его обновления. Затем вы могли бы переставлять и отделять узлы без опасения сломать вашу игру.