Добрались руки мои до Canvas. Посматривал я на него давненько, очень уж он мне в качестве инструмента для графиков приглянулся. Да и неделя Canvas на Хабре поддержала интерес.
Но вместо скучных графиков стал я копать в сторону анимации. Получилось, что отрисовываем сцену каждый раз по-новой, соответственно всю информацию о текущих кадрах храним в JS. И решил я попробовать составить простой алгоритм, который позволял бы хранить анимации для объекта, их состояние и выбор по желанию пользователя.
Тут все просто и стандартно.
Но вместо скучных графиков стал я копать в сторону анимации. Получилось, что отрисовываем сцену каждый раз по-новой, соответственно всю информацию о текущих кадрах храним в 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 (количество кадров).
Изображение представляет собой следующее:
Это самое большое изображение, ответственное за самую длинную анимацию «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 анимации:
- Стоит:
- Прыгает:
- Взрывается:
- Мертв:
Функция для смены анимации
Чтобы удобно менять анимации у нас есть 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 как массив объектов, по принципу библиотеки.
Конечно есть что улучшить, но на текущий момент я не нашел хорошей статьи на тему анимации и предлагаю свой вариант.
Код целиком
Посмотреть можно здесь.
Скачать можно здесь.