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

はじめに

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

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

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

`シングルトンパターン<https://ja.wikipedia.org/wiki/Singleton_パターン>`_ はシーン同士の絶え間ない情報の供給を必要とする一般的なユースケースを解決するためにとても有用なツールです。我々のケースでは異なる名前を付けるならば、同じシーンもしくはクラスを複数のシングルトンの為に再利用する事が可能です。

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

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

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

注釈

Godot won't make an AutoLoad a "true" singleton as per the singleton design pattern. It may still be instanced more than once by the user if desired.

自動読み込み

You can create an AutoLoad to load a scene or a script that inherits from 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 = (PlayerVariables)GetNode("/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()メソッドを使う事が出来ます(詳細はシーンツリーを見てください)。しかし、あなたがシーンの変更の際、より複雑なふるまいを必要としているならば、このメソッドはさらなる機能性を提供します。

はじめに、ここ: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 = (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");
}

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

注釈

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

Alternatively, if the loading time is relatively short (less than 3 seconds or so), you can display a "loading plaque" by showing some kind of 2D element just before changing the scene. You can then hide it just after the scene is changed. This can be used to indicate to the player that a scene is being loaded.