【问题标题】:How to implement a deterministic/tick-based game loop?如何实现确定性/基于刻度的游戏循环?
【发布时间】:2018-07-06 10:30:02
【问题描述】:

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

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

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

【问题讨论】:

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


    【解决方案1】:

    这对我来说看起来很合理,而且我过去使用过类似的模式。

    构建同步模拟是一个巨大的话题,但你所拥有的是一个很好的起点,并且可能就足够了,具体取决于你的游戏的复杂性。

    编辑:更详细一点...

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

    如果您想在帧之间进行平滑/插值...您还必须记录对象的先前状态..您可能不想使用欧拉旋转,因为欧拉不不能很好地插值。因为一个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 中,但无论如何..它可能仍然可以工作..

    【讨论】:

    • 谢谢@manthrax,但是我应该如何处理对象的位置,就我所见,大多数更新功能会像:this.object.rotation.x += Math.PI * this.dt,这在这种情况下可以工作吗?当通过网络时,我应该将 dt 和输入状态发送给另一个客户端,比如在每次更新时发送?抱歉,如果这太宽泛了。
    • nextTickTime 和 nextAlpha 应该是什么?
    • lastTickTime
    • nextAlpha 是一个错字。 alpha 和 recip 是“插值” alpha 介于 0 和 1 之间,recip 只是 1 - alpha ,所以 alpha + recip == 1
    • 好的,那么nextTickTime = lastTickTime+currenTime?还有下一个四元数?
    猜你喜欢
    • 2014-06-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-23
    • 2017-02-10
    • 1970-01-01
    相关资源
    最近更新 更多