2016-05-27 7 views
1

У меня есть код JavaScript, который использует анимацию на основе спрайтов на холсте и пытаюсь увидеть, могу ли я сделать ее более эффективной. Я уже использую requestAnimationFrame, но так как это взаимодействие, я также пытаюсь понять, как заставить его рисовать только кадры, когда установлен новый. Однако, неважно, что я пытаюсь сделать, по-прежнему, кажется, рисует новые кадры, даже когда анимация не работает. Иногда изменение кода даже, по-видимому, увеличивает использование ЦП. Я очень смущен, что происходит здесь.Рисование анимации на холсте только при установке новых кадров?

Вот исходный код:

function CanvasSprite(canvas, width, height, sprite_url, rows, columns, total_frames) { 
 
    \t this.canvas = canvas; 
 
    this.width = width; 
 
    \t this.height = height; 
 
    \t this.rows = rows; 
 
\t this.columns = columns; 
 
\t this.total_frames = total_frames; 
 
\t this.frame = 0; 
 

 
    var scope = this, 
 
    \t func = function(){ 
 
    \t \t \t scope.onSpriteSheet.call(scope); 
 
     \t \t } 
 
    this.load('img', 'spritesheet', sprite_url, func); 
 
}; 
 

 
CanvasSprite.prototype.onSpriteSheet = function() { 
 
    \t this.sw = this.spritesheet.width/this.columns; 
 
    \t this.sh = this.spritesheet.height/this.rows; 
 
    this.tick(new Date().getTime()); 
 
}; 
 

 
CanvasSprite.prototype.load = function(type, prop, url, callback) { 
 
    \t this[prop] = document.createElement(type); 
 
    \t this[prop].addEventListener('load', callback); 
 
    this[prop].src = url; 
 
}; 
 
\t 
 
CanvasSprite.prototype.draw = function() { 
 
\t var relativeFrame = Math.round(this.frame * (this.total_frames-1)); \t 
 
\t var column_frame = relativeFrame % this.columns; 
 
    \t \t \t 
 
    \t var sx = this.sw * column_frame; 
 
    \t var sy = this.sh * Math.floor(relativeFrame/this.columns); 
 

 
\t var context = this.canvas.getContext('2d'); 
 
\t context.clearRect(0, 0, this.width, this.height); 
 
    \t context.drawImage(this.spritesheet, sx, sy, this.sw, this.sh, 0, 0, this.width, this.height); 
 
}; 
 
\t \t 
 
CanvasSprite.prototype.tick = function(time) { 
 
     var scope = this, 
 
     func = function(time){ 
 
     scope.draw(time || new Date().getTime()); 
 
     requestAnimationFrame(func, scope.id); 
 
     //console.log("drawing"); 
 
     }; 
 
     func(); 
 
}; 
 

 
CanvasSprite.prototype.setFrame = function(frame) { 
 
\t this.frame = frame; 
 
    //this.tick(new Date().getTime()); 
 
    //putting tick() here actually makes it slower :p 
 
};

Одна из попыток изменить его дальше:

function CanvasSprite(canvas, width, height, sprite_url, rows, columns, total_frames) { 
 
    \t this.canvas = canvas; 
 
    this.width = width; 
 
    \t this.height = height; 
 
    \t this.rows = rows; 
 
\t this.columns = columns; 
 
\t this.total_frames = total_frames; 
 
    
 
    this.frameOld = null; //old frame for comparison 
 
\t this.frame = 0; 
 

 
    var scope = this, 
 
    \t func = function(){ 
 
    \t \t \t scope.onSpriteSheet.call(scope); 
 
     \t \t } 
 
    this.load('img', 'spritesheet', sprite_url, func); 
 
}; 
 

 
CanvasSprite.prototype.onSpriteSheet = function() { 
 
    \t this.sw = this.spritesheet.width/this.columns; 
 
    \t this.sh = this.spritesheet.height/this.rows; 
 
    if(this.frame != this.frameOld) { 
 
     this.tick(new Date().getTime()); //only call tick when new frame differs from old 
 
    }; 
 
}; 
 

 
CanvasSprite.prototype.load = function(type, prop, url, callback) { 
 
    \t this[prop] = document.createElement(type); 
 
    \t this[prop].addEventListener('load', callback); 
 
    this[prop].src = url; 
 
}; 
 
\t 
 
CanvasSprite.prototype.draw = function() { 
 
\t var relativeFrame = Math.round(this.frame * (this.total_frames-1)); \t 
 
\t var column_frame = relativeFrame % this.columns; 
 
    \t \t \t 
 
    \t var sx = this.sw * column_frame; 
 
    \t var sy = this.sh * Math.floor(relativeFrame/this.columns); 
 

 
\t var context = this.canvas.getContext('2d'); 
 
\t context.clearRect(0, 0, this.width, this.height); 
 
    \t context.drawImage(this.spritesheet, sx, sy, this.sw, this.sh, 0, 0, this.width, this.height); 
 
}; 
 
\t \t 
 
CanvasSprite.prototype.tick = function(time) { 
 
     var scope = this, 
 
     func = function(time){ 
 
     scope.draw(time || new Date().getTime()); 
 
     requestAnimationFrame(func, scope.id); 
 
     console.log("drawing"); 
 
     }; 
 
     func(); 
 
}; 
 

 
CanvasSprite.prototype.setFrame = function(frame) { 
 
    this.frameOld = this.frame; //update frameOld with previous one 
 
\t this.frame = frame; //set new frame 
 
};

Независимо от того, что я делаю, консоль говорит, что он синхронизируется с системными часами, даже когда я не обновляю фреймы. Профили CPU также отражают это. Похоже, здесь есть что-то важное. Может быть, холст уже оптимизирует его за кулисами, поэтому мой JS не имеет никакого значения или только замедляет его с помощью ненужной логики?

+1

Вы можете использовать 'isDirty' флаг, который устанавливается истина от каждого вызова, который изменил бы кадр как-то , Затем в основном цикле нужно только рисовать, если флаг является истинным, а также сбросить его на false. – K3N

+0

Но разве это не совсем другая версия именно того, что я делаю во втором примере кода? Я попытался поставить условие в цикл ничьей, а результаты совпадают. –

+0

Извините, поцарапайте это. Сравнение с старыми кадрами не работало, и, несмотря на это, было ужасно неэффективно, поскольку значения фактически часто соответствуют интервалам подкадра. Теперь я просто пытаюсь выяснить, где поставить свой условный, потому что я могу заставить его работать внутри реального цикла рисования, что не повышает эффективность, так как моя функция тика, где я запрашиваю системные часы и вызываю 'requestAnimationFrame', безусловно, самый большой процессор. –

ответ

0

Решил сам! Прежде всего, используя флаг isDirty вместо того, чтобы пытаться сравнивать фреймы. Затем, поскольку взаимодействие фактически обновляет анимацию в интервалах подкадра, перемещается строка, которая вычисляет фактический кадр, который будет втягиваться в функцию setFrame. Также была перемещена функция tick, которая синхронизирует анимацию в setFrame и использовала вызов для этого при загрузке, чтобы начать с нулевого кадра. Таким образом, это не постоянно передается новая дата и время. Наконец, условие, чтобы проверить, нужно ли рисовать новый фрейм, должно быть внутри замыкания, которое вызывает функцию draw. Это связано с тем, что requestAnimationFrame на самом деле намного больший процессор, чем любые вызовы на холст.

В результате? Нет использование процессора, когда он не обновляется и резко снижается, когда это :)

function CanvasSprite(canvas, width, height, sprite_url, rows, columns, total_frames) { 
 
    \t this.canvas = canvas; 
 
    this.width = width; 
 
    \t this.height = height; 
 
    \t this.rows = rows; 
 
\t this.columns = columns; 
 
\t this.total_frames = total_frames; 
 
    this.isDirty = true; 
 

 
    var scope = this, 
 
    \t func = function(){ 
 
    \t \t \t scope.onSpriteSheet.call(scope); 
 
     \t \t } 
 
    this.load('img', 'spritesheet', sprite_url, func); 
 
}; 
 

 
CanvasSprite.prototype.onSpriteSheet = function() { 
 
    \t this.sw = this.spritesheet.width/this.columns; 
 
    \t this.sh = this.spritesheet.height/this.rows; 
 
    this.setFrame(0); 
 
}; 
 

 
CanvasSprite.prototype.load = function(type, prop, url, callback) { 
 
    \t this[prop] = document.createElement(type); 
 
    \t this[prop].addEventListener('load', callback); 
 
    this[prop].src = url; 
 
}; 
 
\t 
 
CanvasSprite.prototype.draw = function() { \t 
 
\t var column_frame = this.frame % this.columns; 
 
    \t var sx = this.sw * column_frame; 
 
    \t var sy = this.sh * Math.floor(this.frame/this.columns); 
 
    
 
\t var context = this.canvas.getContext('2d'); 
 
    context.clearRect(0, 0, this.width, this.height); 
 
    context.drawImage(this.spritesheet, sx, sy, this.sw, this.sh, 0, 0, this.width, this.height); 
 
}; 
 
\t \t 
 
CanvasSprite.prototype.tick = function(time) { 
 
    var scope = this, 
 
    func = function(time){ 
 
     if (scope.isDirty) { 
 
      scope.draw(time || new Date().getTime()); 
 
      requestAnimationFrame(func, scope.id); 
 
      scope.isDirty = false; 
 
      //only draw to canvas when new frame differs from old 
 
     }; 
 
    }; 
 
    func(); 
 
}; 
 

 
CanvasSprite.prototype.setFrame = function(frame) { 
 
    var tempFrame = Math.round(frame * (this.total_frames-1));; 
 
    if(tempFrame != this.frame) { 
 
     this.frame = tempFrame; 
 
     this.isDirty = true; 
 
     this.tick(new Date().getTime()); 
 
    } 
 
};