ã¯ããã«
èªè ã®çãããããã«ã¡ã¯ãä»æ¥ã¯Unity 3dç°å¢ã§å€éšãªãœãŒã¹ãæäœããããšã«çŠç¹ãåœãŠãŸãã
äŒçµ±ã«ããããããããããäœã§ããããªããããå¿ èŠãªã®ãã決å®ããŸãã ãããã£ãŠããããã®å€éšãªãœãŒã¹ãšã¯æ£ç¢ºã«ã¯äœã§ããã ã²ãŒã éçºã®äžéšãšããŠããã®ãããªãªãœãŒã¹ã¯ãã¢ããªã±ãŒã·ã§ã³ãæ©èœããããã«å¿ èŠãªãã®ãã¹ãŠã§ããå¯èœæ§ãããããããžã§ã¯ãã®æçµãã«ãã«ã¯ä¿åããªãã§ãã ããã å€éšãªãœãŒã¹ã¯ããŠãŒã¶ãŒã®ã³ã³ãã¥ãŒã¿ãŒã®ããŒããã£ã¹ã¯ãšå€éšWebãµãŒããŒã®äž¡æ¹ã«é 眮ã§ããŸãã äžè¬çã«ããã®ãããªãªãœãŒã¹ã¯ããã§ã«å®è¡äžã®ã¢ããªã±ãŒã·ã§ã³ã«ããŒããããã¡ã€ã«ãŸãã¯ããŒã¿ã»ããã§ãã Unity 3dã®ãã¬ãŒã ã¯ãŒã¯ã§èšãã°ã次ã®ããšãå¯èœã§ãã
- ããã¹ããã¡ã€ã«
- ãã¯ã¹ãã£ãã¡ã€ã«
- é³å£°ãã¡ã€ã«
- ãã€ãé å
- AssetBundleïŒ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); }
åæ§ã«ãããã¹ãããŒã¿ã ãã§ãªããä»ã®ããŒã¿ãååŸã§ããŸãã
- ãã€ãé å-www.bytes
- ãã¯ã¹ãã£-www.texture
- ãªãŒãã£ãª-www.GetAudioClip ïŒïŒ
- è³ç£-www.assetBundle
ãã ããããŒãžã§ã³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ã€ã¯ãããŠã³ããŒããããã¡ã€ã«ã®ãã£ãã·ã¥ã§ãã ãã®ãã£ãã·ã¥ã¯äœã®ããã®ãã®ã§ããïŒ
- ãã©ãã£ãã¯ãä¿åããŸãïŒããŠã³ããŒãæžã¿ã®ããŒã¿ãããŠã³ããŒãããªãã§ãã ããïŒ
- ã€ã³ã¿ãŒãããããªãå Žåã®äœæ¥ã®ä¿èšŒïŒãã£ãã·ã¥ããããŒã¿ã衚瀺ã§ããŸãïŒã
äœããã£ãã·ã¥ããå¿ èŠããããŸããïŒ ãã®è³ªåã«å¯Ÿããçãã¯ãã¹ãŠã§ããããŠã³ããŒããããã¹ãŠã®ãã¡ã€ã«ã¯ãã£ãã·ã¥ããå¿ èŠããããŸãã ãããè¡ãæ¹æ³ã以äžãæ€èšããç°¡åãªããã¹ããã¡ã€ã«ããå§ããŸãã
æ®å¿µãªããã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ã€ã®ã¢ãããŒãã䜿çšã§ããŸãã
- CRCãšããŒãžã§ã³çªå·ã䜿çšãã
- ããã·ã¥å€ã®äœ¿çš
ååãšããŠãããªãã¯ãããã®ããããã䜿çšã§ããŸãããç§ã¯ç¬èªã®ããŒãžã§ã³ã·ã¹ãã ãæã¡ã AssetBundleããŒãžã§ã³ã ãã§ãªãã¢ããªã±ãŒã·ã§ã³ã®ããŒãžã§ã³ãèæ ®ãããããå€ãã®å Žåãã³ãã«ã¯ããŒãžã§ã³ãšäºææ§ããªãå¯èœæ§ããããããHashãæãåãå ¥ãããããšæ±ºããŸããåºé ã§çºè¡šã
ããã§ããã£ãã·ã³ã°ã¯ã©ã®ããã«è¡ãããŸããïŒ
- ãããã§ã¹ããµãŒããŒã«ãã³ãã«ãã¡ã€ã«ãèŠæ±ããŸãïŒãã®ãã¡ã€ã«ã¯äœææã«èªåçã«äœæãããå«ãŸããã¢ã»ããã®èª¬æãããã·ã¥ãcrcããµã€ãºãªã©ãå«ãŸããŸãïŒã ãã¡ã€ã«ã«ã¯ããã³ãã«ãšåãååã«æ¡åŒµå.manifestãä»ããŠããŸãã
- ãããã§ã¹ãããhash128å€ãååŸãã
- ãµãŒããŒãžã®ãªã¯ãšã¹ããäœæããŠ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 }
ãã®é¢æ°ã§äœãèµ·ãããïŒ
- ããªã³ã³ãã€ã«ãã£ã¬ã¯ãã£ãDONT_USE_SERVER_IN_EDITORã¯ããµãŒããŒããã®ãã³ãã«ã®å®éã®ããŒããç¡å¹ã«ããããã«äœ¿çšãããŸãã
- æåã®ã¹ãããã¯ããã³ãã«ã®ãããã§ã¹ããã¡ã€ã«ãååŸããããã«ãµãŒããŒã«ãªã¯ãšã¹ããè¡ãããšã§ã
- - , , - ( _externalResourcesStorage ) , , ( , ), , null
- , Caching , , ( )
- , , , , - ( ). , ( )
泚ïŒããã·ã¥å€ãåå¥ã«ä¿åãããçç±ãç解ããããšãéèŠã§ããããã¯ãã€ã³ã¿ãŒãããããªãå Žåãæ¥ç¶ãäžå®å®ãªå ŽåããŸãã¯äœããã®ãããã¯ãŒã¯ãšã©ãŒãçºçãããµãŒããŒãããã³ãã«ãããŠã³ããŒãã§ããªãã£ãå Žåã«å¿ èŠã§ãããã®å Žåããã£ãã·ã¥ãååšããå Žåããã£ãã·ã¥ãããã³ãã«ãããŠã³ããŒãããããšãä¿èšŒãããŸãã
äžèšã®ã¡ãœãããšåæ§ã«ããããŒãžã£ãŒã§ã¯ãããŒã¿ãæäœããããã®ä»ã®é¢æ°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