Пишем игру для Android. Часть 4 - Спрайтовая анимация


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

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

Давайте рассмотрим пример, состоящий из нескольких кадров. Не будем заморачиваться на графике и возьмем готовый рисунок из некогда популярной компьютерной игры Monkey Island.

пример анимированного объекта

В компьютерной графике и gamedev-е широко используется понятиеспрайт(sprite). В современной трактовке оно означает графический объект, который может перемешаться по экрану и при этом изменять свой вид. Со спрайтом связан рисунок, содержащий раскадровку и, в зависимости от ситуации, спрайт имеет вид того или иного кадра. Ниже приведет пример рисунка с последовательностью кадров, который будет использоваться нами для создания спрайта. 

Спрайт

Это изображение имеет ширину в 150 точек, и содержит 5 кадров, то есть ширина каждого кадра составляет 30 пикселей.

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

Раскадровка спрайта

Давайте продолжим разработку нашей игры. Создадим проект. За основу возьмем разработанный на предыдущих уроках пример Droid_03. Добавим в папку res/drawable-mdpi проекта файлwalk_elaine.png Создадим новый класс для нашего персонажа. Поскольку в Monkey Island персонаж зовут Elaine, назовем класс ElaineAnimated.

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

public void draw(Canvas canvas){
// область, где рисуется спрайт
Rect destRect=new Rect(x, y, x+ spriteWidth, y+ spriteHeight);
canvas.drawBitmap(bitmap, sourceRect, destRect,null);
canvas.drawBitmap(bitmap,20,150,null);
Paint paint=new Paint();
paint.setARGB(50,0,255,0);
canvas.drawRect(20+(currentFrame* destRect.width()),150,20+
(currentFrame* destRect.width())+ destRect.width(),150+ destRect.height(), paint);
}
 


Запущенная игра


Как всегда, прикладываюисходник к статье.

Перевод:Александр Ледков

Источники:Android Game Development - Sprite Animation





Наши соцсети

Подписаться Facebook Подписаться Вконтакте Подписаться Twitter Подписаться Google Подписаться Telegram

Популярное

Ссылки

Новости [1] [2] [3]... Android/ iOS/ J2ME[1] [2] [3]) Android / Архив

Рейтинг@Mail.ru Яндекс.Метрика
MobiLab.ru © 2005-2018
При использовании материалов сайта ссылка на www.mobilab.ru обязательна