26 febrero 2026

Mis impresiones sobre los cursos de Zenva


Como muchos sabréis, en mi perfil de X suelo avisar de los packs de assets, cursos y libros que me encuentro por Internet a buen precio. Hace un tiempo salió un pack de cursos de Zenva en Humble Bundle. Basándome en lo que había leído en foros de Reddit, advertí en la noticia de que la calidad de los cursos podía no ser la esperada. Para mi sorpresa, Pablo Farias (@pablofariasnew), fundador de Zenva, me pidió que le diese una oportunidad a sus cursos porque no creía que merecieran esa crítica. Por eso, decidí comprarme el pack de Humble Bundle y hacer varios de los cursos, para comprobar de primera mano si la idea que me había formado en Reddit era correcta o no.

El pack que me compre contenía un montón de cursos de Godot. También tenía algunos cursos de Unity que había comprado hace bastante tiempo, pero que no había podido hacer. Al final, de todos ellos, he hecho 4 cursos:

  • Tower Defenser Game in Godot - Unit 1 - Base Game
  • Tower Defenser Game in Godot - Unit 2 - Enhanced Towers
  • Intro to Visual Shaders in Godot 4
  • The Complete Procedural Terrain Generation Unity Course.

Los dos primeros son de algo más de una hora y media de duración, el de los shaders tiene apenas una hora, mientras que el de generación procedural de terrenos en Unity abarca más de tres horas y media. Se trata de una muestra muy parecida a los que suelen ser los cursos de Zenva, o al menos los que yo he adquirido en mis packs, situándose todos en la franja entre la hora y las cuatro o cinco.

Para mi sorpresa, he de decir que me han gustado mucho. Todos los cursos están bien estructurados y cubren el alcance que prometen. No profundizan, pero lo que explican es suficiente para que entiendas lo que está pasando y lo practiques con seguridad en el editor por ti mismo. Incluso los conceptos teóricos, aunque se cuentan rápido, lo hacen con claridad y te queda una idea correcta, bien válida para profundizar por ti mismo a partir de ahí.

Por supuesto, son todos en inglés, pero cuentan con subtítulos en múltiples idiomas, incluido el español. Por lo que he podido ver, la traducción de los subtítulos en español es muy buena. De todos modos, yo he preferido verlos con los subtítulos en inglés. La pronunciación de los profesores es muy limpia y se les entiende sin dificultad, así que sólo he usado los subtítulos para comprobar palabras puntuales en las que me despistaba. Con los subtítulos tendrás problema en seguir el curso, sea cual sea tu idioma nativo. Aunque sería genial que esta plataforma, y el resto de las que publican cursos, empezasen a hacer como Youtube y tradujesen también el audio mediante IA. No sé el coste de producción que tendrá, pero cuando te encuentras uno de esos, con el audio traducido son una gozada, porque además les queda hasta natural la voz.

He intentado ver el precio de cursos sueltos, pero aparentemente sólo tienes la opción de suscribirte anualmente, lo que te da acceso a todo el catálogo de cursos. Esa suscripción parece valer unos 59.70 $ anuales. En cuanto a la valoración del precio no sabría decirte, porque eso depende del valor que te aporten, en función del punto del aprendizaje en que te encuentres. En mi caso concreto, ya tengo superada la fase de los cursos generalistas de introducción. Ya suelo ir a cursos intermedios que traten temas concretos. Eso me hace ser más selectivo, por lo que suelo evitar las subscripciones como las de Zenva. Pero si estás empezando, puede ser una buena opción pagar ese precio y tener todos los cursos concentrados y al alcance de la mano, en vez de ir dando tumbos por internet buscando tutoriales. También puedes hacer como yo, y estar atentos a los packs que Zenva suele ofrecer con cierta frecuencia. Eso te permitiría acceder a sus contenidos sin necesidad de estar pagando una subscripción que puede ser que no amortices si no tienes tiempo para estar viendo cursos asiduamente. Si me sigues en X te avisaré cuando me entere de un nuevo pack ;-)

Resumiendo: me han gustado los cursos de Zenva. Son de buena calidad. Los comentarios que había leído en Reddit no están justificados, al menos en mi opinión. Por mi parte, no me cabe duda de que compraré más packs conforme los vayan sacando. Me alegro de haber podido salir de mi error, no hay tantas opciones de calidad como para descartar una equivocadamente.

21 febrero 2026

Análisis del libro "Game AI Pro 360 - Guide to Architecture"

Portada del libroHay muchos libros sobre inteligencia artificial (IA), pero no tantos los que se enfocan a su uso para el desarrollo de videojuegos. De esos, hay de dos tipos: los introductorios y los de profundización. Este que nos ocupa es un libro de la segunda categoría. Por eso no se lo recomiendo a nadie que no se haya afilado ya los dientes con una buena cantidad de libros introductorios, y haya hecho sus pinitos implementando algoritmos de IA. Créeme, este libro no es para empezar a aprender IA. En realidad, ninguno de la colección a la que pertenece lo es. Hecha esta advertencia, voy a intentar justificarla explicando en qué consiste el libro.

El libro no sigue un hilo global. No te va a guiar por un itinerario de aprendizaje. En vez de eso, es un compendio de artículos de expertos, cada uno de ellos con un formato y un nivel de detalles muy similar a los papers académicos. Si alguna vez has tenido que procesar la RFC de un estándar, los artículos de este libro te recordarán mucho a eso. Los diferentes artículos se centran en diferentes implementaciones de sistemas de toma de decisión (máquinas de estados finitos, árboles de comportamiento, planificadores de tareas, etcétera) aunque cada uno lo hace de manera dispar.

Se nota que los autores de cada artículo son verdaderos expertos. Eso nadie lo discute. Cada uno de ellos se refiere a las implementaciones que han hecho en juegos de reconocido prestigio. El problema es que, por la razón que sea, la mayor parte de los artículos me dejan la sensación de que el autor se reserva sus mejores cartas; de que no te lo cuentan todo. Vale, me imagino que la explicación de la implementación completa de la IA de esos juegos AAA no cabría en un libro, y mucho menos en un artículo, pero es que he acabado el libro con la impresión de que o eres igual de experto que los autores o no serás capaz de rellenar los huecos que dejan en sus explicaciones. Es una sensación constante de "vale, creo que entiendo a alto nivel lo que quieres hacer, ¿pero eso cómo se implementa?" y ahí se acaba el artículo. Es cierto que a veces hay capturas de código, pero suelen ser fragmentos de interfaces o pseudocódigo de alto nivel.

Supongo que al menos debemos agradecer que este libro sea una recopilación por temas de los artículos aparecidos en las anteriores series de libros de Game AI Pro. Al menos en esta recopilación hay cierta coherencia temática. No lei los libros antiguos, pero si estaba todo mezclado, debían ser muy confusos.

Por todo lo anterior, debo advertirte contra este libro si te estás introduciendo en el mundo de la IA para videojuegos. En ese caso no creo que saques nada en limpio de él. Hay mejores opciones en las que invertir tu dinero, de los que aprenderás mucho más. Sólo le aconsejaría este libro a alguien que ya lleve muchos años trabajando a nivel profesional en este campo; e incluso en ese caso, no tengo claro que el contenido del libro no se haya visto ya superado por el paso de los años.

07 febrero 2026

Creación de paquetes para Unity

Portada

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
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
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
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
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 antes de la importación

Estructura de carpetas tras 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.
Para crear un paquete UPM, tienes que abrir el Package Manager pinchando en Window > Package Management > Package Manager. En la ventana emergente, tienes que pulsar en el símbolo "+" de la esquina superior izquierda y elegir la opción "Create package".

Creación de un paquete UPM mediante el Package Manager
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
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
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
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
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
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
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
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
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.

22 enero 2026

Asignación de costes a la navegación 2D por tilemaps en Unity

Portada del artículo

Hace poco vimos cómo se podía hacer en Godot para asignarle costes a los tiles de un tilemap, de manera que se pudiera usar esos costes en los algoritmos de navegación. Unity tiene su manera particular de hacerlo, pero, como en el caso de Godot, dependerá de si queremos utilizar esos costes con un algoritmo personalizado de navegación o con la navegación con navmesh del engine. Vamos a ver cómo se hace en ambos casos.

En este artículo asumiré que has leído mi artículo anterior sobre cómo asignar datos a los tiles en Unity. En este aprovecharemos algunas cosas lo que conté allí.

Costes para algoritmos personalizados de navegación

Cuando usemos algoritmos personalizados, como nuestras propias implementaciones de Dijkstra o A*, querremos acceder al coste de tránsito de una celda determinada. Podríamos optar por asignarle un game object al tile, tal y como vimos en el artículo mencionado antes, y que ese game object tuviese un script con el dato del coste. Pero eso sería excesivamente complejo, ya que tendríamos que asignarle un collider a ese game object para poder detectarlo con un sensor de volumen, al aplicar el sensor a la posición de la celda. No, definitivamente es mucho mejor en este caso la otra aproximación que veíamos en ese artículo: la de crear una clase que herede de Tile, hacer que incluya un campo de coste y utilizarla para dibujar las celdas que tengan un coste de tránsito más elevado. Sería una clase como la siguiente:

Implementación de nuestro tile personalizado
Implementación de nuestro tile personalizado

La clase se limita a tener un campo para poder meter el coste del tile a través del inspector (línea 13).

Para poder consultar desde nuestro código el coste del tile, en la línea 18 hay una propiedad de sólo lectura.

En el fondo, un Tile es una especie de Scriptable Object, por lo que se extiende de una manera muy parecida a como se hacía con estos. Como con los Scriptable Object, podemos decorarlos con el atributo [CreateAssetMenu] (línea 6) para que el editor muestre una entrada de menú desde las que crear instancias de este tile. En este caso lo he creado para que me lo muestre en la ruta "Scriptable Objects > CoutyardTile".

La creación de un tile de este tipo sería similar a la de un Scriptable Object. Pulsaríamos con el botón derecho en la carpeta donde quisiéramos guardar el tile y elegiríamos la opción configurada en el párrafo anterior.

Creación de una instancia del tile personalizado
Creación de una instancia del tile personalizado

Creada la instancia del tile, podremos darle la apariencia que queramos y asignarle un valor al campo del coste. Mi ejemplo es visualmente muy sencillo. A un tile de agua le he dado un coste de 80.

Configuración de un tile de agua
Configuración de un tile de agua

En mi ejemplo he creado cuatro tipos de tiles transitables:

  • Ground: Gris. Es el que no tiene dificultades de tránsito, por lo que su coste es 1.
  • Grass: Verde. Tiene un coste de 2.
  • Sand: Naranja. Coste 4.
  • Water: Azul. Coste 80.

Acuérdate también de asignarle un sprite. De otra manera, por mucho que le asignes un valor al color, el tile no se verá. Yo me he limitado a usar un sprite de color blanco, para que se aplique sobre él el color.

Creados los tiles personalizados, te bastará con poner en modo edición la pestaña del Tile Palette y arrastrar sobre ella los tiles, desde su carpeta.

Mi tile palette
Mi tile palette

Con eso, ya podemos dibujar las zonas "lentas" del escenario. El mío está bordeado de muros infranqueables y tiene muros internos de color negro.

El escenario de mi ejemplo
El escenario de mi ejemplo


Una vez que los costes están asociados a sus tiles, ¿cómo los recuperaríamos? Nos bastaría con un método como el de la siguiente captura.

Método para recuperar el coste asociado a cada tile
Método para recuperar el coste asociado a cada tile

El método supone que existe una variable global walkableTilemap con una referencia al tilemap donde se encuentren dibujados todos los tiles transitables del escenario. Con esa referencia, usamos su método WorldToCell() (línea 319) para convertir la posición global del escenario que queremos analizar a la coordinada del tilemap que se corresponde con dicha posición. Luego, usamos el método GetTile() (línea 320) para recuperar el tile asociado a esas coordenadas. Una vez que tengamos el tile, podemos acceder a su propiedad Cost para devolver su valor (línea 321).

Mesh navigation con zonas lentas

El método anterior es útil si queremos que nuestros agentes se muevan por el tilemap usando nuestro propio algoritmos de navegación. Sin embargo, puede ser que por rendimiento, o sencillez, prefiramos utilizar el sistema de mesh navigation que ya viene con el engine. El problema en Unity es que su sistema de mesh navigation está pensado para 3D y no sirve tal cual para escenarios 2D como los del ejemplo. Afortunadamente, hay un módulo libre llamado NavMeshPlus que solventa este problema para el 2D y se usa prácticamente igual que el mesh navigation nativo de Unity para 3D. Expliqué su instalación y uso en un artículo anterior en el que cubrí la navegación 2D en Unity. A partir de lo dicho en ese articulo, en este explicaremos cómo implementar zonas lentas que sean tenidas en cuenta por el algoritmo de búsqueda de caminos.

Por tanto, partiendo de que te has leído ese artículo, y que has seguido sus indicaciones para generar un un NavMesh, ahora hay que crear un tipo de área transitable por cada tipo de baldosa que tengamos. Para ello, ve a Window > AI > Navigation. La ventana que se abre tiene dos pestañas: Agents y Areas. Como te imaginarás, la que nos interesa es Areas. Allí ya están definidas las áreas por defecto del sistema de navegación de Unity:  Walkable, Not Walkable y Jump. Fíjate que en la columna de la derecha se definen el coste de tránsito de cada una de las áreas. Es aquí donde definiremos los tipos de áreas que vimos en la sección anterior, con sus correspondientes costes.

Configuración de las áreas transitables y sus costes


Definidas las áreas, hay que asociar cada tile al tipo de área al que pertenecen. Si seguiste el artículo de navegación 2D en Unity, habrás incluido un componente NavigationModifier en el mismo GameObject del tilemap donde tuvieras los tiles transitables. Desmárcale el campo Override Area. 

Acto seguido añade un componente NavigationModifierTilemap. En él hay una lista llamada Tile Modifiers. Añade a esa lista un elemento por cada tipo de tile transitable que queras definir. Luego arrastra a cada uno de los elementos el recurso de su respectivo tile, desde su carpeta. De esta manera podrás definir a qué area pertenece cada tile. 

En mi caso, la configuración queda de la siguiente manera:

Asignación de tiles a cada una de las áreas
Asignación de tiles a cada una de las áreas

Ahora hay que regenerar el NavMesh para que incluya los costes de tránsito que acabamos de definir para cada una de las áreas. 

Sin embargo, antes de pulsar Bake en el componente NavigationSurface tienes que asegurarte de cambiar el parámetro Render Meshes. Si lo tienes configurado tal y como lo dejamos en el artículo de navegación 2D en Unity, este parámetro tendrá el valor Physics Colliders, por lo que sólo tendrá en cuenta los objetos con colliders físicos asociados para "recortar" la zona transitable del NavMesh. Tenemos que cambiarlo a Render Meshes. De esta manera usará como referencia los tiles que hemos definido como transitables (que son componentes visuales, renderizables) para dar forma al NavMesh. Una vez cambiado podremos pulsar Bake.

Configuración de mi NavMesh
Configuración de mi NavMesh

Si haces que el editor te muestre el NavMesh, verás que este tiene varios colores para señalar las distintas áreas que ha detectado.

NavMesh generado con diferentes áreas

Ya tienes tu NavMesh, pero si ejecutas el escenario en este punto es probable que tu agente se quede quieto. Para que se mueva tienes que asegurarte de que esté configurado para usar las áreas transitables. Eso se hace configurando el parámetro AreaMask del NavMeshAgent, de manera que contenga todas las zonas por las que se podrá andar.

Configuración de NavMeshAgent
Configuración de NavMeshAgent

En la captura anterior, puedes ver que he configurado el AreaMask para que incluya las áreas Ground, Grass, Water y Sand.

Ahora sí, si ejecutas el juego, tu agente debería ser capaz de moverse por las zonas transitables, eligiendo siempre el camino con menor coste.

Probablemente te des cuenta de que, aunque el agente elija la ruta de menor coste, no disminuirá la velocidad si se ve obligado a atravesar una zona lenta. Para que eso ocurra, tendrás que llamar al método estático en NavMesh.SamplePosition() desde el Update() del agente, para analizar en todo momento la posición donde se encuentre. Ese método devuelve como parámetro de salida un objeto de tipo AI.NavMeshHit que tiene una propiedad mask con el área a la que está asociada ese punto. Lo normal es que un punto sólo esté asociado a una área, pero a pesar de eso la propiedad mask es (como su nombre indica) una máscara de 32 bits. Un bit por cada una de las áreas de navegación que Unity permite que se definan. Te basta averiguar el índice del bit que está marcado a 1 para saber el área de navegación del punto analizado. Con es índice puedes llamar a otro método estático, NavMesh.GetAreaCost() para obtener el coste del área sobre la que estemos y actuar en consecuencia (habitualmente dividiendo la velocidad del agente entre el coste del área).

Conclusión

Con esto, ya hemos revisado las dos opciones que tenemos en Unity para navegar por un escenario construido con un tilemap.

  1. Usar datos de coste asociados a los tiles para hacer una búsqueda de caminos clásica con nuestra implementación de Dijkstra o con la de A* que integra Godot.
  2. Usar mesh navigation con zonas lentas.

Si tus escenarios tienen un tamaño moderado, puedes permitirte la opción 2, si por la razón que fueses prefirieses modelar el escenario con un grafo. 

Sin embargo, en cuanto tu escenario crezca de tamaño preferirás la opción 3. Es la que mejor rendimiento ofrece y la más sencilla de configurar.

14 enero 2026

Asignación de costes a la navegación 2D por tilemaps en Godot

En mi anterior artículo expliqué cómo asignar datos personalizados a los tile de los tilemap con los que podemos construir un escenario 2D. Uno de esos datos puede ser el coste de tránsito por el tile, para ser utilizado por un algoritmo de Dijkstra que implementemos, o por el componente AStar2D ya incluido en Godot, para crear rutas a través del escenario.

Lo anterior sería una manera perfectamente válida de implementar la navegación por nuestro escenario. Pero podría ocurrir que, a pesar de haber utilizados tilemaps para construir el escenario, prefiriésemos recurrir al rendimiento que ofrece el mesh pathfinding de Godot, partiendo de lo que expliqué en mi artículo sobre navegación 2D en Godot. El problema es que en aquel artículo asumimos un escenario que sólo comprendía obstáculos y una zona transitable uniforme, por lo que no explicamos cómo aplicar costes a determinadas áreas de la zona transitable. Vamos a ver cómo hacerlo.

Para empezar, hay que aclarar que el interfaz de Godot mezcla los distintos métodos de navegación, empleando también términos similares, por lo que es fácil confundirse y sentirse perdido. 

Navegación integrada en los TileMaps

En mi opinión, la primera causa de confusión fue que cuando entramos en la configuración de un TileSet, por ejemplo expandiendo el recurso Tile Set del Tilemap que lo emplea, la categoría de Navigation Layers no tiene nada que ver con la navegación con mesh pathfinding. Estos navigation layers se asignan al TileSet aquí, pero se crean en Project > Project Settings... > General > Layer Names > 2D Navigation. 

Creación de navigation layers dentro de los Project Settings
Creación de navigation layers dentro de los Project Settings



Asignación de navigation layers al recurso de un TileSet en la configuración de un TileMapLayer


Una vez asignados al TileSet, a nivel de recurso, se puede definir la presencia de cada Tile en cada navigation layer "dibujando" un polígono en el apartado Navigation de la pestaña TileSet, de manera similar a como se definiría la capa física del Tile.

Asignación de un tile a un navigation layer
Asignación de un tile a un navigation layer

Hecho eso, tendríamos que activar la navegación en el TileMap, marcando su opción Navigation.

Opción Navigation, dentro de la configuración del TileMap

Con eso ya tendríamos todo para que un agente recorriese el escenario, usando una API similar a la que vimos en el mencionado artículo sobre navegación 2D en Godot. La diferencia respecto a aquel artículo sería que no habría que poner un NavigationRegion2D por encima de los TileMapLayer, y que a nivel de agente el nodo a utilizar no sería un MeshNavigationAgent2D, sino un NavigationAgent2D a secas.

Este método es más rápido y directo, pero tiene dos inconvenientes graves:

  1. Tiene un rendimiento muy malo, por lo que sólo es viable para escenarios pequeños.
  2. No permite establecer áreas de coste diferenciados en la zona transitable, por lo que sólo tiene sentido si tu zona transitable es uniforme. 

En realidad la utilidad de asignar navigation layers aquí es para definir los tiles por los que pueden transitar los diferentes agentes, no para asignar costes. Por ejemplo, el agente de un murciélago navegará por los tiles del cielo, mientras que el agente del protagonista se moverá por los tiles del suelo.

Estos inconvenientes son lo que me llevaron a enfocar el artículo anterior sobre navegación 2D en Godot al uso de mesh pathfinding. Como vamos a ver, este método resuelve los inconvenientes anteriores, pero es algo más complicado de configurar.

Mesh navigation con zonas lentas

Para explicar este método, vamos a partir de lo que ya conté en el artículo sobre navegación 2D en Godot que mencionaba más arriba. Así que si no te lo has leído aún, este sería el momento. En ese artículo dimos forma a un escenario que sólo tenía tres tipos de elementos: muros y obstáculos, ambos tiles con colliders configurados en sus correspondientes capas físicas, y baldosas de suelo, las cuales carecían de collider. También definimos un NavigationRegion2D, del cual colgaban los nodos de los TileMapLayers, y que abarcaba todo el recinto intramuros del escenario. Partiendo de ese escenario, en al de este artículo incorporaremos baldosas que serán transitables pero con un coste superior de lo normal. Sólo como ejemplo, esas baldosas "lentas" serían de hierba (verdes), arena (naranjas) y agua (azules).

Fíjate que, cuando pulsamos "Bake AnimationPolygon", lo que hace NavigationRegion2D es superponer un polígono sobre el recinto que le hayamos dicho y recortar las zonas donde detecte un collider. El polígono resultante será lo que le de forma al NavigationRegion2D. Los collider que tendrá en cuenta en el proceso son los incluidos en las capas físicas marcadas en el campo Parsed Collision Mask de la configuración del NavigationPolygon.

Configuración de las capas físicas a tener en cuenta para darle forma a un NavigationRegion2D
Configuración de las capas físicas a tener en cuenta para darle forma a un NavigationRegion2D

Hay que tener cuidado porque la configuración del NavigationRegion2D tiene un campo de Navigation Layers, por lo que es fácil creer que al darle forma al NavigationPolygon sólo se tendrán en cuenta los tiles asociados a determinados Navigation Layer. Sin embargo, no es así, nada más lejos de la realidad. A la hora de darle forma al NavigationPolygon sólo se tendrán en cuenta los collider asociados a los tiles, independientemente del navigation layer al que estén asociados estos. De hecho, si utilizas este método probablemente no tengas que asociar ningún navigation layer a los TileMapLayers. Eso sólo tenía sentido con la navegación integrada en los TileMap.

Configuración del NavigationRegion2D

Entonces, ¿para qué sirve el campo Navigation Layers de la configuración del NavigationRegion2D? En realidad, no es un parámetro de entrada, sino de salida. Sirve para asociar el NavigationPolygon generado a un navigation layer concreto. Como vamos a ver, puedes tener varios NavigationRegion2D asociados a un mismo navigation layer. Cuando un agente transite por un navigation layer, el algoritmo de búsqueda conformará un mapa que será la suma de todos los NavigationRegion2D asociados a ese navigation layer.

Fíjate también que en la configuración de un NavigationRegion2D hay un campo de Travel Cost. Este parámetro es un multiplicador de la distancia recorrida. Para calcular el coste de transitar por esa región, el algoritmo de búsqueda de caminos entenderá que cada unidad recorrida será equivalente al multiplicador introducido en Travel Cost.

Por tanto, aunque el sistema de mesh navigation de Godot no permita asignarle costes a tiles específicos, si puede poner costes por región, y puede montar un mapa uniendo varias regiones. Partiendo de eso, lo que vamos a hacer es crear una región que abarque los tiles con el coste 100 de tránsito (el coste por defecto), y otra región por cada zona de tiles "lentos que tengamos en el mapa". A esas últimas regiones les pondremos un Travel Cost acorde a los tiles recogidos en ellas. Asociaremos todas esa regiones al mismo navigation layer para que conformen un mapa unificado.

La pega, es que eso supone asociar colliders a las baldosas transitables. Sólo para ser detectadas al generar el NavigationPolygon. En el caso de mi ejemplo, eso supone crear una capa física para cada uno de los tipos de navegación. Como con el resto de las capas físicas, se crean en Project > Project Settings... > General > Layer Names > 2D Physics

Las capas 6, 7, 8, 9 son para definir los costes de navegación

Se puede ver en la captura que he creado cuatro capas físicas (NavGround, NavGrass, NavSand y NavWater) para asociarlas a sus respectivas baldosas. No queremos que nuestro agentes choquen esas baldosas. Nos bastará asegurarnos de no marcar esas capas en las máscaras de colisión de nuestros agentes.

Creadas las capas físicas, hay que asociarlas al TileMapLayer donde hayamos colocado las baldosas lentas. Para ello añadiremos cuatro elementos al campo Physics Layers del recurso TileSet del TileMapLayer. Añadiremos un elemento por cada capa física recién creada y nos aseguraremos de que su Collision Layer apunte a los números asignados a las nuevas capas. Los Collision Mask de los nuevos elementos pueden dejarse en blanco.

Con las capas físicas añadidas al TileMapLayer, ya podremos verlas en la pestaña de TileSet para crear polígonos que definan los collider asociados al tile en esa capa física. Por ejemplo, teniendo en cuenta que la capa NavWater ha quedado en el elemento de índice 6 de los Physics Layers asociados al TileMapLayer, la configuración del collider de la baldosa de agua tal y como puede verse en la captura.

Configuración del collider de la baldosa de agua
Configuración del collider de la baldosa de agua

Tendremos que repetir la misma operación en el resto de baldosas, asegurándonos de definir los collider en sus respectivas capas físicas (en mi caso NavGround, NavGrass, NavSand y NavWater).

Ahora que podemos diferenciar cada tipo de baldosa, en función de la capa física de sus respectivos collider, podemos definir una NavigationRegion2D por cada tipo de baldosa transitable.

Una región por cada baldosa transitable
Una región por cada baldosa transitable

En cada una de esas regiones, configuraremos su respectivo NavigationPolygon para que en su Parsed Collision Mask tenga en cuenta todas las capas físicas de los obstáculos y resto de baldosas, salvo la de la baldosa que da nombre a la región. Sólo entonces podremos regenerar el NavigationPolygon. Por ejemplo, la configuración para la región de la hierba (GrassNavigationRegión2D) sería la de la captura.

Configuración para generar el NavigationPolygon de la región de la hierba.

Como los collider de las capas físicas que marquemos en Parsed Collision Mask recortarán el NavigationPolygon resultante, la única capa que no hemos marcado es precisamente la de NavGrass.

Acuérdate también de fijar el radio de tu agente, para evitar que se roce con las paredes al desplazarse. En el caso de este ejemplo, lo he fijado en 50 píxeles, tal y como se puede ver en la captura anterior.

En mi caso, dado que sólo tengo un tipo de agente, todas las NavigationRegion2D tienen el campo de Navigation Layers fijado a su valor por defecto (1). Como ya he comentado, eso hará que todas las regiones se sumen para conformar un único mapa.

Una vez generado el nuevo NavigationPolygon de cada región, será un buen momento de fijar el Travel Cost de cada una al coste que queramos que tenga transitar por ella. Dado que en mi ejemplo mido las distancias en píxeles, las baldosas normales de suelo tienen un coste de 100 (su anchura en píxeles), mientras que a las de agua le he fijado un coste de 8.000. 

Llegados a este punto, la suma de todas las regiones transitables como en la siguiente captura.

Suma de las diferentes regiones transitables
Suma de las diferentes regiones transitables

Sin duda te darás cuenta de un problema evidente. Las regiones "lentas" no se unen con la región transitable con el coste por defecto. Se debe a que en el proceso de formación del NavigationPolygon, no sólo se recortan las formas de los collider de las capas físicas, sino que también se recorta una distancia equivalente al radio del agente. Se hace para evitar que el agente se acerque tanto a una pared que roce con ella. Podríamos reducir el radio del agente a cero, y con eso las regiones transitables se tocarían, pero entonces los agentes rozarían las paredes de una manera muy poco convincente.

Afortunadamente, hay una manera de resolverlo. Vía código, podemos decirle al sistema de navegación que suelde los laterales de regiones de navegación contiguas que se encuentren a una distancia inferior a un determinado umbral. He situado dicho código en el script que hay en el nodo del que cuelgan las diferentes NavigationRegion2D (puedes ver cual es en la captura de la jerarquía que puse hace unos párrafos).

Código para fusionar los laterales de regiones transitables cercanas
Código para fusionar los laterales de regiones transitables cercanas

Si nos fijamos en cómo han quedado las regiones, veremos que la distancia máxima entre dos contiguas es del doble del radio del agente. Dado que en la configuración de los NavigationPolygon metí un radio de 50 pixeles, en este script he fijado 100 como umbral (línea 13).

En la línea 26, saco el identificador del mapa de navegación activo. Es el que contiene las regiones que hemos generado.

Y ahora viene la magia: gracias al método estático MapSetEdgeConnectionMargin() de la línea 26, se podrá pasar entre las regiones transitables cuyas aristas contiguas estén a menos de 100 píxeles de distancia.

Como el script es además un [Tool], el editor de Godot refleja el cambio inmediatamente.

Regiones soldadas gracias a nuestro script
Regiones soldadas gracias a nuestro script

Fíjate en las marcas rosas con las que el editor ha marcado los lados de las regiones en las que ha aplicado soldadoras con otra contigua lo suficientemente cercana.

Al probar un ejemplo similar a este, verás que funciona si tu agente se desplaza evitando las zonas de mayor coste de tránsito, y entrando en ellas sólo si no tiene más remedio o si pones el objetivo explícitamente en el interior de una de ellas.

Otra cosa que observarás es que el agente no aminorará la velocidad cuando atraviese zonas de mayor coste. Eso es lógico porque debes ser tú quien lo implemente en función de cómo quieres que le afecte a tu agente el coste de una una zona. Lo habitual es que el mayor coste de una zona se refleje en una reducción de la velocidad, pero también podría ser que no quisieras que afectase a la velocidad sino al consumo de combustible o a la energía de tu agente. En todo caso, para averiguar el coste del NavigationRegion2D que esté travesando el agente no tienes más que llamar a NavigationServer2D.MapGetClosestPointOwner() para obtener el identificador de la region sobre la que se encuentre el agente en ese momento. Acto seguido, no tendrías más que usar ese identificador para que NavigationServer2D.RegionGetTravelCost() te diese el coste de tránsito de la región. Con ese dato ya es cosa tuya reflejar el mayor o manor impacto de viajar por una zona determinada.

Conclusión

Entre el anterior artículo y este hemos revisado las diferentes opciones que hay para navegar por un escenarios construido con un tilemap 2D:

  1. Usar datos de coste asociados a los tiles para hacer una búsqueda de caminos clásica con nuestra implementación de Dijkstra o con la de A* que integra Godot.
  2. Usar la navegación integrada con los tilemaps.
  3. Usar mesh navigation con zonas lentas.

Si tienes claro que vas a necesitar zonas de tránsito lentas, ya puede tachar la opción 2. En ese caso. la navegación integrada con los tilemaps no te servirá. A partir de ahí, si tus escenarios tienen un tamaño moderado, empezaría intentando la aproximación de la opción 2 y sólo pasaría a la 3 si el rendimiento de la otra fuese insuficiente. Como has podido ver, la opción 3 es la más potente, pero también más engorrosa de configurar y mantener.

11 enero 2026

Asignación de datos a Tiles en Godot

Portada del artículo
Es muy habitual recurrir a Tilemaps para dar forma a juegos 2D. La sencillez que ofrecen los hacen ideales a la hora de darle forma a un escenario de estética retro.

¿Para que podemos necesitar asociarle datos a un tile? Por múltiples razones. Por ejemplo, supongamos que estemos usando un Tilemap para conformar el mapa de un juego en perspectiva cenital. En ese caso, es probable que queramos añadirle un valor de "resistencia al avance" a los diferentes tiles, de manera que nuestro personaje se mueva más lento en los tiles que representen una ciénaga, más rápido en los tiles que representen un camino, y que no pueda travesar los tiles que muestren piedras infranqueables.

En un artículo anterior, expliqué como asociar este valor a los Tiles que utilices en Unity, pero me he dado cuenta de que no lo expliqué para Godot. Vamos a resolverlo en este artículo.

Imagina tener un escenario creado a base varios TileMapLayer. En el ejemplo de la captura he usado tres TileMapLayer: uno para definir los límites del escenario (WallsTileMapLayer), otro para definir los obstáculos interiores (InteriorObstaclesMapLayer) y otro para las zonas transitables (GroundTileMapLayer). En el de las zonas transitables he colocado todas las baldosas por las que pueden deambular mis agentes, si bien algunas baldosas deben suponer un coste de transito diferente a otras. 

Mi escenario de ejemplo
Mi escenario de ejemplo

Para reflejar los distintos costes de las baldosas, podemos asociar datos a las capas de datos personalizados (Custom Data Layers) del TileSet utilizado en los TileMaps. 

Para empezar, tienes que dar de alta los distintos tipos de datos dentro del TileSet. Dentro del inspector de cualquiera de los TileMaps tienes que extender el recurso del TileSet. Al hacerlo verás que tiene una sección denominada "Custom Data Layers". Si la extiendes podrás ver los tipos de datos asociados a cada baldosa, y añadir otros nuevos con el botón "Add element". En la captura tienes los datos que he asociado a mis baldosas.

Asociación de datos personalizados a un TileSet
Asociación de datos personalizados a un TileSet

Puedes ver que he asociado dos tipos de datos: un booleano definiendo si la baldosa constituye un obstáculo (aunque en mi ejemplo no lo he llegado a usar) y un float definiendo el coste de transitar sobre la baldosa.

Definidos los tipos de datos personalizados que se asocian con las baldosas, tendremos que definir el valor que tienen en cada baldosa.

Cuando selecciones un TileMapLayer en la jerarquía, verás que en el borde inferior del editor te aparecerá una pestaña de TileSet. Al seleccionarla verás que te aparecerán tres secciones para configurar el TileSet: "Setup", "Select" y "Paint". Podemos usar las dos últimas para asignarle valores a los datos personalizados.

En la sección "Select" puedes seleccionar baldosas concretas. Al hacerlo, te aparecerán varias secciones para configurar. Una de ellas es "Custom Data". Al ampliarla puedes ver los distintos datos personalizados y asignarle valores. Recuerda que en los campos numéricos y de texto tienes que pulsar la tecla enter para que el cambio se aplique de manera efectiva.

Configuración de datos personalizados en la sección "Select" de la pestaña "Tileset"
Configuración de datos personalizados en la sección "Select" de la pestaña "Tileset"

Puedes hacer la misma operación de una manera más cómoda desde la pestaña "Paint". Al seleccionarla, te aparecerá un combo en el que elegir el dato al que quieres darle valor, Una vez elegido el tipo de dato, debes meter el valor que quieres asignar en el campo de texto, a partir de ese momento cualquier baldosa que selecciones en el TileSet adoptará dicho valor. Esta opción es ideal para aplicar el mismo valor rápidamente a múltiples baldosas.

Configuración de datos personalizados en la sección "Paint" de la pestaña "Tileset"

Con esto, las baldosas portarán estos datos a las ubicaciones donde las sitúes.

A continuación, y a modo de ejemplo, tienes el método que he utilizado para recuperar el coste de la baldosa asociada a una determinada posición.

Método para recuperar el valor del coste asociado a la baldosa
Método para recuperar el valor del coste asociado a la baldosa

Todo TileMapLayer tiene un método LocalToMap() que te permite pasar una posición a las coordenadas de la baldosa que abarca dicha posición. He usado dicho método en la línea 188. Sin embargo, fíjate en que la posición no puede ser global, sino relativa al TileMapLayer. Por ese motivo he recurrido a la llamada a ToLocal() de la línea 189, con la que he pasado una posición global a otra relativa al TileMapLayer.

Con las coordenadas devueltas por LocalToMap() podemos llamar a GetCellTileData() (línea 190) para recuperar los datos asociados a la baldosa de esa posición. El tipo devuelto (TileData) es un paquete que aglutina todos los datos personalizados. Por eso, para recuperar el dato concreto que nos interese hay que llamar a su método GetCustomData() y hacer casting al tipo que sabemos que tiene ese dato. Ten cuidado porque a ese método hay que pasarle el nombre del dato que queremos y es fácil equivocarse al teclearlo.

Y eso es todo. Con eso ya tienes todo lo que necesitas para asociar datos a tus baldosas y recuperarlos luego dependiendo de la baldosa que ocupe cada posición. Lo que hagas luego con esos datos ya es cosa tuya.