LibGdx内box2d适配高刷新率屏幕

一个适配各种屏幕刷新率的写法。

LibGdx内box2d适配高刷新率屏幕

By img Microanswer Create at:Feb 15, 2022, 5:04:37 PM 

Tags: libgdx 游戏开发 游戏引擎 高刷 fps box2d

一个适配各种屏幕刷新率的写法。


去年(2021年)购置了一款新手机,一加9 Pro,这是一款屏幕刷新率支持到了120fps的安卓手机。现在一时兴起,想把4年前自己的 flappybird 项目拿来玩玩,由于屏幕刷新率120fps,而以前是为60fps的刷新率写的代码,在这台手机上面运行结果发现游戏速度得很快,根本跟不上节奏。这必然是不应该的,这么大一个问题,绝对不能出现在我的项目里面,所以最近又研究了一下LibGdx这款游戏引擎,希望能够修复这个问题。

一、问题原因

由于我在游戏内使用了LibGdx内置集成的box2d物理引擎,该引擎提供了一个 step(timestep, velocityIterations, positionIterations)函数,用来更新物理引擎的状态,通过不停的执行此函数,在这个物理引擎世界内的各个元素,就会开始按照设定的重力值进行运动了。因此,这个函数传入的第一个参数timestep时间步长就会直接影响各个元素的运动效果。

最初,我的设备是60fps的刷新率,因此我直接将timestep的值传入了1/60,也就是每秒计算60次,迎合屏幕刷新率,最终运行效果也是非常如意。过去的代码如下:

// GameScreen.java

@Override
public void render(float delta) {
    world.step(1/60f, 6, 2); // 物理引擎世界更新
    stage.act(); // 逻辑更新
    stage.draw(); // 界面渲染
}

这个代码写死了物理引擎每次执行render的时候都以1/60秒的时间步长去计算各个元素。当其在120fps的设备上执行时,由于render函数的执行次数每秒达到了120次,因此物理引擎内的各个元素的运动速度就比60fps的屏幕上快了2倍,这哪能行啊。

二、解决办法

为了解决这个问题,我心想,既然1/60用于60fps的屏幕,那我直接写 1/120 不就修复了。修复是修复了,但是这个值放到60fps的设备上运行后,又会变成慢动作了。肯定不可以写死。有小伙伴可能会想了,render函数本身传入的delta参数值,不就是每次执行距离上次的时间间隔嘛,直接用它不就可以适配各种刷新率了。是的,想法是很美好的,但是现实是很残酷的,物理引擎如果想要得到一个稳定平滑的运动效果,那每次传入的timestep的值就必须是相同的,而render函数本身传入的delta这个值实际上是一直在变化的,小数点后面三四位数几乎每次都会变动,这会导致我们构建的物理世界一会儿快一会儿慢,用户根本没有办法继续玩游戏下去了。

终于我想到了一个办法,当我设置1/60的时候,在120fps刷新率下会因为render函数执行次数增加导致物理世界运动变快,那么我做一个判断,让world.step()函数不要每次都执行,稍微定时一下,让它每秒只执行60次,不就迎合了吗。于是我写下了下面的代码:

// GameScreen.java

private float timePassed = 0f; // 声明一个变量来保存每次更新时间。
@Override
public void render(float delta) {
    timePassed += delta;
    if (timePassed >= 1/60f) {
        world.step(1/60f, 6, 2); // 物理引擎世界更新
        timePassed = 0f;
    }
    stage.act(); // 逻辑更新
    stage.draw(); // 界面渲染
}

代码写毕,编译运行,轻松解决,但是但是但是,我这可是120fps的手机啊,我这代码写下来,我玩起来怎么感觉还是一台60fps那种感觉啊,那这可不行啊,我这强迫症可忍受不了这种体验。于是我又开始慢慢的思考。

三、最终完美解决

经过我无数脑细胞的牺牲,我总结了上面这个解决办法的问题所在,我这属于是降纬操作了,活生生把120fps强行降低到60fps去计算物理世界的运动。那我为什么不反过来,我人为的一定要保证每一秒让物理世界计算120次,无论是60fps的设备还是120fps的设备,而对于60fps的设备,每秒钟render的执行次数都才60次,怎么让物理世界执行120次呢?很简单啊,每次render时在里面执行两遍step函数,不就可以了。于是,我写出了下面的代码:

// GameScreen.java

@Override
public void render(float delta) {
    // 先计算出这次执行render的间隔时间是 1/120 的几倍。
    // 如果设备是120fps的,那么这里得到的 dt 就会一直为1左右,下面循环就只会执行一次。
    // 如果设备是60fps的,那么这里得到的 dt 就会一直为2左右,下面循环就会执行2次,从而保证60fps的设备,也能做到每秒更新120次物理世界的运动。
    double dt = delta/(1/120f);
    for (int i = 0; i < dt; i++) {
        world.step(1/120f, 6, 2); // 物理引擎世界更新
    }

    stage.act(); // 逻辑更新
    stage.draw(); // 界面渲染
}

代码再次竣工,上机运行,120fps下丝滑流畅,60fps下依然完美运行,同时这样修改后,其它的重力参数和冲量参数也无需进行修改,均可在不同fps的设备上执行得到相同的效果。来看看效果(动图录制帧率好低啊):

上方我人为定的每秒执行 120 次物理世界计算,如果又来了一个 144fps 或者 300 fps 的设备,肯定又会出现物理世界运动变快的情况,但是我们现在有了这个办法,我们只需要把我们定义的每秒执行次数修改为希望的最大值,可以是 144,也可以是 300。

四、注意事项

此解决方案可能并不是完美的解决方案,也许存在着各种我暂未发现的问题,读者请酌情参考。

Full text complete, Reproduction please indicate the source. Help you? Not as good as one:
Comment(Comments need to be logged in. You are not logged in.)
You need to log in before you can comment.

Comments (0 Comments)