Up to date

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

Interfaces de Godot

Souvent, on a besoin de scripts qui s'appuient sur d'autres objets pour des fonctionnalités. Il y a 2 parties à ce processus :

  1. Acquisition d'une référence à l'objet qui possède vraisemblablement les fonctionnalités.

  2. Accès aux données ou à la logique à partir de l'objet.

La suite de ce tutoriel décrit les différentes façons de faire tout cela.

Acquisition de références d'objets

Pour tous les Objects, la façon la plus simple de les référencer est d'obtenir une référence à un objet existant depuis une autre instance acquise.

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.

Au lieu de l'accès à la propriété ou à la méthode, on peut obtenir des ressources par accès par chargement.

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

Notez ce qui suit :

  1. Il existe de nombreuses façons pour un langage de charger de telles ressources.

  2. Lors de la conception de la manière dont les objets accéderont aux données, n'oubliez pas qu'il est également possible de faire circuler des ressources comme références.

  3. Gardez à l'esprit que le chargement d'une ressource récupère l'instance de ressource mise en cache maintenue par le moteur. Pour obtenir un nouvel objet, il faut duplicate une référence existante ou en instancier une à partir de zéro avec new().

Les nœuds ont également un autre point d'accès : le 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())

Accès aux données ou à la logique à partir d'un objet

L'API de script de Godot est duck-typed. Cela signifie que si un script exécute une opération, Godot ne valide pas qu'il supporte l'opération par type. Au lieu de cela, il vérifie que l'objet implémente la méthode individuelle.

Par exemple, la classe CanvasItem a une propriété visible. Toutes les propriétés exposées à l'API de script sont en fait une paire setter et getter liée à un nom. Si on essayait d'accéder à CanvasItem.visible, alors Godot ferait les vérifications suivantes, dans l'ordre :

  • Si l'objet a un script attaché, il tentera de définir la propriété à travers le script. Ceci laisse la possibilité pour les scripts de substituer une propriété définie sur un objet de base en substituant la méthode setter de la propriété.

  • Si le script n'a pas la propriété, il effectue une recherche HashMap dans la ClassDB pour la propriété "visible" envers la classe CanvasItem et tous ses types hérités. S'il est trouvé, il appellera le setter ou le getter lié. Pour plus d'informations sur HashMaps, consultez data preferences.

  • S'il n'est pas trouvé, il vérifie explicitement si l'utilisateur veut accéder aux propriétés "script" ou "meta".

  • Si ce n'est pas le cas, il vérifie la présence d'une implémentation _set/_get (selon le type d'accès) dans l'élément CanvasItem et ses types hérités. Ces méthodes peuvent exécuter une logique qui donne l'impression que l'Objet possède une propriété. C'est également le cas avec la méthode _get_property_list.

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

En conséquence, ce système duck-typed peut localiser une propriété soit dans le script, soit dans la classe de l'objet, soit dans n'importe quelle classe dont l'objet hérite, mais seulement pour les choses qui étendent de Object.

Godot fournit une variété d'options pour effectuer des contrôles d'exécution sur ces accès :

  • 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.
    
  • Un contrôle de méthode. Dans le cas de CanvasItem.visible, on peut accéder aux méthodes, set_visible et is_visible comme toute autre méthode.

    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)

Ces stratégies contribuent à la souplesse de conception de Godot. Entre elles, les utilisateurs disposent d'un large éventail d'outils pour répondre à leurs besoins spécifiques.