シングルトン(自動読み込み)

はじめに

Godotのシーンシステムは、強力で柔軟性がある一方、欠点があります: 複数のシーンに必要とされる情報を保存する為の方法が存在しません(例えばプレイヤーのスコアやインベントリ等)。

それを処理出来るにいくつかの代替方法がありますが、それらの方法はそれら自身の制限も伴います:

  • あなたは"master"シーンを使うことが出来、それの子供の他のシーンをロードもしくはアンロード出来ます。しかし、これの意味する所はこれらのシーンを個別にかつ、期待した様に正しく動作させる事が出来ないという事です。

  • 情報はディスクの user:// に保存が可能で、シーンが必要とするならばロード出来ますが、頻繁にデータをセーブとロードする事は面倒でさらに遅くもなります。

シングルトンパターンは、複数のシーン間で同じ情報を保つ必要があるという、一般的なケースにおいて非常に有用なツールです。私たちの場合では、同じシーンもしくはクラスを複数のシングルトンにて再利用する事は、それらシングルトンの名前が異なる限り可能です。

このコンセプトを使うと、オブジェクトを以下のように作る事が出来ます:

  • シーンが現在実行中でも問題なく、いつでもロードされます。

  • プレイヤー情報のようなグローバル変数を保管できます。

  • シーンの遷移の切り替えを操作する事ができます。

  • シングルトンのように動作します、デザインによりGDScript はグローバル変数をサポートしません。

自動読み込みされたノードとスクリプトは我々にこれらの特徴を与えます。

注釈

Godotでは、AutoLoadをシングルトンデザインパターンのような、「本当の」シングルトンにはしていません。必要であれば、ユーザーが複数回インスタンス化することもできます。

自動読み込み

AutoLoad を作成すれば、 Node を継承したシーンやスクリプトをロードすることができます。

注釈

スクリプトを自動読み込みする時、Nodeが作られそれにスクリプトがアタッチされます。このノードは他のシーンがロードされる前にルートビューポートに追加されます。

../../_images/singleton.png

シーンもしくはスクリプトを自動読み込みするには、メニューから プロジェクト > プロジェクト設定 を選び**自動読み込み**タブに切り替えて下さい。

../../_images/autoload_tab.png

ここにシーンとスクリプト追加する事が出来ます。リストへの登録は名前を必要とし、それはノードの name プロパティとして割り当てられます。登録の順序はグローバルシーンツリーに追加され、それは上下の矢印キーで操作できます。

../../_images/autoload_example.png

これの意味する所は以下のようにすれば"PlayerVariables"と名付けられたシングルトンにアクセス出来るという事です:

var player_vars = get_node("/root/PlayerVariables")
player_vars.health -= 10
var playerVariables = GetNode<PlayerVariables>("/root/PlayerVariables");
playerVariables.Health -= 10; // Instance field.

もし"有効"チェックがオンなら(デフォルトでオン)シングルトンは``get_node()``なしに、直接アクセス出来ます:

PlayerVariables.health -= 10
// Static members can be accessed by using the class name.
PlayerVariables.Health -= 10;

自動読み込みオブジェクトはちょうどシーンツリーの中の他のノードの様にアクセスされる事を心に留めて下さい。実際には、もし実行中のシーンツリーを見たら、自動読み込みされたノードが現れるでしょう:

../../_images/autoload_runtime.png

カスタムシーン・スイッチャー

このチュートリアルは自動読み込みを使ったシーンスイッチャーの構築を実演します。基本的なシーンの切り替えをする為に、SceneTree.change_scene()メソッドを使う事が出来ます(詳細はUsing SceneTreeを見てください)。しかし、あなたがシーンの変更の際、より複雑なふるまいを必要としているならば、このメソッドはさらなる機能性を提供します。

はじめに、ここ:autoload.zipからテンプレートをダウンロードしGodotで開いて下さい。

プロジェクトは2つのシーンを含みます: Scene1.tscnScene2.tscn 。それぞれのシーンはラベル表示されたシーンの名前と pressed() シグナルを接続されたボタンを含みます。プロジェクトを実行した時に、 Scene1.tscn の中で開始されます。しかし、押すためのボタンがありません。

Global.gd

Script タブに切り替え``Global.gd``という新しいスクリプトを作成して下さい。 Node が継承される事を確かめて下さい。

../../_images/autoload_script.png

次のステップはこのスクリプトを自動読み込みリストに追加する事です。メニューから **プロジェクト> プロジェクト設定**と開き、**自動読み込み**タブへ切り替え、ブラウズボタンをクリックして選択するか次のようにパスを入力します: res://Global.gd**追加**をクリックし自動読み込みリストへ追加して下さい:

../../_images/autoload_tutorial1.png

これでスクリプトはプロジェクトのシーンを実行する時は、毎回ロードされる様になりました。

スクリプトに戻り、 _ready() 関数内でカレントシーンを持って来る必要があります。両方のカレントシーン(片方はボタンを含む)と global.gd はルートの子供です。しかし自動読み込みされるノードはいつも最初です。つまりルートの最後の子供はいつも自動読み込みされたシーンになります。

extends Node

var current_scene = null

func _ready():
    var root = get_tree().get_root()
    current_scene = root.get_child(root.get_child_count() - 1)
using Godot;
using System;

public class Global : Godot.Node
{
    public Node CurrentScene { get; set; }

    public override void _Ready()
    {
        Viewport root = GetTree().GetRoot();
        CurrentScene = root.GetChild(root.GetChildCount() - 1);
    }
}

今、我々はシーンを遷移させる為の関数を必要としています。この関数はカレントシーンを開放し、要求されたものに置き換える必要があります。

func goto_scene(path):
    # This function will usually be called from a signal callback,
    # or some other function in the current scene.
    # Deleting the current scene at this point is
    # a bad idea, because it may still be executing code.
    # This will result in a crash or unexpected behavior.

    # The solution is to defer the load to a later time, when
    # we can be sure that no code from the current scene is running:

    call_deferred("_deferred_goto_scene", path)


func _deferred_goto_scene(path):
    # It is now safe to remove the current scene
    current_scene.free()

    # Load the new scene.
    var s = ResourceLoader.load(path)

    # Instance the new scene.
    current_scene = s.instance()

    # Add it to the active scene, as child of root.
    get_tree().get_root().add_child(current_scene)

    # Optionally, to make it compatible with the SceneTree.change_scene() API.
    get_tree().set_current_scene(current_scene)
public void GotoScene(string path)
{
    // This function will usually be called from a signal callback,
    // or some other function from the current scene.
    // Deleting the current scene at this point is
    // a bad idea, because it may still be executing code.
    // This will result in a crash or unexpected behavior.

    // The solution is to defer the load to a later time, when
    // we can be sure that no code from the current scene is running:

    CallDeferred(nameof(DeferredGotoScene), path);
}

public void DeferredGotoScene(string path)
{
    // It is now safe to remove the current scene
    CurrentScene.Free();

    // Load a new scene.
    var nextScene = (PackedScene)GD.Load(path);

    // Instance the new scene.
    CurrentScene = nextScene.Instance();

    // Add it to the active scene, as child of root.
    GetTree().GetRoot().AddChild(CurrentScene);

    // Optionally, to make it compatible with the SceneTree.change_scene() API.
    GetTree().SetCurrentScene(CurrentScene);
}

Object.call_deferred()の使用時は、カレントシーンが完了してから、2つ目の関数のすべてのコードが1回だけ実行されます。従って、カレントシーンは使用されている間には削除されません (すなわち、カレントシーンのコードはまだ実行中です)。

最後に、2つのシーンの空のコールバック関数を満たさなければなりません。

# Add to 'Scene1.gd'.

func _on_Button_pressed():
    Global.goto_scene("res://Scene2.tscn")
// Add to 'Scene1.cs'.

public void OnButtonPressed()
{
    var global = GetNode<Global>("/root/Global");
    global.GotoScene("res://Scene2.tscn");
}

そして

# Add to 'Scene2.gd'.

func _on_Button_pressed():
    Global.goto_scene("res://Scene1.tscn")
// Add to 'Scene2.cs'.

public void OnButtonPressed()
{
    var global = GetNode<Global>("/root/Global");
    global.GotoScene("res://Scene1.tscn");
}

プロジェクトの実行とテストをするとボタンを押す事によりシーンの間を切り替えられます。

注釈

シーンが小さい時にはこの遷移は瞬時に行われます。しかし、シーンがより複雑な場合、表示されるのに大量の時間がかかります。これの操作を学ぶには、次のチュートリアルを見て下さい:バックグラウンド読み込み

また、読み込み時間が比較的短い場合 (3秒以下など) は、シーンが変わる直前に何らかの2D要素を表示して「ローディング中」画面を表示することもできます。そして、シーンが切り替わった直後にそれを隠すことができます。これは、シーンがロード中であることをプレイヤーに示すために使用できます。