Asset File Manager


投稿日:2014年6月12日 | 最終更新日:2024年5月18日

リソースファイルを管理します。
ダウンロードしたファイルをデバイスにキャッシュしたり、ロードしたファイルをシステムメモリにプールするなどして極力ロード待ちがでないようにしています。

宴のファイル管理のイメージ

リファレンス

名前 内容
File IO Manager ファイルの入出力際の管理オブジェクト。通常は自動で設定される。
Enable Resources Load Async Resourcesからロードするときに非同期ロードをするか
Time Out Download ダウンロード時のタイムアウト時間(秒)
Auto Retry Count On Download ダウンロードエラー時の自動リトライ回数
Load File Max 同時にロードするファイルの最大数
Range Of Files On Memory ファイルマネージャーがメモリ上にロードおよびプールしておくファイルの数。詳細は下記
Unload Type アンロードするときの設定。Resources.unloadunusedassetsをどれくらいの頻度で呼ぶか。
Is Out Put Debug Log ONにすると、デバッグログを出力する
Is Debug Cache File Name ONにすると、キャッシュファイル名をデバッグ出力する
Is Debug Boot Delete Chache Text And Binary ONにすると、起動時にテキストとバイナリファイルを削除する
Is Debug Boot Delete Chache All ONにすると、起動時に全てのキャッシュファイルを削除する
LoadSetting ファイルのタイプごとにどうロードするかを設定します。
宴のファイルはローカルからロードしたり、サーバーからDLしたりと色々なパターンがあるので、その設定をします
基本的にはAdvEngineStarterで自動設定されます。
Dummy Files ロードができなかった場合の、ダミーファイルの設定。
プロジェクト初期でファイルを用意してない場合などに、ロードエラーにならずにテストを続けるためのもの

Range Of Files On Memory

宴ではロードしたファイルは使用しなくなってもすぐにアンロードせずにいったんメモリ上にプールして残しておきます。
ただ、あまりプールしすぎるとメモリが足りなくなってしまうので、
メモリ上のファイル数がRange Of Files On Memoryで設定したMax以上になると、Min以下になるまで未使用のファイルはアンロードされます。
例えば、10~20で設定されている場合は、メモリ上にあるロード済みのファイルが20を超えると、可能な限り10以下になるまでアンロードします。
アンロードされるのは「使用していない」ファイルなので、使用中のファイルがMaxやMin以上ある場合であってもそれらのファイルはアンロードされません。
この数値を調整することで、メモリ負荷を抑えたり、頻繁なロード負荷を抑えたりできます。
数字が大きいほうがたくさんメモリにプールするため、メモリ負荷は上がりますが、ロードを負荷は下がります。
反対に数字が小さいほうが、あまりメモリにプールしないため、メモリ負荷は下がりますが、ロード負荷は上がります。
プロジェクトによって、軽いファイルから重いファイルがあるでしょうし、想定するプラットフォームによって確保できるメモリ数も違うでしょう。
デフォルトの数字は小さめ抑えていますが、プロジェクトの性質によって数字を調整してください。
実際のファイルの使用状況はFile Manager Viewerでも確認できます。

シーンから参照されるものと同じ素材を宴のシナリオ内も使う

タイトル画面などのUIで使うテクスチャやサウンドと同じ素材を宴のシナリオ内で使うには、Static Asset Managerを使ってください。

自作のファイルマネージャーと宴のファイルマネージャーを組み合わせる

宴のファイルマネージャーは、上記のようにロードを効率的に行えるように、内部でかなり複雑な処理をしています。
Unity公式の仕組みには細かくファイル(アセット)を「管理する」という設計思想がありませんので、Unityなら共通であろうという処理が作れないためです。
既に自作のファイル管理やアセットバンドルの管理の仕組みがあるプロジェクトで宴使う場合は、この点が問題になるかと思います。
そういったケースではいくつか必要な処理がありますので、以下にまとめておきます。

独自に作成したアセットバンドルリストを登録する

デフォルトの宴の場合は、専用のリソースコンバーターで作ったアセットバンドルのリストを、マニフェストファイルから取得しています。
ただ、マニフェストファイルを使ったバージョン管理は不安定なこともあり、独自に作成したアセットバンドルのリストを使いたいケースもあるかと思います。
その場合は、初期化処理を変更する必要があります。
ただ、デフォルトの初期化処理をまとめたAdvEngineStarter.csは、サーバーからDLする場合とローカルからロードする場合に両対応するために、かなり複雑な書き方をしています。
もう少しシンプルに記述した、サンプルである「Utage/SampleOthers/SampleCustomAssetBundleLoad.cs」(バージョン3.0.8以上で追加)を使います。
ポイントとなるのは、
AssetFileManager.GetInstance().AssetBundleInfoManager.AddAssetBundleInfo() という処理で、
「AdvEngine内で管理するファイルパス」「実際にロードするアセットバンドルのURL」「アセットバンドルのバージョン」
この3点を設定している点です。
こうすることで、AdvEngine内のファイル構成と、実際のアセットバンドルの置き場所を関連付けることができます。
他、詳細は実際のソースコードを確認してください。

SampleCustomAssetBundleLoad.csはあくまでサンプルなので、
実際には、SampleCustomAssetBundleLoad.csとAdvEngineStarter.csを元にして、
プロジェクトごとに適した初期化処理を書く必要があるでしょう。

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

namespace Utage
{
    /// <summary>
    /// 独自のアセットバンドルロードをするサンプル
    /// </summary>
    public class SampleCustomAssetBundleLoad : MonoBehaviour
    {
        /// <summary>開始フレームで自動でADVエンジンを起動する</summary>
        [SerializeField]
        string startScenario = "";

        /// <summary>ADVエンジン</summary>
        public AdvEngine Engine { get { return this.engine ?? (this.engine = FindObjectOfType<AdvEngine>() as AdvEngine); } }
        [SerializeField]
        AdvEngine engine;

        [System.Serializable]
        public class SampleAssetBundleVersionInfo
        {
            public string resourcePath; //宴内で管理するリソースのロードパス
            public string url;          //実際のアセットバンドルのURL
            public int version;         //アセットバンドルのバージョン
        }

        //アセットバンドルのURLと、宴がロードするパスとを関連付けるためのリスト
        //ただし、宴のファイルマネージャーは1アセットバンドル=1アセットとなっているので
        //1つのアセットバンドル内に、複数のファイルを入れる構成ではロードできない
        //その場合は、CustomLoadManagerを使ってロード・アンロードの処理のコールバックを登録すること
        List<SampleAssetBundleVersionInfo> assetBundleList = new List<SampleAssetBundleVersionInfo>()
        {
            //以下は、あくまでサンプル
            //もちろん実際は、ハードコーディングしないで、
            //何らかの命名規則に従って処理するか、
            //アセットバンドル作成ツールが作成するであろうファイルリストを使うことになる
            //デフォルトの宴ではAssetBundleManifestを利用している

            //シナリオのファイルパスと、そのアセットバンドルが置いてあるURL・バージョンを関連付ける
            new SampleAssetBundleVersionInfo()
            {
                resourcePath = @"Sample.scenarios.asset",
                url = @"http://madnesslabo.net/Utage3CustomLoad/Windows/sample.scenarios.asset",
                version = 0,
                size = 128,
            },

            //テクスチャやサウンドのファイルパスと、そのアセットバンドルが置いてあるURL・バージョンを関連付ける
            new SampleAssetBundleVersionInfo()
            {
                resourcePath = @"Texture/Character/Utako/utako.png",
                url = @"http://madnesslabo.net/Utage3CustomLoad/Windows/texture/character/utako/utako.asset",
                version = 0,
                size = 256,
            },

            //同様に、全ての必要なリソースリストを作る
            new SampleAssetBundleVersionInfo()
            {
                resourcePath = @"Texture/BG/TutorialBg1.png",
                url = @"http://madnesslabo.net/Utage3Download/Sample/Windows/texture/bg/tutorialbg1.asset",
                version = 0,
                size = 512,
            },
            new SampleAssetBundleVersionInfo()
            {
                resourcePath = @"Sound/BGM/MainTheme.wav",
                url = @"http://madnesslabo.net/Utage3Download/Sample/Windows/sound/bgm/maintheme.asset",
                version = 0,
                size = 1024,
            },
        };

        AdvImportScenarios Scenarios { get; set; }

        void Awake()
        {
            StartCoroutine(LoadEngineAsync());
        }

        //エンジンをロード
        IEnumerator LoadEngineAsync()
        {
            //シナリオやリソースのロードのまえに
            //アセットバンドルのファイルリストの初期化が必要
            //
            //・宴が渡すファイルパスと、
            //・実際にアセットバンドルが置いてあるサーバーのURL、
            //・アセットバンドルのバージョン
            //これらの情報を設定する
            foreach (var versionInfo in assetBundleList)
            {
                AssetFileManager.GetInstance().AssetBundleInfoManager.AddAssetBundleInfo(versionInfo.resourcePath, versionInfo.url, versionInfo.version);
            }

            //開始ラベルを登録しておく
            if (!string.IsNullOrEmpty(startScenario))
            {
                Engine.StartScenarioLabel = startScenario;
            }

            //ロードファイルタイプをサーバーに
            AssetFileManager.InitLoadTypeSetting(AssetFileManagerSettings.LoadType.Server);

            //シナリオのロード
            yield return LoadScenariosAsync("Sample.scenarios.asset");

            if (this.Scenarios == null)
            {
                Debug.LogError("Scenarios is Blank. Please set .scenarios Asset", this);
                yield break;
            }

            //シナリオとルートパスを指定して、エンジン起動
            //カスタムしてスクリプトを書くときは、最終的にここにくればよい
            Engine.BootFromExportData(this.Scenarios, "");

            //自動再生
            StartEngine();
        }

        //シナリオをロードする
        IEnumerator LoadScenariosAsync(string url)
        {
            AssetFile file = AssetFileManager.Load(url, this);
            while (!file.IsLoadEnd) yield return null;

            AdvImportScenarios scenarios = file.UnityObject as AdvImportScenarios;
            if (scenarios == null)
            {
                Debug.LogError(url + " is  not scenario file");
                yield break;
            }
            this.Scenarios = scenarios;
        }

        //シナリオ開始
        void StartEngine()
        {
            StartCoroutine(CoPlayEngine());
        }

        IEnumerator CoPlayEngine()
        {
            //初期化(シナリオのDLなど)を待つ
            while (Engine.IsWaitBootLoading) yield return null;

            if (string.IsNullOrEmpty(startScenario))
            {
                Engine.StartGame();
            }
            else
            {
                Engine.StartGame(startScenario);
            }
        }
    }
}

ロードやアンロードの処理をカスタムする

ロードやアンロードの処理自体も変更したい場合は、
CustomLoadManagerというのを使って、宴のファイル管理クラスにコールバックとして、ファイル管理クラスを登録します。
以下は、Adx2というサウンドミドルウェアを使う場合の処理です。
Adx2AssetFileは、AssetFileBaseというクラスを継承した、Adx2用のファイルロード、アンロード処理を記述しているクラスです。
同じ要領で、AssetFileBaseを継承したクラスに自作のファイルマネージャーのロード、アンロードを呼び出すような処理を記述し
以下の要領で、コールバックに登録してください。

        //サウンドファイルのロードを上書きするコールバックを登録
        void Awake()
        {
            AssetFileManager.GetCustomLoadManager().OnFindAsset += FindAsset;
        }

        //サウンドファイルのロードをAdx2用に上書き
        void FindAsset(AssetFileManager mangager, AssetFileInfo fileInfo, IAssetFileSettingData settingData, ref AssetFileBase asset)
        {
            if (fileInfo.Setting.FileType == AssetFileType.Sound)
            {
                asset = new Adx2AssetFile(mangager, fileInfo, settingData, this);
            }
        }

AssetFileBaseを継承したクラス(サンプルではAdx2AssetFile)を作成して、
overrideした三つのメソッド内で、自作のファイルマネージャーを呼び出してください。

        //ロード処理
        public override IEnumerator LoadAsync(System.Action onComplete, System.Action onFailed);
        //ローカルまたはキャッシュあるか(つまりサーバーからDLする必要があるか)
        public override bool CheckCacheOrLocal();
        //アンロード処理
        public override void Unload();

自作のファイルマネージャを使うサンプル

宴側からロードやアンロードをせずに、自作のファイルマネージャーでロードしてあるアセットを使う場合のサンプルです。
自作のファイルマネージャーですでにロード済みのアセットを使う前提です。

    //既にロード済みの自作のファイルマネージャーと連結するサンプル
    public class SampleCustomFileManager : MonoBehaviour
    {
        //ロードを上書きするコールバックを登録
        void Awake()
        {
            AssetFileManager.GetCustomLoadManager().OnFindAsset += FindAsset;
        }

        void FindAsset(AssetFileManager mangager, AssetFileInfo fileInfo, IAssetFileSettingData settingData, ref AssetFileBase asset)
        {
            asset = new SampleCustomFile(mangager, fileInfo, settingData);
        }
    }
    //自作のファイルマネージャーと連結するサンプル
    public class SampleCustomFile : AssetFileBase
    {
        public SampleCustomFile(AssetFileManager mangager, AssetFileInfo fileInfo, IAssetFileSettingData settingData)
            : base(mangager, fileInfo, settingData)
        {
        }

        //ロード処理
        public override IEnumerator LoadAsync(System.Action onComplete, System.Action onFailed)
        {
            IsLoadEnd = true;
            InitFromCustomFileManager();
            onComplete();
            yield break;
        }

        //ローカルまたはキャッシュあるか(つまりサーバーからDLする必要があるか)
        public override bool CheckCacheOrLocal() { return false; }

        //アンロード処理
        public override void Unload()
        {
            IsLoadEnd = false;

            //宴からの参照がなくなったということ
            //自作のファイルマネージャーのアンロード処理を呼ぶ
            //このタイミングで行う必要がなければここでおわり
        }

        //以下、自作のファイルマネージャーから、オブジェクトの参照を行う
        void InitFromCustomFileManager()
        {
            //Resources.Loadの部分を、自作のファイルマネージャーからのオブジェクト参照に切り替える
            string path = FilePathUtil.GetPathWithoutExtension(FileInfo.FileName);
            switch (FileType)
            {
                case AssetFileType.Text:        //テキスト
                    Text = Resources.Load<TextAsset>(path);
                    break;
                case AssetFileType.Texture:     //テクスチャ
                    Texture = Resources.Load<Texture2D>(path);
                    break;
                case AssetFileType.Sound:       //サウンド
                    Sound = Resources.Load<AudioClip>(path);
                    break;
                case AssetFileType.UnityObject:     //Unityオブジェクト(プレハブとか)
                    this.UnityObject = Resources.Load(path);
                    break;
                default:
                    break;
            }
        }
    }

アセットバンドルを使うが、一部のオブジェクトのみローカルからロードする

現在のUnityはVideoClipをアセットバンドルにすると、一部のプラットフォーム(WindowsやAndroid)で再生できないバグがあるようです。
その対策として、ビデオファイルのロードパスなどを強制的に変更する処理を追加します。(宴3.15以上が必須)
以下のように、FileMangerにCustomLoadManagerとAdvVideoLoadPathChangerをAddComponentして、
AdvVideoLoadPathChangerのRootPathに、ビデオをロードするResouces以下からの相対ディレクトリを設定してください

AdvVideoLoadPathChanger.cs内のコードはこのようになっています。
同じ要領で、一部のリソースのみロードパスを変えることが可能です。

    /// <summary>
    /// ビデオのロードパスを変更
    /// </summary>
    [AddComponentMenu("Utage/ADV/Extra/VideoLoadPathChanger")]
    public class AdvVideoLoadPathChanger : MonoBehaviour
    {
        public string RootPath { get { return rootPath; } }

        [SerializeField]
        string rootPath = "";

        //ファイルのロードを上書きするコールバックを登録
        void Awake()
        {
            AssetFileManager.GetCustomLoadManager().OnFindAsset += FindAsset;
        }

        //ファイルのロードを上書き
        void FindAsset(AssetFileManager mangager, AssetFileInfo fileInfo, IAssetFileSettingData settingData, ref AssetFileBase asset)
        {
            if (IsVideoType(fileInfo, settingData))
            {
                //宴形式の通常ファイルロード
                asset = new AdvLocalVideoFile(this, mangager, fileInfo, settingData);
            }
        }

        bool IsVideoType(AssetFileInfo fileInfo, IAssetFileSettingData settingData)
        {
            if (fileInfo.FileType != AssetFileType.UnityObject) return false;
            if (settingData is AdvCommandSetting)
            {
                AdvCommandSetting setting = settingData as AdvCommandSetting;
                return setting.Command is AdvCommandVideo;
            }
            else
            {
                AdvGraphicInfo info = settingData as AdvGraphicInfo;
                return (info != null && info.FileType == AdvGraphicInfo.FileTypeVideo);
            }
        }
    }

    //ビデオのみ強制的にローカルからロードする処理
    internal class AdvLocalVideoFile : AssetFileUtage
    {
        public AdvLocalVideoFile(AdvVideoLoadPathChanger pathChanger, AssetFileManager assetFileManager, AssetFileInfo fileInfo, IAssetFileSettingData settingData)
            : base(assetFileManager, fileInfo, settingData)
        {
            fileInfo.StrageType = AssetFileStrageType.Resources;
            if (settingData is AdvCommandSetting)
            {
                AdvCommandSetting setting = settingData as AdvCommandSetting;
                string fileName = setting.Command.ParseCell<string>(AdvColumnName.Arg1);
                this.LoadPath = FilePathUtil.Combine(pathChanger.RootPath, fileName);
            }
            else
            {
                AdvGraphicInfo info = settingData as AdvGraphicInfo;
                string fileName = info.FileName;
                this.LoadPath = FilePathUtil.Combine(pathChanger.RootPath, fileName);
            }
        }
    }