Trucos avanzados de creación de scripts para editores que le ahorrarán tiempo (parte 1)

JORDI CABALLOL / UNITYSenior Software Engineer
Oct 18, 2022|15 minutos
Trucos avanzados de creación de scripts para editores que le ahorrarán tiempo (parte 1)
Para tu comodidad, tradujimos esta página mediante traducción automática. No podemos garantizar la precisión ni la confiabilidad del contenido traducido. Si tienes alguna duda sobre la precisión del contenido traducido, consulta la versión oficial en inglés de la página web.

En la mayoría de los proyectos que he visto, hay muchas tareas que realizan los desarrolladores que son repetitivas y propensas a errores, especialmente cuando se trata de integrar nuevos recursos artísticos. Por ejemplo, configurar un personaje a menudo implica arrastrar y soltar muchas referencias de activos, marcar casillas de verificación y hacer clic en botones: Establezca el equipo del modelo en Humanoide, deshabilite el sRGB de la textura SDF, establezca los mapas normales como mapas normales y las texturas de la interfaz de usuario como sprites. En otras palabras, se pierde un tiempo valioso y aún así se pueden pasar por alto pasos cruciales.

En este artículo de dos partes, te mostraré algunos trucos que pueden ayudarte a mejorar este flujo de trabajo para que tu próximo proyecto se desarrolle con más fluidez que el anterior. Para ilustrar esto mejor, he creado un prototipo simple, similar a un RTS, donde las unidades de un equipo atacan automáticamente edificios enemigos y otras unidades. Con cada truco de scripting, mejoraré un aspecto de este proceso, ya sean las texturas o los modelos.

Así es como se ve el prototipo:

Truco 1: Organiza y automatiza tus activos

La razón principal por la que los desarrolladores tienen que configurar tantos pequeños detalles al importar activos es simple: Unity no sabe cómo vas a utilizar un activo, por lo que no puede saber cuáles son las mejores configuraciones para él. Si desea automatizar algunas de estas tareas, este es el primer problema que debe abordarse.

La forma más sencilla de descubrir para qué sirve un activo y cómo se relaciona con otros es seguir una convención de nombres y una estructura de carpetas específicas, como por ejemplo:

  • Convención de nombres: Podemos agregar cosas al nombre del activo en sí, por lo tanto, Shield_BC.png es el color base mientras que Shield_N.png es el mapa normal.
  • Estructura de carpetas: Knight/Animations/Walk.fbx es claramente una animación, mientras que Knight/Models/Knight.fbx es un modelo, aunque ambos comparten el mismo formato (.fbx).

El problema con esto es que sólo funciona bien en una dirección. Entonces, si bien es posible que ya sepas para qué sirve un activo cuando se te da su ruta, no puedes deducir su ruta si solo tienes información sobre lo que hace el activo. Poder encontrar un activo (por ejemplo, el material para un personaje) es útil cuando se intenta automatizar la configuración de algunos aspectos de los activos. Si bien esto se puede solucionar utilizando una convención de nomenclatura rígida para garantizar que la ruta sea fácil de deducir, aún es susceptible a errores. Incluso si recuerdas la convención, los errores tipográficos son comunes.

Un enfoque interesante para resolver esto es utilizar etiquetas. Puede utilizar un script del Editor que analice las rutas de los activos y les asigne etiquetas según corresponda. Como las etiquetas están automatizadas, es posible determinar la etiqueta exacta que tendrá un activo. Incluso puedes buscar activos por su etiqueta utilizando AssetDatabase.FindAssets.

Si desea automatizar esta secuencia, hay una clase que puede ser muy útil llamada AssetPostprocessor. El AssetPostprocessor recibe varios mensajes cuando Unity importa activos. Uno de ellos es OnPostprocessAllAssets, un método que se llama cada vez que Unity termina de importar activos. Le proporcionará todas las rutas a los activos importados y le brindará la oportunidad de procesar esas rutas. Puedes escribir un método simple, como el siguiente, para procesarlos:

En el caso del prototipo, nos centraremos en la lista de activos importados, tanto para intentar capturar nuevos activos como para los activos trasladados. Después de todo, a medida que la ruta cambia, es posible que queramos actualizar las etiquetas.

Para crear las etiquetas, analice la ruta y busque carpetas, prefijos y sufijos relevantes del nombre, así como las extensiones. Una vez que haya generado las etiquetas, combínelas en una sola cadena y configúrelas en el activo.

Para asignar las etiquetas, cargue el activo utilizando AssetDatabase.LoadAssetAtPathy luego asigne sus etiquetas con AssetDatabase.SetLabels.

Tipo de bloque desconocido "codeBlock", especifique un serializador para él en la propiedad `serializers.types`

Recuerde que es importante establecer etiquetas solo si realmente han cambiado. La configuración de etiquetas activará una reimportación del activo, por lo que no conviene que esto suceda a menos que sea estrictamente necesario.

Si marca esto, la reimportación no será un problema: Las etiquetas se configuran la primera vez que se importa un activo y se guardan en el archivo .meta, lo que significa que también se guardan en el control de versiones. Una reimportación solo se activará si cambia el nombre o mueve sus activos.

Una vez completados los pasos anteriores, todos los activos se etiquetan automáticamente, como en el ejemplo que se muestra a continuación.

Captura de pantalla del posprocesamiento de etiquetado automático en el Editor de Unity.
Truco 2: Determinar configuraciones y tamaños de textura

Importar texturas a un proyecto generalmente implica modificar la configuración de cada textura. ¿Es una textura regular? ¿Un mapa normal? ¿Un duende? ¿Es lineal o sRGB? Si desea cambiar la configuración de un importador de activos, puede utilizar AssetPostprocessor una vez más.

En este caso, querrás utilizar el mensaje OnPreprocessTexture , que se llama justo antes de importar una textura. Esto le permite cambiar la configuración del importador.

A la hora de seleccionar la configuración correcta para cada textura, es necesario verificar con qué tipo de texturas estás trabajando, que es exactamente el motivo por el cual las etiquetas son clave en el primer paso.

Con esta información, puedes escribir un TexturePreprocessorsimple:

Tipo de bloque desconocido "codeBlock", especifique un serializador para él en la propiedad `serializers.types`

Es importante asegurarse de ejecutar esto solo para texturas que tengan la etiqueta de arte (nuestras propias texturas). Luego recibirás una referencia al importador para que puedas configurar todo, empezando por el tamaño de la textura.

AssetPostprocessor tiene una propiedad de contexto desde la cual puede determinar la plataforma de destino. De esta forma, puedes realizar cambios específicos de la plataforma, como configurar las texturas a una resolución más baja para dispositivos móviles:

Tipo de bloque desconocido "codeBlock", especifique un serializador para él en la propiedad `serializers.types`

A continuación, verifique la etiqueta para ver si la textura es una textura de interfaz de usuario y configúrela en consecuencia:

Tipo de bloque desconocido "codeBlock", especifique un serializador para él en la propiedad `serializers.types`

Para el resto de texturas, establezca los valores en un valor predeterminado. Vale la pena señalar que Albedo es la única textura que tendrá sRGB habilitado:

Tipo de bloque desconocido "codeBlock", especifique un serializador para él en la propiedad `serializers.types`

Gracias al script anterior, cuando arrastres y sueltes las nuevas texturas en el Editor, automáticamente tendrán la configuración correcta en su lugar.

Truco 3: Asumir el empaquetamiento de canales de textura

“Empaquetamiento de canales” se refiere a la combinación de diversas texturas en una sola mediante el uso de diferentes canales. Es común y ofrece muchas ventajas. Por ejemplo, el valor del canal Rojo es metálico y el valor del canal Verde es su suavidad.

Sin embargo, combinar todas las texturas en una requiere algo de trabajo extra por parte del equipo artístico. Si es necesario cambiar el empaque por algún motivo (es decir, un cambio en el sombreador), el equipo artístico tendrá que rehacer todas las texturas que se usan con ese sombreador.

Como puedes ver, hay margen de mejora aquí. El enfoque que me gusta usar para el empaquetamiento de canales es crear un tipo de activo especial donde se establecen las texturas "sin procesar" y se genera una textura empaquetada en canales para usar en los materiales.

Primero, creo un archivo ficticio con una extensión específica y luego uso un Importador con script que hace todo el trabajo pesado al importar ese activo. Así es como funciona:

  • Los importadores pueden tener parámetros, como las texturas que necesitas combinar.
  • Desde el importador, puedes configurar las texturas como una dependencia, lo que permite que el activo ficticio se vuelva a importar cada vez que cambia una de las texturas de origen. Esto le permite reconstruir las texturas generadas en consecuencia.
  • El importador tiene una versión. Si necesita cambiar la forma en que se empaquetan las texturas, puede modificar el importador y aumentar la versión. Esto forzará una regeneración de todas las texturas empaquetadas en tu proyecto y todo se empaquetará de la nueva manera, inmediatamente.
  • Un efecto secundario interesante de generar cosas en un importador es que los activos generados solo viven en la carpeta Biblioteca, por lo que no llenan el control de versiones.

Para implementar esto, cree un ScriptableObject que contendrá las texturas creadas y servirá como resultado del importador. En el ejemplo, llamé a esta clase TexturePack.

Una vez creado esto, puede comenzar declarando la clase importadora y agregando ScriptedImporterAttribute para definir la versión y la extensión asociadas con el importador:

Tipo de bloque desconocido "codeBlock", especifique un serializador para él en la propiedad `serializers.types`

En el importador, declare los campos que desea utilizar. Aparecerán en el Inspector, tal como lo hacen MonoBehaviours y ScriptableObjects:

Tipo de bloque desconocido "codeBlock", especifique un serializador para él en la propiedad `serializers.types`

El inspector del importador.

Con los parámetros listos, crea nuevas texturas a partir de las que hayas establecido como parámetros. Sin embargo, tenga en cuenta que en el preprocesador (de la sección anterior), establecemos isReadable en Verdadero para hacer esto.

En este prototipo, notarás dos texturas: el Albedo, que tiene el Albedo en el RGB y una máscara para aplicar el color del jugador en el Alfa, y la textura Máscara, que incluye el metálico en el canal Rojo y la suavidad en el canal Verde.

Si bien esto quizás esté fuera del alcance de este artículo, veamos cómo combinar Albedo y la máscara del jugador como ejemplo. Primero, verifique si las texturas están configuradas y, si lo están, obtenga sus datos de color. Then set the textures as dependencies using AssetImportContext.DependsOnArtifact. Como se mencionó anteriormente, esto obligará a recalcular el objeto si alguna de las texturas termina cambiando.

Tipo de bloque desconocido "codeBlock", especifique un serializador para él en la propiedad `serializers.types`

También necesitas crear una nueva textura. Para ello, obtén el tamaño del TexturePreprocessor que creaste en la sección anterior para que siga las restricciones preestablecidas:

Tipo de bloque desconocido "codeBlock", especifique un serializador para él en la propiedad `serializers.types`

A continuación, complete todos los datos para la nueva textura. Esto podría optimizarse enormemente mediante el uso de Jobs y Burst (pero eso requeriría un artículo completo). Aquí usaremos un bucle simple:

Tipo de bloque desconocido "codeBlock", especifique un serializador para él en la propiedad `serializers.types`

Establezca estos datos en la textura:

Tipo de bloque desconocido "codeBlock", especifique un serializador para él en la propiedad `serializers.types`

Ahora, puedes crear el método para generar otra textura de una manera muy similar. Una vez que esto esté listo, crea el cuerpo principal del importador. En este caso, solo crearemos el ScriptableObject que contiene los resultados, crea las texturas y establece el resultado del importador a través de AssetImportContext.

Al escribir un importador, todos los activos generados deben registrarse mediante AssetImportContext.AddObjectToAsset para que aparezcan en la ventana del proyecto. Seleccione un activo principal utilizando AssetImportContext.SetMainObject. Así es como se ve:

Tipo de bloque desconocido "codeBlock", especifique un serializador para él en la propiedad `serializers.types`

Lo único que queda por hacer es crear los activos ficticios. Como son personalizados, no puedes usar el atributo CreateAssetMenu. En su lugar, deberás realizarlos manualmente.

Utilizando el atributo MenuItem , especifique la ruta completa para crear el menú de activos, Assets/Create. Para crear el activo, utilice ProjectWindowUtil.CreateAssetWithContent, que genera un archivo con el contenido que ha especificado y permite al usuario ingresar un nombre para él. Parece así:

Tipo de bloque desconocido "codeBlock", especifique un serializador para él en la propiedad `serializers.types`

Por último, crea las texturas llenas de canales.

Truco 4: Utilice el sombreador personalizado para materiales

La mayoría de los proyectos utilizan sombreadores personalizados. A veces se utilizan para agregar efectos adicionales, como un efecto de disolución para desvanecer a los enemigos derrotados, y otras veces, los sombreadores implementan un estilo de arte personalizado, como los sombreadores de dibujos animados. Cualquiera sea el caso de uso, Unity creará nuevos materiales con el sombreador predeterminado y deberá cambiarlo para usar el sombreador personalizado.

En este ejemplo, el sombreador utilizado para las unidades tiene dos características añadidas: el efecto de disolución y el color del reproductor (rojo y azul en el prototipo de vídeo). Al implementarlos en su proyecto, debe asegurarse de que todos los edificios y unidades utilicen el sombreador adecuado.

Para validar que un activo cumple ciertos requisitos (en este caso, que utiliza el sombreador correcto), existe otra clase útil: AssetModificationProcessor. Con AssetModificationProcessor.OnWillSaveAssets, en particular, se le notificará cuando Unity esté a punto de escribir un activo en el disco. Esto le dará la oportunidad de verificar si el activo es correcto y corregirlo antes de guardarlo.

Además, puedes “decirle” a Unity que no guarde el activo, lo que resulta eficaz cuando el problema que detectas no se puede solucionar automáticamente. Para lograr esto, cree el método OnWillSaveAssets :

Tipo de bloque desconocido "codeBlock", especifique un serializador para él en la propiedad `serializers.types`

Para procesar los activos, verifique si son materiales y si tienen las etiquetas correctas. Si coinciden con el código siguiente, entonces tienes el sombreador correcto:

Tipo de bloque desconocido "codeBlock", especifique un serializador para él en la propiedad `serializers.types`

Lo conveniente aquí es que este código también se llama cuando se crea el activo, lo que significa que el nuevo material tendrá el sombreador correcto.

Como nueva característica en Unity 2022, también tenemos Variantes de Material. Las variantes de materiales son increíblemente útiles a la hora de crear materiales para unidades. De hecho, puedes crear un material base y derivar los materiales para cada unidad desde allí, anulando los campos relevantes (como las texturas) y heredando el resto de las propiedades. Esto permite tener valores predeterminados sólidos para nuestros materiales, que pueden actualizarse según sea necesario.

Truco 5: Administra tus animaciones

Importar animaciones es similar a importar texturas. Hay varias configuraciones que deben establecerse y algunas de ellas pueden automatizarse.

Unity importa los materiales de todos los archivos FBX (.fbx) de forma predeterminada. Para las animaciones, los materiales que quieras utilizar estarán en el proyecto o en el FBX de la malla. Los materiales adicionales de la animación FBX aparecen cada vez que buscas materiales en el proyecto, lo que agrega bastante ruido, por lo que vale la pena deshabilitarlos.

Para configurar el equipo (es decir, elegir entre Humanoide y Genérico, y en los casos en los que utilizamos un avatar cuidadosamente configurado, asignarlo) aplicamos el mismo enfoque que se aplicó a las texturas. Pero para las animaciones, el mensaje que usarás es AssetPostprocessor.OnPreprocessModel. Esto se llamará para todos los archivos FBX, por lo que deberá distinguir los archivos FBX de animación de los archivos FBX de modelo.

Gracias a las etiquetas que configuraste anteriormente, esto no debería ser demasiado complicado. El método comienza de forma muy similar al de las texturas:

Tipo de bloque desconocido "codeBlock", especifique un serializador para él en la propiedad `serializers.types`

A continuación, querrás usar el equipo del FBX de malla, por lo que deberás encontrar ese activo. Para localizar el activo, utilice las etiquetas una vez más. En el caso de este prototipo, las animaciones tienen etiquetas que terminan con “animación”, mientras que las mallas tienen etiquetas que terminan con “modelo”. Puede realizar un reemplazo simple para obtener la etiqueta para su modelo. Una vez que tenga la etiqueta, busque su activo utilizando AssetDatabase.FindAssets con “l:label-name”.

Al acceder a otros activos, hay algo más que tener en cuenta: Es posible que, en medio del proceso de importación, el avatar aún no se haya importado cuando se llama a este método. Si esto ocurre, LoadAssetAtPath devolverá nulo y no podrás configurar el avatar. Para solucionar este problema, establezca una dependencia en la ruta del avatar. La animación se importará nuevamente una vez que se importe el avatar y podrás configurarla allí.

Poner todo esto en código se vería así:

Tipo de bloque desconocido "codeBlock", especifique un serializador para él en la propiedad `serializers.types`

Ahora puedes arrastrar las animaciones a la carpeta correcta y, si tu malla está lista, cada una se configurará automáticamente. Pero si no hay un avatar disponible cuando importas las animaciones, el proyecto no podrá detectarlo una vez creado. En su lugar, necesitarás volver a importar la animación manualmente después de crearla. Esto se puede hacer haciendo clic derecho en la carpeta con las animaciones y seleccionando Reimportar.

Puedes ver todo esto en el vídeo de muestra a continuación.

Truco 6: Configurar la malla usando importadores FBX

Usando exactamente las mismas ideas de las secciones anteriores, querrás configurar los modelos que vas a utilizar. En este caso, utilice AssetPostrocessor.OnPreprocessModel para establecer la configuración del importador para este modelo.

Para el prototipo, configuré el importador para que no genere materiales (usaré los que he creado en el proyecto) y verifiqué si el modelo es una unidad o un edificio (verificando la etiqueta, como siempre). Las unidades están configuradas para generar un avatar, pero la creación de avatar para los edificios está deshabilitada, ya que los edificios no están animados.

Para su proyecto, es posible que desee configurar los materiales y animadores (y cualquier otra cosa que desee agregar) al importar el modelo. De esta manera, el Prefab generado por el importador está listo para su uso inmediato.

Para hacer esto, utilice el método AssetPostprocessor.OnPostprocessModel . Este método se llama después de finalizar la importación de un modelo. Recibe como parámetro el Prefab que se ha generado, lo que nos permite modificar el Prefab como queramos.

Para el prototipo, encontré el material y el controlador de animación haciendo coincidir la etiqueta, al igual que ubiqué el avatar para las animaciones. Con el Renderer y el Animator en el Prefab, configuro el material y el controlador como en el juego normal.

Luego puedes colocar el modelo en tu proyecto y estará listo para colocarse en cualquier escena. Excepto que no hemos establecido ningún componente relacionado con el juego, lo cual abordaré en la segunda parte de este blog.

Hasta la próxima…

Con estos consejos de programación avanzada, ya estarás prácticamente listo para jugar. Mantente atento a la próxima entrega de este artículo de dos partes Tech from the Trenches , que cubrirá trucos para equilibrar los datos del juego y más.

Si desea discutir el artículo o compartir sus ideas después de leerlo, diríjase a nuestro foro de scripting. También puedes conectarte conmigo en Twitter en @CaballolD.