Estoy experimentando con la construcción de un motor de juego desde cero en Java, y tengo un par de preguntas. Mi bucle principal del juego se ve así:
int FPS = 60;
while(isRunning){
/* Current time, before frame update */
long time = System.currentTimeMillis();
update();
draw();
/* How long each frame should last - time it took for one frame */
long delay = (1000 / FPS) - (System.currentTimeMillis() - time);
if(delay > 0){
try{
Thread.sleep(delay);
}catch(Exception e){};
}
}
Como puede ver, he establecido la velocidad de fotogramas en 60FPS, que se utiliza en el delay
cálculo. El retraso asegura que cada cuadro tome la misma cantidad de tiempo antes de procesar el siguiente. En mi update()
función hago lo x++
que aumenta el valor horizontal de un objeto gráfico que dibujo con lo siguiente:
bbg.drawOval(x,40,20,20);
Lo que me confunde es la velocidad. cuando configuré FPS
150, el círculo procesado atraviesa la velocidad realmente rápido, mientras que establece FPS
30 movimientos en la pantalla a la mitad de la velocidad. ¿La velocidad de fotogramas no solo afecta la "suavidad" del renderizado y no la velocidad de los objetos que se renderizan? Creo que me falta una gran parte, me encantaría algunas aclaraciones.
fuente
1000 / FPS
división podría hacerse y el resultado asignado a una variable antes de suwhile(isRunning)
ciclo. Esto ayuda a ahorrar un par de instrucciones de CPU para hacer algo más de una vez inútilmente.Respuestas:
Estás moviendo el círculo un píxel por fotograma. No debería ser una gran sorpresa que, si su ciclo de renderizado se ejecuta a 30 FPS, su círculo se moverá 30 a píxeles por segundo.
Básicamente tiene tres formas posibles de tratar este problema:
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í:
y, dentro
update()
, ajustando las fórmulas físicas para tener en cuenta el paso de tiempo variable, por ejemplo, así: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í:
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 aldraw()
método, por ejemplo, así: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 eldraw()
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!).Por supuesto, también es posible combinar estos métodos de varias maneras. Por ejemplo, en un juego multijugador cliente-servidor, es posible que el servidor (que no necesita dibujar nada) ejecute sus actualizaciones en un paso de tiempo fijo (para una física consistente y una capacidad de reproducción exacta), mientras hace que el cliente realice actualizaciones predictivas (para ser anulado por el servidor, en caso de desacuerdo) en un intervalo de tiempo variable para un mejor rendimiento. También es posible mezclar de manera útil la interpolación y las actualizaciones de paso de tiempo variable; por ejemplo, en el escenario cliente-servidor que se acaba de describir, realmente no tiene mucho sentido que el cliente use tiempos de actualización más cortos que el servidor, por lo que puede establecer un límite inferior en el paso de tiempo del cliente e interpolar en la etapa de dibujo para permitir mayores FPS
(Editar: Código agregado para evitar intervalos / recuentos de actualizaciones absurdos, en caso de que, por ejemplo, la computadora esté temporalmente suspendida o congelada durante más de un segundo mientras se ejecuta el ciclo del juego. Gracias a Mooing Duck por recordarme la necesidad de eso .)
fuente
updateInterval
es solo la cantidad de milisegundos que desea entre las actualizaciones de estado del juego. Para, digamos, 10 actualizaciones por segundo, estableceríasupdateInterval = (1000 / 10) = 100
.currentTimeMillis
No es un reloj monótono. En sunanoTime
lugar, úselo, a menos que desee que la sincronización de tiempo de la red altere la velocidad de las cosas en su juego.while(lastTime+=updateInterval <= time)
. Sin embargo, eso es solo un pensamiento, no una corrección.Su código se está ejecutando cada vez que se procesa un marco. Si la velocidad de fotogramas es mayor o menor que la velocidad de fotogramas especificada, sus resultados cambiarían ya que las actualizaciones no tienen el mismo tiempo.
Para resolver esto, debe consultar Delta Timing .
Para hacer esto:
Luego deberá multiplicar el tiempo delta por el valor que desea cambiar por tiempo. Por ejemplo:
fuente
time()
devuelve el mismo dos veces, no desea errores div / 0 y procesamiento desperdiciado.Esto se debe a que limita su velocidad de fotogramas, pero solo realiza una actualización por fotograma. Así que supongamos que el juego se ejecuta en el objetivo de 60 fps, obtienes 60 actualizaciones lógicas por segundo. Si la velocidad de cuadros baja a 15 fps, solo tendría 15 actualizaciones lógicas por segundo.
En su lugar, intente acumular el tiempo de fotograma pasado hasta ahora y luego actualice la lógica de su juego una vez por cada intervalo de tiempo que haya pasado, por ejemplo, para ejecutar su lógica a 100 fps, ejecutaría la actualización una vez por cada 10 ms acumulados (y reste esos mostrador).
Agregue una alternativa (mejor para imágenes) actualice su lógica en función del tiempo transcurrido.
fuente