Используйте таймер обратного отсчета JS в Canvas с requestAnimationFrame

Я делаю простую игру на холсте и использую requestAnimationFrame (версия Пола Айриша) для игрового цикла. Я использую в игре обратный отсчет javascript (показывающий секунды и сотые доли секунды), но он не ведет обратный отсчет с правильной скоростью. Я знаю, что это связано с частотой обновления игры, так что счетчик обновляет каждый кадр, а не каждую сотую секунду. Как я могу это исправить?

Вот объект таймера:

/**
 * The timer as an object
*/
function Timer(position, time) {
  this.position = position || new Vector(150,210);
  this.time = time || 6000;
}

Timer.prototype = {
  start: function() {
    console.log('start running');
    this.initial = this.time; //time in 100'ths of seconds
    this.count = this.initial;
    this.counter; //10 will  run it every 100th of a second
    clearInterval(this.counter);
    //this.counter = setInterval(this.timer, 10);
    this.timer();
  },

  timer: function() {
    //console.log(this.count);
    this.count--;
      var res = this.count / 100;
    //Show counter in canvas
    return 'Tid kvar: ' + res.toPrecision(this.count.toString().length) + ' sekunder';
  },

  draw: function(ct) {
    if(this.initial === undefined){this.start();} //Start the timer
    ct.save();
    if(this.count <=0){ //Remove timer if timer has reached 0
      ct.clearRect(this.position.x, this.position.y, Racetrack.innerTrackWidth, Racetrack.innerTrackHeight);
      return false;
    } else { //draw timer
      ct.save();
      ct.font = 'bold 3em arial';
      ct.fillStyle = 'orange';
      ct.fillText(this.timer(), this.position.x, this.position.y);
    }
    ct.restore();
  }, 
}

И игровой цикл и часть, вызывающая таймер:

  var init = function(canvas) {
    timer = new Timer(new Vector(160,210), 3000);
  }

  var render = function() {
    ct.clearRect(0,0,width,height);
    ship.draw(ct);
    racetrack.draw(ct);
    //draw timer or results if timer reached 0
    timer.draw(ct) !=false ? timer.draw(ct) : endFrame.draw(ct);
  };

  var gameLoop = function() {
    var now = Date.now();
    td = (now - (lastGameTick || now)) / 1000; // Timediff since last frame / gametick
    lastGameTick = now;
    if(timer.draw(ct) == false) { //stop the game if timer has reached 0.
      cancelRequestAnimFrame(gameLoop);
      console.log('Time\'s up!');
    } else { //Otherwise, keep the game going.
      requestAnimFrame(gameLoop);
    }
    update(td);
    render();
  };

Я также пробовал это как объект таймера, но отладка this.count показывает номер первого цикла, undefined второй цикл и NaN каждый цикл после этого (и я не уверен, что это в любом случае решит проблему синхронизации?):

function Timer(position, time) {
  this.position = position || new Vector(150,210);
  this.time = time || 6000;
}

Timer.prototype = {
  start: function() {
    console.log('start running');
    this.initial = this.time; //time in 100'ths of seconds
    this.count = this.initial;
    this.counter; //10 will  run it every 100th of a second
    clearInterval(this.counter);
    this.counter = setInterval(this.timer, 10);
    this.timer();
  },


  timer: function() {
    console.log(this.count);
    this.count--;
  },

  getTime: function() {
    var res = this.count / 100;
    return 'Tid kvar: ' + res.toPrecision(this.count.toString().length) + ' sekunder';
  },

  draw: function(ct) {
    if(this.initial === undefined){this.start();} //Start the timer
    ct.save();
    if(this.count <=0){ //Remove timer if timer has reached 0
      ct.clearRect(this.position.x, this.position.y, Racetrack.innerTrackWidth, Racetrack.innerTrackHeight);
      return false;
    } else { //draw timer
      ct.save();
      ct.font = 'bold 3em arial';
      ct.fillStyle = 'orange';
      ct.fillText(this.getTime(), this.position.x, this.position.y);
    }
    ct.restore();
  },
}

person Sluggo    schedule 23.04.2016    source источник
comment
requestAnimationFrame передает время в мс вызываемой функции в качестве первого аргумента. function myLoop(time){requestAnimationFrame(myLoop)} Использование setInterval, особенно с таким коротким интервалом, вызывает проблемы, поскольку все, что задерживает браузер на 1/100 секунды, в конечном итоге приведет к тому, что он попытается наверстать упущенное, и если у вас есть анимация, для рендеринга которой требуется около этого времени, она будет сбой браузера. Избегайте использования setInterval для чего-либо короче, чем браузер, к которому я привязан.   -  person Blindman67    schedule 24.04.2016
comment
@Blindman67 Есть предложения, как решить эту проблему по-другому?   -  person Sluggo    schedule 24.04.2016


Ответы (1)


Не уверен, что вы просите отображать время с интервалом 1/100 или время неточное при использовании setInterval.

A: setInterval не следует использовать для тайминга, так как он далек от точности и тем хуже, чем меньше интервал, и даже хуже, если у вас запущена анимация.

B: Обновление браузера с частотой 1/60 секунды. Вы можете заставить дисплей отображать и отображать с частотой 1/100 секунды, но тогда, в зависимости от графического оборудования, он может никогда не отображаться, потому что в это время дисплей сканирует пиксели в другом месте экрана. Или вы получите сдвиг, когда перезаписываете дисплей так же, как графика записывается на дисплей.

Лучший способ получить обратный отсчет с помощью requestAnimationFrame

var start = true;     // flags that you want the countdown to start
var stopIn = 3000;    // how long the timer should run
var stopTime = 0;     // used to hold the stop time
var stop = false;     // flag to indicate that stop time has been reached
var timeTillStop = 0; // holds the display time

// main update function
function update(timer){

    if(start){  // do we need to start the timer
        stopTime = timer + stopIn; // yes the set the stoptime
        start = false;             // clear the start flag
    }else{                         // waiting for stop
        if(timer >= stopTime){     // has stop time been reached?
            stop = true;           // yes the flag to stop
        }
    }

    timeTillStop = stopTime - timer;      // for display of time till stop
    // log() should be whatever you use to display the time.
    log(Math.floor(timeTillStop / 10) );  // to display in 1/100th seconds

    if(!stop){
        requestAnimationFrame(update); // continue animation until stop 
    }
}
requestAnimationFrame(update);  // start the animation

Забыл добавить.

С помощью этого метода вы никогда не получите точность в 100 секунд. Вы будете в среднем 7-8 мс. Но если вы не сделаете это намного сложнее, это лучшее, что вы можете сделать. Это среднее значение 7-8 мс является постоянным и одинаковым в течение 2 часов и 1 секунды и определяется только временем обновления анимации, составляющим около 16 нечетных мс.

person Blindman67    schedule 23.04.2016
comment
Спасибо, вроде хорошо! Однако один вопрос: что делает параметр timer? -Функции не передаются никакие параметры, но она принимает этот параметр. Откуда она берет свою ценность? - person Sluggo; 25.04.2016
comment
@Sluggo Время передается браузером в качестве аргумента, когда он вызывает функцию обратного вызова, которую вы установили через requestAnimationFrame. Это время в 1/1000 секунды, и большинство браузеров теперь имеют точность до 1 000 000 секунды, поскольку дополнительная точность представлена ​​​​в виде дробного компонента, поэтому значение 100,055 равно 100 055/1 000 000. Время - это время, прошедшее с момента запуска страницы, хотя когда начинается страница, я не знаю. Вы можете синхронизировать его в режиме реального времени, сравнив его с Date.now() и используя это смещение на протяжении жизни страницы. - person Blindman67; 25.04.2016