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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1955687/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-22 22:03:59  来源:igfitidea点击:

Best way for simple game-loop in Javascript?

javascriptgame-loop

提问by noctonura

Is there a simple way to make a game loop in JavaScript? something like...

有没有一种简单的方法可以在 JavaScript 中创建游戏循环?就像是...

onTimerTick() {
  // update game state
}

回答by 1-14x0r

There are a varied amount of ways to achieve this using JavaScript depending on your application. A setInterval() or even with a while() statement would do the trick. This will not work for a game loop. JavaScript interpreted by the browser, so it is prone to interrupts. Interrupts will make the play back of your game feel jittery.

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

The webkitRequestAnimationFrame properties of CSS3 aims to correct this by managing the rendering loop itself. However this is still not the most efficient way to do this and will be prone to jitters if you have alot of objects being updated.

CSS3 的 webkitRequestAnimationFrame 属性旨在通过管理渲染循环本身来纠正这个问题。然而,这仍然不是执行此操作的最有效方法,并且如果您要更新大量对象,则很容易出现抖动。

This is a good website to get started with:

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

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

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

This site has some good information on the basics of making a game loop. It does not touch upon any sort of object oriented design by any means. The most accurate way to achieve precise timings is by using the date function.

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

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

This does not take into account how setTimeout drifts at high frequencies. This will also lead to things getting out of sync and becoming jittery. JavaScript will drift +/- 18ms per second.

这没有考虑 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);

Now lets put all of this into a working example. We want to inject our game loop into WebKit's managed rendering loop. This will help smooth out the rendered graphics. We also want to split up the draw and update functions. This will update the objects in our rendering scene before calculating when the next frame should be drawn. The game loop should also skip draw frames if updating takes to long.

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

Index.html

索引.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

游戏.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;
})();

Even More Information

更多信息

You can find a full working example, and all of the code here. I have convert this answer into a downloadable javascript framework you can build your games off from.

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

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

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

回答by James

setInterval(onTimerTick, 33); // 33 milliseconds = ~ 30 frames per sec

function onTimerTick() {
    // Do stuff.
}

回答by Cody Crumrine

requestAnimationFrame is a great alternative available in most browsers now. It does what you're doing with setInterval, but in a baked in way. Probably the nicest thing about it is that it only runs while your tab is focused. That could be a reason notto use it if you want things to run in the background, but often (especially for render loops that only matter when seen) it's great to not be using resources when the tab's not active.

requestAnimationFrame 现在在大多数浏览器中都是一个很好的替代方案。它使用 setInterval 完成您正在做的事情,但以一种烘烤的方式进行。可能最好的事情是它只在您的选项卡聚焦时运行。这可能是一个理由使用它,如果你想要的东西在后台运行,但往往(尤其是渲染循环,即只看到当物质)这是伟大的使用资源时的标签是不活动时不可。

Usage is pretty simple:

用法非常简单:

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

I hate to post on old threads, but this is a top google result for "javascript game loop" so it really needs to include requestAnimationFrame.

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

Pollyfill for old browsers copied from paulirish:

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

Deeper explanation and usage tips on creativeJS

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

回答by Brian Campbell

Yep. You want setInterval:

是的。你想要setInterval

function myMainLoop () {
  // do stuff...
}
setInterval(myMainLoop, 30);

回答by Tatu Ulmanen

Would this do?

这行吗?

setInterval(updateGameState, 1000 / 25);

Where 25 is your desired FPS. You could also put there the amount of milliseconds between frames, which at 25 fps would be 40ms (1000 / 25 = 40).

其中 25 是您想要的 FPS。您还可以设置帧之间的毫秒数,在 25 fps 时为 40 毫秒(1000 / 25 = 40)。

回答by sdgfsdh

Many of these answers are outdated and suboptimal. The recommended way to do this now is using requestAnimationFrame.

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

For example:

例如:

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;
  loop(time);
});

回答by SwiftNinjaPro

not sure how well this will work, but here is one method that uses a while loop and sleeps the thread. This is just an example, you can replace the game loop below with a better one, based on how you would do it in any other language. You can also find a better while loop here

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

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...
}