Godotインターフェース

多くの場合、機能を他のオブジェクトに依存するスクリプトが必要です。このプロセスには2つの部分があります:

  1. 機能を持つ可能性のあるオブジェクトへの参照を取得します。
  2. オブジェクトからデータまたはロジックにアクセスします。

このチュートリアルの残りの部分では、これを行うさまざまな方法の概要を説明します。

オブジェクト参照の取得

すべての Object について、それらを参照する最も基本的な方法は、取得した別のインスタンスから既存のオブジェクトへの参照を取得することです。

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 オブジェクトにも適用されます。ユーザーはこの方法で NodeResource に頻繁にアクセスしますが、代替手段も利用できます。

プロパティまたはメソッドアクセスの代わりに、ロードアクセスによってリソースを取得できます。

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 "";
    }
}

次の点に注意してください:

  1. 言語がそのようなリソースをロードできる方法はたくさんあります。
  2. オブジェクトがデータにアクセスする方法を設計するとき、リソースを参照としても渡すことができることを忘れないでください。
  3. リソースをロードすると、エンジンによって維持されているキャッシュされたリソースインスタンスがフェッチされることに注意してください。新しいオブジェクトを取得するには、既存の参照を duplicate するか、new() でゼロからインスタンス化する必要があります。

ノードにも同様に、代替アクセス ポイントがあります: SceneTreeです。

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は 型(type) による操作をサポートしているかどうかを検証しません。代わりに、オブジェクトが個々のメソッドを 実装 していることをチェックします。

たとえば、CanvasItem クラスには visible プロパティがあります。スクリプトAPIに公開されるすべてのプロパティは、実際には名前にバインドされたセッターとゲッターのペアです。CanvasItem.visible にアクセスしようとすると、Godot は次のチェックを順番に実行します:

  • オブジェクトにスクリプトがアタッチされている場合、まずスクリプトを介してプロパティを設定しようとします。このプロパティのセッターメソッドをスクリプトでオーバーライドする手段によって、基本オブジェクトで定義されたプロパティをスクリプトから変更することができます。
  • スクリプトにプロパティがない場合は、CanvasItemクラスとその継承されたすべての型に対して、"visible"プロパティの ClassDBでハッシュマップルックアップが実行されます。見つかった場合は、バインドされたセッターまたはゲッターを呼び出します。ハッシュマップの詳細については、データ設定 ドキュメントを参照してください。
  • 見つからない場合は、ユーザーが"script"プロパティまたは"meta"プロパティにアクセスするかどうかを明示的に確認します。
  • そうでない場合は、CanvasItemおよびその継承されたタイプで(アクセスのタイプに応じて) _set/_get の実装をチェックします。これらのメソッドは、オブジェクトにプロパティがあることを示すロジックを実行できます。これは _get_property_list メソッドでも同様です。
    • これは、TileSet の "1/tile_name" プロパティの場合など、非正規なシンボル名の場合でも発生することに注意してください。これは、ID 1のタイルの名前を指します。参照: TileSet.tile_get_name(1)

その結果、このダック・タイプのシステムは、スクリプト、オブジェクトのクラス、またはオブジェクトが継承する任意のクラスのいずれかでプロパティを見つけることができますが、これは Object を拡張するものについてのみです。

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_visibleis_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("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("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 へのアクセスを外部委託します。これらは、依存関係から最大限の自由度が必要な場合に役立ちます。この場合、メソッドの設定は外部コンテキストに依存します。

# 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の柔軟なデザインに貢献します。それらの間から、ユーザーは特定のニーズを満たすための幅広いツールを手にできます。