Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/javascript/458.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/html/88.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Javascript HTML5画布游戏循环增量时间计算_Javascript_Html_Canvas_Game Engine_Timedelta - Fatal编程技术网

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