Godot接口
通常,需要依赖于功能的其他对象的脚本。这个过程分为两部分:
- 获取对可能具有这些功能的对象的引用。
- 从对象访问数据或逻辑。
本教程的其余部分,概述了完成所有这些操作的各种方法。
获取对象引用
对所有 Object 来说,引用它们最基本的方法,是从另一个获取的实例中,获取对现有对象的引用。
GDScript
C#
var obj = node.object # Property access.
var obj = node.get_object() # Method access.
Object obj = node.Object; // Property access.
Object obj = node.GetObject(); // Method access.
同样的原则也适用于 Reference 对象。虽然用户经常以这种方式访问 Node 和 Resource,还可以采取其他措施。
代替属性或方法访问,可以通过加载访问获得 Resource
。
GDScript
C#
var preres = preload(path) # Load resource during scene load
var res = load(path) # Load resource when program reaches statement
# Note that users load scenes and scripts, by convention, with PascalCase
# names (like typenames), often into constants.
const MyScene : = preload("my_scene.tscn") as PackedScene # Static load
const MyScript : = preload("my_script.gd") as Script
# This type's value varies, i.e. it is a variable, so it uses snake_case.
export(Script) var script_type: Script
# If 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.
tool # Must place at top of file.
# Must configure from the editor, defaults to null.
export(Script) var const_script setget set_const_script
func set_const_script(value):
if Engine.is_editor_hint():
const_script = value
# Warn users if the value hasn't been set.
func _get_configuration_warning():
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>("MyScript.cs");
// Initialize with same value. Value cannot be changed.
public readonly Script MyConstScript = GD.Load<Script>("MyScript.cs");
// Like 'readonly' due to inaccessible setter.
// But, value can be set during constructor, i.e. MyType().
public Script Library { get; } = GD.Load<Script>("res://addons/plugin/library.gd");
// 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 _GetConfigurationWarning()
{
if (EnemyScn == null)
return "Must initialize property 'EnemyScn'.";
return "";
}
}
请注意以下几点:
- 一种语言可以通过多种方式加载这些资源。
- 在设计对象如何访问数据时,不要忘记,还可以将资源作为引用传递。
- 请记住,加载资源将获取引擎维护的缓存资源实例。要获取一个新对象,必须 复制 一个现有引用,或者使用
new()
从头实例化一个引用。
节点也有一个可选的访问点:场景树。
GDScript
C#
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` keyword 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)
# 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` statements.
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())
public class MyNode
{
// Slow, dynamic lookup with dynamic NodePath.
public void Method1()
{
GD.Print(GetNode(NodePath("Child")));
}
// Fastest. Lookup node and cache for future access.
// Doesn't break if node moves later.
public Node Child;
public void _Ready()
{
Child = GetNode(NodePath("Child"));
}
public void Method2()
{
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;
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 and terminate.
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()
{
Node globals = GetNode(NodePath("/root/Globals"));
GD.Print(globals);
GD.Print(globals.prop);
GD.Print(globals.my_getter());
}
};
从对象访问数据或逻辑
Godot的脚本API是鸭子类型。这意味着,如果脚本执行操作,则Godot不会通过 类型 验证其是否支持该操作。相反,它检查对象是否 实现 了单个方法。
例如,CanvasItem 类有一个 visible
的属性。实际上,脚本API公开的所有属性,都是绑定到名称的 setter
和 getter
对。如果有人试图访问 CanvasItem.visible,那么Godot会按照以下顺序进行检查:
- 如果对象附加了脚本,它将尝试通过脚本设置属性。通过覆盖属性的
setter
方法,这为脚本提供了覆盖基础对象上定义的属性的机会。 - 如果脚本没有该属性,它在
ClassDB
中对CanvasItem
类及其所有继承的类型执行visible
属性的哈希表查找。如果找到,它将调用绑定的setter
或getter
。有关哈希表的更多信息,参见 数据偏好 文档。 - 如果没有找到,它会进行显式检查,以查看用户是否要访问
script
或meta
属性。 - 如果没有,它将在
CanvasItem
及其继承的类型中检查_set
/_get
实现(取决于访问类型)。这些方法可以执行逻辑,从而给人一种对象具有属性的印象。_get_property_list
方法也是如此。- 请注意,即使对于非合法的符号名称,例如 TileSet 的
1/tile_name
属性,这种情况也会发生。这是指ID为1的tile
的名称,即 TileSet.tile_get_name(1)。
- 请注意,即使对于非合法的符号名称,例如 TileSet 的
因此,这个鸭子类型的系统可以在脚本、对象的类、或对象继承的任何类、但只能用于扩展Object的对象中,定位属性。
Godot提供了多种选项,来对这些访问,执行运行时检查:
鸭子类型属性的访问。这些将进行属性检查(如上所述)。如果对象不支持该操作,则执行将停止。
GDScript
C#
# 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
。GDScript
C#
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/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/group can fill in for it. Also note that in C#, these methods
// will be slower than static accesses with traditional interfaces.
将访问权限外包给 FuncRef。在人们需要最大程度地摆脱依赖的情况下,这些方法可能会很有用。在这种情况下,需要依靠外部上下文来设置此方法。
GDScript
C#
# child.gd
extends Node
var fn = null
func my_method():
if fn:
fn.call_func()
# parent.gd
extends Node
onready var child = $Child
func _ready():
child.fn = funcref(self, "print_me")
child.my_method()
func print_me():
print(name)
// Child.cs
public class Child : Node
{
public FuncRef FN = null;
public void MyMethod()
{
Debug.Assert(FN != null);
FN.CallFunc();
}
}
// Parent.cs
public class Parent : Node
{
public Node Child;
public void _Ready()
{
Child = GetNode("Child");
Child.Set("FN", GD.FuncRef(this, "PrintMe"));
Child.MyMethod();
}
public void PrintMe() {
{
GD.Print(GetClass());
}
}
这些策略有助于Godot的灵活设计。通过它们,用户可以使用多种工具来满足他们的特定需求。