Up to date

This page is up to date for Godot 4.2. If you still find outdated information, please open an issue.

Интерфейсы Godot

Часто требуются скрипты, которые полагаются на другие объекты для работы с функциями. Этот процесс состоит из 2-х частей:

  1. Получение ссылки на объект, который предположительно имеет характеристики.

  2. Доступ к данным или логике из объекта.

В оставшейся части этого руководства описаны различные способы сделать всё это.

Получение ссылок на объект

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

var obj = node.object # Property access.
var obj = node.get_object() # Method access.

Тот же принцип применим и к объектам RefCounted. Хотя пользователи часто обращаются к Node и Resource таким образом, существуют и альтернативные меры.

Вместо доступа к свойствам или методам, можно получить доступ к ресурсам по загрузке.

# If you need an "export const var" (which doesn't exist), use a conditional
# setter for a tool script that checks if it's executing in the editor.
# The `@tool` annotation must be placed at the top of the script.
@tool

# Load resource during scene load.
var preres = preload(path)
# Load resource when program reaches statement.
var res = load(path)

# Note that users load scenes and scripts, by convention, with PascalCase
# names (like typenames), often into constants.
const MyScene = preload("my_scene.tscn") # Static load
const MyScript = preload("my_script.gd")

# This type's value varies, i.e. it is a variable, so it uses snake_case.
@export var script_type: Script

# Must configure from the editor, defaults to null.
@export var const_script: Script:
    set(value):
        if Engine.is_editor_hint():
            const_script = value

# Warn users if the value hasn't been set.
func _get_configuration_warnings():
    if not const_script:
        return ["Must initialize property 'const_script'."]

    return []

Обратите внимание на следующее:

  1. Существует множество способов, которыми язык может загружать такие ресурсы.

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

  3. Имейте в виду, что загрузка ресурса извлекает кэшированный экземпляр ресурса, обслуживаемый движком. Чтобы получить новый объект, необходимо дублировать существующую ссылку или создать её экземпляр с нуля с помощью new().

Узлы также имеют альтернативную точку доступа: Дерево cцены (SceneTree).

extends Node

# Slow.
func dynamic_lookup_with_dynamic_nodepath():
    print(get_node("Child"))

# Faster. GDScript only.
func dynamic_lookup_with_cached_nodepath():
    print($Child)

# Fastest. Doesn't break if node moves later.
# Note that `@onready` annotation is GDScript-only.
# Other languages must do...
#     var child
#     func _ready():
#         child = get_node("Child")
@onready var child = $Child
func lookup_and_cache_for_future_access():
    print(child)

# Fastest. Doesn't break if node is moved in the Scene tree dock.
# Node must be selected in the inspector as it's an exported property.
@export var child: Node
func lookup_and_cache_for_future_access():
    print(child)

# Delegate reference assignment to an external source.
# Con: need to perform a validation check.
# Pro: node makes no requirements of its external structure.
#      'prop' can come from anywhere.
var prop
func call_me_after_prop_is_initialized_by_parent():
    # Validate prop in one of three ways.

    # Fail with no notification.
    if not prop:
        return

    # Fail with an error message.
    if not prop:
        printerr("'prop' wasn't initialized")
        return

    # Fail and terminate.
    # NOTE: Scripts run from a release export template don't run `assert`s.
    assert(prop, "'prop' wasn't initialized")

# Use an autoload.
# Dangerous for typical nodes, but useful for true singleton nodes
# that manage their own data and don't interfere with other objects.
func reference_a_global_autoloaded_variable():
    print(globals)
    print(globals.prop)
    print(globals.my_getter())

Доступ к данным или логике из объекта

Скриптовый API движка Godot является "утиной типизацией". Это означает, что если скрипт выполняет операцию, Godot не проверяет, поддерживает ли он операцию по типу. Вместо этого он проверяет, реализует ли объект отдельный метод.

Например, класс CanvasItem имеет свойство visible (видимый). Все свойства, предоставляемые скриптовому API, на самом деле являются парой установщика и получателя, привязанной к имени. Если кто-то попытается получить доступ к CanvasItem.visible, тогда Godot выполнит следующие проверки в следующем порядке:

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

  • Если у сценария нет свойства, он выполняет поиск HashMap в ClassDB для «видимого» свойства для класса CanvasItem и всех его унаследованных типов. Если он найден, он вызовет связанный сеттер или получатель. Дополнительную информацию о HashMaps см. в документации о настройке данных.

  • Если не найдено, то производится явная проверка, хочет ли пользователь получить доступ к свойствам "скрипта" или "мета".

  • Если нет, то проверяется реализация _set/_get (в зависимости от типа доступа) в CanvasItem и его унаследованных типах. Эти методы могут выполнять логику, создающую впечатление, что у Объекта есть свойство. Это также относится и к методу _get_property_list.

    • Note that this happens even for non-legal symbol names, such as names starting with a digit or containing a slash.

В результате эта система с утиной типизацией может найти свойство либо в скрипте, либо в классе объекта, либо в любом классе, который наследует объект, но только для вещей, которые расширяют Object.

Godot предоставляет различные возможности для выполнения проверки времени выполнения этих обращений:

  • Доступ к свойствам утиной типизации. Это будет проверка свойств (как описано выше). Если операция не поддерживается объектом, выполнение будет остановлено.

    # All Objects have duck-typed get, set, and call wrapper methods.
    get_parent().set("visible", false)
    
    # Using a symbol accessor, rather than a string in the method call,
    # will implicitly call the `set` method which, in turn, calls the
    # setter method bound to the property through the property lookup
    # sequence.
    get_parent().visible = false
    
    # Note that if one defines a _set and _get that describe a property's
    # existence, but the property isn't recognized in any _get_property_list
    # method, then the set() and get() methods will work, but the symbol
    # access will claim it can't find the property.
    
  • Проверка метода. В случае CanvasItem.visible, можно получить доступ к методам set_visible и is_visible, как и к любому другому методу.

    var child = get_child(0)
    
    # Dynamic lookup.
    child.call("set_visible", false)
    
    # Symbol-based dynamic lookup.
    # GDScript aliases this into a 'call' method behind the scenes.
    child.set_visible(false)
    
    # Dynamic lookup, checks for method existence first.
    if child.has_method("set_visible"):
        child.set_visible(false)
    
    # Cast check, followed by dynamic lookup.
    # Useful when you make multiple "safe" calls knowing that the class
    # implements them all. No need for repeated checks.
    # Tricky if one executes a cast check for a user-defined type as it
    # forces more dependencies.
    if child is CanvasItem:
        child.set_visible(false)
        child.show_on_top = true
    
    # If one does not wish to fail these checks without notifying users,
    # one can use an assert instead. These will trigger runtime errors
    # immediately if not true.
    assert(child.has_method("set_visible"))
    assert(child.is_in_group("offer"))
    assert(child is CanvasItem)
    
    # Can also use object labels to imply an interface, i.e. assume it
    # implements certain methods.
    # There are two types, both of which only exist for Nodes: Names and
    # Groups.
    
    # Assuming...
    # A "Quest" object exists and 1) that it can "complete" or "fail" and
    # that it will have text available before and after each state...
    
    # 1. Use a name.
    var quest = $Quest
    print(quest.text)
    quest.complete() # or quest.fail()
    print(quest.text) # implied new text content
    
    # 2. Use a group.
    for a_child in get_children():
        if a_child.is_in_group("quest"):
            print(quest.text)
            quest.complete() # or quest.fail()
            print(quest.text) # implied new text content
    
    # Note that these interfaces are project-specific conventions the team
    # defines (which means documentation! But maybe worth it?).
    # Any script that conforms to the documented "interface" of the name or
    # group can fill in for it.
    
  • Передача доступа к Callable. Они могут быть полезны в случаях, когда требуется максимальная степень свободы от зависимостей. В этом случае при настройке метода полагаются на внешний контекст.

# child.gd
extends Node
var fn = null

func my_method():
    if fn:
        fn.call()

# parent.gd
extends Node

@onready var child = $Child

func _ready():
    child.fn = print_me
    child.my_method()

func print_me():
    print(name)

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