Simplemente elija una velocidad de fotogramas y manténgala. Eso es lo que hicieron muchos juegos de la vieja escuela: se ejecutaban a una velocidad fija de 50 o 60 FPS, generalmente sincronizados con la frecuencia de actualización de la pantalla, y simplemente diseñaban su lógica de juego para hacer todo lo necesario dentro de ese intervalo de tiempo fijo. Si, por alguna razón, eso no sucediera, el juego solo tendría que saltarse un cuadro (o posiblemente bloquearse), disminuyendo efectivamente tanto el dibujo como la física del juego a la mitad de la velocidad.
En particular, los juegos que las funciones más utilizadas, como la detección de colisiones de sprites de hardware más o menos tenían que obra como esta, porque su lógica del juego estaba íntimamente ligada a la prestación, que se hizo en el hardware a una tasa fija.
Usa un paso de tiempo variable para la física de tu juego. Básicamente, esto significa reescribir su ciclo de juego para que se vea así:
long lastTime = System.currentTimeMillis();
while (isRunning) {
long time = System.currentTimeMillis();
float timestep = 0.001 * (time - lastTime); // in seconds
if (timestep <= 0 || timestep > 1.0) {
timestep = 0.001; // avoid absurd time steps
}
update(timestep);
draw();
// ... sleep until next frame ...
lastTime = time;
}
y, dentro update()
, ajustando las fórmulas físicas para tener en cuenta el paso de tiempo variable, por ejemplo, así:
speed += timestep * acceleration;
position += timestep * (speed - 0.5 * timestep * acceleration);
Un problema con este método es que puede ser complicado mantener la física (en su mayoría) independiente del paso de tiempo ; realmente no quieres que la distancia que los jugadores pueden saltar dependa de su velocidad de fotogramas. La fórmula que mostré anteriormente funciona bien para una aceleración constante, por ejemplo, bajo gravedad (y la que está en la publicación vinculada funciona bastante bien incluso si la aceleración varía con el tiempo), pero incluso con las fórmulas físicas más perfectas posibles, es probable que trabajar con flotadores produce un poco de "ruido numérico" que, en particular, puede hacer que las repeticiones exactas sean imposibles. Si eso es algo que cree que podría desear, puede preferir los otros métodos.
Desacople la actualización y dibuje los pasos. Aquí, la idea es que actualices el estado de tu juego usando un paso de tiempo fijo, pero ejecutes un número variable de actualizaciones entre cada cuadro. Es decir, su ciclo de juego podría verse así:
long lastTime = System.currentTimeMillis();
while (isRunning) {
long time = System.currentTimeMillis();
if (time - lastTime > 1000) {
lastTime = time; // we're too far behind, catch up
}
int updatesNeeded = (time - lastTime) / updateInterval;
for (int i = 0; i < updatesNeeded; i++) {
update();
lastTime += updateInterval;
}
draw();
// ... sleep until next frame ...
}
Para hacer que el movimiento percibido sea más suave, es posible que también desee que su draw()
método interpole cosas como las posiciones de los objetos sin problemas entre los estados del juego anterior y siguiente. Esto significa que debe pasar el desplazamiento de interpolación correcto al draw()
método, por ejemplo, así:
int remainder = (time - lastTime) % updateInterval;
draw( (float)remainder / updateInterval ); // scale to 0.0 - 1.0
También necesitaría que su update()
método realmente calcule el estado del juego un paso adelante (o posiblemente varios, si desea hacer una interpolación de splines de orden superior), y que guarde las posiciones de objetos anteriores antes de actualizarlas, para que el draw()
método pueda interpolar entre ellos. (También es posible extrapolar las posiciones pronosticadas en función de las velocidades y aceleraciones de los objetos, pero esto puede parecer desigual, especialmente si los objetos se mueven de manera complicada, lo que hace que las predicciones a menudo fallen).
Una ventaja de la interpolación es que, para algunos tipos de juegos, puede permitirle reducir significativamente la tasa de actualización de la lógica del juego, mientras mantiene una ilusión de movimiento suave. Por ejemplo, es posible que pueda actualizar su estado de juego solo, por ejemplo, 5 veces por segundo, mientras sigue dibujando de 30 a 60 cuadros interpolados por segundo. Al hacer esto, también puede considerar intercalar la lógica de su juego con el dibujo (es decir, tener un parámetro para su update()
método que le indique que solo ejecute x % de una actualización completa antes de regresar), y / o ejecutar la física del juego / lógica y el código de renderizado en hilos separados (¡cuidado con las fallas de sincronización!).