🚴

Godot C#でシーン遷移

2024/03/13に公開

はじめに

Unityでandroidのゲームを作ろうとしたら全くAndroid Build Supportをインストールできず、
全部アンインストールし直してもダメだったので嫌になってGodotで作ることにしました。

Godotで分からないことがあったのでメモがてら書こうと思います。

シーンの移動

以下のようなNodeを用意します。

public partial class SceneMover : Node
{
    /// <summary>
    /// シーン移動します。
    /// </summary>
    /// <param name="path">.tscnを含めたシーンのパスの文字列</param>
    public void MoveScene(string path)
    {
        var nextScene = GD.Load<PackedScene>(path);
        GetTree().ChangeSceneToPacked(nextScene);
    }
}

あとはこれを使いたいNodeから使ってやります。

例えば特定のエリアに入ったらゴールして次のステージに移動するようにしたい場合、こんな感じ。

using Godot;

public partial class GoalArea : Area2D
{
	[Export] private SceneMover _sceneMover; // [Export] ≒ Unityの[SerializeField]

	public override void _Ready()
	{
        // エリアに入ったとき対象のNodeを引数に購読できる
		BodyEntered += (body) =>
		{
			if (body is Player)
			{
				_sceneMover.MoveScene("path/to/next/scene.tscn");
			}
		};
	}
}

たくさんのステージを順に移動する場合

例えば100ステージあるゲームで1ステージクリア後に2ステージ目、その次に3ステージ目、のように遷移する場合。

static classを利用してStage数を保存しておくと実現できます(ベストプラクティスかは分からない…)

public static class StageNumber
{
    public static int Current { get; private set; } = 1;

    public static void Next()
    {
        Current++;
    }
}
using Godot;

public partial class SceneMover : Node
{
    /// <summary>
    /// シーン移動します。
    /// </summary>
    /// <param name="path">.tscnを含めたシーンのパスの文字列</param>
    public void MoveScene(string path)
    {
        var nextScene = GD.Load<PackedScene>(path);
        GetTree().ChangeSceneToPacked(nextScene);
    }
    
    public void MoveToNextStage()
    {
        var stageNumber = StageNumber.Current;
        var nextScene = GD.Load<PackedScene>($"res://nodes/scenes/stages/{stageNumber + 1}.tscn");
        StageNumber.Next();
        GetTree().ChangeSceneToPacked(nextScene);
    }
}
using Godot;
using View;

public partial class GoalArea : Area2D
{
	[Export] private SceneMover _sceneMover;

	public override void _Ready()
	{
		BodyEntered += (body) =>
		{
			GD.Print("GoalArea: BodyEntered");
			if (body is Player)
			{
				_sceneMover.MoveToNextStage();
			}
		};
	}
}

あとはtscn名を連番にして、各シーンにexportをちゃんとアタッチすれば、ちゃんと遷移できます。

イメージ

自分はSERVICEというNodeの下にこういったスクリプトを1つずつぶら下げていこうと思っています。

ところでこのアタッチの作業面倒なんだけど、何とかならないかな…

追記(24/03/15)

GetTree().ChangeSceneToPacked()でエラーが出てた。

正直いまいちよくわからないけど、とりあえず言われた通りCallDeferred()を使うと解決しました。多分シーンの遷移とオブジェクトの破棄のタイミングがうまくかみ合ってなかったのかな…?

using Godot;

public partial class SceneMover : Node
{
    /// <summary>
    /// シーン移動します。
    /// </summary>
    /// <param name="path">.tscnを含めたシーンのパスの文字列</param>
    public void MoveScene(string path)
    {
        var nextScene = GD.Load<PackedScene>(path);
-        GetTree().ChangeSceneToPacked(nextScene);
+        GetTree().CallDeferred("change_scene_to_packed", nextScene);
    }
    
    public void MoveToNextStage()
    {
        var stageNumber = StageNumber.Current;
        var nextScene = GD.Load<PackedScene>($"res://nodes/scenes/stages/{stageNumber + 1}.tscn");
        StageNumber.Next();
-        GetTree().ChangeSceneToPacked(nextScene);
+        GetTree().CallDeferred("change_scene_to_packed", nextScene);
    }
}

Discussion