【LWJGL官方教程】Game loops 游戏循环

本文介绍了游戏的工作原理和两种常见的游戏循环——可变时步与固定时步。在可变时步中,游戏更新依赖于时间增量,而固定时步则基于时间,不受帧率影响,提供更确定的更新频率。固定时步通过累加器和插值alpha来处理剩余时间,确保流畅渲染。
摘要由CSDN通过智能技术生成

原文:https://github.com/SilverTiger/lwjgl3-tutorial/wiki/Game-loops
译注:翻译仅供参考,请以原文为准。代码请看原文中的链接。括号里的内容一般也是译注,供理解参考用。
PS:Markdown还挺好用的,给CSDN点个赞。

How does a game work? 一个游戏如何工作?

从编程角度来讲,一个游戏可以用一个简单的方法来描述:

public void startGame() {
    init();
    gameLoop();
    dispose();
}

这就是游戏全部必须要做的事,init()包含初始化的全部内容,比如创建窗口、读取资源。dispose()包含释放游戏资源的全部代码。

A simple game loop 一个简单的游戏循环

游戏循环的关键部分是处理输入、更新游戏逻辑、渲染游戏。

public void gameLoop() {
    while (running) {
        input();
        update();
        render();
    }
}

这样的游戏循环会耗尽全部的CPU,因为它运行得太快了。通常你并不需要它这样,因此需要让循环适当停歇。

public void gameLoop() {
    long sleepTime = 1000L / 60L;

    while (running) {
        input();
        update();
        render();

        sleep(sleepTime);
    }
}

在这个例子里,加入了16毫秒的休眠时间,游戏每秒将只更新62.5次。
虽然这已经比毫无停歇要好一些了,但是其实你需要有一个可变的休眠时间以保证游戏有稳定的FPS。为此,我们应该看看时间运算教程。
根据时间计数器的选择,有两种形式的游戏循环:
- 可变时步游戏循环
- 固定时步游戏循环

Variable timestep 可变时步

用可变时步,你不需要关心你的游戏是不是跑得太快或太慢了,因为会使用时间增量(delta time)来更新游戏。(意思是,如果跑太慢了,时间增量超长,那么就一次更新许多内容,太快也是同理)
在这种循环,我们首先就得拿到时间增量

float delta = timer.getDelta();

时间增量用来更新游戏。所以给update()方法加上参数。

public void update(float delta) {

    /* do updates */
}

之后也不需要再加什么了,最后是这样的:

public void gameLoop() {
    while (running) {
        float delta = timer.getDelta();

        input();
        update(delta);
        render();

        window.update();
    }
}

你可能想问,为啥不休眠了。因为你可以让GLFW去激活垂直同步,这样你不需要再关心休眠的问题了。
但是如果我们在循环里加入休眠,也不需要改太多东西。

public void gameLoop() {
    long targetTime = 1000L / targetFPS;

    while (running) {
        /* Note that you have to multiply by 1000 to get milliseconds */long startTime = timer.getTime() * 1000;
        float delta = timer.getDelta();

        input();
        update(delta);
        render();

        window.update();

        /* Same as above, multiply time in seconds by 1000 */long endTime = timer.getTime() * 1000;
        sleep(startTime + targetTime - endTime);
    }
}

但是这个循环有一个瑕疵:你的更新受限于你的帧率,在简单的游戏里这还OK,但是如果是想做一个逼真的物理情况模拟,这就不是你想要的了。(意思是,假如FPS很低的话,那更新次数也很低,时间增量也超长,如果模拟铁球下落,会看到铁球在空中是断断续续的,一次还落超长距离……效果很糟糕。大部分情况下虽然不会这么明显,但是也会让玩家有一种违和感。)

Fixed timestep 固定时步

固定时步的话,你的游戏循环不能再受限于帧率了,而是基于时间,比起可变时步,它更具有确定性。
固定时步除了时间增量以外还需要有更多的变量,需要一个累加器(accumulator)记录经过的时间,一个表示更新之间的时间应该是多少的间隔量(interval),还需要有一个aplha值来充当插值。

float delta;
float accumulator = 0f;
float interval = 1f / 30f;
float alpha;

在这个例子里,我们希望每秒有30次更新,所以间隔量大概是33.33毫秒。
变量有了,开始循环。跟可变循环一样,先要拿到时间增量,但在此循环里,还要把增量加在累加器上。

delta = timer.getDelta();
accumulator += delta;

接着处理输入。再之后要检查经过的时间是否应该做更新操作了。(累加器时间超过设定的间隔量,按间隔量来做更新)

while (accumulator >= interval) {
    update(interval);
    accumulator -= interval;
}

之后我们其实还剩下一些多出来的时间,举个例子。
比如现在游戏已经开始了48毫秒,但是我们下一次更新应该是在66.67毫秒的时候,因为我们说好是每33.33毫秒更新一次的(之前设定的间隔量)。这时累加器的值应该是48-33.33=14.67毫秒。
所以我们当前的游戏状态其实是14.67毫秒以前的游戏状态,现在我们可以再渲染一次相同的屏幕内容,但是那样的话我们的游戏看起来就像是以30FPS来渲染而不是我们预期的那样,帧在做重复无意义的废渲染。为了能表现出两次更新间的某种状态,我们引入了alpha这个插值。(意思是,我们虽然希望一秒更新30次,但是却期望能更精确地连续渲染画面,而不是离散地去重复渲染这30次更新时的画面,那样就算一秒渲染了100次,其实也是在反复重复地渲染这30个更新瞬间的画面而已。)

alpha = accumulator / interval;

在上面说的例子里,alpha值应该是14.67/33.33=0.44,所以我们距下次更新大概行进到了44%的阶段。为了渲染,我们需要保持上一次和本次的状态插值,具体在另一篇教程里再说。
最后,固定时步循环应该是这样的:

public void gameLoop() {
    float delta;
    float accumulator = 0f;
    float interval = 1f / targetUPS;
    float alpha;

    while (running) {
        delta = timer.getDelta();
        accumulator += delta;

        input();

        while (accumulator >= interval) {
            update();
            accumulator -= interval;
        }

        alpha = accumulator / interval;
        render(alpha);

        window.update();
    }
}

关于udpate方法,有一点:如果间隔量不打算改的话,其实没必要再把间隔量放在update方法里了。(因为它是固定的值)
本教程里,update()写作update(delta),render()写作render(alpha),所以你用哪种时步都可以。

public void update() {
    update(1f / targetUPS);
}

public void render() {
    render(1f);
}

下一篇学习用shader来渲染

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值