Спрайтовая анимация для начинающих или продолжаем возню с движком 8

До этого я писал о том, как добавил поддержку звука в свой движок, а вот теперь хотелось бы поговорить об анимации. Тем более что в комментах к какой-то статье такой вопрос был. Не мудрствуя лукаво, я решил написать еще один небольшой класс — потомок mSimpleSprite.

Итак, наша задача состоит из нескольких пунктов:

  1. Реализация фреймовой анимации для спрайта.
  2. Возможность регулировки времени отображения для каждого спрайта.
  3. Загрузка фреймов из одного исходного изображения.
  4. Как можно меньше мороки с использованием такого спрайта в основоной программе.

Собственно тем или иным путем все эти цели были достигнуты, а вот как именно, об этом счас и поговорим.

Не буду томить Вас долгими речами, а стразу приведу код спрайта:

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 — который можно не парясь подключить к проекту.

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

Если возникнут вопросы — пишите в комметах или на мыло, найти его можно где-то внизу страницы 🙂

На сегодня у меня все!

Вам понравилось? Было полезно? Поделитесь!

Опубликовать в Facebook
Опубликовать в Google Buzz
Опубликовать в Google Plus
Опубликовать в LiveJournal
Опубликовать в Мой Мир
Опубликовать в Одноклассники
Опубликовать в Яндекс
Запись опубликована в рубрике Android, Новичку, Программирование с метками , , , , , , . Добавьте в закладки постоянную ссылку.

Один комментарий на «Спрайтовая анимация для начинающих или продолжаем возню с движком 8»

  1. Владимир говорит:

    Сам впервые начал разбираться со спрайтами, туториал хороший, очень помогает, НО! но хотелось бы примеры наглядные, чтоб видеть что в итоге у нас получается, в виде анимации или готового apk

  2. wil говорит:

    Не понял как будешь делать если надо несколько анимаций для одного объекта(бег, прыг, присед) ? У меня в SimpleSprite заведена коллекция новых объектов Animation, каждый из которых очень похожи на твой mAnimSprite. В основном SimpleSprite запускаем анимацию по имени playAnimation(«nameRun»), по этому имени берется элемент коллекции и проигрывается(он меняет периодически bmp основного SimpleSprite).

    • davidmd говорит:

      Ну можно ведь перескакивать некоторые фреймы. Там для этого передается массив того сколько раз показывать тот или иной фрейм. А вообще я как-то думал использовать несколько спрайтов для таких целей 🙂

  3. Уведомление: Пишем свою игру для Android (Пятнашки rulez) | Программирование и разработка, android, java – с самых первых шагов

  4. Глеб говорит:

    В Вашем проекте папка layout и drawable пустые, как мне сверстать xml?

  5. bas2000 говорит:

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

    • davidmd говорит:

      Да пожалуйста! Используйте какие угодно 🙂 Хоть 12х39 пикелов! Такие ограничения ставятся на использование спрайтов в OpenGL ES, а поскольку движок не использует OpenGL то можете пользовать спрайты любого размера 🙂

  6. bas2000 говорит:

    Очень нужен пример с анимированными спрайтами, чтоб разобраться с работой движка.

  7. bas2000 говорит:

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

    • davidmd говорит:

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

  8. Михаил говорит:

    Это все замечательно и интересно, но планируются ли статьи про OpenGL ES?) Что-то мне подсказывает, что на одной канве далеко не уедешь 🙂

  9. John говорит:

    Почитал, очень доступно написано. С архитектурой графических движков до этого не сталкивался. Много чего переделал под свои нужды. У меня нет ни точки, ни линии, никаких графических примитивов. Без них в проекте уже около тридцати классов. В базовом классе спрайта 46 функций и процедур. Сделал обратный вызов для апдейта и клика. Сделал определение коллизий и обратный вызов для них. В слои добавил еще один список с невидимыми объектами и перемещаю по нужде объекты из видимых в невидимые и наоборот. Добавил коллекцию объектов. В независимости от сцены их можно вместе двигать, менять им св-ва и т.д. Набор анимаций вынес в отдельный класс. теперь при создании большого количества анимированных спрайтов анимация одна на всех, а не хранится в каждом отдельном спрайте, занимая лишнее место. Все вызовы в связанные списке сопровождаю synchronized (имя списка) {действия}. В итоге на не самом мощном Acer Liquid s100 запускается и без рывков передвигается по экрану с помощью обратного вызова функции update() 2000 спрайтов.

  10. John говорит:

    Пока еще много чего не готово. В окончательном итоге, возможно выложу на суд общественности. Много чего переделываю. Обычно сажусь часов в 10 вечера, и часов до 6 утра пишу. Потом пару-тройку дней даже не берусь, опять сажусь и со свежей головой смотрю, что сделал, возможно что то меняю и дальше…

  11. Владимир говорит:

    Подскажите пожалуйста, как можно объявлять глобальные переменные?
    Хотел попробовать вынести хранение спрайтов в отдельный массив(заполнить его на уровне MainActivity), а в методах Draw объектов mAnimSprite брать нужный спрайт из того массива и рисовать его, и зашел в тупик ….
    пытался разобраться почему параметры mSettings видны из любых методов классов, как это сделано? 🙂
    Заранее благодарю за помощь.

  12. John говорит:

    Да, и самое важное изменение с моей стороны — теперь движок опирается не на свой Activity, а на простой SurfaceHolder. Соответственно: можно рисовать на живых обоях, непосредственно из кода дорисовывать что то на канве. добавил еще один класс — камера. Теперь размерность игрового поля может быть хоть 5000х5000. Отображается только часть поля, на котором находится камера. Смотрю, сколько сделал, понимаю, что не сделано еще и 20%… Может, было проще освоить AndEngine, чем писать что то свое…

  13. Евгений говорит:

    Спасибо большое за цикл полезных статей. Подскажите пожалуйста новичку. Ну написал:
    @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));
    }
    Но у меня он застрял на первом кадре и все. Как же заставить его передвигаться? Прошу небольшой пример. Очень жду Вашего ответа.

  14. Кирилл говорит:

    А как вы собрали движок в jar, и какие преимущества дает такая упаковка?

    • davidmd говорит:

      Просто экспортировал в Jar 🙂 А приемущества?.. Хм… Ну его достаточно легко подключить к проекту…

  15. Валерий говорит:

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

  16. Kadonsky говорит:

    Здравствуйте Уважаемый! Пишу небольшую аркаду используя Ваш движок. Суть вопроса: есть ли у Вашего замечательного движка логотип или значок какой-нибудь? Очень хотелось бы поместить его на экран загрузки в качестве «спасибо» автору.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *