Godot 接口

脚本常常需要依赖其他对象来获取功能。这个过程分为两部分:

  1. 获取对可能具有这些功能的对象的引用。

  2. 从对象访问数据或逻辑。

接下来,本教程将介绍多种方法来完成这些操作。

获取对象引用

对所有 Object 来说,获得引用的最基础的方法,是通过另一个已获得引用的对象。

GDScriptC#

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

同样的方法也适用于 RefCounted 对象。虽然使用者经常以这种方式访问 NodeResource,但还是有一些其他方法可以使用。

除了访问属性和方法,也可以通过加载来获得 Resource。

GDScriptC#

  1. # If you need an "export const var" (which doesn't exist), use a conditional
  2. # setter for a tool script that checks if it's executing in the editor.
  3. # The `@tool` annotation must be placed at the top of the script.
  4. @tool
  5. # Load resource during scene load.
  6. var preres = preload(path)
  7. # Load resource when program reaches statement.
  8. var res = load(path)
  9. # Note that users load scenes and scripts, by convention, with PascalCase
  10. # names (like typenames), often into constants.
  11. const MyScene = preload("my_scene.tscn") # Static load
  12. const MyScript = preload("my_script.gd")
  13. # This type's value varies, i.e. it is a variable, so it uses snake_case.
  14. @export var script_type: Script
  15. # Must configure from the editor, defaults to null.
  16. @export var const_script: Script:
  17. set(value):
  18. if Engine.is_editor_hint():
  19. const_script = value
  20. # Warn users if the value hasn't been set.
  21. func _get_configuration_warnings():
  22. if not const_script:
  23. return ["Must initialize property 'const_script'."]
  24. return []
  1. // Tool script added for the sake of the "const [Export]" example.
  2. [Tool]
  3. public MyType
  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>("res://Path/To/MyScript.cs");
  9. // Initialize with same value. Value cannot be changed.
  10. public readonly Script MyConstScript = GD.Load<Script>("res://Path/To/MyScript.cs");
  11. // Like 'readonly' due to inaccessible setter.
  12. // But, value can be set during constructor, i.e. MyType().
  13. public Script MyNoSetScript { get; } = GD.Load<Script>("res://Path/To/MyScript.cs");
  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[] _GetConfigurationWarnings()
  32. {
  33. if (EnemyScn == null)
  34. {
  35. return new string[] { "Must initialize property 'EnemyScn'." };
  36. }
  37. return Array.Empty<string>();
  38. }
  39. }

请注意以下几点:

  1. 在一种语言中,有许多加载这些资源的方法。

  2. 在设计对象如何访问数据时,不要忘记,还可以将资源作为引用传递。

  3. 请记住,加载资源时只会获取引擎维护的缓存资源实例。如果要获取一个新对象,必须 复制 一个现有引用,或者使用 new() 从头实例化一个对象。

节点同样也有另一种访问方式:场景树。

GDScriptC#

  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` annotation 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. # Fastest. Doesn't break if node is moved in the Scene tree dock.
  18. # Node must be selected in the inspector as it's an exported property.
  19. @export var child: Node
  20. func lookup_and_cache_for_future_access():
  21. print(child)
  22. # Delegate reference assignment to an external source.
  23. # Con: need to perform a validation check.
  24. # Pro: node makes no requirements of its external structure.
  25. # 'prop' can come from anywhere.
  26. var prop
  27. func call_me_after_prop_is_initialized_by_parent():
  28. # Validate prop in one of three ways.
  29. # Fail with no notification.
  30. if not prop:
  31. return
  32. # Fail with an error message.
  33. if not prop:
  34. printerr("'prop' wasn't initialized")
  35. return
  36. # Fail and terminate.
  37. # NOTE: Scripts run from a release export template don't run `assert`s.
  38. assert(prop, "'prop' wasn't initialized")
  39. # Use an autoload.
  40. # Dangerous for typical nodes, but useful for true singleton nodes
  41. # that manage their own data and don't interfere with other objects.
  42. func reference_a_global_autoloaded_variable():
  43. print(globals)
  44. print(globals.prop)
  45. print(globals.my_getter())
  1. using Godot;
  2. using System;
  3. using System.Diagnostics;
  4. public class MyNode : Node
  5. {
  6. // Slow
  7. public void DynamicLookupWithDynamicNodePath()
  8. {
  9. GD.Print(GetNode("Child"));
  10. }
  11. // Fastest. Lookup node and cache for future access.
  12. // Doesn't break if node moves later.
  13. private Node _child;
  14. public void _Ready()
  15. {
  16. _child = GetNode("Child");
  17. }
  18. public void LookupAndCacheForFutureAccess()
  19. {
  20. GD.Print(_child);
  21. }
  22. // Delegate reference assignment to an external source.
  23. // Con: need to perform a validation check.
  24. // Pro: node makes no requirements of its external structure.
  25. // 'prop' can come from anywhere.
  26. public object Prop { get; set; }
  27. public void CallMeAfterPropIsInitializedByParent()
  28. {
  29. // Validate prop in one of three ways.
  30. // Fail with no notification.
  31. if (prop == null)
  32. {
  33. return;
  34. }
  35. // Fail with an error message.
  36. if (prop == null)
  37. {
  38. GD.PrintErr("'Prop' wasn't initialized");
  39. return;
  40. }
  41. // Fail with an exception.
  42. if (prop == null)
  43. {
  44. throw new InvalidOperationException("'Prop' wasn't initialized.");
  45. }
  46. // Fail and terminate.
  47. // Note: Scripts run from a release export template don't run `Debug.Assert`s.
  48. Debug.Assert(Prop, "'Prop' wasn't initialized");
  49. }
  50. // Use an autoload.
  51. // Dangerous for typical nodes, but useful for true singleton nodes
  52. // that manage their own data and don't interfere with other objects.
  53. public void ReferenceAGlobalAutoloadedVariable()
  54. {
  55. MyNode globals = GetNode<MyNode>("/root/Globals");
  56. GD.Print(globals);
  57. GD.Print(globals.Prop);
  58. GD.Print(globals.MyGetter());
  59. }
  60. };

从对象访问数据或逻辑

Godot 的脚本 API 是鸭子类型(duck-typed)的。这意味着,当脚本执行某项操作时,Godot 不会通过 类型 来验证其是否支持该操作。相反,它会检查对象是否 实现 了这个被调用的方法。

实际上,脚本 API 公开的所有属性,都是绑定到名称的 settergetter 对。以 CanvasItem 类的 visible 属性为例,如果有人试图访问 CanvasItem.visible,那么 Godot 会按照以下顺序进行检查:

  • 如果对象附加了脚本,它将尝试通过脚本设置属性。这使得脚本有机会通过覆盖属性的 setter 方法来覆盖在基础对象上定义的属性。

  • 如果脚本没有该属性, 它在 ClassDB 中对 CanvasItem 类及其所有继承的类型执行 visible 属性的哈希表查找. 如果找到, 它将调用绑定的 settergetter. 有关哈希表的更多信息, 参见 数据偏好 文档.

  • 如果没有找到, 它会进行显式检查, 以查看用户是否要访问 scriptmeta 属性.

  • 如果没有, 它将在 CanvasItem 及其继承的类型中检查 _set/_get 实现(取决于访问类型). 这些方法可以执行逻辑, 从而给人一种对象具有属性的印象. _get_property_list 方法也是如此.

    • 请注意,即使对于不合法的符号名称也会发生这种情况,例如以数字开头或包含斜杠(/)的名称。

因此,这个鸭子类型的系统可以在脚本、对象的类,或对象继承的任何类中定位属性,但仅限于扩展 Object 的对象。

Godot 提供了多种选项,来对这些访问执行运行时检查:

  • 鸭子类型属性的访问。Godot 将像上文所述的那样对它进行属性检查。如果对象不支持该操作,则执行将停止。

    GDScriptC#

    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.
  • 方法检查。在 CanvasItem.visible 的例子中,我们可以像访问任何其他方法一样,访问 set_visibleis_visible

    GDScriptC#

    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_method("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,
    19. # one can use an assert instead. These will trigger runtime errors
    20. # immediately if not true.
    21. assert(child.has_method("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
    25. # implements certain methods.
    26. # There are two types, both of which only exist for Nodes: Names and
    27. # Groups.
    28. # Assuming...
    29. # A "Quest" object exists and 1) that it can "complete" or "fail" and
    30. # that it will have text available before and after each state...
    31. # 1. Use a name.
    32. var quest = $Quest
    33. print(quest.text)
    34. quest.complete() # or quest.fail()
    35. print(quest.text) # implied new text content
    36. # 2. Use a group.
    37. for a_child in get_children():
    38. if a_child.is_in_group("quest"):
    39. print(quest.text)
    40. quest.complete() # or quest.fail()
    41. print(quest.text) # implied new text content
    42. # Note that these interfaces are project-specific conventions the team
    43. # defines (which means documentation! But maybe worth it?).
    44. # Any script that conforms to the documented "interface" of the name or
    45. # 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
    10. // certain methods.
    11. // Requires good documentation for the project to keep it reliable
    12. // (unless you make editor tools to enforce it at editor time).
    13. // Note, this is generally not as good as using an actual interface in
    14. // C#, but you can't set C# interfaces from the editor since they are
    15. // language-level features.
    16. if (child.IsInGroup("Offer"))
    17. {
    18. child.Call("Accept");
    19. child.Call("Reject");
    20. }
    21. // Cast check, followed by static lookup.
    22. CanvasItem ci = GetParent() as CanvasItem;
    23. if (ci != null)
    24. {
    25. ci.SetVisible(false);
    26. // useful when you need to make multiple safe calls to the class
    27. ci.ShowOnTop = true;
    28. }
    29. // If one does not wish to fail these checks without notifying users,
    30. // one can use an assert instead. These will trigger runtime errors
    31. // immediately if not true.
    32. Debug.Assert(child.HasMethod("set_visible"));
    33. Debug.Assert(child.IsInGroup("offer"));
    34. Debug.Assert(CanvasItem.InstanceHas(child));
    35. // Can also use object labels to imply an interface, i.e. assume it
    36. // implements certain methods.
    37. // There are two types, both of which only exist for Nodes: Names and
    38. // Groups.
    39. // Assuming...
    40. // A "Quest" object exists and 1) that it can "Complete" or "Fail" and
    41. // that it will have Text available before and after each state...
    42. // 1. Use a name.
    43. Node quest = GetNode("Quest");
    44. GD.Print(quest.Get("Text"));
    45. quest.Call("Complete"); // or "Fail".
    46. GD.Print(quest.Get("Text")); // Implied new text content.
    47. // 2. Use a group.
    48. foreach (Node AChild in GetChildren())
    49. {
    50. if (AChild.IsInGroup("quest"))
    51. {
    52. GD.Print(quest.Get("Text"));
    53. quest.Call("Complete"); // or "Fail".
    54. GD.Print(quest.Get("Text")); // Implied new text content.
    55. }
    56. }
    57. // Note that these interfaces are project-specific conventions the team
    58. // defines (which means documentation! But maybe worth it?).
    59. // Any script that conforms to the documented "interface" of the
    60. // name or group can fill in for it. Also note that in C#, these methods
    61. // will be slower than static accesses with traditional interfaces.
  • 将访问权限外包给 Callable。 在人们需要最大程度地摆脱依赖的情况下,这些方法可能会很有用。 在这种情况下,需要依赖外部上下文来设置该方法。

GDScriptC#

  1. # child.gd
  2. extends Node
  3. var fn = null
  4. func my_method():
  5. if fn:
  6. fn.call()
  7. # parent.gd
  8. extends Node
  9. @onready var child = $Child
  10. func _ready():
  11. child.fn = print_me
  12. child.my_method()
  13. func print_me():
  14. print(name)
  1. // Child.cs
  2. using Godot;
  3. public partial class Child : Node
  4. {
  5. public Callable? Callable { get; set; }
  6. public void MyMethod()
  7. {
  8. Callable?.Call();
  9. }
  10. }
  11. // Parent.cs
  12. using Godot;
  13. public partial class Parent : Node
  14. {
  15. private Child _child;
  16. public void _Ready()
  17. {
  18. _child = GetNode<Child>("Child");
  19. _child.Callable = Callable.From(PrintMe);
  20. _child.MyMethod();
  21. }
  22. public void PrintMe()
  23. {
  24. GD.Print(Name);
  25. }
  26. }

这些策略有助于Godot的灵活设计. 通过它们, 用户可以使用多种工具来满足他们的特定需求.