Que recherchez-vous ?
Engine & platform

Comprendre le langage de sérialisation de Unity, YAML

NICOLAS ALEJANDRO BORROMEO Nicolas Alejandro Borromeo
Jul 28, 2022|13 Min
Comprendre le langage de sérialisation de Unity, YAML
Cette page a été traduite automatiquement pour faciliter votre expérience. Nous ne pouvons pas garantir l'exactitude ou la fiabilité du contenu traduit. Si vous avez des doutes quant à la qualité de cette traduction, reportez-vous à la version anglaise de la page web.

Saviez-vous que vous pouvez éditer n'importe quel type d'actif sans avoir à gérer des langages de sérialisation tels que XML ou JSON dans l'éditeur Unity ? Bien que cela fonctionne la plupart du temps, il y a des cas où vous devez modifier vos fichiers directement. Pensez par exemple aux conflits de fusion ou aux fichiers corrompus.

C'est pourquoi, dans ce billet de blog, nous allons décortiquer le système de sérialisation d'Unity, et partager des cas d'utilisation de ce qui peut être réalisé en modifiant directement les fichiers Asset.

Comme toujours, sauvegardez vos fichiers et, idéalement, utilisez le contrôle de version pour éviter toute perte de données. La modification manuelle des fichiers Asset est une opération risquée et n'est pas prise en charge par Unity. Les fichiers d'actifs ne sont pas conçus pour être modifiés manuellement et ne produisent pas de messages d'erreur utiles pour expliquer ce qui s'est passé en cas d'erreur, ce qui complique la correction des bogues. En comprenant mieux le fonctionnement d'Unity et en vous préparant à résoudre les conflits de fusion, vous pouvez compenser les situations où l'API de la base de données d'actifs ne suffit pas.

Structure YAML

YAML, également connu sous le nom de "YAML Ain't Markup Language", fait partie de la famille des langages de sérialisation de données lisibles par l'homme, tels que XML et JSON. Mais comme il est léger et relativement simple par rapport à d'autres langages courants, il est considéré comme plus facile à lire.

Unity utilise une bibliothèque de sérialisation haute performance qui met en œuvre un sous-ensemble de la spécification YAML. Par exemple, les lignes vides, les commentaires et certaines autres syntaxes utilisées dans YAML ne sont pas prises en charge dans les fichiers Unity. Dans certains cas, le format Unity diverge de la spécification YAML.

Voyons cela en examinant un extrait de code YAML dans un Cube Prefab. Tout d'abord, créez un cube par défaut dans Unity, convertissez-le en Prefab et ouvrez le fichier Prefab dans n'importe quel éditeur de texte. Comme vous pouvez le voir dans la figure 1, les deux premières lignes sont des en-têtes qui ne seront pas répétées par la suite. La première définit la version de YAML que vous utilisez, tandis que la seconde crée une macro appelée "!u !" pour le préfixe URI "tag:unity3d.com,2011 :" (discuté ci-dessous).

Code des lignes d'en-tête au format YAML
Code des lignes d'en-tête au format YAML

Après les en-têtes, vous trouverez une série de définitions d'objets, comme les GameObjects d'un Prefab ou d'une scène, les composants de chaque GameObject, et éventuellement d'autres objets comme les paramètres de Lightmap pour les scènes.

YAML pour un objet de jeu appelé Cube
YAML pour un objet de jeu appelé Cube

Chaque définition d'objet commence par un en-tête de deux lignes, comme dans notre exemple de la figure 2 : "--- !u!1 &7618609094792682308" suit le format "--- !u!{CLASS ID} &{FILE ID}" qui peut être analysé en deux parties :

  • !u!{CLASS ID}:Ceci indique à Unity à quelle classe appartient l'objet. La partie "!u !" sera remplacée par la macro définie précédemment, ce qui nous laisse avec "tag:unity3d.com,2011:1" - le chiffre 1 faisant référence à l'ID du GameObject dans ce cas. Chaque ID de classe est défini dans le code source d'Unity, mais une liste complète peut être trouvée ici.
  • &{FILE ID}:Cette partie définit l'ID de l'objet lui-même, qui est utilisé pour référencer les objets entre eux. Il est appelé ID de fichier car il représente l'ID de l'objet dans un fichier spécifique. Vous trouverez de plus amples informations sur les références croisées dans la suite de ce billet.

La deuxième ligne d'en-tête de l'objet est le nom du type d'objet (ici, GameObject), ce qui permet de l'identifier à la lecture du fichier.

Format de l'en-tête
Format de l'en-tête

Après l'en-tête de l'objet, vous trouverez toutes les propriétés sérialisées. Dans notre exemple de GameObject ci-dessus, la figure 2 fournit des détails tels que son nom (m_Name : Cube) et la couche (m_Layer : 0). Dans le cas de la sérialisation de MonoBehaviour, vous remarquerez les champs publics et les champs privés avec l'attribut SerializeField. Ce format est également utilisé pour les objets scriptables, les animations, les matériaux, etc. Veuillez noter que les objets scriptables utilisent MonoBehaviour comme type d'objet, au lieu de définir leur propre type d'objet. En effet, la même classe interne MonoBehaviour les héberge également.

Refactorisation rapide avec YAML

Avec ce que nous avons couvert jusqu'à présent, vous pouvez commencer à tirer parti de la puissance de la modification de YAML à des fins telles que la refonte des pistes d'animation.

Les fichiers d'animation d'Unity fonctionnent en décrivant un ensemble de pistes ou de courbes d'animation, une pour chaque propriété que vous souhaitez animer. Comme le montre la figure 4, une courbe d'animation identifie l'objet qu'elle doit animer grâce à la propriété du chemin, qui contient les noms des GameObjects enfants jusqu'à l'objet en question. Dans cet exemple, nous animons un GameObject appelé "JumpingCharacter" - un enfant du GameObject "Shoulder", qui est un enfant du GameObject dont le composant Animator joue cette animation. Pour appliquer la même animation à différents objets, le système d'animation utilise des chemins d'accès basés sur des chaînes de caractères au lieu des ID d'objets de jeu.

Code de la propriété path d'une courbe d'animation
Code de la propriété path d'une courbe d'animation

Renommer un objet animé dans la hiérarchie peut entraîner un problème très courant : La courbe pourrait en perdre la trace. Bien que ce problème soit généralement résolu en renommant chaque piste d'animation dans la fenêtre Animation, il arrive que plusieurs animations avec plusieurs courbes soient appliquées au même objet, ce qui rend le processus lent et source d'erreurs. Au lieu de cela, l'édition YAML vous permet de corriger plusieurs chemins de courbes d'animation en une seule fois en utilisant une opération classique de "recherche et remplacement" sur les fichiers d'animation à l'aide de l'éditeur de texte qui vous est le plus familier.

YAML et hiérarchie d'origine à gauche, version renommée du GameObject à droite
YAML et hiérarchie d'origine à gauche, version renommée du GameObject à droite
Références locales

Comme indiqué précédemment, chaque objet d'un fichier YAML possède un identifiant appelé "File ID". Cet identifiant est unique pour chaque objet contenu dans le fichier et sert à résoudre les références entre eux. Pensez à un GameObject et à ses composants, aux composants et à leur GameObject, ou même à des références de script, comme une référence de composant "Weapon" à un GameObject "SpawnPoint" dans le même Prefab.

Le format YAML est le suivant : "{fileID : ID FICHIER}" comme valeur de la propriété. Dans la figure 6, vous pouvez voir que cette transformation appartient à un objet de jeu avec l'ID 4112328598445621100, étant donné que sa propriété "m_GameObject" y fait référence par l'intermédiaire de l'ID de fichier. Vous pouvez également observer des exemples de références nulles comme "m_PrefabInstance" (étant donné que son ID de fichier est zéro). Poursuivez votre lecture pour en savoir plus sur les instances préfabriquées.

Code de transformation associé à un GameObject spécifique
Code de transformation associé à un GameObject spécifique

Prenons le cas de la répartition des objets à l'intérieur d'un Prefab. Vous pouvez modifier le File ID de la propriété "m_Father" d'une Transform avec le File ID de la nouvelle Transform cible, et même corriger l'ancien Transform YAML parent pour supprimer cet objet de son tableau "m_Children", et l'ajouter à la propriété "m_Children" du nouveau parent.

Transformer avec un parent et un seul enfant
Transformer avec un parent et un seul enfant

Pour trouver une transformation spécifique par son nom, vous devez d'abord déterminer son ID de fichier GameObject en recherchant celui qui porte le nom m_Name que vous recherchez. Ce n'est qu'ensuite que vous pourrez localiser la transformation dont la propriété m_GameObject fait référence à cette ID de fichier.

Métafichiers et références croisées

Lorsqu'il s'agit de référencer des objets en dehors de ce fichier, comme un script "Weapon" référençant un Prefab "Bullet", les choses deviennent un peu plus complexes. N'oubliez pas que l'identifiant de fichier est local au fichier, ce qui signifie qu'il peut être répété dans différents fichiers. Afin d'identifier de manière unique un objet dans un autre fichier, nous avons besoin d'un identifiant supplémentaire ou "GUID" qui identifie l'ensemble du fichier plutôt que les objets individuels qu'il contient. Chaque bien a cette propriété GUID définie dans son fichier méta, qui se trouve dans le même dossier que le fichier original, avec le même nom et l'extension ".meta".

Image d'une liste d'actifs Unity et de leurs fichiers méta
Image d'une liste d'actifs Unity et de leurs fichiers méta

Pour les formats de fichiers non natifs d'Unity, comme les images PNG ou les fichiers FBX, Unity sérialise des paramètres d'importation supplémentaires dans les méta-fichiers, comme la résolution maximale et le format de compression d'une texture, ou le facteur d'échelle d'un modèle 3D. Cela permet d'enregistrer séparément les propriétés étendues du fichier et de les versionner de manière pratique dans pratiquement n'importe quel logiciel de contrôle de version. Mais en plus de ces paramètres, Unity enregistrera également les paramètres généraux de l'actif dans le fichier méta, comme le GUID (propriété "GUID") ou l'Asset Bundle (propriété "assetBundleName"), même pour les dossiers ou les fichiers au format natif d'Unity comme les matériaux.

Code pour le fichier Meta d'une texture
Code pour le fichier Meta d'une texture

En gardant cela à l'esprit, vous pouvez identifier un objet de manière unique en combinant le GUID dans le métafichier et le File ID de l'objet dans le YAML, comme le montre la figure 10. Plus précisément, vous pouvez voir que YAML a généré la variable "bulletPrefab" d'un script Weapon, qui référence le GameObject racine avec l'ID de fichier 4551470971191240028 du Prefab avec le GUID afa5a3def08334b95acd2d70ee44a7c2.

Code de référence à un autre objet de fichier
Code de référence à un autre objet de fichier

Vous pouvez également voir un troisième attribut appelé "Type". Le type est utilisé pour déterminer si le fichier doit être chargé à partir du dossier Assets ou du dossier Library. Notez qu'il ne prend en charge que les valeurs suivantes, en commençant par 2 (étant donné que 0 et 1 sont dépréciés) :

  • Type 2: Les actifs qui peuvent être chargés directement par l'éditeur à partir du dossier Assets, comme les matériaux et les fichiers .asset.
  • Type 3: Les actifs qui ont été traités et écrits dans le dossier Bibliothèque, et chargés à partir de là par l'éditeur, comme les préfabriqués, les textures et les modèles 3D.

Un autre élément à souligner concernant la sérialisation des scripts est que le type YAML est le même pour tous les scripts ; il s'agit simplement de MonoBehaviour. Le script proprement dit est référencé dans la propriété "m_Script", à l'aide du GUID du métafichier du script. Vous pouvez ainsi observer comment chaque script est traité, comme un actif.

MonoBehaviour YAML référençant une ressource Script
MonoBehaviour YAML référençant une ressource Script

Les cas d'utilisation de ce scénario incluent, mais ne sont pas limités à :

  • Trouver toutes les utilisations d'un bien en recherchant le GUID du bien dans tous les autres biens.
  • Remplacement de toutes les utilisations de cet actif par un autre GUID d'actif dans l'ensemble du projet
  • Remplacement d'une ressource par une autre ayant une extension différente (par exemple, remplacement d'un fichier MP3 par un fichier WAV) en supprimant la ressource originale, en nommant la nouvelle ressource exactement de la même manière avec la nouvelle extension et en renommant le métafichier de la ressource originale avec la nouvelle extension.
  • Correction des références perdues lors de la suppression et de la réinsertion d'un même bien en remplaçant le GUID de la nouvelle version par le GUID de l'ancienne version.
Instances de préfabriqués, préfabriqués imbriqués et variantes

Lors de l'utilisation d'instances de Prefab dans une scène, ou de Prefabs imbriquées dans un autre Prefab, les GameObjects et composants Prefab ne sont pas sérialisés dans le Prefab qui les utilise, mais un objet PrefabInstance est ajouté. Comme le montre la figure 12, la PrefabInstance possède deux propriétés clés : "m_SourcePrefab" et "m_Modifications".

YAML pour une préfabrication imbriquée
YAML pour une préfabrication imbriquée

Comme vous l'avez peut-être remarqué, "m_SourcePrefab" est une référence à l'élément Nested Prefab. Maintenant, si vous cherchez son ID de fichier dans le Nested Prefab Asset, vous ne le trouverez pas. Dans ce cas, "100100000" est le File ID d'un objet créé lors de l'importation du Prefab, appelé Prefab Asset Handle, qui n'existera pas dans le YAML.

En outre, "m_Modifications" comprend un ensemble de modifications ou de "dérogations" apportées au préfabriqué d'origine. Dans la figure 12, nous remplaçons les axes X, Y et Z de la position locale originale d'un Transform à l'intérieur du Nested Prefab, qui peut être identifié par son File ID dans la propriété target. Notez que la figure 12 ci-dessus a été raccourcie pour des raisons de lisibilité. Une véritable PrefabInstance aura généralement plus d'entrées dans la section m_Modifications.

Vous vous demandez peut-être comment référencer les objets des préfabriqués imbriqués si nous n'avons pas d'objets dans notre préfabriqué extérieur. Pour de tels scénarios, Unity crée un objet "placeholder" dans la préfabrication qui fait référence à l'objet approprié dans la préfabrication imbriquée. Ces objets génériques sont marqués par la balise "stripped", ce qui signifie qu'ils sont simplifiés avec seulement les propriétés nécessaires pour agir en tant qu'objets génériques.

Placeholder Nested Prefab Transform à référencer par ses enfants
Placeholder Nested Prefab Transform à référencer par ses enfants

La figure 13 montre également que nous avons une Transform marquée par la balise "stripped", qui n'a pas les propriétés habituelles d'une Transform (comme "m_LocalPosition"). En revanche, les propriétés "m_CorrespondingSourcePrefab" et "m_PrefabInstance" sont renseignées de manière à faire référence à l'élément Nested Prefab Asset et à l'objet PrefabInstance dans le fichier auquel il appartient. Au-dessus, vous pouvez voir une partie d'une autre transformation dont "m_Father" fait référence à cette Transform placeholder, faisant de ce GameObject un enfant de l'objet Nested Prefab. Au fur et à mesure que vous ferez référence à d'autres objets dans les préfabriqués imbriqués, d'autres objets de remplacement seront ajoutés au YAML.

Il n'y a pas de différence en ce qui concerne les variantes préfabriquées. La préfabrication de base d'une variante est simplement une PrefabInstance avec un Transform qui n'a pas de parent, ce qui signifie qu'il s'agit de l'objet racine de la variante. Dans la figure 14, vous pouvez voir que la propriété "m_TransformParent" de la PrefabInstance fait référence à "fileID : 0.” Cela signifie qu'il n'a pas de père, ce qui en fait l'objet racine.

Code de l'instance Prefab sans parent, ce qui en fait le Prefab de base pour le fichier
Code de l'instance Prefab sans parent, ce qui en fait le Prefab de base pour le fichier

Bien que vous puissiez utiliser ces connaissances pour remplacer un préfabriqué imbriqué ou le préfabriqué de base d'une variante par un autre, ce type de modification peut s'avérer risqué. Procédez avec prudence et prévoyez une solution de secours au cas où.

Commencez par remplacer toutes les références au GUID de la base Prefab actuelle par le GUID de la nouvelle base, à la fois dans l'objet PrefabInstance et dans les objets placeholder. Veillez à prendre note des ID de fichier des objets de remplacement. Leurs propriétés "m_CorrespondingSourceObject" référencent non seulement l'actif, mais aussi les objets qu'il contient via leurs ID de fichier. Il est très probable que les ID de fichier des objets de la préfabrication actuelle soient différents de ceux de la nouvelle préfabrication. Si vous ne les corrigez pas, vous perdrez des substitutions, des références, des objets et d'autres données.

Comme vous pouvez le constater, la modification d'une base ou d'un Nested Prefab n'est pas aussi simple qu'on pourrait le penser. C'est l'une des principales raisons pour lesquelles il n'est pas pris en charge de manière native dans l'éditeur.

Références périmées

Il existe plusieurs scénarios dans lesquels des objets et des références périmés peuvent être laissés dans YAML ; un cas classique serait la suppression de variables dans les scripts. Si vous ajoutez un script Weapon au Player Prefab, vous devrez définir la référence Bullet Prefab à un Prefab existant, puis supprimer la variable Bullet Prefab du script Weapon. À moins que vous ne modifiiez et n'enregistriez à nouveau le Player Prefab, en le resérialisant au passage, la référence à la balle restera dans YAML. Un autre exemple concerne les objets de remplacement des préfabriqués imbriqués qui ne sont pas supprimés lorsque l'objet est supprimé du préfabriqué d'origine, ce qui pourrait être corrigé en modifiant et en sauvegardant le préfabriqué. Enfin, la re-sérialisation des actifs peut être forcée par l'intermédiaire d'un script avec l'API AssetDatabase.ForceReserializeAssets.

Mais pourquoi Unity n'élague-t-il pas automatiquement les références périmées dans les scénarios énumérés ci-dessus ? Ceci est principalement dû à la performance, pour éviter de re-sérialiser tous les actifs à chaque fois que vous changez un script ou une base Prefab. Une autre raison est d'éviter la perte de données. Supposons que vous supprimiez par erreur une propriété de script (telle que Bullet Prefab) et que vous souhaitiez la récupérer. Il vous suffit d'inverser la modification sur votre script. Tant que vous avez une variable portant le même nom que celle qui a été supprimée, vos modifications ne seront pas perdues. La même chose se produirait si vous supprimiez le préfabriqué Bullet référencé. Si vous récupérez la préfabrication exactement comme elle était, y compris le métafichier, la référence sera préservée.

Ce n'est normalement pas un problème pendant l'exécution, étant donné que lorsque Unity construit le lecteur ou les objets adressables, ces objets et références périmés sont effacés. Mais même dans ce cas, les références périmées peuvent poser problème dans certains cas, notamment lors de l'utilisation d'ensembles d'actifs purs. Le calcul de la dépendance du groupe d'actifs prend en compte les références périmées, qui peuvent créer des dépendances inutiles entre les groupes, en chargeant plus que nécessaire au moment de l'exécution. Il convient d'y réfléchir lorsque l'on utilise des regroupements d'actifs. Créez ou utilisez un outil existant pour éliminer les références inutiles.

Conclusion

Bien que vous puissiez ignorer complètement YAML la plupart du temps, il est utile de le connaître pour comprendre le système de sérialisation de Unity. Bien qu'il puisse être rapide et efficace de faire face à des refactors importants et de lire ou de modifier le YAML directement avec des outils de traitement des actifs, il est fortement recommandé de rechercher des solutions basées sur l'API de la base de données d'actifs de Unity lorsque cela est possible. Il est également particulièrement utile pour résoudre les problèmes de fusion dans le cadre du contrôle de version. Nous vous recommandons d'explorer l'outil Smart Merge, qui permet de fusionner automatiquement des préfabriqués en conflit, et d'en savoir plus sur YAML dans notre documentation officielle.