Escribo un juego simple, y quiero limitar mi framerate a 60 fps sin hacer que el loop se coma mi CPU. ¿Cómo haría esto?¿Cómo puedo limitar mi framerate a 60 fps en Java?
Respuesta
Puede leer el Game Loop Article. Es muy importante que primero comprenda las diferentes metodologías para el ciclo del juego antes de intentar implementar algo.
Artículo fácil de entender. – mfx
La respuesta simple es establecer un java.util.Timer para disparar cada 17 ms y hacer su trabajo en el evento del temporizador.
¿Qué sucede si se requieren más de 17 ms para finalizar el trabajo en el evento del temporizador? – cherouvim
@cherouvim: Entonces estás sobrecargado y tu velocidad de fotogramas baja. Todavía está trabajando lo más rápido posible, pero no más rápido que 60 fps. –
Así es como lo hice en C++ ... Estoy seguro de que puedes adaptarlo.
void capFrameRate(double fps) {
static double start = 0, diff, wait;
wait = 1/fps;
diff = glfwGetTime() - start;
if (diff < wait) {
glfwSleep(wait - diff);
}
start = glfwGetTime();
}
Simplemente llámelo con capFrameRate(60)
una vez por lazo. Dormirá, por lo que no pierde preciosos ciclos. glfwGetTime()
devuelve el tiempo desde que el programa comenzó en segundos ... Estoy seguro de que puede encontrar un equivalente en Java en alguna parte.
En Java, el equivalente de glfwGetTime() sería System.currentTimeMillis(). Devuelve el tiempo en milisegundos desde el 1 de enero de 1970. Cuando se usa así, hace más o menos lo mismo, excepto que devuelve el tiempo en milisegundos. – Manitobahhh
En java puede hacer System.currentTimeMillis()
para obtener el tiempo en milisegundos en lugar de glfwGetTime()
.
Thread.sleep(time in milliseconds)
hace que el hilo espere en caso de que no lo sepa, y debe estar dentro de un bloque try
.
Lo que he hecho es seguir recorriendo y hacer un seguimiento de la última vez que hice una animación. Si ha sido al menos 17 ms, entonces sigue la secuencia de animación.
De esta forma pude verificar si hay entradas de usuario y activar/desactivar notas musicales según sea necesario.
Pero, esto fue en un programa para ayudar a enseñar música a niños y mi aplicación estaba acaparando la computadora en que era pantalla completa, por lo que no funcionaba bien con los demás.
Si está utilizando Java Columpio la forma más sencilla de lograr un FPS 60 es mediante la creación de un javax.swing.Timer como este y es una forma común de hacer que los juegos:
public static int DELAY = 1000/60;
Timer timer = new Timer(DELAY,new ActionListener() {
public void actionPerformed(ActionEvent event)
{
updateModel();
repaintScreen();
}
});
Y en algún lugar de su código debe establecer este temporizador para repetir y iniciarlo:
timer.setRepeats(true);
timer.start();
Cada segundo tiene 1000 ms y dividiendo esto con su fps (60) y la configuración del temporizador con este retraso (1000/60 = 16 ms redondeada abajo) obtendrás una tasa de cuadros fija. Digo "algo" porque esto depende en gran medida de lo que haga en las llamadas a updateModel() y repaintScreen() anteriores.
Para obtener resultados más predecibles, intente sincronizar las dos llamadas en el temporizador y asegúrese de que finalicen en 16 ms para mantener 60 fps. Con la preasignación inteligente de memoria, reutilización de objetos y otras cosas, también puede reducir el impacto del recolector de basura. Pero eso es por otra pregunta.
El problema con los temporizadores es comparable al de utilizar Thread.Sleep ... no hay garantía de que el retraso sea exacto. En general, el RETARDO real es impredecible y posiblemente sea un poco más largo que lo que pasó como parámetro DELAY. –
El temporizador es impreciso para controlar FPS en java. Lo he descubierto de segunda mano. Tendrá que implementar su propio temporizador o hacer FPS en tiempo real con limitaciones. Pero no use el temporizador ya que no es 100% exacto y, por lo tanto, no puede ejecutar las tareas correctamente.
que tomaron la Game Loop Article que @cherouvim publicada, y yo llevamos a la estrategia de "mejor" y trató de volver a escribir para una java Ejecutable, parece estar funcionando para mí
double interpolation = 0;
final int TICKS_PER_SECOND = 25;
final int SKIP_TICKS = 1000/TICKS_PER_SECOND;
final int MAX_FRAMESKIP = 5;
@Override
public void run() {
double next_game_tick = System.currentTimeMillis();
int loops;
while (true) {
loops = 0;
while (System.currentTimeMillis() > next_game_tick
&& loops < MAX_FRAMESKIP) {
update_game();
next_game_tick += SKIP_TICKS;
loops++;
}
interpolation = (System.currentTimeMillis() + SKIP_TICKS - next_game_tick
/(double) SKIP_TICKS);
display_game(interpolation);
}
}
Es el esqueleto de la mejor forma de hacer un bucle de juego, pero no explica mucho, ¿verdad? ¿Tal vez agregar algunos puntos cortos que explican las cosas? –
Así es como lo hago. Ok, entonces hacemos un bucle, y si el tiempo pasado entre iteraciones de bucle es inferior al salto, simplemente volvemos a buclear. Si ha saltado o ha transcurrido más tiempo, llamamos al motor principal del juego y solicitamos el siguiente fotograma. Me gusta dibujar todo el fotograma como una imagen y enviarlo al método de pintura. En mi clase de juego, me gusta calcular el máximo frame por segundo en caso de que el jugador quiera mostrar un FPS real, que cuando configuro un máximo de fps, el real siempre es un fotograma menor que el establecido, es decir, el 60 es 57. –
He aquí un consejo para el uso de oscilación y obtener FPS razonablemente preciso sin usar un temporizador.
En primer lugar, no ejecute el bucle del juego en el hilo del asignador de eventos, debe ejecutarse en su propio hilo haciendo el trabajo duro y no interponiéndose en el camino de la IU.
El componente Swing mostrar el juego debe dibujarse a sí mismo anulando este método:
@Override
public void paintComponent(Graphics g){
// draw stuff
}
El problema es que el bucle del juego no sabe cuándo el hilo de eventos distribuidor de hecho ha regresado a la realización de la acción, porque la pintura en el ciclo de juego, simplemente llamamos a component.repaint(), que solicita una pintura solo que pueda suceder más tarde.
Usando wait/notify se puede obtener el método de redibujado para esperar hasta que la pintura solicitada haya sucedido y luego continuar.
Aquí está el código completo de trabajo de https://github.com/davidmoten/game-exploration que hace un simple movimiento de una cuerda a través de un JFrame utilizando el método anterior:
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Image;
import javax.swing.JFrame;
import javax.swing.JPanel;
/**
* Self contained demo swing game for stackoverflow frame rate question.
*
* @author dave
*
*/
public class MyGame {
private static final long REFRESH_INTERVAL_MS = 17;
private final Object redrawLock = new Object();
private Component component;
private volatile boolean keepGoing = true;
private Image imageBuffer;
public void start(Component component) {
this.component = component;
imageBuffer = component.createImage(component.getWidth(),
component.getHeight());
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
runGameLoop();
}
});
thread.start();
}
public void stop() {
keepGoing = false;
}
private void runGameLoop() {
// update the game repeatedly
while (keepGoing) {
long durationMs = redraw();
try {
Thread.sleep(Math.max(0, REFRESH_INTERVAL_MS - durationMs));
} catch (InterruptedException e) {
}
}
}
private long redraw() {
long t = System.currentTimeMillis();
// At this point perform changes to the model that the component will
// redraw
updateModel();
// draw the model state to a buffered image which will get
// painted by component.paint().
drawModelToImageBuffer();
// asynchronously signals the paint to happen in the awt event
// dispatcher thread
component.repaint();
// use a lock here that is only released once the paintComponent
// has happened so that we know exactly when the paint was completed and
// thus know how long to pause till the next redraw.
waitForPaint();
// return time taken to do redraw in ms
return System.currentTimeMillis() - t;
}
private void updateModel() {
// do stuff here to the model based on time
}
private void drawModelToImageBuffer() {
drawModel(imageBuffer.getGraphics());
}
private int x = 50;
private int y = 50;
private void drawModel(Graphics g) {
g.setColor(component.getBackground());
g.fillRect(0, 0, component.getWidth(), component.getHeight());
g.setColor(component.getForeground());
g.drawString("Hi", x++, y++);
}
private void waitForPaint() {
try {
synchronized (redrawLock) {
redrawLock.wait();
}
} catch (InterruptedException e) {
}
}
private void resume() {
synchronized (redrawLock) {
redrawLock.notify();
}
}
public void paint(Graphics g) {
// paint the buffered image to the graphics
g.drawImage(imageBuffer, 0, 0, component);
// resume the game loop
resume();
}
public static class MyComponent extends JPanel {
private final MyGame game;
public MyComponent(MyGame game) {
this.game = game;
}
@Override
protected void paintComponent(Graphics g) {
game.paint(g);
}
}
public static void main(String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
MyGame game = new MyGame();
MyComponent component = new MyComponent(game);
JFrame frame = new JFrame();
// put the component in a frame
frame.setTitle("Demo");
frame.setSize(800, 600);
frame.setLayout(new BorderLayout());
frame.getContentPane().add(component, BorderLayout.CENTER);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
game.start(component);
}
});
}
}
Creo que cualquier bucle de juego que use [Thread.Sleep] (https://docs.oracle.com/javase/7/docs/api/java/lang/Thread.html#sleep%28long,%20int%29) está dañado . De la documentación: 'Hace que el hilo que se está ejecutando se duerma [...] durante el número especificado de milisegundos más el número especificado de nanosegundos, ** sujeto a la precisión y exactitud de los temporizadores y planificadores del sistema **. No hay garantía de que el hilo realmente duerma durante el número de milisegundos + nanos que solicite. –
Estoy de acuerdo en que el uso de 'Thread.sleep' no es ideal. Las técnicas sin bloqueo serían más agradables.No estoy seguro de que pueda hacer algo mejor que la precisión que ofrece el 'Thread.sleep' con una técnica de no bloqueo aunque usando un' ScheduledExecutorService'. ¿Qué piensas? –
IMO el mejor es un ciclo similar al publicado por Parth Mehrotra, es decir, uno que no depende de dormir o algo similar. –
Hay un montón de buena información aquí ya, pero quería compartir un problema que tuve con estas soluciones, y la solución a eso. Si, a pesar de todas estas soluciones, su juego/ventana parece omitirse (especialmente bajo X11), intente llamar al Toolkit.getDefaultToolkit().sync()
una vez por cuadro.
- 1. Mi FPS limitado: 60
- 2. .NET ¿Volver a dibujar a 60 FPS?
- 3. iOS AVFoundation - Conversión de vídeo en imágenes a 60 fps
- 4. Desarrollo del juego: ¿cómo limitar el FPS?
- 5. Capturar 60 fps en la aplicación para iPhone
- 6. ¿por qué 24 * 60 * 60 * 1000 * 1000 dividido por 24 * 60 * 60 * 1000 no es igual a 1000 en Java?
- 7. C, GTK: muestra la secuencia de imágenes RGB a <60 fps
- 8. Enganches de Kindle Fire y corrupción de texturas a 60 fps
- 9. FPS cómo calcular esto?
- 10. ¿Cómo puedo limitar Parallel.ForEach?
- 11. (360/24)/60 = 0 ... en Java
- 12. Java: limitar a clases de jerarquía?
- 13. GUI funcionando a 30 fps?
- 14. ¿Verificar FPS en JS?
- 15. ¿Cómo puedo limitar una fase de compilación "Ejecutar script" a mi configuración de lanzamiento?
- 16. ¿Cómo puedo limitar los caracteres en UITextView?
- 17. ¿Cómo puedo proteger mi aplicación web java?
- 18. XNA Platformer (2D) - Frameraterate/FPS fluctuations
- 19. En MongoDB ¿cómo puedo limitar la consulta, cuando mi devolución de llamada está dentro de "buscar"?
- 20. ¿Cómo puedo ver qué hay en mi montón en Java?
- 21. ¿Cómo se calcula el FPS en OpenGL?
- 22. ¿Cómo calcular correctamente FPS en XNA?
- 23. Llamar a una función cada 60 segundos
- 24. Comportamiento extraño con google chrome y FPS
- 25. ¿Cómo puedo limitar qué países pueden ver mi sitio web (PHP)
- 26. ¿Cómo puedo limitar el número de subprocesos activos en Python?
- 27. En tiempo de ejecución, ¿cómo puedo limitar el número de subprocesos java
- 28. ¿Cómo importo jar a mi programa java?
- 29. ¿Coordenadas de mouse de alta resolución y alto framerate en OSX? (¿O alguna otra solución?)
- 30. ¿Cómo puedo agregar mi spinner a ActionBar?
No codificar 60 FPS, determine la frecuencia de actualización real de la pantalla. 60 FPS serán espasmódicos por ej. un monitor de 85 Hz. –