Главная » Программирование игр под Android » КЛАССЫ ЭМУЛЯЦИИ DROID INVADERS

0

 

Как обычно, создаем классы для каждого объекта нашего мира. У нас в игре есть следующие объекты: корабль; захватчики; выстрелы; щиты.

Управление осуществляется всезнающим классом World. Как вы видели при представлении объектов нет особой разницы между 2D и 3D. Вместо классов GameObject и DynamicObject мы теперь будем использовать классы Game0bject3D и Dynamic0bject3D. Единственное различие заключается в том, что мы теперь для хранения позиций, скоростей и ускорений применяем экземпляры класса Vector3 вместо экземпляров класса Vector2, а форму объектов представляют ограничивающие сферы вместо ограничивающих прямоугольников. Все, что нам осталось сделать, – реализовать поведение различных объектов нашего мира.

Класс Shield

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

Листинг 12.6. Класс Shield.java, представляющий блок щита

Мы определили радиус щита и инициализировали его позицию и ограничивающую сферу соответственно параметрам конструктора. На этом все.

Класс Shot

Класс выстрела также довольно прост. Он наследует от класса Dynami cGameOb ject3D, поскольку он на самом деле перемещается. Код класса Shot содержится в листинге 12.7.

Листинг 12.7. Класс Shot.java, представляющий выстрел

Мы снова определяем несколько констант, представляющих собой скорость выстрела и его радиус. Конструктор принимает в качестве параметра исходную позицию выстрела, а также его скорость по оси z. Но подождите, разве мы не определили скорость как константу? Да, но это позволило бы перемещаться снарядам только вдоль положительной части оси z. Это годится для выстрелов захватчиков, но выстрелы нашего корабля должны передвигаться в противоположном направлении. Когда мы создаем выстрел (вне этого класса), мы знаем, в каком направлении должен двигаться выстрел. Поэтому его скорость задается его создателем.

Метод updateO просто выполняет привычные нам физические задачи. Для выстрела не используется ускорение, и поэтому нам нужно добавить к позиции выстрела константную скорость, умноженную на прошедшее время. Главный момент заключается в том, что мы также обновляем позицию центра ограничивающей сферы в соответствии с позицией выстрела. В противном случае ограничивающая сфера не будет перемещаться вместе с выстрелом.

 

Класс Ship

Класс Ship отвечает за обновление позиции корабля, ограничивая ее границами игрового поля. Кроме того, он отслеживает текущее состояние корабля. Он может быть либо целым, либо взрывающимся. В обоих случаях мы отслеживаем промежуток времени, в течение которого корабль находился в этом состоянии. Впоследствии он будет использован для создания анимации, в качестве примера можно взять игру Большой прыгун и ее класс WorldRenderer. Корабль будет получать текущее значение скорости извне, основываясь на данных, поступивших от пользователя. Это будут или показания акселерометра (в случае игры Большой прыгун) или константа в зависимости от кнопки, нажатой пользователем.

В дополнение корабль будет отслеживать количество жизней и предлагать нам способ сообщать ему о том, что он был убит. Код класса корабля содержится в листинге 12.8.

Листинг 12.8. Класс Ship.Java, представляющий корабль

Мы начинаем с описания нескольких констант, определяющих максимальную скорость корабля, два состояния (целый и взрывающийся), время, необходимое кораблю для того, чтобы полностью взорваться, а также радиус ограничивающей сферы. Этот класс также наследует от класса Dynami cGameOb ject3D, поскольку у него есть позиция, ограничивающая сфера и скорость. Вектор ускорения, хранящийся в классе DynamicGame0bject3D, снова не используется.

Далее следуют две переменные типа int, необходимые для отслеживания количества жизней и состояния корабля (либо SHIP ALIVE, либо SHIP EXPL0DING). Последний член класса отслеживает количество секунд, которое корабль провел в текущем состоянии.

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

Метод update довольно прост. Он принимает промежуток времени, а также показания акселерометра по оси у (помните, игра работает в пейзажном режиме, поэтому ось у акселерометра является нашей осью х). Если корабль цел, мы устанавливаем его скорость соответственно показаниям акселерометра (которые будут лежать в промежутке от -10 до 10) так же, как мы поступали и в случае с игрой Большой прыгун. В дополнение обновляем позицию корабля, основываясь на его текущей скорости. Далее проверяем, покинул ли корабль границы игрового поля, используя две константы, которые определим далее в классе World. Когда позиция корабля установлена, мы, наконец, можем обновить позицию ограничивающей сферы корабля.

Если корабль взрывается, мы проверяем, как долго он находится в этом состоянии. После 1,6 секунды, проведенных в этом состоянии, корабль заканчивает взрываться, теряет одну жизнь и переходит обратно в целое состояние.

Наконец, обновляем параметр stateTime, основываясь на заданном промежутке времени.

Последний метод ki11 может быть вызван классом World, если он определит, что произошло и столкновение между кораблем и выстрелом или захватчиком. Он установит состояние взрывающийся, сбросит время состояния, а также убедится, что текущая скорость корабля равна нулю по всем осям (мы никогда не устанавливаем у- и z-компоненты вектора скорости, поскольку корабль перемещается только по оси х).

Класс Invader

Согласно шаблону, определенному ранее, захватчики просто парят в космосе. Этот шаблон приведен на рис. 12.11.

Рис. 12.11. Передвижение захватчиков: влево, вниз, вправо, вниз, влево, вниз, вправо, вниз…

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

Расстояния, на которые захватчик перемещается вправо и влево, всегда одинаковы, кроме начала.

Рис. 12.11 иллюстрирует передвижение левого верхнего захватчика. Расстояние, на которое он переместится влево в первый раз, меньше, чем то, на которое он будет двигаться в стороны каждый последующий раз. Расстояние, на которое захватчик перемещается по горизонтали, равно половине игрового поля, в нашем случае – 14 единиц. При первом перемещении по горизонтали захватчик переместится на половину этого расстояния – 7 единиц.

Нам нужно отслеживать направление перемещения захватчика, а также то, насколько он уже переместился в этом направлении. Если он передвигается на максимальное расстояние (14 единиц для гбризонтального перемещения, 1 единица для вертикального перемещения), он переходит в следующий режим перемещения. Расстояние, на которое передвигаются все захватчики, равно половине игрового поля. Взгляните снова на рис. 12.11, чтобы убедиться, почему это работает. В результате захватчики будут отскакивать от левой и правой границ игрового поля.

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

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

Листинг 12.9. Класс Invader.java, представляющий захватчика

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

Мы отслеживаем состояние захватчика, время состояния, направление передвижения и расстояние, на которое переместился захватчик, равное сначала половине ширины игрового поля. Мы также отслеживаем, перемещался ли захватчик в последний раз влево. Это позволяет нам решить, в каком направлении будет двигаться захватчик после того, как завершит перемещение по оси z.

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

Метод update  принимает текущий промежуток времени и множитель скорости, который используется для ускорения новых волн захватчиков. Перемещение осуществляется только в том случае, если захватчик еще жив.

Мы начинаем с расчета того, на сколько единиц захватчик переместится в этом обновлении, и соответственно увеличиваем значение переменной movedDi stance. Если он перемещается влево, мы обновляем позицию, вычитая скорость из координаты по оси х, умноженную на изменение времени и множитель скорости. Если захватчик переместился достаточно далеко, мы указываем ему переместиться по вертикали, устанавливая значение переменной move равным M0VE D0WN. Мы также устанавливаем значение переменной wasLastStateLeft равным true для того, чтобы знать, что при следующем перемещении по горизонтали необходимо будет двигаться вправо.

То же самое мы делаем и при обработке движения вправо. Единственное различие заключается в том, что мы добавляем скорость перемещения к координате позиции по оси х и устанавливаем значение переменной wasLastStateLeft равным false в тот момент, когда перемещение закончено.

Если захватчик перемещается вниз, мы работаем с z-координатой его позиции и опять же проверяем, как далеко он уже передвинулся. Если он переместился на максимальное расстояние, мы переключаем состояние на M0VE LEFT или MOVE RIGHT в зависимости от направления последнего перемещения по горизонтали, хранящегося в переменной wasLastStateLeft. Как только мы обновили позиции захватчиков, мы можем установить позицию ограничивающей сферы так же, как мы делали это для корабля. Наконец, обновляем текущее время состояния и считаем, что обновление завершено.

Метод ki11  нужен для того же, для чего служит одноименный метод класса Ship. Он позволяет сообщить захватчику, что он должен начать умирать. Мы устанавливаем его состояние равным INVADER DEAD и сбрасываем время состояния. Захватчик больше не будет перемещаться и будет только обновлять свое состояние, основываясь на текущем изменении времени.

Класс World

Класс World является самым главным классом. Он хранит экземпляры классов корабля, захватчиков и выстрелов, а также отвечает за их обновление и проверку столкновений. Он делает то же самое, что и в игре Большой прыгун, с небольшими различиями. Исходное размещение блоков щита и захватчиков – это также обязанность этого класса. Мы также создаем экземпляр класса World Listener для того, чтобы информировать всех слушателей о событиях, произошедших внутри мира, таких как выстрел или взрыв. Он будет воспроизводить звуковые эффекты, как и в игре Большой прыгун. Рассмотрим каждый метод этого класса подробнее. Код содержится в листинге 12.10.

Листинг 12.10. Класс World.Java, представляющий собой игровой мир. Он связывает все воедино

Мы хотим, чтобы внешние слушатели класса знали о событиях, произошедших внутри него, таких как взрыв или выстрел. Для этого мы определяем интерфейс слушателей, который можем реализовать и зарегистрировать с экземпляром World, который будет вызываться всякий раз, когда выполняется соответствующее событие. Точно то же самое происходило и в игре Большой прыгун, только события были другими.

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

Этот класс отслеживает множество вещей. В нем есть слушатель, который будет вызываться всякий раз, когда происходит взрыв или выстрел. Он также отслеживает, сколько волн пришельцев игрок уже уничтожил. Переменная score отвечает за текущий счет, а параметр speed Multipiiег позволяет ускорить движение захватчиков (вспомните метод Invaders.update). Кроме того, здесь хранятся списки выстрелов, захватчиков и блоков щита, которые в данный момент существуют в игровом мире. Наконец, здесь есть экземпляр класса Ship, а также время, в которое был произведен последний выстрел. Это время представляется в наносекундах, поскольку метод System.nanoTime возвращает время в таком формате, и хранится в переменной типа long. Экземпляр класса Random пригодится, когда нам понадобится определить, выстрелит ли захватчик.

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

Метод generatelnvaders просто создает сетку размером 8×4, которая состоит из пришельцев, выстроенных так, как показывает рис. 12.11.

Метод generateShields выполняет те же самые задачи – он создает 3 щита, каждый из которых состоит из 5 блоков, выстроенных так, как показано на рис. 12.2.

В классе World есть также метод, устанавливающий слушателя.

Метод update удивительно прост. Он принимает текущее изменение времени, а также данные с акселерометра по оси у, которые мы передаем методу Ship, update . Как только обновится корабль, вызываются методы update Invaders  и updateShots , которые ответственны за обновление захватчиков и выстрелов. После того как все объекты мира обновились, мы можем начать проверку того, произошло ли столкновение. Метод checkShotCol1ision проверит на столкновения все выстрелы, корабль и захватчиков. Наконец, мы проверяем, уничтожены ли все захватчики. Если да, то генерируется их новая волна. К радости сборщика мусора мы могли бы повторно использовать старые экземпляры класса Invader, например с помощью экземпляра класса Pool. Однако для простоты мы создаем новые экземпляры класса Invader. Кстати, то же самое верно и для выстрелов. Поскольку за одну игру может быть создано не так уж много объектов, сборщик мусора не будет часто запускаться. Но если вы хотите предотвратить все возможные проблемы, просто используйте класс Pool, чтобы повторно применять погибших захватчиков и выстрелы. Обратите также внимание на то, что в этом методе мы увеличиваем множитель скорости.

У метода update Invaders О есть несколько обязанностей. Он в цикле проходит по всем захватчикам и вызывает их методы update . Как только экземпляр класса Invader обновился, мы проверяем, жив ли он к этому моменту. Если да, то генерируется случайное число, представляющее собой шанс на выстрел. Если оно меньше 0,001, то захватчик производит выстрел. Это означает, что каждый захватчик имеет 0,1 % шанса на выстрел каждый кадр. Если шанс срабатывает, создается новый экземпляр класса Shot, его скорость устанавливается таким образом, что он начинает перемещаться вдоль положительной части оси г. Об этом событии информируется слушатель. Если захватчик умирает и заканчивает взрываться, мы просто удаляем его из списка захватчиков.

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

В методе checkInvaderCol1isions происходит проверка того, столкнулся ли какой-нибудь захватчик с кораблем. Это довольно просто, поскольку все, что нужно сделать, – пройти в цикле по всем захватчикам и проверить, пересекается ли их ограничивающая сфера с ограничивающей сферой корабля. В соответствии с игровой механикой, когда это происходит, игра заканчивается. Поэтому мы устанавливаем количество жизней корабля равным 1 перед тем, как вызвать метод Ship. klllO. После этого вызова параметр 1ive корабля устанавливается равным 0, что будет использовано в другом методе для проверки наступления состояния конца игры.

Метод checkShotCol1isions несколько более сложен. Он проходит в цикле по всем экземплярам класса Shot и проверяет их пересечение с блоком щита, захватчиком или кораблем. Блоки щита могут быть задеты как в случае выстрела захватчика, так и в случае выстрела корабля. Захватчик может быть сбит только выстрелом корабля, а корабль – только выстрелом захватчика. Чтобы определить, кто именно стрелял, необходимо лишь посмотреть z-компоненту скорости стрелявшего. Если она положительна, то выстрел произвел захватчик, в противном случае – корабль.

Метод is GameOver сообщает сторонним слушателям о том, что корабль потерял все жизни.

Наконец, рассмотрим метод shoot . Он будет вызываться извне всякий раз, когда будет нажиматься кнопка выстрела. В разделе описания игровой механики мы говорили, что корабль может стрелять каждую секунду или если на игровом поле нет выстрела корабля. Конечно же, корабль не сможет стрелять, если он взрывается, поэтому сначала выполняем именно эту проверку. Далее проходим в цикле по всем выстрелам и проверяем, является хотя бы один из них выстрелом корабля. Если такового не находится, корабль может мгновенно выстрелить. В противном случае проверяется последний выстрел, произведенный кораблем. Если со времени его появления прошло больше секунды, корабль может выстрелить снова. В этот раз мы устанавливаем значение скорости равным -Shot. SH0T VEL0CITY, что заставит выстрел перемещаться вдоль отрицательной части оси z, в сторону захватчиков. Как обычно, мы информируем об этом событии слушателя.

И это все классы игрового мира. Сравните их с классами игры Большой прыгун. Принципы практически одинаковы, и код выглядит похоже.

Droid Invaders – это, конечно, очень простая игра, поэтому мы можем использовать самые простые решения вроде применения в качестве ограничивающих фигур сфер. Это все, что нужно, для многих простых 3D-nrp. Перейдем к рассмотрению двух заключительных фрагментов нашей игры, классам GameScreen и WorldRenderer.

Источник: Mario Zechner / Марио Цехнер, «Программирование игр под Android», пер. Егор Сидорович, Евгений Зазноба, Издательство «Питер»

По теме:

  • Комментарии