Up to date

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

Interfaces en Godot

Frecuentemente se necesitan scripts que dependen de otros objetos para su funcionamiento. Hay 2 partes en este proceso:

  1. Obtener una referencia al objeto que presumiblemente tenga las características.

  2. Accediendo a los datos o lógica desde el objeto.

El resto de este tutorial describe las distintas formas de hacer esto.

Obteniendo referencias a objetos

Para todos los Object, el modo más básico de referenciarlos es obtener una referencia de un objeto existente desde otra instancia.

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

El mismo principio aplica para objetos RefCounted. Mientras que los usuarios acceden normalmente a Node y Resource de este modo, hay modos alternativos disponibles.

En lugar de acceso mediante propiedades o métodos, se pueden obtener recursos por acceso de carga.

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

Nota lo siguiente:

  1. Existen muchas formas en las que un lenguaje puede cargar tales recursos.

  2. Al diseñar cómo los objetos accederán a los datos, no olvides que también se pueden pasar recursos como referencias.

  3. Ten en mente que al cargar un recurso se obtiene la versión cacheada de la instancia del recurso mantenida por el motor. Para obtener uno nuevo, se debe duplicate (duplicar) una referencia existente o instanciar una nueva con new().

Los nodos también tienen un punto de acceso alternativo: el 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())

Acceso a datos o lógica desde un objeto

El API de scripting de Godot es tipado dinámico (duck-typed). Esto quiere decir que si un script ejecuta una operación, Godot no valida si soporta la operación por tipo. En su lugar chequea que el objeto implemente el método individual.

Por ejemplo, la clase CanvasItem tiene la propiedad visible. Todas las propiedades expuestas al API de scripting son en efecto un par setter y getter vinculados a un nombre. Si uno intenta acceder a CanvasItem.visible, entonces Godot hará los siguientes chequeos en orden:

  • Si el objeto tiene un script adjunto, intentará establecer la propiedad a través del script. Esto deja abierta la oportunidad de que los scripts anulen una propiedad definida en un objeto base al anular el método setter de la propiedad.

  • Si el script no tiene la propiedad, realiza una búsqueda en el HashMap de la ClassDB en busca de la propiedad "visible", contra la clase CanvasItem y todos sus tipos heredados. Si la encuentra, este llamará al setter o getter vinculado. Para más información acerca de HashMaps, ver data preferences.

  • Si no se encuentra, realiza una comprobación explícita para ver si el usuario desea acceder a las propiedades "script" o "meta".

  • Si no es así, busca una implementación _set/_get (dependiendo del tipo de acceso) en el CanvasItem y sus tipos heredados. Estos métodos pueden ejecutar una lógica que da la impresión de que el objeto tiene una propiedad. Este es también el caso del método _get_property_list.

    • Ten en cuenta que esto sucede incluso con nombres de símbolos no permitidos, como nombres que empiezan por un número o que contienen una barra.

Como resultado, este sistema puede localizar una propiedad ya sea en el script, en la clase del objeto o en cualquier clase que el objeto herede, pero sólo para cosas que se extiendan en Object.

Godot proporciona una variedad de opciones para realizar comprobaciones de tiempo de ejecución en estos accesos:

  • Un acceso a propiedad en tipado dinámico. Estas harán chequeo de propiedades (como se describió anteriormente). Si la operación no está disponible por el objeto se detendrá la ejecución.

    # 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.
    
  • Un chequeo de método. En el caso de CanvasItem.visible, se puede acceder al método set_visible y is_visible como cualquier otro 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.
    
  • Externaliza el acceso a un Callable. Puede resultar útil en casos en que se quiere maximizar el nivel de libertad de dependencias. En este caso se depende de un contexto externo para configurar el método.

# 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 estrategias contribuyen al diseño flexible de Godot. Entre todos ellos, los usuarios disponen de una amplia gama de herramientas para satisfacer sus necesidades específicas.