有没有一种简单的方法在JavaScript中创建游戏循环?就像这样...
onTimerTick() {
// update game state
}
有没有一种简单的方法在JavaScript中创建游戏循环?就像这样...
onTimerTick() {
// update game state
}
有许多种使用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++;
}
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);
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框架,您可以在其基础上构建游戏。
setInterval(onTimerTick, 33); // 33 milliseconds = ~ 30 frames per sec
function onTimerTick() {
// Do stuff.
}
requestAnimationFrame
。 - CerlancismrequestAnimationFrame现在在大多数浏览器中都是一个很好的替代方法。它可以用来代替setInterval,但是以一种更加内置的方式实现。它最棒的地方可能在于只有在标签页处于激活状态时才会运行。如果你想让事情在后台运行,那可能是不使用它的一个原因,但通常情况下(特别是对于只有在可见时才有意义的渲染循环),当标签页不活动时不使用资源是很好的。
使用方法非常简单:
function gameLoop(){
window.requestAnimationFrame(gameLoop);
Game.update();
}
我不喜欢在过时的帖子上发帖,但这是“javascript游戏循环”的谷歌搜索结果之一,所以它真的需要包括requestAnimationFrame。
针对旧浏览器的Polyfill代码从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
来完成此操作。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);
});
固定时间步长
如果你想实现固定的时间步长,只需累加 delta-time 直到达到某个阈值,然后更新游戏。
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);
});
requestAnimationFrame
来处理与动画无关的逻辑或物理逻辑似乎不太合适。您是否看到过任何官方指导或任何负面影响/原因,说明这可能不是一个好主意? - undefined是的,你需要 setInterval
:
function myMainLoop () {
// do stuff...
}
setInterval(myMainLoop, 30);
这个可以吗?
setInterval(updateGameState, 1000 / 25);
其中25是您期望的FPS。您还可以将帧之间的毫秒数放在那里,以25 fps为例,该值应为40ms(1000/25 = 40)。
不确定这种方法的效果如何,但这里有一种使用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...
}