Текст книги "Создание игр для мобильных телефонов"
Автор книги: Майкл Моррисон
Жанр: Зарубежная компьютерная литература, Зарубежная литература
сообщить о неприемлемом содержимом
Текущая страница: 31 (всего у книги 35 страниц)
Основные переменные игры Space Out расположены в специальном классе холста – SOCanvas. Этот класс отвечает за всю игровую логику. Поскольку SOCanvas достаточно велик, я разбил его на отдельные части, полный код класса доступен на прилагаемом компакт-диске. Ниже перечислены наиболее важные переменные:
private LayerManager layers;
private Image background;
private Image smallCar;
private MovingSprite playerSprite;
private MovingSprite[] blobboSprite = new MovingSprite[3];
private MovingSprite[] jellySprite = new MovingSprite[3];
private MovingSprite[] timmySprite = new MovingSprite[3];
private MovingSprite[] missileSprite = new MovingSprite[10];
private Sprite[] explosionSprite = new Sprite[3]; //Спрайты взрывов не перемещаются, поэтому они создаются, как обычные анимационные спрайты
private Player musicPlayer;
private Player explosionPlayer;
private Player gameoverPlayer;
private boolean gameOver;
private int score, carsLeft;
Первые несколько переменных используются для хранения менеджера слоев, фонового изображения, изображения маленькой машины, а также различных игровых спрайтов. Изображение background – это изображение пустыни и неба, а изображение маленького автомобиля используется для отображения числа оставшихся жизней. Спрайтовые переменные представляют особый интерес. Они отражают сущность разработки игры Space Out: спрайты не создаются и не уничтожаются во время игры.
В игре не создается динамически случайное число спрайтов, как вы, вероятно, ожидали. Наоборот, все спрайты создаются при запуске игры. При необходимости спрайты скрываются. Например, если пришелец подбит ракетой, то оба спрайта скрываются, а не уничтожаются. Как видно из объявления переменных, в игре есть по три спрайта каждого из пришельцев, десять ракет и три взрыва.
Далее объявляются объекты класса Player, которые используются для работы с музыкой в игре. Наконец, состояние игры описывается переменными gameOver, score и carsLeft.
Метод start() в игре Space Out очень важен, поскольку он выполняет все необходимые инициализации. Например, в следующем фрагменте кода создается звездное ночное фоновое изображение и изображение маленького автомобиля:
try {
background = Image.createImage("/StarryNight.png");
smallCar = Image.createImage("/SmallCar.png");
}
catch (IOException e) {
System.err.println("Failed loading images!");
}
Когда эти два изображения загружены, вы можете перейти к игровым спрайтам. Если вы вспомните, то в игре есть спрайт автомобиля, управляемый игроком, а также несколько спрайтов ракет, пришельцев и взрывов. Все эти спрайты, кроме спрайта взрыва, – объекты класса MovingSprite; спрайт взрыва является объектом обычного класса Sprite, потому что он неподвижен. Ниже приведен код, создающий все указанные спрайты:
try {
// создать спрайт автомобиля игрока
playerSprite = new MovingSprite(Image.createImage("/Car.png"), 0, 0,
MovingSprite.BA_STOP, this); //Спрайт игрока останавливается по достижении границы экрана
int sequence5[] = { 0, 0, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 4, 4, 3, 3, 2, 2, 1, 1 }; //Эта последовательность позволяет замедлить анимацию фреймов спрайтов
}
int sequence3[] = { 0, 0, 0, 1, 1, 1, 2, 2, 2, 1, 1, 1 };
for (int i = 0; i < 3; i++) {
// создать спрайт пришельца Блоббо
blobboSprite[i] = new MovingSprite(Image.createImage("/Blobbo.png"), 20, 21, 3, 2,
MovingSprite.BA_BOUNCE, this); //Спрайт пришельца Блоббо отталкивается от границы экрана
blobboSprite[i].setFrameSequence(sequence5);
blobboSprite[i].setVisible(false);
// Создать спрайт пришельца Джелли
jellySprite[i] = new MovingSprite(Image.createImage("/Jelly.png"), 21, 21, 1, 4,
MovingSprite.BA_BOUNCE, this); //Спрайт пришельца Джелли отталкивается от границы экрана
jellySprite[i].setFrameSequence(sequence3);
jellySprite[i].setVisible(false);
// Создать спрайт пришельца Тимми
timmySprite[i] = new MovingSprite(Image.createImage("/Timmy.png"), 21, 11, 5, 0,
MovingSprite.BA_WRAP, this); //Если спрайт пришельца Тимми достигает границы экрана, то он появляется у противоположной границы
timmySprite[i].setFrameSequence(sequence3);
timmySprite[i].setVisible(false);
// Создать спрайты взрывов
explosionSprite[i] = new Sprite(Image.createImage("/Explosion.png"), 21, 21);
explosionSprite[i].setVisible(false);
}
// создать спрайты ракет
for (int i = 0; i < 10; i++) {
missileSprite[i] = new MovingSprite(Image.createImage("/Missiles.png"),
11, 11, 0, 0,MovingSprite.BA_HIDE, this);
missileSprite[i].setVisible(false); //Все спрайты в игре сначала скрыты
}
}
catch (IOException e) {
System.err.println("Failed loading images!");
}
Спрайт игрока создается как объект класса MovingSprite с обработкой достижения спрайтом границы экрана. Остальные спрайты – это также движущиеся спрайты, но с различными скоростями и действиями по достижении границы экрана. Например, скорость пришельца Jelly по оси Х равна 1, а по оси Y – 4, а скорость спрайта пришельца Timmy по оси X равна 5, а по оси Y – 0. Спрайты пришельцев Blobbo и Jelly отталкиваются от границ экрана, спрайт Timmy при достижении границы экрана появляется у противоположной стороны, а спрайты ракет скрываются по достижении границы экрана. Наконец, в игре есть анимационный спрайт – спрайт взрыва, который остается неподвижным. Все спрайты за исключением спрайта игрока скрываются перед началом игры.
Затем спрайты добавляются в менеджер слоев, который отвечает за очередность и отрисовку спрайтов. Ниже приведен код работы с менеджером слоев:
layers = new LayerManager();
layers.append(playerSprite);
for (int i = 0; i < 3; i++) {
layers.append(blobboSprite[i]);
layers.append(jellySprite[i]);
layers.append(timmySprite[i]);
layers.append(explosionSprite[i]);
}
for (int i = 0; i < 10; i++) {
layers.append(missileSprite[i]);
}
Несмотря на то что в игре Space Out это не столь значительно, не забудьте, что порядок добавления спрайтов в менеджер слоев определяет порядок вывода их на экран, Z-глубину, – первый спрайт, добавленный с помощью метода append(), будет выведен на экран поверх остальных. Но это не относится к игре Space Out, поскольку порядок вывода спрайтов на экран в ней не важен.
Звуковые эффекты и музыка играют важную роль в игре Space Out, что, вероятно, неудивительно. Ниже приведен код, выполняющий инициализацию проигрывателей:
try {
InputStream is = getClass().getResourceAsStream("Music.mid");
musicPlayer = Manager.createPlayer(is, "audio/midi");
musicPlayer.prefetch();
musicPlayer.setLoopCount(-1);
is = getClass().getResourceAsStream("Explosion.wav");
explosionPlayer = Manager.createPlayer(is, "audio/X-wav");
explosionPlayer.prefetch();
is = getClass().getResourceAsStream("GameOver.wav");
gameoverPlayer = Manager.createPlayer(is, "audio/X-wav");
gameoverPlayer.prefetch();
}
catch (IOException ioe) {
}
catch (MediaException me) {
}
Из приведенного кода видно, что для проигрывания музыки используется один проигрыватель MIDI-аудио, а также два проигрывателя для воспроизведения игровых звуковых эффектов (звуки взрыва и завершения игры).
Последний фрагмент метода start() – это вызов метода, начинающего игру:
newGame();
Чуть позже вы познакомитесь с работой метода newGame(). А пока давайте рассмотрим метод update() – сердце большинства игровых мидлетов, включая Space Out.
Метод update() вызывается один раз за игровой цикл и отвечает за обработку пользовательского ввода, он выполняет обновление спрайтов, проверку столкновений, добавление новых пришельцев, а также обеспечивает работу игры. Метод update() начинается с проверки окончания игры, если это так, то начинается новая игра нажатием клавиши Огонь.
if (gameOver) {
int keyState = getKeyStates();
if ((keyState & FIRE_PRESSED) != 0)
// старт новой игры
newGame();
// игра закончена, не нужно обновлять что либо
return;
}
Чтобы начать новую игру, необходимо вызвать метод newGame(), о котором вы узнаете чуть позже в этой главе. Обратите внимание, что метод update() завершает свою работу сразу после вывода метода newGame(), поскольку нет необходимости обновлять что-либо в игре, которая только начата. Этот метод прекратит свою работу даже в случае, если не будет нажата клавиша Огонь, поскольку игра окончена и нет необходимости обновлять игру.
Если игра не окончена, метод update() продолжает отвечать на пользовательский ввод. Приведенный ниже код отвечает на нажатия клавиш Влево и Вправо, которые управляют автомобилем, а нажатием клавиши Огонь запускается ракета:
int keyState = getKeyStates();
if ((keyState & LEFT_PRESSED) != 0) {
playerSprite.setXSpeed(-2);
}
else if ((keyState & RIGHT_PRESSED) != 0) {
playerSprite.setXSpeed(4); //Автомобиль игрока движется вправо быстрее, чем влево, поскольку для движения влево включается задний ход. Это интересный штрих к игре
}
if ((keyState & FIRE_PRESSED) != 0) {
// воспроизвести звук огня
try {
Manager.playTone(ToneControl.C4 + 12, 10, 100);
}
catch (Exception e) {
}
addMissile(playerSprite);
}
playerSprite.update();
Если вы вспомните дизайн игры Space Out (рис. 18.1), то автомобиль игрока располагается у нижней границы экрана и может перемещаться по горизонтали влево или вправо. Код обработки нажатий клавиш устанавливает скорость автомобиля по оси X, таким образом, автомобиль перемещается в ответ на нажатие клавиши. Важно отметить, что скорость в направлении влево меньше скорости направления вправо. Эта разница объясняется тем, что автомобиль направлен вправо, следовательно, при движении влево он движется назад. В реальности автомобиль движется назад медленнее, чем вперед.
В копилку Игрока
Кроме того, что в игру Space Out добавлена доля реализма, такое различие скоростей несколько усложняет игру. Так, при движении влево сложнее скрыться от ракет.
Код обработки ввода реагирует на нажатие клавиши Огонь тоновым сигналом и вызовом метода addMissle(). О том, как работает этот метод, вы узнаете позже в этой главе, а пока важно отметить, что в этот метод передается спрайт игрока. Идея метода addMissle() заключается в том, что он запускает ракету спрайта, который передается в него в качестве параметра. Итак, вы можете вызвать метод addMissle() и передать в него спрайт игрока или пришельца, в результате будет запущена ракета.
Основная работа метода update() – это обновление спрайтов. Приведенный ниже код обновляет спрайты пришельцев и взрывов:
for (int i = 0; i < 3; i++) {
if (blobboSprite[i].isVisible()) {
blobboSprite[i].update();
blobboSprite[i].nextFrame();
}
if (jellySprite[i].isVisible()) { //Обновляются только видимые спрайты
jellySprite[i].update();
jellySprite[i].nextFrame();
}
if (timmySprite[i].isVisible()) {
timmySprite[i].update();
timmySprite[i].nextFrame();
}
if (explosionSprite[i].isVisible()) {
if (explosionSprite[i].getFrame() < 3) //Этот код создает анимацию взрыва, после чего спрайт взрыва исчезает
explosionSprite[i].nextFrame();
else
explosionSprite[i].setVisible(false);
}
}
Этот код достаточно прост, здесь обновляются и перемещаются спрайты пришельцев и взрывов. Важно понять, что обновляются только видимые спрайты. Возможно, также важным является и то, что спрайты взрыва скрываются после того, как все фреймы показаны.
Совет Разработчику
Чтобы сделать игру более эффективной и не создавать и не удалять объекты динамически, все спрайты создаются в начале игры, а затем скрываются и отображаются, чтобы создать эффект разрушения. В любой момент в игре «существуют» только видимые спрайты.
Следующий фрагмент метода update() достаточно велик, поскольку в нем реализуется большая часть игровой логики.
Код, о котором я говорю, – это код обновления ракет. Он очень важен, поскольку ход игры зависит от того, с каким спрайтом столкнется ракета – со спрайтом игрока или пришельца. Ниже приведен код, работающий со спрайтами ракет:
for (int i = 0; i < 10; i++) {
if (missileSprite[i].isVisible()) {
// ракета игрока?
if (missileSprite[i].getFrame() == 0) { //Индекс фрейма спрайта ракеты используется для определения типа ракеты
for (int j = 0; j < 3; j++) {
// ракета попала в пришельца Блоббо?
if (blobboSprite[j].isVisible())
if (missileSprite[i].collidesWith(blobboSprite[j], false)) {
// Воспроизвести звук разрушения
try {
Manager.playTone(ToneControl.C4 – 6, 100, 100);
}
catch (Exception e) {
}
// создать взрыв
addExplosion(blobboSprite[j]);
// спрятать спрайт и увеличить счет
blobboSprite[j].setVisible(false); //При столкновении спрайта ракеты игрока со спрайтом пришельца оба спрайта исчезают
missileSprite[i].setVisible(false);
score += 10;
break;
}
// ракета попала в пришельца Джелли?
if (jellySprite[j].isVisible())
if (missileSprite[i].collidesWith(jellySprite[j], false)) {
// воспроизвести звук разрушения
try {
Manager.playTone(ToneControl.C4 – 6, 100, 100);
}
catch (Exception e) {
}
// создать взрыв
addExplosion(jellySprite[j]);
// спрятать спрайт и увеличить счет
jellySprite[j].setVisible(false);
missileSprite[i].setVisible(false);
score += 15;
break;
}
// ракета попала в спрайт пришельца Тимми?
if (timmySprite[j].isVisible())
if (missileSprite[i].collidesWith(timmySprite[j], false)) {
// воспроизвести звук разрушения
try {
Manager.playTone(ToneControl.C4 – 6, 100, 100);
}
catch (Exception e) {
// создать взрыв
addExplosion(timmySprite[j]);
// спрятать спрайт и увеличить счет
timmySprite[j].setVisible(false);
missileSprite[i].setVisible(false);
score += 20; //Поскольку пришельцы Тимми летают быстрее других, за его уничтожение дается больше очков
break;
}
}
}
// ракета пришельца?
else {
// ракета попала в спрайт автомобиля?
if (missileSprite[i].collidesWith(playerSprite, false)) {
// воспроизвести звук взрывающегося автомобиля
try {
explosionPlayer.start(); //Если ракета пришельца попадает в автомобиль, воспроизводится звук взрыва
}
catch (MediaException me) {
}
// создать взрыв
addExplosion(playerSprite);
// установить спрайт игрока в исходное положение
playerSprite.setPosition(0,
getHeight() – playerSprite.getHeight() – 10); //Положение автомобиля игрока изменяется, чем создается иллюзия нового автомобиля
playerSprite.setXSpeed(4);
playerSprite.setYSpeed(0);
// спрятать спрайт ракеты
missileSprite[i].setVisible(false);
// проверить, закончена ли игра
if (carsLeft– == 0) {
// остановить музыку
try {
musicPlayer.stop();
}
catch (MediaException me) {
}
// воспроизвести звук окончания игры
try {
gameoverPlayer.start();
}
catch (MediaException me) {
}
// спрятать спрайт автомобиля
playerSprite.setVisible(false);
gameOver = true;
return;
}
}
}
missileSprite[i].update();
}
}
Приведенный код проверяет столкновение спрайта ракеты с другими игровыми спрайтами. Это очень важная информация, поскольку она определяет, как взаимодействует спрайт ракеты с прочими спрайтами. Ракеты игрока могут подбить только пришельцев, а ракеты пришельцев – игрока.
Совет Разработчику
Чтобы спрайты пришельцев не уничтожили друг друга, спрайты ракет разработаны так, что они не могут уничтожить спрайты пришельцев. Поэтому код работы со спрайтами ракет сначала проверяет, кому принадлежит ракета, и уже потом проверяется столкновение спрайта ракеты с другими спрайтами. Кроме того, это позволяет минимизировать число проверок столкновений, делая код игры более эффективным.
Если вы внимательно изучите приведенный код, то увидите, что при попадании ракеты в спрайт пришельца выполняются следующие действия:
1. воспроизводится тоновый сигнал;
2. создается спрайт взрыва;
3. спрайты пришельца и ракеты скрываются;
4. счет увеличивается;
Если ракета – это ракета пришельца, то выполняются следующие действия:
1. воспроизводится звуковой файл;
2. создается спрайт взрыва;
3. спрайт автомобиля возвращается в исходное положение у левого края экрана;
4. спрайт ракеты скрывается;
5. проверяется окончание игры.
Если значение переменной carsLeft показывает, что у игрока закончились автомобили, то игра заканчивается. Музыка останавливается, воспроизводится звук конца игры, спрайт автомобиля скрывается, а значение переменной gameOver становится равным true.
Совет Разработчику
Другой способ сделать игровой код эффективнее – передать в метод collidesWith() в качестве второго параметра значения false. Если вы вспомните, то этот параметр определяет, будет ли использоваться пиксельный метод детектирования столкновений. В игре Space Out такой способ детектирования изображений не нужен.
Следующий фрагмент кода в методе update() добавляет спрайты в игру случайным образом. Скорость, с которой новые пришельцы появляются в игре, зависит от уровня сложности. Следовательно, вы можете плавно изменять сложность игры, увеличивая скорость появления пришельцев. Это можно сделать на основании счета игры:
if (score < 250) {
if (rand.nextInt() % 40 == 0) //Это самый простой уровень игры, который заканчивается, когда игрок набирает 250 очков
addAlien();
}
else if (score < 500) {
if (rand.nextInt() % 20 == 0)
addAlien();
}
else if (score < 1000) {
if (rand.nextInt() % 10 == 0)
addAlien();
}
else {
if (rand.nextInt() % 5 == 0) //Это самый сложный уровень игры, который начинается, когда игрок набирает 1000 очков
addAlien();
}
Если счет меньше 250, то вероятность добавления нового спрайта на каждом игровом цикле равна 1/40. Это соответствует самому простому уровню игры. Сложность игры постепенно увеличивается, пока счет не станет равным 1000. В этом случае вероятность появления пришельца в игровом цикле равна 1/5. Если вы задумались о том, сколько времени требуется для выполнения одного цикла, то знайте, что пришельцы появляются достаточно быстро.
Последний фрагмент кода метода update() случайным образом запускает ракеты пришельцев:
if (rand.nextInt() % 4 == 0) {
switch (Math.abs(rand.nextInt() % 3)) {
// стреляет Блоббо
case 0:
for (int i = 0; i < 3; i++)
if (blobboSprite[i].isVisible()) {
addMissile(blobboSprite[i]);
break;
}
break;
// стреляет Джелли
case 1:
for (int i = 0; i < 3; i++)
if (jellySprite[i].isVisible()) {
addMissile(jellySprite[i]);
break;
}
break;
// стреляет Тимми
case 2:
for (int i = 0; i < 3; i++)
if (timmySprite[i].isVisible()) {
addMissile(timmySprite[i]);
break;
}
break;
}
}
Этот код случайным образом определяет, должен ли выстрелить пришелец. Соотношение 1 к 4 было выведено методом проб и ошибок. Если в результате выполнения кода должна быть запущена ракета, то выбирается ракета пришельца. Затем определяется видимый спрайт нужного типа, после чего вызывается метод addMissle() для запуска ракеты. Обратите внимание, что спрайт пришельца, запускающего ракету, передается в метод addMissle().
Совет Разработчику
Подобно тому, как возрастает скорость появления пришельцев с увеличением счета игры, можно увеличивать и число запускаемых пришельцами ракет.
Вывод графики на экран сравним с методом обновления игры. В листинге 18.3 приведен код метода draw() класса SOCanvas.
Листинг 18.3. Метод draw() класса SOCanvas выводит фоновое изображение и игровые слои, а также при необходимости сообщение о завершении игры
private void draw(Graphics g) {
// вывод звездного ночного неба
g.drawImage(background, 0, 0, Graphics.TOP | Graphics.LEFT);
// вывод слоев
layers.paint(g, 0, 0);
// вывод оставшегося числа автомобилей и счета
for (int i = 0; i < carsLeft; i++)
g.drawImage(smallCar, 2 + (i * 20), 2, Graphics.TOP | Graphics.LEFT); //Этот код выводит число оставшихся автомобилей
g.setColor(255, 255, 255); // white
g.setFont(Font.getFont(Font.FACE_SYSTEM,
Font.STYLE_PLAIN, Font.SIZE_MEDIUM));
g.drawString(Integer.toString(score), 175, 2,
Graphics.TOP | Graphics.RIGHT);
if (gameOver) {
// вывести сообщение о конце игры и счет
g.setColor(255, 255, 255); // white
g.setFont(Font.getFont(Font.FACE_SYSTEM, Font.STYLE_BOLD,
Font.SIZE_LARGE));
g.drawString("GAME OVER", 90, 40, Graphics.TOP | Graphics.HCENTER);
g.setFont(Font.getFont(Font.FACE_SYSTEM, Font.STYLE_BOLD,
Font.SIZE_MEDIUM));
g.drawString("Final Score : " + score, 90, 70, Graphics.TOP |
Graphics.HCENTER);
}
// вывести графику на экран
flushGraphics();
}
Первая часть кода выводит фоновое изображение на экран – звездное небо в пустыне. Затем одной строкой кода выводятся слои, за которыми следуют вывод оставшихся автомобилей и счет в игре. Если игра окончена, то выводится соответствующее сообщение, состоящее из слов «GAME OVER» и числа набранных очков.
Правообладателям!
Это произведение, предположительно, находится в статусе 'public domain'. Если это не так и размещение материала нарушает чьи-либо права, то сообщите нам об этом.