Пишем игру для телефона. Часть 5 - Спрайты: анимирование, трансформация и столкновения

Класс Sprite


Спрайт (Sprite) - это графический объект, представляемый одним рисунком (в данный момент времени).  Факт что Sprite состоит только из одного рисунка - принципиальное отличие между ним и TiledLayer, который представляет собой область, которая покрывается рисунками, способными двигаться. (Класс Sprite имеет несколько очень важных особенностей,  но то, что он использует одну картинку, а не заполнение площади картинками очевидное отличие.) Sprite используется в основном для маленьких активных игровых объектов (например как космичесий корабль и астероиды, которые того и гляди уничтожат его), а TiledLayer более предпочтительно использовать для анимированного заднего плана.

Замечательной особенностью спрайтов является то, что он может быть представлен в различных обстоятельствах различными рисунками. То есть, может быть задана целая серия рисунков, позволяющая анимировать спрайт. В моем примере ковбой имеет три различных изображения которые составляют его бег и одно для прыжка. Но ведь в спрайте можно использовать только один рисунок. Выход прост, соединим эти четыре рисунка в один большой, превышающий размер спрайта. В результате у нас есть загруженная в спрайт последовательность кадров. (Чтобы сообщить, где следует искать файлы рисунков, отправьте адрес рисунка вместе с jar в точно таком же формате, что используется при поиске ресурсов в методе  Class.getResource().) То, что составленные вместие кадры хранятся в одном рисунке, очень удобно, посткольку вам не придется возиться с различными графическими объектами, чтобы определить каким рисунком представлен спрайт в данный момент. Ниже придставлен рисунок, используемый в качестве спрайта ковбоя:


А вот рисунок для кустика, который анимирует его качение.


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

Пример нумерации

1 2 3
4 5 6
7 8 9

Чтобы задать отображаемый в данный момент кадр используйте метод setFrame(int sequenceIndex). В качестве параметра передается номер кадра.

Класс Sprait имеет несколько дополнительных возможностей для поддержки анимации. В частности с помощью метода setFrameSequence(int[] sequence) можно задать последовательность кадров для анимации. Я задал следующие последовательности: { 1, 2, 3, 2 } - для ковбоя и { 0, 1, 2 } - для кустика.  Чтобы сменить кадр спрайта на следующий, надо просто вызвать метод nextFrame() (или prevFrame() если нужен предыдущий кадр). Это очень удобно в случаях, когда все кадры спрайта учавствуют в анимации. В случае, когда не все кадры входят в анимационную последовательность возникают небольшие трудности. Если я попытаюсь установить кадр не входящий в последовательность, у меня ничего не получится. Вспомним заданную нами последловательность  для бега ковбоя { 1, 2, 3, 2 }. Допустим мне необходимо отобразить кадр, соответствующий прыжку. Этот кадо имеет номер 0 и не входит в последовательность. Если я просто вызову метод setFrame(0), будет отображен не нулевой кадр спрайта, а первый кадр. (setFrame(1) отобразит второй кадр, setFrame(2) - третий кадр, setFrame(3) - второй кадр). Решить эту проблему достаточно просто. Надо заново создать последовательность, состоящую только из нулевого кадра, а уже затем вызывать setFrame(0). После того как ковбой приземлится, надо опять вернуть старую последовательность { 1, 2, 3, 2 }. Все это реализовано в методах jump() и advance(int tickCount, boolean left) в приведенном ниже коде.

В добавок к возможности менять кадры спрайта, вы можете модифицировать кадры, например поворачивать их или зеркально отражать. Поскольку в игре подразумевается, что и ковбой и кустик могут двигаться как вправо, так и влево, очевидно что для реализации смены направления необходимо использовать трансформацию зеркального отображения. Однако перед тем как применить трансформацию необходимо задать reference пиксел спрайта. Reference пиксел - это пиксел, который остается на своем месте при трансформации спрайта. Вы можете подумать, что поскольку рисунок вашего спрайта прямоугольный, то после трансформации он будет продолжать занимать тот же прямоугольный регион на экране. Это не всегда так. Рассмотрим пример. Пусть у нас есть спрайт смотрящего влево человека, чей reference пиксел определен на кончике его носка. Тогда после применения поворота на 90 градусов, человек окажется на месте в котором бы он был если бы он споткнулся и упал вперед. Конечно это может быть удобно в некоторых случаях, но если Вы хотите чтобы спрайт после трансформации оставался в том же месте экрана, что и до нее, то должны сначала вызвать метод defineReferencePixel(int x, int y) и установить reference пиксел в центр спрайта. Именно так я и поступил в конструкторе Cowboy. В метод defineReferencePixel(int x, int y) надо передавать координаты относительно верхнего левого угла спрайта, в то время как setRefPixelPosition(int x, int y) ртебует координаты относительно верхнего левого угла экрана. Чтобы быть более точным, координаты, посланные в setRefPixelPosition(int x, int y) ссылаются на систему координат Canvas если спрайт рисуется непосредственно на Canvas; но если Sprite рисуется в LayerManager, то координаты надо задавать в системе LayerManager-а. (О том как связаны эти две системы координат говорилось в разделе LayerManager.)

Если вы выполняете сложную трансформацию, то более поздняя трансформация применяется к оригинальному изображению, а не к текущему его состоянию. Другими словами если два раза применить setTransform(TRANS_MIRROR), то вторая трансформация не переведет изображение обратно в исходную позицию, а просто повторит первую трансформацию. Если Вы хотите вернуть трансформированный рисунок в исходное состояние, вызовите метод setTransform(TRANS_NONE). Это проиллюстрировано в начале метода Cowboy.advance(int tickCount, boolean left).

Дркгой замечательной возможностью класса Layer (присущей как Sprite так и TiledLayer) является возможность располагать объекты в относительных координатах вместо абсолютных. Если вам надо сместить спрайт на три пикселя от текущего положения, вы можете просто вызвать метод move(int x, int y), передав ему в качестве параметров необходимое смещение в пикселях, а не мучиться с методом setRefPixelPosition(int x, int y), высчитывая абсолютные координаты нового положения спрайта. Очень полезным является метод collidesWith(). Он проверяет пересечение спрайта с другим спрайтом, TiledLayer или даже Image.

В моей игре после обновления состояния всех спрайтов, я проверяю, не сталкнулся ли мой ковбой с кустиком (это происходит в методе Cowboy.checkCollision(Tumbleweed tumbleweed), который я вызываю из JumpManager.advance(int gameTicks)). Я все время проверяю пересечения между ковбоем и всеми кустиками. Поскольку для внеэкранных объектов автоматически возвращается значение false, это не очень расточительно.

В своем примере я не беспокоюсь о столкновении кустиков между собой и травкой на заднем плане, поскольку это неуместно. Если Вы проверяете столкновение на уровне пикселей, Убедитесь, что ваш рисунок имеет прозрачный заднй план. (Это вообще полезно, поскольку если фон не прозрачный, то ваш спрайт будет перекрывать другие спрайты и рисунки. Получится некрасиво.) Созданию файлов изображений посвящено Приложение B.

Ниже приводится код Cowboy.java:

package net.frog_parrot.jump;
 
import javax.microedition.lcdui.*;
import javax.microedition.lcdui.game.*;
 
/**
* Этот класс отображает игрока.
*
* @автор Carol Hamer
*/
publicclass Cowboyextends Sprite{
 
//---------------------------------------------------------
// переменные размера
 
/**
* Ширина и высота изображения ковбоя.
*/
static int WIDTH=32;
static int HEIGHT=48;
 
/**
* Задаем порядок кадров анимации
*/
static int[] FRAME_SEQUENCE={3,2,1,2};
 
//---------------------------------------------------------
// instance fields
 
/**
* Координаты где появляется ковбой вначале игры
*/
int myInitialX;
int myInitialY;
 
/**
* Индекс прыжка
*/
int myNoJumpInt=-6;
 
/**
* Находится ли ковбой в состоянии прыжка.
*/
int myIsJumping= myNoJumpInt;
 
/**
* Если ковбой сейчас прыгает, эта переменная содержит на сколько
* очков увеличится счет в течении прыжка. Это поможит при расчете
* бонуса, который определяется соотношением прыжков и
* перепрыгнутых кустиков.
*/
int myScoreThisJump=0;
 
//---------------------------------------------------------
// инициализация
 
/**
* Конструктор. Инициализация рисунков и анимации.
*/
public Cowboy(int initialX, int initialY) throws Exception{
super(Image.createImage("/icons/cowboy.png"),
WIDTH, HEIGHT);
myInitialX= initialX;
myInitialY= initialY;
// Устанавливаем reference пиксел в середине изображения ковбоя.
// Это позволит сохранить его изображение на том же месте
// при повороте
defineReferencePixel(WIDTH/2,0);
setRefPixelPosition(myInitialX, myInitialY);
setFrameSequence(FRAME_SEQUENCE);
}
 
//---------------------------------------------------------
// Игровые методы
 
/**
* Если ковбой врезался в куст, уменьшаем счет.
*/
int checkCollision(Tumbleweed tumbleweed){
int retVal=0;
if(collidesWith(tumbleweed,true)){
retVal=1;
// Если ковбой врезался в куст, делаем его невидимым и
// перезагружаем, чтобы использовать снова.
tumbleweed.reset();
}
return(retVal);
}
 
/**
* Установить ковбоя на исходную позицию.
*/
voidreset(){
myIsJumping= myNoJumpInt;
setRefPixelPosition(myInitialX, myInitialY);
setFrameSequence(FRAME_SEQUENCE);
myScoreThisJump=0;
// at first the cowboy faces right:
setTransform(TRANS_NONE);
}
 
//---------------------------------------------------------
// графика
 
/**
* Переделать рисунок ковбоя..
*/
void advance(int tickCount, boolean left){
if(left){
// Зеркально отражаем рисунок если ковбой идет влево.
setTransform(TRANS_MIRROR);
move(-1,0);
}else{
// Используем оригинальный рисунок если ковбой идет вправа.
setTransform(TRANS_NONE);
move(1,0);
}
// Обновляем анимацию:
// меняем кадр каждый третий виток игрового цикла если
// ковбой идет
if(tickCount%3==0){// немного замедляем анимацию
if(myIsJumping== myNoJumpInt){
// если он не прыгает, устанавливаем следующий кадр
// последовательности
 
nextFrame();
}else{
// Если прыгает, обновляем прыжок:
// Прыжок занимает несколько тактов игрового цикла и в переменной
// myIsJumping хранится фаза прыжка.
myIsJumping++;
if(myIsJumping<0){
// myIsJumping отрицательно. Пока это так, ковбой движется вверх.
// сначала он движется быстро, постепенно замедляясь приближаясь
// к наивысшей точке.
setRefPixelPosition(getRefPixelX(),
getRefPixelY()-(2<<(-myIsJumping)));
}else{
// в противном случае, ковбой начинает падать вниз.
if(myIsJumping!=-myNoJumpInt-1){
setRefPixelPosition(getRefPixelX(),
getRefPixelY()+(2<< myIsJumping));
}else{
// Если прыжок завершен переводим ковбоя в состояние бега.
myIsJumping= myNoJumpInt;
setRefPixelPosition(getRefPixelX(), myInitialY);
// Мы устанавливаем последовательность бега.
setFrameSequence(FRAME_SEQUENCE);
// myScoreThisJump хранит в себе сколько очков мы набрали
// за прыжок. Как только прыжок закончился переводим ее в 0
// we set it back to zero.
myScoreThisJump=0;
}
}
}
}
}
 
/**
* Прыжок.
*/
void jump(){
if(myIsJumping== myNoJumpInt){
myIsJumping++;
// Выключаем использование последовательностей.
setFrameSequence(null);
setFrame(0);
}
}
 
/**
* Этот метод выдает счет в зависимости от результата прыжка.
*/
int increaseScoreThisJump(){
if(myScoreThisJump==0){
myScoreThisJump++;
}else{
myScoreThisJump*=2;
}
return(myScoreThisJump);
}
 
}



Автор Carol Hamer
Перевод: Alex.




Наши соцсети

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

Популярное

Ссылки

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

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