19 julio 2025

Los Resources en Godot y el riesgo de compartirlos

En Godot, los Resources (recursos) son un tipo de objeto que se utiliza para almacenar y gestionar datos reutilizables en un proyecto. Son componentes modulares que pueden ser creados, guardados, cargados y compartidos entre diferentes nodos o escenas dentro del motor. Los recursos son esenciales para organizar y estructurar proyectos de manera eficiente.

Están pensados para almacenar información específica, como configuraciones, propiedades o datos personalizados, que pueden ser accedidos o modificados por nodos u otros sistemas. Los recursos pueden guardarse como archivos (generalmente con extensión .tres para recursos textuales o .res para binarios) y cargarse en el proyecto usando ResourceLoader. Esto permite compartir recursos entre escenas o incluso entre proyectos, al no estar ligados una escena específica, sino que pueden ser referenciados por cualquier nodo o script.

Las ventajas de usar recursos son varias:

  • Modularidad: Facilita la reutilización de datos sin duplicarlos.
  • Organización: Ayuda a mantener los datos separados del código o las escenas.
  • Flexibilidad: Puedes modificar un recurso y los cambios se reflejarán en todos los nodos que lo usen.

Godot ya trae muchos predefinidos, pero nosotros podemos crear los nuestros extendiendo la clase Resource. Por ejemplo, para un proyecto que estoy desarrollando en Godot C# he creado el siguiente recurso:

Ejemplo de recurso personalizado en Godot C#
Ejemplo de recurso personalizado en Godot C#

Como verás, no tiene mucho misterio. Basta con heredar de Resource y definir los campos/propiedades que queramos que tenga el recurso. Lo que sí merece mención aparte son los atributos. 

Como en cualquier otro script de Godot, el atributo [Export] sirve para que el campo al que decora sea editable en el inspector cuando se use el recurso. 

En cuanto al atributo [GlobalClass] de la línea 7, se necesita para que tu recurso personalizado esté disponible en el buscador del editor, cuando quieras usar el recurso. 

El cuanto al atributo [Tool], sólo es necesario si vas a necesitar acceder al contenido de tus recursos desde código que se ejecute en el editor (y no sólo en el juego).

Este recurso en concreto lo uso para poder crear un array de ellos en otro nodo y almacenar en cada uno de ellos enlaces a otros nodos presentes en la escena (ese enlace es precisamente el campo NodePath). Podría haber añadido métodos al recurso, nada te lo impide, por ejemplo, para validar los datos que se guarden en el recurso, pero en mi caso no me ha hecho falta.

Ejemplo de uso de mi recurso personalizado

Cuando pinches en cada recurso, lo desplegarás y podrás ver su contenido, así como editarlo.

Edición del contenido de los recursos
Edición del contenido de los recursos

Si vienes del mundo de Unity, los recursos son el equivalente a los Scriptable Object dado que también permiten guardar sus instancias como un asset más del proyecto. 

También cubren el hueco de las clases serializables de Unity. Aunque Godot C# tiene acceso al atributo [Serializable] de C#, a diferencia de Unity, Godot no es capaz de mostrarlas en el inspector. Por eso, la alternativa es crear una clase heredera de Resource con los campos que en Unity le habríamos puesto a la clase serializada. Tengo una versión en Unity de todo el ejemplo anterior y allí lo he resuelto haciendo de WeightedBehavior una clase serializable.

Los recursos son ubicuos en Godot, te los encuentras en todas partes. Por ejemplo, cada vez quieras usar un nodo Area2D, para crear un área de detección, dicho nodo te exigirá colgarle otro de tipo CollisionShape2D, con el que definir los límites del área de detección.

Un nodo Area2D utiliza a otro nodo CollisionShape2D para definir su área
Un nodo Area2D utiliza a otro nodo CollisionShape2D para definir su área

Lo interesante es que no podremos definir la forma del CollisionShape2D hasta que creemos un nuevo recurso en su campo Shape.

Un nodo CollisionShape2D con un recurso RectangleShape2D asociado
Un nodo CollisionShape2D con un recurso RectangleShape2D asociado

Esto responde a la obsesión de Godot por separar responsabilidades. En este caso, el nodo CollisionShape2D se limita a implementar una funcionalidad en un área, pero la forma de ese área se guarda en un recurso (en este caso, RectangleShape2D). 

Si extendemos el combo veremos que RectangleShape2D no es el único recurso que podremos asociar a ese campo.

Los distintos recursos que se pueden asociar al campo Shape
Los distintos recursos que se pueden asociar al campo Shape

Si conoces los ScriptableObjects de Unity, intuirás la utilidad de los recursos de Unity y si no, ya te irás dando cuenta conforme vayas usando el engine. Al final, lo más probable es que modeles los recursos en tu cabeza como "cajitas con datos".

Una de las utilidades de estas "cajitas" es que múltiples nodos pueda leer datos de la misma cajita, en vez de repetir dichos datos en cada nodo. Se trata de una medida de eficiencia que Godot sigue por defecto de manera transparente al usuario. 

Generalmente no pasa nada, pero a veces nos puede dar sorpresas si no somos conscientes de este comportamientos. Te pongo un ejemplo que acabo de sufrir "en mis propias carnes". 

En mi proyecto he desarrollado un sensor de cono. A todos los efectos es un cono de visión. La escena que compone el sensor cuenta con los siguientes nodos:

Nodos que componen mi sensor
Nodos que componen mi sensor

Las capturas de antes del CollisionShape2D están sacadas precisamente de esta escena.

El funcionamiento general es que la definición del parámetro de alcance y apertura del cono (definidos desde otro nodo) hacen que el nodo BoxRangeManager redimensione el CollisionShape2D para que sólo se detecten los objetos que estén dentro de su área. Luego, se hace un filtrado adicional más fino por ángulo y por distancia, pero el primer filtrado "grueso" es por si el objeto detectado entra en la caja.

Un agente de mi proyecto al que le he agregado mi sensor
Un agente de mi proyecto al que le he agregado mi sensor 

En la captura anterior, la caja azul es precisamente el área del RectangleShape2D.

Con un sólo agente todo funcionaba estupendamente. El problema empezó cuando añadí un segundo agente con otro sensor de cono.

Un ejemplo de problema al instanciar escenas con recursos
Un ejemplo de problema al instanciar escenas con recursos

Para mi sorpresa, los respectivos RectangleShape2D (ojo a la cursiva) se empeñaban en tener el mismo tamaño. Cuando cambiaba las dimensiones en uno de los agentes, ambas áreas tenían las dimensiones fijadas en ese agente, y cuando pasaba a cambiarlas en el otro agente entonces ambas áreas pasaban a configurarse con esas últimas dimensiones.

Tardé un buen rato en darme cuenta del problema. La cuestión era que no había dos RectangleShape2D independientes. Como los RectangleShape2D son recursos, todas las instancias del sensor usaban, y modificaban, el mismo recurso. De ahí la cursiva de antes, no había "respectivos" RectangleShape2D sino uno sólo compartido por los sensores de ambos agentes.

¿Cómo se soluciona esto? ¿Estamos condenados a usar una única instancia de nuestro sensor? Obviamente no, pero la solución no es tan evidente.

La clave estar en volver a la configuración del nodo CollisionShape2D y pinchar en el recurso RectangleShape2D para configurarlo por dentro.

Configuración interna del recurso RectangleShape2D
Configuración interna del recurso RectangleShape2D

Dentro de esa configuración, el campo Size es evidente y responde a las dimensiones del recurso.

El campo clave esté dentro del apartado Resource y se llama Local To Scene. Por defecto viene desmarcado, lo que significa que todas las instancias de la escena actual compartirán este recurso. En otros caso, esta configuración puede ser beneficiosa, pero en el mío provocaba el problema que te acabo de describir.

Por tanto, la solución era marcar este campo Local To Scene. Al hacerlo, cada instancia de la escena creará su propia copia del recurso y trabajará con ella. Al marcarlo y salvar la escena, ambos agentes mostraron la configuración correcta de sus respectivos (ahora sí) RectangleShape2D.

Problema corregido

Así que ten cuidado cuando utilices recursos en tus escenas de Godot. Ten presente que el comportamiento por defecto de Godot será compartir los recursos entre las distintas instancias de la escena. Por eso, si detectas algún caso en el que pueda haber instancias que necesiten personalizar los valores de ese recurso, deberás anticiparte a los problemas y marcar el valor Local To Scene, para que cada instancia tenga su propia copia del recurso.

Espero que este artículo te sirva para ahorrarte problemas como el mío.

16 julio 2025

Los live-games y el riesgo del power creep

Internet ha cambiado radicalmente el mundo del desarrollo de los videojuegos, desde aquellos tiempos en que la promoción de estos se basaba en artículos en revistas especializadas que ofrecían discos con demos. Gracias a la hiperconectividad que tenemos en casa, el acceso a los juegos se ha vuelto inmediato y estamos sometidos a un bombardeo constante de anuncios de otros nuevos. Para el jugador, el reto ya no es enterarse de qué juegos salen, sino elegir los que más le puedan gustar entre un universo casi infinito de posibilidades. Para los desarrolladores, el reto sigue siendo llegar al jugador, pero no por falta de medios para hacerlo sino por lo complicado que es despuntar en un mercado saturado.

En un ecosistema así, muchas casas desarrolladoras prefieren no estar sacando nuevos juegos continuamente, sino centrarse en uno sólo y hacerlo evolucionar con el tiempo, ganando nuevos jugadores poco a poco y fidelizándolos durante años. La idea es que, al centrar los esfuerzos en un único juego a lo largo de un tiempo prolongado, hay más posibilidades de despuntar entre la competencia e ir ganando notoriedad. Dicha evolución se suele hacer a partir del análisis de una telemetría exhaustiva de los jugadores, con la que los desarrolladores puedan entender cómo interactúan con el juego, que partes disfrutan más, cuáles menos y qué echan de menos. Con esos datos, los desarrolladores pueden hacer pivotar al juego hacia los gustos cambiantes de su público, ir ganando nuevos jugadores y reteniendo a los anteriores. Con frecuencia, este proceso de evolución hace que, con el paso de los años, el juego no se parezca en nada a lo que era inicialmente.

Este tipo de juegos es lo que se conoce como live-game (o juego en vivo), término que se usa principalmente en el contexto de los videojuegos para referirse a un título que recibe actualizaciones continuas, contenido nuevo y soporte activo por parte de los desarrolladores después de su lanzamiento. También conocido como juego como servicio (Game as a Service, GaaS), se caracteriza por mantener una experiencia dinámica y evolutiva para los jugadores, fomentando una comunidad activa a lo largo del tiempo. 

Fortnite
Fortnite es uno de los ejemplos más claros de live-game actuales

El modelo de negocio

Esta continuidad en el servicio supone mantener servidores desde los que funcione el juego y se haga telemetría de los usuarios, contar con un equipo de desarrolladores que sigan actualizando el juego con nuevos contenidos y mecánicas, así como realizar campañas de marketing constantes que atraigan nuevos jugadores. Por tanto, estos juegos necesitan una inversión continua que no es sostenible con el modelo clásico de monetizar el juego con su venta inicial al jugador. Con estos juegos los desarrolladores tienen que acudir a modelos de monetización continua. 

El más clásico de ellos es el de la subscripción, en el que el World of Warcraft de Blizzard fue un pionero que aún pervive. El problema de las subscripciones es que el gasto recurrente que conllevan puede suponer una barrera económica o psicológica a la entrada de nuevos jugadores, bien porque no puedan asumir ese gasto o bien porque no tengan claro que vayan a jugar tanto al juego como para amortizarlo.

World of Warcraft
World of Warcraft fue el pionero

Otras casas desarrolladoras prefieren eliminar esa barrera de entrada haciendo completamente gratuito el juego, sin costes de adquisición ni subscripciones. En estos casos la monetización puede venir de la venta de grandes paquetes de contenidos opcionales, en forma de DLC, o de contenidos con un volumen mucho menor, con un coste también reducido... y aquí llegamos a las microtransacciones.

Las microtransacciones son compras pequeñas dentro de un videojuego (o aplicación) que permiten a los jugadores adquirir contenido digital adicional usando dinero real. Suelen ser opcionales y se supone que están diseñadas para mejorar la experiencia de juego. Su coste es bajo, generalmente van desde céntimos hasta unos pocos euros por transacción. Los ejemplos más habituales de contenidos ofrecidos a través de transacciones son:

  • Cambios cosméticos respecto al juego base: Skins, atuendos o personalizaciones visuales de las armas (ejemplo: skins en Fortnite).
  • Ventajas de juego: Objetos que facilitan el progreso, como potenciadores o recursos (ejemplo: gemas en Clash of Clans).
  • Contenido adicional: Niveles, personajes o historias extras.
El modelo de negocio

Para el desarrollador, las microtransacciones suponen un riesgo mucho menor que los DLC. Con un DLC hay que desarrollar mucho contenido y, si no triunfa, las pérdidas pueden ser muy importantes. Las microtransacciones son mucho más granulares, al ser tan pequeñas, su coste es reducido y el riesgo también. Combinadas con una buena telemetría que permita seguir su aceptación entre los jugadores, se pueden usar varias microtransacciones fallidas para centrar el tiro de lo que le gusta a los usuarios y, a partir de ahí generar otras muchas que den en el blanco de lo que estos quieren comprar. Es mucho más fácil entrar en un balance de caja positivo si se cuenta con un equipo de análisis competente y unos desarrolladores y artistas creativos.

Las críticas

Para el jugador, las microtransacciones tienen el atractivo del capricho a coste nimio. Son "bombones" que resultan fáciles de comprar porque a primera vista su coste parece irrelevante, si bien, con el paso del tiempo, el coste acumulado puede ser considerable. Esa facilidad en el gasto ha hecho que muchos acusasen a las microtransacciones de generar adicción. Estas acusaciones tuvieron especial virulencia con las loot-boxes (o cajas de botín), microtransacciones donde los jugadores pagan (con dinero real o moneda virtual del juego) para obtener una caja virtual que contiene recompensas aleatorias. Estas recompensas pueden incluir objetos cosméticos, personajes, armas, potenciadores u otros elementos, pero el contenido exacto es desconocido hasta abrir la caja, lo que introduce un elemento de azar similar a las apuestas. Esa similitud ha llevado a que algunos países (como Bélgica y Países Bajos) hayan restringido o prohibido las loot boxes al considerarlas apuestas. Otros exigen que se publiquen las probabilidades de obtener ciertos ítems. Al final, debido a las críticas, muchos desarrolladores han reducido el uso de loot boxes o han optado por sistemas más transparentes, como pases de batalla o tiendas directas donde los jugadores compran exactamente lo que quieren. Algunos juegos, como Rocket League, eliminaron las loot boxes tras incluirlas. No es el único caso, en Star Wars Battlefront II (2017), las loot boxes generaron una gran controversia por su impacto en la jugabilidad, lo que llevó a EA a modificarlas tras críticas masivas, a pesar de que todo el modelo de monetización giraba en torno  ellas, por lo que su retirada supuso el final del atractivo económico del juego para su productora.

Loot boxes


La adicción no es la única crítica que reciben las microtransacciones. Otra de las más recurrentes es que, al dar ventajas injustas a quienes gastan más, reduzcan el juego a un pay-to-win que frustre a los que no estén dispuestos a pagar en la misma proporción. El pay-to-win puede disuadir incluso a los que sí tuvieran poder adquisitivo para permitirse las microtransacciones al no sentirse interesados por una experiencia de juego en la que primase más la cartera que la habilidad.

El riesgo del Power Creep

Otro peligro de las microtransacciones es lo que se denomina el power creep. Cuando un juego usa las microtransacciones como modelo de monetización, tiene que sacar contenido continuamente y además hacer más atractivo que el anterior para que los jugadores vayan comprando lo nuevo y no se conformen con lo que ya tienen. Hasta aquí lo normal, pero hay una posibilidad de que esta dinámica torne en un círculo vicioso que puede acabar siendo perjudicial para el juego, tanto desde el punto de vista de los desarrolladores como de los jugadores. El power creep (o "escalada de poder") se refiere al fenómeno en el que los desarrolladores introducen nuevo contenido (personajes, armas, habilidades, etc.) que es significativamente más poderoso que el contenido anterior, haciendo que los elementos previos queden obsoletos o menos competitivos. Por tanto, es un fenómeno que afecta sobre todo a las microtransacciones enfocadas a ofrecer ventajas en el juego.

Cuando el power creep se prolonga en el tiempo, puede introducir graves inconvenientes en el juego. Los más destacados son:

  • Impacto en la imagen del juego: Aplicado a juegos multijugador competitivos, donde los jugadores que no gastan dinero se enfrentan a desventajas claras, supondrá que el juego acabe teniendo la mala prensa de ser considerado un pay-to-win.
  • Impacto en la mecánica de juego: Los nuevos personajes, armas o ítems suelen tener estadísticas, habilidades o efectos superiores a los existentes, lo que desplaza el equilibrio del juego. Con el tiempo, esa "carrera armamentística" puede hacer evolucionar la mecánica de juego hasta que no se parezca en nada a como estaba concebida inicialmente... y probablemente no para bien.
  • Impacto en el desarrollo: Es muy difícil mantener indefinidamente una dinámica de desarrollo en la que en cada iteración hay que sacarse de la chistera nuevas ventajas, más poderosas que las anteriores, pero que no den al traste con la diversión de la mecánica de juego.
  • Impacto en los jugadores: la obsolescencia del contenido antiguo hará que los jugadores que usaban objetos o personajes previos pueden sentirse en desventaja, ya que estos ya no son tan efectivos conforme pase el tiempo. Quienes invirtieron tiempo o dinero en contenido antiguo pueden sentir que su esfuerzo fue inútil. Esto puede hacer que acaben desencantándose del juego y lo abandonen.
Power creep en las cartas de Pokemon
Power creep en las cartas de Pokemon

El power creep fuera de los videojuegos

En realidad, el power creep no es exclusivo de los live-games. Si hacemos memoria podemos encontrar ejemplos similares en el mundo de las series de televisión. Si te pones a pensar en las que has visto hasta el momento, te darás cuenta de que muchas se enfocan a una estructura de "vencer-al-villano-de-la-temporada". Para mantener el interés, dichos villanos son cada vez más poderosos, lo que hace las situaciones más dramáticas y extremas. Sin embargo, ese enfoque no es sostenible. Conforme pasan las temporadas los villanos de las primeras empiezan a parecer irrelevantes, comparándolos con los de las últimas. Por otro lado, el creciente dramatismo necesario para vencer a enemigos tan poderosos puede alcanzar un punto que desborde hacia el ridículo y la incredulidad. 

Hay muchos ejemplos de series que han sufrido este fenómeno, pero quizás una de las más evidentes es Dragon Ball. Los fans de Akira Toriyama recordarán como la serie empieza siendo un relato alegre y travieso, con un niño como protagonista, y unos enfrentamientos que se limitan a golpes de artes marciales. Con el tiempo, aparece la técnica de la "onda vital" o "Kame Hame Ha" (según la traducción e la serie que hayas visto) como un rayo legendario y definitivo. 

Dragon Ball al principio

Pero como la serie no para ahí, tampoco lo hacen las técnicas de combate ni los enemigos. Conforme la serie va avanzando, los enemigos se hacen más fuertes, ya no basta con pegarles para vencerles, la "onda vital" se queda corta, así que los protagonistas se hacen adultos, aprenden a volar y a lanzar nuevos tipos de rayos (la wikipedia identifica hasta 18 versiones distintas de la "onda vital", cada una de ellas de creciente poder). Con tanto despliegue de poder, los combates de la serie se vuelven tan aparatosos que un golpe mal dado puede acabar partiendo un planeta en dos. Al llegar, a este punto la serie ya había perdido su alegría y vitalismo y se había reducido a combatir a una larga sucesión de villanos más poderosos que los anteriores. Fue cuando perdí el interés y me desconecté de la serie, aunque me consta que siguió durante mucho tiempo y con abundantes spin-offs. 

Dragon Ball, ya víctima del power creep

Posibles soluciones al Power Creep

Esto, que ya pasaba con las series, puede pasarle también a un live-game, minándolo hasta su fin. Evitar o mitigar el power creep exige que los desarrolladores diseñen estrategias que equilibre la mecánica de juego y mantengan la experiencia justa, divertida y sostenible a largo plazo. Los enfoques que se suelen seguir son:

  • Enfocar las microtransacciones a la venta de contenido cosmético: Al tratarse de cambios meramente visuales (como skins o efectos visuales) no hay cambios funcionales que puedan afectar a la mecánica de juego, lo que mantiene su equilibrio. Un ejemplo de este enfoque es Fortnite, que centra la mayoría de sus microtransacciones en skins y bailes, evitando que el poder del jugador dependa de compras, por lo que estos no sienten presión por gastar para mantenerse competitivos, y el power creep funcional es mínimo.
  • Diseño de contenido lateral (sidegrading) en lugar de mejoras directas: En lugar de introducir nuevos elementos (personajes, armas, habilidades) que sean estrictamente más poderosos, los desarrolladores crean contenido con características diferentes pero equilibradas, que ofrecen nuevas formas de jugar sin superar al contenido existente. Por ejemplo, en Overwatch, los nuevos héroes se diseñan con habilidades únicas que no necesariamente son más fuertes, sino que aportan estilos de juego distintos. Esto minimiza la obsolescencia del contenido anterior, ya que la elección entre uno u otro se basa en preferencias o estrategias, no en poder bruto.
  • Ajustes de equilibrio regulares (buffs y nerfs): Los desarrolladores monitorean el meta del juego y ajustan las estadísticas o habilidades de elementos nuevos y antiguos para mantener un equilibrio competitivo. Por ejemplo, en League of Legends, los parches regulares ajustan a los campeones para evitar que los nuevos dominen el juego, fortaleciendo a los menos usados (buffs) o debilitando a los dominantes (nerfs). Con esta técnica se mantiene la relevancia de contenido antiguo y evita que el nuevo sea abrumadoramente superior.
  • Limitar el impacto de la progresión: Se trata de diseñar el juego para que las mejoras de poder tengan un impacto limitado o estén restringidas a ciertos modos (como PvE en lugar de PvP). Por ejemplo, en Destiny 2, el equipo nuevo puede ser más fuerte, pero los modos competitivos a menudo tienen límites de poder para igualar a los jugadores. Esto reduce la percepción de pay-to-win y mantiene la equidad en entornos competitivos.
  • Implementar sistemas de rotación o meta cambiante: Se basa en introducir cambios en las reglas del juego, modos o eventos que alteren qué elementos son más efectivos, sin necesidad de hacerlos más poderosos. Por ejemplo, en Hearthstone, la rotación de cartas entre formatos estándar y salvaje asegura que las cartas antiguas no compitan directamente con las nuevas, controlando el power creep.
  • Reutilización y mejora de contenido antiguo: Se trata de actualizar personajes, armas o ítems antiguos para que sigan siendo relevantes, en lugar de reemplazarlos con contenido nuevo más fuerte. Es el enfoque que sigue World of Warcraft, modificando las clases antiguas para asegurar que sigan siendo viables en nuevas expansiones. De esta manera, los jugadores perciben que la inversión realizada en contenido antigua mantiene su valor.
Soluciones al problema

Conclusión

Para mi gusto, el enfoque más sencillo es el seguido por Fortnite, de limitarse a vender contenidos cosméticos. Al no afectar a la jugabilidad, no requiere un esfuerzo de análisis y de revisión del contenido con el paso del tiempo. Aunque también es verdad que la mecánica de juego de Fortnite es tan sencilla, y su marco escenográfico tan plano, que puede permitirse la heterogeneidad loca de personajes que se dan en sus batallas (que más bien parecen un extracto de Ready Player One). 

Sin embargo, otros juegos basados en mundos con una ambientación mucho más marcada, como pueden ser los de World of Warcraft o Star Wars, probablemente no tengan tanto margen para ofrecer heterogeneidad visual sin que la cosa degenere en un delirio estético en el que no se reconozca el mundo en el que quieren sumergirnos.

Se trata por tanto de estudiar nuestro juego y ver hasta donde podemos llegar con cada una de las estrategias anteriores. La solución no tiene por qué ser apostar por una sola de las estrategias, sino que podemos optar por enfoques mixtos. El peso que le demos a cada una de las estrategias en nuestra mezcla final dependerá del marco argumental de nuestro juego, de nuestra disponibilidad de creativos para crear contenidos visuales, y de analistas y programadores para revisar y ajustar aquellos contenidos que pudieran afectar a la mecanica de juego.

12 julio 2025

Uso de temporizadores en Unity y Godot

Portada del artículo
Los temporizadores (o timers en inglés) en el desarrollo de videojuegos son herramientas o mecanismos que permiten medir y controlar el tiempo dentro del juego. Se utilizan para gestionar eventos, acciones o comportamientos que dependen del tiempo, ya sea en tiempo real o en ciclos de actualización del juego (frames).

Son una herramienta fundamental para controlar la lógica temporal en videojuegos, desde mecánicas básicas hasta sistemas complejos, y por tanto son esenciales en muchos aspectos del diseño y desarrollo de videojuegos. 

Son muchas sus utilidades y casos de uso. Veamos algunos de los ejemplos más destacados:

  • Control de eventos temporales: Como, por ejemplo, ejecutar acciones después de un tiempo específico, como activar una animación, generar enemigos (spawning), o mostrar un mensaje en pantalla.
  • Mecánicas de juego basadas en tiempo: Por ejemplo, implementar límites de tiempo para completar una misión, resolver un puzzle o sobrevivir a una oleada de enemigos.
  • Sincronización de animaciones y efectos: Para coordinar animaciones para que ocurran en el momento adecuado, como explosiones, transiciones o efectos visuales.
  • Gestión de cooldowns: Para controlar el tiempo de reutilización (cooldown) de habilidades, ataques especiales, recarga de armas o acciones del jugador.
  • Frecuencia de actualización de sistemas: Con el fin de regular la frecuencia con la que ciertos sistemas (como la IA de enemigos o la generación de objetos) se actualizan para optimizar el rendimiento
  • Eventos cíclicos o repetitivos: Para crear patrones que se repiten en intervalos regulares, como oleadas de enemigos, cambios climáticos en el juego o ciclos día/noche.
  • Sincronización en multijugador: Se usan para asegurar que los eventos en juegos multijugador estén sincronizados entre todos los clientes, como el inicio de una partida o la actualización de estados.
  • Efectos de retardo o espera: Con ellos se busca introducir pausas intencionadas para mejorar la jugabilidad o la narrativa, como esperar antes de mostrar un diálogo o activar un evento.

Teniendo en cuenta lo anterior, podemos distinguir los siguientes tipos de temporizadores (no excluyentes entre si):

  • Ligados al tiempo de juego: Su avance trascurre al mismo ritmo que el tiempo del juego. Por ejemplo, si el tiempo del juego está ralentizado, por encontrarse en tiempo-bala, entonces el avance del temporizador se ralentizará de igual manera.
  • Ligados al tiempo real: Toman su referencia del reloj del sistema y avanzarán de manera uniforme independientemente de que el tiempo de juego trascurra más lento.
  • Cuenta regresiva: Comienzan con un valor y disminuyen hasta cero, desencadenando un evento al finalizar.
  • Temporizadores cíclicos: Se reinician automáticamente para eventos repetitivos, como disparos automáticos o regeneración de vida.

Su implementación general es sencilla. Un temporizador no es más que una variable o sistema que registra el paso del tiempo, ya sea en segundos, milisegundos o ciclos de juego (frames). En la práctica, suelen implementarse como contadores que se actualizan en cada iteración del bucle del juego (game loop) o mediante funciones específicas proporcionadas por los motores de juego. Vamos a ver, algunas sus posibles implementaciones.

Implementación general

Todos los engines ofrecen una función que se ejecuta periódicamente. Unreal la llama Tick(), Unity Update() y Godot C# _Process(). A esas funciones se le suele pasar un valor, denominado deltaTime, que suele ser transcurrido entre esa llamada a la función y la anterior.
Suponiendo que llamásemos a esa función Actualizar(), podríamos utilizarla para crear un temporizador con el siguiente pseudocódigo:


Pseudocódigo para implementar un temporizador
Pseudocódigo para implementar un temporizador

Muchos engines ofrecen dos versiones diferentes de esa función periódica: una que se ejecuta al renderizarse un nuevo fotograma gráfico, y otra que se ejecuta tras periodos fijos de tiempo. El momento en el que se ejecuta la función que depende de los fotogramas se denomina idle frame, mientras que el momento en el que se ejecuta la función que sigue periodos fijos se denomina physics frame (porque esta última función periódica se usa para cálculos relacionados con la física del juego, mientras que la primera función suele ir más ligada a operaciones visuales).

Ten en cuenta que nuestra tarjeta gráfica no genera fotogramas a un ritmo constante. Por eso, a la hora de implementar temporizadores, es mejor utilizar la función basada en periodos fijos.

Implementación en Godot

Siguiendo su filosofía de "un nodo para cada tarea", Godot cuenta con un nodo temporizador especializado que, ¡oh, sorpresa!, se llama Timer. Este nodo es ideal, para usar temporizadores sin necesidad de implementarlos desde cero.

El nodo ofrece los siguientes campos para su configuración:

Campos de un nodo Timer
Campos de un nodo Timer


  • Process Callback: Define si queremos que el avance del Timer se contabilice al generar los idle frames o los physics frames, según lo que vimos en el apartado anterior.
  • Wait Time: Es el número de segundos de duración del temporizador.
  • One Shot: Si está marcado el temporizador sólo funcionará una vez y se desactivará. Para que vuelva a contar, habrá que arrancarlo manualmente. Si marcamos esta casilla, el temporizador volverá a empezar desde cero, una vez que alcance el tiempo marcado, repitiendo ese ciclo indefinidamente.
  • AutoStart: Si está marcado, el nodo empezará a contar conforme se introduzca en la jerarquía de la escena principal del juego. De otra manera, el nodo estará desactivado hasta que lo arranquemos manualmente.
  • Ignore Time Scale: Si lo marcamos, el temporizador se ligará al tiempo real, mientras que si lo dejamos sin marcar estará ligado al tiempo de juego.

El nodo ofrece la señal timeout(), la cual se dispara cuando el temporizador alcanza el tiempo marcado en el campo Wait Time.

Señal timeout

Configurando los campos del nodo Timer desde el inspector, y enlazando a su señal timeout() aquel método de otro nodo que queramos ejecutar al finalizar el tiempo, ya tendremos cubierta la mayor parte de los casos de uso de este nodo. 

Este nodo también puede ser manipulado desde el código. Vamos a ver un ejemplo de uso. Imaginemos que queremos programar un cooldown, según el cual una vez que se ejecute un determinado método, este no debe poder ejecutarse hasta que finalice el tiempo del temporizador. Supongamos que ese método se denomina GetSteering() y que se llame desde el método _Process() de otro nodo.

Para arrancar el cooldown, podríamos llamar a una función de activación justo al final del método GetSteering().

Ejemplo de arranque de un cooldown
Ejemplo de arranque de un cooldown

La función de activación del cooldown se llama en este caso StartAvoidanceTimer() y se limita a arrancar la cuenta del nodo Timer y a fijar a verdadero una bandera:

Implementación del método de arranque del cooldown
Implementación del método de arranque del cooldown

Si un Timer no tiene marcado el campo AutoStart, tendremos que llamar a su método Start() para que arranque, como se puede ver en la línea 235.

La bandera fijada a verdadero en la línea 236 se puede usar a modo de guarda al comienzo del método cuya ejecución queramos limitar. 

Uso de la bandera como guarda para controlar si se ejecuta o no el método


El ejemplo de la captura puede ser un poco alambicado si no se conoce el contexto del desarrollo en el que estoy trabajando, pero viene a funcionar calculando la ruta hacia un objetivo (línea 157). Si no hay otro agente con el que podamos chocar, y no hay activo un temporizador, se sigue la ruta calculada (línea 160). Si por el contrario hay un temporizador en marcha, se continua con la ruta de escape que se calculó en la anterior llamada a GetSteering() (línea 162). El método sólo se ejecuta por completo si se ha detectado una potencial colisión y no hay activo un temporizador, en cuyo caso se continua calculando una ruta de escape para evitar la colisión (línea 164 en adelante).

Sin un cooldown, mi agente detectaría una posible colisión por delante de él, calcularía un ruta de escape y giraría para evitar la colisión. El problema sería que en el siguiente fotograma ya no tendría el obstáculo delante de él (al haber girado antes para evitarlo) por lo que volvería a calcular la ruta más corta a su objetivo y giraría para encararlo, encontrándose de nuevo justo enfrente con el obstáculo que pretendíamos evitar. A partir de entonces se repetiría el ciclo. Para evitarlo, se calcula una ruta de escape y esta se sigue durante un tiempo (el de cooldown) para que el agente cambie sensiblemente de posición, evitando la colisión; entonces, y sólo entonces, se vuelve a reevaluar la ruta para alcanzar el objetivo. 

La configuración del Timer se puede hacer desde el código del método _Ready() del nodo del que cuelgue el Timer.

Configuración de un Timer en el método _Ready
Configuración de un Timer en el método _Ready

Ignora las líneas previas a la 83, no tienen nada que ver con el Timer, pero las dejo para que veas un ejemplo real de uso de un Timer.

En la línea 83, cargo una referencia al nodo Timer que cuelga del nodo que ejecuta este script. Con esa referencia podríamos configurar todos los campos disponibles en el inspector del Timer. En mi caso no ha hecho falta, porque no tenía que variar la configuración del Timer que ya había metido en el inspector, pero sí que he enlazado manualmente un método a la señal del Timer (línea 84). En este caso, el método enlazado es OnAvoidanceTimeout(). Podría haberlo enlazado usando la pestaña "Node" del inspector, pero en este caso he preferido hacerlo por código.

El método OnAvoidanceTimeout() no puede ser más sencillo, ya que se limita a poner a falso el flag, para que en el próximo fotograma el método GetSteering() sepa que ya no queda ningún temporizador activo.

Callback enlazado a la señal del Timer
Callback enlazado a la señal del Timer


Implementación en Unity

Unity no cuenta con componente especializado, como el nodo Timer de Godot, pero crearse uno es sencillísimo, haciendo uso de corutinas.

Vamos a ver cómo se podría crear un componente que emulase la funcionalidad ofrecida por el nodo de Godot. Podríamos llamar a nuestro componente CustomTimer:

 

Comienzo de nuestra clase Timer para Unity
Comienzo de nuestra clase Timer para Unity

Los campos que podría ofrecer este componente serían los mismos que los del citado nodo.

Campos de la clase, ofrecidos al inspector
Campos de la clase, ofrecidos al inspector

Con esto, lo que veríamos desde el inspector sería lo siguiente:

Vista desde el inspector de nuestra clase CustomTimer
Vista desde el inspector de nuestra clase CustomTimer

Si autoStart estuviera fijado a verdadero, el componente se limitaría a llamar al método StartTimer(), desde el Awake(), conforme se arrancase el script.

Arranque del temporizador
Arranque del temporizador

El método llamado en la línea 50 del método Awake se limita a arrancar una corutina y a guardar una referencia a ella para poder controlarla desde fuera (línea 55).

El cuerpo de la corutina lanzada es muy sencillo:

Cuerpo de la corutina del Timer
Cuerpo de la corutina del Timer

La corutina se limita a esperar los segundos que hayamos fijado en el waitTime. En función del tipo de tiempo que queramos usar (tiempo de juego o tiempo real) se esperan segundos de juego (línea 65) o segundos de tiempo real (línea 68).

Pasado ese tiempo, se lanza el evento de timeout (línea 72).

Si el temporizador es de un sólo uso se finaliza la corutina (línea 74) o, en caso contrario, se vuelve a repetir el ciclo (línea 60).

¿Para qué sirve la referencia que guardamos en la línea 55 del método StartTimer()? Pues sirve para parar la corutina antes de tiempo, si fuese necesario, usando el método StopCoroutine.

Código para la parada anticipada del temporizador
Código para la parada anticipada del temporizador

Un GameObject que tuviese un componente CustomTimer como el descrito podría hacer uso él, desde otro script de GameObject, sacando una referencia al componente:

Obtención de una referencia a CustomTimer
Obtención de una referencia a CustomTimer

A partir de esa referencia, el uso del Timer sería calcado al que ya vimos con el nodo de Godot.

Uso del componente CustomTimer

Como el ejemplo es la versión en Unity de lo ya visto para Godot, no me voy a extender más.

Implementación en C#

¿Qué tienen en común Godot C# y Unity? Efectivamente, su lenguaje de programación: C#. Este lenguaje es multipropósito y viene con "pilas incluidas", es decir con un montón de módulos y librerías para hacer múltiples cosas. Uno de esos módulos ofrece un temporizador con funcionalidades muy similares a las ya vistas. 

Si C# ya ofrece un temporizador, ¿por qué es tan habitual ver implementaciones en Godot y en Unity como las de antes? ¿por qué no usan directamente el temporizador de C#?

Creo que la respuesta más sencilla es para el caso de Godot. El lenguaje por defecto de Godot es GDScript y este no ofrece viene con "pilas incluidas" o, mejor dicho, estas son los miles de nodos que ofrece Godot. Por eso, Godot C# hereda el acceso al nodo Timer gracias a su disponibilidad para el código en GDScript.

El caso de Unity es más complicado de responder y creo que se debe al desconocimiento de lo que ofrece C# por si mismo. Además, ya hemos visto que crear un temporizador personalizado es muy sencillo si usamos un mecanismo tan familiar para los desarrolladores de Unity como lo son las corutinas. Creo que esa sencillez para crearse uno mismo su propio temporizador ha hecho que muchos no llegasen a indagar y a conocer lo que le ofrecía C# en este sentido.

El temporizador que ofrece C# es System.Timers.Timer y su uso es muy similar al que ya hemos visto en el caso de Unity y Godot.

Uso del temporizador nativo de C#
Uso del temporizador nativo de C#

En la línea 103 de la captura se puede ver que hay que pasarle a su constructor el tiempo que queremos que dure el temporizador. 

El campo AutoReset de la línea 105 es completamente equivalente Autostart del nodo de Godot.

En la línea 106 se puede ver que el evento que lanza este temporizador es Elapsed (equivalente a la señal timeout del nodo de Godot). A dicho evento se pueden subscribir callbacks (como OnTimerTimeout) de manera similar a como hacíamos en las versiones anteriores para Godot y Unity.

Por último, este temporizador de C# también tiene métodos Start() (línea 117) y Stop(), para arrancarlo y pararlo.

La única precaución que hay que tener con este temporizador es que nunca debemos llamar a su método Start() si no estamos seguros de que haya acabado la cuenta anterior. Si lo que necesitamos es iniciar una nueva cuenta sin que haya acabado la anterior, primero deberemos llamar a Stop() y luego Start(). El problema es que este temporizador está optimizado para ser utilizado en entornos multihilo, así que cuando llamas a varios Start() seguidos no va finalizando las cuentas anteriores, sino que las va acumulando en hilos distintos, haciendo que las cuentas continúen en paralelo y que los eventos vayan saltando conforme dichas cuentas vayan finalizando (aunque el efecto visible será que los eventos saltarán sin una periodicidad evidente, por ser de cuentas diferentes).

Conclusión

Si programas en C# tanto en Godot, como en Unity, hay muy pocos argumentos para no utilizar el temporizador de C#. Es una versión ligera, eficiente y, al ser multiplataforma, te permitirá reutilizar código entre Unity y Godot con mayor facilidad.

La única razón que se me ocurre para seguir utilizando el nodo de Godot es para que pueda ser manipulado desde el inspector al integrarse con otros elementos del juego; pero aparte de eso, no veo la utilidad de seguir usando ni el nodo de Godot, ni los temporizadores basados en corutinas de Unity.

28 junio 2025

¿Qué nombre le pongo a mi videojuego?

Chico pensando
A todo proyecto de desarrollo de videojuegos le llega el temible momento de reflejar la esencia de su obra en unas pocas palabras. 

Con todo el esfuerzo, tiempo, dinero e implicación que supone crear un videojuego, tener que resumirlo todo en unas pocas palabras puede antojarse injusto. Por eso, y por los habituales cambios de enfoque de la industria, muchos proyectos trabajan con un nombre provisional durante el desarrollo, y no es hasta el último momento que lo cambian al definitivo.

No es una tarea fácil porque no sirve cualquier nombre, sino que debe cumplir una larga lista de condiciones:

  • No demasiado genérico.- De otra manera, quien acuda a los buscadores de internet para encontrarlo puede acabar buceando en un montón de resultados que no tengan que ver hasta dar con él.
  • Fácil de pronunciar.- El boca a boca es una publicidad muy potente, pero puede verse truncada si nuestro nombre es una suerte de trabalenguas. Deberías pronunciarlo en alto y comprobar que lo puedes decir cómodamente, sin trabarte.
  • Fácil de recordar.- Tampoco facilitaremos el boca a boca, ni amortizaremos el dinero que invirtamos en publicidad, si el nombre es una especie de fórmula matemática que cueste recordar. Puedes probar a decirle posibles nombres a personas cercanas a ti y comprobar si se acuerdan de ellos al día siguiente.
  • Corto.- Esta condición viene a resumir las anteriores. Cuanto más corto y sencillo sea un nombre, mejor.
  • Atractivo.- Debe inspirar sentimientos y emociones positivas. Salvando el género del terror, nadie quiere comprar un videojuego para sufrir, así que tu nombre debe trasladar emociones que supongan una mejora respecto a la vida cotidiana de tus potenciales jugadores. Esto supone indagar en la psicología mayoritaria de los jugadores a los que quieras apuntar.
  • Acorde al tono del juego.- Si tu nombre sugiere un tono diferente al del juego, puedes acabar trayendo al público equivocado. No te interesa que ese público se sienta desengañado, al encontrarse con un juego diferente al que esperaban, y que acaben poniendo reseñas negativas de él. 
  • Debe sugerir las características más distintivas del juego.- Por ejemplo, si nuestro juego se distingue por su mecánica colaborativa, el nombre debería reflejarlo ("It Takes Two", "Dont Starve Together").
  • Original.- En el sentido de que no debe haber otro juego con el mismo nombre. De haber coincidencia, en el peor de los casos nos podremos encontrar con una demanda legal, y en el mejor con que nuestra promoción acaba enviando gente al otro juego.
  • Sin traducciones negativas.- Tienes que plantearte desde el principio dónde vas a querer vender el juego y asegurarte de que al traducir el nombre no van a encontrarte con palabras con un sentido equívoco u ofensivo. De otra manera, te puedes encontrar con problemas como el que tuvo Mitsubishi con su Pajero (en España tuvo que vender ese vehículo con el nombre de Montero), o Mazda con su Laputa. Hay muchos más ejemplos, así que no añadas tu juego a la lista.
Es bastante común acudir a los nombres abstractos o inventados porque te permiten cumplir fácilmente muchos de los requisitos anteriores. La historia de los videojuegos está llena de casos de éxito con este tipo de nombres, como por ejemplo Half-Life o Fortnite. Sin embargo, debes tener en cuenta que la contrapartida de estos nombres es que te obligan a invertir más en marketing para explicar de qué va el juego a tus potenciales jugadores, dado que la abstracción del nombre no lo transmite por si mismo.

De todos modos, todas estas condiciones no están grabadas en piedra. Son sugerencias para echar a andar tu proceso de búsqueda de nombres, pero ten en cuenta que la genialidad no suele seguir las convenciones. Hay muchos ejemplos de grandes juegos cuyos nombres no cumplen una o varias de las condiciones anteriores. Al final se trata de ser prácticos, las condiciones anteriores te pueden servir para filtrar las ideas más locas que puedan surgirte pero, al final, cuando por fin llegas al nombre, el nombre con mayúsculas, lo sabes porque se te queda clavado, como si fuera un flechazo.

Si aun con todo y con eso no te llega la inspiración, puedes usar Gemini, Grok o cualquier otra IA para que te sugiera nombres que te sirvan de punto de partida. Por ejemplo, puedes meterles un prompt en el que describas el juego y pidas veinte propuestas de nombres. Alguna habrá que, con unos pocos retoques, te valga. Soy consciente de que el uso de herramientas de IA no es muy popular dentro de la comunidad de desarrollo, pero son una herramienta más. Se trata de ser práctico y finalizar el juego para lanzarlo. Mejor eso que quedarse encasquillado.

23 junio 2025

Análisis del libro "Agile Game Development: Build, Play, Repeat"

Portada del libro

Las técnicas de desarrollo ágiles (Agile Development) están revolucionando la forma de crear software, permitiendo mejorar el trabajo colaborativo y la adaptación al cambio de los equipos de desarrollo, que pasan a adaptarse mucho mejor a los constantes cambios del mercado y a iniciar círculos virtuosos de mejora continua.

En lugar de planear todo el proyecto desde el principio (como en métodos tradicionales de desarrollo en cascada), las técnicas ágiles dividen el trabajo en pequeñas partes llamadas "iteraciones" o "sprints" (normalmente de 1 a 4 semanas). Al final de cada sprint, se entrega una parte funcional del producto. Esta naturaleza iterativa del trabajo busca establecer canales de comunicación constante entre el equipo de desarrollo y sus clientes, capacitar al equipo de desarrollo para responder rápidamente a los cambios en las necesidades de sus clientes, ofrecer entregas frecuentes del software desarrollado para que los clientes empiecen a obtener valor desde el principio y puedan alertar cuanto antes en caso de que el software no responda a sus expectativas. Todo esto se consigue impulsando la autoorganización del equipo de desarrollo y mejorando su motivación.

Partiendo de esos principios generales del desarrollo ágil, hay metodologías que los aterrizan a mecanismos de trabajo concretos y cotidianos. Hay tenemos, por ejemplo, a Scrum que propone los ciclos cortos, antes mencionados, y un abanico de roles y de reuniones periódicas para que todos los implicados en el proyecto (no sólo los desarrolladores) puedan conocer el estado de este e influir en su evolución. También es habitual mezclar con lo anterior las técnicas Kanban, con sus tableros con columnas, para ayudar a visualizar el flujo de trabajo y evitar cuellos de botella; o incluir también las técnicas de Extreme Programming (XP) para mejorar la calidad del código, usando prácticas como la programación en parejas y pruebas automatizadas.

El mundo del desarrollo de los videojuegos no es ajeno a todas estas técnicas. De hecho, la naturaleza extremadamente creativa de los videojuegos y lo cambiante de los gustos de sus consumidores hace muy recomendable huir del encorsetado desarrollo en cascada y acudir a las técnicas ágiles anteriores.

El libro "Agile Game Development: Build, Play, Repeat", de Clinton Keith, me ha parecido la obra ideal tanto para iniciarse como para profundizar en este tema.

Se trata de una obra muy madura, escrita por un profesional de Scrum, que antes lo fue del desarrollo, trabajando primero en desarrollos "clásicos" (para los sistemas de control de cazas estadounidenses) y luego en desarrolladoras de videojuegos de primera línea. Toda esa experiencia previa asoma constantemente con ejemplos reales realmente enriquecedores y divertidos que permiten contextualizar la teoría en la que se quedan muchas obras similares. Otros libros se limitan a explicarte en qué consiste Scrum, pero este parte primero de los problemas que suponía para la industria del videojuego el desarrollo en cascada, y los ejemplos y anécdotas que utiliza para ello son realmente entretenidos, esclarecedores y profundamente humanos (consuela saber que "en todos lados cuecen habas").

Partiendo de esas problemáticas, el autor explica con muchísimo detalle los principios del desarrollo ágil y cómo los aterriza al día a día la metodología Scrum. En los primeros capítulos (aproximadamente el primer tercio del libro), la descripción de las tareas de la metodología son similares a las que se realizan con Scrum en el desarrollo de aplicaciones más genéricas. De hecho, como yo ya había leído otros libros sobre Scrum, mi primera impresión en esos capítulos fue "pues esto del Scrum parece aplicarse exactamente igual en los videojuegos que en las aplicaciones generales". Esto no es una crítica, muy al contrario, creo que ese primer tercio es valioso para los que quieran iniciarse en Scrum ya que los ejemplos que se van poniendo son del mundo de los videojuegos, muy visuales, sencillos de entender y muy divertidos, pero al mismo tiempo perfectamente extrapolables al desarrollo general. 

Los dos tercios restantes es donde, afortunadamente, el libro se despega de otras obras más genéricas sobre Scrum y profundiza en las peculiaridades que obliga el desarrollo de videojuegos. Detalla los casos en los que la aplicación literal de Scrum no funcionaría en un grupo de desarrollo de videojuegos y qué aproximaciones ha conocido él para resolver esas problemáticas. El resultado es, no sólo un manual práctico de referencia sobre cómo dirigir un equipo desarrollador de videojuegos, sino también un testimonio de las grandezas y miserias de este segmento de mercado.

Por tanto, creo que esta obra es imprescindible para todos aquellos que quieran madurar, dando el salto desde el desarrollo en solitario por afición, al desarrollo en equipo con fines comerciales. Desarrollar no es sólo programar, sino entregar algo funcional a nuestros clientes y que estos puedan disfrutarlo. Este libro te enseñará como lograrlo. 

14 junio 2025

¿Qué archivos debemos incluir en Git para un proyecto de Unity?

Imagen de portada
Si aún no tienes tu proyecto de Unity a buen recaudo, en un repositorio de Git como GitHub, deberías hacerlo cuanto antes. 

Las principales razones para añadir un repositorio de versiones a tu rutina de trabajo son:

  • Tener tu proyecto en un repositorio remoto (como GitHub, GitLab o Bitbucket) asegura que no pierdas tu trabajo si algo le pasa a tu ordenador.
  • Git te permite rastrear cambios en tu proyecto, revertir a versiones anteriores si algo sale mal y entender qué modificaciones se hicieron y cuándo.
  • Si trabajas en equipo, Git facilita que múltiples personas trabajen en el mismo proyecto sin conflictos, usando ramas y merges.
  • Puedes usar ramas para experimentar con nuevas funciones o correcciones sin afectar la versión principal, manteniendo el proyecto ordenado.
  • Muchas plataformas de CI/CD y herramientas de desarrollo se integran con Git, lo que agiliza flujos de trabajo como pruebas automáticas o despliegues.
  • Usar Git es un estándar en la industria, y dominarlo te prepara para trabajar en entornos profesionales.

Sin embargo, no debes subir todos tus ficheros a un repositorio Git. Por ejemplo, no tiene sentido subir los compilados porque cualquiera los puede generar si cuentan con el código fuente. Subir los compilados sólo incrementaría innecesariamente el tamaño del repositorio, haciéndolo más caro de mantener e incrementando los tiempos de sincronización del resto del equipo de desarrolladores.

Por eso, para proyectos de Unity, asegúrate de incluir un archivo .gitignore adecuado para evitar subir archivos innecesarios. Git espera encontrar este fichero en la raíz del proyecto (al mismo nivel que la carpeta Assets). Su contenido enumera el listado de nombres de fichero y carpetas que Git debe evitar incluir en el repositorio para mantenerlo limpio. En internet, puedes encontrar abundantes ejemplos de ficheros .gitignore adecuados para Unity. Si usas Rider, JetBrains tiene un plugin oficial (denominado .ignore) que te ofrece un wizard para generar un .gitignore adecuado al proyecto que te traigas entre manos (incluido Unity). Otra fuente puede ser GitHub, que tienen un repositorio oficial con archivos .gitignore para los frameworks de desarrollo más populares, y tampoco falta el de Unity

Qué debes dejar fuera del repositorio

Si optas por construirte un fichero .gitignore a mano, debes excluir lo siguiente:

  • Carpetas de Unity: Excluye Library, Temp, Obj, Build, Logs y UserSettings, ya que son generadas automáticamente por Unity y no deben versionarse.
  • Archivos de compilación: Archivos como .csproj, .sln y otros generados por Visual Studio o Rider no son necesarios en el repositorio.
  • Caché y assets: Excluye caché de Addressables, StreamingAssets y paquetes para reducir el tamaño del repositorio.
  • Archivos del sistema operativo: Ignora .DS_Store (macOS) y Thumbs.db (Windows).
  • Editores y herramientas: Excluye configuraciones específicas de editores como VS Code o Rider, pero permite incluir configuraciones compartibles (como .vscode/settings.json).
  • Archivos grandes: Si no usas Git LFS, excluye archivos comprimidos o pesados como .unitypackage.

Qué debes incluir en el repositorio

Eliminado lo anterior, tu repositorio debería contener sólo las siguientes carpetas:

  • Assets
  • Packages
  • Project Settings

La más crítica de las carpetas anteriores es la de Assets ya que contiene todo el código del proyecto, así como los modelos, texturas, música, sonidos, y todos los elementos con los que está construido nuestro proyecto. 

La pregunta de siempre: ¿Qué hago con los ficheros .meta?

Si usas el explorador de ficheros para examinar la carpeta Assets, verás que cada uno de los elementos anteriores lleva asociado un fichero con extensión .meta. Dado que Unity se encarga de generar automáticamente esos ficheros .meta, mucha gente se pregunta si deberían incluirse en el repositorio. Para responder a esa pregunta, primero hay que entender para qué sirven.

Unity asocia un fichero .meta a cada uno de los ficheros assets que se incluyan en el proyecto. En los ficheros .meta se guardan los parámetros para importar cada asset en el proyecto. Esto es especialmente importante con assets como las texturas o los sonidos ya que, si no incluimos los ficheros .meta en el versionado, otros desarrolladores del equipo pueden importar los mismos recursos con diferentes parámetros, lo que puede darnos problemas.

Dado que Unity tiende a generarlos automáticamente si no los encuentra, es muy importante insistirle a todo el equipo en que, cuando creen un nuevo asset, lo incluyan en el repositorio junto con su respectivo archivo .meta. Si alguno se le pasase incluir en el repositorio el fichero .meta del recién creado asset, los próximos desarrolladores que se lo bajasen generarían un fichero .meta para él (lo haría el editor de Unity de manera automática al echarlo en falta) por lo que tendríamos un potencial conflicto al hacer los merges, dado que tendríamos diferentes ficheros .meta (uno por desarrollador) para un mismo asset. Es una situación fea que da para recurrentes preguntas en reddit

Moraleja: bajo ningún concepto te olvides de incluir los ficheros .meta en el repositorio de versiones.

Conclusión

Con lo anterior, ya tienes lo básico para saber si te falta algo importante en tu repositorio. Luego, operar con ese repositorio es un capítulo aparte que da para muchísimo. Usar un repositorio de Git es fácil, pero hacerlo de manera efectiva, y sin liarla, tiene mucha más miga.

Si quieres profundizar en este tema, le dedico el capítulo 15 de mi libro "Reconstrucción de un juego mítico con Unity".

12 junio 2025

Game Engines basados en Blender

Portada de Yo Frankie!
Blender se ha erigido, por derecho propio, como una de las grandes herramientas de modelado, animación y rendering del mercado. Su calidad, unida a su carácter open-source y gratuito, ha disparado su popularidad aupándola a una posición en la que puede mirar de tú a tú a grandes "monstruos" comerciales como Maya o 3D Studio.

Lo que no sabe tanta gente es que Blender también fue un engine para construir juegos. Se llamaba Blender Game Engine (BGE), y estuvo integrado con Blender desde comienzo de la década de los 2000. Permitía la creación de aplicaciones interactivas directamente desde Blender, sin necesidad de herramientas externas. Para la lógica, permitía no sólo programar en Python sino también emplear un lenguaje visual, muy similar a los actuales Blueprints de Unreal Engine.

El engine permitía crear juegos tanto 3D como 2D, con física en tiempo real (usando la librería Bullet Physics). También permitía crear shaders básicos y animaciones. Como estaba integrado en Blender, ya no hacía falta exportar los modelos a otros motores.

Para demostrar las capacidades del engine, la fundación Blender desarrolló el juego "Yo Frankie!" en 2008. El juego era vistoso, pero pronto quedó claro que que se quedaba atrás respecto a lo que podían ofrecer otros engines como Unity o Unreal. Frente a estos, el BGE no podía ofrecer más que un rendimiento limitado en proyectos complejos, adolecía de falta de soporte para plataformas móviles modernas y su interfaz de desarrollo estaba menos pulido.

Al final, la fundación Blender tuvo que tomar una decisión. Para mantenerse a la par que otros engines, el BGE requería en exclusiva de un equipo de desarrolladores que la fundación no podía dedicar. Además, la comunidad de desarrolladores de BGE, ajenos a la fundación, era muy pequeña y eso hacía que el ritmo de actualizaciones fuera muy lento. BGE se iba quedando atrás a ojos vista e iba lastrando al resto del proyecto de Blender. Al final se optó por abandonar el BGE, para poder concentrar los recursos de desarrollo en aquello que mejor se le daba a Blender: las herramientas de modelado, animación y renderizado (como Cycles).

La eliminación formal del BGE tuvo lugar en 2019, con la versión 2.80 de Blender. A partir de ahí, la fundación recomendó usar Blender como herramienta de modelado y exportar desde allí los assets a motores más avanzados.

Afortunadamente, como suele pasar en el mundo open-source, el cierre de un proyecto no suele ser tal, sino más bien su traspaso. Otros desarrolladores, interesados en continuar con el proyecto, recogieron el código fuente de BGE y lo hicieron evolucionar a partir de ahí. Gracias a eso, dónde antes teníamos un engine (BGE) ahora tenemos dos descendientes de aquel y uno fuertemente inspirado en él. Vamos a analizarlos:

UPBGE (Uchronia Project Blender Game Engine)

Logo de UPBGE
Este engine fue creado, junto con otros colaboradores, por Porteries Tristan, uno de los desarrolladores del BGE original 

Inicialmente, UPBGE pretendía ser una bifurcación que mejorase el código de BGE, limpiando su base y experimentando con nuevas características, pero con vistas a acabar incorporándose en el código principal de BGE. Sin embargo, la eliminación de BGE en la versión 2.80 de Blender llevó a UPBGE a adquirir identidad propia e independiente. A partir de entonces, UPBGE ha continuado su desarrollo asegurándose de sincronizar su código con el de Blender, para mantener la compatibilidad.

UPBGE está completamente integrado con Blender, permitiendo modelado, animación y desarrollo de juegos en un solo entorno sin necesidad de exportar assets.

Utiliza el motor EEVEE de Blender para renderizado en tiempo real, con soporte para sombreado PBR, reflejos SSR, oclusión ambiental GTAO, bloom, sombras suaves y volumétricos.

Usa la biblioteca Bullet Physics para simulaciones en tiempo real, incluyendo cuerpos rígidos, simulación de obstáculos y búsqueda de caminos.

Además, incluye un motor de audio basado en OpenAL y Audaspace, con soporte para sonido 3D

Para la lógica de los juegos, mantiene el sistema de "logic bricks" del BGE original y añade Logic Nodes, facilitando la creación de comportamientos interactivos sin programar. También soporta scripting en Python para los que prefieran programar a golpe de teclado.

A la hora de importar y exportar elementos, permite los mismos formatos que Blender, principalmente FBX, Collada, glTF, OBJ y STL.

Se trata de un proyecto muy activo, con un equipo pequeño pero muy implicado de desarrolladores, que sacan actualizaciones regulares cada pocos meses y mantienen el engine sincronizado con las evoluciones de Blender.

A nivel de funcionalidades, los desarrolladores planean añadir características modernas como SSAO, profundidad de campo (DoF), herramientas para modo en línea o luces de área.

Por tanto, se trata de un proyecto muy interesante y prometedor, al que sólo se puede poner un pero y es que el rendimiento que se puede esperar en escenas complejas, con muchos polígonos o riqueza de efectos, puede ser más limitado que el que se puede conseguir con engines como Unity o Unreal.

También hay que tener en cuenta que hereda la licencia GPL de BGE. Los juegos desarrollados con UPBGE no están obligados a ser de código abierto si solo usan los datos del proyecto (modelos, texturas, scripts, etc.) y no distribuyen el ejecutable de UPBGE (blenderplayer). Sin embargo, si un juego incluye el blenderplayer o partes del código de UPBGE, debe cumplir con la GPL y ser distribuido con el código fuente.

Range Engine

Logo de Range Engine
Este engine no es un fork del BGE original, sino de UPBGE. Se originó en 2022, como consecuencia de determinadas decisiones de diseño que se tomaron para UPBGE 3.0. Algunos desarrolladores entendieron que dichas decisiones alejaban a UPBGE del espíritu del BGE original y por eso decidieron escindirse a través de un fork.

Por ese motivo, Range Engine conserva un interfaz mucho más cercano al que había en el BGE original. Frente a UPBGE, Range Engine prima la sencillez de uso.

Range Engine prioriza la compatibilidad con proyectos antiguos de BGE y la sencillez en el flujo de trabajo de los desarrolladores.

Su juventud, y su relativamente menor base de desarrolladores, explican que sus funcionalidades y ritmo de actualizaciones sean menores que los de UPBGE. 

Aun así, parece que han conseguido optimizar el apartado de animaciones frente a UPBGE. Por otro lado, también se han observado limitaciones de rendimiento cuando se utiliza este engine con grandes escenarios abiertos. 

Al igual que UPBGE, está licenciado con GPL. Sin embargo, incluye Range Armor, un formato proxy que permite empaquetar datos de juego separadamente del ejecutable blenderplayer, posibilitando la creación de juegos bajo licencias más flexibles como MIT, lo que facilita la distribución comercial sin restricciones estrictas.

Armory 3D

Logo de Armory 3D
A diferencia de los dos anteriores, no se trata de un fork directo de BGE, sino de engine de juegos independiente que usa a Blender como complemento para el modelado, texturizado, animación y composición de escenas. Además, enriquece lo anterior con herramientas relacionadas como ArmorPaint (para pintura de texturas PBR) y ArmorLab (para creación de texturas con IA), ambas integradas con el motor Armory.

Basa su editor de lógica en scripts de lenguaje Haxe. Algunos dicen que este lenguajes es más flexible que el Python utilizado en BGE. También ofrece un editor visual basado en nodos, a los que recientemente se han añadido uevos nodos para interpolación (tweening) de valores, vectores y rotaciones, nodos de dibujo de cámara, nodos de pulso para controlar tasas de disparo, y mejoras en la jerarquía de nodos lógicos.

A nivel multimedia, este engine se apoya en el framework Kha. Utiliza un renderizador físicamente basado, compatible con pipelines HDR, sombreado PBR y efectos de posprocesamiento, todo ello de forma muy personalizable.

Al igual que BGE, utiliza Bullet Physics, como motor de físicas, y soporta cuerpos rígidos, colisiones y físicas blandas. También permite configurar el motor de físicas Oimo como alternativa. Además, ofrece soporte para sistemas de partículas (emisor y cabello) con instanciación por GPU para mejorar el rendimiento

Armory 3D tiene mayor compatibilidad para exportar tu juego a diferentes plataformas y ofrecer un rendimiento optimizado de las animaciones. Actualmente soporta Windows, macOS, Linux, Web, Android, iOS y consolas (con configuraciones específicas).

A diferencia de los anteriores, Armory 3D está impulsado principalmente por un único desarrollador (Lubos Lenco) y eso se nota en que las actualizaciones llegan con cuenta gotas. Aun así, poco a poco se van incrementando las colaboraciones y aportaciones externas conformando un primigenio núcleo de desarrolladores.

En cuanto a la licencia, una ventaja respecto a los herederos directos de BGE es que Armory 3D usa la licencia Zlib, más permisiva que la GPL de Blender, permitiendo mayor flexibilidad para proyectos comerciales.

Conclusiones

Como puedes ver, el desarrollo de juegos con Blender está muy vivo, y no sólo usándolo como herramienta complementaria de modelado y animación, sino como un engine en si mismo.

A día de hoy no ofrece un rendimiento equiparable al que se puede obtener con engines dedicados como Unity, Unreal o Godot Engine, pero la posibilidad de poder concentrar en una misma herramienta tanto la programación como el modelado, texturizado y rigging de assets, puede simplificarle mucho la vida a los que quieran iniciarse en el mundo de los videojuegos. Sin contar con que lenguajes como Python o Haxe son realmente sencillos para aprender a implementar los algoritmos típicos de un juego.

Mi consejo, si quieres iniciarte en este tipo de engines, es que empieces con UPBGE y luego pruebes los otros dos, a ver qué valor diferencial te pueden ofrecer.