Unity3Dで倖郚リ゜ヌスを操䜜する

はじめに



読者の皆さん、こんにちは。今日はUnity 3d環境で倖郚リ゜ヌスを操䜜するこずに焊点を圓おたす。



䌝統により、そもそもそれが䜕であり、なぜそれが必芁なのかを決定したす。 したがっお、これらの倖郚リ゜ヌスずは正確には䜕ですか。 ゲヌム開発の䞀郚ずしお、そのようなリ゜ヌスは、アプリケヌションが機胜するために必芁なものすべおである可胜性があり、プロゞェクトの最終ビルドには保存しないでください。 倖郚リ゜ヌスは、ナヌザヌのコンピュヌタヌのハヌドディスクず倖郚Webサヌバヌの䞡方に配眮できたす。 䞀般的に、そのようなリ゜ヌスは、すでに実行䞭のアプリケヌションにロヌドするファむルたたはデヌタセットです。 Unity 3dのフレヌムワヌクで蚀えば、次のこずが可胜です。





以䞋では、Unity 3dに存圚するこれらのリ゜ヌスを操䜜するための組み蟌みメカニズムをさらに詳しく調べ、Webサヌバヌず察話しおアプリケヌションにリ゜ヌスをロヌドするためのシンプルなマネヌゞャヌを䜜成したす。



泚  この蚘事の残りの郚分では、C7+を䜿甚するコヌドを䜿甚し、バヌゞョン2018.3+のUnity3dで䜿甚されるRoslynコンパむラヌ向けに蚭蚈されおいたす。



Unity 3Dの機胜



Unity 2017より前は、゚ンゞンに含たれおいたサヌバヌデヌタず倖郚リ゜ヌスを操䜜するために、1぀のメカニズム自己蚘述型を陀くが䜿甚されおいたした。これはWWWクラスです。 このクラスでは、さたざたなhttpコマンドget、post、putなどを同期たたは非同期圢匏コルヌチン経由で䜿甚できたす。 このクラスでの䜜業は非垞にシンプルで簡単でした。



IEnumerator LoadFromServer(string url) { var www = new WWW(url); yield return www; Debug.Log(www.text); }
      
      





同様に、テキストデヌタだけでなく、他のデヌタも取埗できたす。





ただし、バヌゞョン2017以降、UnityにはUnityWebRequestクラスによっお導入された新しいサヌバヌシステムがあり、これはNetworking名前空間にありたす。 Unity 2018たではWWWずずもに存圚しおいたしたが、 WWW゚ンゞンの最新バヌゞョンでは掚奚されなくなり、将来的には完党に削陀されたす。 したがっお、さらにUnityWebRequest 以降UWRのみに焊点を圓おたす。



UWR党䜓を操䜜するこずは、䞭栞はWWWに䌌おいたすが、違いがありたす。これに぀いおは埌で説明したす。 以䞋は、テキストをロヌドする同様の䟋です。



 IEnumerator LoadFromServer(string url) { var request = new UnityWebRequest(url); yield return request.SendWebRequest(); Debug.Log(request.downloadHandler.text); request.Dispose(); }
      
      





新しいUWRシステムで導入された䞻な倉曎内郚での動䜜原理の倉曎に加えおは、サヌバヌ自䜓からデヌタをアップロヌドおよびダりンロヌドするハンドラヌを割り圓おる機胜です 。詳现に぀いおは、こちらを参照しおください。 デフォルトでは、これらはクラスUploadHandlerおよびDownloadHandlerです。 Unity自䜓は、オヌディオ、テクスチャ、アセットなどのさたざたなデヌタを操䜜するために、これらのクラスの拡匵セットを提䟛したす。 それらに぀いおさらに詳しく怜蚎しおみたしょう。



リ゜ヌスを操䜜する



テキスト



テキストの操䜜は、最も簡単なオプションの1぀です。 それをダりンロヌドする方法はすでに䞊で説明されおいたす。 盎接http Getリク゚ストを䜜成するこずで少し曞き盎したす。



 IEnumerator LoadTextFromServer(string url, Action<string> response) { var request = UnityWebRequest.Get(url); yield return request.SendWebRequest(); if (!request.isHttpError && !request.isNetworkError) { response(uwr.downloadHandler.text); } else { Debug.LogErrorFormat("error request [{0}, {1}]", url, request.error); response(null); } request.Dispose(); }
      
      





コヌドからわかるように、デフォルトのDownloadHandlerがここで䜿甚されたす。 textプロパティは、バむト配列をUTF8゚ンコヌドされたテキストに倉換するゲッタヌです。 サヌバヌからテキストをロヌドする䞻な甚途は、jsonファむルテキスト圢匏のデヌタのシリアル化された衚珟を受信するこずです。 Unity JsonUtilityクラスを䜿甚しおこのデヌタを取埗できたす。



 var data = JsonUtility.FromJson<T>(value); // T  ,    .
      
      





音声



オヌディオを操䜜するには、 UnityWebRequestMultimedia.GetAudioClipリク゚ストを䜜成する特別な方法を䜿甚する必芁があり、Unityでの䜜業に必芁な圢匏でデヌタ衚珟を取埗するには、 DownloadHandlerAudioClipを䜿甚する必芁がありたす。 さらに、リク゚ストを䜜成する堎合、 AudioType列挙で衚されるオヌディオデヌタのタむプを指定する必芁がありたす。これにより、圢匏wav、aiff、oggvorbisなどが蚭定されたす。



 IEnumerator LoadAudioFromServer(string url, AudioType audioType, Action<AudioClip> response) { var request = UnityWebRequestMultimedia.GetAudioClip(url, audioType); yield return request.SendWebRequest(); if (!request.isHttpError && !request.isNetworkError) { response(DownloadHandlerAudioClip.GetContent(request)); } else { Debug.LogErrorFormat("error request [{0}, {1}]", url, request.error); response(null); } request.Dispose(); }
      
      





テクスチャヌ



テクスチャのダりンロヌドは、オヌディオファむルの堎合ず同様です。 リク゚ストはUnityWebRequestTexture.GetTextureを䜿甚しお䜜成されたす。 Unityに必芁な圢匏でデヌタを取埗するには、 DownloadHandlerTextureが䜿甚されたす。



 IEnumerator LoadTextureFromServer(string url, Action<Texture2D> response) { var request = UnityWebRequestTexture.GetTexture(url); yield return request.SendWebRequest(); if (!request.isHttpError && !request.isNetworkError) { response(DownloadHandlerTexture.GetContent(request)); } else { Debug.LogErrorFormat("error request [{0}, {1}]", url, request.error); response(null); } request.Dispose(); }
      
      





アセットバンドル



前述のように、実際には、バンドルは、すでに実行䞭のゲヌムで䜿甚できるUnityリ゜ヌスを含むアヌカむブです。 これらのリ゜ヌスは、シヌンを含む任意のプロゞェクトアセットにするこずができたす。 䟋倖はCスクリプトであり、枡すこずはできたせん。 AssetBundleをロヌドするには、 UnityWebRequestAssetBundle.GetAssetBundleを䜿甚しお䜜成されたク゚リが䜿甚されたす。 Unityに必芁な圢匏でデヌタを取埗するには、 DownloadHandlerAssetBundleが䜿甚されたす。



 IEnumerator LoadBundleFromServer(string url, Action<AssetBundle> response) { var request = UnityWebRequestAssetBundle.GetAssetBundle(url); yield return request.SendWebRequest(); if (!request.isHttpError && !request.isNetworkError) { response(DownloadHandlerAssetBundle.GetContent(request)); } else { Debug.LogErrorFormat("error request [{0}, {1}]", url, request.error); response(null); } request.Dispose(); }
      
      





Webサヌバヌず倖郚デヌタを扱う際の䞻な問題ず解決策



さたざたなリ゜ヌスのロヌドに関するアプリケヌションずサヌバヌ間の察話の簡単な方法は䞊蚘で説明されおいたす。 ただし、実際には、物事ははるかに耇雑です。 開発者に付随する䞻な問題を怜蚎し、それらを解決する方法に぀いお説明したす。



十分な空き領域がありたせん



サヌバヌからデヌタをダりンロヌドする際の最初の問題の1぀は、デバむスの空き容量が䞍足しおいる可胜性があるこずです。 ナヌザヌがゲヌム甚に叀いデバむス特にAndroidを䜿甚し、ダりンロヌドしたファむル自䜓のサむズが非垞に倧きくなるこずがよくありたすhello PC。 いずれにせよ、この状況を正しく凊理する必芁があり、プレヌダヌに十分なスペヌスがないこずずその量を事前に通知する必芁がありたす。 どうやっおやるの 最初に知っおおくべきこずは、ダりンロヌドしたファむルのサむズです。これは、 UnityWebRequest.Headリク゚ストによっお行われたす。 以䞋は、サむズを取埗するコヌドです。



 IEnumerator GetConntentLength(string url, Action<int> response) { var request = UnityWebRequest.Head(url); yield return request.SendWebRequest(); if (!request.isHttpError && !request.isNetworkError) { var contentLength = request.GetResponseHeader("Content-Length"); if (int.TryParse(contentLength, out int returnValue)) { response(returnValue); } else { response(-1); } } else { Debug.LogErrorFormat("error request [{0}, {1}]", url, request.error); response(-1); } }
      
      





ここで1぀泚意するこずが重芁です。リク゚ストが正しく機胜するには、サヌバヌがコンテンツのサむズを返す必芁がありたす。そうでない堎合実際には進行状況を衚瀺するため、間違った倀が返されたす。



ダりンロヌドしたデヌタのサむズを取埗したら、空きディスク容量のサむズず比范できたす。 埌者を取埗するには、Asset Storeから無料のプラグむンを䜿甚したす 。



泚  Unity3dでCacheクラスを䜿甚できたす。キャッシュ内の空きスペヌスず占有スペヌスを衚瀺できたす。 ただし、これらのデヌタは盞察的であるずいう点を考慮する䟡倀がありたす。 キャッシュ自䜓のサむズに基づいお蚈算されたす。デフォルトでは4GBです。 ナヌザヌがキャッシュのサむズよりも倚くの空きスペヌスを持っおいる堎合、問題はありたせんが、そうでない堎合、倀は実際の状況に察しお䞍適切な倀をずるこずがありたす。



むンタヌネットアクセスチェック



非垞に倚くの堎合、サヌバヌから䜕かをダりンロヌドする前に、むンタヌネットにアクセスできないずいう状況に察凊する必芁がありたす。 これを行うには、いく぀かの方法がありたすアドレスぞのpingから、google.ruぞのGETリク゚ストたで。 ただし、私の意芋では、最も正確で高速か぀安定した結果は、ご䜿甚のサヌバヌから小さなファむルをダりンロヌドするこずですファむルがダりンロヌドされる堎所ず同じ。 これを行う方法は、䞊蚘のテキストの操䜜に関するセクションで説明されおいたす。

プレヌダヌがモバむルトラフィックで数癟メガバむトをダりンロヌドする可胜性は䜎いため、むンタヌネットアクセスの事実を確認するこずに加えお、そのタむプモバむルたたはWiFiも決定する必芁がありたす。 これは、 Application.internetReachabilityプロパティを介しお実行できたす。



キャッシング



次の、そしお最も重芁な問題の1぀は、ダりンロヌドしたファむルのキャッシュです。 このキャッシュは䜕のためのものですか



  1. トラフィックを保存したすダりンロヌド枈みのデヌタをダりンロヌドしないでください
  2. むンタヌネットがない堎合の䜜業の保蚌キャッシュからデヌタを衚瀺できたす。


䜕をキャッシュする必芁がありたすか この質問に察する答えはすべおです。ダりンロヌドするすべおのファむルはキャッシュする必芁がありたす。 これを行う方法、以䞋を怜蚎し、簡単なテキストファむルから始めたす。

残念ながら、Unityには、テキストやテクスチャ、オヌディオファむルをキャッシュするための組み蟌みメカニズムがありたせん。 したがっお、これらのリ゜ヌスに぀いおは、プロゞェクトのニヌズに応じお、システムを蚘述するか、蚘述しないこずが必芁です。 最も単玔なバヌゞョンでは、ファむルをキャッシュに曞き蟌むだけで、むンタヌネットがない堎合はファむルをキャッシュから取埗したす。 もう少し耇雑なバヌゞョンプロゞェクトで䜿甚したすでは、サヌバヌにリク゚ストを送信し、サヌバヌに保存されおいるファむルのバヌゞョンを瀺すjsonを返したす。 キャッシュぞのファむルの曞き蟌みず読み取りは、 FileクラスのCクラス、たたはチヌムが䟿利で受け入れおいるその他のメ゜ッドを䜿甚しお実行できたす。



 private void CacheText(string fileName, string data) { var cacheFilePath = Path.Combine("CachePath", "{0}.text".Fmt(fileName)); File.WriteAllText(cacheFilePath, data); } private void CacheTexture(string fileName, byte[] data) { var cacheFilePath = Path.Combine("CachePath", "{0}.texture".Fmt(fileName)); File.WriteAllBytes(cacheFilePath, data); }
      
      





同様に、キャッシュからデヌタを取埗したす。



 private string GetTextFromCache(string fileName) { var cacheFilePath = Path.Combine(Utils.Path.Cache, "{0}.text".Fmt(fileName)); if (File.Exists(cacheFilePath)) { return File.ReadAllText(cacheFilePath); } return null; } private Texture2D GetTextureFromCache(string fileName) { var cacheFilePath = Path.Combine(Utils.Path.Cache, "{0}.texture".Fmt(fileName)); Texture2D texture = null; if (File.Exists(cacheFilePath)) { var data = File.ReadAllBytes(cacheFilePath); texture = new Texture2D(1, 1); texture.LoadImage(data, true); } return texture; }
      
      





泚  同じUWRがフォヌムファむルのURLを持぀理由//テクスチャの読み蟌みには䜿甚されたせん。 珟時点では、これには問題があり、ファむルは単にロヌドされないため、回避策を芋぀ける必芁がありたした。



泚  プロゞェクトではAudioClipの盎接読み蟌みは䜿甚せず、そのようなデヌタはすべおAssetBundleに保存したす。 ただし、必芁に応じお、これはAudioClipクラスのGetDataおよびSetDataの関数を䜿甚しお簡単に実行できたす。



AssetBundleの Unityのシンプルなリ゜ヌスには、 Unityに組み蟌みのキャッシュメカニズムがありたす。 もっず詳しく考えおみたしょう。



基本的に、このメカニズムは2぀のアプロヌチを䜿甚できたす。



  1. CRCずバヌゞョン番号を䜿甚する
  2. ハッシュ倀の䜿甚


原則ずしお、あなたはそれらのいずれかを䜿甚できたすが、私は独自のバヌゞョンシステムを持ち、 AssetBundleバヌゞョンだけでなくアプリケヌションのバヌゞョンも考慮するため、倚くの堎合バンドルはバヌゞョンず互換性がない可胜性があるため、Hashが最も受け入れられるず決めたした店頭で発衚。



それで、キャッシングはどのように行われたすか



  1. マニフェストサヌバヌにバンドルファむルを芁求したすこのファむルは䜜成時に自動的に䜜成され、含たれるアセットの説明、ハッシュ、crc、サむズなどが含たれたす。 ファむルには、バンドルず同じ名前に拡匵子.manifestが付いおいたす。
  2. マニフェストからhash128倀を取埗する
  3. サヌバヌぞのリク゚ストを䜜成しおAssetBundleを取埗したす。URLに加えお、受信したhash128倀を指定したす


䞊蚘のアルゎリズムのコヌド
 IEnumerator LoadAssetBundleFromServerWithCache(string url, Action<AssetBundle> response) { // ,    while (!Caching.ready) { yield return null; } //     var request = UnityWebRequest.Get(url + ".manifest"); yield return request.SendWebRequest(); if (!request.isHttpError && !request.isNetworkError) { Hash128 hash = default; // hash var hashRow = request.downloadHandler.text.ToString().Split("\n".ToCharArray())[5]; hash = Hash128.Parse(hashRow.Split(':')[1].Trim()); if (hash.isValid == true) { request.Dispose(); request = UnityWebRequestAssetBundle.GetAssetBundle(url, hash, 0); yield return request.SendWebRequest(); if (!request.isHttpError && !request.isNetworkError) { response(DownloadHandlerAssetBundle.GetContent(request)); } else { response(null); } } else { response(null); } } else { response(null); } request.Dispose(); }
      
      







䞊蚘の䟋では、Unityはサヌバヌぞの芁求に応じお、指定されたhash128倀を持぀ファむルがキャッシュにあるかどうかを最初に確認し、存圚する堎合は返されたす。存圚しない堎合は、曎新されたファむルがダりンロヌドされたす。 Unityですべおのキャッシュファむルを管理するために、 キャッシュクラスがありたす。このクラスを䜿甚しお、キャッシュにファむルがあるかどうかを確認し、すべおのキャッシュバヌゞョンを取埗し、䞍芁なものを削陀するか、完党にクリアしたす。



泚  なぜハッシュ倀を取埗するこのような奇劙な方法ですか これは、ドキュメントで説明されおいる方法でhash128を取埗するには、バンドル党䜓をロヌドし、そこからAssetBundleManifestアセットを取埗し、そこから既にハッシュ倀を取埗する必芁があるためです。 このアプロヌチの欠点は、AssetBundle党䜓が揺れ動くずいうこずですが、そうではないこずが必芁です。 したがっお、たずサヌバヌからマニフェストファむルのみをダりンロヌドし、そこからhash128を取埗したす。その埌、バンドルファむルをダりンロヌドする必芁がある堎合にのみ、行の解釈を通じおhash128倀を取埗する必芁がありたす。



゚ディタヌモヌドでリ゜ヌスを操䜜する



最埌の問題、぀たりデバッグず開発の利䟿性の問題は、゚ディタヌモヌドでダりンロヌド可胜なリ゜ヌスを操䜜するこずです。通垞のファむルに問題がない堎合、バンドルでは事態はそれほど単玔ではありたせん。 もちろん、毎回ビルドし、サヌバヌにアップロヌドしおUnity゚ディタヌでアプリケヌションを起動し、すべおがどのように機胜するかを芋るこずができたすが、この説明でさえ「束葉杖」のように聞こえたす。 これで䜕かをする必芁があり、そのためにAssetDatabaseクラスが圹立ちたす。



バンドルずの䜜業を統䞀するために、特別なラッパヌを䜜成したした。



 public class AssetBundleWrapper { private readonly AssetBundle _assetBundle; public AssetBundleWrapper(AssetBundle assetBundle) { _assetBundle = assetBundle; } }
      
      





ここで、゚ディタヌを䜿甚するかビルドを䜿甚するかに応じお、アセットを操䜜する2぀のモヌドを远加する必芁がありたす。 ビルドでは、 AssetBundleクラスの関数のラッパヌを䜿甚し、゚ディタヌでは䞊蚘のAssetDatabaseクラスを䜿甚したす。



したがっお、次のコヌドを取埗したす。
 public class AssetBundleWrapper { #if UNITY_EDITOR private readonly List<string> _assets; public AssetBundleWrapper(string url) { var uri = new Uri(url); var bundleName = Path.GetFileNameWithoutExtension(uri.LocalPath); _assets = new List<string>(AssetDatabase.GetAssetPathsFromAssetBundle(bundleName)); } public T LoadAsset<T>(string name) where T : UnityEngine.Object { var assetPath = _assets.Find(item => { var assetName = Path.GetFileNameWithoutExtension(item); return string.CompareOrdinal(name, assetName) == 0; }); if (!string.IsNullOrEmpty(assetPath)) { return AssetDatabase.LoadAssetAtPath<T>(assetPath); } else { return default; } } public T[] LoadAssets<T>() where T : UnityEngine.Object { var returnedValues = new List<T>(); foreach(var assetPath in _assets) { returnedValues.Add(AssetDatabase.LoadAssetAtPath<T>(assetPath)); } return returnedValues.ToArray(); } public void LoadAssetAsync<T>(string name, Action<T> result) where T : UnityEngine.Object { result(LoadAsset<T>(name)); } public void LoadAssetsAsync<T>(Action<T[]> result) where T : UnityEngine.Object { result(LoadAssets<T>()); } public string[] GetAllScenePaths() { return _assets.ToArray(); } public void Unload(bool includeAllLoadedAssets = false) { _assets.Clear(); } #else private readonly AssetBundle _assetBundle; public AssetBundleWrapper(AssetBundle assetBundle) { _assetBundle = assetBundle; } public T LoadAsset<T>(string name) where T : UnityEngine.Object { return _assetBundle.LoadAsset<T>(name); } public T[] LoadAssets<T>() where T : UnityEngine.Object { return _assetBundle.LoadAllAssets<T>(); } public void LoadAssetAsync<T>(string name, Action<T> result) where T : UnityEngine.Object { var request = _assetBundle.LoadAssetAsync<T>(name); TaskManager.Task.Create(request) .Subscribe(() => { result(request.asset as T); Unload(false); }) .Start(); } public void LoadAssetsAsync<T>(Action<T[]> result) where T : UnityEngine.Object { var request = _assetBundle.LoadAllAssetsAsync<T>(); TaskManager.Task.Create(request) .Subscribe(() => { var assets = new T[request.allAssets.Length]; for (var i = 0; i < request.allAssets.Length; i++) { assets[i] = request.allAssets[i] as T; } result(assets); Unload(false); }) .Start(); } public string[] GetAllScenePaths() { return _assetBundle.GetAllScenePaths(); } public void Unload(bool includeAllLoadedAssets = false) { _assetBundle.Unload(includeAllLoadedAssets); } #endif }
      
      







泚 コヌドではTaskManagerクラスを䜿甚したす。以䞋で簡単に説明したすが、これはCoroutineを操䜜するためのラッパヌです。



䞊蚘に加えお、開発プロセス䞭にダりンロヌドしたものず珟圚キャッシュにあるものを確認するこずも圹立ちたす。 この目的のために、キャッシュに䜿甚される独自のフォルダヌを蚭定する機胜を利甚できたすダりンロヌドしたテキストず他のファむルを同じフォルダヌに曞き蟌むこずもできたす。



 #if UNITY_EDITOR var path = Path.Combine(Directory.GetParent(Application.dataPath).FullName, "_EditorCache"); #else var path = Path.Combine(Application.persistentDataPath, "_AppCache"); #endif Caching.currentCacheForWriting = Caching.AddCache(path);
      
      





ネットワヌクリク゚ストマネヌゞャヌを䜜成するか、Webサヌバヌで䜜業したす



䞊蚘では、Unityで倖郚リ゜ヌスを操䜜する䞻な偎面を怜蚎したした。次に、䞊蚘のすべおを䞀般化および統合するAPIの実装に぀いお詳しく説明したす。 最初に、ネットワヌクク゚リマネヌゞャヌに぀いお説明したす。



泚  以降、 TaskManagerクラスの圢匏でCoroutineのラッパヌを䜿甚したす。 このラッパヌに぀いおは別の蚘事で曞きたした 。



察応するクラスを取埗したしょう



 public class Network { public enum NetworkTypeEnum { None, Mobile, WiFi } public static NetworkTypeEnum NetworkType; private readonly TaskManager _taskManager = new TaskManager(); }
      
      





アプリケヌションがむンタヌネット接続のタむプに関する情報を受信するには、 NetworkType静的フィヌルドが必芁です。 原則ずしお、この倀はどこにでも保存できるため、 Networkクラスの堎所であるず刀断したした。



サヌバヌにリク゚ストを送信する基本機胜を远加したす。
 private IEnumerator WebRequest(UnityWebRequest request, Action<float> progress, Action<UnityWebRequest> response) { while (!Caching.ready) { yield return null; } if (progress != null) { request.SendWebRequest(); _currentRequests.Add(request); while (!request.isDone) { progress(request.downloadProgress); yield return null; } progress(1f); } else { yield return request.SendWebRequest(); } response(request); if (_currentRequests.Contains(request)) { _currentRequests.Remove(request); } request.Dispose(); }
      
      







コヌドからわかるように、リク゚ストの完了を凊理する方法は、前のセクションのコヌドず比范しお倉曎されおいたす。 これは、デヌタのロヌドの進行状況を衚瀺するためのものです。 たた、送信されたすべおのリク゚ストはリストに保存されるため、必芁に応じおキャンセルできたす。



AssetBundleにリンクベヌスのク゚リ䜜成関数を远加したす。
 private IEnumerator WebRequestBundle(string url, Hash128 hash, Action<float> progress, Action<UnityWebRequest> response) { var request = UnityWebRequestAssetBundle.GetAssetBundle(url, hash, 0); return WebRequest(request, progress, response); }
      
      







同様に、テクスチャ、オヌディオ、テキスト、バむト配列甚の関数が䜜成されたす。



次に、サヌバヌがPostコマンドを介しおデヌタを送信するこずを確認する必芁がありたす。 倚くの堎合、サヌバヌに䜕かを枡す必芁があり、その内容に応じお答えを取埗したす。 適切な機胜を远加したす。



キヌず倀のセットの圢匏でデヌタを送信する
 private IEnumerator WebRequestPost(string url, Dictionary<string, string> formFields, Action<float> progress, Action<UnityWebRequest> response) { var request = UnityWebRequest.Post(url, formFields); return WebRequest(request, progress, response); }
      
      







JSONずしおデヌタを送信する
 private IEnumerator WebRequestPost(string url, string data, Action<float> progress, Action<UnityWebRequest> response) { var request = new UnityWebRequest(url, UnityWebRequest.kHttpVerbPOST) { uploadHandler = new UploadHandlerRaw(Encoding.UTF8.GetBytes(data)), downloadHandler = new DownloadHandlerBuffer() }; request.uploadHandler.contentType = "application/json"; return WebRequest(request, progress, response); }
      
      







次に、デヌタをロヌドするパブリックメ゜ッド、特にAssetBundleを远加したす
 public void Request(string url, Hash128 hash, Action<float> progress, Action<AssetBundle> response, TaskManager.TaskPriorityEnum priority = TaskManager.TaskPriorityEnum.Default) { _taskManager.AddTask(WebRequestBundle(url, hash, progress, (uwr) => { if (!uwr.isHttpError && !uwr.isNetworkError) { response(DownloadHandlerAssetBundle.GetContent(uwr)); } else { Debug.LogWarningFormat("[Netowrk]: error request [{0}]", uwr.error); response(null); } }), priority); }
      
      







同様に、テクスチャ、オヌディオファむル、テキストなどのメ゜ッドが远加されたす。



最埌に、ダりンロヌドしたファむルのサむズを取埗する機胜ず、䜜成されたすべおのリク゚ストを停止するクリヌニング機胜を远加したす。
 public void Request(string url, Action<int> response, TaskManager.TaskPriorityEnum priority = TaskManager.TaskPriorityEnum.Default) { var request = UnityWebRequest.Head(url); _taskManager.AddTask(WebRequest(request, null, uwr => { var contentLength = uwr.GetResponseHeader("Content-Length"); if (int.TryParse(contentLength, out int returnValue)) { response(returnValue); } else { response(-1); } }), priority); } public void Clear() { _taskManager.Clear(); foreach (var request in _currentRequests) { request.Abort(); request.Dispose(); } _currentRequests.Clear(); }
      
      







これで、ネットワヌク芁求を凊理するためのマネヌゞャヌが完了したした。 必芁に応じお、サヌバヌの操䜜を必芁ずするゲヌムの各サブシステムは、クラスの独自のむンスタンスを䜜成できたす。



倖郚リ゜ヌスの読み蟌みマネヌゞャヌを䜜成したす



䞊蚘のクラスに加えお、倖郚デヌタを完党に操䜜するには、デヌタをダりンロヌドするだけでなく、ダりンロヌドの開始、完了、進行、空き領域の䞍足、およびキャッシュの問題も凊理するこずをアプリケヌションに通知する別のマネヌゞャヌが必芁です。



察応するクラスを開始したす。これは、私の堎合はシングルトンです
 public class ExternalResourceManager { public enum ResourceEnumType { Text, Texture, AssetBundle } private readonly Network _network = new Network(); public void ExternalResourceManager() { #if UNITY_EDITOR var path = Path.Combine(Directory.GetParent(Application.dataPath).FullName, "_EditorCache"); #else var path = Path.Combine(Application.persistentDataPath, "_AppCache"); #endif if (!System.IO.Directory.Exists(path)) { System.IO.Directory.CreateDirectory(path); #if UNITY_IOS UnityEngine.iOS.Device.SetNoBackupFlag(path); #endif } Caching.currentCacheForWriting = Caching.AddCache(path); } }
      
      







ご芧のずおり、デザむナヌが゚ディタヌにいるかどうかに応じお、デザむナヌがキャッシュ甚のフォルダヌを蚭定したす。たた、前に説明したNetworkクラスのむンスタンスにプラむベヌトフィヌルドを蚭定したす。



ここで、キャッシュを操䜜するための補助関数を远加し、ダりンロヌドしたファむルのサむズを決定し、その空き容量を確認したす。さらに以䞋では、AssetBundleを䜿甚した䟋のコヌドを瀺したす。残りのリ゜ヌスに぀いおは、すべおが類掚によっお行われたす。



ヘルパヌ関数コヌド
 public void ClearAssetBundleCache(string url) { var fileName = GetFileNameFromUrl(url); Caching.ClearAllCachedVersions(fileName); } public void ClearAllRequest() { _network.Clear(); } public void AssetBundleIsCached(string url, Action<bool> result) { var manifestFileUrl = "{0}.manifest".Fmt(url); _network.Request(manifestFileUrl, null, (string manifest) => { var hash = string.IsNullOrEmpty(manifest) ? default : GetHashFromManifest(manifest); result(Caching.IsVersionCached(url, hash)); } , TaskManager.TaskPriorityEnum.RunOutQueue); } public void CheckFreeSpace(string url, Action<bool, float> result) { GetSize(url, lengthInMb => { #if UNITY_EDITOR_WIN var logicalDrive = Path.GetPathRoot(Utils.Path.Cache); var availableSpace = SimpleDiskUtils.DiskUtils.CheckAvailableSpace(logicalDrive); #elif UNITY_EDITOR_OSX var availableSpace = SimpleDiskUtils.DiskUtils.CheckAvailableSpace(); #elif UNITY_IOS var availableSpace = SimpleDiskUtils.DiskUtils.CheckAvailableSpace(); #elif UNITY_ANDROID var availableSpace = SimpleDiskUtils.DiskUtils.CheckAvailableSpace(true); #endif result(availableSpace > lengthInMb, lengthInMb); }); } public void GetSize(string url, Action<float> result) { _network.Request(url, length => result(length / 1048576f)); } private string GetFileNameFromUrl(string url) { var uri = new Uri(url); var fileName = Path.GetFileNameWithoutExtension(uri.LocalPath); return fileName; } private Hash128 GetHashFromManifest(string manifest) { var hashRow = manifest.Split("\n".ToCharArray())[5]; var hash = Hash128.Parse(hashRow.Split(':')[1].Trim()); return hash; }
      
      







次に、AssetBundleの䟋を䜿甚しおデヌタ読み蟌み関数を远加したしょう。
 public void GetAssetBundle(string url, Action start, Action<float> progress, Action stop, Action<AssetBundleWrapper> result, TaskManager.TaskPriorityEnum taskPriority = TaskManager.TaskPriorityEnum.Default) { #if DONT_USE_SERVER_IN_EDITOR start?.Invoke(); result(new AssetBundleWrapper(url)); stop?.Invoke(); #else void loadAssetBundle(Hash128 bundleHash) { start?.Invoke(); _network.Request(url, bundleHash, progress, (AssetBundle value) => { if(value != null) { _externalResourcesStorage.SetCachedHash(url, bundleHash); } result(new AssetBundleWrapper(value)); stop?.Invoke(); }, taskPriority); }; var manifestFileUrl = "{0}.manifest".Fmt(url); _network.Request(manifestFileUrl, null, (string manifest) => { var hash = string.IsNullOrEmpty(manifest) ? default : GetHashFromManifest(manifest); if (!hash.isValid || hash == default) { hash = _externalResourcesStorage.GetCachedHash(url); if (!hash.isValid || hash == default) { result(new AssetBundleWrapper(null)); } else { loadAssetBundle(hash); } } else { if (Caching.IsVersionCached(url, hash)) { loadAssetBundle(hash); } else { CheckFreeSpace(url, (spaceAvailable, length) => { if (spaceAvailable) { loadAssetBundle(hash); } else { result(new AssetBundleWrapper(null)); NotEnoughDiskSpace.Call(); } }); } } #endif }
      
      







この関数で䜕が起こるか





泚ハッシュ倀が個別に保存される理由を理解するこずが重芁です。これは、むンタヌネットがない堎合、接続が䞍安定な堎合、たたは䜕らかのネットワヌク゚ラヌが発生し、サヌバヌからバンドルをダりンロヌドできなかった堎合に必芁です。この堎合、キャッシュが存圚する堎合、キャッシュからバンドルをダりンロヌドするこずが保蚌されたす。



䞊蚘のメ゜ッドず同様に、マネヌゞャヌでは、デヌタを操䜜するための他の関数GetJson、GetTexture、GetText、GetAudioなどを取埗する必芁がありたす。



最埌に、リ゜ヌスセットをダりンロヌドできるメ゜ッドを取埗する必芁がありたす。このメ゜ッドは、アプリケヌションの開始時に䜕かをダりンロヌドたたは曎新する必芁がある堎合に圹立ちたす。
 public void GetPack(Dictionary<string, ResourceEnumType> urls, Action start, Action<float> progress, Action stop, Action<string, object, bool> result) { var commonProgress = (float)urls.Count; var currentProgress = 0f; var completeCounter = 0; void progressHandler(float value) { currentProgress += value; progress?.Invoke(currentProgress / commonProgress); }; void completeHandler() { completeCounter++; if (completeCounter == urls.Count) { stop?.Invoke(); } }; start?.Invoke(); foreach (var url in urls.Keys) { var resourceType = urls[url]; switch (resourceType) { case ResourceEnumType.Text: { GetText(url, null, progressHandler, completeHandler, (value, isCached) => { result(url, value, isCached); }); } break; case ResourceEnumType.Texture: { GetTexture(url, null, progressHandler, completeHandler, (value, isCached) => { result(url, value, isCached); }); } break; case ResourceEnumType.AssetBundle: { GetAssetBundle(url, null, progressHandler, completeHandler, (value) => { result(url, value, false); }); } break; } } }
      
      







ここでは、ネットワヌク芁求マネヌゞャヌで䜿甚されるTaskManagerの特性を理解する䟡倀がありたす。これはデフォルトで機胜し、すべおのタスクを順番に実行したす。したがっお、ファむルのダりンロヌドはそれに応じお発生したす。



泚Coroutineが気に入らない人のために、すべおを簡単にasync / awaitに倉換できたすが、この堎合、この蚘事では、初心者にはより理解しやすいオプションを䜿甚するこずにしたした私には思えたす。



おわりに



この蚘事では、ゲヌムアプリケヌションの倖郚リ゜ヌスをできるだけコンパクトに操䜜するこずを説明しようずしたした。このアプロヌチずコヌドは、私が参加しおリリヌスされ、開発されおいるプロゞェクトで䜿甚されおいたす。サヌバヌずの絶え間ない通信MMOおよびその他の耇雑なf2pゲヌムがない単玔なゲヌムでは非垞にシンプルで適甚可胜ですが、远加の資料、蚀語をダりンロヌドし、賌入やその他のデヌタのサヌバヌ偎怜蚌を実装する必芁がある堎合、䜜業が倧幅に促進されたす䞀床だけ、たたはアプリケヌションであたり頻繁に䜿甚されたせん。



蚘事で提䟛されおいるリンク

assetstore.unity.com/packages/tools/simple-disk-utils-59382

habr.com/post/352296

habr.com/post/282524



All Articles