До этого я писал о том, как добавил поддержку звука в свой движок, а вот теперь хотелось бы поговорить об анимации. Тем более что в комментах к какой-то статье такой вопрос был. Не мудрствуя лукаво, я решил написать еще один небольшой класс — потомок 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 🙂 А приемущества?.. Хм… Ну его достаточно легко подключить к проекту…
Добрый день. Большое спасибо за статьи. у вас в движке много функций, но к сожалению наглядных примеров реализации этих функций очень немного. Конечно, классы очень хорошо прокомментированы и пояснения к ним есть, но все-таки примеров очень не хватает. Может быть вы все-таки сделаете пример для работы парочки классов? Все, кого заинтересовал ваш движок, будут очень признательны, в том числе и я.
Я счас его немного причесываю и выложу вторую версию, на ней уже можно будет примеры какие-то делать
Спасибо большое, будем ждать.
Здравствуйте Уважаемый! Пишу небольшую аркаду используя Ваш движок. Суть вопроса: есть ли у Вашего замечательного движка логотип или значок какой-нибудь? Очень хотелось бы поместить его на экран загрузки в качестве «спасибо» автору.
Я выслал Вам на почту, указанную при вводе комментария
И если можно кинете ссылку на Вашу игрушку, интересно же посмотреть!