Attention: Here be dragons
This is thelatest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.
Checking the stable version of the documentation...
Godot interfaces
Often one needs scripts that rely on other objects for features. Thereare 2 parts to this process:
Acquiring a reference to the object that presumably has the features.
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 allObjects, the most basic way of referencing themis to get a reference to an existing object from another acquired instance.
varobj=node.object# Property access.varobj=node.get_object()# Method access.
GodotObjectobj=node.Object;// Property access.GodotObjectobj=node.GetObject();// Method access.
The same principle applies forRefCounted objects.While users often accessNode andResource this way, alternative measures are available.
Instead of property or method access, one can get Resources by loadaccess.
# 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.varpreres=preload(path)# Load resource when program reaches statement.varres=load(path)# Note that users load scenes and scripts, by convention, with PascalCase# names (like typenames), often into constants.constMyScene=preload("my_scene.tscn")# Static loadconstMyScript=preload("my_script.gd")# This type's value varies, i.e. it is a variable, so it uses snake_case.@exportvarscript_type:Script# Must configure from the editor, defaults to null.@exportvarconst_script:Script:set(value):ifEngine.is_editor_hint():const_script=value# Warn users if the value hasn't been set.func_get_configuration_warnings():ifnotconst_script:return["Must initialize property 'const_script'."]return[]
// Tool script added for the sake of the "const [Export]" example.[Tool]publicMyType{// Property initializations load during Script instancing, i.e. .new().// No "preload" loads during scene load exists in C#.// Initialize with a value. Editable at runtime.publicScriptMyScript=GD.Load<Script>("res://Path/To/MyScript.cs");// Initialize with same value. Value cannot be changed.publicreadonlyScriptMyConstScript=GD.Load<Script>("res://Path/To/MyScript.cs");// Like 'readonly' due to inaccessible setter.// But, value can be set during constructor, i.e. MyType().publicScriptMyNoSetScript{get;}=GD.Load<Script>("res://Path/To/MyScript.cs");// If need a "const [Export]" (which doesn't exist), use a// conditional setter for a tool script that checks if it's executing// in the editor.privatePackedScene_enemyScn;[Export]publicPackedSceneEnemyScn{get{return_enemyScn;}set{if(Engine.IsEditorHint()){_enemyScn=value;}}};// Warn users if the value hasn't been set.publicstring[]_GetConfigurationWarnings(){if(EnemyScn==null){return["Must initialize property 'EnemyScn'."];}return[];}}
Note the following:
There are many ways in which a language can load such resources.
When designing how objects will access data, don't forgetthat one can pass resources around as references as well.
Keep in mind that loading a resource fetches the cached resourceinstance maintained by the engine. To get a new object, one mustduplicate an existing referenceor instantiate one from scratch with
new().
Nodes likewise have an alternative access point: the SceneTree.
extendsNode# Slow.funcdynamic_lookup_with_dynamic_nodepath():print(get_node("Child"))# Faster. GDScript only.funcdynamic_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")@onreadyvarchild=$Childfunclookup_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.@exportvarchild:Nodefunclookup_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.varpropfunccall_me_after_prop_is_initialized_by_parent():# Validate prop in one of three ways.# Fail with no notification.ifnotprop:return# Fail with an error message.ifnotprop: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.funcreference_a_global_autoloaded_variable():print(globals)print(globals.prop)print(globals.my_getter())
usingGodot;usingSystem;usingSystem.Diagnostics;publicclassMyNode:Node{// SlowpublicvoidDynamicLookupWithDynamicNodePath(){GD.Print(GetNode("Child"));}// Fastest. Lookup node and cache for future access.// Doesn't break if node moves later.privateNode_child;publicvoid_Ready(){_child=GetNode("Child");}publicvoidLookupAndCacheForFutureAccess(){GD.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.publicobjectProp{get;set;}publicvoidCallMeAfterPropIsInitializedByParent(){// Validate prop in one of three ways.// Fail with no notification.if(prop==null){return;}// Fail with an error message.if(prop==null){GD.PrintErr("'Prop' wasn't initialized");return;}// Fail with an exception.if(prop==null){thrownewInvalidOperationException("'Prop' wasn't initialized.");}// Fail and terminate.// Note: Scripts run from a release export template don't run `Debug.Assert`s.Debug.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.publicvoidReferenceAGlobalAutoloadedVariable(){MyNodeglobals=GetNode<MyNode>("/root/Globals");GD.Print(globals);GD.Print(globals.Prop);GD.Print(globals.MyGetter());}};
Accessing data or logic from an object
Godot's scripting API is duck-typed. This means that if a script executes anoperation, Godot doesn't validate that it supports the operation bytype.It instead checks that the objectimplements the individual method.
For example, theCanvasItem class has avisibleproperty. All properties exposed to the scripting API are in fact a setter andgetter pair bound to a name. If one tried to accessCanvasItem.visible, then Godot would do thefollowing checks, in order:
If the object has a script attached, it will attempt to set the propertythrough the script. This leaves open the opportunity for scripts to overridea property defined on a base object by overriding the setter method for theproperty.
If the script does not have the property, it performs a HashMap lookup inthe ClassDB for the "visible" property against the CanvasItem class and allof its inherited types. If found, it will call the bound setter or getter.For more information about HashMaps, see thedata preferences docs.
If not found, it does an explicit check to see if the user wants to accessthe "script" or "meta" properties.
If not, it checks for a
_set/_getimplementation (depending on typeof access) in the CanvasItem and its inherited types. These methods canexecute logic that gives the impression that the Object has a property. Thisis also the case with the_get_property_listmethod.Note that this happens even for non-legal symbol names, such as namesstarting with a digit or containing a slash.
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 thingswhich extend Object.
Godot provides a variety of options for performing runtime checks on theseaccesses:
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.
// All Objects have duck-typed Get, Set, and Call wrapper methods.GetParent().Set("visible",false);// C# is a static language, so it has no dynamic symbol access, e.g.// `GetParent().Visible = false` won't work.
A method check. In the case ofCanvasItem.visible, one canaccess the methods,
set_visibleandis_visiblelike any other method.varchild=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.ifchild.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.ifchildisCanvasItem: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(childisCanvasItem)# 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.varquest=$Questprint(quest.text)quest.complete()# or quest.fail()print(quest.text)# implied new text content# 2. Use a group.fora_childinget_children():ifa_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.
Nodechild=GetChild(0);// Dynamic lookup.child.Call("SetVisible",false);// Dynamic lookup, checks for method existence first.if(child.HasMethod("SetVisible")){child.Call("SetVisible",false);}// Use a group as if it were an "interface", i.e. assume it implements// certain methods.// Requires good documentation for the project to keep it reliable// (unless you make editor tools to enforce it at editor time).// Note, this is generally not as good as using an actual interface in// C#, but you can't set C# interfaces from the editor since they are// language-level features.if(child.IsInGroup("Offer")){child.Call("Accept");child.Call("Reject");}// Cast check, followed by static lookup.CanvasItemci=GetParent()asCanvasItem;if(ci!=null){ci.SetVisible(false);// useful when you need to make multiple safe calls to the classci.ShowOnTop=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.Debug.Assert(child.HasMethod("set_visible"));Debug.Assert(child.IsInGroup("offer"));Debug.Assert(CanvasItem.InstanceHas(child));// 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.Nodequest=GetNode("Quest");GD.Print(quest.Get("Text"));quest.Call("Complete");// or "Fail".GD.Print(quest.Get("Text"));// Implied new text content.// 2. Use a group.foreach(NodeAChildinGetChildren()){if(AChild.IsInGroup("quest")){GD.Print(quest.Get("Text"));quest.Call("Complete");// or "Fail".GD.Print(quest.Get("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. Also note that in C#, these methods// will be slower than static accesses with traditional interfaces.
Outsource the access to aCallable. These may be usefulin cases where one needs the max level of freedom from dependencies. Inthis case, one relies on an external context to setup the method.
# child.gdextendsNodevarfn=nullfuncmy_method():iffn:fn.call()# parent.gdextendsNode@onreadyvarchild=$Childfunc_ready():child.fn=print_mechild.my_method()funcprint_me():print(name)
// Child.csusingGodot;publicpartialclassChild:Node{publicCallable?Callable{get;set;}publicvoidMyMethod(){Callable?.Call();}}// Parent.csusingGodot;publicpartialclassParent:Node{privateChild_child;publicvoid_Ready(){_child=GetNode<Child>("Child");_child.Callable=Callable.From(PrintMe);_child.MyMethod();}publicvoidPrintMe(){GD.Print(Name);}}
These strategies contribute to Godot's flexible design. Between them, usershave a breadth of tools to meet their specific needs.