Pull to refresh

Анимация и Canvas

Reading time 9 min
Views 50K
Добрались руки мои до Canvas. Посматривал я на него давненько, очень уж он мне в качестве инструмента для графиков приглянулся. Да и неделя Canvas на Хабре поддержала интерес.

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

Канва


Тут все просто и стандартно.
<!DOCTYPE html>
<html>
  <head>
    <title>Animation test</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  </head>
  <body>    
    <canvas id="cnv" width="600" height="200">It's not working!</canvas>
    <select id="animations" name="animations" onchange="changeAnimation(this.value)">
      <option value="stop">Stop</option>
      <option value="jump">Jump</option>
      <option value="bum">Bum</option>
      <option value="dead">Dead</option>
    </select>
  </body>
</html>

* This source code was highlighted with Source Code Highlighter.


Под холстом добавлен Select со списком анимаций для объекта. Объект у меня будет один, хотя алгоритм не подразумевает каких либо ограничений.

Глобальные переменные

Теперь перейдем к JS. Здесь мне понадобятся несколько переменных для хранения объектов сцены и возможных анимаций.
//Сократим запись для потомков
function $(id) {
  return document.getElementById(id);
}
      
var сtx     = $('cnv').getContext('2d'); //Наша канва
var childs  = {};                        //Массив объектов сцены
var animate = {};                        //Массив анимаций для объектов

* This source code was highlighted with Source Code Highlighter.



В childs будут храниться все объекты сцены с базовыми параметрами. Мы будем пробегаться по массиву и отрисовывать один за другим.
В animate храним для объекта его анимации. Тут можно сделать и по-другому, к примеру в animate храним анимации только для объектов которым они нужны, а для прочих храним информацию по отрисовке в самом массиве childs. В моем варианте каждый объект должен иметь по меньшей мере одну анимацию, если хочет быть отображенным на холсте.

Заполняем массивы

Сначала добавим наш объект. Я не стал росписывать параметры, т.к. считаю что для краткости кода можно на этой пойти, да и имена все интуитивно понятные.

function initAnimation() {       

  //Добавляем шарик
  childs['ball'] = {
    'at' : 'jump', //Стартовая анимация
    'w'  : 30,   //Ширина объекта
    'h'  : 30,   //Высота объекта
    'fw' : 30,   //Ширина кадра анимации
    'x'  : 100,  //Положение по горизонтали
    'y'  : 100   //Положение по вертикали
  }

  //Добавляем в массив анимации для шарика
  animate['ball'] = {
    'jump': {                      //Ключ есть имя анимации
      'el'    : null,              //Объект Image
      'src'   : 'images/ball.png', //Путь к изображению
      'step'  : 0,                 //Текущий шаг анимации
      'speed' : 3,                 //Скорость анимации
      'curr'  : 0,                 //Счетчик кадров
      'steps' : 3,                 //Количество кадров анимации, считаем от 0
      'onend' : null               //Функция для вызова по окончании анмации
    },
    'bum': {
      'el'    : null,
      'src'   : 'images/ball_m.png',
      'step'  : 0,
      'speed' : 3,
      'curr'  : 0,
      'steps' : 7,
      'onend' : 'onBumEnd'
    },
    'stop': {
      'el'    : null,
      'src'   : 'images/ball_s.png',
      'step'  : 0,
      'speed' : 10,
      'curr'  : 0,
      'steps' : 0,
      'onend' : null
    },
    'dead': {
      'el'    : null,
      'src'   : 'images/ball_d.png',
      'step'  : 0,
      'speed' : 10,
      'curr'  : 0,
      'steps' : 0,
      'onend' : null
    }
  }

  //Идем по всем объектам
  for (var o in childs) {          

    //И по все их анимациям
    for (var a in animate[o]) {

      //Подгружаем изображения
      var img = new Image();
      img.src = animate[o][a].src;
      //Помещаем объект изобраения в анимацию
      animate[o][a].el = img;

    }

  }

}

* This source code was highlighted with Source Code Highlighter.



Немного поясню. Каждая анимация представляет собой файл со всеми кадрами. На холст я вывожу только часть изображения, соответствующую текущему кадру. Для этого я храню step и steps (количество кадров).

Изображение представляет собой следующее:
image
Это самое большое изображение, ответственное за самую длинную анимацию «Bum» в 8 кадров.

Speed и curr отвечают за скорость переключения кадров. Я перерисовываю холст каждые 16мс и чтобы кадры сменялись с требуемой скоростью я считаю curr и по достижении speed меняю кадр.

Onend я вызываю по окончании анимации. Здесь мы можем сменить тип анимации — со взрыва на отображение останков или удалить объект из массива. Для этого чуть выше у меня есть функция:
function onBumEnd() {
        
  //Меняем тип анимации на мертый шарик
  childs['ball'].at = 'dead';
  //Сбрасываем счетчик
  animate['ball'][childs['ball'].at].curr = 0;
        
}


* This source code was highlighted with Source Code Highlighter.



Анимации

Для объекта есть 4 анимации:
  • Стоит: stop
  • Прыгает: jump
  • Взрывается: bum
  • Мертв: dead


Функция для смены анимации

Чтобы удобно менять анимации у нас есть Select и функция для него:
function changeAnimation(value) {
        
  //Меняем анимацю на выбранную
  childs['ball'].at = value; 
  //Сбрасываем счетчик      
  animate['ball'][childs['ball'].at].curr = 0;
        
}


* This source code was highlighted with Source Code Highlighter.


Запускаем

Вот и готовы все изображения и массивы, и я запускаю анимацию.
function startAnimation() {

  //Запускаем единый таймер
  setInterval(function() {          

    //Чистим сцену
    ctx.save();
    ctx.fillStyle = '#FFFFFF';
    ctx.fillRect(0, 0, 600, 200);
    ctx.restore();

    //Проходим по всем объектам и отрисовываем
    for (var o in childs) {

      //Смотрим текущую анимацию
      if (animate[o]) {
        
        //Берем текущий шаг
        var step = animate[o][childs[o].at].step;

        //Рисуем его
        ctx.drawImage(
          animate[o][childs[o].at].el,     //Объект Image анимации
          Math.round(childs[o].fw * step), //Берем текущий кадр, ширина кадра * шаг
          0,                               //Кадры идут один за другим, тут 0
          childs[o].w,                     //Вырез в ширину объекта
          childs[o].h,                     //И в высоту
          childs[o].x,                     //Размещаем по горизонтали на холсте
          childs[o].y,                     //И по вертикали
          childs[o].w,                     //Ширина как у кадра
          childs[o].h                      //Ну и высота
        );
        
        //Проверяем счетчик и если достигли speed, переходим к следующему кадру
        if (animate[o][childs[o].at].curr >= animate[o][childs[o].at].speed) {

          //Проверяем, если кадр последний переходим к первому
          if (animate[o][childs[o].at].step >= animate[o][childs[o].at].steps) {

            animate[o][childs[o].at].step = 0;

            //Кадр последний, значит вызываем функцию, если есть
            if (animate[o][childs[o].at].onend)
              window[animate[o][childs[o].at].onend]();

          }
          else animate[o][childs[o].at].step++;

          //Сбрасываем счетчик
          animate[o][childs[o].at].curr = 0;

        }

        //Увеличиваем счетчик
        animate[o][childs[o].at].curr++;      

      }

    }

  }, 1000/16);

}


* This source code was highlighted with Source Code Highlighter.



В принципе, я думаю из комментариев понятно что за чем идет и как работает. В результате я получил возможность сохранять объекты сцены, динамически задавать анимацию и реагировать на ее окончание.
Поскольку часто объекты в анимации повторяются, то есть смысл использовать childs как массив объектов, по принципу библиотеки.
Конечно есть что улучшить, но на текущий момент я не нашел хорошей статьи на тему анимации и предлагаю свой вариант.

Код целиком

Посмотреть можно здесь.
Скачать можно здесь.
Tags:
Hubs:
+22
Comments 61
Comments Comments 61

Articles