Godot interfaces

Often one needs scripts that rely on other objects for features. There are 2 parts to this process:

  1. Acquiring a reference to the object that presumably has the features.
  2. Accessing the data or logic from the object.

The rest of this tutorial outlines the various ways of doing all this.

Acquiring object references

For all Objects, the most basic way of referencing them is to get a reference to an existing object from another acquired instance.

GDScript

C#

  1. var obj = node.object # Property access.
  2. var obj = node.get_object() # Method access.
  1. Object obj = node.Object; // Property access.
  2. Object obj = node.GetObject(); // Method access.

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

Instead of property or method access, one can get Resources by load access.

GDScript

C#

  1. var preres = preload(path) # Load resource during scene load
  2. var res = load(path) # Load resource when program reaches statement
  3. # Note that users load scenes and scripts, by convention, with PascalCase
  4. # names (like typenames), often into constants.
  5. const MyScene : = preload("my_scene.tscn") as PackedScene # Static load
  6. const MyScript : = preload("my_script.gd") as Script
  7. # This type's value varies, i.e. it is a variable, so it uses snake_case.
  8. export(Script) var script_type: Script
  9. # If need an "export const var" (which doesn't exist), use a conditional
  10. # setter for a tool script that checks if it's executing in the editor.
  11. tool # Must place at top of file.
  12. # Must configure from the editor, defaults to null.
  13. export(Script) var const_script setget set_const_script
  14. func set_const_script(value):
  15. if Engine.is_editor_hint():
  16. const_script = value
  17. # Warn users if the value hasn't been set.
  18. func _get_configuration_warning():
  19. if not const_script:
  20. return "Must initialize property 'const_script'."
  21. return ""
  1. // Tool script added for the sake of the "const [Export]" example.
  2. [Tool]
  3. public MyType : extends Object
  4. {
  5. // Property initializations load during Script instancing, i.e. .new().
  6. // No "preload" loads during scene load exists in C#.
  7. // Initialize with a value. Editable at runtime.
  8. public Script MyScript = GD.Load<Script>("MyScript.cs");
  9. // Initialize with same value. Value cannot be changed.
  10. public readonly Script MyConstScript = GD.Load<Script>("MyScript.cs");
  11. // Like 'readonly' due to inaccessible setter.
  12. // But, value can be set during constructor, i.e. MyType().
  13. public Script Library { get; } = GD.Load<Script>("res://addons/plugin/library.gd");
  14. // If need a "const [Export]" (which doesn't exist), use a
  15. // conditional setter for a tool script that checks if it's executing
  16. // in the editor.
  17. private PackedScene _enemyScn;
  18. [Export]
  19. public PackedScene EnemyScn
  20. {
  21. get { return _enemyScn; }
  22. set
  23. {
  24. if (Engine.IsEditorHint())
  25. {
  26. _enemyScn = value;
  27. }
  28. }
  29. };
  30. // Warn users if the value hasn't been set.
  31. public String _GetConfigurationWarning()
  32. {
  33. if (EnemyScn == null)
  34. return "Must initialize property 'EnemyScn'.";
  35. return "";
  36. }
  37. }

Note the following:

  1. There are many ways in which a language can load such resources.
  2. When designing how objects will access data, don’t forget that one can pass resources around as references as well.
  3. Keep in mind that loading a resource fetches the cached resource instance maintained by the engine. To get a new object, one must duplicate an existing reference or instantiate one from scratch with new().

Nodes likewise have an alternative access point: the SceneTree.

GDScript

C#

  1. extends Node
  2. # Slow.
  3. func dynamic_lookup_with_dynamic_nodepath():
  4. print(get_node("Child"))
  5. # Faster. GDScript only.
  6. func dynamic_lookup_with_cached_nodepath():
  7. print($Child)
  8. # Fastest. Doesn't break if node moves later.
  9. # Note that `onready` keyword is GDScript only.
  10. # Other languages must do...
  11. # var child
  12. # func _ready():
  13. # child = get_node("Child")
  14. onready var child = $Child
  15. func lookup_and_cache_for_future_access():
  16. print(child)
  17. # Delegate reference assignment to an external source
  18. # Con: need to perform a validation check
  19. # Pro: node makes no requirements of its external structure.
  20. # 'prop' can come from anywhere.
  21. var prop
  22. func call_me_after_prop_is_initialized_by_parent():
  23. # Validate prop in one of three ways.
  24. # Fail with no notification.
  25. if not prop:
  26. return
  27. # Fail with an error message.
  28. if not prop:
  29. printerr("'prop' wasn't initialized")
  30. return
  31. # Fail and terminate.
  32. # Compiled scripts in final binary do not include assert statements
  33. assert prop.
  34. # Use an autoload.
  35. # Dangerous for typical nodes, but useful for true singleton nodes
  36. # that manage their own data and don't interfere with other objects.
  37. func reference_a_global_autoloaded_variable():
  38. print(globals)
  39. print(globals.prop)
  40. print(globals.my_getter())
  1. public class MyNode
  2. {
  3. // Slow, dynamic lookup with dynamic NodePath.
  4. public void Method1()
  5. {
  6. GD.Print(GetNode(NodePath("Child")));
  7. }
  8. // Fastest. Lookup node and cache for future access.
  9. // Doesn't break if node moves later.
  10. public Node Child;
  11. public void _Ready()
  12. {
  13. Child = GetNode(NodePath("Child"));
  14. }
  15. public void Method2()
  16. {
  17. GD.Print(Child);
  18. }
  19. // Delegate reference assignment to an external source
  20. // Con: need to perform a validation check
  21. // Pro: node makes no requirements of its external structure.
  22. // 'prop' can come from anywhere.
  23. public object Prop;
  24. public void CallMeAfterPropIsInitializedByParent()
  25. {
  26. // Validate prop in one of three ways.
  27. // Fail with no notification.
  28. if (prop == null)
  29. {
  30. return;
  31. }
  32. // Fail with an error message.
  33. if (prop == null)
  34. {
  35. GD.PrintErr("'Prop' wasn't initialized");
  36. return;
  37. }
  38. // Fail and terminate.
  39. Debug.Assert(Prop, "'Prop' wasn't initialized");
  40. }
  41. // Use an autoload.
  42. // Dangerous for typical nodes, but useful for true singleton nodes
  43. // that manage their own data and don't interfere with other objects.
  44. public void ReferenceAGlobalAutoloadedVariable()
  45. {
  46. Node globals = GetNode(NodePath("/root/Globals"));
  47. GD.Print(globals);
  48. GD.Print(globals.prop);
  49. GD.Print(globals.my_getter());
  50. }
  51. };

Accessing data or logic from an object

Godot’s scripting API is duck-typed. This means that if a script executes an operation, Godot doesn’t validate that it supports the operation by type. It instead checks that the object implements the individual method.

For example, the CanvasItem class has a visible property. All properties exposed to the scripting API are in fact a setter and getter pair bound to a name. If one tried to access CanvasItem.visible, then Godot would do the following checks, in order:

  • If the object has a script attached, it will attempt to set the property through the script. This leaves open the opportunity for scripts to override a property defined on a base object by overriding the setter method for the property.
  • If the script does not have the property, it performs a HashMap lookup in the ClassDB for the “visible” property against the CanvasItem class and all of its inherited types. If found, it will call the bound setter or getter. For more information about HashMaps, see the data preferences docs.
  • If not found, it does an explicit check to see if the user wants to access the “script” or “meta” properties.
  • If not, it checks for a _set/_get implementation (depending on type of access) in the CanvasItem and its inherited types. These methods can execute logic that gives the impression that the Object has a property. This is also the case with the _get_property_list method.
    • Note that this happens even for non-legal symbol names such as in the case of TileSet’s “1/tile_name” property. This refers to the name of the tile with ID 1, i.e. TileSet.tile_get_name(1).

As a result, this duck-typed system can locate a property either in the script, the object’s class, or any class that object inherits, but only for things which extend Object.

Godot provides a variety of options for performing runtime checks on these accesses:

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

    GDScript

    C#

    1. # All Objects have duck-typed get, set, and call wrapper methods.
    2. get_parent().set("visible", false)
    3. # Using a symbol accessor, rather than a string in the method call,
    4. # will implicitly call the `set` method which, in turn, calls the
    5. # setter method bound to the property through the property lookup
    6. # sequence.
    7. get_parent().visible = false
    8. # Note that if one defines a _set and _get that describe a property's
    9. # existence, but the property isn't recognized in any _get_property_list
    10. # method, then the set() and get() methods will work, but the symbol
    11. # access will claim it can't find the property.
    1. // All Objects have duck-typed Get, Set, and Call wrapper methods.
    2. GetParent().Set("visible", false);
    3. // C# is a static language, so it has no dynamic symbol access, e.g.
    4. // `GetParent().Visible = false` won't work.
  • A method check. In the case of CanvasItem.visible, one can access the methods, set_visible and is_visible like any other method.

    GDScript

    C#

    1. var child = get_child(0)
    2. # Dynamic lookup.
    3. child.call("set_visible", false)
    4. # Symbol-based dynamic lookup.
    5. # GDScript aliases this into a 'call' method behind the scenes.
    6. child.set_visible(false)
    7. # Dynamic lookup, checks for method existence first.
    8. if child.has("set_visible"):
    9. child.set_visible(false)
    10. # Cast check, followed by dynamic lookup
    11. # Useful when you make multiple "safe" calls knowing that the class
    12. # implements them all. No need for repeated checks.
    13. # Tricky if one executes a cast check for a user-defined type as it
    14. # forces more dependencies.
    15. if child is CanvasItem:
    16. child.set_visible(false)
    17. child.show_on_top = true
    18. # If one does not wish to fail these checks without notifying users, one
    19. # can use an assert instead. These will trigger runtime errors
    20. # immediately if not true.
    21. assert child.has("set_visible")
    22. assert child.is_in_group("offer")
    23. assert child is CanvasItem
    24. # Can also use object labels to imply an interface, i.e. assume it implements certain methods.
    25. # There are two types, both of which only exist for Nodes: Names and Groups
    26. # Assuming...
    27. # A "Quest" object exists and 1) that it can "complete" or "fail" and
    28. # that it will have text available before and after each state...
    29. # 1. Use a name.
    30. var quest = $Quest
    31. print(quest.text)
    32. quest.complete() # or quest.fail()
    33. print(quest.text) # implied new text content
    34. # 2. Use a group.
    35. for a_child in get_children():
    36. if a_child.is_in_group("quest"):
    37. print(quest.text)
    38. quest.complete() # or quest.fail()
    39. print(quest.text) # implied new text content
    40. # Note that these interfaces are project-specific conventions the team
    41. # defines (which means documentation! But maybe worth it?).
    42. # Any script that conforms to the documented "interface" of the name/group can fill in for it.
    1. Node child = GetChild(0);
    2. // Dynamic lookup.
    3. child.Call("SetVisible", false);
    4. // Dynamic lookup, checks for method existence first.
    5. if (child.HasMethod("SetVisible"))
    6. {
    7. child.Call("SetVisible", false);
    8. }
    9. // Use a group as if it were an "interface", i.e. assume it implements certain methods
    10. // requires good documentation for the project to keep it reliable (unless you make
    11. // editor tools to enforce it at editor time.
    12. // Note, this is generally not as good as using an actual interface in C#,
    13. // but you can't set C# interfaces from the editor since they are
    14. // language-level features.
    15. if (child.IsInGroup("Offer"))
    16. {
    17. child.Call("Accept");
    18. child.Call("Reject");
    19. }
    20. // Cast check, followed by static lookup.
    21. CanvasItem ci = GetParent() as CanvasItem;
    22. if (ci != null)
    23. {
    24. ci.SetVisible(false);
    25. // useful when you need to make multiple safe calls to the class
    26. ci.ShowOnTop = true;
    27. }
    28. // If one does not wish to fail these checks without notifying users, one
    29. // can use an assert instead. These will trigger runtime errors
    30. // immediately if not true.
    31. Debug.Assert(child.HasMethod("set_visible"));
    32. Debug.Assert(child.IsInGroup("offer"));
    33. Debug.Assert(CanvasItem.InstanceHas(child));
    34. // Can also use object labels to imply an interface, i.e. assume it implements certain methods.
    35. // There are two types, both of which only exist for Nodes: Names and Groups
    36. // Assuming...
    37. // A "Quest" object exists and 1) that it can "Complete" or "Fail" and
    38. // that it will have Text available before and after each state...
    39. // 1. Use a name.
    40. Node quest = GetNode("Quest");
    41. GD.Print(quest.Get("Text"));
    42. quest.Call("Complete"); // or "Fail".
    43. GD.Print(quest.Get("Text")); // Implied new text content.
    44. // 2. Use a group.
    45. foreach (Node AChild in GetChildren())
    46. {
    47. if (AChild.IsInGroup("quest"))
    48. {
    49. GD.Print(quest.Get("Text"));
    50. quest.Call("Complete"); // or "Fail".
    51. GD.Print(quest.Get("Text")); // Implied new text content.
    52. }
    53. }
    54. // Note that these interfaces are project-specific conventions the team
    55. // defines (which means documentation! But maybe worth it?)..
    56. // Any script that conforms to the documented "interface" of the
    57. // name/group can fill in for it. Also note that in C#, these methods
    58. // will be slower than static accesses with traditional interfaces.
  • Outsource the access to a FuncRef. 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.

GDScript

C#

  1. # child.gd
  2. extends Node
  3. var fn = null
  4. func my_method():
  5. if fn:
  6. fn.call_func()
  7. # parent.gd
  8. extends Node
  9. onready var child = $Child
  10. func _ready():
  11. child.fn = funcref(self, "print_me")
  12. child.my_method()
  13. func print_me():
  14. print(name)
  1. // Child.cs
  2. public class Child extends Node
  3. {
  4. public FuncRef FN = null;
  5. public void MyMethod()
  6. {
  7. Debug.Assert(FN != null);
  8. FN.CallFunc();
  9. }
  10. }
  11. // Parent.cs
  12. public class Parent extends Node
  13. {
  14. public Node Child;
  15. public void _Ready()
  16. {
  17. Child = GetNode("Child");
  18. Child.Set("FN", GD.FuncRef(this, "PrintMe"));
  19. Child.MyMethod();
  20. }
  21. public void PrintMe() {
  22. {
  23. GD.Print(GetClass());
  24. }
  25. }

These strategies contribute to Godot’s flexible design. Between them, users have a breadth of tools to meet their specific needs.