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

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

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

Анимация, основанная на 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 год)

переменная 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() . Внутри этой процедуры мы проверяем, достаточно ли времени текущий

Часть 2. Анимирование спрайта.

Немного теории:

Итак, для начала давайте разберемся, как же создается анимация в XNA GameStudio . Здесь нет ничего сложного. В игру загружается изображение, в котором содержатся все кадры анимации на одинаковом расстоянии друг от друга (это самое важное). Вот как будет выглядеть файл с изображением анимации моего героя:

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

Мы создаем прямоугольник, который размерами совпадает с одним фреймом анимации (размеры всех фреймов должны быть одинаковы!). Главная мысль в том, что если прямоугольник выделит первый кадр (фрейм) анимации, то другие кадры будут не видны. Т.е. если прямоугольник станет так:

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

Итак, если Вы разобрались с теорией, то пора переходить к практике (если же нет, можете задать вопрос мне в ЛС, я на все отвечу =)).

Анимирование спрайта:

Итак, для начала перейдем в класс Sprites и добавим несколько вспомогательных переменных.

После строки

200?"200px":""+(this.scrollHeight+5)+"px");">public Vector2 spritePosition;

Запишем:

200?"200px":""+(this.scrollHeight+5)+"px");">int FrameCount; //Количество всех фреймов в изображении (у нас это 10)
int frame;//какой фрейм нарисован в данный момент
float TimeForFrame;//Сколько времени нужно показывать один фрейм (скорость)
float TotalTime;//сколько времени прошло с показа предыдущего фрейма

Здесь все понятно из комментариев. Единственное, что может вызвать у Вас вопрос, так это последняя переменная. Эта переменная не дает выйти значению «TimeForFrame » за рамки, и если оно все же выйдет, то последняя переменная обновляет фрейм. Например, если у нас один фрейм должен быть показан на экране ровно одну секунду, а уже прошло времени 1 секунда и одна милисекунда, то переменная TotalTime сигнализирует, что пора показывать новый фрейм. И так, продолжим.

Создадим новый перегруженный конструктор и запишем в его параметры переменную, которая будет отвечать за скорость. А так же присвоим значения остальным переменным. Вот как выглядит код второго конструктора:

200?"200px":""+(this.scrollHeight+5)+"px");">public Sprites(int speedAnimation)
{
frame = 0;
TimeForFrame = (float)1 / speedAnimation;
TotalTime = 0;
}

Теперь давайте разберем все это по порядку.

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

Первая строчка считает, какой фрейм сейчас показан на экране. Так анимация еще не начала проигрываться, на экране показан нолевой фрейм (т.е. ничего).

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

И последняя переменная так же равна нулю, так как анимация еще не проигрывается.

Теперь давайте добавим новый метод в наш класс. Пусть это будет метод Udate() , с параметром gameTime . Сразу после второго конструктора напишите:

200?"200px":""+(this.scrollHeight+5)+"px");">
{

Вы спросите, зачем же нам gameTime ? Очень просто. Как видно из названия, эта переменная отвечает за игровое время. Это единственная известная мне переменная, с помощью которой мы можем раситать время на какое-то действие.
В gameTime хранятся все значения времени нашего игрового проекта. Она считает время вызова метода Update() (класса Game1 ), время продолжительновти игры и все тому подобное. Так же выводить это время наша переменная может в виде милисекунд, секунд, минут и т.д.
Именно gameTime поможет нам расчитать скорость нашей анимации.

Итак, в первую очередь в методе Update мы расчитаем TotalTime . Для этого сделаем так, что бы при каждом вызове метода Update , эта переменная увеличивалась на определенное количество времени (в нашем случае это время, за которое полностью выполняется метод Update ). Для этого пропишем в нашем методе:

200?"200px":""+(this.scrollHeight+5)+"px");">

Далее проверим уловие, если TotalTime больше TimeForFrame (т.е. время показа фрейма закончилось), мы увеличим фрейм на один (покажем следующий фрейм), а так же уменьшим TotalTime на TimeForFrame , что бы для нового фрейма значение TotalTime было первоначальным. Вот так будет выглядеть наш метод после редактирования:

200?"200px":""+(this.scrollHeight+5)+"px");">public void Update(GameTime gameTime)
{
FrameCount = spriteTexture.Width / spriteTexture.Height;

TotalTime += (float)gameTime.ElapsedGameTime.TotalSeconds;

If (TotalTime > TimeForFrame)
{
frame++;

TotalTime -= TimeForFrame;
}

Первая строчка этого метода вычисляет количество фреймов в одной текстуре (если помните, у нас на текстуре нарисовано десять фреймов (кадров анимации)). Как я говорил, все фреймы должны быть одинакового размера, и желательно квадратными. Так как мои фреймы квадратные, то высота одного из них, равна его ширине.
Но, высота одного фрейма равна высоте всей текстуры. Короче говоря, из всего вышеизложенного понятно, что высота всей текстуры (spriteTexture.Height )равна ширине и высоте одного фрейма. И, так как фреймы оlинаковые, то можно разделить ширину всей текстуры на ширину одного фрейма. Тем самым мы получим количесто фреймов.

В остальном все понятно все понятно, кроме строчки

200?"200px":""+(this.scrollHeight+5)+"px");">frame = frame % (FrameCount - 1);

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

Ну, можно сказать, что самое сложное уже позади. Осталось только нарисовать нашу анимацию. Для этого создадим новый метод, под названием DrawAnimation с параметром spriteBatch . Вот так он должен выглядеть сразу после создания:

200?"200px":""+(this.scrollHeight+5)+"px");">public void DrawAnimation(SpriteBatch spriteBatch)
{

Теперь давайте просчитаем ширину одного фрейма. Впрочем, если кадры анимации квадаратные, то этого можно не делать, но что бы Вам было понятней, все же проститаем. Для этого нужно просто разделить всю ширину текстуры на количество фреймов. Представим, что наша текстура шириной 300 пикселей. И если мы разделим эти 300 пикселей на те 10 фреймов, которые в них нарисованы (а фреймы одинаковы), то получится что ширина каждого фрейма равна 30 пикселей. Но, это я привел не реальные значения нашей текстуры, а выдуманные (на самом деле ширина моей текстуры 960 пикселей, а у Вас она сожет быть любой другой). Короче говоря, такая будет первая строчка нового метода:

200?"200px":""+(this.scrollHeight+5)+"px");">int frameWidth = spriteTexture.Width / FrameCount;

Теперь давайте нарисуем тот самый прямоугольник, который будет показывать один единственный кадр анимации. О нем я говорил в начале этой части статьи. Для этого сразу после только что написанной строки, запишем:

200?"200px":""+(this.scrollHeight+5)+"px");">Rectangle rectanglе = new Rectangle(frameWidth * frame, 0, frameWidth, spriteTexture.Height);

Здесь мы создали прямоугольник rectangle класса Rectangle . В его параметрах мы прописали положение каждого фрейма в текстуре (а точнее, перемещение по текстуре, по координате X , т.е. вбок), перемещение по кординате Y (так как все спрайты нарисованы слева направо, то по координате Y нам перемещаться не надо, т.е. укажем во втором параметре 0), ширину и высоту каждого кадра.

Все что осталось, это нарисовать нашу анимацию. Для этого далее запишем:

200?"200px":""+(this.scrollHeight+5)+"px");">spriteBatch.Draw(spriteTexture, spritePosition, rectangle, Color.White);

Все, работу с этим классом мы закончили!

Теперь перейдем к классу Game1 и создадим второй объект класса Sprites . Для этого сразу после строки (в начале класса)

200?"200px":""+(this.scrollHeight+5)+"px");">Sprites hero;

Запишем:

200?"200px":""+(this.scrollHeight+5)+"px");">Sprites runAnimation;

Этот объект будет отвечать за анимацию бега в нашей игре. Теперь давайте его инициализируем в конструкторе Game1 . Сразу после строки

200?"200px":""+(this.scrollHeight+5)+"px");">hero = new Sprites();

Запишите:

200?"200px":""+(this.scrollHeight+5)+"px");">runAnimation = new Sprites(10);

Цифра 10 в аргументе означает скорость нашей анимации. Здесь Вы можете поэксперементировать и сделать ту скорось, которая подходит Вам.

Теперь давайте установим позицию нашей анимации. Она должна совпадать с позицией объекта hero , что бы в игре была видимость, что бежит не отдельный объект, а именно игрок. Для этого сразу после:

200?"200px":""+(this.scrollHeight+5)+"px");">hero.spritePosition = new Vector2(300, 300);

Запишем:

200?"200px":""+(this.scrollHeight+5)+"px");">runAnimation.spritePosition = new Vector2(300, 300);

Итак, теперь давайте добавим наш файл с анимацией в папку Content/Textures и назовем его run . Как я уже и говорил, мой файл выглядит так:

Итак, после добавления файла запишем в метод LoadContent такие строки:

200?"200px":""+(this.scrollHeight+5)+"px");">runAnimation.LoadContent(Content, "Textures//run");

Тем самым мы загрузили нашу анимацию в игру. Осталось вызвать метод Update (класса Sprites ), в котором мы как раз и описывали технологию воспроизведения анимации, и нарисовать нашу анимацию. Для начала в методе Update (класса Game1 ) перед строкой:

влево 200?"200px":""+(this.scrollHeight+5)+"px");">if(states.IsKeyDown(Keys.Left))
{
runAnimation.DrawAnimation(spriteBatch);
}
else
hero.Draw(spriteBatch);

Этот код означает: «Если нажата клавиша влево, то анимация проигрывается. Если клавиша влево не нажата, то анимация останавливается и проигрывается текстура отдыха» .

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

Вот что у меня получилось:

А если я нажму клавишу влево , то проиграется анимация:

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