La reutilización es clave en el desarrollo de software. Unity no es la excepción. Cuando lleves ya tiempo desarrollando juegos, antes o después te encontrarás con problemas que ya resolviste previamente y querrás aprovechar implementaciones anteriores. También puede ser que un compañero tuyo necesite un componente que hubieras desarrollado y que le permitiría resolver un problema de implementación. En ambos casos te verás en la necesidad de exportar una serie de assets de un proyecto anterior de manera que luego sea fácil importarlo en otro. En este artículo veremos algunas manera de hacerlo.
La copia directa
La manera más evidente de compartir componentes es copiarlos a un pincho USB, o a un paquete ZIP, y luego copiarlos en el arbol de carpetas del proyecto de destino.
Este enfoque tiene la ventaja de la sencillez. Es inmediato hacerlo. Pero tiene la desventaja de que te obliga a hacer una importación manual de cada componente, copiándolos en las carpetas correctas. Si los componentes importados tienen dependencias entre sí, es fácil que estas dependencias se rompan si importas los componentes en carpetas diferentes a las de origen.
Además, debes tener en cuenta qué quieres copiar. Muchas veces no basta con los ficheros originales. Cuando incluyes un fichero en un proyecto de Unity, el editor genera otro de extensión .meta en el que se guardan los parámetros usados en el proceso. Incluir los ficheros .meta en la copia es muy importante si quieres estar seguro de que otros desarrolladores utilizan los assets exportados de la misma manera que tú.
Teniendo en cuenta lo anterior, el método de la copia directa sólo es aconsejable para copias personales. Cosas que usaste en otros proyectos, que conoces bien y sabes cómo funcionan, y que por tanto sabes cómo "trasplantar" a otro proyecto. De no ser esa la situación te aconsejo cualquiera de los otros métodos de este artículo. Serán más limpios y menos propensos a errores.
El Unity Package
El editor de Unity permite la creación de un paquete contenedor con los assets que decidamos y sus dependencias. Podemos pasarle este paquete a otro compañero para que este lo importe desde su editor.
Supongamos que queremos exportar una serie de scripts que utilizamos para las pruebas automatizadas.
| Los scripts que queremos exportar |
Para crear el paquete no tenemos más que pulsar con el botón derecho del ratón en cualquier carpeta de la pestaña Project del editor y elegiremos la opción "Export as Asset Package...".
Nos saldrá una ventana emergente en la que aparecerá seleccionado todo el árbol de carpetas de nuestro proyecto. En esa ventana tendremos que desmarcarlo todo, salvo lo que queramos incluir dentro del paquete exportado.
| Ventana de exportación |
Hecho eso, pulsaremos en el botón Export y elegiremos el nombre del paquete generado (yo lo he llamado TestsCommonTools) y dónde queremos guardarlo. El fichero generado será el que pasaremos a aquellos con los que queramos compartir nuestros componentes.
| Unity package generado |
Ahora vamos a ponernos en el lugar de una persona que quisiera importar nuestros componentes. Esa persona sólo tiene que arrastrar el paquete a la pestaña project de su editor, para que se abra la ventana de importación. Esa ventana le permitirá elegir qué componentes importar, en el caso de que no quisiera importarlos todos.
| Ventana de importación |
Al importar, debemos tener en cuenta que se nos creará la estructura de carpetas del paquete dentro de un nuestra carpeta Assets. Eso puede no ser un problema, si compartimos el mismo estándar de carpetas con el que exportó el paquete, o puede serlo si no nos gusta su estructura de carpeta o si ya tenemos otras con ese nombre, pero no queremos "contaminarlas" con el contenido del paquete.
| Estructura de carpetas antes de la importación |
| Estructura de carpetas tras la importación |
Lo bueno del Unity Package es que el editor se ocupa de incluir los ficheros meta y dependencias varias que pudieran ser necesarias para cargar los assets al importar. Lo malo, como ya hemos visto, es que nuestra estructura de carpetas se "contamina" con el estándar utilizado por el creador del paquete. Por tanto, este recurso sólo es recomendable de manera muy puntual o en caso de compartir assets con miembros de nuestro equipo que compartan nuestro estándar de carpetas.
El paquete UPM
La evolución de los Unity Packages son los paquetes para el Unity Package Manager (UPM). Se trata del sistema moderno y recomendado por Unity para gestionar paquetes, librerías, herramientas, shaders, samples y funcionalidades en tus proyectos.
Tiene múltiples ventajas respecto al sistema anterior:
- Los paquetes se pueden instalar y desinstalar desde el Package Manager, lo que simplifica muchísimo el proceso y asegura que al desinstalar no queden trazas del paquete.
- La instalación se produce fuera de la carpeta Assets, lo que mantiene la limpieza del proyecto.
- El Package Manager permite gestionar versiones, facilitando que se actualicen los paquetes cuando haga falta.
- Si un paquete depende de otros, el Package Manager se encarga de instalarlos también.
- Permite usar repositorios oficiales, como el de Unity; de terceros, como OpenUPM; o personales, como GitHub.
| Creación de un paquete UPM mediante el Package Manager |
Al hacerlo te aparecerá una pequeña ventana emergente, en la esquina superior izquierda de la pestaña, donde tendrás que poner el nombre que le quieres dar el paquete. Ehn el caso de mi ejemplo, le he dado el nombre de CommonTestTools.
Hecho eso, nuestro paquete aparecerá en el listado de los paquetes del proyecto.
| Apariencia provisional de nuestro paquete |
Sin embargo, todo lo que muestra el paquete en este punto es provisional. Ahora hay que configurarlo y darle contenido.
Si pulsas el botón Locate, de la descripción del paquete en el Package Manager, verás que la pestaña Project del editor se situará en la carpeta recién creada para el paquete, dentro de la carpeta Packages.
| Estructura de carpetas para nuestro paquete |
Como puedes ver en la captura anterior, el Package Manager ya se ha encargado de crear una estructura de carpetas para nuestro paquete. Cada una de esas carpetas tiene un propósito específico, pero dependiendo del de nuestro paquete, es posible que no necesites algunas. Paso a explicarte la función de cada una:
- Documentation: Aquí tienes que colocar la documentación de ayuda de tu paquete, en formato MarkDown o HTML.
- Editor: Aquí van los scripts con las extensiones del editor, si es que tu paquete incluye alguna.
- Runtime: Esta contienen los scripts destinados a ser utilizados por el juego, al ser ejecutado.
- Samples: Ejemplos de uso del paquete.
- Tests: Esta carpeta debería contener todos los tests (tanto pruebas unitarias, como de integración) para verificar el correcto funcionamiento de tu paquete. Esta carpeta contiene a su vez dos subcarpetas: una de Editor y otra de Runtime. La de Editor se usa para utilidades de testing que usen la API del Editor. Salvando ese caso, lo normal es que uses la de Tests/Runtime.
De todas las carpetas anteriores, el contenido de la de Runtime es el único que puede acabar en el juego final, si se le referencia desde el código compilado. El resto sólo tienen sentido para ser usadas desde el editor, por lo que no contaminan el juego final.
El caso de mi ejemplo es un poco particular, ya que son herramientas para ser utilizadas en los test unitarios de un juego, a priori no tiene sentido que vayan ni a la carpeta Runtime, ni a la de Editor, de la carpeta raíz. Sin embargo, he visto que no consigo visibilidad de las clases de la herramienta, desde mis PlayTests, a no ser que las coloque en la carpeta Runtime. Así que eso es lo que he hecho finalmente.
Es muy importante que copiemos los ficheros con la pestaña Project del editor de Unity. De esta manera, el editor se ocupará de copiar no sólo sus ficheros sino también sus correspondientes ficheros .meta, indispensables para que el paquete se ejecute correctamente.
| Ficheros de mi ejemplo, situados en el paquete |
Dotado de contenido el paquete, ahora toca definir sus metadatos. Eso se hace editando el fichero package.json, situado en la carpeta raíz del paquete. El contenido por defecto del paquete, tal y como lo crea el Package Manager, es algo como lo siguiente:
| Contenido por defecto del fichero package.json |
El propósito de los campos es muy intuitivo:
- name: Es el nombre completo del paquete. Usa el formato de dominio inverso para evitar coincidencias de nombre con otro paquetes. En realidad, da igual el dominio que pongas siempre que el nombre completo no acabe siendo el mismo que el de otro paquete.
- displayName: Es el nombre corto que se mostrará en el Package Manager y por el que el resto de la gente conocerá tu paquete.
- version: La versión de tu paquete. Conviene que lo vayas cambiando, conforme saques nuevas versiones del paquete, para que el Package Manager detecte el cambio y aconseje la actualización al usuario.
- unity: Versión utilizada para generar el paquete. Sirve como señal de la versión mínima compatible con el paquete.
- unityRelease: La minor-version de la etiqueta anterior. Sólo necesaria si, por lo que quiera que sea, nuestro paquete sólo funcionase a partir de un determinado parche.
- description: La descripción de lo que hace nuestro paquete.
- dependencies: Todos los paquetes de los que depende el nuestro para funcionar. El Package Manager instalará todos esos paquetes antes que el nuestro.
- author: Nuestros datos como autor del paquete.
- changelogUrl: La URL de nuestro registro de cambios. Lo normal es que lo hagamos apuntar al changelog de nuestro repositorio.
- documentationUrl: URL a la documentación del paquete. Es buena idea que apunte al readme o a la wiki del repositorio del paquete.
- licensesUrl: Si alojas tu paquete en GitHub y has seguido sus buenas prácticas, lo normal es que hayas creado un fichero license en tu repositorio. Esta URL debería apuntar a él.
No tienes que usar todos los campos. En realidad hay muchos más. Estos son los que introduce por defecto el Package Manager al crear el paquete. Pero puedes usar muchos menos. Por ejemplo, un paquete que creé para contar con una regla de medir en el editor tiene el siguiente package.json:
| Contenido del package.json de mi paquete unity-measuring-tape |
En el caso del paquete que nos ocupa en nuestro ejemplo, podría bastar por el momento con lo siguiente:
| package.json para nuestro ejemplo |
Si luego colgamos el código fuente de nuestro paquete en GitHub, siempre podremos actualizar el package.json introduciendo los correspondientes campos que apunten al repositorio.
Lo más cómodo es utilizar el IDE para editar el fichero package.json, pero Unity también te deja configurarlo desde el editor. Si en el Package Manager localizas el paquete, verás que te ofrece traes botones: Locate, Export y Manage. Ya hemos usado el botón Export. Ahora toca pulsar el botón Manage, lo que abrirá un combo en el que tienes que elegir la opción "Edit Manifest". Esto abrirá una ventana como esta en el inspector:
| Edición del package.json desde el inspector |
Una vez hechos los cambios, es probable que tengas que reiniciar el editor para que se reflejen en el Package Manager.
Ya casi lo tenemos listo. Falta prestarle algo de atención a los ficheros .asmdef. El Package Manager los crea en los directorios donde puede haber código y los enlaza entre sí. En teoría, no deberías necesitar tocarlos porque la configuración que traen por defecto suele bastar para casos sencillos. El problema es que el Package Manager le pone el nombre del ID de nuestro usuario en Unity, y suele ser bastante feo. Por mucho que luego le hayamos cambiado el nombre al paquete en el package.json, el editor de Unity no regenera los ficheros .asmdef. Así que hay que ir buscando esos ficheros por las distintas carpetas para cambiarles el nombre, tanto a nivel de fichero de la pestaña Project, como a nivel del campo Name del inspector del dichero .asmdef. Lo que ocurrirá al hacerlo es que se romperán los enlaces que apuntasen al fichero renombrado desde otros ficheros .asmdef. Por eso, una vez que hayas renombrado todos los ficheros .asmdef te tocará hacer una segunda vuelta rehaciendo los enlaces en el inspector de cada fichero. No tiene mayor complejidad. Verás el enlace roto porque el inspector lo sombrea, así que no tienes más que añadir un nuevo elemento a la lista de enlaces, elegir el fichero con su nuevo nombre y eliminar el elemento del enlace roto. Por ejemplo, el fichero .asmdef de la carpeta donde he dejado las clases de mi paquete ha quedado de la siguiente manera:
| El fichero .asmdef de mi paquete |
Con carácter general, que el campo "Auto Referenced" esté marcado puede ayudar para que no haya que enlazar manualmente el asmdef desde los asmdef de código que quiera usar las clases de la herramienta. Sin embargo, puede haber casos en los que con todo y con eso no te quede otra que enlazar el asmdef del paquete para usarlo desde tu código.
Otra cosa importante a tener en cuenta es que el parámetro "Root Namespace" del asmdef debe coincidir con el namespace que usen las clases de nuestro paquete. De otra manera puedes encontrarte con que el usuario del paquete no vea sus clases desde su código.
Teniendo en cuenta lo anterior, el código del proyecto donde hayamos creado el paquete ya podrá ver sus clases importando por el namespace que hayamos definido en el asmdef.
| Importación de las clases de nuestro paquete |
Compartir un paquete UPM
Con lo anterior, ya tendremos un paquete funcional, pero sólo podrá ser usado en el proyecto desde el que lo hayamos creado. Lo lógico es que queremos exportarlo para poder usarlo en otros proyectos.
La manera más inmediata de exporter un paquete UPM es en formato tgz. Nos basta con pulsar con el botón derecho la carpeta del paquete, dentro de Packages y elegir la opción "Export as UPM package...". Con eso crearemos un fichero .tgz que incluirá nuestro paquete. Aquel al que le pasemos el fichero no tendrá más que importarlo desde el Package Manager, pulsando el icono "+" de la esquina superior izquierda y eligiendo la opción "Install package from tarball...", lo que le permitirá elegir el fichero tgz para cargarlo.
El problema de la opción anterior es que nos obligaría a enviarle nuevos ficheros tgz a nuestros usuarios con cada nueva versión. Una alternativa más versátil es subir el código de nuestro paquete a GitHub. Basta con que la raíz del repositorio esté al nivel del fichero package.json. Haciéndolo así, nuestros usuarios podrán instalarse el paquete usando la opción "Install package from git URL..." y facilitando la URL del repositorio. La ventaja de este enfoque es que si vamos cambiando la versión recogida en el package.json, el Package Manager detectará el cambio y avisará al usuariod e que puede actualizar.
Por último, también puedes compartir tu paquete subiéndolo a un repositorio público como OpenUPM, para maximizar su visibilidad. Sin embargo, OpenUPM se usa al margen del Package Manager por lo que bien puede dar para otro artículo.
Conclusión
En este artículo hemos repasado todas las maneras de reutilizar tus assets de Unity, bien entre tus propios proyectos, o bien compartiéndolos con otros desarrolladores. Esto debería evitar que te vieses obligado a "reinventar" la rueda reimplementando componentes ya presentes en otros proyectos o copiando su código, componentes y dependencias de manera manual entre proyectos.