Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

Interfacce in Godot

Spesso sono necessari script che dipendono da altri oggetti per il loro funzionamento. Questo processo si divide di 2 parti:

  1. Acquisire un riferimento all'oggetto che presumibilmente ha le caratteristiche richieste.

  2. Accedere ai dati o alla logica dell'oggetto.

Il resto di questo tutorial descrive i vari modi per fare tutto questo.

Acquisire riferimenti di oggetti

Per tutti gli Object, il modo più semplice per referenziarli è ottenere un riferimento a un oggetto esistente da un'altra istanza acquisita.

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

Lo stesso principio si applica agli oggetti RefCounted. Sebbene gli utenti accedano spesso a Node e Resource in questo modo, sono disponibili misure alternative.

Invece di accedere tramite proprietà o metodi, è possibile ottenere risorse accedendovi tramite caricamento.

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

Notare quanto segue:

  1. Esistono molti modi in cui un linguaggio può caricare tali risorse.

  2. Durante la progettazione del modo in cui gli oggetti accederanno ai dati, non bisogna dimenticare che è possibile anche passare le risorse come riferimenti.

  3. Tenete presente che il caricamento di una risorsa recupera l'istanza di tale risorsa dalla cache e ciò è gestito dal motore. Per ottenere un nuovo oggetto, è necessario duplicare un riferimento esistente o istanziarne uno da zero con new().

Anche i nodi hanno un punto di accesso alternativo: lo 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())

Accedere ai dati o alla logica da un oggetto

L'API di scripting di Godot è "duck-typed". Ciò significa che se uno script esegue un'operazione, Godot non ne verifica il supporto per tipo. Verifica invece che l'oggetto implementi il metodo individuale.

Ad esempio, la classe CanvasItem ha una proprietà visible. Tutte le proprietà esposte all'API di scripting sono in realtà una coppia setter e getter associata a un nome. Se si provasse ad accedere a CanvasItem.visible, Godot effettuerebbe i seguenti controlli, nell'ordine:

  • Se l'oggetto ha uno script allegato, tenterà di impostare la proprietà attraverso lo script. Questo lascia la possibilità agli script di sovrascrivere una proprietà definita su un oggetto base, sovrascrivendo il metodo setter per la proprietà.

  • Se lo script non ha la proprietà, esegue una ricerca in un HashMap nel ClassDB per la proprietà "visible" sulla classe CanvasItem e tutti i suoi tipi ereditati. Se trovata, chiama il setter o il getter associato. Per ulteriori informazioni sulle HashMap, consultare la documentazione di preferenze sui dati.

  • Se non viene trovato, effettua una verifica esplicita per vedere se l'utente desidera accedere alle proprietà "script" o "meta".

  • Se no, verifica la presenza di un'implementazione di _set/_get (a seconda del tipo di accesso) nel CanvasItem e nei suoi tipi ereditati. Questi metodi possono eseguire una logica che dà l'impressione che l'oggetto abbia una proprietà. Questo vale anche per il metodo _get_property_list.

    • Si noti che ciò accade anche per i nomi di simboli non permessi, ad esempio i nomi che iniziano con una cifra o contengono una barra.

Pertanto, questo sistema può individuare una proprietà nello script, nella classe dell'oggetto o in qualsiasi classe ereditata dall'oggetto, ma solo per le cose che estendono Object.

Godot offre una varietà di opzioni per eseguire verifiche su questi accessi in fase di esecuzione:

  • Un accesso alle proprietà in stile dinamico. Si tratterà di verifiche di proprietà (come descritto in precedenza). Se l'operazione non è supportata dall'oggetto, l'esecuzione si interromperà.

    # 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.
    
  • Una verifica di metodo. Nel caso di CanvasItem.visible, è possibile accedere ai metodi set_visible e is_visible come qualsiasi altro metodo.

    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.
    
  • Esternalizzare l'accesso a un Callable. Cio può essere utile nei casi in cui è necessario il massimo livello di libertà dalle dipendenze. In questo caso, ci si affida a un contesto esterno per configurare il metodo.

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

Queste strategie contribuiscono alla flessibilità del design di Godot. Tra di esse, gli utenti hanno a disposizione una vasta scelta di strumenti per soddisfare le loro esigenze specifiche.