07 diciembre 2025

Serialización de diccionarios en Unity

En desarrollo, la serialización consiste cambiar el formato de un objeto de memoria para que pueda ser almacenado en disco. 

Uno de los formatos más populares de serialización es JSON, un estándar de texto con el que guardas datos en ficheros siguiendo una estructura de llaves muy similar a la de los diccionarios de Python. 

Cuando serializas un objeto, la instancia de una clase, a JSON lo que haces es seleccionar los datos claves del objeto y los conviertes en campos y valores de un fichero JSON. ¿Cuáles son los datos claves del objeto? es el mínimo subconjunto de datos que te permita reconstruir el objeto en el estado que necesitas. Ese proceso de reconstrucción es lo que se denomina deserialización y es el inverso al otro: lees el fichero y con su contenido creas una instancia de la clase con un estado lo más parecido posible al original.

Unity está constantemente serializando y deserializando. Cuando usamos el inspector para darle valor a un campo público de un MonoBehavior, Unity serializa ese valor a su propio formato para que lo tengas en el inspector la próxima vez que arranques el editor. Por defecto, Unity hace ese proceso con todos los campos públicos de los MonoBehaviour y los ScriptableObject, pero también puedes forzarlo en los campos privados si los defines precedidos del atributo [SerializeField]. Ese tributo tiene también el efecto de permitirnos editar en el inspector campos privados. Sin él, el inspector sólo muestra los públicos. Cuidado, eso no significa que tengas que usar ese atributo en todos los campos privados de tu MonoBehavior. Su uso sólo tiene sentido en aquellos campos que contienen valores de base de tu clase, es decir, aquellos que configurarías en el inspector antes de la ejecución del juego. Preceder un campo calculado de con [SerializeField] no tendría sentido, salvo que quisieras conservar ese valor calculado para una ejecución posterior.

Precisamente, ese era el caso que me movió a escribir este artículo. Estoy escribiendo una clase que me permita analizar un escenario y generar un grafo representando todas sus zonas transitables. El proceso de creación del grafo no viene a cuento, pero mi era que el grafo se generase en tiempo de desarrollo, se guardase y se cargase en tiempo de ejecución. Vamos, que quería que mi grafo se guardase en un ScriptableObject. Uno de los componentes de mi grafo se basa en un diccionario, cuyas claves son las coordenadas enteras de una malla rectangular, y sus valores los nodos con los enlaces a los nodos vecinos. Y el problema viene porque Unity no sabe serializar diccionarios. Puede serializar listas, pero no diccionarios. Ese es el motivo por el que puedes editar listas en el inspector, pero no diccionarios.

Los diccionarios son una de las estructuras de datos más comunes en desarrollo, así que no he sido el primero en encontrarse este problema. Es tan habitual, que otros engines, como Godot, llevan a gala poder serializar diccionarios.

¿Cómo se puede sortear ese problema? Bueno, puedes generar una clase que represente hacia fuera el comportamiento de un diccionario, pero que por dentro se base en dos listas, una de claves y otra de valores. A la hora de serializar, se guardarían esas dos listas que sí que son procesables por Unity. A la hora de deserializar, se leerían esas dos listas y se usarían para crear en memoria un diccionario interno desde el que la clase ofrecería su funcionalidad al resto de los componentes del juego. Esta solución es perfectamente legítima, pero a estas alturas ha sido tan usada que ya ha sido incluida en una herramienta de serialización de múltiples tipos de clases (no sólo diccionarios) como es Odin Serializer. Así que sería como reinventar la rueda. Si con todo y con eso, quieres hacértelo tú mismo, la misma página de Odin te explica cómo, aunque advierte de que el demonio está en los detalles y que puede haber situaciones raras al editar prefabs que pueden obligar sofisticar bastante la implementación inicial. Ese camino ya lo han recorrido los chicos de Odin Serializer, así que voy a explicar cómo utilizarlo.

Logo de Odin Serializer
Logo de Odin Serializer

Odin Serializer es una herramienta open source y gratuita. Te la puedes descargar de su página web, en formato de paquete de unity con todos los ficheros de código fuente para serializar todo lo que permite Odin. Odin Serializer es sólo el banderín de enganche gratuito de un conjunto de herramientas que, esas sí, son de pago. A partir del serializador gratuito abarcan una herramienta para crear inspectores personalizados y otra para encontrar errores en tus proyectos. Ambas son potentísimas. La primera te permite ahorrarte el paso por el UI Toolkit a la hora de implementar inspectores cómodos y eficientes. La segunda detecta los errores más típicos en el desarrollo con Unity y ofrece un conjunto de atributos personalizados para cargar de semántica tus campos y detectar casos en los que sus valores no se ajustes a su semántica (o directamente impedirte introducir esos valores). Aunque en el caso concreto de este artículo me ha bastado con usar el Serializer gratuito, te recomiendo que le eches un ojo a sus otras herramientas y precios. Son de pago único y de una cuantía tirada para un desarrollador indie.

La página de descarga sólo te pide el namespace base de tu juego, para personalizar los namespaces de todos los ficheros de código incluidos. El paquete incluye una carpeta OdinSerializer de la que cuelgan el resto de carpetas de la herramienta. Tienes que colocar esa carpeta dentro de la de Scripts de tu juego.

Una vez importado en tu carpeta de Scripts, OdinSerializer ofrece un conjunto de clases especializadas que heredan de las habituales en Unity:

  • SerializedBehaviour
  • SerializedComponent
  • SerializedMonoBehaviour
  • SerializedNetworkBehaviour
  • SerializedScriptableObject
  • SerializedStateMachineBehaviour
  • SerializedUnityObject

Te basta con sustituir la clase de Unity de la que herede tu componente por la equivalente para que Odin se encargue de serializar aquellos tipos de los que Unity no se ocupe. Todos los diccionarios, por ejemplo, serán serializados por Odin sin que haga falta nada más, de manera que si editas su contenido desde el editor, se conservará entre ejecuciones de este.

Sin embargo, hay que tener en cuenta que aunque serialices el contenido del diccionario con Odin, el inspector de Unity será incapaz de leerlo. Así que no te extrañe si tu diccionario público sigue sin aparecer en el inspector. Para eso haría falta instalarse Odin Inspector, que es uno de los componentes de pago. Sin ese componente, cualquier modificación desde el editor del contenido de un diccionario serializado debería venir de scripts personalizados para ejecutarse desde el editor. Mi caso era precisamente ese, ya que he configurado un botón en el inspector del componente de mi grafo para generar la malla al pulsarlo y guardar los valores creados en el diccionario interno del componente. Si modifico el escenario, no tengo más que volver a pulsar el botón para regenerar la malla del grafo.

Con Odin Serializer deberías ser capaz de resolver la mayor parte de tus necesidades para serializar diccionarios, pero no siempre es tan sencillo. Aunque ya había usado Odin Serializer en proyectos más simples, no he conseguido hacerlo funcionar en el proyecto que motivó este artículo. Generaba mi grafo correctamente al pulsar el botón, pero dicho grafo no estaba allí en el siguiente arranque del editor o al recargar el nivel. Por lo que quiera que sea, el diccionario que contenía el grafo no se salvaba al guardar el nivel. Le he dado muchas vueltas y no sé si se debe a que tengo varios niveles de diccionarios anidados unos dentro de otros, o porque los scripts que contienen esos diccionarios están colocados en prefabs también anidados. También puede ser porque mi componente usa un editor personalizado, lo que añade un nivel más de indirección a la serialización de sus datos. Sea por la razón que sea, al final no me ha quedado más remedio que implementar mi propia serialización. Precisamente esa serialización manual acerca de la que prevenía, si podías resolver tu problema con Odin. En esta ocasión no me ha quedado más remedio que reinventar la rueda, aunque he tenido la suerte de poder contar con el método explicado en la misma página de Odin Serializer. Te voy a contar cómo, por si te sirve para salir del paso en alguna ocasión.

Como decía antes, la clave está en crear una clase que herede de Dictionary para conservar su funcionalidad. Para dotar a esa nueva clase de la capacidad de serializarse dentro de Unity, hay que hacer que implemente el interfaz ISerializationCallbackReceiver. Cuando Unity recibe la orden de serializar una clase, porque el campo de esa clase sea público o esté marcado con el atributo [SerializeField], espera que dicha clase implemente el interfaz ISerializationCallbackReceiver. Ese interfaz consta de dos métodos, OnBeforeSerialize() en el que deberemos implementar cómo queremos guardar la información de nuestro objeto al serializar, y OnAfterDeserialize() en el que implementaremos cómo recuperar la información guardada para reconstruir con ella el estado del objeto.

La clase que he creado se basa en dos listas, una para las claves del diccionario y otra para sus valores. Será en esas listas donde guarde los datos a conservar, gracias a que Unity sí que serializa listas de manera nativa.

Los campos de mi diccionario personalizable
Los campos de mi diccionario personalizable

Ahora la implementación del interfaz. Primero para el proceso de serialización.

Proceso de serialización de mi diccionario personalizable
Proceso de serialización de mi diccionario personalizable

Cuando Unity llame a OnBeforeSerialize() será porque es hora de guardar la información del estado del objeto. Para ello, lo que hago es vaciar el contenido anterior de las listas (líneas 42 y 43) para volver a rellenarlas con el listado actualizado de las claves y los valores del diccionario. Cuando se cierre la instancia de la clase se perderá el contenido del diccionario, pero las listas habrán quedado serializadas junto con el resto de información de la escena.

Ahora el proceso inverso. Se abre de nuevo la escena y Unity llama a los métodos OnAfterDeserialize() de todos sus objetos para que estos restauren su estado anterior a partir de la información deserializada.

Proceso de deserialización de mi diccionario personalizable
Proceso de deserialización de mi diccionario personalizable

Como se puede ver en el listado, dado que las listas sí que serializaron, estarán con su contenido intacto al llamar a OnAfterDeserialize() lo que permitirá utilizarlo para restaurar las entradas del diccionario. En la línea 34 a 36 se puede ver que recorro ambas listas para regenerar las entradas del diccionario interno de la clase.

Ahora viene algo importante. Unity no puede serializar tipos genéricos y la clase recién creada (UnitySerializedDictionary) lo es. La solución es concretar la clase genérica para cada uno de los usos a los que la apliquemos. Una vez concretada, la clase resultante sí podrá ser serializada por Unity. Esa es la razón por la que tengo un fichero aparte, estático, con las diferentes versiones concretadas del diccionario genérico.

Clases concretas a partir de la genérica
Clases concretas a partir de la genérica

Para evitar cualquier tentación de usar directamente la clase genérica, sin concretarla, lo mejor es marcarla como abstracta.

Serán esas versiones concretas las que podamos usar en nuestro código, con la tranquilidad de que Unity conservará su contenido entre ejecuciones.

Uso del diccionario concretado
Uso del diccionario concretado

MonoBehaviour que utiliza nuestro diccionario concretado y serializable
MonoBehaviour que utiliza nuestro diccionario concretado y serializable

Espero que te haya resultado útil y que te sirva para no cortarte a la hora de usar diccionarios en tus componentes.

26 octubre 2025

Implementación de una regla para medir distancias en escenarios de Unity

Por alguna razón que desconozco, Unity carece de una herramienta que permita medir distancias en los escenarios y prefabs. Godot sí que la tiene, pero Unity no, a pesar de que es muy útil tanto para construir tus escenarios como para evaluar tus pruebas de funcionalidad. Por ese motivo, el Unity Asset Store está repleta de assets que ofrecen esta funcionalidad... previo pago. Hace poco me vi en la necesidad de medir distancias en un proyecto de Unity en el que ando metido y me planteé comprar uno de estos assets, pero luego me lo pensé mejor. "No puede ser tan difícil de implementar por uno mismo", lo intenté y resultó ser sencillísimo. En este artículo te voy a explicar cómo. Primero lo que queremos conseguir y luego pasaremos a los detalles de implementación.

Una regla para medir distancias es conceptualmente sencilla. Se basa en dos variables independientes, una posición A y una posición B, cuya diferencia da lugar a la variable dependiente que queremos calcular, la distancia. Eso es lo mínimo. A partir de ahí, podemos añadir mejoras visuales o facilidades de manipulación. Para el ejemplo de este artículo quería una herramienta visual que pudiese mover por el escenario, con dos puntos de agarre para ajustar con el ratón las dos posiciones a medir.

La herramienta de medida a implementar
La herramienta de medida a implementar

También quería poder disponer de múltiples reglas simultáneamente, para poder desplegarlas por diferentes ubicaciones del escenario, siendo aquellas visibles en todo momento sin necesidad de que estuviesen seleccionadas. La apariencia visual de las reglas debía ser configurable para que pudieran resaltar adecuadamente en diferentes escenarios. Así que debía poder configurar el color de las líneas, su grosor, y las dimensiones de los extremos de la regla. Además la regla debía dar las posiciones globales de sus extremos para permitir un emplazamiento preciso. Con todas esas condiciones, lo que buscaba era que la regla tuviese un inspector como el de la figura siguiente.

Inspector de la herramienta
Inspector de la herramienta

Teniendo en cuenta todo lo anterior, podemos pasar a los detalles de implementación. Por cierto, todo el código que vamos a ver está recogido en el repositorio de GitHub de la herramienta. Allí se describe cómo instalar la herramienta cómodamente, usando el Package Manager de Unity. En otro articulo explicaré cómo he empaquetado la herramienta para que se pueda instalar desde el repositorio con el Package Manager.

Usando el repositorio como referencia, el primer fichero a explicar es el de Assets/Runtime/Scripts/MeasuringTape.cs. Este fichero es el componente MonoBehaviour que se montará sobre un Transform para ser instanciado en el escenario, y es el que contiene el modelo de datos de la herramienta. Este no puede ser más sencillo:

Assets/Runtime/Scripts/MeasuringTape.cs
Assets/Runtime/Scripts/MeasuringTape.cs


Los campos de las líneas 8 y 9 son las dos variables independientes de las que hablábamos antes, las posiciones de los puntos A y B. Es importante señalar que son posiciones relativas a la del GameObject sobre el que se monte este componente. Quería hacerlo así para tener la posibilidad de mover la regla por el escenario de una sola vez, sin necesidad de tener que desplazar primero un extremo y luego el otro. También es importante darle un valor por defecto, diferente de cero, a ambas posiciones. De otra manera, sus agarradera visuales coincidirán con la del Transform del GameObject y no podremos manipularlas.

El resto de campos se refieren a la configuración visual de la herramienta:

  • color: El color de las líneas de la regla.
  • thickness: El grosor de dichas líneas.
  • endWidth: La longitud de las líneas transversales de los extremos de la regla.
  • endAlignment: Un valor entre -1 y +1 para bascular las líneas transversales de los extremos a un lado o a otro de la regla. Es una opción interesante si queremos poner múltiples reglas en el escenario a modo de cotas.
  • textSize: Tamaño de la fuente utilizada para mostrar la distancia medida.
  • textDistance: Distancia a la regla del texto con la distancia medida. Puede recibir valores negativos, en cuyo caso el texto pasa a mostrarse por el otro lado de la regla.
Aparte de lo anterior, MeasuringTape.cs ofrece dos propiedades con la posición global de los extremos de la regla:

Assets/Runtime/Scripts/MeasuringTape.cs
Assets/Runtime/Scripts/MeasuringTape.cs

Para tener dos agarraderas visuales con las que fijar los valores de localPositionA y localPositionB, arrastrando el ratón, recurriremos a los Handles de Unity. Los Handles son controles visuales que responden a la interacción del usuario, como clicks, arrastres y rotaciones. Cada vez que mueves un objeto por la escena, lo haces manipulando el Handle que representa la posición del objeto.

Para mostrar Handles personalizados tienes que crear un script en la carpeta Editor con una clase que herede de UnityEditor.Editor. Esa clase debe estar decorada con la etiqueta [CustomEditor] para señalar a qué MonoBehavior asociar los Handles. En el caso de nuestro ejemplo, este script está en Assets/Editor/DrawMeasuringTape.cs.

Es muy importante resaltar que cualquier script como este, que utilice clases del namespace UnityEditor, debe ir forzosamente en una carpeta llamada Editor. Si metes el script directamente en la carpeta Scripts, junto a los MonoBehaviours, te será imposible compilar el juego. Hay gente que mete la carpeta Editor dentro de la de Scripts. Yo prefiero tenerlas bien separadas.

Assets/Editor/DrawMeasuringTape.cs
Assets/Editor/DrawMeasuringTape.cs

Fíjate en la línea 6. A la etiqueta [CustomEditor] hay que pasarle el tipo del MonoBehaviour al que se asocia. Es una manera de decirle al editor: "cada vez que te encuentres con un MonoBehaviour de este tipo represéntalo, en el inspector y en la escena, tal y como se define aquí".

Es importante destacar que he dejado este script dentro del namespace por defecto. No sé muy bien la razón, pero sin intentaba ponerle un namespace personalizado me fallaba el dibujado de Gizmos del método DrawTapeGizmos(), que veremos más adelante.

La configuración de Handles se hace en el método OnSceneGUI(), propio de la clase UnityEditor.Editor

Assets/Editor/DrawMeasuringTape.cs
Assets/Editor/DrawMeasuringTape.cs

La gestión de Handles dentro de OnSceneGUI() siempre tiene la misma estructura. De hecho, no sería descabellado guardarte una plantilla, porque siempre acabas haciendo lo mismo.

Empiezas recuperando una referencia al MonoBehaviour al que se asocian los Handles. En OnSceneGUI(), lo habitual es usar el campo target de UnitEditor.Editor. Ese campo tiene la referencia que buscamos, aunque hay que hacerle un cast (línea 160) para recuperar el tipo exacto. Esa referencia nos será muy útil para leer y fijar las propiedades y campos del MonoBehaviour original.

Luego, viene el segmento en el que muestras tus Handles y recuperas sus valores. Dicho segmento se inicia con una llamada a EditorGUI.BeginChangeCheck() (línea 162). Esa llamada sirve para detectar si el usuario ha modificado algún control de la interfaz del editor entre esta llamada y su correspondiente EditorGUI.EndChangeCheck() (línea 172). En caso de que esta última llamada detecte algún cambio en los controles del editor, se recuperan los valores de estos y se guardan en el MonoBehaviour original (líneas 176 y 177). Esos cambios se registran en el sistema de Undo/Redo con la llamada a Undo.RecordObject() (línea 175). Dicho método registra todos los cambios que se realicen sobre el objeto pasado como parámetro, a partir de la llamada. El texto del segundo parámetro sirve para identificar el cambio en el historial de estos.

Entre las líneas 165 y 170 es donde se produce la creación de los Handles. Los hay de muchos tipos, cada uno con su propia apariencia y forma de ser manipulado. Los que he usado aquí son los más sencillos. Los PositionHandle se limitan a presentar un eje de coordenadas que podemos desplazar por el escenario. En caso de moverse, el Handle devuelve la nueva posición que, en nuestro caso, se almacena en las variables positionAHandle (línea 165) y positionBHandle (línea 168). Esas variables son las que se utilizan para actualizar los campos del MonoBehaviour de origen (líneas 176 y 177).

En casos sencillos, es bastante habitual ver implementaciones que introducen la representación visual de los Gizmos del objeto en el mismo método OnSceneGUI(), generalmente después del bloque del método EditorGUI.EndChangeCheck(). Yo lo he hecho así a menudo, pero tiene un par de inconvenientes. El primero es que mezclas en el mismo método la gestión de los controles visuales del objeto y la de los Gizmos que lo representan, lo que alarga y complica la implementación del método. El segundo problema es que toda la representación visual que incluyas en ese método sólo se mostrará cuando el objeto de origen esté seleccionado. Esto último era lo que me resultó bloqueante, dado que quería que la representación de la regla permaneciese aunque tuviésemos seleccionado otro objeto.

La alternativa que he descubierto es la de decorar un método estático con el atributo [DrawGizmo]. Este atributo señala a un método como el responsable de dibujar los Gizmos de un objeto. Por un lado te permite concentrar en él toda la lógica de la representación visual, dejando OnSceneGUI() para la de la gestión de Handles; y por otro lado, en función de los parámetros que le pasemos al atributo podremos definir cuándo queremos que se muestren los Gizmos.

Assets/Editor/DrawMeasuringTape.cs

Assets/Editor/DrawMeasuringTape.cs
Assets/Editor/DrawMeasuringTape.cs

En el caso del ejemplo, como se puede ver en la línea 108, le he pasado al atributo los flags necesarios para que los Gizmos se muestren tanto cuando el Gizmo esté seleccionado, como cuando no lo esté.

Para dibujar los Gizmos, he utilizado las funciones de dibujado de la librería Handles. Aquí hay cierto solapamiento con lo que ofrece la librería Gizmos, con la que también podemos dibujar. La ventaja de Handles es que te permite seleccionar el grosor de la línea dibujada, mientras que Gizmos sólo te permite dibujar con un grosor de 1 pixel. Partiendo de lo anterior, el resto de las líneas del método son muy similares a cuando dibujamos con Gizmos. 

En la línea 112, fijos el color de las líneas a dibujar, mientras que en la 113 dibujo la línea entre los dos extremos de la herramienta de medida. Fíjate que el tercer parámetro que se le pasa al método DrawLine() es precisamente el grosor de la línea a dibujar.  

Para resaltar los extremos y sus agarraderas visuales, en la línea 116 y en la 121 dibujo una circunferencia alrededor de los extremos. Para rematar estos, he dibujado líneas perpendiculares a la principal. Si quisiese que mi regla se limitase a medir en escenarios de juegos 2D me habría conformado con calcular la perpendicular a la línea principal, pero como quiero poder usarla en entornos 3D hay que buscar que la líneas que crucen los extremos sean también perpendiculares a la dirección de enfoque de la cámara. De otra manera, podría haber momentos en los que la cámara se moviese y las líneas de los extremos desapareciesen por quedar longitudinales a la dirección de enfoque. Para calcular la perpendicular a dos vectores, se usa el producto cruzado, el cual calculo en la línea 133. Hecho eso, ese vector perpendicular me sirve para calcular la semilongitud de los extremos (línea 146) y para dibujar estos, incorporando la basculación de endAlignment (líneas 147 y 150).

El vector perpendicular también es muy útil para separar el texto de la línea principal, desde el centro de esta. Dicho centro se calcula en la línea 138 y desde él se calcula la separación en la línea 142. Una vez que tenemos la ubicación, dibujo el texto en la línea 143. El "F2" de la llamada a ToString() es para que la distancia se muestre con dos decimales.

En este punto, ya tengo una regla de medir cuyos extremos puedo manipular y que se dibuja de la forma que se muestra en la imagen que se muestra al comienzo del artículo. Queda por aclarar cómo he hecho para que el inspector muestre la posición global en campos de sólo lectura. Aunque el MonoBehavior MeasuringTape tenga dos propiedades que ofrecen la posición global de los extremos, Unity no puede mostrar en el inspector propiedades (es una pena porque Godot sí que puede hacerlo). Para mostrar esas propiedades, y que sean sólo de lectura, hay que recurrir a un inspector personalizado. Para hacerlo hay que crear una clase que herede de UnityEditor.Editor, precisamente como la que ya hemos usado para los Handles y la representación visual, pero para representar inspectores personalizados hay que usar el método CreateInspectorGUI().

Dicho método se basa en la lectura que se hace de los valores serializados de los campos del objeto original a representar en el inspector. Esos valores se le pasan a la clase a través de un campo llamado serializedObject. Lo que yo suelo hacer es usar el método OnEnable() para conseguir referencias a cada uno de los campos del objeto original.

Assets/Editor/DrawMeasuringTape.cs

Para conseguir cada una de las referencias hay que llamar al método serializedObject.FindProperty() y pasarle una cadena de texto con el nombre del campo del objeto original que nos interesa. Lo de usar una cadena de texto no me convence, porque es fácil equivocarse, pero es lo que hay.

Una vez obtenidas las referencias a los campos del objeto original, ya podemos usarlas en CreateInspectorGUI().

Assets/Editor/DrawMeasuringTape.cs
Assets/Editor/DrawMeasuringTape.cs
Assets/Editor/DrawMeasuringTape.cs

En la línea 38 se crea el panel donde se representarán todos los campos del inspector. No quería hacer grandes alardes con el orden de los campos, así que me he limitado a organizarlos en el layout clásico vertical.

¿Te acuerdas cuando en OnSceneGUI() conseguía una referencia al MonoBehavior original, usando el campo target? Bueno, puede que estés tentado a hacer lo mismo en este método, pero la documentación de Unity advierte contra ello y exige que en este método en concreto se use serializedObject.targetObject, tal y como se puede ver en la línea 47. No entiendo bien a qué se debe esta peculiaridad, pero he preferido obedecer y hacerlo como recomendaba la documentación, en vez de ir a contracorriente y encontrarme con problemas raros luego.

Como me vale la visualización por defecto que se hace de los dos primeros campos, los de las posiciones locales, me he limitado a crear dos campos por defecto y a poblarlos con los valores del objeto original (líneas 50 y 52). Luego, añadimos los campos al panel (líneas 51 y 53)

En mi caso particular, suelo usar inspectores personalizados cuando quiero mostrar u ocultar campos en función del valor de otro anterior (por ejemplo, un booleano). En esas situaciones, me gusta situar los campos variables (los que se pueden mostrar o no) sobre un panel aparte del principal. Ese panel aparte es el que creo en la línea 56 y añado al principal en la 57. Luego, en la línea 60, le pasamos ese panel al método UpdateGlobalPositionFields() para que se creen sobre él los campos de sólo lectura con las posiciones globales. En un momento, veremos cómo funciona por dentro UpdateGlobalPositionFields(), pero para no perder el hilo de CreateInspectorGUI(), antes vamos a acabar de ver cómo funciona. 

Una vez que añadidos los campos donde se mostrarán las posiciones globales, quiero que estos se mantengan actualizados. Para ello, he enlazado UpdatePositionFields() con los campos de las posiciones locales, para que se ejecute cada vez que cambien los valores de las posiciones locales (líneas 64 a 67). Esto tendrá como efecto la actualización de las posiciones globales. 

Como me vale con que el resto de campos muestren su visualización por defecto, me limito a añadir sus respectivos PropertyField al panel principal (líneas 70 a 75).

Fíjate en que CreateInspectorGUI() tiene que devolver el panel principal para que el editor pueda mostrarlo en el inspector. Es lo que hago en la línea 77.

Lo prometido es deuda, así que ahora vamos a ver qué ocurre dentro de UpdateGlobalPositionFields().

Assets/Editor/DrawMeasuringTape.cs
Assets/Editor/DrawMeasuringTape.cs

Recuerda que acabo de mencionar que a este método le paso como primer parámetro el subpanel en el que se dibujan los campos que queremos actualizar manualmente, bien para insertarles valores o bien para mostrarlos u ocultarlos. 

Lo primero que hago con ese subpanel, en la línea 91, es borrar su contenido para empezar con un lienzo en blanco.

En la línea 93 defino que quiero colar en columna descendente aquellos elementos que vaya añadiendo al subpanel.

Por último, añado los campos con las posiciones globales (líneas 96 y 101). En ambos casos hago lo mismo, creo un campo especializado en mostrar valores posicionales (Vector3Field). Luego, le doy valor a esos campos posicionales usando las propiedades de MeasuringTape que devolvían las respectivas posiciones globales (líneas 97 y 102). Como quiero que los campos sean de sólo lectura, y por tanto no se le puedan insertar valores manualmente, los deshabilito con SetEnabled(false) (líneas 98 y 103). Por último, añado los campos recién creados y configurados al subpanel (líneas 99 y 104).

Con esto ya tendríamos un regla para medir distancias completamente funcional... aunque bastante incómoda. Para usarla, tendríamos que crear un GameObject vacío dentro de la escena y luego añadirle el componente MeasuringTape. Ciertamente trabajoso. Podríamos simplificar las cosas creando un prefab que sólo incorporase el componente MeasuringTape. Así sólo habría que arrastrar el prefab a la jerarquía, cada vez que quisiéramos una regla en la escena. Sin embargo, es nos obligaría a tener que buscar el prefab en nuestras carpetas cada vez que necesitásemos medir algo. Por eso, lo que he hecho es crear una entrada en el menú principal del editor con la que crear una instancia del prefab cada vez que sea necesario. Veamos cómo lo he hecho.

Para añadir entradas al menú principal de Unity te basta con decorar un método estático con el atributo [MenuItem]. A este atributo se le pasa la ruta, dentro del menú principal, de nuestra entrada. Lo que ocurrirá a partir de entonces es que, cada vez que se pulse a esa entrada en el menú, se llamará al método estático.

En mi caso, he implementado el método estático en el fichero Assets/Editor/InstanceMeasuringTape.cs. Fíjate en que el atributo [MenuItem] está en el namespace de UnityEditor, así que un script que lo utilice tiene que situarse en la carpeta Editor.

Assets/Editor/InstanceMeasuringTape.cs
Assets/Editor/InstanceMeasuringTape.cs

Lo primero que hace el método, en la línea 15, es llamar a LoadAssetAtPath() para cargar el prefab con MeasuringTape del que hablábamos antes. La ruta donde buscar dependerá de cómo ejecutes la herramienta. Si copia-pegas todo el código directamente dentro de tu proyecto, tendrás que poner la ruta donde dejes el prefab (siendo Assets la raíz de la ruta). En mi caso, lo he configurado todo para que se ejecute como un paquete descargado desde GitHub con el Package Manager de Unity (en otro artículo explicaré cómo se hace). Así que la ruta tiene que ser la de la carpeta donde se instale el paquete. Los paquetes se instalan en la carpeta Packages, así que mi ruta parte de esa raíz, tal y como se puede ver en la línea 9.

Una vez cargado el prefab, en forma de GameObject, se puede instanciar dentro de la escena usando el método PrefabUtility.InstantiatePrefab() (línea 23). El resultado de esa llamada es que la instancia del prefab aparece en la escena, colgando de la raíz de la jerarquía.

Por último, como lo lógico es que quieras operar sobre la regla recién creada, en la línea 32 se selecciona dicha regla dentro de la jerarquía para que no tengas que buscarla para pulsar sobre ella. 

Y eso es todo. Espero que te haya resultado interesante y que te hay dado alguna idea para crear tus propias herramientas dentro de Únity. Como ya he mencionado en este artículo, en un futuro próximo espero poder sacar un rato para explicar cómo se puede hacer para colgar tus herramientas en GitHub de manera que se se puedan cargar, y mantener actualizadas, usando el PackageManager de Unity.