Адаптивные спрайт анимации с ImageMagick и GreenSock.

Анимация, основанная на 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 содержит множество кадров для различных анимаций, мы должны знать номера тех кадров, анимацию которых мы хотим сейчас воспроизводить.

Послесловие

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

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

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

Когда компьютеры были большими, спрайты были совсем крошечными, размером 8 на 8 пикселей и могли быть раскрашены в 4-8 цветов. Самым известным героем первых спрайтовых игр был всеми любимый Марио. Из-за крохотных размеров спрайта нарисовать этому персонажу рот было невозможно: так появились характерные усы Марио, герой стал узнаваемым.

С увеличением мощности компьютеров спрайты росли, становились полноцветными, всё более похожими на настоящих персонажей из мультфильмов. Пик распространения спрайтовой анимации пришёлся на девяностые годы: как на компьютерах, так и на игровых приставках тогда расцвёл жанр "файтингов", где каждый мог почувствовать себя Брюсом Ли. Способствовал этому популярный тогда жанр "боевиков" в кинематографе. Кто из нас не играл в Mortal Kombat, по мотивам которого был даже снят фильм! Такие "файтинги" были полностью основаны на спрайтах.

Образцы спрайтов из игр серии Street Fighter

Однако ещё более популярным стал жанр "платформенной аркады", где спрайтовая анимация по-настоящему правила бал. На рубеже девяностых на студии Дисней наступила эпоха возрождения: из года в год рождались новые шедевры. Небывалым спросом пользовались игры по мотивам знаменитых мультфильмов студии. Возможности спрайтовой анимации возросли настолько, что в аркадной игре можно было буквально погрузиться в атмосферу мультфильма. Игры были подлинными шедеврами - под стать оригинальным анимационным полнометражкам! "Алладин", "Король лев", "Геркулес", "Книга джунглей" и многие другие игры с логотипом Disney навсегда вошли в золотую коллекцию аркад. Это был настоящий расцвет спрайтовой анимации.

Аркада The Lion King (1994 год)

Все спрайты, образующие анимацию, хранятся в специальном файле в виде одной большой растровой карты (битмэпа). "Картой" это изображение называют потому, что игровая программа постоянно ищет нужный спрайт по координатам. Допустим, область анимации составляет 16 на 32 пикселя, а отправной точкой на битмэпе в требуемый момент времени является 84-й пиксель по горизонтали, 460-й - по вертикали. Эти данные заносятся программистом в код игры. Таким образом, битмэп является своеобразной "раскадровкой" спрайтовой анимации, "зашитой" в код программы и скрытой от постороннего зрителя. Вот как, например, выглядит эта "раскадровка" для игры "Король лев", персонаж - маленький Симба:

Ещё одним популярным жанром игр, где использовались спрайты, были анимационные квесты. Стоит отметить, что на сегодня это, пожалуй, единственный жанр игр для настольных компьютеров, где спрайтовая анимация всё ещё широко применяется. Многим из нас знакомы игры серии "Петька и Василий Иванович", "Братья пилоты" и другие популярные отечественные мультипликационные квесты. Они тоже основаны на спрайтах.

Скриншот из игры "Братья пилоты: по следам полосатого слона" (1997 год)

На рубеже тысячелетий традиционная анимация начала уступать место 3D-анимации. То же самое происходило и в мире компьютерных игр - спрайтовые персонажи стали вытесняться полигональными. Даже игры по мотивам новых диснеевских мультфильмов, выполненных в технике классической анимации, были перенесены в трёхмерное пространство. Примеры - "Тарзан", "Лило и Стич", "Братец Медвежонок", "Принцесса и лягушка".

Скриншот из игры "Братец медвежонок" (2002 год)

Однако спрайтовая анимация ещё не собирается уходить со сцены. Она продолжает широко использоваться в оформлении (вспомните "помощников" из более ранних версий Microsoft Office), в обучающих программах, а также в играх - главным образом, для мобильных устройств. Кроме того, в наши дни спрайты используются во Flash и gif-анимации, только хранятся не в виде битмэпа, а в формате многослойного графического файла, где каждый кадр анимации расположен на отдельном слое. В Adobe After Effects можно поместить в сцену анимацию, созданную из последовательности графических файлов. Строго говоря, это тоже спрайтовая анимация, только источником спрайтов является не многослойная картинка или битмэп, а отдельная папка на компьютере. Так что спрайты, как волшебные феи, всегда готовы прийти на выручку аниматору, особенно при создании игр! :)

Спрайты из игры Angry Birds (2009 год)


Спрайты из игры Dust: an Elysian Tail (2012 год)

CSS спрайты не новинка. С момента популяризации в A List Apart в 2004 году, скромный спрайт стал основным методом во многих инструментариях веб-разработчиков. Но в то время как преимущества скорости, обеспечиваемые спрайтами понятны, их использование в современной веб-анимации редко обсуждается. Принцип остается тем же: объединить несколько изображений в одну «главную» графику, из которой только отобранные участки отображаются одновременно.

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

Создание спрайта

Спрайты возникли из мира видеоигр, так что нужно отдать должное их наследию и анимации Рю из Street Fighter. Излишне говорить, что все произведения искусства принадлежит Capcom.

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

ImageMagick, “швейцарский армейский нож” обработки изображений - бесплатный и открытый исходный код обработки изображений, который идеально подходит для автоматизации задач, которые могли бы стать весьма трудоемкими, таких как комбинирование изображений. ImageMagick также доступен для почти любой операционной системы. Пользователи Mac могут установить его с помощью Homebrew, приверженцы Windows могут скачать программу установки с официального сайта, а преданным Linux, вероятно, мне ничего не нужно объяснять.

Сохраните кадры анимации идентичного размера в папке в виде последовательности в формате PNG. Прервите терминал командной строки, переведите (cd) в директорию, где ваши изображения сохраняются, и выполните следующую команду:

Convert *.png -append result-sprite.png

Эта команда указывает ImageMagick объединить вертикально все файлы.PNG в нашем каталоге (‘*’ - общий знак) в один полный спрайт под названием “result-sprite.png”. Это рисунок, который мы будем применять в качестве background-image в CSS для элемента, который мы хотим анимировать, изменяя его положение, чтобы перебрать каждый кадр в последовательности.

Чистая CSS анимация

Мы начнем просто, и анимируем наш спрайт с CSS3 keyframe animation. Если вы не знакомы с основным синтаксисом, статья Луи Лазарис на Smashing Magazine введет вас в курс дела. Мы собираемся анимировать background-position нашего элемента от 0% до 100% (сверху вниз), выставляя каждый кадр поочередно.

Позволив анимации стать одной непрерывной последовательностью, как по умолчанию, появятся промежуточные кадры спрайтов и иллюзия разрушится. Однако, помощью функции steps(), мы можем контролировать количество изображенных ключевых кадров. Из-за способа работы background-position, при определенных в процентах, мы должны установить ряд действий, чтобы получилось на один кадр меньше, чем общее количество кадров в нашей анимации.

Эксперимент со следующим CodePen, чтобы получить представление:

Обратите внимание, что мы определили width и height нашего элемента для точного совпадения с данными одного кадра, чтобы избежать просочения предшествующих или последующих кадров. Мы использовали сокращенное обозначение для установки animation-iteration-count до “бесконечный” и animation-duration до 3,5 секунд, в результате чего Рю будет проводить сво удар со комбинацию ударов до конца времен.

Это далеко от совершенства, однако. Попробуйте изменить height нашего элемента Рю, или применить background-size к нашему спрайту, и эффект будет ломаться. Наш анимированный элемент не адаптирован, и зависит от стойких пиксельных размеров.

Адаптирование

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

Первый вариант - установить background-size нашего спрайта до 100% от ширины нашего элемента, конвертировать width и height нашего элемента из пикселей в ems, а затем изменить базовый font-size, как требуется. Это позволит достичь желаемого эффекта, и может быть пригодно для большинства целей, но не приведет к истинной текучести.

Наш второй вариант - воспользоваться фиксированными пропорциями чистого CSS Marc Hinse. Этот трюк используется в демо ниже, чтобы дать нашему спрайту ширину жидкости 70%.

Это легкий метод и он подходит для мобильных устройств, помогает достичь адаптивного покадрового эффекта анимации. Поддержка браузеров для анимации CSS отличная, с единственным ограничивающим фактором - синтаксис CSS keyframe. Включая в себя соответствующий -webkit- vendor prefix, а эффект работает во всех современных браузерах, включая Internet Explorer 10 и выше.

Анимация JavaScript

Что делать, если мы хотим более точный контроль над нашей анимацией, что обычно возможно с синтаксисом CSS3 keyframe? Давайте предположим, что вместо одной циклической анимации, мы хотим объединить несколько спрайтов и связать триггеры и функции синхронизации в положение полосы прокрутки?

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

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

Во-первых, давайте протриггеруем двух секундную анимацию в определенном положении прокрутки. Она должна закончиться на последнем кадре, и играть в обратном направлении, когда пользователь прокручивает ее назад (даже если это происходит в середине воспроизведения). Мы будем придерживаться темы видеоигры и использовать спрайт из Westwood Studios’ strategy classic Red Alert, в настоящее время выпущенное как бесплатное.

Обратите внимание, что мы используем вышеупомянутый em-метод, попробуйте изменить font-size для масштабирования спрайта вверх и вниз. Мы определили анимацию TweenMax.to, установив конечное значение вертикальной background-position нашего элемента на 100%, и использовали метод SteppedEase для достижения требуемого покадрового эффекта.

Спрайт остается в фиксированном положении на протяжении анимации, которая в настоящее время контролируется прокруткой пользователя. Для достижения этого эффекта, мы дали нашему ScrollMagic.Scene duration 1500px и использовали метод setPin для исправления родительского элемента для полной картины действия. Для более детальной разбивки этих методов, убедитесь, что знакомы с превосходной документацией на веб-сайте ScrollMagic.

Поддержка браузеров для спрайт анимаций, разработанных JavaScript даже лучше, чем у нашей чистой CSS версии. ScrollMagic и GreenSock поддерживаются во всех современных браузерах, включая IE 9+, с приставками браузера и несоответствий обрабатываемых автоматически. Поддержка мобильных устройств также хороша; эти методы работают на iOS 8+ без каких-либо специальных обходных путей, в том числе перерисовка на ходу и прокрутка без потерь.

Вывод

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

Как сказала Рейчел Наборс, «пожалуйста, анимируйте отвественно». Только потому, что что-то может быть анимировано - не означает, что нужно это сделать. И если вы решите заниматься анимацией, делайте это сознательно, красиво, и с целью.

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

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

Давайте рассмотрим пример, состоящий из нескольких кадров. Не будем заморачиваться на графике и возьмем готовый рисунок из некогда популярной компьютерной игры 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 и будем поверх нее выводить прозрачный зеленый квадратик, соответствующий текущему слайду. Практической пользы в этом, конечно, нет, но для общего понимания идеологии работы со спрайтами весьма полезно