如何实现确定性/基于刻度的游戏循环?

时间:2018-07-03 21:54:11

标签: javascript three.js game-engine game-physics

首先,我正在尝试使用Three.js制作“简单”的3D游戏,并在将来使用一些网络框架使其成为多人游戏,因为我计划在将来做网络部分,因此我做了一些搜索并且发现大多数“动作”游戏都使用基于“滴答”的游戏循环来同步客户端和服务器,然后在滴答之间进行插值以使其顺畅。

我已经有了一些滴答(处理输入,更新,绘制)功能的“工作”代码,我想知道的是我的实现是否正确,以及该“确定性”循环应该如何工作(假设我的实现)在正常工作时,当我增加“报价”速度时,游戏会变得更快(更新功能运行了多次),对吗?

this.loops = 0;
this.tick_rate = 20;
this.skip_ticks = 1000 / this.tick_rate;
this.max_frame_skip = 10;
this.next_game_tick = performance.now();

代码的第一部分在Game类的构造函数中

Game.prototype.run = function () {
    this.handle_input();

    this.loops = 0;

    while (performance.now() > this.next_game_tick && this.loops < this.max_frame_skip){
        this.up_stats.update();
        this.update();
        this.next_game_tick += this.skip_ticks;
        this.loops++;
    }

    this.draw();
    //monitor performance
    this.stats.update();

    //next update
    requestAnimationFrame(this.run.bind(this));
};

完整代码位于:https://github.com/derezzedex/first_three_js/blob/master/js/game/main.js

1 个答案:

答案 0 :(得分:0)

这对我来说似乎很合理,过去我也使用过类似的模式。

构建同步模拟是一个巨大的话题,但是您拥有的是一个很好的起点,并且可能取决于游戏的复杂性。

编辑:更多细节...

是的,它的工作原理相同,只是this.dt始终相同。即1000 /游戏循环所需的FPS。

如果要在两帧之间进行平滑/插值...则还必须记录对象的先前状态..您可能不想使用Euler旋转,因为euler不会插值不好。因为360度的角度会翻转回0,所以插值逻辑变得很奇怪...

但是,相反,您可以记录更新前后的状态...

然后内插.quaternion ..对于较小的旋转变化,它仅适用于线性内插..如果变化太大,则可以使用quaternion.slerp()来处理较大距离的内插。

所以您有lastTickTime,currentTime和nextTickTime...。

每个框架..您正在做类似的事情:

要进行插值,请执行以下操作:

var alpha= (currentTime-lastTickTime) / (nextTickTime-lastTickTime);//nextTickTime-lastTickTime = your framerate delta so for 60fps = 1000/60 = 16.666666666666668

var recip = 1.0 - alpha;

object.position.x = (object.lastPosition.x * recip)+(object.nextPosition.x*alpha)
object.position.y = (object.lastPosition.y * recip)+(object.nextPosition.y*alpha)
object.position.z = (object.lastPosition.z * recip)+(object.nextPosition.z*alpha)

object.scale.x = (object.lastScale.x * recip)+(object.nextScale.x*alpha)
object.scale.y = (object.lastScale.y * recip)+(object.nextScale.y*alpha)
object.scale.z = (object.lastScale.z * recip)+(object.nextScale.z*alpha)

object.quaternion.x = (object.lastQuaternion.x * recip)+(object.nextQuaternion.x*alpha)
object.quaternion.y = (object.lastQuaternion.y * recip)+(object.nextQuaternion.y*alpha)
object.quaternion.z = (object.lastQuaternion.z * recip)+(object.nextQuaternion.z*alpha)
object.quaternion.w = (object.lastQuaternion.w * recip)+(object.nextQuaternion.w*alpha)

在适当的三个应用程序中,您可能不应该将lastPosition和nextPosition直接存储在对象上,而是将其放置在object.userData中,但是不管怎样,它仍然可以工作。.