SceneのAssetBundleはいつUnloadするべきか

## `AssetBundle`という闇

`AssetBundle`、**闇**ですね！
めんどくせーからぜってーやんねーからな！　と叫び続けていたのですが、遂にやることになってしまいました。
噂には聞いてましたが、マジで**闇**深い……これでも改善されてるってんだから参るぜ……。[^gc]
*メサイア*と噂される`Addressable`はその影が見え隠れしているだけで、まだ到来はしていないとの噂です。

スクリプト[^dark]でなければなんでも入るが故の**闇**と思います。

[^gc]: 依存関係の解決のために自前で参照カウンタ実装する必要があるのマジでわけわかんなくて面白い
[^dark]: 黒魔術を使えばスクリプトも叩き込めるそうですが実際ヤバい

## 検証
なんでも入るので、当然ですが`Scene`も入ります。
`Scene`の`AssetBundle`は他の`Asset`と扱いが違って、中に`Scene`しか含むことができません。
`AssetBundle`は`Unload`しなければリークしますが、`Scene`の`AssetBundle`はいつ`Unload`すればいいのでしょうか？
他の`Asset`と同じように、読み込んだら`AssetBundle.Unload(false)`で開放しちゃっても大丈夫なんでしょうか？
ちょっと気になって調べてみたのですが、それらしい情報は見つかりませんでした。
そう手間でもないので試してみたいと思います。

環境
`2019.4.0`
`Editor`

### Scene

なにもない`BaseScene`と、オブジェクトが壊れた検知用に`Cube`を配した`BundleScene`を作ります。
`BundleScene`は`AssetBundle`にして`StreamingAssets`に置いておきます。

`BundleScene`
<img width="432" alt="スクリーンショット 2020-07-17 2.29.52.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146245/a18df608-d091-100e-3582-6abc09eb2cf7.png">

`BundleScene(missingのすがた)`
<img width="432" alt="スクリーンショット 2020-07-17 2.30.00.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146245/8e33a70f-1d2a-eadb-8fec-6574115ed3d7.png">

### Script

[ContextMenuItem](https://docs.unity3d.com/ScriptReference/ContextMenuItemAttribute.html)を使って`Inspector`上から操作できるようにします。

<img width="342" alt="スクリーンショット 2020-07-17 0.15.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146245/d3da007c-f5a1-334c-e0bc-0cc0d450d665.png">

- Base  
`BaseScene`をロードします
- LoadScene  
`AssetBundle`を読み込んでから`BundleScene`をロードします
- UnloadScene  
`BundleScene`をアンロードします
- UnloadBundle  
`AssetBundleをアンロードします`

<details>
<summary>コード全文</summary><div>

```c#:SceneSwitcher
using System.IO;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityEngine.SceneManagement;

public class SceneSwitcher : MonoBehaviour
{
    #region LifeCycle

    private void Start()
    {
        var current = FindObjectsOfType<SceneSwitcher>();
        if (current.Length > 1)
        {
            GameObject.Destroy(this.gameObject);
            return;
        }

        DontDestroyOnLoad(this.gameObject);

        SceneManager.sceneLoaded += (scene, mode) =>
        {
            if (scene.buildIndex == 0)
            {
                _base = true;
            }
        };

        SceneManager.sceneUnloaded += scene =>
        {
            if (scene.buildIndex == 0)
            {
                _base = false;
            }
        };
    }

    #endregion

    #region LoadBase

    [SerializeField, ContextMenuItem("Base", "LoadBase")]
    private bool _base = false;

    private void LoadBase()
    {
        SceneManager.LoadSceneAsync(0);
    }

    #endregion

    #region LoadSceneFromAssetBundle

    private AssetBundle _sceneBundle = null;
    private string _sceneName;

    [SerializeField, ContextMenuItem("loadScene", "Load")]
    private bool loadScene = false;

    private async void Load()
    {
        if (_sceneBundle == null)
        {
            var bundlePath = Path.Combine(Application.streamingAssetsPath, "bundlescene");
            var buffer = File.ReadAllBytes(bundlePath);
            using (var memoryStream = new MemoryStream(buffer))
            {
                // LoadFromFileだとAssetBundleに対するキャッシュの仕方が違うらしいので
                // より実運用に近いと思われるメモリからの読み込みにします
                _sceneBundle = await AssetBundle.LoadFromStreamAsync(memoryStream);
            }

            var scenePath = _sceneBundle.GetAllScenePaths()[0];
            _sceneName = Path.GetFileNameWithoutExtension(scenePath);
        }

        SceneManager.LoadSceneAsync(_sceneName, loadScene ? LoadSceneMode.Additive : LoadSceneMode.Single);
    }

    #endregion

    #region UnloadScene

    [SerializeField, ContextMenuItem("unLoadScene", "Unload")]
    private bool unloadScene = false;

    private void Unload()
    {
        SceneManager.UnloadSceneAsync(_sceneName);
    }

    #endregion

    #region UnloadAssetBundle

    [SerializeField, ContextMenuItem("unloadBundle", "UnloadBundle")]
    private bool unloadBundle;

    private void UnloadBundle()
    {
        if (_sceneBundle != null)
        {
            _sceneBundle.Unload(unloadBundle);
            _sceneBundle = null;
        }

        AssetBundle.UnloadAllAssetBundles(true);
    }

    #endregion
}

```

</div>
</details>

### 観点

[LoadSceneMode](https://docs.unity3d.com/ja/current/ScriptReference/SceneManagement.LoadSceneMode.html)
`LoadSceneMode.Single` or `LoadSceneMode.Additive`

[Unload](https://docs.unity3d.com/ja/current/ScriptReference/AssetBundle.Unload.html)
`AssetBundle.Unload(true)` or `AssetBundle.Unload(false)`

で2×2の4パターンを試します。

## 結果

|  | Single | Additive |
|:-:|:-:|:-:|
| Unload(true) | <font color="Magenta">死</font> | <font color="Magenta">死</font> |
| Unload(false) | 生 | 生 |

## 考察

`AssetBundle`から読み込んだ`Scene`でも、他の`Asset`と同じく、読み込んだら`AssetBundle.Unload(false)`で開放しちゃっても……まあ……死にはしないみたいです。
ただ、めんどくさいので検証はしてませんが、実機だと確実になにかしらのよくないことが起こると思います。

[アセットバンドルを使いこなす](https://docs.unity3d.com/ja/2019.4/Manual/AssetBundles-Native.html)
ちなみに、巷ではよく`Unload(false)`のサンプルを見かけますが、`Unity`の見解としては`Unload(true)`を推奨しているようです。[^belive]

## 結論

普通は[SceneManager.sceneUnloaded](https://docs.unity3d.com/ScriptReference/SceneManagement.SceneManager-sceneUnloaded.html)で`Unload(true)`するよね。

おしまい。

## 参考

[アセットバンドルを使いこなす](https://docs.unity3d.com/ja/2019.4/Manual/AssetBundles-Native.html)
[【Unity】SceneをAssetBundleに格納して実行時にロードする方法について](http://tsubakit1.hateblo.jp/entry/2016/08/23/233604)
[AssetBundleを完全に理解する](https://qiita.com/k7a/items/d27640ac0276214fc850)


[^belive]: Unityの公式リファレンスってなぜか読んでも読んだ感じがしないっていうか、問題が解決した気がしないっていうか……なぜ？