Présentation
Bonjour chers lecteurs, nous allons parler aujourd'hui de travailler avec des ressources externes dans l'environnement Unity 3d.
Par tradition, pour commencer, nous dĂ©terminerons ce que c'est et pourquoi nous en avons besoin. Alors, quelles sont exactement ces ressources externes. Dans le cadre du dĂ©veloppement de jeux, ces ressources peuvent ĂȘtre tout ce qui est nĂ©cessaire au fonctionnement de l'application et ne doivent pas ĂȘtre stockĂ©es dans la version finale du projet. Les ressources externes peuvent ĂȘtre situĂ©es Ă la fois sur le disque dur de l'ordinateur de l'utilisateur et sur un serveur Web externe. Dans le cas gĂ©nĂ©ral, ces ressources sont tout fichier ou ensemble de donnĂ©es que nous chargeons dans notre application dĂ©jĂ en cours d'exĂ©cution. S'exprimant dans le cadre d'Unity 3d, alors ils peuvent ĂȘtre:
- Fichier texte
- Fichier de texture
- Fichier audio
- Tableau d'octets
- AssetBundle (archive avec les actifs du projet Unity 3d)
Ci-dessous, nous examinerons plus en détail les mécanismes intégrés pour travailler avec ces ressources qui sont présents dans Unity 3d, ainsi que pour écrire des gestionnaires simples pour interagir avec le serveur Web et charger des ressources dans l'application.
Remarque : le
reste de cet article utilise du code utilisant C # 7+ et est conçu pour le compilateur Roslyn utilisé dans Unity3d dans les versions 2018.3+.Caractéristiques de Unity 3D
Avant Unity 2017, un mécanisme (à l'exclusion de l'auto-description) était utilisé pour travailler avec les données du serveur et les ressources externes, qui étaient incluses dans le moteur - il s'agit de la classe WWW. Cette classe a permis l'utilisation de diverses commandes http (get, post, put, etc.) sous forme synchrone ou asynchrone (via Coroutine). Le travail avec cette classe était assez simple et direct.
IEnumerator LoadFromServer(string url) { var www = new WWW(url); yield return www; Debug.Log(www.text); }
De mĂȘme, vous pouvez obtenir non seulement des donnĂ©es texte, mais Ă©galement d'autres:
Cependant, Ă partir de la version 2017, Unity dispose d'un nouveau systĂšme de serveur introduit par la classe
UnityWebRequest , qui se trouve dans l'espace de noms Networking. Jusqu'Ă Unity 2018, il existait avec
WWW , mais dans la derniĂšre version du moteur
WWW , il n'était plus recommandé, et à l'avenir, il sera complÚtement supprimé. Par conséquent, nous nous concentrerons davantage sur
UnityWebRequest (ci-aprĂšs UWR).
Travailler avec UWR dans son ensemble est similaire Ă WWW dans son cĆur, mais il existe des diffĂ©rences, qui seront discutĂ©es plus tard. Voici un exemple similaire de chargement de texte.
IEnumerator LoadFromServer(string url) { var request = new UnityWebRequest(url); yield return request.SendWebRequest(); Debug.Log(request.downloadHandler.text); request.Dispose(); }
Les principaux changements que le nouveau systĂšme UWR a introduits (en plus de changer le principe de travailler Ă l'intĂ©rieur) sont la possibilitĂ© d'affecter des gestionnaires pour tĂ©lĂ©charger et tĂ©lĂ©charger des donnĂ©es depuis le serveur lui-mĂȘme, plus de dĂ©tails peuvent ĂȘtre lus
ici . Par défaut, ce sont les classes
UploadHandler et
DownloadHandler . Unity lui-mĂȘme fournit un ensemble d'extensions de ces classes pour travailler avec diverses donnĂ©es, telles que l'audio, les textures, les actifs, etc. Envisageons de travailler avec eux plus en dĂ©tail.
Travailler avec des ressources
Texte
Travailler avec du texte est l'une des options les plus simples. La mĂ©thode de tĂ©lĂ©chargement a dĂ©jĂ Ă©tĂ© dĂ©crite ci-dessus. Nous le réécrivons un peu en utilisant la crĂ©ation d'une requĂȘte http Get directe.
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(); }
Comme vous pouvez le voir dans le code, le
DownloadHandler par défaut est utilisé ici. La propriété text est un getter qui convertit un tableau d'octets en texte codé UTF8. L'utilisation principale du chargement de texte à partir du serveur est de recevoir un fichier json (représentation sérialisée des données sous forme de texte). Vous pouvez obtenir ces données à l'aide de la classe Unity
JsonUtility .
var data = JsonUtility.FromJson<T>(value);
Audio
Pour travailler avec l'audio, vous devez utiliser la méthode spéciale de création de la demande
UnityWebRequestMultimedia.GetAudioClip , et pour obtenir la représentation des données sous la forme nécessaire pour travailler dans Unity, vous devez utiliser
DownloadHandlerAudioClip . De plus, lors de la création d'une demande, vous devez spécifier le type de données audio représenté par l'énumération
AudioType , qui définit le format (wav, aiff, oggvorbis, etc.).
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(); }
La texture
Le téléchargement des textures est similaire à celui des fichiers audio. La demande est créée à l'aide de
UnityWebRequestTexture.GetTexture . Pour obtenir les données sous la forme nécessaire pour Unity,
DownloadHandlerTexture est utilisé.
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(); }
Ensemble d'actifs
Comme mentionnĂ© prĂ©cĂ©demment, le bundle est, en fait, une archive avec des ressources Unity qui peuvent ĂȘtre utilisĂ©es dans un jeu dĂ©jĂ en cours d'exĂ©cution. Ces ressources peuvent ĂȘtre n'importe quel actif du projet, y compris des scĂšnes. L'exception concerne les scripts C #, ils ne peuvent pas ĂȘtre transmis. Pour charger le
AssetBundle , une requĂȘte est utilisĂ©e qui est créée Ă l'aide de
UnityWebRequestAssetBundle.GetAssetBundle. DownloadHandlerAssetBundle est utilisé pour obtenir des données sous la forme nécessaire pour Unity.
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(); }
Les principaux problÚmes et solutions lors de l'utilisation d'un serveur Web et de données externes
Des méthodes simples d'interaction entre une application et un serveur en termes de chargement de différentes ressources ont été décrites ci-dessus. Cependant, dans la pratique, les choses sont beaucoup plus compliquées. Considérez les principaux problÚmes qui accompagnent les développeurs et insistez sur les moyens de les résoudre.
Pas assez d'espace libre
L'un des premiers problĂšmes lors du tĂ©lĂ©chargement de donnĂ©es Ă partir du serveur est un Ă©ventuel manque d'espace libre sur l'appareil. Il arrive souvent que l'utilisateur utilise de vieux appareils pour les jeux (en particulier sur Android), ainsi que la taille des fichiers tĂ©lĂ©chargĂ©s lui-mĂȘme peut ĂȘtre assez grande (bonjour PC). Dans tous les cas, cette situation doit ĂȘtre correctement traitĂ©e et le joueur doit ĂȘtre informĂ© Ă l'avance qu'il n'y a pas assez d'espace et combien. Comment faire La premiĂšre chose que vous devez savoir est la taille du fichier tĂ©lĂ©chargĂ©, cela se fait au moyen de la demande
UnityWebRequest.Head () . Voici le code pour obtenir la taille.
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); } }
Il est important de noter une chose ici, pour que la requĂȘte fonctionne correctement, le serveur doit ĂȘtre en mesure de renvoyer la taille du contenu, sinon (comme, en fait, pour afficher la progression), la mauvaise valeur sera retournĂ©e.
AprÚs avoir obtenu la taille des données téléchargées, nous pouvons les comparer avec la taille de l'espace disque libre. Pour obtenir ce dernier, j'utilise le
plugin gratuit du Asset Store .
Remarque :
vous pouvez utiliser la classe Cache dans Unity3d, elle peut afficher l'espace de cache libre et utilisĂ©. Cependant, il convient de considĂ©rer le fait que ces donnĂ©es sont relatives. Ils sont calculĂ©s en fonction de la taille du cache lui-mĂȘme, par dĂ©faut, il est de 4 Go. Si l'utilisateur a plus d'espace libre que la taille du cache, il n'y aura pas de problĂšme, mais si ce n'est pas le cas, les valeurs peuvent prendre des valeurs incorrectes par rapport Ă la situation rĂ©elle.VĂ©rification de l'accĂšs Internet
TrĂšs souvent, avant de tĂ©lĂ©charger quoi que ce soit Ă partir du serveur, il est nĂ©cessaire de gĂ©rer la situation de manque d'accĂšs Ă Internet. Il existe plusieurs façons de procĂ©der: du ping d'une adresse Ă une demande GET sur google.ru. Cependant, Ă mon avis, le rĂ©sultat le plus correct et le plus rapide et le plus stable consiste Ă tĂ©lĂ©charger Ă partir de votre propre serveur (le mĂȘme Ă partir duquel les fichiers seront tĂ©lĂ©chargĂ©s) un petit fichier. La procĂ©dure Ă suivre est dĂ©crite ci-dessus dans la section sur l'utilisation du texte.
En plus de vĂ©rifier le fait d'avoir accĂšs Ă Internet, il est Ă©galement nĂ©cessaire de dĂ©terminer son type (mobile ou WiFi), car il est peu probable qu'un joueur veuille tĂ©lĂ©charger plusieurs centaines de mĂ©gaoctets sur le trafic mobile. Cela peut ĂȘtre fait via la propriĂ©tĂ©
Application.internetReachability .
Mise en cache
Le suivant, et l'un des problÚmes les plus importants, est la mise en cache des fichiers téléchargés. à quoi sert cette mise en cache?
- Ăconomisez du trafic (ne tĂ©lĂ©chargez pas les donnĂ©es dĂ©jĂ tĂ©lĂ©chargĂ©es)
- Assurer le travail en l'absence d'Internet (vous pouvez afficher les données du cache).
Qu'est-ce qui doit ĂȘtre mis en cache? La rĂ©ponse Ă cette question est tout, tous les fichiers que vous tĂ©lĂ©chargez doivent ĂȘtre mis en cache. Comment faire, considĂ©rez ci-dessous et commencez avec des fichiers texte simples.
Malheureusement, Unity n'a pas de mĂ©canisme intĂ©grĂ© pour la mise en cache du texte, ainsi que des textures et des fichiers audio. Par consĂ©quent, pour ces ressources, il est nĂ©cessaire d'Ă©crire votre systĂšme, ou de ne pas Ă©crire, selon les besoins du projet. Dans le cas le plus simple, nous Ă©crivons simplement le fichier dans le cache et, en l'absence d'Internet, nous en prenons le fichier. Dans une version lĂ©gĂšrement plus complexe (je l'utilise dans des projets), nous envoyons une requĂȘte au serveur, qui retourne json indiquant les versions des fichiers qui sont stockĂ©s sur le serveur. Vous pouvez Ă©crire et lire des fichiers Ă partir du cache Ă l'aide de la classe C # de la classe
File ou de toute autre maniÚre pratique et acceptée par votre équipe.
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); }
De mĂȘme, obtenir des donnĂ©es du cache.
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; }
Remarque :
pourquoi le mĂȘme UWR avec une URL du fichier de formulaire: // n'est pas utilisĂ© pour charger les textures. Pour le moment, il y a des problĂšmes avec cela, le fichier ne se charge tout simplement pas, j'ai donc dĂ» trouver une solution de contournement.Remarque :
Je n'utilise pas le chargement direct d'AudioClip dans les projets, je stocke toutes ces donnĂ©es dans AssetBundle. Cependant, si nĂ©cessaire, cela peut facilement ĂȘtre fait en utilisant les fonctions de la classe AudioClip GetData et SetData.
Les ressources simples d'Unity pour
AssetBundle , Unity dispose d'un mécanisme de mise en cache intégré. Examinons-le plus en détail.
Fondamentalement, ce mécanisme peut utiliser deux approches:
- Utilisation du CRC et du numéro de version
- Utilisation des valeurs de hachage
En principe, vous pouvez utiliser n'importe lequel d'entre eux, mais j'ai dĂ©cidĂ© moi-mĂȘme que Hash est le plus acceptable, car j'ai mon propre systĂšme de version et il prend en compte non seulement la version
AssetBundle , mais aussi la version de l'application, car souvent le bundle peut ne pas ĂȘtre compatible avec la version, prĂ©sentĂ© en magasin.
Alors, comment se fait la mise en cache:
- Nous demandons un fichier de bundle au serveur manifeste (ce fichier est créé automatiquement lors de sa crĂ©ation et contient une description des actifs qu'il contient, ainsi que le hachage, le crc, la taille, etc.). Le fichier a le mĂȘme nom que le bundle plus l'extension .manifest.
- Obtenez la valeur hash128 du manifeste
- Nous crĂ©ons une demande au serveur pour obtenir un AssetBundle, oĂč en plus de l'URL, spĂ©cifiez la valeur de hachage128 reçue
Code pour l'algorithme décrit ci-dessus: IEnumerator LoadAssetBundleFromServerWithCache(string url, Action<AssetBundle> response) {
Dans l'exemple ci-dessus, Unity, lors de l'interrogation du serveur, cherche d'abord à voir s'il y a un fichier dans le cache avec la valeur de hachage 128 spécifiée, si c'est le cas, il sera renvoyé; sinon, le fichier mis à jour sera téléchargé. Pour gérer tous les fichiers de cache dans Unity, il existe une classe de
mise en cache , avec laquelle nous pouvons savoir s'il y a un fichier dans le cache, obtenir toutes les versions mises en cache, ainsi que supprimer les versions inutiles ou les effacer complĂštement.
Remarque :
pourquoi une telle façon Ă©trange d'obtenir des valeurs de hachage? Cela est dĂ» au fait que l'obtention de hachage128 de la maniĂšre dĂ©crite dans la documentation nĂ©cessite le chargement de l'ensemble complet, puis la rĂ©ception de l'actif AssetBundleManifest de celui-ci et de lĂ dĂ©jĂ des valeurs de hachage. L'inconvĂ©nient de cette approche est que l'ensemble du AssetBundle oscille, mais nous avons juste besoin qu'il ne le soit pas. Par consĂ©quent, nous tĂ©lĂ©chargeons d'abord uniquement le fichier manifeste depuis le serveur, prenons hash128 de celui-ci, et seulement ensuite, si nĂ©cessaire, tĂ©lĂ©chargeons le fichier de bundle, et la valeur hash128 doit ĂȘtre extraite via l'interprĂ©tation des lignes.Travailler avec des ressources en mode Ă©diteur
Le dernier problĂšme, ou plutĂŽt la question du dĂ©bogage et de la commoditĂ© du dĂ©veloppement, est de travailler avec des ressources tĂ©lĂ©chargeables en mode Ă©diteur, s'il n'y a pas de problĂšmes avec les fichiers normaux, alors les choses ne sont pas si simples avec les bundles. Bien sĂ»r, vous pouvez construire leur build Ă chaque fois, le tĂ©lĂ©charger sur le serveur et lancer l'application dans l'Ă©diteur Unity et regarder comment tout fonctionne, mais mĂȘme cette description ressemble Ă une "bĂ©quille". Quelque chose doit ĂȘtre fait avec cela et pour cela la classe
AssetDatabase nous aidera.
Afin d'unifier le travail avec les bundles, j'ai créé un wrapper spécial:
public class AssetBundleWrapper { private readonly AssetBundle _assetBundle; public AssetBundleWrapper(AssetBundle assetBundle) { _assetBundle = assetBundle; } }
Maintenant, nous devons ajouter deux modes de travail avec les actifs, selon que nous sommes dans l'éditeur ou dans la build. Pour la construction, nous utilisons des wrappers sur les fonctions de la classe
AssetBundle , et pour l'éditeur, nous utilisons la classe
AssetDatabase mentionnée ci-dessus.
Ainsi, nous obtenons le code suivant: 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 }
Remarque : le code utilise la classe
TaskManager , il sera discuté ci-dessous, en bref, c'est un wrapper pour travailler avec
Coroutine .
En plus de ce qui prĂ©cĂšde, il est Ă©galement utile pendant le dĂ©veloppement de regarder ce que nous avons tĂ©lĂ©chargĂ© et ce qui est actuellement dans le cache. Ă cette fin, vous pouvez profiter de la possibilitĂ© de dĂ©finir votre propre dossier, qui sera utilisĂ© pour la mise en cache (vous pouvez Ă©galement Ă©crire du texte tĂ©lĂ©chargĂ© et d'autres fichiers dans le mĂȘme dossier):
#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);
Nous Ă©crivons un gestionnaire de requĂȘtes rĂ©seau ou travaillons avec un serveur web
Ci-dessus, nous avons examinĂ© les principaux aspects du travail avec des ressources externes dans Unity, maintenant je voudrais m'attarder sur la mise en Ćuvre de l'API, qui gĂ©nĂ©ralise et unifie tout ce qui prĂ©cĂšde. Et d'abord, attardons-nous sur le gestionnaire de requĂȘtes rĂ©seau.
Remarque :
ci-aprÚs, nous utilisons le wrapper sur Coroutine sous la forme de la classe TaskManager . J'ai écrit sur ce wrapper dans un autre article .Obtenons la classe correspondante:
public class Network { public enum NetworkTypeEnum { None, Mobile, WiFi } public static NetworkTypeEnum NetworkType; private readonly TaskManager _taskManager = new TaskManager(); }
Le champ statique
NetworkType est requis pour que l'application reçoive des informations sur le type de connexion Internet. En principe, cette valeur peut ĂȘtre stockĂ©e n'importe oĂč, j'ai dĂ©cidĂ© que c'Ă©tait la place dans la classe
Network .
Ajoutez la fonction de base d'envoi d'une requĂȘte au serveur: 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(); }
Comme vous pouvez le voir dans le code, la mĂ©thode de traitement de l'achĂšvement d'une demande a Ă©tĂ© modifiĂ©e par rapport au code des sections prĂ©cĂ©dentes. Il s'agit de montrer la progression du chargement des donnĂ©es. De plus, toutes les demandes envoyĂ©es sont stockĂ©es dans une liste afin que, si nĂ©cessaire, elles puissent ĂȘtre annulĂ©es.
Ajoutez une fonction de crĂ©ation de requĂȘte basĂ©e sur un lien pour 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); }
De mĂȘme, des fonctions sont créées pour la texture, l'audio, le texte, le tableau d'octets.
Vous devez maintenant vous assurer que le serveur envoie des données via la commande Post. Souvent, vous devez transmettre quelque chose au serveur et, en fonction de quoi exactement, obtenir une réponse. Ajoutez les fonctions appropriées.
Envoi de données sous la forme d'un ensemble de valeurs-clés: 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); }
Envoi de données au format 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); }
Nous allons maintenant ajouter des méthodes publiques avec lesquelles nous chargerons des données, en particulier 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); }
De mĂȘme, des mĂ©thodes sont ajoutĂ©es pour la texture, le fichier audio, le texte, etc.
Et enfin, nous ajoutons la fonction d'obtention de la taille du fichier tĂ©lĂ©chargĂ© et la fonction de nettoyage pour arrĂȘter toutes les requĂȘtes créées. 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(); }
à ce sujet, notre gestionnaire pour travailler avec les demandes de réseau est terminé. Si nécessaire, chaque sous-systÚme du jeu qui nécessite de travailler avec le serveur peut créer ses propres instances de la classe.
Ăcrire un gestionnaire de chargement de ressources externes
En plus de la classe décrite ci-dessus, pour travailler pleinement avec des données externes, nous avons besoin d'un gestionnaire distinct qui non seulement téléchargera les données, mais informera également l'application du début du chargement, de l'achÚvement, de la progression, du manque d'espace libre et traitera également les problÚmes de mise en cache.
Nous commençons la classe correspondante, qui dans mon cas est un singleton 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); } }
Comme vous pouvez le voir, le concepteur définit le dossier pour la mise en cache, selon que nous soyons dans l'éditeur ou non. De plus, nous avons configuré un champ privé pour une instance de la classe Network, que nous avons décrite précédemment.Nous allons maintenant ajouter des fonctions auxiliaires pour travailler avec le cache, ainsi que pour déterminer la taille du fichier téléchargé et vérifier l'espace libre pour celui-ci. Plus loin et plus bas, le code est donné sur un exemple de travail avec AssetBundle, pour le reste des ressources tout se fait par analogie.Code de fonction d'assistance 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; }
Ajoutons maintenant des fonctions de chargement de données en utilisant l'exemple 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 }
Alors, que se passe-t-il dans cette fonction:- La directive de précompilation DONT_USE_SERVER_IN_EDITOR est utilisée pour désactiver le chargement réel des bundles depuis le serveur.
- La premiÚre étape consiste à faire une demande au serveur pour obtenir le fichier manifeste du bundle
- - , , - ( _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; } } }
Ici, il vaut la peine de comprendre la particularitĂ© du TaskManager , qui est utilisĂ© dans le gestionnaire de requĂȘtes rĂ©seau, par dĂ©faut, il fonctionne, effectuant toutes les tĂąches Ă tour de rĂŽle. Par consĂ©quent, le tĂ©lĂ©chargement des fichiers se fera en consĂ©quence.Remarque : pour ceux qui n'aiment pas Coroutine , tout peut ĂȘtre facilement traduit en async / wait , mais dans ce cas, dans l'article, j'ai dĂ©cidĂ© d'utiliser une option plus comprĂ©hensible pour les dĂ©butants (comme il me semble).Conclusion
. , . , ( f2p ), , , , , .
, :
assetstore.unity.com/packages/tools/simple-disk-utils-59382habr.com/post/352296habr.com/post/282524