Java 中的游戏循环模式:掌握流畅的游戏机制
又称
- 游戏循环
- 主游戏循环
游戏循环设计模式的意图
游戏循环设计模式对于创建流畅且交互式游戏体验至关重要,因为它可以促进游戏持续执行。每个循环周期都处理输入、更新游戏状态,并将游戏状态渲染到屏幕上,确保在所有硬件设置中都能保持一致的性能。
游戏循环模式的详细说明,包括现实世界示例
现实世界示例
游戏循环的一个实际比喻可以在游乐园的游乐设施中看到,比如过山车。就像过山车以循环方式运行、更新其状态并确保平稳运行一样,游戏循环也会持续处理输入并更新游戏状态,以提供无缝的游戏体验。过山车以连续循环的方式运行,其中游乐设施的状态(过山车的速度和位置)在游乐设施运行时不断更新。过山车的控制系统确保车辆沿轨道平稳移动,实时调整速度和处理游乐设施的安全系统。就像游戏循环一样,该控制系统会重复处理输入(例如当前速度和位置)、更新状态并触发输出(例如调整刹车或加速车辆)以在整个游乐设施运行过程中保持所需的运行方式。
简单来说
游戏循环模式确保游戏时间在所有不同的硬件设置中都以相同的速度进行。
维基百科说
从编程的角度来看,任何游戏的核心组件都是游戏循环。无论用户是否有输入,游戏循环都让游戏能够平稳运行。
Java 中游戏循环模式的编程示例
在我们的 Java 示例中,我们展示了一个简单的游戏循环,它控制子弹的移动,更新其位置,确保平稳渲染并响应用户输入。游戏循环是驱动所有现代游戏渲染线程的主要流程。它处理输入处理、内部状态更新、渲染、AI 和其他流程。从一个简单的 Bullet
类开始,我们展示了游戏中子弹的移动,重点是它们的 1 维位置以用于演示目的。
public class Bullet {
private float position;
public Bullet() {
position = 0.0f;
}
public float getPosition() {
return position;
}
public void setPosition(float position) {
this.position = position;
}
}
GameController
负责移动游戏中的物体,包括上面提到的子弹。
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();
}
}
现在我们介绍游戏循环。实际上,在这个演示中我们有 3 个不同的游戏循环。让我们先看看基类 GameLoop
。
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();
}
这是第一个游戏循环实现,FrameBasedGameLoop
public class FrameBasedGameLoop extends GameLoop {
@Override
protected void processGameLoop() {
while (isGameRunning()) {
processInput();
update();
render();
}
}
protected void update() {
controller.moveBullet(0.5f);
}
}
这是第二个游戏循环实现,FixedStepGameLoop
public class FixedStepGameLoop extends GameLoop {
/**
* 20 ms per frame = 50 FPS.
*/
private static final long MS_PER_FRAME = 20;
@Override
protected void processGameLoop() {
var previousTime = System.currentTimeMillis();
var lag = 0L;
while (isGameRunning()) {
var currentTime = System.currentTimeMillis();
var elapsedTime = currentTime - previousTime;
previousTime = currentTime;
lag += elapsedTime;
processInput();
while (lag >= MS_PER_FRAME) {
update();
lag -= MS_PER_FRAME;
}
render();
}
}
protected void update() {
controller.moveBullet(0.5f * MS_PER_FRAME / 1000);
}
}
这是第三个游戏循环实现,VariableStepGameLoop
public class VariableStepGameLoop extends GameLoop {
@Override
protected void processGameLoop() {
var lastFrameTime = System.currentTimeMillis();
while (isGameRunning()) {
processInput();
var currentFrameTime = System.currentTimeMillis();
var elapsedTime = currentFrameTime - lastFrameTime;
update(elapsedTime);
lastFrameTime = currentFrameTime;
render();
}
}
protected void update(Long elapsedTime) {
controller.moveBullet(0.5f * elapsedTime / 1000);
}
}
最后,我们展示所有游戏循环的运行情况。
public static void main(String[] args) {
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());
}
}
程序输出
Start frame-based game loop:
Current bullet position: 0.5
Current bullet position: 1.0
Current bullet position: 1.5
Current bullet position: 2.0
Current bullet position: 2.5
Current bullet position: 3.0
Current bullet position: 3.5
Current bullet position: 4.0
Current bullet position: 4.5
Current bullet position: 5.0
Current bullet position: 5.5
Current bullet position: 6.0
Stop frame-based game loop.
Start variable-step game loop:
Current bullet position: 6.5
Current bullet position: 0.038
Current bullet position: 0.084
Current bullet position: 0.145
Current bullet position: 0.1805
Current bullet position: 0.28
Current bullet position: 0.32
Current bullet position: 0.42549998
Current bullet position: 0.52849996
Current bullet position: 0.57799995
Current bullet position: 0.63199997
Current bullet position: 0.672
Current bullet position: 0.778
Current bullet position: 0.848
Current bullet position: 0.8955
Current bullet position: 0.9635
Stop variable-step game loop.
Start fixed-step game loop:
Current bullet position: 0.0
Current bullet position: 1.086
Current bullet position: 0.059999995
Current bullet position: 0.12999998
Current bullet position: 0.24000004
Current bullet position: 0.33999994
Current bullet position: 0.36999992
Current bullet position: 0.43999985
Current bullet position: 0.5399998
Current bullet position: 0.65999967
Current bullet position: 0.68999964
Current bullet position: 0.7299996
Current bullet position: 0.79999954
Current bullet position: 0.89999944
Current bullet position: 0.98999935
Stop variable-step game loop.
何时在 Java 中使用游戏循环模式
游戏循环模式非常适合实时模拟和游戏,这些模拟和游戏需要持续的状态更新和流畅的帧速率。
Java 中游戏循环模式的现实世界应用
- 各种平台上的 2D 和 3D 电子游戏。
- 需要稳定帧速率以更新逻辑和渲染的实时模拟。
游戏循环模式的优缺点
优点
- 确保游戏平稳且确定性地进行。
- 便于游戏状态、用户输入和屏幕渲染之间的同步。
- 为游戏开发者提供清晰的结构来管理游戏动态和时间。
缺点
- 如果循环管理不善,可能会导致性能问题,尤其是在资源密集型更新或渲染中。
- 难以管理不同硬件之间的不同帧速率。
相关 Java 设计模式
- 状态:通常在游戏循环中使用,用于管理游戏的不同状态(例如菜单、游戏、暂停)。这种关系在于在游戏循环中管理特定于状态的行为并平稳过渡。
- 观察者:在游戏循环中用于事件处理,游戏实体可以订阅事件并对事件做出反应(例如碰撞、得分)。