Реклама
Перетяжка // Коробка 3.0

Введение в spritesheet анимацию

Обложка: Введение в spritesheet анимацию

Анимация, основанная на spritesheet’ах, используется в играх довольно продолжительное время. В том числе в таких популярных играх, как Legend of Zelda: A Link to the Past или Cut the Rope. В этой статье мы поговорим о том, как работает такая анимация и как ее запрограммировать. В качестве языка программирования будем использовать JavaScript, но вы можете выбрать любой удобный для вас.

Прежде чем говорить о том, как программировать spritesheet анимацию, нам следует определить три термина: анимация, спрайт и, собственно, spritesheet.

Анимация

В далеком 1872 году английскому и американскому фотографу Эдварду Мейбриджу было поручено узнать, поднимает ли лошадь все свои 4 ноги во время бега. Мейбридж для этого использовал несколько камер, которые делали снимок, когда лошадь с наездником пробегала мимо. В итоге фотограф получил 16 изображений лошади. Кстати говоря, в парочке из них все четыре ноги лошади действительно находятся в воздухе.

Введение в spritesheet анимацию_4

Позднее Мейбридж повторил эксперимент, но поместил все фотографии в устройство, которое могло проецировать их в быстрой последовательности так, что получалась иллюзия бега лошади. Это устройство позднее назвали кинопроектор.

Введение в spritesheet анимацию_6

Процесс быстрой смены изображений для создания иллюзии движения назвали анимацией.

Спрайт

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

Введение в spritesheet анимацию_10

Спрайты являются популярным способом для создания больших и сложных сцен, так как вы можете манипулировать и управлять каждым изображением в отдельности. Это позволяет лучше контролировать происходящее на сцене.

Совсем не редкость, когда в игре существуют десятки, а то и сотни спрайтов. Загрузка каждого из них в качестве индивидуального и самостоятельного изображения будет потреблять много памяти и вычислительной мощности. Для того, чтобы избежать этих неудобств, и используют spritesheet’ы.

Spritesheet’ы

Когда вы кладете множество спрайтов в одно изображение, вы получаете spritesheet.

Введение в spritesheet анимацию_15

Spritesheet’ы используют для ускорения процесса отображения картинки на экране. Гораздо эффективнее и быстрее загрузить одно большое изображение и показать его часть, чем загрузить множество спрайтов и показать их.

Анимация, основанная на spritesheet, есть не более чем обычный spritesheet, который показывается не целиком и полностью, а покадрово. Причем эти кадры меняются с достаточно большой скоростью. Принцип работы такой анимации аналогичен принципу работы кинопроектора, созданного Мейбриджем.

Части spritesheet’а

Сам по себе spritesheet состоит из двух частей: кадров и циклов.

Кадр является обычным изображением (или спрайтом). На примере лошади Мейбриджа: каждое изображение лошади во всей анимации является кадром.

Если кадры поставить в правильном порядке, то получится непрерывное движение. Это и есть цикл.

Программирование spritesheet анимации

Существует 3 этапа создания spritesheet анимации:

  1. Создание изображения.
  2. Обновление изображения для каждого кадра анимации.
  3. Рисование анимации на экране.

Создание изображения

Начнем с создания функции (или класса), который будет обрабатывать нашу spritesheet анимацию. Эта функция будет создавать изображение и установит его путь для того, чтобы мы могли использовать его для дальнейшего рисования на экране.

			function SpriteSheet(path, frameWidth, frameHeight) {
 
   var image = new Image();
   var framesPerRow;
 
   // вычисление количества кадров в строке после загрузки изображения
   var self = this;
   image.onload = function() {
      framesPerRow = Math.floor(image.width / frameWidth);
   };
 
   image.src = path;
}
		

Так как spritesheet’ы могут иметь разные размеры, то, зная размер одного кадра, мы будем знать количество кадров в строке и столбце.

Очень важно, чтобы каждый кадр анимации имел один и тот же размер, иначе анимация получится не натуральная.

Обновление изображений

Чтобы обновить spritesheet анимацию, нам понадобится изменить тот кадр, который рисуется в данный момент. Ниже spritesheet разделен на кадры, каждый из которых пронумерован.

Введение в spritesheet анимацию_32

Менять текущий кадр на следующий нужно не моментально, а через какое-то время. То есть при показе одного из кадров требуется сделать определенную задержку перед тем, как поменять его.

Важно отметить, что не каждый spritesheet имеет доступный спрайт в каждом из своих кадров. Например, в spritesheet’е Мейбриджа 16-й спрайт отсутствует. Если бы мы не учли этого, то анимация получилась бы со вспышкой.

Избежать всплесков запросто: мы не будем отрисовывать эти пустые изображения:

			function SpriteSheet(path, frameWidth, frameHeight, frameSpeed, endFrame) {
 
  // код удален для краткости
 
  var currentFrame = 0;  // текущий кадр для отрисовки
  var counter = 0;       // счетчик ожидания
 
  // обновление анимации
  this.update = function() {
 
    // если подошло время смены кадра, то меняем
    if (counter == (frameSpeed - 1))
      currentFrame = (currentFrame + 1) % endFrame;
 
    // обновление счетчика ожидания
    counter = (counter + 1) % frameSpeed;
    }
};
		

При использовании операции по взятию остатка от деления (%) для currentFrame мы создаем непрерывный цикл чисел от 0 до endFrame. Таким образом мы воспроизводим только те кадры, изображения для которых у нас есть в spritesheet’е.

Рисование анимации

Вычисляем строку с нужным кадром, взяв остаток от деления номера текущего кадра на количество кадров в строке. Также вычисляем столбец с нужным кадром путем деления номера текущего кадра на количество кадров в строке.

Используя найденные величины мы можем найти координаты той рамки, внутри которой и находится искомый кадр:

			// отрисовка текущего кадра
   this.draw = function(x, y) {
      // вычисление столбика и строчки с нужным кадром
      var row = Math.floor(currentFrame / framesPerRow);
      var col = Math.floor(currentFrame % framesPerRow);
 
      ctx.drawImage(
         image,
         col * frameWidth, row * frameHeight,
         frameWidth, frameHeight,
         x, y,
         frameWidth, frameHeight);
  };
}
		

Теперь мы готовы создать анимацию:

			spritesheet = new SpriteSheet('Walk_Cycle_Image.png', 125, 125, 3, 16);
 
function animate() {
   requestAnimFrame( animate );
   ctx.clearRect(0, 0, 150, 150);
 
   spritesheet.update();
 
   spritesheet.draw(12.5, 12.5);
}
		

Несколько циклов в одном spritesheet

Приведенный выше код будет работать для любого spritesheet’а, содержащего ровно один цикл. Тем не менее, существуют spritesheet’ы с несколькими циклами (анимациями).

Для работы с несколькими анимациями в одном spritesheet’е, нам понадобится изменить наш принцип работы.

Создание изображения

Храним информацию об изображении и его размерах:

			function SpriteSheet(path, frameWidth, frameHeight) {
  this.image = new Image();
  this.frameWidth = frameWidth;
  this.frameHeight = frameHeight;
 
  // вычисление количества кадров в строке после загрузки изображения
  var self = this;
  this.image.onload = function() {
    self.framesPerRow = Math.floor(self.image.width / self.frameWidth);
  };
 
  this.image.src = path;
}
		

Обновление и рисование анимации

Функция Animation() будет отвечать за обновление и отрисовку картинки:

			function Animation(spritesheet, frameSpeed, startFrame, endFrame) {
 
  var animationSequence = [];  // array holding the order of the animation
  var currentFrame = 0;        // the current frame to draw
  var counter = 0;             // keep track of frame rate
 
  // создание последовательности из номеров кадров анимации
  for (var frameNumber = startFrame; frameNumber <= endFrame; frameNumber++)
    animationSequence.push(frameNumber);
 
  // обновление анимации
  this.update = function() {
 
    // если подошло время смены кадра, то меняем
    if (counter == (frameSpeed - 1))
      currentFrame = (currentFrame + 1) % animationSequence.length;
 
    // обновление счетчика ожидания
    counter = (counter + 1) % frameSpeed;
  };
 
  // отрисовка текущего кадра
  this.draw = function(x, y) {
    // вычисление количества кадров в строке после загрузки изображения
    var row = Math.floor(animationSequence[currentFrame] / spritesheet.framesPerRow);
    var col = Math.floor(animationSequence[currentFrame] % spritesheet.framesPerRow);
 
    ctx.drawImage(
      spritesheet.image,
      col * spritesheet.frameWidth, row * spritesheet.frameHeight,
      spritesheet.frameWidth, spritesheet.frameHeight,
      x, y,
      spritesheet.frameWidth, spritesheet.frameHeight);
  };
}
 
spritesheet = new SpriteSheet('Walk_Cycle_Image.png', 125, 125);
walk = new Animation(spritesheet, 3, 0, 15);
 
function animate() {
   requestAnimFrame( animate );
   ctx.clearRect(0, 0, 150, 150);
 
   walk.update();
 
   walk.draw(12.5, 12.5);
}
		

Поскольку spritesheet содержит множество кадров для различных анимаций, мы должны знать номера тех кадров, анимацию которых мы хотим сейчас воспроизводить.

Послесловие

Мы написали простенькую игру-раннер, используя ту самую анимацию, о которой говорилось в статье. Исходный код, приведенный в статье, вы можете увидеть, пройдя по этой ссылке.

Перевод статьи «An Introduction to Spritesheet Animation»