До этого я писал о том, как добавил поддержку звука в свой движок, а вот теперь хотелось бы поговорить об анимации. Тем более что в комментах к какой-то статье такой вопрос был. Не мудрствуя лукаво, я решил написать еще один небольшой класс — потомок mSimpleSprite.
Итак, наша задача состоит из нескольких пунктов:
- Реализация фреймовой анимации для спрайта.
- Возможность регулировки времени отображения для каждого спрайта.
- Загрузка фреймов из одного исходного изображения.
- Как можно меньше мороки с использованием такого спрайта в основоной программе.
Собственно тем или иным путем все эти цели были достигнуты, а вот как именно, об этом счас и поговорим.
Не буду томить Вас долгими речами, а стразу приведу код спрайта:
package ru.davidmd.myengine;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
public class mAnimSprite extends mSimpleSprite{
/**
* Ширина кадра
*/
protected int fWidth;
/**
* Высота кадра
*/
protected int fHeight;
protected boolean animated;
/**
* Количество кадров
*/
protected int frameCount;
/**
* Массив где хранятся сами изображения
*/
protected Bitmap[] bmpList;
/**
* Массив где хранится количество
* показов для каждого кадра
*/
protected int [] frameLen;
/**
* Номер текущего кадра
*/
protected int curFrame;
/**
* счетчик показов кадра
*/
protected int curFrameCounter;
public mAnimSprite(String s, AssetManager am, int FrameCount) {
super(s, am);
this.frameCount=FrameCount;
this.fHeight=this.height;
this.fWidth=this.width/this.frameCount;
this.MakeFrames();
}
/**
* Пресоздает фреймы в спрайте
*/
public void MakeFrames()
{
curFrame=0;
bmpList = new Bitmap[this.frameCount];
frameLen = new int[this.frameCount];
for (int i = 0; i< this.frameCount; i++)
{
bmpList[i]=Bitmap.createBitmap(bmp, i*this.fWidth, 0, this.fWidth, this.fHeight);
frameLen[i]=1;
}
}
/**
* @see ru.davidmd.myengine.mSimpleSprite#draw(android.graphics.Canvas, android.graphics.Paint)
* Рисует на экране анимированный спрайт
*/
@Override
public void draw(Canvas c, Paint p)
{
if (curFrameCounter >=frameLen[curFrame])
{
curFrameCounter = 1;
if (curFrame==frameCount-1)
{
curFrame=0;
}
else
{
curFrame++;
}
}
c.drawBitmap(bmpList[this.curFrame], this.x, this.y, p);
if (this.animated)
{
curFrameCounter++;
}
}
@Override
public void draw(Canvas c, Paint p, int dx, int dy)
{
if (curFrameCounter >=frameLen[curFrame])
{
curFrameCounter = 1;
if (curFrame==frameCount-1)
{
curFrame=0;
}
else
{
curFrame++;
}
}
c.drawBitmap(bmpList[this.curFrame], this.x+dx, this.y+dy, p);
if (this.animated)
{
curFrameCounter++;
}
}
/**
* @see ru.davidmd.myengine.mSimpleSprite#isSelected(float, float)
*/
@Override
public boolean isSelected(float x, float y) {
selected = false;
if (x > this.x && x < (this.x + this.fWidth) && y > this.y
&& y < (this.y + this.fHeight)) {
selected = true;
}
return selected;
}
/**
* @return Возвращает ширину каждого кадра.
* необходимо использовать для определения ширины
* отображаемого на экране анимированного спрайта
*/
@Override
public int getWidth() {
return fWidth;
}
/**
* @return Возвращает высоту каждого кадра.
* необходимо использовать для определения ширины
* отображаемого на экране анимированного спрайта
*/
@Override
public int getHeight() {
return fHeight;
}
/**
* @return количество кадров в анимации спрайта
*/
public int getFrameCount() {
return frameCount;
}
/**
* @return номер текущего кадра
*/
public int getCurFrame() {
return curFrame;
}
/**
* Устанавливает текущий кадр
*
* @param curFrame - новый текущий кадр
*/
public void setCurFrame(int curFrame) {
this.curFrame = curFrame;
}
/**
* Устанавливается длительность для отображения каждого кадра в
* анимированном спрайте
* @param frameLen - массив целых чисел где для каждого i-го
* кадра указано сколько раз он будет показан прежде чем
* произойдет переход на другой кадр;
*/
public void setFrameLen(int[] frameLen) {
if (this.frameLen.length>=this.frameCount)
this.frameLen = frameLen;
}
/**
* @return Возвращает истину, если
* анимация включена и ложь - если выключена
*/
public boolean isAnimated() {
return animated;
}
/**
* Включает и выключает анимацию
* @param animated
*/
public void setAnimated(boolean animated) {
this.animated = animated;
}
Я правда старался все это дело откомментировать как можно лучше, но думаю что стоит все же заострить внимание на некоторых моментах. Итак у экземпляра класса будут иметься два основополагающих поля: это во-первых массив bitmap-ов (bmpList)- собственно сами фремы (кадры) нашей анимации и массив целых чисел frameLen — в каждом элементе массива хранится информация о том, сколько раз должен быть показан кадр анимации перед переключением на следующий. Помимо этого имеется переменная curFrame — в которой хранится отображаемый в данный момент кадр и переменная curFrameCounter — которая есть ни что иное как счетчик показов текущего кадра.
Сразу оговорюсь, что на изображение, используемое в анимированном спрайте накладываются некоторые ограничения. Все фреймы анимации должны быть выстроены в одну большую линию. Итак если у нас три кадра анимации то выглядеть это должно примерно так:
Спрайт сам разрежет его на то количество кадров, которое мы укажем ему при создании. На самом деле можно было конечно немного заморочиться и хотя бы обойти требование на выстраивание кадров в линию, но и так работает вполне не плохо :).
Итак, переходим к конструктору. Он у нас получает несколько параметров — первое — это AssetManager, затем имя файла в папке assets нашего проекта. Ну и конечно число кадров на которые надо разбить спрайт. Конструктор загружает спрайт используя метод родительского класса mSimpleSprite а затем вызывает метод MakeFrames() — который и разобьет картинку на некоторое количество отдельных картинок и заполнит ими массив bmpList. Там же, для каждого кадра устанавливается время количество показов по умолчанию (по умолчанию кадры показываются по одному разу).
В методе draw() помимо простой отрисовки, еще и переключается счетчик кадров в соответствии со значениями из массива frameLen. Таким образом, что если для какого-то i-того фрейма в i-том элементе массива frameLen установлено например 5, то и сам фрейм будет отрисован 5 раз, прежде, чем фрейм сменится.
Установить количество показов для каждого кадра можно используя метод setFrameLen(int[] frameLen). Если мы хотим чтоб каждый кадр нашей анимации из трех кадров перерисовывался например по семь раз, то мы должны передать такой массив: {7, 7, 7}. При этом если нам нужно чтоб какие-то кадры отображались дольше мы можем подрегулировать это например так: {7, 1, 7}, и тогда второй кадр анимации отрисуется всего 1 раз. Зная фреймрейт (ведь мы его сами устанавливаем) и то сколько милисекунд должен отображаться кадр не сложно рассчитать соответствующие значения из этого массива.
В этом же классе мы перегрузили методы getWidth() и getHeight() так, чтобы они возвращали высоту и ширину не всего загруженного спрайта, а лишь одного кадра анимации.
Думаю с остальными методами вопросов не возникнет, ибо там все проще некуда!
Ну и последнее, поскольку я достаточно много чего добавил в свой движок с момента как последний раз выкладывал его здесь, то вот пожалуйста самая последняя его версия! Архив с исходниками mEngine и jar-архив mEngine.jar — который можно не парясь подключить к проекту.
Именно здесь уже есть и анимированные спрайты и подержка звука и много чего еще, о чем я не писал. Я очень старался документировать код, надеюсь в нем будет не сложно разобраться.
Если возникнут вопросы — пишите в комметах или на мыло, найти его можно где-то внизу страницы 🙂
На сегодня у меня все!

Сам впервые начал разбираться со спрайтами, туториал хороший, очень помогает, НО! но хотелось бы примеры наглядные, чтоб видеть что в итоге у нас получается, в виде анимации или готового apk
Ну думаю что-нить заанимировать можно 🙂
Не понял как будешь делать если надо несколько анимаций для одного объекта(бег, прыг, присед) ? У меня в SimpleSprite заведена коллекция новых объектов Animation, каждый из которых очень похожи на твой mAnimSprite. В основном SimpleSprite запускаем анимацию по имени playAnimation(«nameRun»), по этому имени берется элемент коллекции и проигрывается(он меняет периодически bmp основного SimpleSprite).
Ну можно ведь перескакивать некоторые фреймы. Там для этого передается массив того сколько раз показывать тот или иной фрейм. А вообще я как-то думал использовать несколько спрайтов для таких целей 🙂
Уведомление: Пишем свою игру для Android (Пятнашки rulez) | Программирование и разработка, android, java – с самых первых шагов
В Вашем проекте папка layout и drawable пустые, как мне сверстать xml?
Ну на сколько я помню верстать хмл там не надо 🙂
Подскажите, пожалуйста, новичку: в вашем движке возможно использовать спрайты, размеры которых не кратны степеням двойки. Если нет, то вообще в андроиде это возможно?
Да пожалуйста! Используйте какие угодно 🙂 Хоть 12х39 пикелов! Такие ограничения ставятся на использование спрайтов в OpenGL ES, а поскольку движок не использует OpenGL то можете пользовать спрайты любого размера 🙂
Очень нужен пример с анимированными спрайтами, чтоб разобраться с работой движка.
Ну так в статье ж вроде все описанот:-)
Спасибо за вашу работу, все очень доходчиво. У меня вопрос. Ваши крестики нолики работают нормально только в вертикальной ориентации, отображение mAnimSprite рассчитано тоже на вертикальную, но это не проблема, думаю смогу и сам исправить. Вопрос в том, как зафиксировать горизонтальную ориентацию при любом положении смартфона? Тогда уже смогу подогнать движок под свои нужды.
На самом деле я просто фиксировал ориентацию активити в манифесте, а потом просто передавала параметры на которые рассчитана графика. Так что сам движок может работать с любой ориентацией экрана. Правда реализовывать правильнуб смену ориентации вам придется самому. Однако если вам нужен только landscape режим выможете сделать это так же как и я 🙂
Это все замечательно и интересно, но планируются ли статьи про OpenGL ES?) Что-то мне подсказывает, что на одной канве далеко не уедешь 🙂
Пока что не знаю. Я сам OpenGL ES не разбирал.
Почитал, очень доступно написано. С архитектурой графических движков до этого не сталкивался. Много чего переделал под свои нужды. У меня нет ни точки, ни линии, никаких графических примитивов. Без них в проекте уже около тридцати классов. В базовом классе спрайта 46 функций и процедур. Сделал обратный вызов для апдейта и клика. Сделал определение коллизий и обратный вызов для них. В слои добавил еще один список с невидимыми объектами и перемещаю по нужде объекты из видимых в невидимые и наоборот. Добавил коллекцию объектов. В независимости от сцены их можно вместе двигать, менять им св-ва и т.д. Набор анимаций вынес в отдельный класс. теперь при создании большого количества анимированных спрайтов анимация одна на всех, а не хранится в каждом отдельном спрайте, занимая лишнее место. Все вызовы в связанные списке сопровождаю synchronized (имя списка) {действия}. В итоге на не самом мощном Acer Liquid s100 запускается и без рывков передвигается по экрану с помощью обратного вызова функции update() 2000 спрайтов.
Прикольно!
А кодом не поделитесь? 🙂
Пока еще много чего не готово. В окончательном итоге, возможно выложу на суд общественности. Много чего переделываю. Обычно сажусь часов в 10 вечера, и часов до 6 утра пишу. Потом пару-тройку дней даже не берусь, опять сажусь и со свежей головой смотрю, что сделал, возможно что то меняю и дальше…
Подскажите пожалуйста, как можно объявлять глобальные переменные?
Хотел попробовать вынести хранение спрайтов в отдельный массив(заполнить его на уровне MainActivity), а в методах Draw объектов mAnimSprite брать нужный спрайт из того массива и рисовать его, и зашел в тупик ….
пытался разобраться почему параметры mSettings видны из любых методов классов, как это сделано? 🙂
Заранее благодарю за помощь.
Почитайте про модификаторы доступа public private 🙂
Да, и самое важное изменение с моей стороны — теперь движок опирается не на свой Activity, а на простой SurfaceHolder. Соответственно: можно рисовать на живых обоях, непосредственно из кода дорисовывать что то на канве. добавил еще один класс — камера. Теперь размерность игрового поля может быть хоть 5000х5000. Отображается только часть поля, на котором находится камера. Смотрю, сколько сделал, понимаю, что не сделано еще и 20%… Может, было проще освоить AndEngine, чем писать что то свое…
Спасибо большое за цикл полезных статей. Подскажите пожалуйста новичку. Ну написал:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EngineSettings.Init(this, this.getWindowManager().getDefaultDisplay(), 20);
scene = new GameScene(480, 800, 1);
scene.setCurLay(1);
SpriteAnimation animTest = new SpriteAnimation(«herotest.png», this.getAssets(), 7);
scene.addItem(animTest);
setContentView(new GameSurfaceView(this, scene));
}
Но у меня он застрял на первом кадре и все. Как же заставить его передвигаться? Прошу небольшой пример. Очень жду Вашего ответа.
Здравствуйте!
В каестве примера есть вот такая вот небольшая игрушка давно ее выложил 🙂 http://davidmd.ru/2011/11/23/%D0%BF%D0%B8%D1%88%D0%B5%D0%BC-%D1%81%D0%B2%D0%BE%D1%8E-%D0%B8%D0%B3%D1%80%D1%83-%D0%B4%D0%BB%D1%8F-android-%D0%BF%D1%8F%D1%82%D0%BD%D0%B0%D1%88%D0%BA%D0%B8-rulez/
А как вы собрали движок в jar, и какие преимущества дает такая упаковка?
Просто экспортировал в Jar 🙂 А приемущества?.. Хм… Ну его достаточно легко подключить к проекту…
Добрый день. Большое спасибо за статьи. у вас в движке много функций, но к сожалению наглядных примеров реализации этих функций очень немного. Конечно, классы очень хорошо прокомментированы и пояснения к ним есть, но все-таки примеров очень не хватает. Может быть вы все-таки сделаете пример для работы парочки классов? Все, кого заинтересовал ваш движок, будут очень признательны, в том числе и я.
Я счас его немного причесываю и выложу вторую версию, на ней уже можно будет примеры какие-то делать
Спасибо большое, будем ждать.
Здравствуйте Уважаемый! Пишу небольшую аркаду используя Ваш движок. Суть вопроса: есть ли у Вашего замечательного движка логотип или значок какой-нибудь? Очень хотелось бы поместить его на экран загрузки в качестве «спасибо» автору.
Я выслал Вам на почту, указанную при вводе комментария
И если можно кинете ссылку на Вашу игрушку, интересно же посмотреть!