Лучший способ для простой игровой цикл в Javascript? - PullRequest
20 голосов
/ 24 декабря 2009

Есть ли простой способ сделать игровой цикл в JavaScript? что-то вроде ...

onTimerTick() {
  // update game state
}

Ответы [ 7 ]

30 голосов
/ 16 мая 2013

Существует множество способов достичь этого с помощью 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/

23 голосов
/ 24 декабря 2009
setInterval(onTimerTick, 33); // 33 milliseconds = ~ 30 frames per sec

function onTimerTick() {
    // Do stuff.
}
12 голосов
/ 28 мая 2015

requestAnimationFrame - отличная альтернатива, доступная сейчас в большинстве браузеров. Он делает то, что вы делаете с setInterval, но в запеченном виде. Наверное, самое приятное в этом то, что он работает только тогда, когда ваша вкладка сфокусирована. Это может быть причиной , а не , чтобы использовать его, если вы хотите, чтобы все работало в фоновом режиме, но часто (особенно для циклов рендеринга, которые имеют значение только при просмотре), здорово не использовать ресурсы, когда вкладка не активна .

Использование довольно просто:

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

Ненавижу публиковать сообщения в старых темах, но это лучший результат Google для "javascript game loop", поэтому он действительно должен включать requestAnimationFrame.

Pollyfill для старых браузеров, скопированный с paulirish :

(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 голосов
/ 24 декабря 2009

Да. Вы хотите setInterval:

function myMainLoop () {
  // do stuff...
}
setInterval(myMainLoop, 30);
3 голосов
/ 24 декабря 2009

Будет ли это делать?

setInterval(updateGameState, 1000 / 25);

Где 25 - желаемый FPS. Вы также можете указать количество миллисекунд между кадрами, которое при 25 кадрах в секунду будет 40 мс (1000/25 = 40).

0 голосов
/ 08 июня 2019

Многие из этих ответов устарели и неоптимальны. Рекомендованный способ сделать это сейчас - использовать 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);
});
0 голосов
/ 31 августа 2011
...