Tabla de contenido

Hola otra vez.
De nuevo, hace ya algunos meses que no escribo nada y la verdad que ya va siendo hora de aparecer con algo nuevo por aquí. En esta ocasión vamos a tratar un tema bastante interesante y al que le estoy dedicando últimamente mis esfuerzos.

Se trata de la decisión de cambiar todo el sistema de entidades de Project Creation FX, de herencia y polimorfismo al uso de sistema de componentes. Resumiendo un poco, Project Creation FX es un motor de juegos 2D, una herramienta que permite desarrollar prototipos y pequeños juegos de manera sencilla. Las entidades, dentro de este ámbito lo son todo: personajes, enemigos, partículas, los tiles del mapa, puertas, luces, y un largo etcétera. Es decir, cualquier cosa que forme parte de un juego es una entidad.

Herencia vs. composición

Inicialmente la idea de usar herencia y polimorfismo no parece tan mala idea. Todo son entidades, y a partir de ahí los diferentes objetos heredan los unos de los otros compartiendo características, variables y comportamientos. Si tomamos por ejemplo la clase de los enemigos, todos tiene vida, ¿verdad? De esta clase heredarán todos los tipos de enemigos que tenga el juego con su cantidad de vida correspondiente.

Usar estas técnicas no parece una mala idea, la lógica que controla todos los aspectos de niveles de vida está en una clase superior de la que heredan todos los monstruos de nuestro juego. Ahora piensa en los ciudadanos de las ciudades del juego. También tienen vida, pero no atacan al personaje, por lo que no son enemigos. Debemos mover toda la lógica a una clase superior que englobe a los ciudadanos y enemigos, llamémosla personajes no controlables. Bien, y ahora ¿qué pasa con el personaje del jugador? También tiene vida. ¿Volvemos a mover la lógica que controla la vida a una clase superior que englobe a personajes controlables y no controlables? Creo que entendéis ya la idea.

Cada vez que deseamos hacer un cambio, vemos que existen conflictos con las clases y es necesario realizar modificaciones en muchos objetos distintos. Aunque la herencia es útil, no parece que sea la mejor solución para resolver este problema.

Vamos a sacar toda la información de la vida de una entidad a una clase nueva. Esta clase almacenará la vida actual y la cantidad máxima de una entidad. A este tipo de clase la llamaremos componente. Toda la lógica relacionada con el componente, como curarse o recibir daño, estará en otra clase, a la que llamaremos sistema. La entidad estará compuesta de diferentes componentes y sistemas que interactúan los unos con los otros y dan la forma a esa entidad única.

Componentes de control, de colisiones, de renderizado, de sonido, de inventario, de movimiento,… Las opciones son casi infinitas. Cualquier lógica que se desee incluir a una entidad se le podrá añadir en forma de componente o sistema. Todo esto, añadido a la idea de modificar en caliente las entidades para añadirle, cambiarle o quitarle características hacen que esta solución parezca bastante mejor que la anterior.

Desde mi punto de vista, y según lo que estoy viviendo al aplicar este cambio al proyecto es que la complejidad aumenta en gran medida. No todo iba a ser bueno. Pero el nivel de reutilización de código se dispara. La idea de utilizar la orientación a objetos en un juego aparece de forma natural al enfrentarnos al problema, sin embargo, partir los objetos en partes más pequeñas e independientes puede resultar un poco más farragoso. Debemos de dejar de ver a las entidades como objetos con lógica, para pasar a verlos como contenedores de componentes y sistemas.

Opinión actual

Hasta ahora he notado ventajas enormes, por ejemplo, en los componentes de renderizado. Antes, indicaba que un objeto era renderizable usando una interfaz. Al implementarla debía crear las funciones para dibujar en pantalla la entidad correspondiente. Tenía el mismo código para pintar repetido en todos los proyectos y demos.

Ahora tengo un componente de dimensiones para almacenar el tamaño de la entidad, otro de posicionamiento para almacenar dónde se encuentra, y el componente de dibujado en pantalla consulta esos valores y después hace su trabajo. Existen diferentes componentes de renderizado, en función de lo que se quiera dibujar en pantalla: una elipse, un rectángulo, una imagen fija o una imagen que varía en función de la acción que esté realizando la entidad. Todos estos componentes y sistemas se encuentran en el núcleo del proyecto, de modo que son utilizables desde cualquier juego o demo que desarrolle.

La ventaja creo que se ve claramente. El código de los componentes solo existe una vez y es posible actualizar todas las entidades que lo usan de una sola tacada sin importar que estén en proyectos distintos. En caso de necesitar una versión anterior de un componente, siempre es posible recurrir a Maven para que nos eche un cable. O lo que es lo mismo, usar una versión anterior del motor para un juego concreto. Teniendo esto a nuestro alcance, sabemos que los juegos no tiene porqué ser actualizados conforme el motor evolucione. Además, siempre es posible heredar el comportamiento de un componente o sistema para realizar modificaciones sobre él, es decir, cada proyecto puede tener componentes propios que no se usen ningún otro proyecto.

Al aplicar esta solución, las entidades no son más que una lista de componentes y sus parámetros de personalización y configuración, siendo la norma que incluyan la mínima cantidad de lógica posible. Lo más cómodo sería poder acceder a los distintos componentes por su tipo, dado que no es posible saber si una entidad tiene ese componente o no hasta preguntarle. Además, pueden existir dependencias entre los componentes. Por ejemplo, el componente de renderizado, requiere los componentes de posicionamiento y dimensión para poder funcionar. En caso de no disponer de alguno de ellos, no podrá hacer su trabajo correctamente por falta de información. Estos problemas quizás los discutamos en otra entrada.

Por ahora, solo quería comentar en qué estoy metido y detallar un poco lo que estoy haciendo. Rehacer el sistema de colisiones con esta nueva estrategia tiene pinta de ser bastante más complejo de lo que pensaba, y es posible que requiera de más tiempo para alcanzar una solución aceptable.

Me despido y hasta la próxima.

Muchas gracias por leerme,
Lázarus Surazal.


Perfil
prLázarus logo info
Carlos J. Peláez Rivas (Lázarus Surazal)
Graduado y Máster en Ingeniería Informática por la Universidad de Málaga. Actualmente trabajando como desarrollador de aplicaciones en Java usando Vaadin.
Apasionado de los videojuegos, la música y alguna que otra tecnología, siempre buscando cosas nuevas que aprender y hacer.
Más sobre mi...
Contacto
Notificaciones