Javascript HTML5画布游戏循环增量时间计算
我是游戏开发新手。目前我正在做一个比赛的游戏,所以游戏应该是小的,这就是为什么我不使用任何现代流行的框架 在开发我的无限游戏循环时我找到了几篇文章和一些建议来实现它。现在看起来是这样的:Javascript HTML5画布游戏循环增量时间计算,javascript,html,canvas,game-engine,timedelta,Javascript,Html,Canvas,Game Engine,Timedelta,我是游戏开发新手。目前我正在做一个比赛的游戏,所以游戏应该是小的,这就是为什么我不使用任何现代流行的框架 在开发我的无限游戏循环时我找到了几篇文章和一些建议来实现它。现在看起来是这样的: self.gameLoop = function () { self.dt = 0; var now; var lastTime = timestamp(); var fpsmeter = new FPSMeter({decimals: 0, g
self.gameLoop = function () {
self.dt = 0;
var now;
var lastTime = timestamp();
var fpsmeter = new FPSMeter({decimals: 0, graph: true, theme: 'dark', left: '5px'});
function frame () {
fpsmeter.tickStart();
now = window.performance.now();
// first variant - delta is increasing..
self.dt = self.dt + Math.min(1, (now-lastTime)/1000);
// second variant - delta is stable..
self.dt = (now - lastTime)/16;
self.dt = (self.dt > 10) ? 10 : self.dt;
self.clearRect();
self.createWeapons();
self.createTargets();
self.update('weapons');
self.render('weapons');
self.update('targets');
self.render('targets');
self.ticks++;
lastTime = now;
fpsmeter.tick();
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
};
所以问题在于self.dt
我最终发现第一个变量不适合我的游戏,因为它会永远增加,武器的速度也会随之增加(例如this.position.x+=(Math.cos(this.angle)*this.speed)*self.dt;
第二个变量看起来更合适,但它是否对应于这种循环()?我没有检查代码中的数学逻辑。但是,下面是适合我的:
GameBox = function()
{
this.lastFrameTime = Date.now();
this.currentFrameTime = Date.now();
this.timeElapsed = 0;
this.updateInterval = 2000; //in ms
}
GameBox.prototype.gameLoop = function()
{
window.requestAnimationFrame(this.gameLoop.bind(this));
this.lastFrameTime = this.currentFrameTime;
this.currentFrameTime = Date.now();
this.timeElapsed += this.currentFrameTime - this.lastFrameTime ;
if(this.timeElapsed >= this.updateInterval)
{
this.timeElapsed = 0;
this.update(); //modify data which is used to render
}
this.render();
}
这个实现从CPU速度(滴答声)来看是理想的。希望你能利用它!这并不是你问题的答案,在不了解更多关于特定游戏的信息的情况下,我无法确定它是否对你有帮助,但你真的需要知道dt
(和FPS)吗
在我对JS游戏开发的有限尝试中,我发现通常你不需要计算任何类型的dt
,因为你通常可以根据预期的帧速率得出一个合理的默认值,并根据滴答声的数量简单地使任何基于时间的(比如武器重新加载)工作(也就是说,一个船首可能需要60次滴答声才能重新装载(~1秒@~60FPS))
我通常使用
window.setTimeout()
而不是window.requestAnimationFrame()
,我发现它通常提供更稳定的帧速率,这将允许您定义一个合理的默认值来代替dt
。从负面来看,游戏将更多地占用资源,在速度较慢的机器上性能较差(或者如果用户有很多其他东西在运行),但根据您的用例,这些可能不是真正的问题
现在,这纯粹是一个轶事建议,所以你应该对此持保留态度,但它在过去对我很有用。这完全取决于你是否介意游戏在旧的/功能较弱的机器上运行得更慢,以及你的游戏循环有多高效。如果它是简单的,不需要显示真实时间,那么你可能能够做到完全取消
dt
。现代版本的requestAnimationFrame现在发送一个时间戳,您可以使用它来计算经过的时间。当您所需的时间间隔过去后,您可以执行更新、创建和渲染任务
下面是示例代码:
var lastTime;
var requiredElapsed=1000/100; // desired interval is 10fps
requestAnimationFrame(loop);
function loop(now){
requestAnimationFrame(loop);
if(!lastTime){lastTime=now;}
var elapsed=lastTime-now;
if(elapsed>requiredElapsed){
// do stuff
lastTime=now;
}
}
下面是一个HTML5渲染系统的实现,使用固定的时间步长和可变的渲染时间: 它基于这篇文章: 以下是游戏循环:
//Set the frame rate
var fps = 60,
//Get the start time
start = Date.now(),
//Set the frame duration in milliseconds
frameDuration = 1000 / fps,
//Initialize the lag offset
lag = 0;
//Start the game loop
gameLoop();
function gameLoop() {
requestAnimationFrame(gameLoop, canvas);
//Calcuate the time that has elapsed since the last frame
var current = Date.now(),
elapsed = current - start;
start = current;
//Add the elapsed time to the lag counter
lag += elapsed;
//Update the frame if the lag counter is greater than or
//equal to the frame duration
while (lag >= frameDuration){
//Update the logic
update();
//Reduce the lag counter by the frame duration
lag -= frameDuration;
}
//Calculate the lag offset and use it to render the sprites
var lagOffset = lag / frameDuration;
render(lagOffset);
}
render
函数调用每个精灵上的render
方法,并引用lagOffset
function render(lagOffset) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
sprites.forEach(function(sprite){
ctx.save();
//Call the sprite's `render` method and feed it the
//canvas context and lagOffset
sprite.render(ctx, lagOffset);
ctx.restore();
});
}
下面是精灵的渲染方法,它使用滞后偏移来插值精灵在画布上的渲染位置
o.render = function(ctx, lagOffset) {
//Use the `lagOffset` and previous x/y positions to
//calculate the render positions
o.renderX = (o.x - o.oldX) * lagOffset + o.oldX;
o.renderY = (o.y - o.oldY) * lagOffset + o.oldY;
//Render the sprite
ctx.strokeStyle = o.strokeStyle;
ctx.lineWidth = o.lineWidth;
ctx.fillStyle = o.fillStyle;
ctx.translate(
o.renderX + (o.width / 2),
o.renderY + (o.height / 2)
);
ctx.beginPath();
ctx.rect(-o.width / 2, -o.height / 2, o.width, o.height);
ctx.stroke();
ctx.fill();
//Capture the sprite's current positions to use as
//the previous position on the next frame
o.oldX = o.x;
o.oldY = o.y;
};
重要的部分是这段代码,它使用lagOffset和帧之间精灵渲染位置的差异来确定其新的当前画布位置:
o.renderX = (o.x - o.oldX) * lagOffset + o.oldX;
o.renderY = (o.y - o.oldY) * lagOffset + o.oldY;
请注意,oldX
和oldY
值在方法结束后的每一帧都会重新计算,以便在下一帧中使用它们来帮助找出差异
o.oldX = o.x;
o.oldY = o.y;
实际上,我不确定这个插值是否完全正确,或者这是否是最好的方法。如果有人在读这篇文章时知道它是错误的,请告诉我们:)游戏引擎的一个很好的解决方案是在对象和实体中进行思考。你可以将世界上的所有东西都视为对象和实体。然后你想制作一个游戏对象管理器,其中包含所有游戏对象的列表。然后你想在引擎中创建一个通用的通信方法,以便游戏对象可以进行事件触发ers。游戏中的实体(例如,玩家)不需要从任何事物中获得渲染到屏幕或具有碰撞检测的能力。您可以在游戏引擎正在寻找的实体中简单地创建通用方法。然后让游戏引擎根据需要处理实体。可以创建游戏中的实体在游戏中的任何时候都会被破坏,所以你不应该在游戏循环中硬编码任何实体 您希望游戏引擎中的其他对象响应引擎接收到的事件触发器。这可以通过使用实体中的方法来完成,游戏引擎将检查该方法是否可用,以及该方法是否可用,并将事件传递给实体。不要将任何游戏逻辑硬编码到引擎中,否则会影响可移植性限制了你以后在游戏中扩展的能力 代码的问题是,首先调用不同的对象渲染和更新的顺序不正确。您需要调用所有更新,然后按该顺序调用所有渲染。另一个问题是,当您需要一个对象不再存在于游戏中,或者如果您以后想在游戏中添加更多对象 您的游戏对象将有一个
update()
和一个render()
您的游戏引擎将在对象/实体中查找该函数,并在每一帧调用它。您可以使用非常奇特的方式使引擎在调用它们之前检查游戏对象/实体是否具有这些函数。例如,您可能需要一个具有update()的对象
但从不在屏幕上渲染任何内容。您可以通过在调用游戏对象函数之前进行引擎检查,使游戏对象函数成为可选函数。为所有游戏对象提供init()
函数也是一个很好的做法。当游戏引擎启动场景并创建
window.requestAnimationFrame = window.requestAnimationFrame || function(callback){window.setTimeout(callback,16)};
gameEngine = function () {
this.frameCount=0;
self=this;
this.update = function(){
//loop over your objects and run each objects update function
}
this.render = function(){
//loop over your objects and run each objects render function
}
this.frame = function() {
self.update();
self.render();
self.frameCount++;
window.requestAnimationFrame(frame);
}
this.frame();
};