ロバメモ - 素人のUnity覚書と奮闘記

素人のUnity覚書と奮闘記

AssetBundleを使ってみる

AssetBundleとは

シーン・スプライト・プレハブなどをサーバーからアプリに読み込むもの。

メリットは、Appの容量を抑えられる。
デメリットは、ダウンロードに時間がかかる場合がある。
以前、最初のスプラッシュ画面で一括読み込みを試みたんですが、ダウンロードに時間がかかり過ぎたみたいで散々でした。

ということで、今回は移動ごとにゲームシーンを読み込む作戦にきりかえてみます。
一度読み込んだシーンは、保持できるので、初めての場面では表示が遅くなりますが、ゲームができないよりはマシかと。

1:Editorフォルダを作成する。

Editorフォルダに入れないとエラーでたので、念のため。

2:アセットバンドルをエクスポートするためのスクリプトを書く。

手順1で作ったEditorフォルダにCreateAssetBundles.csを作る。

using UnityEditor;

public class CreateAssetBundles
{
    [MenuItem ("Assets/Build AssetBundles")]
    static void BuildAllAssetBundles ()
    {
        // Android用
        BuildPipeline.BuildAssetBundles ("Assets/AssetBundles/Build/Android", BuildAssetBundleOptions.ChunkBasedCompression, BuildTarget.Android);
        // iOS用
        BuildPipeline.BuildAssetBundles ("Assets/AssetBundles/Build/iOS", BuildAssetBundleOptions.ChunkBasedCompression, BuildTarget.iOS);
    }
}

[MenuItem ("Assets/Build AssetBundles")]
これを書くと、Unityのメニューに" "の項目が追加される。

BuildPipeline.BuildAssetBundles();
エディターで指定されたすべてのアセットバンドルを作成する。

第一引数 "Assets/AssetBundles/Build/Android"
生成されるアセットバンドルファイルを保管する場所

第二引数 BuildAssetBundleOptions.ChunkBasedCompression
アセットバンドルのビルドオプション
圧縮してくれるほうが良さそうだったので、なんとなくそれを選んで見た。
詳細は公式にて:
docs.unity3d.com

第三引数 BuildTarget.Android
プラットフォームの設定

3:第一引数で書いた通りにフォルダを作成する。

f:id:nico-taniku:20170615225842p:plain:w300

4:アセットバンドルにしたいprefabにアセットバンドル名をつける。

prefabを選択すると、インスペクターの下側に表示されるassetBundleのところでNoneになっているから、Newで新しい名前をつける。
名前はすべて小文字になるので注意。
f:id:nico-taniku:20170615233042p:plain:w300

5:メニューのAssets > Build AssetBundlesを実行する。

f:id:nico-taniku:20170615230022p:plain:w300
ファイル数が多いとかなり時間がかかるので、気長に待つ。
f:id:nico-taniku:20170615230152p:plain:w300
こんな感じで生成される。

6:アセットバンドルをサーバーにアップする

作成されたファイルをサーバーにアップして置く。

7:アセットバンドルを読み込むスクリプトを書く。

いつもは、ゲームシーン移動専用のMoveクラスを作成し、そこでResouceに配置したゲームシーンプレハブをstage(空のGameObject)にInstantiate()で配置している。
ということで、Moveクラスをカスタマイズして使うことにした。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Move : MonoBehaviour
{
    //シングルトン用
    static public Move instance;

    //すでに配置されているプレハブ
    GameObject now;

    //バージョン
    int version = 1;

    //AssetBundle URL
    string bundleURL;

    //対象のバンドル名
    string bundleName;

    //キャッシュ
    AssetBundle chach;

    //キャッシュからバンドル名で呼び出すためのdictionary
    Dictionary<string, GameObject> bundleList = new Dictionary<string, GameObject> ();

    void Awake ()
    {
        //シングルトン処理
        if (instance == null) {
            instance = this;
            DontDestroyOnLoad (this.gameObject);
        } else {
            Destroy (this.gameObject);
        }

    }

    public void OnMove (string sceneName)
    {
        bundleName = sceneName;

        if (!bundleList.ContainsKey (sceneName)) {
            //URL設定
            #if UNITY_EDITOR
            bundleURL = "file://" + Application.dataPath + "/AssetBundles/Build/Android/" + bundleName;
            #elif UNITY_ANDROID
            bundleURL = "http://hogehoge/android/"+bundleName;
            #elif UNITY_IOS
            bundleURL = "http://hogehoge/ios/"+bundleName;
            #endif

            //バージョンチェック
            if (PlayerPrefs.GetInt ("bundleVersion", -1) != version) {
                Caching.CleanCache ();
                PlayerPrefs.SetInt ("bundleVersion", version);
            }

            StartCoroutine ("Import");
        } else {
            OnComplete ();
        }
    }

    IEnumerator Import ()
    {
        //キャッシュの読み込み準備待ち
        while (!Caching.ready) {
            yield return null;
        }
        //ダウンロード
        WWW www = WWW.LoadFromCacheOrDownload (bundleURL, version);
        while (!www.isDone) {
            yield return null;
        }
        //アセットバンドルをキャッシュ
        chach = www.assetBundle;

        //dictionaryに保管
        string[] names = chach.GetAllAssetNames ();
        for (int i = 0; i < names.Length; i++)
        {
            bundleList.Add(chach.LoadAsset<GameObject>(names[i]).name, chach.LoadAsset<GameObject>(names[i]));
        }
        foreach (var keyValuePair in bundleList)
        {
            Debug.Log(keyValuePair.Key);
        }

        //リクエストの解放
        www.Dispose ();

        OnComplete ();

    }

    void OnComplete ()
    {
        
        prefab = bundleList [bundleName];

        if (prefab != null) {
            if (now != null) {
                Destroy (now);
            }
            now = Instantiate (prefab);
            GameObject stage = GameObject.Find ("stage");
            now.transform.SetParent (stage.transform);
            now.transform.localPosition = Vector3.zero;
            now.transform.localScale = Vector3.one;

            //保存
            PlayerPrefs.SetString ("presentScene", bundleName);

        }
    }

}

シングルトンにした理由

・エンディングでシーンが切り替わる→ゲーム本編のシーンに戻るとbundleListが空っぽになっちゃうので、シングルトンにして保持することにした。

処理の流れ

1:OnMove()で実行される。 引数はアセットバンドル名になる。

2:バージョンチェックをして、新しいバージョンがあればキャッシュをクリアする。
  なので、アセットバンドルを変更したときは、このバージョンも変更すること。

3:キャッシュがあればキャッシュから、キャッシュがなければサーバーから読み込む。
  この部分→ WWW www = WWW.LoadFromCacheOrDownload (bundleURL, version);

4:読込先のファイルURLはbundleURLに設定する。
  Unityで再生した場合はローカルファイルから読み込むようにしてある。
  サーバーにアップしたURLをそれぞれ設定すること。

5:読み込み後は、bundleListに順に代入していく。
  key=バンドル名 , value=ゲームシーンのprefab

6:bundleListが作成されたら、OnComplete()でstageに配置する。
  stageがゲーム本編にしか配置していないので、ゲームシーン移動時に毎回Findすることにした。

※2度目以降は、キャッシュから読み込む必要がないので、bundleListのkeyがあるかどうかを調べて、あるなら配置のみ。

以上。

[ 2019/09/12 修正 ] 項目4と5が逆になってたのを修正。