Стреляющий космический корабль
Давайте теперь поговорим о том, как сделать так, чтобы заставить звездолет (или любой другой объект игры) стрелял. Это действие используется почти во всех action-играх типа MarsAttacks или моей игры J-Rio. И хотя оно реализуется почти элементарно, я получил много писем от людей, не знающих, как сделать это (в чем нет ничего постыдного ;-). Что ж, приступим к решению задачи!
Наброски классов и важные шаги
Как всегда, я хочу начать эту главу с объяснения основ классов и главных шагов и техник, которыен я собираюсь использовать. Так как Java - объекто-ориентированный язык программирования, и выстрел должен иметь множество атрибутов и типов поведения (движение, проверка на столкновения с врагом, стенами, ...), должно быть понятно, что мы должны создать класс Shot, чтобы реализовать все типы поведения. Этот класс будет содержать все переменные атрибутов (координаты x и y, скорость...) выстрела и иметь методы, представляющие поведения типа движения. В большинстве случаев выстрел производится объектом игрока, так что у нас будет объект игрока с методом generateShot. Он будет генерировать выстрел (не удивляйтесь этому имени ;-) на позиции игрока, с правильныи направлением... и будет возвращать этот объект в вызывающий класс. В моем примере вызывающим классом будет Main. Этот класс хранит сгенерированный выстрел в массиве объектов выстрелов. Каждый раз, когда будет вызываться метод run главного класса, мы будем iterate over этот массив, чтобы перемещать выстрелы, уничтожать их, если они покинули поле игры, а также мы можем проверять наличие столкновений с врагами либо элементами уровня (не ищите, их нет в апплете, прилагающемся к главе). Метод paint апплета будет также run through the shots array и прорисовывать каждый в нем существующий выстрел.
Если вы все поняли, можете просто взять исходный код и просмотреть его самостоятельно. Классы просты, и в них нет ничего особенного. Тем не менее, вы конечно можете прочитать мои объяснения, приведенные ниже.
Класс Shot
Этот класс хранит координаты выстрела, и имеет метод moveShot для реализации поведения движения в y-направлении и метод drawShot(), рисующий изображения выстрелов на экране.
import java.awt.Graphics;
import java.awt.Color;
public class Shot
{
-
// переменные
private int x_pos;
private int y_pos;
// размер выстрела
private final int radius = 3;
// конструктор
public Shot(int x, int y)
{
-
x_pos = x;
y_pos = y;
// возвращает y-позицию, необходимую для проверки, покинул ли выстрел пространство игры
public int getYPos()
{
-
return y_pos;
// движение выстрела по оси y
public void moveShot(int speed)
{
-
y_pos += speed;
// рисуем выстрел на экране
public void drawShot(Graphics g)
{
-
g.setColor(Color.yellow);
g.fillOval(x_pos, y_pos, radius, radius);
Класс Player
Этот класс также прост, как три копейки. Он также имеет метод move (чтобы двигать игрока по оси x), и метод draw, и хранит координаты космического корабля. Единственный "интересный" метод - generateShot.
import java.awt.Graphics;
import java.awt.Color;
public class Player
{
-
// переменные
private int x_pos;
private int y_pos;
// конструктор
public Player(int x, int y)
{
-
x_pos = x;
y_pos = y;
// перемещение корабля по оси x
public void moveX(int speed)
{
-
x_pos += speed;
// генерируем выстрел на текущей позиции космического корабля
// и возвращаем этот выстрел в вызывающий метод
public Shot generateShot()
{
-
Shot shot = new Shot(x_pos, y_pos);
return shot;
// рисуем игрока
public void drawPlayer(Graphics g)
{
-
g.setColor(Color.red);
int [ ] x_poly = {x_pos, x_pos - 10, x_pos, x_pos + 10};
int [ ] y_poly = {y_pos, y_pos + 15, y_pos + 10, y_pos + 15};
g.fillPolygon(x_poly, y_poly, 4);
Класс Main
Теперь взглянем на класс Main. Я удалил все неважные части (обозначенные как "...") для поведения стрельбы корабля. Вы увидите эти части, если скачаете исходный код.
import java.applet.*;
import java.awt.*;
public class Main extends Applet implements Runnable
{
-
// переменные
...
private Player player;
private Shot [] shots;
// константы
private final int shotSpeed = -2;
...
// двойная буферизация
private Image dbImage;
private Graphics dbg;
public void init()
{
-
...
// генерация массива shot
shots = new Shot[5];
...
public void run ()
{
-
while (true)
{
-
// Iterate over the shot array and move shots,
// проверка, ушел ли выстрел за пределы поля игры
// вы можете добавить сюда другие проверки (проверку на столкновение...)
for(int i=0; i<shots.length; i++)
{
-
if(shots[i] != null)
{
-
// перемещение выстрела
shots[i].moveShot(shotSpeed);
// проверка, покинул ли выстрел пределы поля игры
// если значение true, удалить из массива
if(shots[i].getYPos() < 0)
{
-
// удалить выстрел
shots[i] = null;
// другие операции
// ...
// например, проверка столкновений...
// ...
// перемещение игрока
...
...
public boolean keyDown(Event e, int key)
{
-
...
// Генерация нового выстрела при нажатии клавиши "Пробел"
else if(key == 32)
{
-
// генерация нового выстрела и попытка сохранить его в массиве
for(int i=0; i<shots.length; i++)
{
// only store shot if there is a place left in the array
-
if(shots[i] == null)
{
-
shots[i] = player.generateShot();
// вызов break, чтобы сохранить выстрел только один раз, важно!
break;
...
public void paint (Graphics g)
{
-
// рисуем игрока
...
// рисуем выстрелы
for(int i=0; i<shots.length; i++)
{
-
if(shots[i] != null)
{
-
shots[i].drawShot(g);
Вывод
Надеюсь, что я смог убедить вас, что генерация выстрелов - несложное дело. Техника хранения игровых объектов одного типа в массиве и iterate over его каждый раз, когда вызываются методы run и paint, часто используется в программировании игр и других приложений, так что не забывайте о ней, если окажетесь в схожей ситуации.
Скачать исходный код апплета
Запустить апплет
Следующая глава:
Основы платформенной игры