Создание игр для мобильных телефонов
Шрифт:
Вероятно, сложно представить, что это изображение может помочь имитировать движение, но именно этот эффект достигается при быстрой смене фреймов. Вы можете создать анимационный спрайт, передав конструктору спрайта изображение, а также его размер. Фреймы изображения должны иметь одинаковый размер. Ниже приведен код создания анимационного спрайта, изображение которого показано на рис. 6.2:
roidSprite = new Sprite(Image.createImage(«/Roid.png»), 42, 35);
Этот код определяет размер фрейма в изображении – 42х35 пикселей. Фреймы в изображении могут располагаться вертикально, горизонтально или в двух направлениях. Если вы располагаете фреймы по сетке, то нумерация происходит слева направо и сверху вниз (рис. 6.3).
Чтобы создать фреймовую анимацию спрайта, необходимо вызвать методы nextFrame и prevFrame:
roidSprite.nextFrame;
Этот метод отображает следующий фрейм анимации спрайта. По умолчанию эта последовательность соответствует порядку следования фреймов в изображении, однако вы можете изменить эту последовательность. При достижении конца последовательности воспроизведения фреймов начинается воспроизведение с противоположного конца. В любое время вы можете узнать индекс текущего фрейма, вызвав метод getFrame. Этот метод возвращает индекс фрейма в последовательности, а не реальный индекс в изображении. Рассмотрим в качестве примера следующую последовательность фреймов:
int [] sequence = {0, 1, 2, 3, 3, 3, 2, 1};
Пятый элемент этой последовательности – это третий фрейм изображения. Если сейчас отображается пятый фрейм анимации, то метод getFrame возвратит значение 4 (отсчет ведется от 0), а не номер фрейма в изображении. Метод setFrame позволяет назначить текущий индекс фрейма. Выполнив setFrame(6), вызовите фрейм с номером 6, поскольку 2 – это номер фрейма, который стоит шестым по счету. Помните, что эти номера соответствуют местам фреймов в изображении. Последовательности фреймов можно использовать для имитации взмахов крыльев, взрывов и т. п. С помощью метода setFrameSequence вы можете изменить последовательность отображения фреймов. Этот метод принимает в качестве параметра целочисленный массив. Ниже приведен пример вызова этой функции для спрайта с птицей:
birdSprite.setFrameSequence(sequence);
Если вы уже близки к пониманию анимации спрайтов, то можно двинуться дальше! Оставшаяся часть главы посвящена совершенствованию мидлета UFO, рассмотренного в предыдущей главе. Вы добавите анимацию спрайта и пользовательский ввод. Ведь лучше один раз увидеть, чем сто раз услышать!
Создание программы UFO 2
Пример программы UFO из предыдущей главы поможет на практике освоить анимацию спрайтов. Теперь вы можете перевести мидлет на новый уровень, добавив управление, а также астероиды – препятствия на пути НЛО. Я буду называть эту программу UFO 2.
Мидлет UFO 2 содержит следующие изменения по отношению к исходной программе:
► пользовательский ввод, обеспечивающий управление летающим объектом;
► анимационные астероиды, летающие по экрану;
► детектирование столкновений летающего объекта с астероидами.
Вы уже достаточно хорошо подготовлены, чтобы сделать это!
Написание программного кода
Класс мидлета в примере UFO 2 не изменился по сравнению с предыдущим приложением, поэтому давайте перейдем непосредственно к изменению класса UFOCanvas. Первое изменение – это добавление трех спрайтов астероидов, которые хранятся в массиве типа Sprite:private Sprite[] roidSpace = new Sprite[3];
В методе start выполняется инициализация спрайтов астероида следующим кодом:
Image img = Image.createImage(«/Roid.png»);
roidSprite[0] = new Sprite(img, 42, 35);
roidSprite[1] = new Sprite(img, 42, 35);
roidSprite[2] = new Sprite(img, 42, 35);Как вы видите, изображение астероида (Roid.png) создается один раз, а затем передается каждому конструктору спрайта. Также при инициализации изменилось и начальное положение НЛО:
ufoSprite.setPosition((getWidth – ufoSprite.getWidth) / 2, (getHeight – ufoSprite.getHeight) / 2);
Хотя этот код может показаться странным, но он не делает ничего особенного, просто выводит спрайт в центре экрана, чтобы НЛО сразу не столкнулся с астероидом, который стартует из точки (0,0). В методе update находятся наиболее интересные новые строки кода. Вся обработка пользовательского ввода сосредоточена в следующем фрагменте кода:
int keyState = getKeyStates;
if ((keyState & LEFT_PRESSED) != 0)
ufoXSpeed–;
else if ((keyState & RIGHT_PRESSED) != 0)
ufoXSpeed++;
if ((keyState & UP_PRESSED) != 0)
ufoYSpeed–;
else if ((keyState & DOWN_PRESSED) != 0)
ufoYSpeed++;
ufoXSpeed = Math.min(Math.max(ufoXSpeed, -8), 8); //Скорость НЛО устанавливается случайно из диапазона от -8 до 8
ufoYSpeed = Math.min(Math.max(ufoYSpeed, -8), 8);Этот код просто проверяет нажатия четырех клавиш управления и в соответствии с этим изменяет скорость НЛО. Обратите внимание, что и в этом случае скорость ограничена 8 вне зависимости от того, сколько раз была нажата та или иная клавиша. После того как скорость изменена, НЛО обновляется следующим кодом:
ufoSprite.move(ufoXSpeed, ufoYSpeed); checkBounds(ufoSprite);
Известный метод move перемещает спрайт, а метод checkBounds проверяет, не вышел ли НЛО за границы экрана. Проверка не изменилась, но ее код оформлен отдельным методом. Это очень важно, поскольку вам необходимо выполнить аналогичную проверку и для астероидов. Для этого нецелесообразно копировать код, если можно использовать существующий. Обновление спрайтов астероидов производится в цикле, который выполняет несколько функций. Ниже приведено начало цикла:
for (int i = 0; i < 3; i++) {
Первое, что нужно выполнить в цикле, – это переместить астероиды и проверить, не вышли ли они за границы экрана:
roidSprite[i].move(i + 1, 1 – i); checkBounds(roidSprite[i]);
Единственная хитрость в этом коде – перемещения астероидов. Чтобы каждый астероид двигался со своей особой скоростью, для перемещения используется индекс каждого из них. Аналогичный код используется для изменения очередности следования фреймов анимации:
if (i == 1)
roidSprite[i].prevFrame;
else
roidSprite[i].nextFrame;Идея этого кода заключается в том, что второй астероид вращается в направлении, противоположном остальным. Для этого достаточно пролистывать фреймы спрайта в противоположном направлении относительно других. Оставшаяся часть кода в цикле обновления астероидов проверяет их столкновение с НЛО:
if (ufoSprite.collidesWith(roidSprite[i], true)) {
// воспроизвести предупреждающий звук
AlertType.ERROR.playSound(display);
// вернуть спрайт в исходное положение и обнулить скорости
ufoSprite.setPosition((getWidth – ufoSprite.getWidth) / 2, //Спрайт выводится в центре игрового экрана, его скорость равна 0
(getHeight – ufoSprite.getHeight) / 2);
ufoXSpeed = ufoYSpeed = 0;
for (int j = 0; j < 3; j++)
roidSprite[j].setPosition(0, 0);
// нет необходимости обновлять спрайты астероидов
break;
}
}Если столкновение произошло, то воспроизводится стандартный звук возникновения ошибки (он зависит от конкретной модели телефона), для чего используется объект AlertType. В главе 8 вы узнаете, как использовать разнообразные звуки в играх. В этой программе столкновение возвратит НЛО в исходное положение и обнулит его скорость. Если бы вы создавали полноценную игру, то в этом месте вы бы уменьшили число жизней и проверили, не закончена ли игра. Но в этой программе вы просто изменяете положение спрайтов, и анимация продолжается. По сравнению с мидлетом UFO в методе draw есть только одно незначительное изменение – код, рисующий астероиды:
for (int i = 0; i < 3; i++) roidSprite[i].paint(g);
На этом весь новый код мидлета UFO 2 завершен. В листинге 6.1 приведен полный код нового класса UFOCanvas. Листинг 6.1. Класс UFOCanvas, выполняющий роль холста для мидлета UFO 2
import javax.microedition.lcdui.*;
import javax.microedition.lcdui.game.*;
import java.util.*;
import java.io.*;
public class UFOCanvas extends GameCanvas implements Runnable {
private Display display;
private boolean sleeping;
private long frameDelay;
private Random rand;
private Sprite ufoSprite;
private int ufoXSpeed, ufoYSpeed;
private Sprite[] roidSprite = new Sprite[3]; //В игре UFO 2 есть 3 спрайта астероида
public UFOCanvas(Display d) {
super(true);
display = d;
// установить частоту кадров (30 fps)
frameDelay = 33;
}
public void start {
// установить холст как текущий экран
display.setCurrent(this);
// инициализировать генератор случайных чисел
rand = new Random;
// инициализировать спрайты НЛО и астероидов
ufoXSpeed = ufoYSpeed = 0;
try {
ufoSprite = new Sprite(Image.createImage("/Saucer.png"));
ufoSprite.setPosition((getWidth – ufoSprite.getWidth) / 2,
(getHeight – ufoSprite.getHeight) / 2);
Image img = Image.createImage("/Roid.png");
roidSprite[0] = new Sprite(img, 42, 35);
roidSprite[1] = new Sprite(img, 42, 35);
roidSprite[2] = new Sprite(img, 42, 35);
}
catch (IOException e) {
System.err.println("Failed loading images!");
}
// запустить поток анимации
sleeping = false;
Thread t = new Thread(this);
t.start;
}
public void stop {
// остановить анимацию
sleeping = true;
}
public void run {
Graphics g = getGraphics;
// игровой цикл
while (!sleeping) {
update;
draw(g);
try {
Thread.sleep(frameDelay);
}
catch (InterruptedException ie) {}
}
}
private void update {
// обработка пользовательского ввода, изменение скорости НЛО
int keyState = getKeyStates;
if ((keyState & LEFT_PRESSED) != 0) //Клавиши со стрелками изменяют скорость НЛО по всем четырем направлениям
ufoXSpeed–;
else if ((keyState & RIGHT_PRESSED) != 0)
ufoXSpeed++;
if ((keyState & UP_PRESSED) != 0)
ufoYSpeed–;
else if ((keyState & DOWN_PRESSED) != 0)
ufoYSpeed++;
ufoXSpeed = Math.min(Math.max(ufoXSpeed, -8), 8);
ufoYSpeed = Math.min(Math.max(ufoYSpeed, -8), 8);
// переместить спрайт НЛО
ufoSprite.move(ufoXSpeed, ufoYSpeed);
checkBounds(ufoSprite);
// обновить спрайты астероидов
for (int i = 0; i < 3; i++) {
// переместить спрайты астероидов
roidSprite[i].move(i + 1, 1 – i);
checkBounds(roidSprite[i]); //Эта строка кода отвечает за отрисовку астероида при достижении границ экрана
// изменить отображаемый фрейм астероида
if (i == 1) //Индекс астероида определяет направление анимации
roidSprite[i].prevFrame;
else
roidSprite[i].nextFrame;
// проверить столкновение НЛО с астероидом
if (ufoSprite.collidesWith(roidSprite[i], true)) { //Поскольку второй параметр метода collidesWith равен true, то выполняется пиксельное детектирование столкновения
// воспроизвести предупреждающий звук
AlertType.ERROR.playSound(display);
// восстановить исходные положения и скорости объектов
ufoSprite.setPosition((getWidth – ufoSprite.getWidth) / 2,
(getHeight – ufoSprite.getHeight) / 2);
ufoXSpeed = ufoYSpeed = 0;
for (int j = 0; j < 3; j++)
roidSprite[j].setPosition(0, 0);
// нет необходимости обновлять спрайты астероидов
break;
}
}
}
private void draw(Graphics g) {
// Clear the display
g.setColor(0x000000);
g.fillRect(0, 0, getWidth, getHeight);
// нарисовать спрайт НЛО
ufoSprite.paint(g);
// нарисовать спрайты астероидов
for (int i = 0; i < 3; i++)
roidSprite[i].paint(g);
// отобразить содержимое буфера на экране
flushGraphics;
}
private void checkBounds(Sprite sprite) {
// проверить положение спрайта
if (sprite.getX < -sprite.getWidth)
sprite.setPosition(getWidth, sprite.getY);
else if (sprite.getX > getWidth)
sprite.setPosition(-sprite.getWidth, sprite.getY);
if (sprite.getY < -sprite.getHeight)
sprite.setPosition(sprite.getX, getHeight);
else if (sprite.getY > getHeight)
sprite.setPosition(sprite.getX, -sprite.getHeight);
}
}