Up to date

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

Godot 接口

脚本常常需要依赖其他对象来获取功能。这个过程分为两部分:

  1. 获取对可能具有这些功能的对象的引用。

  2. 从对象访问数据或逻辑。

接下来,本教程将介绍多种方法来完成这些操作。

获取对象引用

对所有 Object 来说,获得引用的最基础的方法,是通过另一个已获得引用的对象。

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

同样的方法也适用于 RefCounted 对象。虽然使用者经常以这种方式访问 NodeResource,但还是有一些其他方法可以使用。

除了访问属性和方法,也可以通过加载来获得 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() 从头实例化一个对象。

节点同样也有另一种访问方式:场景树。

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())

从对象访问数据或逻辑

Godot 的脚本 API 是鸭子类型(duck-typed)的。这意味着,当脚本执行某项操作时,Godot 不会通过 类型 来验证其是否支持该操作。相反,它会检查对象是否 实现 了这个被调用的方法。

实际上,脚本 API 公开的所有属性,都是绑定到名称的 settergetter 对。以 CanvasItem 类的 visible 属性为例,如果有人试图访问 CanvasItem.visible,那么 Godot 会按照以下顺序进行检查:

  • 如果对象附加了脚本,它将尝试通过脚本设置属性。这使得脚本有机会通过覆盖属性的 setter 方法来覆盖在基础对象上定义的属性。

  • 如果脚本没有该属性, 它在 ClassDB 中对 CanvasItem 类及其所有继承的类型执行 visible 属性的哈希表查找. 如果找到, 它将调用绑定的 settergetter. 有关哈希表的更多信息, 参见 数据偏好 文档.

  • 如果没有找到, 它会进行显式检查, 以查看用户是否要访问 scriptmeta 属性.

  • 如果没有, 它将在 CanvasItem 及其继承的类型中检查 _set/_get 实现(取决于访问类型). 这些方法可以执行逻辑, 从而给人一种对象具有属性的印象. _get_property_list 方法也是如此.

    • 请注意,即使对于不合法的符号名称也会发生这种情况,例如以数字开头或包含斜杠(/)的名称。

因此,这个鸭子类型的系统可以在脚本、对象的类,或对象继承的任何类中定位属性,但仅限于扩展 Object 的对象。

Godot 提供了多种选项,来对这些访问执行运行时检查:

  • 鸭子类型属性的访问。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_visibleis_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的灵活设计. 通过它们, 用户可以使用多种工具来满足他们的特定需求.