シーンとスクリプトを使用する場合

シーンとスクリプトの違いについてはすでに説明しました。スクリプトは、命令型コードでエンジンクラスの拡張を定義し、宣言型コードでシーンを定義します。

結果として、それぞれのシステムの機能は異なります。シーンは、拡張クラスの初期化方法を定義できますが、実際の動作は定義できません。シーンは多くの場合、スクリプトと組み合わせて使用されるため、シーンはスクリプトの宣言型コードの拡張機能として機能します。

匿名型

スクリプトのみを使用してシーンのコンテンツを完全に定義することもできます。これは本質的にはGodotエディタで行うべきことですが、スクリプトで行う場合はそのオブジェクトのC++コンストラクタで行います。

しかし、どちらを使用するかを選択することはジレンマになる可能性があります。スクリプトインスタンスの作成はエンジン内クラスの作成と同じですが、シーンの処理にはAPIを変更する必要があります:

const MyNode = preload("my_node.gd")
const MyScene = preload("my_scene.tscn")
var node = Node.new()
var my_node = MyNode.new() # Same method call
var my_scene = MyScene.instance() # Different method call
var my_inherited_scene = MyScene.instance(PackedScene.GEN_EDIT_STATE_MAIN) # Create scene inheriting from MyScene
using System;
using Godot;

public class Game : Node
{
    public readonly Script MyNodeScr = (Script)ResourceLoader.Load("MyNode.cs");
    public readonly PackedScene MySceneScn = (PackedScene)ResourceLoader.Load("MyScene.tscn");
    public Node ANode;
    public Node MyNode;
    public Node MyScene;
    public Node MyInheritedScene;

    public Game()
    {
        ANode = new Node();
        MyNode = new MyNode(); // Same syntax
        MyScene = MySceneScn.Instance(); // Different. Instantiated from a PackedScene
        MyInheritedScene = MySceneScn.Instance(PackedScene.GenEditState.Main); // Create scene inheriting from MyScene
    }
}

また、エンジンとスクリプトコードの処理速度が異なるため、スクリプトの動作はシーンよりも少し遅くなります。ノードが大きく複雑になるほど、ノードをシーンとして構築する理由が増えます。

名前付き型

場合によっては、ユーザーはエディタ自体の中でスクリプトを新しい型として登録できます。これにより、ノードまたはリソース作成ダイアログに新しい型として表示され、オプションのアイコンが表示されます。このような場合、ユーザーがスクリプトを使用する機能は大幅に合理化されます。むしろ...

  1. 使用するスクリプトの基本型を知っている。
  2. その基本型のインスタンスを作成します。
  3. ノードにスクリプトを追加します。
    1. (ドラッグアンドドロップ方式)
      1. ファイル システムドックでスクリプトを見つけます。
      2. スクリプトをシーンドックのノードにドラッグ アンド ドロップします。
    2. (プロパティメソッド)
      1. インスペクタの一番下までスクロールして Script プロパティを見つけて選択します。
      2. ドロップダウンから「読込み」を選択します。
      3. ファイル ダイアログからスクリプトを選択します。

この手順の代わりに登録済みスクリプトを使用すると、スクリプト化された型をシステム内の他のノードやリソースと同様な、作成オプションの一つにすることが出来ます。なので上記の作業を行う必要はありません。作成ダイアログには、(登録済みの)型を名前で検索するための検索バーがあります。

型を登録するための 2つのシステムがあります...

  • カスタム型

    • エディタのみ。型名は実行時にはアクセスできません。
    • 継承されたカスタム型はサポートされていません。
    • 初期化ツール。スクリプトを使用してノードを作成します。これ以外の方法はありません。
    • エディタは、スクリプトの型や、他のエンジン内の型やスクリプトとの関係を認識しません。
    • ユーザーがアイコンを定義できます。
    • スクリプトリソースを抽象化して扱うので、すべてのスクリプト言語で動作します。
    • EditorPlugin.add_custom_type を使用してセットアップします。
  • スクリプトクラス

    • エディタとランタイムにアクセスできます。
    • 継承関係を完全に表示します。
    • スクリプトを使用してノードを作成しますが、エディタから型を変更したり、型を拡張したりすることもできます。
    • エディタは、スクリプト、スクリプトクラス、およびエンジンのC++クラス間の継承関係を認識します。
    • ユーザーがアイコンを定義できます。
    • エンジン開発者は、言語のサポートを手動で追加する必要があります(名前の公開とランタイムのアクセシビリティの両方)。
    • Godot3.1以降のみです。
    • エディタは、プロジェクトフォルダをスキャンし、すべてのスクリプト言語の公開名を登録します。各スクリプト言語は、この情報を公開するための独自のサポートを実装する必要があります。

どちらの方法でも作成ダイアログに名前が追加されますが、特にスクリプト クラスを使用すると、ユーザーはスクリプト リソースを読み込まずに型名にアクセスできます。インスタンスの作成と定数または静的メソッドへのアクセスは、どこからでも実行可能です。

このような機能を使用すると、それによってユーザーに付与される使いやすさから、その型をシーンのないスクリプトにしたい場合があります。このようにプラグインを開発したり、デザイナーが使用する社内ツールを作成したりすることで、物事をより簡単な手順で進められるようになります。

欠点として、大部分が命令型プログラミングを使用する必要があることも意味します。

Performance of Script vs PackedScene

One last aspect to consider when choosing scenes and scripts is execution speed.

As the size of objects increases, the scripts' necessary size to create and initialize them grows much larger. Creating node hierarchies demonstrates this. Each Node's logic could be several hundred lines of code in length.

The code example below creates a new Node, changes its name, assigns a script to it, sets its future parent as its owner so it gets saved to disk along with it, and finally adds it as a child of the Main node:

# Main.gd
extends Node

func _init():
    var child = Node.new()
    child.name = "Child"
    child.script = preload("Child.gd")
    child.owner = self
    add_child(child)
using System;
using Godot;

public class Main : Resource
{
    public Node Child { get; set; }

    public Main()
    {
        Child = new Node();
        Child.Name = "Child";
        Child.Script = ResourceLoader.Load<Script>("child.gd");
        Child.Owner = this;
        AddChild(Child);
    }
}

Script code like this is much slower than engine-side C++ code. Each instruction makes a call to the scripting API which leads to many "lookups" on the back-end to find the logic to execute.

Scenes help to avoid this performance issue. PackedScene, the base type that scenes inherit from, defines resources that use serialized data to create objects. The engine can process scenes in batches on the back-end and provide much better performance than scripts.

結論

最後に、最善のアプローチは次のことを考慮することです。

  • いくつかの異なるプロジェクトで再利用され、すべてのスキルレベルの人々(自分自身を「プログラマー」とラベル付けしない人を含む)が使用する可能性のある基本的なツールを作成したい場合、それは良い機会であり、おそらくスクリプトで作成する必要があります。また、カスタム名/アイコンが付いたものでなければなりません。

  • ゲームに特化したコンセプト(モデル)を作りたい場合は、常にシーンである必要があります。シーンは追跡/編集が容易で、スクリプトよりもセキュリティが強化されます。

  • シーンに名前を付けたい場合は、スクリプトクラスを宣言し、シーンに定数として与えることで、3.1でもこれを行うことができます。スクリプトは、実際には名前空間になります:

    # game.gd
    extends Reference
    class_name Game # extends Reference, so it won't show up in the node creation dialog
    const MyScene = preload("my_scene.tscn")
    
    # main.gd
    extends Node
    func _ready():
        add_child(Game.MyScene.instance())