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

发布于 2024-08-15 16:36:57 字数 110 浏览 2 评论 0原文

有没有一种简单的方法可以用 JavaScript 制作游戏循环?像...

onTimerTick() {
  // update game state
}

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

onTimerTick() {
  // update game state
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

万劫不复 2024-08-22 16:36:57

根据您的应用程序,使用 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/

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.

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.

This is a good website to get started with:

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.

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.

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

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.

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

记忆里有你的影子 2024-08-22 16:36:57
setInterval(onTimerTick, 33); // 33 milliseconds = ~ 30 frames per sec

function onTimerTick() {
    // Do stuff.
}
setInterval(onTimerTick, 33); // 33 milliseconds = ~ 30 frames per sec

function onTimerTick() {
    // Do stuff.
}
赴月观长安 2024-08-22 16:36:57

requestAnimationFrame 是现在大多数浏览器中可用的一个很好的替代方案。它完成了您使用 setInterval 所做的事情,但以一种烘焙的方式进行。也许它最好的一点是它只在您的选项卡聚焦时运行。如果您希望在后台运行,这可能是不使用它的一个原因,但通常(特别是对于仅在可见时才重要的渲染循环),当选项卡不可用时不使用资源是很好的。积极的。

用法非常简单:

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

我讨厌在旧线程上发帖,但这是“javascript 游戏循环”的最佳谷歌结果,因此它确实需要包含 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

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 not to 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.

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.

Pollyfill for old browsers copied from 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);
        };
}());

Deeper explanation and usage tips on creativeJS

夏了南城 2024-08-22 16:36:57

其中许多答案已经过时且不是最佳的。现在推荐的方法是使用 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);
});

固定时间步长

如果您想实现固定时间步长,只需累积增量时间直到达到某个阈值,然后更新您的游戏。

const timeStep = 1.0 / 60.0;

let previousTime = 0.0;
let delta = 0.0;

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

  // Accumulate delta time
  delta = delta + dt;

  // Update the previous time
  previousTime = time;

  // Update your game
  while (delta > timeStep) {
    update(timeStep);

    delta = delta - timeStep;
  }

  // Render your game
  render();

  // Repeat
  window.requestAnimationFrame(loop);
};

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

  window.requestAnimationFrame(loop);
});

Many of these answers are outdated and suboptimal. The recommended way to do this now is using 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;

  window.requestAnimationFrame(loop);
});

Fixed time-step

If you want to achieve a fixed time-step, simply accumulate delta-time until it reaches some threshold, then update your game.

const timeStep = 1.0 / 60.0;

let previousTime = 0.0;
let delta = 0.0;

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

  // Accumulate delta time
  delta = delta + dt;

  // Update the previous time
  previousTime = time;

  // Update your game
  while (delta > timeStep) {
    update(timeStep);

    delta = delta - timeStep;
  }

  // Render your game
  render();

  // Repeat
  window.requestAnimationFrame(loop);
};

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

  window.requestAnimationFrame(loop);
});
骑趴 2024-08-22 16:36:57

是的。您需要 setInterval

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

Yep. You want setInterval:

function myMainLoop () {
  // do stuff...
}
setInterval(myMainLoop, 30);
奈何桥上唱咆哮 2024-08-22 16:36:57

这样可以吗?

setInterval(updateGameState, 1000 / 25);

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

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

影子是时光的心 2024-08-22 16:36:57

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

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

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...
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文