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.
Checking the stable version of the documentation...
Godot 接口
脚本常常需要依赖其他对象来获取功能。这个过程分为两部分:
获取对可能具有这些功能的对象的引用。
从对象访问数据或逻辑。
接下来,本教程将介绍多种方法来完成这些操作。
获取对象引用
对所有 Object 来说,获得引用的最基础的方法,是通过另一个已获得引用的对象。
var obj = node.object # Property access.
var obj = node.get_object() # Method access.
GodotObject obj = node.Object; // Property access.
GodotObject obj = node.GetObject(); // Method access.
同样的原则也适用于 RefCounted 对象。虽然用户经常以这种方式访问 Node 和 Resource,但还有其他方法可用。
除了访问属性和方法,也可以通过加载来获得 Resource。
# 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 []
// Tool script added for the sake of the "const [Export]" example.
[Tool]
public MyType
{
// 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.
public Script MyScript = GD.Load<Script>("res://Path/To/MyScript.cs");
// Initialize with same value. Value cannot be changed.
public readonly Script MyConstScript = GD.Load<Script>("res://Path/To/MyScript.cs");
// Like 'readonly' due to inaccessible setter.
// But, value can be set during constructor, i.e. MyType().
public Script MyNoSetScript { 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.
private PackedScene _enemyScn;
[Export]
public PackedScene EnemyScn
{
get { return _enemyScn; }
set
{
if (Engine.IsEditorHint())
{
_enemyScn = value;
}
}
};
// Warn users if the value hasn't been set.
public string[] _GetConfigurationWarnings()
{
if (EnemyScn == null)
{
return ["Must initialize property 'EnemyScn'."];
}
return [];
}
}
请注意以下几点:
在一种语言中,有许多加载这些资源的方法。
在设计对象如何访问数据时,不要忘记,还可以将资源作为引用传递。
请记住,加载资源时只会获取引擎维护的缓存资源实例。如果要获取一个新对象,必须 复制 一个现有引用,或者使用
new()
从头实例化一个对象。
节点同样也有另一种访问方式:场景树。
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())
using Godot;
using System;
using System.Diagnostics;
public class MyNode : Node
{
// Slow
public void DynamicLookupWithDynamicNodePath()
{
GD.Print(GetNode("Child"));
}
// Fastest. Lookup node and cache for future access.
// Doesn't break if node moves later.
private Node _child;
public void _Ready()
{
_child = GetNode("Child");
}
public void LookupAndCacheForFutureAccess()
{
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.
public object Prop { get; set; }
public void CallMeAfterPropIsInitializedByParent()
{
// 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)
{
throw new InvalidOperationException("'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.
public void ReferenceAGlobalAutoloadedVariable()
{
MyNode globals = GetNode<MyNode>("/root/Globals");
GD.Print(globals);
GD.Print(globals.Prop);
GD.Print(globals.MyGetter());
}
};
从对象访问数据或逻辑
Godot 的脚本 API 是鸭子类型(duck-typed)的。这意味着,当脚本执行某项操作时,Godot 不会通过 类型 来验证其是否支持该操作。相反,它会检查对象是否 实现 了这个被调用的方法。
例如,CanvasItem 类具有 visible`
属性。暴露给脚本 API 的所有属性实际上都是与名称绑定的 setter 和 getter 对。如果有人尝试访问 CanvasItem.visible,那么 Godot 将按顺序执行以下检查:
如果对象附加了脚本,它将尝试通过脚本设置属性。这使得脚本有机会通过覆盖属性的
setter
方法来覆盖在基础对象上定义的属性。如果脚本没有该属性,它会在 ClassDB 中针对 CanvasItem 类及其所有继承类型执行 HashMap 查找以查找“visible”属性。如果找到,它会调用绑定的 setter 或 getter。有关 HashMap 的更多信息,请参阅《数据偏好》文档。
如果没有找到, 它会进行显式检查, 以查看用户是否要访问
script
或meta
属性.如果没有, 它将在
CanvasItem
及其继承的类型中检查_set
/_get
实现(取决于访问类型). 这些方法可以执行逻辑, 从而给人一种对象具有属性的印象._get_property_list
方法也是如此.请注意,即使对于不合法的符号名称也会发生这种情况,例如以数字开头或包含斜杠(/)的名称。
因此,这个鸭子类型的系统可以在脚本、对象的类,或对象继承的任何类中定位属性,但仅限于扩展 Object 的对象。
Godot 提供了多种选项,来对这些访问执行运行时检查:
鸭子类型属性的访问。Godot 将像上文所述的那样对它进行属性检查。如果对象不支持该操作,则执行将停止。
# 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.
方法检查。在 CanvasItem.visible 的例子中,我们可以像访问任何其他方法一样,访问
set_visible
和is_visible
。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.
Node child = 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. CanvasItem ci = GetParent() as CanvasItem; if (ci != null) { ci.SetVisible(false); // useful when you need to make multiple safe calls to the class ci.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. Node quest = 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 (Node AChild in GetChildren()) { 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.
将访问权限外包给 Callable。当需要最大程度地摆脱依赖时,这种方法可能很有用。在这种情况下,人们依赖外部上下文来设置该方法。
# 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)
// Child.cs
using Godot;
public partial class Child : Node
{
public Callable? Callable { get; set; }
public void MyMethod()
{
Callable?.Call();
}
}
// Parent.cs
using Godot;
public partial class Parent : Node
{
private Child _child;
public void _Ready()
{
_child = GetNode<Child>("Child");
_child.Callable = Callable.From(PrintMe);
_child.MyMethod();
}
public void PrintMe()
{
GD.Print(Name);
}
}
这些策略有助于Godot的灵活设计. 通过它们, 用户可以使用多种工具来满足他们的特定需求.