lunes, 26 de octubre de 2015

...se encienden las ilusiones

Si realmente te gustan las carreras de motos, ya sabías cómo se iba a titular este segundo post. Se nos pone la piel de gallina cada vez que Martín Urruty pronuncia estas palabras un domingo a la mañana. Pero más allá del pequeño homenaje al relator (y extiendo mi simpatía también a su genial compañero Gustavo Morea), la idea es seguir hablando de cómo avanza MotoGT 2. En el post anterior comentaba que la escena principal (la carrera) ya empezaba a tomar forma, con varios cambios en el Game, y ya casi sin trabajo del lado del Engine y su Bridge. Sin embargo, sí hubo otro pequeño trabajo en el Engine, y queda por hacer uno más grande en el bridge. Hoy les cuento de qué se trata y de paso les traigo otro video.

El cambio más grande tiene que ver con optimizaciones. Hace poco empecé a usar la herramienta "perf" para profiling, y estoy maravillado con su potencia. Básicamente, utiliza servicios del kernel de GNU/Linux para acceder a los performance counters del microprocesador. Estos son, como su nombre lo indica, contadores, que instrumentados directamente en el hardware, cuentan ciertos eventos relevantes para medir pérformance. Seguramente vendrá otro post más detallado al respecto, pero en relación a MotoGT, confirmó algo que era obvio y me temía que iba a pasar: los sistemas de particulas matan el rendimiento del juego. El gráfico de abajo (generado con flamegraph) prueba lo que digo.

Información de profiling del bucle principal de MotoGT 2 durante una carrera.
Se resaltan los dos sistemas de partículas críticos para el tiempo de renderizado.

El eje Y se puede leer como un callstack, y en el eje X cada función se estira en proporción al tiempo que consume. Los sistemas de partículas se llevan más de la mitad del tiempo de ejecución en cada frame. Era obvio, porque los estoy renderizando a través del mecanismo de sprites genérico, y entonces cada sistema genera O(1000) pequeños sprites. Tendría que pasar a algo que requiera menos draw-calls, como usar vertex arrays, vertex buffer objects, o como quiera que se lo llame en el bridge de turno. Por suerte SFML tiene soporte para algo de eso, pero requerirá de trabajo adicional y servicios ad-hoc en el bridge, así que quedará para el final.

El otro pequeño cambio, es simple, pero ridículamente útil. Ya había comentado que el Engine tiene una clase principal Main que gestiona el bucle de juego, a la cual solo hay que decirle cual es la escena actual. Si quiero combinar escenas, las escenas se pueden enlazar en cascada. Es decir, que la principal tenga una escena hija, que a su vez puede tener otra hija, y así recursivamente cuantas veces quiera. Piensen en una ventana principal, y otras ventanas emergentes saltando por sobre la misma (así funcionan los menúes). Pero agregué al Engine la posibilidad de definir una segunda escena para esta clase Main, a la que llamo "overlay", porque se dibuja como si fuera una capa extra, por sobre la principal.

Tiene dos usos. Por un lado, puede mostrar información tipo OSD en cualquier momento, sin importar la escena actual, o aún si estamos justo cambiando de escena. Creo haber visto por primera vez en Dave Mirra BMX, y más recientemente en la saga Need For Speed, que cuando cambiaba la música de fondo, sea en el momento que sea, en ambos juegos sale un pequeño pop-up indicando nombre y autor del tema. Pero mejor que eso, esta escena Overlay es increíblemente útil para hacer depuración visual. Tengo dos tipos básicos de overlay scenes implementadas: una que me permite incorporar texto arbitrariamente en una especia de minibuffer que siempre muestra las últimas 5 lineas; y otra que me permite dibujar puntos y segmentos con colores llamativos. El primero lo puedo usar para mensajes del Engine, como warnings, e informes de estado, o para volcar valores de variables en tiempo real. El segundo, durante la carrera, sirve para ver los puntos y vectores que utilizan la física y/o la inteligencia artificial. En el ejemplo del video se marca el checkpoint al que apunta una moto, y algunos puntos y vectores auxiliares importantes para el algoritmo que decide los controles.

Segunda vuelta, con una primer versión de la lluvia (todavía alpha), el semáforo y la bandera
a cuadros. Esta vez solo la IA (también en alpha), con su capa de debug visual.

Respecto al código, ya se está ensuciando. En algunas partes es inevitable, porque hay dos cosas puntuales de MotoGT 1 que quiero portar tal cual estaban, y por ahora no perder tiempo repensandolas: la física, y la inteligencia artificial. No son cosas menores, son dos algoritmos que por horribles que se ven, me requirieron muchísimo trabajo en su momento, y muchísimo tiempo de pruebas para ajustar unas cuantas constantes mágicas, o decidir cual era la mejor heurística. Así, que hay dos métodos desarrollados directamente como copy/paste de MotoGT 1, simplemente adaptando los nombres de variables a las nuevas convenciones, y actualizando tipos de datos. En relación al último punto, decidí implementar unos cuantos alias para tipos de datos básicos, de forma de expresar mejor en el código qué es cada cosa. Por ejemplo: Angle, Distance, Speed y Acceleraton son todos typedefs para "float". Es decir, estas unidades se guardan como floats, pero los typedefs (en realidad usings de C++11) permiten expresar más información en el código para hacer más entendibles los cálculos y sus razonamientos.

Todavía falta muchísimo. Y si bien avanza mejor de lo que pensaba, igual avanza lento. Tengo mil otras cosas que hacer, y este es el proyecto de menor prioridad (a pesar de ser uno de los más divertidos para trabajar), así que solo consume tiempos marginales. Pero de a poco se va acercando el día en que MotoGT 2 verá la luz. Espero que los adelantos les vayan dando ganas de jugarlo, porque esté o no a la altura de las espectativas, sin lugar a dudas el resultado será muy superior a MotoGT 1.

1 comentario: