Программирование мобильных телефонов на Java

         

Рынок мобильных телефонов развирается стремительными



Рынок мобильных телефонов развирается стремительными темпами. Все больше телефонов имеют поддержку технологии Java. Веянье игровой индустрии захватило и мобильные телефоны, поэтому платформа Java 2 ME позиционируется в большей степени как игровая платформа Для мобильных телефонов. При разработке игр под профиль MIDP 1.0 программист сталкивается с массой проблем в виде написания большого количества собственных классов для создания игрового процесса, рисование графики, уровней и так далее. В профиль MIDP 2.0 добавлено пять игровых классов, значительно упрощающих создание мобильных игр, это:
 GameCanvas - абстрактный класс, содержащий основные элементы игрового интерфейса;   Layer — абстрактный класс, отвечающий за уровни представляемые в игре;  LayerManager - менеджер уровней;  sprite - представляет на экране спрайтовое изображение;  TiledLayer - отвечает за создание фоновых изображений.
Все вышеперечисленные классы доступны только в профиле MIDP 2.0. Также можно воспользоваться этими классами и в программировании телефонов Siemens под профиль MIDP 1.0, импортировав пакет com.siemens.mp.color_game. Компания Siemens входит в экспертную группу MIDP Expert Group и, по всей видимости, на базе уже имеющихся игровых классов от компании Siemens были созданы игровые классы для профиля MIDP 2.0.

При использовании игровых классов профиля MIDP 2.0, построение мобильной игры основано на системе уровней. Формируя игру, программист создает необходимое ему количество уровней. Каждый из уровней содержит набор графических элементов, например; можно создать уровень с фоновым изображением, уровень с препятствиями, уровень с игровыми бонусами, уровень с главным персонажем игры. После этого все имеющиеся уровни компонуются воедино, накладываются один уровень на другой и прорисовываются на экране телефона. Контроль над всеми уровнями, осуществляется при помощи класса

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

В этой главе, сначала вы познакомитесь с устройством всех игровых классов, а затем будет создан ряд примеров иллюстрирующих работу и взаимодействие игровых классов профиля MIDP 2.0.

Класс GameCanvas



Абстрактный класс GameCanvas составляет основу интерфейса всей создаваемой игры. Этот класс отвечает за прорисовку экрана, улучшая механизм работы игрового цикла и не используя при этом входные системные потоки. Весь игровой процесс может быть сосредоточен в одном цикле метода run () интерфейса Runnable.

Игровой цикл, используемый классом GameCanvas, отличается от аналогичного цикла применяемого классом Canvas. Типичная проблема игрового цикла класса Canvas заключается в том, что он разбит на несколько потоков. Вывод графики происходит в одном методе, обработка событий с клавиш — в другом, а изменение состояния игрового процесса в третьем. Такая модель работы может давать сбои, поэтому в классе GameCanvas была придумана более изящная конструкция игрового цикла, располагающаяся в одном единственном цикле.

public void run()
 {
Graphics g = getGraphics();
while(true)
{
// метод, обрабатывающий нажатия клавиш с телефона
inputKey();
// метод, прорисовывающий графику
GameGraphics();
// копирует графические элементы
на экран из внеэкранного буфера
flushGraphics();
}


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

Сама же обработка событий с клавиш упрощена с помощью метода getKeyState (). Вы просто определяете нажатую клавишу пописываете соответствующие игровые действия для нее. Изменены некоторые названия констант для клавиш телефона. Обработка событий с клавиш телефона происходит с помощью следующих констант:
 static int DOWN_PRESSED - движение.вниз;  static int FIRE_PRESSED - реализует стрельбу из оружия;  static int GAME_A_PRESSED - игровая клавиша A;  static int GAME_B_PRESSED - игровая клавиша В;  static int GAME_C_PRES.SED - игровая клавиша С;  static int GAME_D_PRESSED - игровая клавиша D;  static int LEFT_PRESSED - движение влево;  static int RIGHT_PRESSED-движение вправо;.  static int UP_PRESSED - движение вверх.
Использование констант значительно упрощает работу с объектом, а с помощью метода getKeyStates () класса GameCanvas, можно определить, какая именно клавиша нажата в данный момент. Проанализируем методы класса GameCanvas:
void f lushGraphics () - копирует изображение из внеэкранного буфера на экран;  void flushGraphics(int x, int y, int width, int height) -копирует изображение из внеэкранного буфера на экран в заданный по размеру прямоугольник;  protected Graphics getGraphics () - получает графические элементы для представления их в последствии классом GameCanvas;  int getKeyStates ()-определяет какая из клавиш нажата;  void paint (Graphics g) - рисует графические элементы, представленные классом GameCanvas.
Класс GameCanvas создает основной цикл игрового процесса в одном потоке, наблюдает за событиями, получаемыми с клавиш телефона на основе которых производит постоянное обновление экрана.

Класс Layer



Абстрактный класс Layer задает основные свойства для всех созданных уровней игры.

Класс Layer имеет два подкласса TiledLayer и Sprite. При создании любых других подклассов класса Layer необходимо реализовать метод paint () в этих классах, чтобы иметь возможность рисовать созданные уровни на экране телефона, представляемом классом Graphics. С помощью методов класса Layer можно задавать размер и позицию уровня.
 int getHeight() - получает высоту экрана;  int getwidth() - получает ширину экрана;  int getX () - получает горизонтальную координату уровня;  int getY () - получает вертикальную координату уровня;  void move (int dx, int dy) — перемещает уровень на dx и dy координаты;  abstract void paint (Graphics g) - рисует уровень; void setPosition (int x, int у) - устанавливает уровень в позицию, обозначенную в координатах х и у.

Класс TiledLayer



С помощью класса TiledLayer создается фон игровой сцены. Фоновое изображение выполняется в виде одинаковых по размеру ячеек как показано на рис. 8.1.


Рис 8.1. Ячейки фонового изображения

Количество и расположение ячеек может варьироваться как угодно, но нумерация ячеек следует от единицы, слева направо и сверху вниз. Построение сцены происходит путем загрузки исходного изображения, разбитого на ячейки и указания индекса необходимой ячейки на игровом поле. Но прежде нужно создать объект класса TiledLayer с помощью конструктора, прототип которого выглядит следующим образом:

TiledLayer(int columns, int rows, Image image,
 int tileWidth, int tileHeight)

 columns-количество столбцов;  rows - количество строк;  image - исходное изображение;  tileWidth - размер ширины ячейки в пикселях;  tileHeigh - размер высоты ячейки в пикселях.
Размеры одной ячейки по ширине и высоте могут быть разными, но все ячейки исходного изображения должны быть одинаковыми по размеру. В качестве примера, возьмем за основу изображение на рис. 8.1 с шестью ячейками и предположим, что размер одной ячейки по ширине равен 10 пикселям, а по высоте 15 пикселям, тогда загрузка изображения и создание объекта TiledLayer может выглядеть следующим образом:

Image im = Image.createlmage("/fon.png");
TiledLayer tl =new TiledLayer(3, 2, im, 10, 15);


Загрузив изображение и создав объект класса TiledLayer, вы можете приступить к разметке фона па игровом ноле. Допустим, каждая из перечисленных по номеру ячеек обладает следующими характеристиками:

1) камни;

2) трава;

3) вода;

4) песок;

5) воздух;

6) заграждение.

А игровое поле разбито на пятнадцать столбцов и десять строк, тогда создав массив данных, очень легко выполнить разметку всего поля, например таким образом:

int[] pole =
{
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
 1, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
1, 1, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
1, 1, 1, 1, 5, 5, 5, 1, 1, .5, 1, 1, 5, 5, 5,
1, 1, 1, 1, 1, 5, 5, 1, 1, 1, 1, 1, 1, 5, 5, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, 6, 6, 6, 1,
1, 1,1, 1, 1, 4, 4, 4, 4, 6, 6, 6, 6, 6, 4, 4,
4,4,4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4,
4, 4, 4, 3, 3/3, 3, 3, 3, 3, 3, 3, 4, 4, 4,
4, 4, 4, 3, 3, 3, 3,,3, 3, 3, 3, 3,
 }


Затем весь имеющийся массив данных считывается с помощью цикла и рисуется методом paint () на экране телефона.

Познакомимся с методами класса TiledLayer:
 int createAnimatedTilefint staticTilelndex) - создает анимационный фон и возвращает следующий индекс ячейки;  int getCellHeight () - получает высоту ячейки в пикселях;  int getCellWidth() - получает ширину ячейки в пикселях;  int getColumns () - получает количество колонок, на которое разбито изображение фона;  int getRows () - получает количество строк, на которое разбито изображение фона;  void paint (Graphics g) - рисует фон;  void setCellfint col, int row, int tilelndex) - рисует заданную ячейку.

Класс LayerManager



Менеджер уровней представлен классом LayerManager. Это класс осуществляет представление любого количества уровней на игровом поле. Для создания объекта нужно воспользоваться конструктором класса LayerManager.
 LayerManager () - создает уровень.
А чтобы добавить уровни в игру необходимо использовать следующие методы:
 void append (Layer 1) - добавляет уровень в менеджер уровнен;  Layer getLayerAt (int index) - получает уровень с заданным индексом;  int getsize ()-получает общее количество уровней;  void insert (Layer 1, int index) - подключает новый уровень в заданный индекс;  void paint (Graphics g, int x, int у)- представляет текущий менеджер уровней в заданных координатах; void remove (Layer 1) - удаляет уровень из менеджера уровней.
Предположим, у вас имеется четыре уровня: фон, игрок, препятствия и артефакты. Для того чтобы связать все четыре уровня воедино, создается объект класса LayerManager и вызывается метод append (). Например:

LayerManager im = new LayerManager();
im.append(fon);
im.append(igrok);
im.append(prep); .
im. append (artf.) ;


И все! Четыре перечисленных уровня отражается на игровом поле.

Класс Sprite



Механизм работы с объектом класса Sprite идентичен модели работы с классом TiledLayer. Но если класс TiledLayer в основном отвечает за фоновое изображение, то с помощью класса Sprite рисуются на экране основные анимированные герои, космические корабли, машины, люди, звери, артефакты и так далее. Изображение, загружаемое в игру, может быть выполнено в виде анимационной последовательности. Количество рисунков в анимационной последовательности неограниченно, а отсчет происходит от нуля. Располагаться рисунки могут как в виде столбца, так и в виде колонки, в зависимости от ваших предпочтений. Каждый рисунок анимационной последовательности называется фреймом. Фрейм может быть любого размера по ширине и высоте, но все фреймы должны быть одинаковых размеров. Размер фрейма должен быть известен, потому что он используется при создании объекта класса Sprite. Есть три конструктора класса Sprite каждый из которых можно использовать при создании объекта класса Sprite.
 Sprite (Image image) - создает не анимированный спрайт;  Sprite(Image image, int frameWidth, int frameHeight)—создает анимационный спрайт, взятый из заданного фрейма;  Sprite (Sprites)- создает спрайт из другого спрайта.
Для перехода по фреймам исходного изображения, а также определения столкновения между объектами используются методы класса Sprite.
 boolean collidesWith(Sprite s, boolean pixelLevel) — определяет столкновение между спрайтами;  boolean collidesWith (TiledLayer t, boolean. pixelLevel) - определяет столкновение между спрайтом и препятствием, нарисованным при помощи класса TiledLayer;  int getFrame () - получает текущий фрейм;  void next Frame ()- осуществляет переход наследующий фрейм;  void paint (Graphics g) - рисует спрайт;  void prevFrame () - осуществляет переход на предыдущий фрейм;  void setFrame(int sequencelndex) - устанавливает заданный фрейм;  void setFrameSequence (int [ ] sequence) - устанавливает определенную фреймовую последовательность;  void set Image(Image img, int frameWidth, int frameHeight)-изменяет изображение спрайта на новое изображение;  void setTransform( int transform) - производит трансформацию спрайта.  public void defineReferencePixel (int x, int у)- изменяет опорную позицию спрайта, перенося ее в точку с координатами х и у.
Метод defineReferencePixel () изменяет опорную позицию спрайта, но для чего это нужно? Опорная позиция для спрайта задается левым верхним углом, но не всегда это удобно, поэтому опорную позицию можно перенести, в центр спрайта. Например, если спрайт сделан в виде машины, то при вращении вокруг своей оси, если опорная позиция перенесена в центр, вращение будет правдоподобным. Но если не переопределить позицию, то вращение произойдет в точке левого верхнего угла спрайта и выглядеть это будет не вполне естественно, как будто у машины пробито одно из колес. Для этого вызывается метод  def ineReferencePixel () с заданными координатами и переопределяет опорную позицию, например в центр спрайта:

defineReferencePixel(frameWidth / 2, frameHeight / 2);


Метод setTransform() производит трансформацию спрайта, вращая или зеркально отображая спрайт вокруг своей оси с помощью следующих констант:
 static1int TRANS_MIRROR;  static int TRANS_MIRROR_ROT180;  static int TRANS_MIRROR_ROT270;  static int TRANS_MIRROR_ROT90;  static int TRANS_NONE;  static int TRANS_ROT180;  static int TRANS_ROT270;  static int TRANS_ROT90.
Посмотрите на рис. 8.2, где очень привлекательно показаны константы для различных состояний спрайта.

Рассмотрев классы GameCanvas, Layer, Sprite, TiledLayer и LayerManager, перейдем к практическим занятиям этой главы.

Создание фонового изображения



С помощью класса TiledLayer можно создавать любое количество уровней, накладывая их друг на друга, а с помощью менеджера уровней, представленного классом LayerManager, отслеживать все имеющиеся уровни. В качестве примера будет создан фон на основе элементов разметки игрового поля. Фоновое изображение загружается из файла fon.png. Само изображение выполнено в виде шести ячеек размером 15x15 пикселей.

В листинге 8.1 находится код примера создающего фоновое изображение. Ознакомьтесь с листингом, а потом мы перейдем к анализу этого примера.


Рис. 8.2. Константы трансформации

/ * *
Листинг 8.1 класс MainGame
*/
import
javax.microedition.lcdui.* ;
import
javax.microedition.midlet.*;
public class MainGame extends
MIDlet implements CommandListener
{
//команда выхода
private Command exitMidlet = new Command!"Выход",
Command.EXIT, 0) ;
// объект класса MyGameCanvas
private MyGameCanvas mr;
public void startApp.{}
{
// обрабатываем исключительную ситуацию
try{
// инициализируем объект класса MyGameCanvas;
mr = new MyGameCanvas() ;
// запускаем поток
mr.start();
// добавляем команду выхода
mr.addCommand(exitMidlet);
mr.setCommandListener(this) ;
// отражаем текущий дисплей
Display .getDisplay (this) . setCurrent (mr-) ;
} catch (Java . io. ID-Exception zxz) {} ;
}
public void pauseApp() {}
public void destroyApp(boolean unconditional)
{
// останавливаем поток
if(mr != null) mr.stop'O; }
public void coramandAction(Command c, Displayable d)
if (c == exitMidlet)
{
destroyApp(false) ; notifyDestroyed() ;
}
}
 }
/ * *
Файл MyGameCanvas.Java класс MyGameCanvas
*/
import Java.io.IOException;
 import javax.microedition.Icdui.*;
import javax.microedition Nlcdui.game.* ;
public class MyGameCanvas extends GameCanvas implements
Runnable-
{
// создаем объект класса TiledLayer
private TiledLayer fonPole;
// создаем объект класса LayerManager
private LayerManager im;
// логическая переменная для выхода из цикла
boolean z;
public MyGameCanvas(), throws IOException
{
// обращаемся к конструктору суперклассаNCanvas
super(true);
// инициализируем fonPole
fonPole = Fon () ;
// создаем менеджер уровней
1m = new LayerManager();
// добавляем объект fonPole к уровню im.append (fonPole);
}
public void start()
{
z = true;
// создаем и запускаем поток
Thread t = new Thread(this);
t.start();
}
//* метод, создающий объект класса TiledLayer
и загружающий фоновое изображение
*/
 public TiledLayer Fon()throws IOException
{
// загрузка изображения из файла ресурса
Image im = Image.createlmage("/fon.png");
// создаем объект класса TiledLayer
fonPole = new TiledLayer(/*столбцы*/10,/*строки*/10,
/*изображение*/lm,/*ширина*/15,/*высота*/15);
// разметка игрового поля
int[] pole =
{
5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
1, 5, 5, 5, 5, 5, 5, 5, 5, 5,
1, 1, 5, 5, 5, 5, 5, 5, 5, 5,
1, 1, 1, 1, 5, 5, 5, 1, 1, 5,
1, 1, 1, 1, 1, 5, 5, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 6, 6,
1, 1, 1, 1, 1, 1, 6, 6, 6, 6,
2, 4, 4, 4, 4, 4, 3, 3, 3, 3,
2, 2, 2, 4, 4, 4, 3, 3, 3, 3,
2, 2, 2, 4, 4, 4, 3, 3, 3, 3
 };
// цикл, считывающий разметку поля
for(int i = 0; i < pole.length; i + + )
{
/* присваиваем каждому элементу игрового
поля определенную ячейку изображения
 im*/ fonPole.setCell(i % 10, i / 10, pole[i]);
}
return fonPole;
}
public void stop(){ z = false;
 }
public void run()
{
// получаем графический контекст Graphics g = getGraphics();
 while (z)
{
// рисуем графические элементы
init(g) ;
// останавливаем цикл на 20 миллисекунд
try { Thread.sleep(20);
 }
catch (Java.lang.InterruptedExceptlori zx.z) {};
 }
 }
private void init(Graphics g)
{
// белый цвет фона для перерисовки экрана
g.setColor(0xffffff);
// размер перерисовки экрана
g.fillRect(0, 0, getWidth(), getHeight());
// рисуем уровень с левого верхнего угла дисплея
1m.paint(g, 0, 0) ;
// двойная буферизация
flushGraphics();
}
}


Листинг 8.1 состоит из двух классов MainCanvas и MyGameCanvas, находящихся в файлах MainCanvas.java и MyGameCanvas.java. Анализ листинга начнем с класса MyGameCanvas. В первых строках кода этого класса объявляются два объекта классов TiledLayer и LayerManager, атак же логическая переменная z.

private TiledLayer fonPole;
private LayerManager lm,
boolean z;


Объект fonPole класса TiledLayer будет отвечать за фоновое изображение. Объект im класса LayerManager является менеджером уровней. Логическая переменная z необходима для прерывания цикла в методе run () и для окончания системного потока, в котором происходит работа всего игрового цикла.

В конструкторе MyGameCanvas происходит инициализация объекта fonPole класса TiledLayer и объект im класса LayerManager. Инициализированный объект fonPole добавляется менеджером уровней к текущему уровню для представления на экране телефона. Обратите внимание, объект fonPole инициализируется с помощью метода Fon ().

Image im = Image.createlmage("/fon.png");
fonPole= new TiledLayer(/*столб*/10,/*строки*/10,im,
/*ширина*/15,/*высота*/15);


В этих двух строках происходит загрузка исходного изображения из файла ресурса и создание объекта fonPole с помощью конструктора класса TiledLayer.

Вся разметка игрового поля выполнена в виде десяти строк и десяти столбцов. Первые два параметра конструктора класса TiledLayer как раз и отвечают за количество столбцов и строк. Третий параметр конструктора - это исходное изображение, выполненное в виде шести ячеек, каждая размером 15x15 пикселей. Два последних параметра конструктора класса TiledLayer определяют ширину и высоту ячейки. При создании объекта класса TiledLayer необходимо быть внимательным и указывать реальные размеры одной ячейки. Если размер одной ячейки будет, предположим 20x20 пикселей, а вы обозначите как 15x15 пикселей, то в итоге ячейки изображения загружены не будут.

Дальше в методе Fon () происходит разметка игрового поля выполненного в виде десяти столбцов и десяти строк.

int[] pole =
{
5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
1, 5, 5, 5, 5, 5, 5, 5, 5, 5,
1, 1, 5, 5, 5, 5, 5, 5, 5, 5,
1, 1, 1, 1, 5, 5, 5, 1, 1, 5,
1, 1, 1, 1, 1, 5, 5, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 6, 6,
1, 1, 1, 1, 1, 1, 6, 6, 6, 6,
2, 4, 4, 4, 4, 4, 3, 3, 3, 3,
2, 2, 2, 4, 4, 4, 3, 3, 3, 3,
2, 2, 2, 4, 4, 4, 3, 3, 3, 3,
 };


Все строки и столбцы состоят из элементов. Отсчет ячеек происходит от единицы и выше. Присвоение номера ячейки исходного изображения одному из элементов и организует разметку игрового поля, которое в последствии будет рисоваться на экране. Единственное О5чем нельзя забывать - это о размере дисплеев реальных телефонов. Если вы имеете десять столбцов и размер каждой ячейки 15 пикселей по ширине, то в итоге ваше игровое поле в ширину будет 10x15 = - 150 пикселей. Не каждый телефон может похвастаться таким разрешением, поэтому при создании игрового поля нужно учитывать эти нюансы. Вслед за разметкой игрового поля в методе Fon () происходит считывание всех элементов с помощью цикла for.

forfint i = 0; i < pole.length; i++)
{
fonPole.setCell(i % 10, i / 10, pole[i]);
}


Присвоение номера ячейки определенному элементу происходит при помощи метода setCell (). В этом методе первый параметр-номер столбца, второй - номер строки и последний - номер ячейки изображения. Здесь главное не запутаться, потому что номера столбцов и строк начинаются с нуля из-за того, что это обычный массив данных, а все массивы, как вы знаете, ведут свой отсчет с нуля, тогда как ячейка исходного изображения начинается с единицы. Сразу возникает вопрос, а почему не произвести отсчет тоже с нуля, чтобы не было путаницы? Дело в том, что отсчет и производится с нуля, но число ноль - это своего рода зарезервированное значение для ячеек изображения. Нулевое значение может использоваться, но оно не. загружает ничего, поэтому отсчет ячеек ведется с единицы. С методом Fon () мы разобрались, перейдем к методу init ().

g.setColor(0xffffff);
g.fillRect(0, 0, getWidth0, getHeight());


В этих строках кода происходит постоянная перерисовка фона экрана. Эти действия вы производили в главе 7, когда разбирали механизм отсечения.

С помощью метода paint () рисуется уровень. Начало точки вывода уровня задано точкой 0,0, что соответствует началу, системы координат.

И последний метод flushGraphics () осуществляет двойную буферизацию, копируя графические элементы из внеэкранного буфера на экран.

В методе run () происходит остановка игрового цикла. Перед тем как цикл создается с помощью оператора while, методом getGraphics () происходит получение графического контекста, что и является одним из достоинств механизма игрового цикла в профиле MIDP 2.0.

В файле MainGame.java создается основной класс мидлета MainGame. В методе startApp() производится создание объекта рассмотренного класса MyGameCanvas, добавляется команда выхода, запускается системный поток и отражается текущий дисплей. В методе destroyApp () происходит остановка потока методом stop () класса MyGameCanvas.

Обработка событий с клавиш телефона



В профиле MIDP 2.0 предусмотрена улучшенная обработка событий получаемых с клавиш телефона. Используя метод getKeyState () можно определять состояние клавиш телефона и действовать адекватным образом. 'В демонстрационном примере к этому разделу мы выведем на экран мячик, созданный при помощи класса MySprite являющимся подклассом класса Sprite. В листинге 8.2 представлен код примера, в котором на экране рисуется синий мяч, а с помощью клавиш телефона Up, Down, Left и Right этот мяч можно передвигать по экрану. Листинг 8.2 состоит из трех классов: MainGame. MyGameCanvas и MySprite, расположенных в трех разных файлах MainGame.java, MyGameCanvas.java и MySprite.java.

/**
Листинг 8.2 класс MainGame
*/
import javax.microedition.Icdui.*;
import javax.microedition.midlet.*;
public class MainGame extends MIDlet implements
CommandListener
{
// команда выхода
private Command exitMidlet = new Command("Выход",
Command.EXIT, 0);
// объект класса MyGameCanvas
private MyGameCanvas mr;
public void startApp()
{
// обрабатываем исключительную ситуацию
 try
{
// инициализируем объект класса
MyGameCanvas mr = new MyGameCanvas();
// запускаем поток mr.start();
// добавляем команду выхода
mr.addCommand(exitMidlet);
mr.setCommandLis'tener (this) ;
// отражаем текущий дисплей
Display.getDisplay(this).setCurrent(mr);
}
catch (Java.io.lOException zxz) {} ;
}
public void pauseApp() {}
public void destroyApp(boolean unconditional)
{
// останавливаем поток
if(mr != null) mr.stopt);
 }
public void commandAction (Command c, Displayable d;)
 {
if (с == exitMidlet)
{
destroyApp(false); notifyDestroyed();
}
}
}
/**
файл MyGameCanvas.Java класс MyGameCanvas
 */
import java.io.*;
import javax.microedition.Icdui.*;
import javax.microedition. Icdui .game .*;
public class MyGameCanvas extends GameCanvas implements
Runnable
{
// создаем объект класса MySprite
private MySprite bol;
// создаем объект класса Lay'erManager
private LayerManager lm;
// логическая переменная
boolean z;
public MyGameCanvas() throws IOException
{
// обращаемся к конструктору суперкласса Canvas
super(true);
// загружаем изображение
Image im = Image.createlmage("/bol.png");
// инициализируем объект bol
bol = new MySprite (itn, 23, 23);
// выбираем позицию в центре экрана
bol.setPosition(getWidth()/2, getHeight()/2);
// инициализируем менеджер уровней
1m = new LayerManager();
// добавляем объект bol к уровню
lm.append(bol); }
public void start()
{
z = true;
// создаем и запускаем поток
Thread t = new Thread(this);
t.start();
 }
// останавливаем поток
 public void stop()
{ z = false;
}
public void run()
{
// получаем графический контекст
Graphic's g = getGraphics () ;
while (z)
{
// обрабатываем события с клавиш телефона
 inputKey();
// рисуем графические элементы init(g);
// останавливаем цикл на 20 миллисекунд
 try { Thread.sleep(20);
}
catch (Java.lang.InterruptedException zxz) {};
 }
 }
private void inputKey()
{
// определяем нажатую клавишу
int keyStates = getKeyStates();
// код обработки для левой нажатой клавиши
-if ((keyStates & LEFT_PRESSED) != 0) bol.moveLeft();
// код обработки для правой нажатой клавиши
if ((keyStates & RIGHT_PRESSED) != 0) bol.moveRight();
// код обработки для клавиши вверх
if ((keyStates & UP_PRESSED) != 0) bol.moveUp();
// код обработки для клавиши вниз
if ((keyStates & DOWN_PRESSED) != 0) bol.moveDown();
}
private void init(Graphics g)
{
// белый цвет фона g.setColor(0xffffff);
// перерисовываем экран
g.fillRect (0 , 0, getWi.dth () , getHeight());
// рисем уровень в точке 0,0
lm.paint(g, 0, 0) ;
// двойная буферезация
flushGraphics();
};
}
/**
файл MySprite.Java
класс MySprite
*/
import javax.microedition.Icdui.*;
 import javax.microedition.Icdui.game.*;
public class MySprite extends Sprite
{
// конструктор
public MySprite(Image image, int fw, int fh)
{
// обращаемся к конструктору суперкласса
super (image, fw, fh);
}
// метод для клавиши Left public void moveLeft() ;
 {
// передвигаем спрайт
move(-1,0);
}
// метод для клавиши Right public void moveRight()
{
/ / передвигаем спрайт
move(1,0);
}
// метод для клавиши Up public void moveUp()
{
// передвигаем спрайт
move(0,-1);
}
// метод для клавиши Down public void moveDown()
{
// передвигаем спрайт
move(0,1);
'}
 }


В файле MySprite.java находится класс MySprite, с которого и начнем рассмотрение листинга. Конструктор класса MySprite обращается к конструктору своего суперкласса Sprite!

super(image, fw, fh);


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

Для передвижения спрайта по экрану телефона созданы методы moveLef t (), moveRight (), moveUp () и moveDowri (), в основу которых положены вызовы метода move (). Метод move () имеет два параметра - это координаты по осям X и Y. Задавая необходимое смещение на 1, 2, 3 и более пикселей по одной из осей, вы будете производить движение объекта по экрану.

Класс MyGameCanvas работает по схеме, использованной в разделе 8.6, с той лишь разницей, что вместо фонового изображения загружается спрайт в виде мячика. В конструкторе класса MyGameCanvas происходит загрузка исходного изображения bol.png, это и есть наш мячик или спрайт. Спрайт размером 23x23 пикселя. При создании объекта bol класса MySprite

bol = new MySprite(im, 23, 23)


используется изображение bol.png, затем, в конструкторе класса MyGameCanvas происходит выбор позиции для первоначальной отрисовки спрайта на экране с помощью метода setPosition (). Мячик рисуется в центре экрана и добавляется методом append () к уровню.

В методе run () в игровом цикле происходит вызов двух методов. Метод init () производит рисование всех графических элементов с помощью менеджера уровней, а метод input Key () осуществляет обработку нажатий клавиш телефона.

private void inputKey()
{
int keyStates = getKeyStates();
if ((keyStates & LEFT_PRESSED) != 0) bol.moveLeft();
if ((keyStates & RIGHT_PRESSED) !=0) bol.moveRight();
if ((keyStates & UP_PRESSED) != 0) bol.moveUp();
if ((keyStates & DOWN_PRESSED) != 0) bol.moveDown ();
}


В методе inputKey () происходит определение нажатой клавиши посредством метода getKeyState (). Весь остальной код в методе inputKey () использует оператор if для обработки нажатых клавши, вызывая соответствующие методы moveLeftO, moveRight (), moveUp ()или moveDown () для перемещения объекта по экрану.

В классе MainGame из файла MainGame.java создается объект класса MyGameCanvas, запускается системный поток и отражается текущий экран.

В этом примере использовался спрайт файла ресурса bol.png состоящий из одного фрейма размером 23x23 пикселя. В следующем разделе мы рассмотрим технику анимации спрайтов в играх, и будем исполВзовать спрайт, состоящий уже из нескольких фреймов.

Анимация в игровом процессе



Анимация в игровом процессе строится на основе последовательной цепочки рисунков. Как вы уже знаете, отдельно взятый рисунок из анимационной последовательности в Java 2 ME называется фреймом. Для того чтобы осуществить плавную анимацию в игре, необходимо выполнить ряд сменяющих друг друга рисунков. Посмотрите на рис. 8.3, где изображен матрос с флажками.


Рис. 8.3. Анимационная последовательность

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

Анимация повсеместно используется в компьютерных и мобильных играх. Вы наверно замечали, что при перемещении в игре персонажа, он производит ряд повторяющихся движений, создавая видимость игрового цикла. Такие элементарные движения следствие перехода" по имеющимся фреймам исходного изображения. Для этих целей в Java 2 ME имеются специальные методы класса Sprite.

Метод nextFrame () позволяет осуществить переход по всем имеющимся фреймам исходного изображения. Как только достигается последний фрейм, то автоматически происходит переход к первому фрейму с последующим переходом по всей имеющейся последовательности, что обеспечивает цикличность анимации.

Давайте рассмотрим пример использующий анимационную последовательность, изображенную на рис. 8.3. В этом примере на экран выводится изображение матроса и осуществляется цикличный переход По всем имеющимся фреймам, создавая эффект движения матроса, который с помощью семафорной азбуки передает слово «анимация».

В листинге 8.3, а так же на компакт-диске в папке \Code\Listing8_3\src дается код примера иллюстрирующего работу анимационной последовательности.

/**
Листинг 8.3
класс MainGame
*/
import javax.microedition.lcdui.*;
import javax.microedition.midlet. * ;
public class MainGame extends MIDlet implements
CornmandListener
{
// команда выхода
private Command exitMidlet = new Command(«Выход»,
Command.EXIT, 0);
// объект класса
 MyGameCanvas
private MyGameCanvas mr;
public void startApp()
{
// обрабатываем исключительную ситуацию
try{
// инициализируем объект класса MyGameCanvas
mr = new MyGameCanvas();
// запускаем поток
mr .start();
// добавляем команду выхода
mr.addCommand(exitMidlet);
mr:setCommandListener{this);
/7 отражаем текущий дисплей
Display.getDisplay(this).setCurrent(mr);
}catch (Java.io.lOException zxz) {}; }
public void pauseApp() {}
public void destroyApp(boolean unconditional)
{
// останавливаем потоку
if(mr != null) mr.stop();
}
public void commandAction(Command c, Displayable d)
{
if (с == exitMidlet)
{
destroyApp(false);
notifyDestroyedO ;
 }
}
}
/**
файл MyGameCanvas.java
класс MyGameCanvas 
*/
import java.io.
import javax.microedition.Icdui.*;
 import javax.microedition.Icdui.game.*;
public class MyGameCanvas extends GameCanvas implements
Runnable
{
// создаем объект класса MySprite
private Matros matros;
// создаем объект класса LayerManager
private LayerManager lm;
// логическая переменная
boolean z;
public MyGameCanvas() throws IOException
{
// обращаемся к конструктору суперкласса Canvas
super(true);
// загружаем изображение
Image im = Image.createlmage(«/matros.png»);
// инициализируем объект matros
matros = new Matros(im, 94, 100);
// выбираем позицию
matros.setPosition(30, 30);
// инициализируем менеджер уровней
1m = new LayerManager();
// добавляем объект матроса к уровню
lm.append(matros); }
public void start()
{
{
z= true;
// создаем и згшускаем поток Thread t = new Thread(this);
t.start () ;
}
// останавливаем поток
public void stop()
{ z = false;
}
public void run()
{
// получаем графический контекст
 Graphics g = getGraphics();
while (z)
{
// рисуем графические элементы init(g)
// останавливаем цикл try { Thread.sleep(250);
}
catch (Java.lang.InterruptedException zxz) {};
}
 }
private void init(Graphics g)
 {
// белый цвет фона
g.setColor(0xffffff);
// перерисовываем экран
g.fillRect(0, 0, getWidth(),getHeight());
// рисуем уровень в точке 0,0
lm.paint(g, 0 , 0) ;
// рисуем анимацию
matros.Animation();
// двойная буферизация
iluGhGraphics();
}
}
/* *
файл Matros.Java класс
Matros * /
import- javax .rnicroedition. Icdui .* ;
import javax.microedition.lcdui.game.*;
public class Matros extends Sprite
{
// конструктор
public Matros(Image image, int fw, int fh)
{
// обращаемся к конструктору суперкласса
super(image, fw, fh) ;
}
// метод осуществляющий анимацию
public void Animation!)
{
// вызываем следующий фрейм
nextFrame();
}
}


В листинге 8.3 нам интересно несколько моментов. В классе Matros, являющимся подклассом класса Sprite, создается метод Animation (), который выглядит следующим образом:

public void Animation()
 {
nextFrame();
}


Метод Animation () осуществляет тот самый последовательный переход по имеющимся фреймам исходного изображения. В классе MyGameCanvas происходит создание объекта класса Matros:

private Matros matros;


Затем в конструкторе класса MyGameCanvas загружается изображение матроса и инициализируется объект matros.

Image im = Image.createlmage(«/matros.png»);
matros = new Matros(im, 94, 100);


Размер одного фрейма с матросом равен 94x100 пикселей, поэтому указывается размер именно одного фрейма. По умолчанию загружается самый первый фрейм изображения, но можно использовать метод setFrame () для установки необходимого фрейма из анимационной последовательности. В методе Graphics () класса MyGameCanvas происходит вызов метода Animation ():

matros.Animation();


Это в итоге приводит к цикличному перебору всех имеющихся фреймов. Откомпилируйте код из листинга 8.3 и посмотрите работу этого примера. На экране телефона матрос с помощью семафорной азбуки передает слово «анимация».

Столкновение объектов



Практически во всех играх приходится обрабатывать события связанные со столкновением двух объектов или с препятствием. В профиле MIDP 2.0 существует три отличных метода, отслеживающих факт столкновения. Все три метода определены в классе Sprite.
 collidesWith(Image image, int x, int y, Boolean pixelLevel) - определяет факт столкновения со статическим изображением;  collidesWith (Sprite s, Boolean pixelLevel) - определяет столкновение между двумя объектами класса Sprite;  collidesWith(TiledLayer t. Boolean pixelLevel) -отслеживает столкновение между объектом класса Sprite и объектом класса TiledLayer.
Используя эти методы можно обрабатывать всевозможные ситуации, связанные со столкновением. Как правило, при столкновении должны произойти какие-то события, чаще всего связанные с изменением первоначального состояния объекта. Это может быть уничтожение объекта, его перемещение, уменьшение или увеличение. В основе изменения состояния объекта положен перебор имеющихся фреймов в анимационной последовательности или перерисовка изображения на новом месте. Для этих операций в классе Sprite имеется несколько методов. С одним из методов nextFrame (), вы уже познакомились в разделе 8.8, рассмотрим оставшиеся методы.
 prevFrame () - с помощью этого метода происходит переход к предыдущим фреймам изображения. Этот метод подобен методу nextFrame (), только переход осуществляется в обратном порядке;  setFrame() - производит установку при помощи индекса заданного фрейма из всей имеющейся последовательности фреймов.  setFrameSequence () - устанавливает определенную фреймовую последовательность при помощи массива индексов в анимационной последовательности;  getFrame () - узнает, какой из фреймов исходного изображения используется в текущий момент;  set Image () - заменяет исходное изображение на новое. Можно использовать этот метод, например в том случае, если объект уничтожен, и его необходимо перерисовать заново.
Набора этих методов вполне достаточно для обработки различных ситуаций возникающих в игровом процессе. Однако необходимо очень тщательно разобраться в действии всех вышеперечисленных методов. Для этого был написан исходный код примера, иллюстрирующего работу методов nextFrame (), prevFrame (), setFrame () и setFrameSequence (). В этой программе на экран выводятся четыре бомбы и синий шарик, перемещая который в разные стороны можно произвести столкновение с четырьмя бомбами. Все бомбы являются объектами класса MySprite, являющимся подклассом класса Sprite. Метод, обрабатывающий столкновение мяча и бомбы, использует один из четырех методов nextFrame (), prevFrame.(), setFrame () и setFrameSequence () для каждой из бомб, что красочно иллюстрирует работу каждого метода. Исходные, изображения бомбы и мяча выполнены в виде последовательности четырех фреймов размером 23x23 пикселя. Первый фрейм мяча и бомбы является исходным изображением, а три последующих фрейма реализованы в виде взрыва. Переход по этим фреймам создает иллюзию взрыва мячика или бомбы. Но оттого, что для каждой бомбы как мы договорились, используются различные методы, то результат может оказаться неожиданным. Поэтому внимательно посмотрите на код примера в листинге 8.4 и запустите программу  с помощью любого эмулятора, поддерживающего профиль MIDP 2.0. Либо откомпилируйте исходный код из листинга 8.4 и внимательно ознакомьтесь с работой этой программы. Я уверен, что вы без труда разберетесь, что именно нужно сделать для логического завершения взрывов бомб и мяча. Алгоритм действий очень прост и может быть следующим после столкновения мяча с одной из мин. Необходимо произвести взрыв, последовательно переходя по всем фреймам, после чего, например, нарисовать бомбу и мячик заново. В листинг 8.4 предложен исходный код примера.

/ * *
листинг 8.4
класс MainGame
*/
import javax.microedition.Icdui.*;
import javax.microedition.midlet.*;
public class MainGame extends MIDlet implements
CommandListener
{
// команда выхода
private Command
exitMidlet = new Command(«Выход»,Command.EXIT, 0);
// объект класса MyGameCanvas
private MyGameCanvas mr;
public void startApp()
{
// обрабатываем исключительную ситуацию.
try{
// инициализируем'объект класса MyGameCanvas
mr = new MyGameCanvas();
// запускаем поток
mr.start{};
// добавляем команду выхода
mr.addCommand(exitMidlet) ;
 mr.setCommandListener(this);
// отражаем текущий дисплей
Display.getDisplay(this).setCurrent(mr) ;
}
catch (Java . io. lOException zxz) {} ;
 }
public void pauseApp() {}
public void destroyApp(boolean unconditional)
{
// останавливаем поток
if(mr != null) mr.stopf);
 }
public void commandAction(Command c, Displayable d)
{
if (c = = exitMidlet)
{
destroyApp (false);
notifyDestroyed(); }
 }
  }
}
/**
файл MyGameCanvas.Java
класс MyGameCanvas
*/
import java.io.*;
import javax.microedition.Icdui.*;
import javax.microedition.Icdui.game.*;
public class MyGameCanvas
extends GameCanvas implements
Runnable
{
// создаем объект класса MySprite
private MySprite bol;
// создаем объект класса LayerManager
private LayerManager lm;
// создаем бомбы
private MySprite bombal, bomba2, bombaB, bomba4;
// логическая переменная
boolean z;
public MyGameCanvas() throws lOException
{
// обращаемся к конструктору суперкласса Canvas
super(true);
// загружаем изображение мяча
linage bollmage = Image.createlmage («/bol.png»)
// инйциалтзируем объект bol
bol = new MySprite(bollmage, 23, 23);
// выбираем позицию в центре экрана
bol.setPosition(getWidth() /2, getHeight. () /2) ;
// загрузка изображения бомбы
Image bombalmage = Image.createlmage(«/bomba.png»);
// инициализируем первую бомбу
bombal = new MySprite(bombalmage, 23, 23);
// выбираем позицию для первой бомбы
bombal.setPosition(10, 10);
// инициализируем вторую бомбу
bomba2 = new MySprite(bombalmage, 23, 23);
// выбираем позицию для второй бомбы
bomba2 . setPosition( getwidth()-30, 10);
// инициализируем третью-бомбу
bоmbа3 = new MySprite(bombalmage, 23, 23);
// выбираем позицию для третьей бомбы
bоmbа3.setPosition(10, getHeight()-40);
// инициализируем четвертую бомбу
bomba4 = new MySprite(bombalmage, 23, 23);
// выбираем позицию для четвертой бомбы
bomba4.setPosition(getWidth()-30, getHeight 0-40);
// инициализируем менеджер уровней
lm = new LayerManager();
// добавляем мяч к уровню
lm.append(bol);
// добавляем бомбы к уровню
lm.append(bombal);
lm.append(bomba2);
lm.append(ЬотЬаЗ);
lm.append(bomba4); }
// обрабатываем столкновение
public void stolknovenie()
{
// столкновение с первой бомбой
if (bol. collidesWith (bombal, true))
{
bol.nextFrame();
bombal.nextFrame(); }
// столкновение со второй бомбой
if(bol.collidesWith(bomba2, true))
{
bol.prevFrame(); bomba2.prevFrame();
}
// столкновение с третьей бомбой
if (bol .collidesWith(bоmbа3 , true))
{
bol.setFrame (2) ;
vbomba3.setFrame(0);
}
// столкновение с четвертой бомбой
if(bol.collidesWith(bomba4, true))
{
int[] i = {2,3};
bol.setFrame(0);
bomba4 . setFrameSequence (i) ;
}
 }
public void start()
{
z = true;
// создаем и запускаем поток
 Thread t = new Thread(this);
t.start();
}
// останавливаем поток
public void stop()
{ z = false; }
public void run()
{
// получаем графический контекст
Graphics g = getGraphics(); while (z)
 {
// столкновение с препятствием
stolknovenie ();
// обрабатываем события с клавиш телефона
inputKey();
// рисуем графические элементы init(g);
// останавливаем цикл на 20 миллисекунд
 try { Thread.sleep(20);
}
catch (Java.lang.InterruptedException zxz) {};
}
}
private void inputKey()
{
//-определяем нажатую клавишу
int keyStates = getKeyStates();
// код обработки для левой нажатой клавиши
if ((keyStates & LEFT_PRESSED) != 0) bol.moveLeft();
// код обработки для правой нажатой клавиши
if ((keyStates & RIGHT_PRESSED) != 0) bol.moveRight();
// код обработки для клавиши вверх
if ((keyStates & UP_PRESSED) != 0) bol.moveUp();
// код обработки для клавиши вниз
if ({keyStates & DOWN_PRESSED) !=-0) bol.moveDown();
}
private void init(Graphics g)
 {
// желтый цвет фона
g.setColor(0xffff00);
// перерисовываем экран
g.fillRect(0, 0, getWidth(), getHeight());
// рисем уровень в точке 0,0
lm.paint(g, 0, 0);
// двойная буферизация
flushGraphics();
}
}
/**
файл MySprite.Java класс MySprite
*/
import javax.microedition.Icdui.*;
import javax.microedition.Icdui.game.*;
public class MySprite extends Sprite
// констоуктор
oublic MySprite(Image image, int fw, int fh)
// обращаемся к конструктору суперкласса
super( image , fw, fh);
}
//метод для левой нажатой клавиши
public void moveLeft()
//передвигаем спрайт
move(-1,0);
//метод  для правой нажатой клавиши
publiс void moveRight()
//передвигаем спрайт
move(1,0);
}
// метод для клавиши вверх
 public void rnoveUp ()
// передвигаем спрайт
// метод для клавиши вниз
public void moveDown()
//передвигаем спрайт
 move(0,1);
}
}


В листинге 8-4 содержатся три класса MainGame, MyGameCanvas и My Sprite. Основной код обработки столкновений бомб и мяна находится в классе Мус игпйСа n vas - этому классу мы и уделим особое внимание при разборе - листинга.

В конструкторе класса MyGameCanvas происходит загрузка изображения мяча из файла ресурса bol.png, инициализация объекта bol, класса MySprite и устанавливается позиция прорисовки на экране объекта bol.

ImagebolImage = Image.createlmage(«/bol.png») ;
bol= new KySprioe(bollmage, 23, 23);
bol.setPosition(getWidth()/2 ,getHeight()/2 ) ;


Заметьте, что позиция вывода мяча на экран установлена в центре экрана, но эта позиция определена для левой верхней точки мяча, поэтому именно левый верхний угол изображения спрайта мяча будет находиться в центре экрана. Для того чтобы нарисовать сам спрайт в центре экрана, нужно переопределить опорную позицию мяча с помощью метода def ineRef erencePixel ().

Затем в конструкторе класса MyGameCanvas загружается изображение бомбы.

Image bombalmage=Image.createlmage(«/bomba.png»);


После этого происходит инициализация четырех объектов bombal, bomba2, bomba3 и bomba4 класса MySprite и устанавливается позиция для вывода всех четырех бомб на экран телефона.

bombal = new MySprite(bombalmage, 23, 23);
bombal.setPosition(10,10);
bomba2 = new MySprite(bombalmage, 23, 23);
bomba2.setPosition( getwidth()-30, 10);
bomba3 = new MySprite(bombalmage, 23, 23);
bonba3.setPosition(10, getHeight()-40);
bomba4 = new MySprite(bombalmage, 23, 23);.
bomba4.setPosition(getWidth()-30, getHeight()-40);


Все четыре бомбы рисуются в разных углах экрана слева направо и сверху вниз.

Произведя загрузку необходимых изображений и инициализируя все объекты класса MySprite, можно добавить их к уровню с помощью менеджера уровней.

lm.append(bol);
lm.append(bombal);
lm.append(bomba2);
lm.append(bomba3);
 lm. append (bomba4);


Наша задача в этом примере - это определение столкновения бомб и мячика, для этого создан метод stolknovenie (), где при помощи конструкции if/ else происходит обработка столкновения объектов bol и bombalbomba4. Сейчас было бы очень хорошо, если бы вы могли запустить  программу из листинга 8.4 и попробовали осуществить столкновение мяча с четырьмя бомбами. Как мы договорились, при столкновении будет обсуждаться работа четырех разных методов.

В столкновении с первой бомбой, находящейся в левом верхнем углу экрана, используется метод next Frame (). Если вы переместите мячик по экрану, то при наезде на первую бомбу произойдет взрыв бомбы и мяча. То есть начнется перебор всех имеющихся фреймов обоих объектов в цикличном порядке. Как только вы уберете мячик с бомбы, взрыв обоих объектов прекратится, потому что закончится перебор фреймов изображений. А состояние бомбы и мяча будет соответствовать одному из фреймов всей анимационной последовательности, при этом возможности повлиять на остановку- перехода по фреймам в методе nextFrame () нет.

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

Третья бомба рисуется в нижнем углу экрана и для обработки столкновения мяча с бомбой используется метод setFrame (). Этот метод производит переход по заданным фреймам всей анимационной последовательности по номеру или индексу фрейма. В этом примере используется следующий код при столкновении мяча и третьей бомбы.

bol, setFrame (2);
bomba3.setFrame(0);


Когда вы передвинете мячик на третью бомбу, то увидите, что изображение мячика изменится, и будет соответствовать третьему фрейму всей анимационной последовательности мячика. Состояние бомбы останется неизменным, потому что используется индекс 0, а это первый фрейм бомбы.

Четвертая бомба при столкновении задействует метод setFrameSequenсе (), благодаря которому можно использовать фреймовые последовательности в виде массива индексов.

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