[Unity] Sceneの役割や使い方をざっくりまとめた

Unity触る上で避けては通れないのでざっくり書いた
 
## Sceneとは？
- https://docs.unity3d.com/ja/current/Manual/CreatingScenes.html
- UnityはGameObjectを任意の座標に配置したり、GameObjectに任意のコンポーネントをアタッチすることでオブジェクトを描画したり、ゲーム中の動作を実現したりしている
- 「GameObjectの配置座標」や「GameObjectにアタッチされたコンポーネント」等はScene(.unity拡張子)ファイルに保存される
- 任意のSceneを呼びこむことで、そのsceneに含まれているGameObjectを展開することが出来る
- 複数のSceneを用意し適宜切り替えることで、容易に画面遷移を実現することが出来る
- またUnity5.3から複数のSceneを同時に呼び出すことが可能になった
  - 参考: [【Unity】5.3に追加されたマルチ・シーン・エディティングで出来る事 - テラシュールブログ](http://tsubakit1.hateblo.jp/entry/2016/01/13/233000)

## SceneをUnityEditor上で作る
- Pojectビュー上の[create]ボタン (もしくは右クリック > Create) からSceneを選択
- もしくは上部メニューより **[Edit] => [NewScene]** を選択で新規Sceneが読み込まれる
  - **[Ctrl(Cmd) + n]** がショートカット

## SceneをUnityEditor上で読み込む
- Project上での任意のSceneファイルをダブルクリック
  - 現在Hierarchy上で開かれているSceneは閉じられる(アンロードされる)ので注意
  - 複数のSceneを読み込んでいる場合、全て閉じられるので注意
- 複数のSceneを同時に読み込みたい場合は任意のSceneファイルをHierarchy上にドラッグ&ドロップ

![addscene.gif](https://qiita-image-store.s3.amazonaws.com/0/102664/f22e87a2-9ce7-57c5-df91-4810254c39f2.gif)
(実際はCameraを破棄されないGameObjectとして複数混在させないほうが良いがDEMO用に...)

## SceneをUnityEditor上でアンロードする
- https://docs.unity3d.com/ja/current/Manual/MultiSceneEditing.html
- Hierarchy上の任意のシーンを右クリック (もしくはScene名右のメニューを展開)
- **UnloadScene**
  - シーンをアンロードされるが、Hierarchy内に表示されたままとなる
  - Editor上で一時的に特定のシーンを無効にしたいときに使用する
- **RemoveScene**
  - シーンをアンロードし、Hierarchy上からも消す

![unloadscene.gif](https://qiita-image-store.s3.amazonaws.com/0/102664/64f63c30-c1f3-9e8e-fdc4-d081d7a96009.gif)

## Sceneの保存形式をバイナリからテキストに変更する
- デフォルトではバイナリファイルで保存される
- この状態でgit等でバージョン管理した際にコンフリクトすると解消が困難
  - (テキストに切り替えても仕様ベースの問題で解消困難なケースは多々あるけど....)
- 設定からテキスト(YAML形式)として保存するようにしておくことをオススメ

### 変更方法
- **[Edit] => [Project Settings] => [Editor]** でEditorSettingsを表示
- **AssetSerialization** を **ForceText** に変更

![forcetext.png](https://qiita-image-store.s3.amazonaws.com/0/102664/d7957947-dcef-6521-d691-b9a0dae524fa.png)

## BuildSettingsにSceneを定義する
- https://docs.unity3d.com/jp/current/Manual/BuildSettings.html
- 上部メニューより **[File] => [BuildSettings...]** を開く
- 上部の `Scenes in Build` にSceneを登録しないと実行時に動的に呼び出せないので注意
  - SceneをEditor上で開いた状態で **[Add Open Scenes]** を選択する
  - Project上のSceneファイルをドラッグ&ドロップする

![buildsetting.gif](https://qiita-image-store.s3.amazonaws.com/0/102664/09a4e0ef-06ec-667c-9952-0445659e6ac3.gif)

## 実行中にScript上からSceneを操作する
### SceneManagerについて
- https://docs.unity3d.com/ja/current/ScriptReference/SceneManagement.SceneManager.html
- Unity5.3から ` SceneManager` クラスを使うようになった
- `UnityEngine.SceneManagement` 空間上に存在するクラスなのでusingしておく必要がある

```csharp
using UnityEngine.SceneManagement;
```

### 任意のSceneに切り替える
- https://docs.unity3d.com/ja/current/ScriptReference/SceneManagement.SceneManager.LoadScene.html
- 現状のSceneを全て破棄しつつ、指定したSceneを呼び込む
- 1Scene1機能運用でタイトルからメニューに遷移するとき等に使う

```csharp
using UnityEngine;
using UnityEngine.SceneManagement;

public class SceneTest : MonoBehaviour
{
    public void MoveScene()
    {
        // 引数にシーン名を指定する
        // Build Settings で確認できる sceneBuildIndex を指定しても良い
        SceneManager.LoadScene("stage1"); 
    }
}
```

### 任意のSceneを追加で読み込む
- https://docs.unity3d.com/ja/current/ScriptReference/SceneManagement.LoadSceneMode.html
- LoadSceneメソッドは第二引数に`LoadSceneMode`を指定できる
  - デフォルトは `LoadSceneMode.Single`
- 第二引数に `LoadSceneMode.Additive` を指定することで追加で読み込むことが出来る

```csharp
using UnityEngine;
using UnityEngine.SceneManagement;

public class SceneTest : MonoBehaviour
{
    public void AddScene()
    {
        SceneManager.LoadScene("stage2", LoadSceneMode.Additive); 
    }
}
```

### 任意のSceneを非同期に読み込む
- https://docs.unity3d.com/ja/current/ScriptReference/SceneManagement.SceneManager.LoadSceneAsync.html
- LoadSceneAsyncメソッドを使う
- オープンワールド型のゲームを作ってるときに追加のフィールドを現状の動作を阻害すること無く読み込みたいときなんかに使えそう

```csharp
using UnityEngine;
using UnityEngine.SceneManagement;

public class SceneTest : MonoBehaviour
{
    public void AddSceneAsync()
    {
        SceneManager.LoadSceneAsync("stage3", LoadSceneMode.Additive); 
    }
}
```

### 任意のSceneをアンロードする
- https://docs.unity3d.com/ja/current/ScriptReference/SceneManagement.SceneManager.UnloadScene.html
- UnloadSceneメソッドを使う

```csharp
using UnityEngine;
using UnityEngine.SceneManagement;

public class SceneTest : MonoBehaviour
{
    public void RemoveScene()
    {
        // 引数にシーン名を指定する
        // Build Settings で確認できる sceneBuildIndex を指定しても良い
        SceneManager.UnloadScene("stage1"); 
    }
}
```

### 現在読み込まれているSceneの名称をリストで取得する
- https://docs.unity3d.com/ja/current/ScriptReference/SceneManagement.SceneManager-sceneCount.html
- https://docs.unity3d.com/ja/current/ScriptReference/SceneManagement.SceneManager.GetSceneAt.html
- https://docs.unity3d.com/ja/current/ScriptReference/SceneManagement.Scene.html
- 読み込まれたSceneにはindexが割り振られる
    - Build Settings で視認できるbuildIndexとは別物
- GetSceneAtメソッドと割り振られたSceneを使って任意のsceneを呼び出す

```csharp
using UnityEngine.SceneManagement;
using System.Collections.Generic;

public static class SceneUtil
{
    public static List<string> GetActiveSceneNames()
    {
        int sceneCount = SceneManager.sceneCount;
        List<string> activeSceneNames = new List<string>();
        for (int i = 0; i < sceneCount; i++)
        {
            Scene scene = SceneManager.GetSceneAt(i);
            activeSceneNames.Add(scene.name);
        }
        return activeSceneNames;
    }
}
```

```csharp
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections.Generic;

public class SceneTest : MonoBehaviour
{
    public void ShowActiveSceneNames()
    {
        foreach (string name in SceneUtil.GetActiveSceneNames())
        {
            Debug.Log(name);
        }
    }
}
```

### 現在のシーンをアンロードし、指定したScene群を読み込む
- もっと綺麗に書ける気がするけどざっくり

```csharp
using UnityEngine.SceneManagement;
using System.Collections.Generic;
using System.Linq;

public static class SceneUtil
{
    public static void MoveScenes(List<string> sceneNames)
    {
        List<string> activeSceneNames = GetActiveSceneNames();
        Dictionary<string, bool> activeSceneNameMap = activeSceneNames.ToDictionary(name => name, name => true);
        Dictionary<string, bool> sceneNameMap = sceneNames.ToDictionary(name => name, name => true);

        // シーンのロード
        // シーンが空にならないように先に行う
        // 既に読み込まれている場合、そのまま利用する
        foreach (string sceneName in sceneNames)
        {
             if (!activeSceneNameMap.ContainsKey(sceneName))
             {
                 SceneManager.LoadScene(sceneName, LoadSceneMode.Additive);
             }
        }

        // シーンのアンロード
        // 読み込むシーンに指定されている場合はアンロードしない
        foreach (string activeSceneName in activeSceneNames)
        {
             if (!sceneNameMap.ContainsKey(activeSceneName))
             {
                 SceneManager.UnloadScene(activeSceneName);
             }
        }
    }

    public static List<string> GetActiveSceneNames()
    {
        int sceneCount = SceneManager.sceneCount;
        List<string> activeSceneNames = new List<string>();
        for (int i = 0; i < sceneCount; i++)
        {
            Scene scene = SceneManager.GetSceneAt(i);
            activeSceneNames.Add(scene.name);
        }
        return activeSceneNames;
    }
}
```

```csharp
using UnityEngine;
using UnityEngine.SceneManagement;
using System.Collections.Generic;

public class SceneTest : MonoBehaviour
{
    public void MoveScenes()
    {
        List<string> sceneNames = new List<string>() { "stage1", "stage2", "stage3" };
        SceneUtil.MoveScenes(sceneNames);
    }
}
```

### Sceneを跨いで使用するGameObjectを用意する
- https://docs.unity3d.com/ja/current/ScriptReference/Object.DontDestroyOnLoad.html
- 残念ながら `Cross-scene references` (Scene間を跨ぐ参照) は制限されており、現在行えない
  - public にしたフィールドに他SceneのGameObjectをドラッグしても適応できない
- `DontDestroyOnLoad` メソッドで指定されたオブジェクトは `DontDestroyOnLoad` Sceneに移動し、これは元のSceneを破棄しても破棄されずに残る
  - `Find` や `FindWithTag` メソッドで取得する感じ
- これを使ったシングルトンデザインパターンがよく使われている印象
- 参考: [【Unity】シーン間でスコアを共有　まとめ](http://tsubakit1.hateblo.jp/entry/2015/11/07/024350 - テラシュールブログ)

## Editor拡張でSceneを取り扱う
- https://docs.unity3d.com/ScriptReference/SceneManagement.EditorSceneManager.html
- EditorSceneManagerを使用する
  - 大体 UnityEngine の SceneManager と同じような感じで使えて、やれることが増えた感じ
  - SaveScenes / CloseScene 等

### 例) 全てのシーンのTextComponentを指定したフォントに置き換える君
- 全てのシーンを開き、フォントを置き換えた上で、最初に開いていたシーン以外をアンロードする
- https://gist.github.com/lycoris102/9439fd0ad54fe9b96ff419d4b566b8f8