シーンとスクリプトを使用する場合
シーンとスクリプトの違いについてはすでに説明しました。スクリプトは、命令型コードでエンジンクラスの拡張を定義し、宣言型コードでシーンを定義します。
Each system's capabilities are different as a result. Scenes can define how an extended class initializes, but not what its behavior actually is. Scenes are often used in conjunction with a script, the scene declaring a composition of nodes, and the script adding behavior with imperative code.
匿名型
スクリプトのみを使用してシーンのコンテンツを完全に定義することもできます。これは本質的には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.instantiate() # Different method call.
var my_inherited_scene = MyScene.instantiate(PackedScene.GEN_EDIT_STATE_MAIN) # Create scene inheriting from MyScene.
using Godot;
public partial class Game : Node
{
public static CSharpScript MyNode { get; } =
GD.Load<CSharpScript>("res://Path/To/MyNode.cs");
public static PackedScene MyScene { get; } =
GD.Load<PackedScene>("res://Path/To/MyScene.tscn");
private Node _node;
private Node _myNode;
private Node _myScene;
private Node _myInheritedScene;
public Game()
{
_node = new Node();
_myNode = MyNode.New().As<Node>();
// Different than calling new() or MyNode.New(). Instantiated from a PackedScene.
_myScene = MyScene.Instantiate();
// Create scene inheriting from MyScene.
_myInheritedScene = MyScene.Instantiate(PackedScene.GenEditState.Main);
}
}
また、エンジンとスクリプトコードの処理速度が異なるため、スクリプトの動作はシーンよりも少し遅くなります。ノードが大きく複雑になるほど、ノードをシーンとして構築する理由が増えます。
名前付き型
場合によっては、ユーザーはエディタ自体の中でスクリプトを新しい型として登録できます。これにより、ノードまたはリソース作成ダイアログに新しい型として表示され、オプションのアイコンが表示されます。このような場合、ユーザーがスクリプトを使用する機能は大幅に合理化されます。次の方法よりも...
使用するスクリプトの基本型を知っている。
その基本型のインスタンスを作成します。
ノードにスクリプトを追加します。
この手順の代わりに登録済みスクリプトを使用すると、スクリプト化された型をシステム内の他のノードやリソースと同様な、作成オプションの一つにすることが出来ます。作成ダイアログには、型を名前で検索するための検索バーがあります。
型を登録するためには 2つのシステムがあります:
-
エディタのみ。型名は実行時にはアクセスできません。
継承されたカスタム型はサポートされていません。
初期化ツール。スクリプトを使用してノードを作成します。これ以外の方法はありません。
エディタは、スクリプトの型や、他のエンジン内の型やスクリプトとの関係を認識しません。
ユーザーがアイコンを定義できます。
スクリプトリソースを抽象化して扱うので、すべてのスクリプト言語で動作します。
EditorPlugin.add_custom_type を使用してセットアップします。
-
エディタとランタイムにアクセスできます。
継承関係を完全に表示します。
スクリプトを使用してノードを作成しますが、エディタから型を変更したり、型を拡張したりすることもできます。
エディタは、スクリプト、スクリプトクラス、およびエンジンのC++クラス間の継承関係を認識します。
ユーザーがアイコンを定義できます。
エンジン開発者は、言語のサポートを手動で追加する必要があります(名前の公開とランタイムのアクセシビリティの両方)。
エディタは、プロジェクトフォルダをスキャンし、すべてのスクリプト言語の公開名を登録します。各スクリプト言語は、この情報を公開するための独自のサポートを実装する必要があります。
どちらの方法でも作成ダイアログに名前が追加されますが、特にスクリプト クラスを使用すると、ユーザーはスクリプト リソースを読み込まずに型名にアクセスできます。インスタンスの作成と定数または静的メソッドへのアクセスは、どこからでも実行可能です。
このような機能を使用すると、それによってユーザーに付与される使いやすさから、その型をシーンのないスクリプトにしたい場合があります。このようにプラグインを開発したり、デザイナーが使用する社内ツールを作成したりすることで、物事をより簡単な手順で進められるようになります。
欠点として、大部分が命令型プログラミングを使用する必要があることも意味します。
スクリプトとPackedSceneのパフォーマンス
シーンとスクリプトを選択する際に考慮すべき最後の観点は、実行速度です。
オブジェクトのサイズが大きくなるにつれて、オブジェクトの生成と初期化に必要なスクリプトのサイズがどんどん大きくなります。ノード階層を作成すると、これが実際に発生します。各ノードのロジックは、長さが数百行のコードになる可能性があります。
以下のコード例は、新しい Node を作成し、その名前を変更し、スクリプトをそれに割り当て、将来の親(Main)を所有者として設定し、それとともにディスクに保存され、最後に Main ノードの子として追加します:
# main.gd
extends Node
func _init():
var child = Node.new()
child.name = "Child"
child.script = preload("child.gd")
add_child(child)
child.owner = self
using Godot;
public partial class Main : Node
{
public Node Child { get; set; }
public Main()
{
Child = new Node();
Child.Name = "Child";
var childID = Child.GetInstanceId();
Child.SetScript(GD.Load<Script>("res://Path/To/Child.cs"));
// SetScript() causes the C# wrapper object to be disposed, so obtain a new
// wrapper for the Child node using its instance ID before proceeding.
Child = (Node)GodotObject.InstanceFromId(childID);
AddChild(Child);
Child.Owner = this;
}
}
このようなスクリプトコードは、エンジン側のC++コードよりもはるかに遅いです。各命令がスクリプトAPIへの呼び出しを行うため、実行するロジックを見つけるためにバックエンドで多くの「検索」が発生します。
シーンは、このパフォーマンスの問題を回避するのに役立ちます。PackedScene は、シーンが継承する基本型であり、シリアル化されたデータを使用してオブジェクトを作成するリソースを定義します。エンジンは、バックエンドでシーンをバッチ処理し、スクリプトよりもはるかに優れたパフォーマンスを提供できます。
結論
最後に、最善のアプローチは次のことを考慮することです。
いくつかの異なるプロジェクトで再利用され、すべてのスキルレベルの人々(自分自身を「プログラマー」とラベル付けしない人を含む)が使用する可能性のある基本的なツールを作成したい場合、それは良い機会であり、おそらくスクリプトで作成する必要があります。また、カスタム名/アイコンが付いたものでなければなりません。
ゲームに特化したコンセプト(モデル)を作りたい場合は、常にシーンである必要があります。シーンは追跡/編集が容易で、スクリプトよりもセキュリティが強化されます。
シーンに名前を付けたい場合は、スクリプトクラスを宣言し、それにシーンを定数として割り当てることで、ある程度それを実現できます。スクリプトは事実上、名前空間になります:
# game.gd class_name Game # extends RefCounted, so it won't show up in the node creation dialog. extends RefCounted const MyScene = preload("my_scene.tscn") # main.gd extends Node func _ready(): add_child(Game.MyScene.instantiate())
// Game.cs public partial class Game : RefCounted { public static PackedScene MyScene { get; } = GD.Load<PackedScene>("res://Path/To/MyScene.tscn"); } // Main.cs public partial class Main : Node { public override void _Ready() { AddChild(Game.MyScene.Instantiate()); } }