Unityで、ビルド設定の最初から再生するエディタ拡張を作成した

# 前置き
（編集時点の）Unity  では、再生ボタンを押すと、今エディタで開いているシーンが再生されます。

しかし、ゲームやアプリによっては初期化やメインメニュー、素材の読み込みなど、再生してほしいシーンがある場合があります。

エディタ拡張として、ビルド設定の最初のシーンから再生するものを作りました。


また、なぜこのようなコードになっているかを説明してエディタ拡張を作成される方の参考にもしてほしいと思っています。


（本来は、標準で入っていて欲しい機能なのですが..）



ダウンロードして実行すると、「最初のシーンから実行」が追加され選ぶと、ビルド設定の最初から実行されるようになります。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15024/6002ef02-5fad-3385-3514-439d14f93337.png)

# パッケージのダウンロード

ダウンロードして実行して importしてください。

https://github.com/yuki2006/unity_play_at_first_scene/releases/download/1.0.0/PlayAtFirstScene.unitypackage


# 作成コード

https://github.com/yuki2006/unity_play_at_first_scene/blob/1.0.0/Editor/PlayAtFirstScene.cs


# なぜ作ったか

ゲームをビルドして実行するとビルド設定の最初にあるシーンから再生されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/15024/cf6e3ec0-cfc9-25e6-e788-75e67ec5b22a.png)


ビルドとは、各OSのネイティブアプリとして実行するためにexeやapkファイルなどを書き出すものです。
このビルドしたものは、Unityが今開いているシーンなども関係なく、自分がこうしたいと思っていても、ビルド設定の最初から再生されます。
それはそれで良いのですが、ゲーム開発中で別のシーンを編集中のときはわざわざ最初のシーンを開いて、再生するのが余計なことを考えないといけないので、面倒です。

その上で、標準の再生ボタンは、今開いているシーンを再生するという機能そのままにしたいと思いました。

# 機能

1. メニューから再生すると、ビルド設定の最初の再生される。（変更がある場合は保存の確認）
2. 終了時は、再生直前に開いていたシーンを復元する。
    1. 特に、複数シーンを開いている場合も復元する。（Activeも復元）
3. Unityの標準の再生ボタンを押したら、そのシーンから実行されるようにする（標準の挙動のままになるようにする）

# 説明の概要

・　**UnityのEditorでの操作中** と **ゲーム中** は、同じスクリプトでも別空間で実行されているので注意する。
（以下、前者をエディタ空間, 後者をゲーム空間としている）

・　**Editorでの操作中に再生するイベント**　と　**ゲーム中にゲームが終わったイベント** をそれぞれ捕捉する。

・　`EditorSceneManager.LoadScene` はゲーム空間用、`EditorSceneManager.OpenScene` はエディタ空間用のシーンのロードのメソッド

# 説明

```C#
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
```

で、C#のeventにより、Unityの再生モードの検知します。

さてこれをどこで、設定するべきかですが、
今回
[InitializeOnLoadMethod]
を使うようにしました。

これをつけると、 **Unityがスクリプトのロードが完了したとき** に、このメソッドを実行してくれるというものです。

**Unityがスクリプトのロードが完了したとき** というのは　**Editor自身を起動したタイミング**と、**ゲーム自体を起動したタイミング** の2つがあるのがミソです。

ここで、エディタ拡張に関して重要なことがあるのですが、　**UnityのEditorでの操作中** と **ゲーム中** は、スクリプト自体が別空間で実行されているということです。

そのため[InitializeOnLoadMethod]は2箇所で呼ばれることになるのですね。


例えば static フィールドに状態をもたせて、情報のやり取りすればいいと考えられそうなのですが、別空間で実行されるため、それはかないません。

そのために、状態のやり取りは、PlayerPrefsを経由しています。PlayerPrefsは、ゲーム中もEditor中も同じファイル空間を使用します。
（実装としては、ファイルなどでやり取りしても良いと思います。）

また、今回、再生の状態変化のイベントを取得したいわけですが、
**エディタ空間で再生するイベント**　と　**ゲーム空間でゲーム終了のイベント** をそれぞれ捕捉する事が必要です。

イベントを捕捉する処理をどこで書けばいいのかという問題になりますが、

ここで、C#のstaticコンストラクタは、実際に使われるタイミングで始めて呼び出されるということです。

たとえば、SetEventメソッドではなく、下のようにstaticコンストラクタでもよいのでは考えられるのですが、
PlayAtFirstSceneのクラスのメソッドなど何かが呼ばれる必要になったときではないと呼ばれないので、
**エディタ空間** でも呼び出し、**ゲーム空間** でもPlayAtFirstSceneの何かを呼び出す処理が必要になってしまいます。


```C#
public class PlayAtFirstScene
{
    static PlayAtFirstScene()
    {
        EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
    }
}
```

実は、似ている役割の [InitializeOnLoad] を使うことにより、PlayAtFirstSceneの何かを使わなくても必ず最初に呼び出すということは可能ですのでこちらでも大丈夫です。

```C#
[InitializeOnLoad]
public class PlayAtFirstScene
{
    static PlayAtFirstScene()
    {
        EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
    }
}
```

今回はメソッドに指定できる[InitializeOnLoadMethod]を選んでいます。


メニューを選択すると、EnteredPlayModeが呼ばれますが、ゲームを再生する前に、現在のシーン情報を保存しています。

保存後、`EditorSceneManager.OpenScene` でシーンをロードしていますが、ここはエディタ空間ですので、 このメソッドを使います。
ゲームでよく使う `EditorSceneManager.LoadScene` ではないことに注意してください。
ちなみに、これはゲーム空間でしか使えないメソッドになります。
（なぜ違うのかは興味ありますが）

例えば、エディタ空間で`EditorSceneManager.LoadScene`を呼び出すと、実行時エラーになります。

OnPlayModeStateChangedメソッドで、シーンの復元をするのですが、

（ちなみに、シーンの復元をしないと、ビルド設定の最初のシーンが開かれてしまいます。）

4種類のイベントで呼ばれますが、その時の状態も気をつけないといけないです。

    EnteredEditMode   エディタ空間
    ExitingEditMode   エディタ空間
    EnteredPlayMode   ゲーム空間
    ExitingPlayMode   ゲーム空間


EnteredEditModeとExitingPlayModeは、ほぼ同タイミングで呼ばれますが、実行される空間は異なります。

例えば、ExitingPlayModeのときに、`EditorSceneManager.LoadScene` を使ってシーンをロードしてもゲームが終了したら、元に戻ってしまいます。

なお、ゲームの終了ボタンは、標準のボタンですので、PlayerPrefsに情報が存在すれば復元処理するということにしております。


ということで、EnteredEditModeを使うようにして、保存したデータをもとに、`EditorSceneManager.OpenScene`で復元をします。

このように引数をつけると追加のシーンにすることができたり、
`EditorSceneManager.OpenScene(scenePaths[i], OpenSceneMode.Additive);`

アクティブシーンの設定などの実装も入っております。
`SceneManager.SetActiveScene(scene);`

# まとめ

この機能によってだいぶ開発が楽になった。（自己比）
Unityのエディタ拡張を作る場合は、　ゲーム空間かエディタ空間かどうかを区別してコーディングしないといけない。