En el desarrollo de videojuegos modernos, la gestión eficiente de colisiones entre entidades es crucial para lograr una experiencia jugable fluida y realista. ECS Survivors, un proyecto basado en el paradigma de Entidad-Componente-Sistema (ECS), presenta un enfoque meticuloso y científicamente analizado para implementar sistemas de colisión efectivos y optimizados. En esta cuarta entrega de la serie, se profundiza en los distintos métodos para detectar y resolver colisiones, sus desafíos y soluciones, así como en las ventajas de integrar texturas y optimizaciones gráficas para un rendimiento superior. Para empezar, es importante entender que las colisiones se dividen en dos fases fundamentales: la detección y la resolución. La detección se encarga de identificar cuándo dos o más entidades entran en contacto o se superponen, mientras la resolución se encarga de corregir dichas superposiciones evitando comportamientos poco realistas o indeseados.
Dado que en ECS Survivors los cuerpos de colisión son exclusivamente circulares, la ecuación para detección se simplifica considerablemente, permitiendo comparaciones basadas en distancias entre los centros de los círculos y sus radios combinados. La generación de enemigos o entidades con cuerpos de colisión también es un aspecto esencial para poner a prueba el sistema. Se implementa un módulo de juego específico en ECS que periódicamente crea enemigos en los límites de la pantalla, alternando el eje de aparición para una distribución semi-aleatoria y equilibrada. Esto asegura que el mundo de juego no solo esté dinámico, sino que también las colisiones sean frecuentes y variadas, poniendo a prueba la robustez y rendimiento de los algoritmos implementados. Un detalle fascinante de esta implementación es la elección de usar texturas en lugar de simples formas geométricas para representar las entidades.
Más allá de la estética, esta decisión impacta positivamente en el rendimiento, ya que frameworks como Raylib optimizan el dibujo de texturas en lotes, minimizando la comunicación entre la CPU y la GPU y reduciendo la sobrecarga. Esta optimización es especialmente valiosa cuando se manejan cientos o miles de entidades simultáneamente, evitando cuellos de botella gráficos. Entrando en las distintas técnicas para gestionar colisiones, se destacan principalmente tres métodos que emplean un ECS puro y que comparten una complejidad temporal de orden cuadrático, es decir, O(n^2). Cada uno presenta ventajas y desafíos particulares. El primero se basa en la utilización de relaciones en ECS para marcar qué entidades han colisionado entre sí.
Aunque conceptualmente intuitivo, este método sufre de fragmentación de arquetipos, lo que implica que se generan muchas tablas pequeñas y diversas, afectando negativamente el rendimiento al dificultar el proceso de unión de estas tablas fragmentadas. La segunda técnica trata de mitigar esta fragmentación creando nuevas entidades que representan registros de colisión. Aunque mejora el rendimiento respecto al método de relaciones, sigue teniendo la desventaja de gestionar la sobrecarga de crear y eliminar miles de entidades por frame, lo que introduce una latencia innecesaria y potencialmente costosa en términos computacionales. La tercera y definitiva técnica consiste en almacenar todos los registros de colisión detectados en una lista centralizada contenida en un componente singleton. Este acercamiento elimina la necesidad de crear entidades adicionales para cada colisión y reduce drásticamente la fragmentación de arquetipos, más que duplicando la eficiencia de los métodos anteriores.
Además, este método se adapta bien al límite empírico de la cantidad máxima razonable de entidades activas en pantalla, asegurando estabilidad y escalabilidad. Más allá de estas técnicas puras de ECS, el artículo menciona dos estrategias adicionales pero no detalladas: el hashing espacial y el uso de la biblioteca Box2d. El hashing espacial funciona dividiendo el espacio en regiones o células, permitiendo que las comprobaciones de colisión solo se realicen entre entidades que estén en la misma región o en regiones vecinas, reduciendo así la cantidad total de comparaciones. Box2d, por otro lado, es una biblioteca física ampliamente reconocida que utiliza estructuras de datos avanzadas como árboles AABB dinámicos para lograr consultas con complejidad logarítmica, lo que representa una mejora significativa frente al método de fuerza bruta empleado en los primeros métodos. Sin embargo, dado el alcance y las necesidades específicas del proyecto ECS Survivors, la optimización a través del almacenamiento de colisiones en una lista global centralizada ha resultado ser la solución ideal en términos de balance entre rendimiento y simplicidad.
El siguiente desafío que plantea este sistema es cómo hacer que las entidades reaccionen a las colisiones detectadas. Para ello, se utiliza un sistema híbrido que combina el método eficiente de registro en lista con la notificación basada en relaciones, con condiciones que reducen la sobrecarga. Específicamente, la relación CollidedWith solo se añade entre entidades de diferentes capas de colisión, por ejemplo, entre el jugador y enemigos, evitando interacciones redundantes como las colisiones entre enemigos. Esta solución pragmática permite implementar mecánicas de juego como el daño mutuo fácilmente. Al aprovechar el paradigma ECS, un sistema reacciona ante la presencia simultánea de componentes Colisionado, Daño y Salud, aplicando probablemente un componente temporal de TakeDamage para luego disminuir efectivamente la salud y manejar la destrucción o supervivencia de la entidad.
En cuanto a la fragmentación, un problema inherente en los sistemas ECS cuando se añaden o retiran componentes frecuentemente, se menciona una mejora técnica destinada a mitigar este problema: la eliminación periódica de tablas vacías que puedan haberse generado por la dinámica de componentes como CollidedWith. Aunque actualmente existe esta solución temporal, los desarrolladores de Flecs están trabajando en una funcionalidad llamada DONTFRAGMENT para manejar esta cuestión de forma nativa en el motor. Por último, también se ha llevado a cabo una mejora en el sistema de renderizado para reflejar la transición de formas simples a texturas más elaboradas. Se ha consolidado el sistema mediante un componente Renderable que contiene información crucial como la textura, transformaciones visuales y el tinte. Este sistema asocia las entidades visibles con sus posiciones y aspectos visuales y utiliza un sistema ordenado por prioridades para su dibujo correcto en pantalla.