Спрайтовая анимация. Sprite animation – делаем спрайтовую анимацию с помощью CSS

Всем привет. Пару дней назад совершенно случайно наткнулся в залежах дисков на «Космические рейнджеры 2: Доминаторы». Устанавливать не стал, так как сейчас не хватает времени, чтобы как следует в неё погрузиться. А решил посмотреть, что на диске находится. Посмотрел «Фан-Арт» и там увидел программку для ковыряния ресурсов рейнджеров. Вот и решил посмотреть, из чего же сделаны наши доминаторы. Покликав немного, нашел файлики с анимацией в формате GAI. Начал любоваться той анимацией. Захотел их сохранить в «гифки», но не как не давала та программка сохранить анимацию? Можно либо сохранить текущий кадр, либо все файлы в PNG. Я решил сохранить все кадры, а их было - 150. Картинки все есть, а почему бы не сделать с ними ту же анимацию.

Спрайт

Для того чтобы порадовать себя такой анимацией взял в помощь CSS + JS. А что мне делать со 150-ю файлами изображений? Их вес не критичен, хоть и весят они в сумме больше мегабайта. Главные проблемы с их одновременной загрузкой и манипуляцией. Поэтому я решил «склеить» их в одну. Грузить только одну, да и при помощи CSS + JS придется только правильно позиционировать её.
Осталось выбрать метод «склейки». Я же программист, а мы все ленивые:), поэтому я сразу отбросил ручную склейку в графическом редакторе. Первым делом я бросился, как обычно, к PHP и библиотеке GD. Но она оставляет желать лучшего по работе с полупрозрачными PNG. Дальше я подумал, а чем же ещё можно «склеить» картинки? И остановился на том, что сейчас у всех на слуху, что считается сейчас модным - HTML5. За работу с графикой у него отвечает - Canvas, причем мне очень нравится эта «вкусняшка». Вот поэтому все же решил «клеить» на «холсте».
И так добавлю я ка этот тег в HTML:

Не поддерживается
В JS укажу маску для имени картинки, первую картинку и номер последней (мне не пришлось переименовывать картинки, так как они все идут по порядку). Это должно выглядеть примерно так:

Var first = "000"; //номерная часть имени первой картинки var last = 49; //номер последней картинки var num = 0; //итератор var maskFileName = ["2HULL_PEOPLE_P_A_", ".png"];//маска имени картинки var dir = "ship"; //директория, в которой лежат картинки

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

Var canvas = document.getElementById("sprite"); //выберем наш канвас canvas.width = (last + 1) * 75; //задаем ширину, в зависимости от количества canvas.height = 75; var width = 0; //переменная, в которую будем записывать сдвиг var context = canvas.getContext("2d"); //получаем контекст

Для облегчения перевода из числа в строку (аналог str_pad из PHP) написал функцию-преобразователь с диким названием - zerofikator():

Function zerofikator(int, length) { //на вход получаем число и длину строки var prefix = ""; for (var i = num.toString().length; i < length; i++) { prefix += "0"; } return prefix + num; }

Function draw() { var img = document.createElement("img"); /* каждый раз при вызове этой функции мы создаем новый обьект-изображение */ img.onload = function () { //когда изображение загрузится рисуем им на канвасе context.drawImage(img, width, 0); width += 75; //при этом каждый раз сдвигаем на ширину изображения, чтобы рисовать справа от предыдущего рисунка, а не на нем if (zerofikator(num, first.length) != zerofikator(last, first.length)) { //проверяем достигли ли мы последнего рисунка num++; //увиличуем итератор draw(); //и запускаем функцию вновь } } img.src = dir + "/" + maskFileName + zerofikator(num, first.length) + maskFileName; //собираем имя файла картинки для загрузки } draw(); //вызываем функцию впервые

После запуска такой страницы мы увидим широкое покадровое изображение, которое, если мы сохраним - сможем назвать спрайтом. Кстати, сохраненный спрайт весит 615 KB, а 150 картинок 1 189 KB, хм, вот и ещё один плюс:).

Решил добавить преобразование сразу в файл по клику на канвас (решает проблему сохранения для некоторых браузеров):

Canvas.onclick = function () { window.location = context.canvas.toDataURL("image/png"); };

Анимация

Ну а теперь, можно приступить и к анимации.
В HTML добавляем пару «дивчиков», с которыми мы дальше будем работать:

Var styles = {}; styles.cursor = "pointer"; //чтобы видно было что это наш элемент, меняем курсор styles.width = "75px"; //размеры элемента styles.height = "75px"; var elementId = "gun"; // id элемента, в котором будет анимация var imgName = "canvas.png"; // имя файла спрайта

Function spriteAnimation(elementId, imgName, styles) { var img = document.createElement("img"); var offset = 0; img.onload = function () { //как только спрайт загружается var element = document.getElementById(elementId); element.style.cursor = styles.cursor; element.style.width = styles.width; element.style.height = styles.height; element.style.background = "url("" + imgName + "") " + offset + "px 50%"; //меняем стили для нашего элемента var i = 0; element.onmouseover = function() { //вешаем обработчик на наведение мыши interval = setInterval(function() { //запускаем интервал if (offset < img.width) { //для смены позиции изображения i++; // если дошли до конца спрайта } else { i = 0; // то возвращаемся к началу } offset = 75 * i; //сдвиг по слайду element.style.background = "url("" + imgName + "") " + offset + "px 50%"; //меняем позиционирование спрайта } , 1000/24) //24 кадра в секунду } element.onmouseout = function(){ //вешаем обработчик на убирание курсора мыши clearInterval(interval) //удаляем интервал (прекращаем анимацию) } } img.src = imgName; //даем имя нашего спрайта }

Ах да, нужно ведь ещё вызвать эту функцию:

SpriteAnimation(elementId, imgName, styles); spriteAnimation("ship", "ship.png", styles);

И, чтобы смотрелось к месту, можно добавить картинку с фоном и правильно спозиционировать:

переменная rotate , которая задает угол поворота спрайта.

Нажатие на кнопку A приводит к изменению цвета спрайта – новый цвет выбирается случайным образом. Нажатие на кнопку S приводит к уменьшению параметра scale , который отвечает за размер спрайта, выводимого на экран, нажатие на кнопку W приводит к увеличению параметра scale , и, соответственно, к уменьшению размера спрайта.

Параметр origin задает начало координат для спрайта. По умолчанию координата позиции спрайта соответствует его левому верхнему углу. Относительно левого верхнего угла, в таком случае, осуществляется и вращение спрайта. Для того, чтобы вращение происходило вокруг центра спрайта мы записываем в переменную Origin результат от деления длины и ширины спрайта на 2. В итоге спрайт , во-первых, выводится на экран с учетом нового для него начала координат, а во-вторых – при вращении спрайта оно осуществляется вокруг центра спрайта, а не вокруг его левого верхнего угла. Рассмотрим подробнее команду Draw , которую мы использовали для вывода спрайта на экран. В табл. 10.1. описан каждый из ее параметров.

Таблица 10.1. Описание параметров команды Draw
Элемент Описание
MySprite Текстура спрайта
position Позиция спрайта
spRec Прямоугольник, ограничивающий спрайт в текстуре, может применяться для вывода различных участков текстуры
color Оттенок спрайта
rotation Угол поворота спрайта
origin Начало координат спрайта, относительно которого осуществляется поворот и вывод спрайта на экран
scale Размер спрайта.
SpriteEffects.None Эффект вывода спрайта – позволяет поворачивать спрайт на 180 градусов или, если установлен параметр None – никак не влияет на положение спрайта
(float) 0 Глубина спрайта. Может изменяться от 0 до 1

На рис. 10.1. вы можете видеть игровой экран проекта P6_1.

Теперь рассмотрим анимацию спрайтов.

Анимация спрайтов

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

Создадим спрайт , который будем анимировать. Мы использовали для анимации спрайт , состоящий из двух изображений (рис. 10.2.) . При его анимации будет создан эффект мигания – черный цвет будет сменяться зеленым.

Создадим стандартный игровой проект, назовем его P6_2. В листинге 10.2. приведен код класса Game1 . В этом проекте мы обошлись без создания дополнительных игровых компонентов, реализовав всю необходимую функциональность в основном игровом классе.

Using System; using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.GamerServices; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Net; using Microsoft.Xna.Framework.Storage; namespace P6_2 { ///

/// This is the main type for your game /// public class Game1: Microsoft.Xna.Framework.Game { GraphicsDeviceManager graphics; SpriteBatch spriteBatch; Texture2D texture; //Количество кадров в изображении const int frames = 2; //Частота анимации - кадров в секунду const int framesPerSec =10; //Текущий кадр для вывода на экран int numberOfFrame=0; //Время, в течение которого отображается один кадр float timePerFrame; //Время, которое прошло с начала отображения текущего кадра float totalElapsed; //Позиция вывода спрайта Vector2 position; //Прямоугольник для задания позиции кадра в изображении Rectangle sprRec; //Ширина кадра int widthFrame = 64; public Game1() { graphics = new GraphicsDeviceManager(this); Content.RootDirectory = "Content"; } protected override void Initialize() { position = new Vector2(100, 100); //Время для одного кадра вычисляется как результат деления 1 секунды //на заданное количество кадров в секунду timePerFrame = (float)1 / framesPerSec; sprRec = new Rectangle(0, 0, 64, 64); base.Initialize(); } protected override void LoadContent() { // Create a new SpriteBatch, which can be used to draw textures. spriteBatch = new SpriteBatch(GraphicsDevice); texture = Content.Load("animation"); // TODO: use this.Content to load your game content here } protected override void UnloadContent() { // TODO: Unload any non ContentManager content here } //Эта процедура используется для анимации спрайта //Она принимает количество секунд, прошедших с предыдущего вызова //процедуры Update void ChangeFrame(float elpT) { //Увеличим общее время отображения спрайта на //время, прошедшее с последнего вызова процедуры totalElapsed += elpT; //если общее время отображения спрайта больше времени, //выделенного на один спрайт if (totalElapsed > timePerFrame) { //Если номер кадра равен количеству кадров-1 if (numberOfFrame == frames-1) { //установим номер кадра в 0 numberOfFrame = 0; } //иначе увеличим номер кадра на 1 else numberOfFrame++; //создадим новый прямоугольник //Его координата X соответствует координате левого верхнего угла //кадра, Y равно 0, длина и ширина всегда равны 64 sprRec = new Rectangle((int)widthFrame * numberOfFrame, 0, 64, 64); //сбросим totalElapsed в 0 totalElapsed = 0; } } protected override void Update(GameTime gameTime) { //Вызов процедуры анимации спрайта //в качестве параметра передается время,прошедшее после //последнего вызова Update ChangeFrame((float)gameTime.ElapsedGameTime.TotalSeconds); base.Update(gameTime); } protected override void Draw(GameTime gameTime) { graphics.GraphicsDevice.Clear(Color.CornflowerBlue); spriteBatch.Begin(); spriteBatch.Draw(texture, position, sprRec, Color.White); spriteBatch.End(); base.Draw(gameTime); } } } Листинг 10.2. Код класса Game1

Если кратко описать последовательность работы этого проекта, получится следующее: в цикле Update() мы вызываем процедуру ChangeFrame() , которой передаем время, прошедшее после последнего вызова Update() . Внутри этой процедуры мы проверяем, достаточно ли времени текущий

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

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

Анимация

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

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

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

Спрайт

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

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

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

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

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 имеет доступный спрайт в каждом из своих кадров. Например, в 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 / spritesheet.framesPerRow); var col = Math.floor(animationSequence % 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 содержит множество кадров для различных анимаций, мы должны знать номера тех кадров, анимацию которых мы хотим сейчас воспроизводить.

Послесловие

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

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

Давайте на какое-то время отвлечемся от игры и поговорим об анимации вообще. Представим, что нам требуется нарисовать человечка, который шагает слева направо по экрану. Как это можно реализовать? Обычно новичкам эта задача кажется непомерно трудной. На самом деле здесь нет ничего сложного. Идея взята из кинематографа. У нас должен быть набор изображений, представляющий собой "фотографии" нашего человечка в разные, достаточно близкие, моменты времени. Быстро меняя кадры, мы увидим, что наша картинка начала двигаться. Ситуацию с походкой упрощает тот факт, что она носит периодический характер. Грубо говоря, чтобы получить красивую и достоверную анимацию нам достаточно иметь кадры с момента, когда человечек опирается, скажем, на левую ногу, до момента, когда он сделав два шага вновь на нее обопрется.

Давайте рассмотрим пример, состоящий из нескольких кадров. Не будем заморачиваться на графике и возьмем готовый рисунок из некогда популярной компьютерной игры Monkey Island.

В компьютерной графике и gamedev-е широко используется понятиеспрайт (sprite ). В современной трактовке оно означает графический объект, который может перемешаться по экрану и при этом изменять свой вид. Со спрайтом связан рисунок, содержащий раскадровку и, в зависимости от ситуации, спрайт имеет вид того или иного кадра. Ниже приведет пример рисунка с последовательностью кадров, который будет использоваться нами для создания спрайта.

Это изображение имеет ширину в 150 точек, и содержит 5 кадров, то есть ширина каждого кадра составляет 30 пикселей.

Чтобы создать анимацию мы можем загрузить каждый кадр, как отдельную картинку и затем через равные интервалы времени выводить их последовательно на экран. Можно сделать по-другому: загрузить одну картинку, содержащую все кадры, а затем налету разбивать ее на кадры и выводить требуемый в данный момент кадр. На самом деле это довольно просто. Мы знаем, что наша картинка содержит 5 кадров шириной 30 пикселей. Определим прямоугольник, имеющий ширину кадра (30 точек) и высоту всего изображения. На приведенном ниже рисунке синими прямоугольниками отмечены первые два кадра.

Давайте продолжим разработку нашей игры. Создадим проект. За основу возьмем разработанный на предыдущих уроках пример Droid_03. Добавим в папку res/drawable-mdpi проекта файл Создадим новый класс для нашего персонажа. Поскольку в Monkey Island персонаж зовут Elaine, назовем класс ElaineAnimated.

public class ElaineAnimated{ private static final String TAG= ElaineAnimated. class . getSimpleName() ; private Bitmap bitmap; // Картинка с анимационной последовательностью private Rect sourceRect; // Прямоугольная область в bitmap, которую нужно нарисовать private int frameNr; // Число кадров в анимации private int currentFrame; // Текущий кадр private long frameTicker; // время обновления последнего кадра private int framePeriod; // сколько миллисекунд должно пройти перед сменой кадра (1000/fps) private int spriteWidth; // ширина спрайта (одного кадра) private int spriteHeight; // высота спрайта private int x; // X координата спрайта (верхний левый угол картинки) private int y; // Y координата спрайта (верхний левый угол картинки) }

Здесь bitmap - png рисунок, содержащий все кадры; sourceRect - прямоугольная область, которая "очерчивает" в рисунке границы текущего кадра; frameTicker - переменная, содержащая время, которое прошло с момента последной смены кадра анимации. Указанная в комментарии переменная fps обозначает не fps игры, а fps спрайта, то есть сколько раз изменяется кадр спрайта за секунду. Для плавной анимации это значение должно иметь величину порядка 25-30, однако для наших учебных целей подойдет и более скромное число 5. Мы не можем поставить здесь большее значение, поскольку у нас всего 5 кадров в рисунке. framePeriod - период между сменой кадра в миллисекундах. Для нашего случая, когда мы хотим менять 5 кадров в секунду эта величина равна 200 мс.

Напишем конструктор

public ElaineAnimated(Bitmap bitmap, int x, int y, int width, int height, int fps, int frameCount) { this. bitmap= bitmap; this. x= x; this. y= y; currentFrame= 0 ; frameNr= frameCount; spriteWidth= bitmap. getWidth() / frameCount; spriteHeight= bitmap. getHeight() ; sourceRect= new Rect(0 , 0 , spriteWidth, spriteHeight) ; framePeriod= 1000 / fps; frameTicker= 0l; }

Подразумевается, что кадры имеют одинаковую ширину, поэтому spriteWidth вычисляется, как общая ширина рисунков поделенная на число кадров. В конструктор передается параметр fps, который обозначает число кадров спрайта в секунду.

Добавим в конструктор класса MainGamePanel строку, создающую объект для нашего нового анимированного персонажа

public MainGamePanel(Context context) { super(context) ; // Сообщаем, что обработчик событий от поверхности будет реализован // в этом классе. getHolder() . addCallback(this) ; elaine= new ElaineAnimated( BitmapFactory. decodeResource(getResources() , R. drawable. walk_elaine) , 10 , 50 // начальное положение , 30 , 47 // ширина и высота спрайта , 5 , 5 ) ; // FPS и число кадров в анимации ...

Добавим в класс метод update, который будет изменять текущий кадр в зависимости от текущего времени. Мы специально не привязываемся к частоте обновления игрового цикла, чтобы не нарушать процессор лишней работой. Например, допустим, что наша программа запущена на очень продвинутом телефоне, который обеспечивает проход итерации игрового цикла за 20 мс, допустим также, что менять картинку спрайта нужно раз в 200 мс, таким образом, мы должны обновлять спрайт каждый десятый шаг игрового цикла.

public void update(long gameTime) { if (gameTime> frameTicker+ framePeriod) { frameTicker= gameTime; // увеличиваем номер текущего кадра currentFrame++; //если текущий кадр превышает номер последнего кадра в // анимационной последовательности, то переходим на нулевой кадр if (currentFrame>= frameNr) { currentFrame= 0 ; } } // Определяем область на рисунке с раскадровкой, соответствующую текущему кадру this. sourceRect. left= currentFrame* spriteWidth; this. sourceRect. right= this. sourceRect. left+ spriteWidth; }

Этот метод получает в качестве параметра текущее время и если это время превышает сумму времени последнего обновления (frameTicker) и длительности показа кадра (framePeriod), то необходимо перейти к показу следующего кадра. Для этого увеличиваем на единицу значение переменная currentFrame, а затем на основании ее значения вычисляем заново границы кадра (sourceRect.left и sourceRect.right).

Внесем изменение в метод update класса MainGamePanel

public void update() { elaine. update(System . currentTimeMillis() ) ; ...

Теперь у нашего спрайта меняются кадры, но мы не видим этого. Добавим в класс ElaineAnimated метод draw, который будет выводить на экран текущий кадр

public void draw(Canvas canvas) { // область, где рисуется спрайт Rect destRect= new Rect(x, y, x+ spriteWidth, y+ spriteHeight) ; //комманда вывода рисунка на экран. canvas. drawBitmap(bitmap, sourceRect, destRect, null ) ; }

Команда canvas.drawBitmap рисует прямоугольную область sourceRect из рисунка bitmap в прямоугольной области destRect.

Изменим также метод onDraw, добавив туда вызов метода перерисовки спрайта

protected void onDraw(Canvas canvas) { // Заливаем canvas черным цветом canvas. drawColor(Color. BLACK) ; //рисуем анимированный спрайт elaine. draw(canvas) ; ...

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

Cпрайты и спрайтовую анимацию (sprite animation) использовали в видеоиграх еще в 90-е годы. Некоторые даже полностью состояли из спрайтов, позже ими заменяли только отдельные вещи (например взрывы, внутриигровую растительность, даже источники света были стилизованы под спрайты). Но кто бы мог подумать, что в 21 веке мы все еще будем сталкиваться с ними? Разберемся сегодня на !

Сейчас зачастую спрайты используются для удобного кеширования нескольких изображений, чтобы уменьшить нагрузку на сервер и сократить количество HTTP-запросов. Ниже представлен основной спрайт, который используется для элементов управления на видео-хостинге Youtube.

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

Frame { width: 280px; height: 456px; background: url("http://thewebivore.com/demos/collie/deer-sprite.png"); animation: play 1s steps(7) infinite; -webkit-animation: play 1s steps(7) infinite; /* Ключевой момент здесь steps(7), мы указываем количество шагов, за которые нужно полностью прокрутить ленту кадров. Если мы этого не сделаем, а просто оставим infinite, то не получится ощущение анимации и будет просто быстро текущий поток рисунков */ } @-webkit-keyframes play { from { background-position: 0px; } to { background-position: -1956px; } } @-moz-keyframes play { from { background-position: 0px; } to { background-position: -1956px; } } @-o-keyframes play { from { background-position: 0px; } to { background-position: -1956px; } } @keyframes play { from { background-position: 0px; } to { background-position: -1956px; } }

Sprite animation VS gif-файлы

Почему же не использовать классические анимированные gif-изображения, ведь это намного проще? Проще, но у спрайтов есть несколько неоспоримых преимуществ:

  • Gif-изображения ограничены 256 цветами – невозможно сделать красивые изображения с большим количеством полутонов.
  • «Гифки» автоматически начинают проигрываться, даже если не загрузились до конца, тогда как спрайты можно предзагрузить и убедиться что анимация будет плавной.
  • Спрайтовую анимацию можно полностью контролировать через Javascript и стилизовать с помощью CSS: останавливать, проигрывать частично, воспроизводить только после какого-то события, а также добавлять рамки, заливки и многое другое.
  • Ну и конечно же со спрайтами возможен частичный рендеринг. То есть у стоячего на фоне леса человечка можно анимировать только волосы и руки, все остальное будет статичным – это экономит определенное количество ресурсов.

Реализация sprite animation через css-анимации может похвастаться неплохой поддержкой браузеров.

На самом деле, многие праздничные дудлы от Google выполнены спрайтовой анимацией, компания Adobe недавно выкатила экспериментальный сайт, посвященный css-анимациям – . Я уверен, что этот функционал станет еще более популярным в ближайшие пару лет.