martes, 29 de septiembre de 2015

El control del tiempo en MotoGT2 (parte 1)

La gestión de los FPS en un juego es mucho más compleja de lo que parece a primera vista. Para MotoGT 1 usé la técnica más simple y menos elegante: framerate constante. Para MotoGT 2 empezé tratando de independizar la velocidad de la física de la del rendering, usando para lo primero un velocidad constante, pero implementando el código de forma tal que esa constante pudiera ser ajustada luego. Pero ahora la cosa mutó, y resulta que al final el framerate ya no es constante, para ninguna de las dos. Hubo que resolver algunos conflictos, pero la inversión pagó, la diferencia se nota mucho más de lo que había imaginado.

En esta primera parte, un poco de "marco teórico". Supongamos que queremos "mover" una pelotita por la pantalla. ¿Cómo se logra el efecto de movimiento? Fácil, redibujando muy rápidamente la pantalla, muchas veces por segundo, haciendo que el dibujo de la pelotita aparezca levemente corrido en cada redibujado respecto al anterior. Cada "redibujado", a partir de ahora rendering, compone un "frame" (cuadro). Por esto, se le llama framerate (medido en FPS, cuadros por segundo) a la velocidad del redibujado. Si redibujo 2 veces por segundo, parece que la pelota se teletransporta en lugar de moverse. Necesito unas 10 veces más que eso para dar una ilusión de movimiento más razonable. Pero, para que la cosa se sienta realmente bien, necesito todavía más.

Animación de movimiento a aprox. 10 FPS

En MotoGT 1 usé un framerate constante de 30 FPS. Se ve suficientemente bien, uno no puede pensar mucho en 1/30 segundos como para evitar que el cerebro asuma esos cambios como verdaderos movimientos fluidos. Sin embargo, si uno compara con una animación a 60 FPS notará que esta segunda se "siente" mejor, aunque no sepamos detallar por qué. Es que estamos alrededor de los límites del ojo y del cerebro. Es decir, ¿cuanto le lleva a nuestro sistema percibir un pequeño cambio? Digamos que para un humano "promedio" será entre 1/45 y 1/60 segundos (no hay consenso total, pero estaría en ese orden). Pero como varía de persona en persona (un piloto de caza entrenado puede pasar los 1/200), y además el cerebro no está sincronizado con el monitor (ni siquiera trabaja "por frames"), tenemos que usar un framerate un poco más alto para asegurarnos que no se note.

La misma velocidad de la pelota que antes, pero animada a 30 FPS

Pero, un momento. Entonces ¿por qué no usé 60FPS en MotoGT 1? Pues bien, porque cuanto más suba los FPS más "cálculos" tiene que hacer la PC para lograrlo. Y generalmente los del rendering son los más complejos. Obligar a la PC a renderizar muchos cuadros por segundo para poder jugar es limitar la cantidad de PCs en las que se podrá jugar. Una PC vieja o con un driver de video inadecuado tal vez no pueda mantener el ritmo. Por eso elegí un FPS mínimo para que el moviemiento sea suficientemente fluido, y no más, para que el juego corra en casi cualquier PC vieja. Además, en su momento pensé que la diferencia no sería tan perceptible (prueben este link que encontré en wikipedia para tratar de ver la diferencia)

¿Y cómo entra en esta historia la física del juego? Pues bien, en MotoGT 1 estaba directamente atada al rendering. Es decir, si renderizo 30 veces por segundo, tengo que calcular 30 veces por segundo los movimientos, y cada cálculo aplica el movimiento equivalente a 1/30 segundos. Razonable, ¿no?... Más o menos. Sí que es lo más simple, pero no lo mejor. Por un lado, que la física siempre avance a pasos de 1/30s simplifica todos los cálculos. Digamos que si un objeto se mueve a velocidad constante, en 1/30s siempre se mueve la misma distancia D, y entonces para actualizar la posición solo tengo que sumarle un constante. Si fuera 1/60s, para la misma velocidad, debería sumar D/2, y si fuera 1/15s, 2*D. Y así. Pero si fuera 1/Xs. entonces, debería sumarle 30/X*D. Hacerlo genérico entonces es apenas más complejo, no es tanto lío, ¿no?

Ahora bien, esa D era en realida la velocidad (distancia/30 segundos). Supongamos ahora que en cada frame a 1/30s reduzco la velocidad al 90%. ¿Cómo cambia ahora esta ecuación? Ya no es para nada trivial hacer la equivalencia con otras tazas de actualización, intenten si quieren encontrarla. Y si los cálculos se complican, todo se complica y se vuelve más "pesado". Pero hay cosas peores. Digamos que en cada cuadro de esos a 1/30s cambio aleatoriamente la dirección de la moto en como mucho 5 grados (como cuando va por la leca), con igual probabilidad para cada cambio (un rand entre -5 y 5). A 1/30, los 5 grados se notan, es un cambio suficientemente brusco. A 1/300 no se nota nada, porque el cambio equivalente por frame para lograr la misma velocidad de cambio es ahora medio grado, y porque la estadística hace que a lo largo de muchos frames el movimiento "promedio" sea 0. Ya nunca llego a ver un cambio de 5 grados como antes. Pues bien, la física y la lógica de MotoGT 1 están llenas de estas cosas, y por eso pasarlas de MotoGT 1 y su framerate variable al nuevo motor no está siendo nada trivial.

Cosas raras que pueden pasar si la "física" se actualiza con saltos de tiempo muy grandes

Para cerrar con esto de los framerates, si la física se actualiza a la misma velocidad que el dibujado, pero dejamos que esa velocidad sea variable, de forma que en una PC lenta sea menos veces por segundo que en una PC rápida, pueden pasar cosas muy raras. Antes daba un ejemplo de un objeto que se mueve a velocidad constante. ¿Qué pasa con la detección de colisiones? ¿Cómo me doy cuenta si se choca con otro? Si en cada posición que toma, pregunto si se choca con otro, tengo un problema: si se mueve demasiado rápido, puede que en un frame esté por ejemplo justo antes de darse contra el muro, y en el siguiente aparezca del otro lado del muro, y entonces no detecto la colisión, porque no tocó un frame justo en el medio como para que me de cuenta. Esto quiere decir, que si verifico colisiones en cada frame individualmente, me tengo que asegurar de que nadie se mueva demasiado rápido, en comparación con el framerate. Esto impone entonces un framerate mínimo para la física, y si la física está atada al rendering, un framerate mínimo para el rendering. Una alternativa sería que en cada frame verifique toooda la trayectoria que el objeto hizo desde el frame anterior, pero entonces los cálculos de colisiones se complicarían muchísimo más.

Como dije al principio, en MotoGT 2 empecé separando las velocidades de física y rendering, para asegurarme un buen mínimo de la primera, y dejar más o menos libre la segunda. La idea era al principio seguir con la de la física constante para mantener los cálculos simples, pero finalmente terminé con un sistema donde ambas son variables y bastante independientes. En la segunda parte, les cuento los detalles de la solución, y sus problemas de implementación.

No hay comentarios:

Publicar un comentario