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

时间:2009-12-23 22:32:56

标签: javascript game-loop

是否有一种简单的方法可以在JavaScript中进行游戏循环?类似......

onTimerTick() {
  // update game state
}

8 个答案:

答案 0 :(得分:29)

根据您的应用程序,使用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每秒会漂移+/- 18ms。

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 :(得分:24)

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

function onTimerTick() {
    // Do stuff.
}

答案 2 :(得分:12)

requestAnimationFrame现在是大多数浏览器的绝佳选择。它使用setInterval执行您所做的事情,但是以烘焙方式执行。可能最好的事情是它只在你的标签聚焦时运行。这可能是使用它的原因,如果你想让事情在后台运行,但经常(特别是对于渲染循环,只有在看到时才重要),不使用资源是很好的选项卡未激活时。

用法非常简单:

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

我讨厌在旧帖子上发帖,但这是&#34; javascript游戏循环的最佳google结果&#34;所以它确实需要包含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

的更深入的解释和使用提示

答案 3 :(得分:4)

是的。你想要setInterval

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

答案 4 :(得分:3)

这会吗?

setInterval(updateGameState, 1000 / 25);

25是你想要的FPS。你也可以把帧之间的毫秒数放在那里,25 fps就是40ms(1000/25 = 40)。

答案 5 :(得分:0)

答案 6 :(得分:0)

这些答案中许多都过时且不够理想。现在推荐的方法是使用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;
  loop(time);
});

答案 7 :(得分:0)

不确定这样做的效果如何,但这是一种使用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...
}