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

はじめに

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

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

  • あなたは"master"シーンを使うことが出来、それの子供の他のシーンをロードもしくはアンロード出来ます。しかし、これの意味する所はこれらのシーンを個別にかつ、期待した様に正しく動作させる事が出来ないという事です。
  • 情報はディスクの user:// に保存が可能で、シーンが必要とするならばロード出来ますが、頻繁にデータをセーブとロードする事は面倒でさらに遅くもなります。

Singleton Patternはシーン同士の絶え間ない情報の供給を必要とする一般的なユースケースを解決するためにとても有用なツールです。我々のケースでは異なる名前を付けるならば、同じシーンもしくはクラスを複数のシングルトンの為に再利用する事が可能です。

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

  • シーンが現在実行中でも問題なく、いつでもロードされます
  • プレイヤー情報のようなグローバル変数を保管できます
  • シーンの切り替えと、シーン遷移同士の切り替えを操作する事ができます
  • シングルトンのように動作します、デザインによりGDScript からはグローバル変数をサポートしません

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

自動読み込み

:ref:`Node <class_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 = (PlayerVariables)GetNode("/root/PlayerVariables");
playerVariables.Health -= 10; // Instance field.

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

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

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

../../_images/autoload_runtime.png

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

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

はじめに、ここ: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 はルートの子供です。しかし自動読み込みされたノードはいつも1番です。つまりルートの最後の子供はいつもロードされたシーンになります。

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 = (Global)GetNode("/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 = (Global)GetNode("/root/Global");
    global.GotoScene("res://Scene1.tscn");
}

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

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