【问题标题】:JavaScript Smooth Animation with a game loop带有游戏循环的 JavaScript 平滑动画
【发布时间】:2012-08-02 22:11:55
【问题描述】:

我已将我的游戏循环简化为一个在屏幕上移动的框,Click Here。由于某种原因,盒子似乎移动不顺畅。我做了一个video of it here

游戏循环是这样调用的:

var game = function( ) {
    var now = Date.now( );
    var delta = now - then;

    update( delta / 1000 );
    draw( );

    then = now;
};

setInterval( game, 1000 / 50 );

我尝试将draw 调用与主游戏循环分开并将它们放入requestAnimationFrame,但问题仍然存在。我看过一堆似乎运行顺利的教程。我什至尝试过使用fixed time-step game loop,但这只会让我的游戏运行得快得无法控制。

我如何改进上述逻辑,也许利用requestAnimationFrame 并为update 调用维护deltaTime

【问题讨论】:

  • 我完全没有看到,它也一直在说59。那是fps吗?
  • 嗯,是的,59 是我尝试显示每秒更新次数的尝试。在我的 Mac 上的 Safari 和 Chrome 中,我看到桨和球向前跳了一下,而不是平稳地移动。我想我应该在其他一些计算机上检查它。
  • 如果我仔细看的话,我猜球有点生涩......这是因为在某些帧中,球一次移动超过 1px。
  • 正确。有没有办法纠正这种行为?我认为通过基于deltaTime(更新周期​​之间的时间差异)进行更新可以使事情顺利进行。
  • 我似乎没有注意到我的计算机上的这种行为,但这很可能是因为 FPS 或多或少是恒定的。但是,这很可能是您绘制太多的事实的副产品,制作一个您不更新的单独画布,上面有背景,或者让自己成为一个只绘制脏项目的系统(已经改变位置的东西/缩放/旋转/颜色等)

标签: javascript animation html5-canvas game-physics


【解决方案1】:

我相信在使用画布时,您的位置变量应该是整数值,因为它们代表像素,而浮点值没有意义。如果您打开控制台并输入sceneManager.currentScene.GameplayLayer.ball.position.x,那么您会得到一个非常长的小数。我认为关于 OP 的评论表明有时球移动 2px 而不是 1px 可能是在某事上。当你更新你的位置时,你最终会得到一个浮点值。

我相信它有时会向上取整到下一个最高像素位置,有时会向下取整。我会尝试像这样占用地板或天花板:

this.position.x += Math.floor(this.speed * 100 * deltaTime * Math.cos(directionInRadians));
this.position.y += Math.floor(this.speed * 100 * deltaTime * Math.sin(directionInRadians));

我会进行这两项更改,看看它的表现如何。

编辑:由于您编辑了问题以简化逻辑。我可以建议一些尝试,即使用我创建的这个我一直使用的时钟对象。它给了我流畅的动画,而且相当简单。它基于clock that Three.JS uses,因此您可能也想检查一下。即使你想使用自己的代码,你至少可以尝试这个现成的解决方案,看看它是否能给你同样的结果。这对我来说似乎工作得很好。另外,您尝试使用 shim,因此您在游戏函数中的调用应该是 requestAnimFrame(game);

var Clock = function () {

    /** Member startTime will remain fixed at its integer
        millisecond value returned by Date.now(). Will always
        be equal to the time the clock was started */
    this.startTime = Date.now();

    /** Member ms is updated by tick() to a integer value reprsenting 
        the number of milliseconds between the epoch (January 1, 1970)
        and the current date and time of the system. */
    this.ms = this.startTime;
    this.last = this.startTime;  /** millis at last call to tick() */
    this.time = 0;               /** ms in floating point seconds not millis */

    /** Member dt is updated by tick() to an integer value representing
        the number of milliseconds since the last call to tick(). */
    this.dt = 0;
    this.delta = 0; /** dt in floating point seconds not millis */

    /** Member fps is updated by tick() to a floating point value representing
        frames per second, updated and averaged approximately once per second */
    this.fps = 0.0;

    /** Member frameCount is updated to an integer value representing the
        total number of calls to tick() since the clock was created. */
    this.frameCount = 0;

    /** The frameCounter member is a flag you can turn off if you don't need to
        calculate the frameCount or do the average FPS calculation every second */
    this.frameCounter = true;

    /** Private globals needed to calculcate/average fps over eachs second */
    var timeToUpdate = 0;
    var framesToUpdate = 0;

    /************************************************************************************
        The tick() method updates ALL the Clock members, which should only
        be read from and never written to manually. It is recommended that
        tick() is called from a callback loop using requestAnimationFrame

        Learn more: http://paulirish.com/2011/requestanimationframe-for-smart-animating/
    *************************************************************************************/
    this.tick = function () {
        /** This is a new frame with it's very own unique number */

        if (this.frameCounter) this.frameCount++;

        /** Set the private currentTime variable */
        this.ms = Date.now();

        /** Update time delta and immediately set last time to
            be as accurate as possible in our timings. */
        this.dt = this.ms - this.last;
        this.last = this.ms;

        /** Calculate floating-point delta and increment time member */
        this.delta = 0.001 * this.dt;
        this.time += this.delta;

        /** Calculate private temp variables for fps calculation */
        if (this.frameCounter) {
            timeToUpdate += this.dt;
            framesToUpdate++;
            if (timeToUpdate > 1000) {
                this.fps = Math.round((framesToUpdate * 1000) / timeToUpdate);
                framesToUpdate = 0;
                timeToUpdate = 0;
            }
        }
    }
}

如果你使用这个对象,那么你需要做的就是在你的初始化函数中创建一个新的时钟对象,就像clock = new Clock();。然后在每个动画调用中调用clock.tick()。然后,您可以访问成员 clock.deltaclock.time,这将为您提供以秒为单位的浮点值的增量和时间。 clock.dtclock.ms 将以毫秒为单位为您提供与整数相同的值。您还可以使用 clock.fps 访问 fps 或通过设置 clock.frameCounter = false 禁用它。

【讨论】:

  • 好吧,我尝试了您的建议,但仍然存在同样的问题。我认为我的游戏循环逻辑出于某种原因是不正确的,而且我尝试的一切似乎都以相同的方式做出反应。我拿了别人的代码,并且能够在我的电脑上制作流畅的动画,所以我可能不得不将我的游戏重新设计到他们的主循环中,看看会发生什么。再次感谢您的帮助。我希望 JavaScript 有一个万能的游戏循环逻辑。
  • 这听起来是个好主意,您在此应用程序中的结构化执行得很好,但就您目前所拥有的而言,它似乎真的很复杂。我将从 3 个函数 init、animate 和 render 开始。尽可能以最简单的方式使其运行良好,并在增加游戏复杂性时构建抽象。到目前为止,所有不同的文件和类似乎都有些矫枉过正。不过,它对我来说确实是很好的编程。一些小事情可能是不对的,并且很难将程序流程想象得如此简单。
  • 注意:我已经简化了逻辑并更新了我的问题。这个答案可能不再相关。
  • 感谢您的持续关注。我构建了一个使用 Clock 类的jsFiddle。我一定是疯了,因为我仍然看到 60 FPS 时断断续续的动画。我把它降到 30 FPS,它的反应是一样的。我认为我对 JavaScript 的期望可能太高了,我可能不得不安顿下来。盒子以正确的速度移动,但它时不时地有点卡顿。我在 Chrome 中的 about:flags 中打开了 FPS 计数器,它会在动画抖动时显示谷值。
  • 嗯,对我来说似乎运行得很顺利...我认为您所指和看到的内容是由垃圾收集器经常清理造成的..我可以询问什么您正在运行的浏览器以及可能影响某些事情的任何扩展?还是完全一样?对我来说,它绝对是 Chrome 中最流畅的,但在您完全放弃 Javascript 之前,您可能需要查看 Three.JS 示例并了解 Canvas 演示如何使用该库在您的机器上运行。它有一个出色的画布渲染器,对我来说也很光滑。
【解决方案2】:

使用three.js 时钟平滑了我的动画。我强烈推荐它。里面还有很多其他好的代码。

【讨论】:

  • clock.js 使用Date.now() 来实现。我想知道它如何比 OP 中的代码更流畅。
  • 使用 @tigrou Date.now 是因为尽管有命名,但它比 performance.now 性能更高。这些天它们都非常快,但由于它在每一帧都被调用,所以你想要尽可能快的东西。 performance.now 优于 Date.now(以及任何其他在 JavaScript 中获取时间戳的方法)的优势在于 performance.now 具有亚毫秒级的精度。在动画的情况下,Date.now 的一毫秒精度就足够了。 performance.now 的使用更适合用于基准测试、物理模拟等应用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-10
  • 1970-01-01
  • 2013-10-24
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多