Game Loop
Propósito
Un bucle de juego se ejecuta continuamente durante la partida. En cada vuelta del bucle, procesa las entradas del usuario sin bloquearse, actualiza el estado del juego y lo renderiza. Realiza un seguimiento del paso del tiempo para controlar el ritmo de juego.
Este patrón desvincula la progresión del tiempo de juego de la entrada del usuario y de la velocidad del procesador.
Aplicabilidad
Este patrón se utiliza en todos los motores de juego.
Explicación
Ejemplo del mundo real
El bucle de juego es el proceso principal de todos los hilos de renderizado del juego. Está presente en todos los juegos modernos. Controla el proceso de entrada, la actualización del estado interno, el renderizado, la IA y todos los demás procesos.
En pocas palabras
El patrón de bucle de juego garantiza que el tiempo de juego progrese a la misma velocidad en todas las configuraciones de hardware diferentes.
Wikipedia dice
El componente central de cualquier juego, desde el punto de vista de la programación, es el bucle de juego. El bucle de juego permite que el juego se ejecute sin problemas, independientemente de la entrada de un usuario, o la falta de ella.
Ejemplo programático
Empecemos con algo sencillo. Aquí está la claseBullet
. Las balas se moverán en nuestro juego. Para propósitos de demostración es suficiente que tenga una posición unidimensional.
public class Bullet { private float position; public Bullet() { position= 0.0f; } public float getPosition() { return position; } public void setPosition(float position) { this.position = position; }}
ElGameController
es el responsable de mover los objetos del juego, incluida la mencionada bala.
public class GameController { protected final Bullet bullet; public GameController() { bullet= new Bullet(); } public void moveBullet(float offset) { var currentPosition = bullet.getPosition(); bullet.setPosition(currentPosition+ offset); } public float getBulletPosition() { return bullet.getPosition(); }}
Ahora introducimos el bucle de juego. O en realidad en esta demo tenemos 3 bucles de juego diferentes. Veamos primero la clase baseGameLoop
.
public enum GameStatus { RUNNING, STOPPED}public abstract class GameLoop { protected final Logger logger= LoggerFactory.getLogger(this.getClass()); protected volatile GameStatus status; protected GameController controller; private Thread gameThread; public GameLoop() { controller= new GameController(); status= GameStatus.STOPPED; } public void run() { status= GameStatus.RUNNING; gameThread= new Thread(this::processGameLoop); gameThread.start(); } public void stop() { status= GameStatus.STOPPED; } public boolean isGameRunning() { return status== GameStatus.RUNNING; } protected void processInput() { try { var lag = new Random().nextInt(200)+ 50; Thread.sleep(lag); }catch (InterruptedException e) { logger.error(e.getMessage()); } } protected void render() { var position = controller.getBulletPosition(); logger.info("Current bullet position: " + position); } protected abstract void processGameLoop();}
Aquí está la primera implementación del bucle de juego,FrameBasedGameLoop
:
public class FrameBasedGameLoop extends GameLoop { @Override protected void processGameLoop() { while (isGameRunning()) { processInput(); update(); render(); } } protected void update() { controller.moveBullet(0.5f); }}
Por último, mostramos todos los bucles del juego en acción.
try { LOGGER.info("Start frame-based game loop:"); var frameBasedGameLoop= new FrameBasedGameLoop(); frameBasedGameLoop.run(); Thread.sleep(GAME_LOOP_DURATION_TIME); frameBasedGameLoop.stop(); LOGGER.info("Stop frame-based game loop."); LOGGER.info("Start variable-step game loop:"); var variableStepGameLoop= new VariableStepGameLoop(); variableStepGameLoop.run(); Thread.sleep(GAME_LOOP_DURATION_TIME); variableStepGameLoop.stop(); LOGGER.info("Stop variable-step game loop."); LOGGER.info("Start fixed-step game loop:"); var fixedStepGameLoop= new FixedStepGameLoop(); fixedStepGameLoop.run(); Thread.sleep(GAME_LOOP_DURATION_TIME); fixedStepGameLoop.stop(); LOGGER.info("Stop variable-step game loop."); }catch (InterruptedException e) { LOGGER.error(e.getMessage()); }
Salida del programa:
Start frame-based game loop:Current bullet position: 0.5Current bullet position: 1.0Current bullet position: 1.5Current bullet position: 2.0Current bullet position: 2.5Current bullet position: 3.0Current bullet position: 3.5Current bullet position: 4.0Current bullet position: 4.5Current bullet position: 5.0Current bullet position: 5.5Current bullet position: 6.0Stop frame-based game loop.Start variable-step game loop:Current bullet position: 6.5Current bullet position: 0.038Current bullet position: 0.084Current bullet position: 0.145Current bullet position: 0.1805Current bullet position: 0.28Current bullet position: 0.32Current bullet position: 0.42549998Current bullet position: 0.52849996Current bullet position: 0.57799995Current bullet position: 0.63199997Current bullet position: 0.672Current bullet position: 0.778Current bullet position: 0.848Current bullet position: 0.8955Current bullet position: 0.9635Stop variable-step game loop.Start fixed-step game loop:Current bullet position: 0.0Current bullet position: 1.086Current bullet position: 0.059999995Current bullet position: 0.12999998Current bullet position: 0.24000004Current bullet position: 0.33999994Current bullet position: 0.36999992Current bullet position: 0.43999985Current bullet position: 0.5399998Current bullet position: 0.65999967Current bullet position: 0.68999964Current bullet position: 0.7299996Current bullet position: 0.79999954Current bullet position: 0.89999944Current bullet position: 0.98999935Stop variable-step game loop.
Diagrama de clases
