如何实现 deterministic/tick-based 游戏循环?

How to implement a deterministic/tick-based game loop?

首先,我正在尝试使用 Three.js 制作一个 "simple" 3D 游戏,并在未来使用一些网络框架使其成为多人游戏,因为我计划在未来 我搜索了一下,发现大多数 "action" 游戏都使用基于 "tick" 的游戏循环来同步客户端和服务器,然后在滴答之间进行插值以使其平滑。

我已经有了滴答(处理输入、更新、绘制)函数的一些 "working" 代码,我想知道我的实现是否正确,以及这个 "deterministic" 循环应该如何工作,假设我的实施工作正常,当我增加 "tick rate" 时,游戏会变得更快(更新功能 运行 次),对吗?

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();

代码的第一部分在游戏的构造函数中 class

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

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

构造同步模拟是一个很大的话题,但您所拥有的是一个很好的起点,并且根据您游戏的复杂性可能就足够了。

编辑:更详细一点...

是的,它的工作原理是一样的,只是 this.dt 总是一样的。即 1000 / 游戏循环所需的 FPS。

如果您想在帧之间执行 smoothing/interpolation...您还必须记录对象的先前状态...并且您可能不想使用欧拉旋转,因为欧拉不能很好地插值。因为 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 中,但无论如何..它可能仍然有效..