Novedades

Comprende el lenguaje de serialización de Unity, YAML



¿Sabías que puedes editar cualquier tipo de activo sin la molestia de lidiar con lenguajes de serialización como XML o JSON en el Editor de Unity? Si bien esto funciona la mayor parte del tiempo, sin embargo, hay algunos casos en los que debe modificar sus archivos directamente. Piense en conflictos de fusión o archivos corruptos como ejemplos. 

Es por eso que, en esta publicación de blog, desglosaremos aún más el sistema de serialización de Unity y compartiremos casos de uso de lo que se puede lograr modificando archivos de activos directamente.

Como siempre, haga una copia de seguridad de sus archivos e, idealmente, use el control de versiones para evitar la pérdida de datos. La modificación manual de los archivos de activos es una operación riesgosa y no es compatible con Unity. Los archivos de activos no están diseñados para modificarse manualmente y no generarán mensajes de error útiles para explicar qué sucedió si se producen errores y cuándo, lo que dificulta la corrección de errores. Al comprender mejor cómo funciona Unity y prepararse para resolver los conflictos de combinación, puede compensar las situaciones en las que la API de la base de datos de activos no es suficiente.

Estructura YAML

YAML, también conocido como "YAML no es un lenguaje de marcado", es parte de la familia de lenguajes de serialización de datos legibles por humanos como XML y JSON. Pero debido a que es liviano y relativamente sencillo en comparación con otros lenguajes comunes, se considera más fácil de leer.

Unity usa una biblioteca de serialización de alto rendimiento que implementa un subconjunto de la especificación YAML. Por ejemplo, las líneas en blanco, los comentarios y alguna otra sintaxis admitida en YAML no son compatibles con los archivos de Unity. En ciertos casos extremos, el formato de Unity difiere de la especificación YAML.

Exploremos esto mirando un fragmento de código YAML en un Cube Prefab. Primero, cree un cubo predeterminado en Unity, conviértalo en Prefab y abra el archivo Prefab en cualquier editor de texto. Como puede ver en la Figura 1, las dos primeras líneas son encabezados que no se repetirán más adelante. El primero define qué versión de YAML estás usando, mientras que el segundo crea una macro llamada “!u!” para el prefijo URI “tag:unity3d.com,2011:” (discutido a continuación). 

Figura 1: Líneas de encabezado en formato YAML

Siguiendo los encabezados, encontrará una serie de definiciones de objetos, como GameObjects en un Prefab o escena, los componentes de cada GameObject y posiblemente otros objetos como la configuración de Lightmap para escenas.
YAML para un GameObject llamado Cube
Figura 2: YAML para un GameObject llamado Cube

Cada definición de objeto comienza con un encabezado de dos líneas, como el de nuestro ejemplo de la Figura 2: “--- !u!1 &7618609094792682308” sigue el formato “--- !u!{ID DE CLASE} &{ID DE ARCHIVO }” que se puede analizar en dos partes:

  • !u!{ ID DE CLASE } :Esto le dice a Unity a qué clase pertenece el objeto. El “!u!” parte será reemplazada con la macro previamente definida, dejándonos con “tag:unity3d.com,2011:1” – el número 1 se refiere a la GameObject ID en este caso. Cada ID de clase se define en el código fuente de Unity, pero se puede encontrar una lista completa de ellos aquí .
  • &{ID DE ARCHIVO} : esta parte define la ID del objeto en sí, que se utiliza para hacer referencia a objetos entre sí. Se llama ID de archivo porque representa la ID del objeto en un archivo específico. Siga leyendo para obtener más información sobre las referencias entre archivos más adelante en esta publicación.

La línea de encabezado del segundo objeto es el nombre del tipo de objeto (aquí, GameObject), que le permite identificarlo leyendo el archivo.

Formato de encabezado
Figura 3: formato de encabezado

Después del encabezado del objeto, puede encontrar todas las propiedades serializadas. En nuestro ejemplo de GameObject anterior, la figura 2 proporciona detalles como su nombre (m_Name: Cube) y la capa (m_Layer: 0). En el caso de la serialización de MonoBehaviour, notará los campos públicos y los privados con el atributo SerializeField. Este formato se usa de manera similar para ScriptableObjects, Animations, Materials, etc. Tenga en cuenta que ScriptableObjects usa MonoBehaviour como su tipo de objeto, en lugar de definir el suyo propio. Esto se debe a que la misma clase MonoBehaviour interna también los aloja.

Refactorización rápida con YAML

Con lo que hemos cubierto hasta ahora, puede comenzar a aprovechar el poder de modificar YAML para fines como la refactorización de pistas de animación.

Los archivos de animación de Unity funcionan describiendo un conjunto de pistas o curvas de animación; uno para cada propiedad que desee animar. Como se muestra en la figura 4, una curva de animación identifica el objeto que necesita animar a través de la propiedad de la ruta, que contiene los nombres de los GameObjects secundarios hasta el específico. En este ejemplo, estamos animando un GameObject llamado "JumpingCharacter", un elemento secundario del GameObject "Shoulder", que es un elemento secundario del GameObject que tiene el componente Animator reproduciendo esta animación. Para aplicar la misma animación a diferentes objetos, el sistema de animación utiliza rutas basadas en cadenas en lugar de ID de GameObject.

Figura 4: Propiedad de ruta de una curva de animación

Cambiar el nombre de un objeto animado en la jerarquía puede provocar un problema muy común: la curva puede perder el rastro. Si bien esto generalmente se resuelve cambiando el nombre de cada pista de animación en la ventana Animación, hay casos en los que se aplican varias animaciones con varias curvas al mismo objeto, lo que lo convierte en un proceso lento y propenso a errores. En cambio, la edición YAML le permite corregir varias rutas de curvas de animación de una sola vez mediante una operación clásica de "buscar y reemplazar" en los archivos de animación con el editor de texto con el que está más familiarizado.


Figura 5: YAML original y jerarquía a la izquierda, versión renombrada de GameObject a la derecha

Referencias locales

Como se mencionó anteriormente, cada objeto en un archivo YAML tiene una identificación conocida como "ID de archivo". Este ID es único para cada objeto dentro del archivo y sirve para resolver las referencias entre ellos. Piense en un GameObject y sus componentes, los componentes y su GameObject, o incluso referencias de secuencias de comandos, como una referencia de componente "Arma" a un GameObject "SpawnPoint" en el mismo Prefab.

El formato YAML para esto es "{fileID: FILE ID}" como el valor de la propiedad. En la Figura 6 se puede ver como este Transform pertenece a un GameObject con ID 4112328598445621100, dado que su propiedad “m_GameObject” lo referencia a través del File ID. También puede observar ejemplos de referencias nulas como "m_PrefabInstance" (dado que su ID de archivo es cero). Siga leyendo para obtener más información sobre las instancias prefabricadas.

Figura 6: Transformación asociada con un GameObject específico

Consideremos el caso de reparentar objetos dentro de un Prefab. Puede cambiar el Id. de archivo de la propiedad "m_Father" de una Transformación con el Id. de archivo de la nueva Transformación de destino, e incluso corregir el YAML de la Transformación principal anterior para eliminar este objeto de su matriz "m_Children" y agregarlo a la nueva propiedad padre “m_Children”.


Figura 7: Transformar con un padre y un solo hijo

Para encontrar una Transformación específica por nombre, primero debe determinar su ID de archivo de GameObject buscando el que tiene el m_Name que está buscando. Solo entonces podrá localizar el Transform cuya propiedad m_GameObject hace referencia a ese ID de archivo.

Metaarchivos y referencias entre archivos

Cuando se hace referencia a objetos fuera de este archivo, como un script de "Arma" que hace referencia a un prefabricado "Bullet", las cosas se vuelven un poco más complejas. Recuerde que el ID de archivo es local para el archivo, lo que significa que puede repetirse en diferentes archivos. Para identificar de manera única un objeto en otro archivo, necesitamos una ID adicional o "GUID" que identifique todo el archivo en lugar de los objetos individuales dentro de él. Cada activo tiene esta propiedad GUID definida en su metaarchivo, que se puede encontrar en la misma carpeta que el archivo original, con exactamente el mismo nombre más una extensión ".meta" agregada.

Imagen de una lista de recursos de Unity y sus metaarchivos

Figura 8: Activos de Unity y sus metaarchivos

Para los formatos de archivo nativos que no son de Unity, como imágenes PNG o archivos FBX, Unity serializa configuraciones de importación adicionales para ellos en los metaarchivos, como la resolución máxima y el formato de compresión de una textura, o el factor de escala de un modelo 3D. Esto se hace para guardar las propiedades extendidas del archivo por separado y convertirlas convenientemente en casi cualquier software de control de versiones. Pero además de esta configuración, Unity también guardará la configuración general de activos en el metaarchivo, como el GUID (propiedad "GUID") o el paquete de activos (propiedad "assetBundleName"), incluso para carpetas o archivos de formato nativo de Unity como materiales.
Código para archivo Meta para una texturaFigura 9: Metaarchivo para una textura

Figura 9: Metaarchivo para una textura

Con esto en mente, puede identificar un objeto de manera única combinando el GUID en el metaarchivo y el ID de archivo del objeto dentro de YAML, como se muestra en la Figura 10. Más específicamente, puede ver que YAML generó la variable "bulletPrefab" de un script de Arma, que hace referencia al GameObject raíz con el ID de archivo 4551470971191240028 del Prefab con el GUID afa5a3def08334b95acd2d70ee44a7c2.


Figura 10: Referencia a otro objeto de archivo

También puede ver un tercer atributo llamado "Tipo". El tipo se utiliza para determinar si el archivo debe cargarse desde la carpeta Activos o desde la carpeta Biblioteca. Tenga en cuenta que solo admite los siguientes valores, a partir de 2 (dado que 0 y 1 están en desuso):
  • Tipo 2 : activos que el editor puede cargar directamente desde la carpeta de activos, como materiales y archivos .asset
  • Tipo 3 : recursos que han sido procesados ​​y escritos en la carpeta Biblioteca, y cargados desde allí por el Editor, como prefabricados, texturas y modelos 3D

Otro factor a destacar con respecto a la serialización de secuencias de comandos es que el tipo YAML es el mismo para todas las secuencias de comandos; solo MonoBehaviour. Se hace referencia al script real en la propiedad "m_Script", utilizando el GUID del metaarchivo del script. Con esto, puede observar cómo se trata cada script, solo como un activo.


Figura 11: MonoBehaviour YAML haciendo referencia a un recurso de script

Los casos de uso para este escenario incluyen, pero no se limitan a:

  • Encontrar todos los usos de un activo buscando el GUID del activo en todos los demás activos
  • Reemplazar todos los usos de ese activo con otro GUID de activo en todo el proyecto 
  • Reemplazar un activo con otro que tenga una extensión diferente (es decir, reemplazar un archivo MP3 con un archivo WAV) eliminando el activo original, nombrando el nuevo exactamente igual con la nueva extensión y renombrando el metaarchivo del activo original con la nueva extensión
  • Corrección de referencias perdidas al eliminar y volver a agregar el mismo activo cambiando el GUID de la nueva versión con el GUID de la versión anterior

Instancias prefabricadas, prefabricadas anidadas y variantes

Cuando se usan instancias de Prefab en una escena, o Prefabs anidados dentro de otro Prefab, los GameObjects y los componentes de Prefab no se serializan en el Prefab que los usa, sino que se agrega un objeto PrefabInstance. Como puede ver en la Figura 12, PrefabInstance tiene dos propiedades clave: "m_SourcePrefab" y "m_Modifications".

YAML para un prefabricado anidado
Figura 12: YAML para una casa prefabricada anidad

Como habrá notado, "m_SourcePrefab" es una referencia al activo prefabricado anidado. Ahora, si busca su ID de archivo en el activo prefabricado anidado, no lo encontrará. En este caso, "100100000" es el Id. de archivo de un objeto creado durante la importación de Prefab, llamado Prefab Asset Handle, que no existirá en YAML.

Además, "m_Modifications" comprende un conjunto de modificaciones o "anulaciones" realizadas en el Prefab original. En la Figura 12, anulamos los ejes X, Y y Z de la posición local original de una Transformación dentro del prefabricado anidado, que se puede identificar a través de su ID de archivo en la propiedad de destino. Tenga en cuenta que la Figura 12 anterior se ha abreviado para facilitar la lectura. Una PrefabInstance real normalmente tendrá más entradas en la sección m_Modifications.

Ahora, es posible que se pregunte, si no tenemos los objetos prefabricados anidados en nuestro prefabricado externo, ¿cómo hacemos referencia a los objetos en los prefabricados anidados? Para tales escenarios, Unity crea un objeto de "marcador de posición" en el Prefab que hace referencia al objeto adecuado en el Prefab anidado. Estos objetos de marcador de posición están marcados con la etiqueta "despojada", lo que significa que se simplifican con solo las propiedades necesarias para actuar como objetos de marcador de posición.

Transformación prefabricada anidada de marcador de posición para ser referenciada por sus elementos secundarios
Figura 13: Transformación prefabricada anidada de marcador de posición para ser referenciada por sus hijos

La Figura 13 muestra de manera similar cómo tenemos un Transform marcado con la etiqueta "stripped", que no tiene las propiedades habituales de un Transform (como "m_LocalPosition"). En su lugar, tiene las propiedades "m_CorrespondingSourcePrefab" y "m_PrefabInstance" rellenadas de una manera que hace referencia al activo prefabricado anidado y al objeto PrefabInstance en el archivo al que pertenece. Encima, puede ver parte de otra transformación cuyo "m_Father" hace referencia a esta Transformación de marcador de posición, lo que convierte a GameObject en un elemento secundario del objeto Nested Prefab. A medida que comience a hacer referencia a más objetos en los prefabricados anidados, se agregarán más de estos objetos de marcador de posición a YAML.

Convenientemente, no hay diferencia cuando se trata de variantes prefabricadas. El Prefab base de una Variant es solo una PrefabInstance con una Transformación que no tiene padre, lo que significa que es el objeto raíz de la Variant. En la Figura 14, puede ver que la propiedad "m_TransformParent" de PrefabInstance hace referencia a "fileID: 0". Esto significa que no tiene padre, lo que lo convierte en el objeto raíz.

Código de instancia de Prefab sin padre, lo que lo convierte en el Prefab base para el archivo
Figura 14: instancia de Prefab sin principal, lo que la convierte en la Prefab base para el archivo

Si bien puede usar este conocimiento para reemplazar un prefabricado anidado o el prefabricado base de una variante por otro, este tipo de modificación puede ser riesgoso. Proceda con precaución y tenga una copia de seguridad por si acaso.

Comience reemplazando todas las referencias al GUID del Prefab base actual con el GUID del nuevo, tanto en el objeto PrefabInstance como en los objetos de marcador de posición. Asegúrese de tomar nota de los ID de archivo de los objetos de marcador de posición. Sus propiedades "m_CorrespondingSourceObject" no solo hacen referencia al activo, sino también a los objetos dentro de él a través de sus ID de archivo. Es muy probable que los ID de archivo de los objetos en el Prefab actual difieran de los del nuevo Prefab, y si no los corrige, perderá anulaciones, referencias, objetos y otros datos.

Como puede ver, cambiar una base o un prefabricado anidado no es tan sencillo como podría pensarse. Esa es una de las razones principales por las que no se admite de forma nativa en el Editor.

Referencias obsoletas

Hay varios escenarios en los que se pueden dejar objetos obsoletos y referencias en YAML; un caso clásico sería eliminar variables en scripts. Si agrega una secuencia de comandos de armas a Player Prefab, tendría que establecer la referencia de Bullet Prefab a una prefabricada existente y luego eliminar la variable Bullet Prefab de la secuencia de comandos de armas. A menos que cambie y guarde el Player Prefab nuevamente, volviendo a serializarlo en el proceso, la referencia de la viñeta permanecerá en YAML. Otro ejemplo se centra en los objetos de marcador de posición de Prefabs anidados que no se eliminan cuando el objeto se elimina del Prefab original, lo que, nuevamente, podría solucionarse cambiando y guardando el Prefab. Finalmente, la re-serialización de activos podría forzarse a través de secuencias de comandos con la API AssetDatabase.ForveReserializeAssets.

Pero, ¿por qué Unity no elimina automáticamente las referencias obsoletas en los escenarios enumerados anteriormente? Esto se debe principalmente al rendimiento; para evitar volver a serializar todos los activos cada vez que cambia un script o Prefab base. Otra razón es para evitar la pérdida de datos. Supongamos que elimina por error una propiedad de secuencia de comandos (como Bullet Prefab) y desea recuperarla. Solo necesita revertir el cambio en su secuencia de comandos. Siempre que tenga una variable con el mismo nombre que la eliminada, sus cambios no se perderán. Lo mismo sucedería si elimina el Bullet Prefab al que se hace referencia. Si recupera el Prefab exactamente como estaba, incluido el metaarchivo, se conservará la referencia.

Normalmente, esto no es un problema durante el tiempo de ejecución, dado que cuando Unity crea Player o Addressables, estos objetos obsoletos y referencias se borran. Pero incluso entonces, hay algunos casos en los que las referencias obsoletas pueden causar problemas, es decir, usar paquetes de activos puros. El cálculo de la dependencia del paquete de activos considera referencias obsoletas, lo que podría crear dependencias innecesarias entre paquetes, cargando más de lo necesario en tiempo de ejecución. Vale la pena pensar en esto al usar paquetes de activos. Cree o use cualquier herramienta existente para eliminar las referencias innecesarias.

Conclusión

Aunque puede ignorar completamente YAML la mayor parte del tiempo, entenderlo es útil para comprender el sistema de serialización de Unity. Si bien enfrentarse a grandes refactorizaciones y leer o modificar el YAML directamente con herramientas de procesamiento de activos puede ser rápido y efectivo, se recomienda buscar soluciones basadas en la API de la base de datos de activos de Unity siempre que sea posible. También es particularmente útil para resolver problemas de combinación en el control de versiones. Le recomendamos que explore la herramienta Smart Merge , que puede fusionar automáticamente Prefabs en conflicto, y lea más sobre YAML en nuestra documentación oficial .


Fuente: https://blog.unity.com/technology/understanding-unitys-serialization-language-yaml

No hay comentarios.