Up to date

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

Interfaces em Godot

Muitas vezes são necessários scripts que dependem de outros objetos para funcionalidades. Este processo tem 2 partes:

  1. Adquirir uma referência ao objeto que presumivelmente possui as funcionalidades.

  2. Acessar os dados ou a lógica do objeto.

O resto deste tutorial descreve as várias formas de fazer tudo isto.

Adquirindo referências de objetos

Para todos os Objects, a forma mais básica de referenciá-los é obtendo uma referência para um objeto existente de outra instância adquirida.

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

The same principle applies for RefCounted objects. While users often access Node and Resource this way, alternative measures are available.

Em vez de acesso a propriedade ou método, pode-se obter Recursos por acesso de carregamento.

# 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 []

Observe o seguinte:

  1. Existem muitas maneiras de uma linguagem carregar tais recursos.

  2. Ao projetar como os objetos acessarão os dados, não se esqueça de que também se pode passar recursos como referências.

  3. Tenha em mente que carregar um recurso bisca a instância do recurso em cache mantida pelo motor. Para obter um novo objeto, deve-se duplicar uma referência existente ou instanciar uma do zero com new().

Nós também têm um ponto de acesso alternativo: a 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())

Acessando dados ou lógica a partir de um objeto

A API de scripting do Godot é duck-typed. Isto significa que se um script executa uma operação, o Godot não valida que ele suporta a operação por type*. Em vez disso, ele verifica se o objeto implementa o método individual.

Por exemplo, a classe CanvasItem tem uma propriedade visible. Todas as propriedades expostas à API de scripting são na verdade um par de setter e getter vinculado a um nome. Se alguém tentasse acessar CanvasItem.visible, então o Godot faria as seguintes verificações, na ordem:

  • Se o objeto tiver um script anexado, ele tentará definir a propriedade através do script. Isto deixa aberta a oportunidade para os scripts sobreporem uma propriedade definida em um objeto base sobrepondo o método setter para a propriedade.

  • Se o script não tiver a propriedade, ele realiza uma pesquisa HashMap na ClassDB pela propriedade "visible" na classe CanvasItem e todos os seus tipos herdados. Se encontrada, ela chamará o setter ou getter vinculado. Para mais informações sobre HashMaps, veja a documentação preferências de dados.

  • Se não for encontrado, ele faz uma verificação explícita para ver se o usuário deseja acessar as propriedades "script" ou "meta".

  • Caso contrário, ele verifica se há uma implementação _set/ _get (dependendo do tipo de acesso) no CanvasItem e seus tipos herdados. Estes métodos podem executar uma lógica que dá a impressão de que o objeto possui uma propriedade. Este também é o caso com o método _get_property_list.

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

Como resultado, este sistema duck-typed pode localizar uma propriedade no script, na classe do objeto ou em qualquer classe que o objeto herda, mas apenas para coisas que estendem de Object.

O Godot fornece uma variedade de opções para realizar verificações durante a execução nestes acessos:

  • A duck-typed property access. These will be property checks (as described above). If the operation isn't supported by the object, execution will halt.

    # 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.
    
  • Uma verificação de método. No caso de CanvasItem.visible, pode-se acessar os métodos, set_visible e is_visible como qualquer outro método.

    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.
    
  • Outsource the access to a Callable. These may be useful in cases where one needs the max level of freedom from dependencies. In this case, one relies on an external context to setup the method.

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

Estas estratégias contribuem para o design flexível do Godot. Entre elas, os usuários têm uma grande variedade de ferramentas para atender às suas necessidades específicas.