Javascript中简单游戏循环的最佳方式?

Posted

技术标签:

【中文标题】Javascript中简单游戏循环的最佳方式?【英文标题】:Best way for simple game-loop in Javascript? 【发布时间】:2009-12-23 22:32:56 【问题描述】:

有没有用 javascript 制作游戏循环的简单方法?类似...

onTimerTick() 
  // update game state

【问题讨论】:

在 JavaScript 的事件驱动世界中,您可能不需要这样做。你认为你为什么需要这个? @Jon,如果没有触发任何事件,你会如何更新游戏?即使您不在,许多游戏也在做事...... 关于如何在javascript中实现游戏循环有全面的解释isaacsukin.com/news/2015/01/… 【参考方案1】:

根据您的应用程序,有多种方法可以使用 JavaScript 实现此目的。 setInterval() 甚至带有 while() 语句都可以解决问题。这不适用于游戏循环。 JavaScript 由浏览器解释,因此很容易出现中断。中断会使您的游戏回放感到紧张。

CSS3 的 webkitRequestAnimationFrame 属性旨在通过管理呈现循环本身来纠正此问题。但是,这仍然不是最有效的方法,如果您有很多要更新的对象,很容易出现抖动。

这是一个很好的入门网站:

http://nokarma.org/2011/02/02/javascript-game-development-the-game-loop/index.html

这个网站有一些关于制作游戏循环的基础知识的好信息。它没有以任何方式触及任何类型的面向对象设计。 实现精确计时的最准确方法是使用日期函数。

while ((new Date).getTime() > nextGameTick && loops < maxFrameSkip) 
  Game.update();
  nextGameTick += skipTicks;
  loops++;

这并没有考虑到 setTimeout 在高频下如何漂移。这也将导致事情变得不同步并变得紧张不安。 JavaScript 会以每秒 +/- 18 毫秒的速度漂移。

var start, tick = 0;
var f = function() 
    if (!start) start = new Date().getTime();
    var now = new Date().getTime();
    if (now < start + tick*1000) 
        setTimeout(f, 0);
     else 
        tick++;
        var diff = now - start;
        var drift = diff % 1000;
        $('<li>').text(drift + "ms").appendTo('#results');
        setTimeout(f, 990);
    
;

setTimeout(f, 990);

现在让我们将所有这些放到一个工作示例中。我们想将我们的游戏循环注入到 WebKit 的托管渲染循环中。这将有助于平滑渲染的图形。我们还想拆分绘制和更新函数。这将在计算应该何时绘制下一帧之前更新渲染场景中的对象。如果更新时间过长,游戏循环也应该跳过绘制帧。

Index.html

<html>
    <head>
        <!--load scripts--> 
    </head>
    <!-- 
        render canvas into body, alternative you can use div, but disable 
        right click and hide cursor on parent div
    -->
    <body oncontextmenu="return false" style="overflow:hidden;cursor:none;-webkit-user-select: none;-moz-user-select: none;-ms-user-select: none;user-select: none;">
        <script type="text/javascript" charset="utf-8">
            Game.initialize();
            window.onEachFrame(Game.run);
        </script>
    </body>
</html>

Game.js

var Game = ;

Game.fps = 60;
Game.maxFrameSkip = 10;
Game.skipTicks = 1000 / Game.fps;

Game.initialize = function() 
    this.entities = [];
    this.viewport = document.body;

    this.input = new Input();

    this.debug = new Debug();
    this.debug.initialize(this.viewport);

    this.screen = new Screen();
    this.screen.initialize(this.viewport);
    this.screen.setWorld(new World());
;

Game.update = function(tick) 
    Game.tick = tick;
    this.input.update();
    this.debug.update();
    this.screen.update();
;

Game.draw = function() 
    this.debug.draw();
    this.screen.clear();
    this.screen.draw();
;

Game.pause = function() 
    this.paused = (this.paused) ? false : true;
;

/*
 * Runs the actual loop inside browser
 */
Game.run = (function() 
    var loops = 0;
    var nextGameTick = (new Date).getTime();
    var startTime = (new Date).getTime();
    return function() 
        loops = 0;
        while (!Game.paused && (new Date).getTime() > nextGameTick && loops < Game.maxFrameSkip) 
            Game.update(nextGameTick - startTime);
            nextGameTick += Game.skipTicks;
            loops++;
        
        Game.draw();
    ;
)();

(function() 
    var onEachFrame;
    if (window.requestAnimationFrame) 
       onEachFrame = function(cb) 
          var _cb = function() 
                cb();
             requestAnimationFrame(_cb);
          ;
          _cb();
       ;
     else if (window.webkitRequestAnimationFrame) 
       onEachFrame = function(cb) 
          var _cb = function() 
             cb();
             webkitRequestAnimationFrame(_cb);
          ;
          _cb();
       ;
     else if (window.mozRequestAnimationFrame) 
        onEachFrame = function(cb) 
            var _cb = function() 
                cb();
                mozRequestAnimationFrame(_cb);
            ;
            _cb();
        ;
     else 
        onEachFrame = function(cb) 
            setInterval(cb, Game.skipTicks);
        ;
    

    window.onEachFrame = onEachFrame;
)();

更多信息

您可以在此处找到完整的工作示例以及所有代码。我已将此答案转换为可下载的 JavaScript 框架,您可以从中构建游戏。

https://code.google.com/p/twod-js/

【讨论】:

-1 这是一个格式不佳、表达不佳的杂乱无章的答案,有无数的拼写和语法错误。请阅读Answering How-To。 您的答案是页面上最正确和最新的答案,但现在您的示例代码太多了。未来的读者不会关心 HTML 布局、world.js、player.js、camera.js 或 tile.js。请仅包含与使用 requestAnimationFrame 处理游戏循环的时间相关的答案元素。 我按照您的建议删除了一些代码和类。请注意,现在这不是一个完整的示例,因为您需要添加您的世界加载、播放器和相机逻辑。您所称的 html 布局实际上是主机页面,示例需要它来向您展示使用 webkit 将游戏循环注入浏览器 顺便说一句,我遇到了这个 npm 包,它做同样的事情,但卷成一个易于使用的模块。 github.com/IceCreamYou/MainLoop.js【参考方案2】:
setInterval(onTimerTick, 33); // 33 milliseconds = ~ 30 frames per sec

function onTimerTick() 
    // Do stuff.

【讨论】:

您可能希望颠倒第一行和其余行的顺序。 ;) @Dav:没有理由这样做。函数声明在同范围内的分步代码之前生效。 导致抖动,只是一个存根。 对于现代浏览器和各种js游戏引擎现在都在使用requestAnimationFrame【参考方案3】:

requestAnimationFrame 是现在大多数浏览器中的一个很好的替代方案。它可以完成您使用 setInterval 所做的事情,但是以一种烘焙方式。可能最好的一点是它只在您的标签集中时运行。如果您希望事情在后台运行,这可能是使用它的原因,但通常(特别是对于只有在看到时才重要的渲染循环)在标签不使用时不使用资源是很好的活跃。

用法很简单:

function gameLoop()
  window.requestAnimationFrame(gameLoop);
  Game.update();

我讨厌在旧线程上发帖,但这是“javascript 游戏循环”的***谷歌搜索结果,因此它确实需要包含 requestAnimationFrame。

从paulirish复制的旧浏览器的Pollyfill:

(function() 
    var lastTime = 0;
    var vendors = ['ms', 'moz', 'webkit', 'o'];
    for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) 
        window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
        window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] 
                                   || window[vendors[x]+'CancelRequestAnimationFrame'];
    

    if (!window.requestAnimationFrame)
        window.requestAnimationFrame = function(callback, element) 
            var currTime = new Date().getTime();
            var timeToCall = Math.max(0, 16 - (currTime - lastTime));
            var id = window.setTimeout(function()  callback(currTime + timeToCall); , 
              timeToCall);
            lastTime = currTime + timeToCall;
            return id;
        ;

    if (!window.cancelAnimationFrame)
        window.cancelAnimationFrame = function(id) 
            clearTimeout(id);
        ;
());

creativeJS上更深入的解释和使用技巧

【讨论】:

【参考方案4】:

其中许多答案已过时且不理想。现在推荐的方法是使用requestAnimationFrame

例如:

let previousTime = 0.0;

const loop = time => 
  // Compute the delta-time against the previous time
  const dt = time - previousTime;

  // Update the previous time
  previousTime = time;

  // Update your game
  update(dt);

  // Render your game
  render();

  // Repeat
  window.requestAnimationFrame(loop);
;

// Launch
window.requestAnimationFrame(time => 
  previousTime = time;

  window.requestAnimationFrame(loop);
);

【讨论】:

【参考方案5】:

是的。你要setInterval:

function myMainLoop () 
  // do stuff...

setInterval(myMainLoop, 30);

【讨论】:

【参考方案6】:

这样可以吗?

setInterval(updateGameState, 1000 / 25);

其中 25 是您想要的 FPS。您还可以在此处输入帧之间的毫秒数,在 25 fps 下为 40 毫秒 (1000 / 25 = 40)。

【讨论】:

你很想传递函数,而不是结果 不处理漂移,会导致延迟。不使用 webkit【参考方案7】:

不确定这将如何工作,但这是一种使用 while 循环并使线程休眠的方法。这只是一个示例,您可以根据您在任何其他语言中的操作方式,将下面的游戏循环替换为更好的循环。还可以找到更好的while循环here

let gameRunning = false;

async function runGameThread()
    if(!gameRunning)
        gameRunning = true;

        // this function allows the thread to sleep (found this a long time ago on an old stack overflow post)
        const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

        // the game loop
        while(gameRunning)
            let start = new Date().getTime();
            gameUpdate();
            await sleep(start + 20 - new Date().getTime());
        

    


function stopGameThread()
    while(gameRunning)
        try
            gameRunning = false;
        catch(e)
    


function gameUpdate()
    // do stuff...

【讨论】:

以上是关于Javascript中简单游戏循环的最佳方式?的主要内容,如果未能解决你的问题,请参考以下文章

在 JavaScript 中循环一组元素的最佳方式是啥?

将简单的 javascript 游戏移植到 android 的最佳方法

使用 requestAnimationFrame 的 javascript 中一个简单的 30fps 游戏循环?

使用 Javascript 保存游戏

Javascript - 使用增量时间的游戏循环

Javascript 游戏循环