Текст книги "Создание игр для мобильных телефонов"
Автор книги: Майкл Моррисон
сообщить о нарушении
Текущая страница: 10 (всего у книги 35 страниц) [доступный отрывок для чтения: 13 страниц]
Как только мидлет UFO собран, вы можете протестировать его в эмуляторе J2ME. В результате летающая тарелка немедленно начинает перемещаться по экрану (рис. 5.7).
Рис. 5.7. Когда мидлет запускается, по экрану немедленно начинает летать НЛО
Поскольку на картинке сложно отобразить анимацию, то на рис. 5.8 показан летающий объект в другом месте экрана.
Рис. 5.8. Как кролик из рекламы Energizer, НЛО беспрестанно летает по экрану
Все, чего не хватает в мидлете UFO, – это пара астероидов и возможность управления НЛО. Не беспокойтесь, мы восполним этот пробел в следующей главе.
РезюмеВ этой главе вы познакомились с анимацией и ее применением в мобильных играх. Вы узнали, что анимация широко используется при создании фильмов, телевизионных передач и видеоигр. При разработке компьютерных игр применяются два основных типа анимации, и в этой главе рассказывалось, как они работают. Затем вы узнали об основах спрайтовой анимации, поддерживаемой MIDP API. Глава завершилась созданием анимационного мидлета, который демонстрирует основы спрайтовой анимации.
В следующей главе вы примените свои знания в области создания анимации для программирования управляемого объекта.
Еще немного об играхРассматривая мидлет UFO как первый пример работы с анимацией, полезно поработать еще с рядом ее свойств. Я имею в виду частоту кадров и скорость НЛО. Ниже приведены шаги, которые дают возможность изменить анимацию:
1. попробуйте увеличить значение переменной frameDelay, например, до 100 (10 кадров/с), а потом уменьшить до 20 (50 кадров/с). Обратите внимание на то, как работает анимация в каждом из случаев, а также – насколько она плавная;
2. измените частоту изменения скорости летающего объекта так, чтобы она изменялась чаще. Например, для этого измените код rand.nextInt() % 5 на rand.nextInt() % 2;
3. измените границу скорости НЛО так, чтобы он мог двигаться быстрее. Для этого необходимо изменить вызовы методов min() и max() и установить большую границу.
Приведенные шаги могут значительно изменить скорость и производительность анимации, особенно первый шаг. Так что не пожалейте времени и поэкспериментируйте с различными настройками, посмотрите, как они влияют на анимацию.
Глава 6
Обработка ввода пользователя
Архив Аркад
Выпущенная в 1980 году компанией Stern игра Berzerk – простой шутер в лабиринте, в ней вы управляете героем-гуманоидом, проводите его через комнаты и сражаетесь с роботами. Berzerk – это одна из первых игр, в которой в конце каждого уровня необходимо было сражаться с «главным монстром». В этой игре «главный монстр» – Злой Отто (Evil Otto), который заставляет героя войти в определенную комнату. Игра Berzerk известна тем, что роботы в ней могут совершать ошибки, например, случайно убить друг друга. Также эта игра известна гибелью игроков: в 1981 году мужчина скончался от сердечного приступа, случившегося после игры; в 1982 году еще один человек также скончался от сердечного приступа, при этом он возглавил список лучших результатов. Играйте в Berzerk на свой страх и риск!
Неважно, сколько времени и сил вы потратите на создание сюжета игры и графики, если в итоге игрой нельзя будет управлять. Чтобы в игру можно было играть, необходимо предоставить пользователю возможность ввода. С точки зрения программирования, это одновременно и сложно, и просто. С одной стороны, в мобильных телефонах управление значительно проще по сравнению с компьютерами, с другой – это ограничивает возможности ввода, делает ввод менее гибким. В этой главе рассказывается, как обрабатывать пользовательский ввод в мидлетах.
Из этой главы вы узнаете:
► почему пользовательский ввод так важен в мобильных играх;
► как эффективно определять и обрабатывать нажатия клавиш;
► как управлять анимационным объектом, используя клавиатуру;
► как определять столкновения спрайтов;
► как создать спрайты, вид которых изменяется с течением времени.
Обработка пользовательского вводаПользовательский ввод – это средство взаимодействия пользователя с мобильной игрой. Поскольку пользовательский ввод – это взаимодействие пользователя с приложением, вы должны понять, что создание интуитивно понятного и эффективного интерфейса должно стоять на первом месте в списке ключевых элементов разработки. Несмотря на все достижения современной индустрии компьютерных игр (игры в реальном времени, трехмерная графика и звук), в большинстве случаев вопрос разработки эффективного пользовательского ввода остается без внимания. Простой ввод позволяет пользователю легко и эффективно управлять ходом игры.
В копилку Игрока
Я – игрок старой закалки, помню те времена, когда я платил дань богам игр, желая поиграть во что-то еще. Это было в те времена, когда на домашнем компьютере можно было поиграть только в Pong. В ответ на пожертвованные четвертаки боги разрешали мне поиграть в увлекательные игры. Поскольку аппаратные средства того времени не могли обеспечить высокого уровня графики и звука, разработчики игр были вынуждены компенсировать этот недостаток за счет самой игры. Конечно, они не ставили своей задачей разработку удобного ввода, но в условиях ограниченных аппаратных возможностей, у них просто не было другого выбора.
Позвольте мне пояснить, что я имею в виду, говоря о пользовательском вводе и удобстве игры. Одна из самых популярных игр всех времен и народов – это Ring King, боксерская игра для Nintendo Entertainment System (NES, Игровая система Nintendo). По современным меркам эта игра считается старой, но, вероятно, зря. По сравнению с современными играми у нее слабая графика, анимация и звук, однако я до сих пор играю в нее, потому что это так просто! Простота достигается за счет хорошо продуманного ввода, что и делает игру приближенной к реальному боксерскому бою. В Ring King, конечно, есть ряд ограничений, но разработчики грамотно продумали время ударов.
Я пробовал найти современный аналог Ring King, но безуспешно. Хотя на сегодняшний день есть множество игр с великолепной графикой, в них нет такого продуманного управления, как в моей любимой игре. Поэтому я до сих пор в поисках.
Цель моих рассуждений – показать, что программист мобильных игр сталкивается с теми же проблемами, что и создатели первых аркад: с ограниченными аппаратными ресурсами. В вашем распоряжении нет мощного микропроцессора, который поддерживает самый современный и великолепный механизм трехмерного рендеринга. Разрабатывая дизайн игры, необходимо делать скидку на используемые ресурсы и уделять значительное внимание самой игре. Как я упомянул выше, под этим обычно подразумевается более детальное рассмотрение ввода.
В копилку Игрока
Хотя в этой главе речь пойдет о простейших формах пользовательского ввода, стоит отметить, что недавно ученые провели эксперимент – подсоединили электроды к головному мозгу добровольцев. В результате добровольцы могли управлять игрой одними мыслями. Я знаю, что это звучит, как нечто из области научной фантастики, но это действительность. Возможно, пройдет еще очень много времени, прежде чем эта технология будет внедрена в компьютерные игры, однако ее можно применять в медицине для реабилитации парализованных людей и людей с различными физическими отклонениями.
Ваша главная цель – сделать ввод в игре как можно более простым. Если вы действительно хотите узнать, насколько хорош созданный вами интерфейс, создайте альтернативный вариант с ужасной графикой и без звука и посмотрите, интересно ли вам будет играть. Я советую вам попробовать сделать это с играми, приводимыми в книге.
Обработка пользовательского ввода с помощью класса GameCanvasВ главе 5 вы познакомились с классом GameCanvas, который предлагает уникальное решение для создания графики – двухбуферную анимацию. Класс GameCanvas предназначен не только для этого, он реализует высоко эффективную обработку ввода, специально разработанную для мобильных устройств. Традиционный подход, используемый в J2ME, годится для большинства мидлетов, но не в полной мере отвечает требованиям игр. Поэтому класс GameCanvas содержит более эффективный метод обработки ввода – метод getKeyStates().
Метод getKeyStates() используется для получения снимка состояния клавиш мобильного телефона в любой момент времени. Этот метод не содержит информации обо всех клавишах мобильного телефона, а только тех, которые используются в играх. Ниже приведены константы, которые вы можете использовать вместе с методом getKeyStates() для определения нажатия клавиш:
► UP_PRESSED – клавиша вверх;
► DOWN_PRESSED – клавиша вниз;
► LEFT_PRESSED – клавиша влево;
► RIGHT_PRESSED – клавиша вправо;
► FIRE_PRESSED – клавиша выстрела;
► GAME_A_PRESSED – дополнительная клавиша A;
► GAME_B_PRESSED – дополнительная клавиша B;
► GAME_C_PRESSED – дополнительная клавиша С;
► GAME_D_PRESSED – дополнительная клавиша D.
В копилку Игрока
Клавиши A B, C и D – это дополнительные клавиши, которые могут отсутствовать на мобильном телефоне. Поэтому вы не должны рассчитывать на эти клавиши, если не создаете игру для особой модели телефона.
Метод getKeyStates() возвращает целочисленное значение, которое можно использовать для проверки нажатой клавиши. Чтобы проверить нажатие клавиши, вызовите метод getKeyStates() и сравните возвращенное значение с одной из констант, например, так:
int keyState = getKeyStates();
if ((keyState & LEFT_KEY) != 0) {
// переместить влево
}else if ((keyState & RIGHT_KEY) != 0) {
//переместить вправо
}
Этот код следует поместить в игровой цикл так, чтобы состояние клавиш проверялось через равные промежутки времени. Важно понять, что метод getKeyStates() не обязательно возвращает текущее состояние клавиш. Если клавиша была нажата после предыдущего вызова этого метода, то возвращаемое значение будет говорить о том, что она нажата. Это гарантирует перехват быстрых нажатий кнопок даже в случае медленной работы игрового цикла.
Уверен, что вы не хотите развития такого сценария, однако, по крайней мере, вы не потеряете ни одного нажатия клавиш.
В копилку Игрока
Некоторые телефоны могут поддерживать клавишные комбинации, но гарантии этому нет. Если вы создаете мидлет для конкретной модели телефона, то можете спокойно использовать все его возможности. В некоторые современные коммерческие игры, например, Tony Hawk's Pro Skater, невозможно играть, не используя клавишные комбинации.
Другая причина использовать метод getKeyStates() заключается в том, что он не возвращает значимой информации до тех пор, пока игровой холст невидим. Если игра поддерживает несколько экранов, то клавиши для игрового холста не будут активны, пока холст не будет выбран как текущий экран.
Снова о классе SpriteНесмотря на то что эта глава посвящена обработке пользовательского ввода, стоит немного уйти в сторону и узнать больше о спрайтовой анимации, чтобы создать более интересный пример мидлета с обработкой пользовательского ввода. Мы более глубоко рассмотрим класс Sprite и научимся детектировать столкновения спрайтов и создавать спрайты с несколькими фреймовыми изображениями.
В предыдущей главе вы познакомились с теорией определения столкновений спрайтов, а также различными методами обнаружения. Если вы помните, обсуждались три подхода:
► обнаружение столкновений с помощью ограничивающих прямоугольников;
► обнаружение столкновений с помощью уменьшенных ограничивающих прямоугольников;
► обнаружение столкновений с использованием данных изображений.
Эти три метода обнаружения столкновений были представлены в порядке повышения точности, а также увеличения требуемой мощности процессора. Иначе говоря, обнаружение столкновений с помощью прямоугольников не такое точное, как если бы использовались данные изображений, но в первом случае нагрузка на процессор существенно меньше, чем во втором. Исходя из этого, вы должны решить, когда и какой метод обнаружения использовать.
Чтобы использовать MIDP API для обнаружения столкновений, вы должны применять методы класса Sprite(), которые называются collidesWith(). Каждый из этих методов отличается в зависимости от типа проверяемого объекта. Например, метод collidesWith() проверяет столкновение двух спрайтов:
CollidesWith(Sprite s, boolean pixelLevel)
Чтобы проверить столкновение, вызовите этот метод для спрайта и передайте в него другой спрайт. Второй параметр определяет, будет ли детектироваться столкновение на уровне пикселей, что соответствует методу детектирования столкновений с помощью данных изображения. Приведенный ниже фрагмент кода показывает, как можно обнаружить столкновение спрайта космического корабля с астероидом, используя данные изображений спрайтов:
shipSprite.collidesWith(roidSprite, true);
Если вы хотите использовать метод ограничивающих прямоугольников, то необходимо изменить код:
shipSprite.collidesWith(roidSprite, false);
Существует метод уменьшенных ограничивающих прямоугольников, который идентичен обычному методу ограничивающих прямоугольников. Чтобы изменить размер ограничивающего прямоугольника, вызовите метод defineCollisionRectangle() и введите новый размер. Например, если размер астероида составляет 42 35 пикселей, то, вероятно, вы захотите использовать меньший ограничивающий прямоугольник размером 32 25 пикселей. Ниже приведен код, выполняющий эту задачу:
alienSprite.defineCollisionRectangle(5, 5, 32, 35);
В этом примере прямоугольник, используемый для обнаружения столкновений, уменьшен с каждой стороны на 5 пикселей, поэтому он остается центрированным по отношению к спрайту. На рис. 6.1 показано изображение астероида с уменьшенным ограничивающим прямоугольником.
Рис. 6.1. Уменьшенный прямоугольник используется для ограничения изображения астероида при обнаружении столкновения
Интересно, что метод defineRectangleCollision() сказывается не только при использовании метода ограничивающих прямоугольников, но и при детектировании столкновения с применением данных изображения.
Ранее я упомянул, что существует три различных метода collidesWith(), определенных в классе Sprite, один из них вы уже видели. Ниже приведены остальные два метода:
collidesWith(Image image, int x, int y, boolean pixelLevel)
collidesWith(TiledLayer t, boolean pixelLeve)
Они проверяют столкновение спрайта с изображением и замощенным слоем соответственно. В этом случае вы должны указать координаты изображения XY. Слои похожи на спрайты, но используют несколько изображений, составляющих композицию. Вы можете создать лабиринт, используя замощенные слои, а затем проверить столкновение героя со стеной и ограничить перемещение. Подробнее о том, как сделать это, вы узнаете в главе 11.
Другая интересная возможность класса Sprite – это поддержка фреймовой анимации. Из предыдущей главы вы знаете, что фреймовая анимация создается путем показа последовательности изображений. В случае спрайта фреймовая анимация используется для изменения его внешнего вида, таким образом, спрайт может изменять не только свое положение на экране, но и внешний вид. Хороший пример анимационного спрайта – это астероид, летящий в космосе. Эффект движения достигается перемещением спрайта с течением времени, а эффект вращения – фреймовой анимацией спрайта.
Чтобы задать фреймы спрайта, расположите их внутри одного изображения в хронологическом порядке. На рис. 6.2 показан спрайт астероида, его изображение состоит из 14 фреймов, имитирующих вращение.
Рис. 6.2. Несколько фреймов спрайта помогают создать иллюзию вращения астероида
Вероятно, сложно представить, что это изображение может помочь имитировать движение, но именно этот эффект достигается при быстрой смене фреймов.
Вы можете создать анимационный спрайт, передав конструктору спрайта изображение, а также его размер. Фреймы изображения должны иметь одинаковый размер. Ниже приведен код создания анимационного спрайта, изображение которого показано на рис. 6.2:
roidSprite = new Sprite(Image.createImage(«/Roid.png»), 42, 35);
Этот код определяет размер фрейма в изображении – 42х35 пикселей. Фреймы в изображении могут располагаться вертикально, горизонтально или в двух направлениях. Если вы располагаете фреймы по сетке, то нумерация происходит слева направо и сверху вниз (рис. 6.3).
Рис. 6.3. Располагая фреймы изображения в сетке, проход по сетке осуществляется слева направо и сверху вниз
Чтобы создать фреймовую анимацию спрайта, необходимо вызвать методы nextFrame() и prevFrame():
roidSprite.nextFrame();
Этот метод отображает следующий фрейм анимации спрайта. По умолчанию эта последовательность соответствует порядку следования фреймов в изображении, однако вы можете изменить эту последовательность. При достижении конца последовательности воспроизведения фреймов начинается воспроизведение с противоположного конца. В любое время вы можете узнать индекс текущего фрейма, вызвав метод getFrame(). Этот метод возвращает индекс фрейма в последовательности, а не реальный индекс в изображении. Рассмотрим в качестве примера следующую последовательность фреймов:
int [] sequence = {0, 1, 2, 3, 3, 3, 2, 1};
Пятый элемент этой последовательности – это третий фрейм изображения. Если сейчас отображается пятый фрейм анимации, то метод getFrame() возвратит значение 4 (отсчет ведется от 0), а не номер фрейма в изображении. Метод setFrame() позволяет назначить текущий индекс фрейма. Выполнив setFrame(6), вызовите фрейм с номером 6, поскольку 2 – это номер фрейма, который стоит шестым по счету. Помните, что эти номера соответствуют местам фреймов в изображении. Последовательности фреймов можно использовать для имитации взмахов крыльев, взрывов и т. п.
С помощью метода setFrameSequence() вы можете изменить последовательность отображения фреймов. Этот метод принимает в качестве параметра целочисленный массив. Ниже приведен пример вызова этой функции для спрайта с птицей:
birdSprite.setFrameSequence(sequence);
Если вы уже близки к пониманию анимации спрайтов, то можно двинуться дальше! Оставшаяся часть главы посвящена совершенствованию мидлета UFO, рассмотренного в предыдущей главе. Вы добавите анимацию спрайта и пользовательский ввод. Ведь лучше один раз увидеть, чем сто раз услышать!
Создание программы UFO 2Пример программы UFO из предыдущей главы поможет на практике освоить анимацию спрайтов. Теперь вы можете перевести мидлет на новый уровень, добавив управление, а также астероиды – препятствия на пути НЛО. Я буду называть эту программу UFO 2.
Мидлет UFO 2 содержит следующие изменения по отношению к исходной программе:
► пользовательский ввод, обеспечивающий управление летающим объектом;
► анимационные астероиды, летающие по экрану;
► детектирование столкновений летающего объекта с астероидами.
Вы уже достаточно хорошо подготовлены, чтобы сделать это!
Класс мидлета в примере UFO 2 не изменился по сравнению с предыдущим приложением, поэтому давайте перейдем непосредственно к изменению класса UFOCanvas. Первое изменение – это добавление трех спрайтов астероидов, которые хранятся в массиве типа Sprite:
private Sprite[] roidSpace = new Sprite[3];
В методе start() выполняется инициализация спрайтов астероида следующим кодом:
Image img = Image.createImage(«/Roid.png»);
roidSprite[0] = new Sprite(img, 42, 35);
roidSprite[1] = new Sprite(img, 42, 35);
roidSprite[2] = new Sprite(img, 42, 35);
Как вы видите, изображение астероида (Roid.png) создается один раз, а затем передается каждому конструктору спрайта. Также при инициализации изменилось и начальное положение НЛО:
ufoSprite.setPosition((getWidth() – ufoSprite.getWidth()) / 2,
(getHeight() – ufoSprite.getHeight()) / 2);
Хотя этот код может показаться странным, но он не делает ничего особенного, просто выводит спрайт в центре экрана, чтобы НЛО сразу не столкнулся с астероидом, который стартует из точки (0,0).
В методе update() находятся наиболее интересные новые строки кода. Вся обработка пользовательского ввода сосредоточена в следующем фрагменте кода:
int keyState = getKeyStates();
if ((keyState & LEFT_PRESSED) != 0)
ufoXSpeed–;
else if ((keyState & RIGHT_PRESSED) != 0)
ufoXSpeed++;
if ((keyState & UP_PRESSED) != 0)
ufoYSpeed–;
else if ((keyState & DOWN_PRESSED) != 0)
ufoYSpeed++;
ufoXSpeed = Math.min(Math.max(ufoXSpeed, -8), 8); //Скорость НЛО устанавливается случайно из диапазона от -8 до 8
ufoYSpeed = Math.min(Math.max(ufoYSpeed, -8), 8);
Этот код просто проверяет нажатия четырех клавиш управления и в соответствии с этим изменяет скорость НЛО. Обратите внимание, что и в этом случае скорость ограничена 8 вне зависимости от того, сколько раз была нажата та или иная клавиша. После того как скорость изменена, НЛО обновляется следующим кодом:
ufoSprite.move(ufoXSpeed, ufoYSpeed);
checkBounds(ufoSprite);
Известный метод move() перемещает спрайт, а метод checkBounds() проверяет, не вышел ли НЛО за границы экрана. Проверка не изменилась, но ее код оформлен отдельным методом. Это очень важно, поскольку вам необходимо выполнить аналогичную проверку и для астероидов. Для этого нецелесообразно копировать код, если можно использовать существующий.
Обновление спрайтов астероидов производится в цикле, который выполняет несколько функций. Ниже приведено начало цикла:
for (int i = 0; i < 3; i++) {
Первое, что нужно выполнить в цикле, – это переместить астероиды и проверить, не вышли ли они за границы экрана:
roidSprite[i].move(i + 1, 1 – i);
checkBounds(roidSprite[i]);
Единственная хитрость в этом коде – перемещения астероидов. Чтобы каждый астероид двигался со своей особой скоростью, для перемещения используется индекс каждого из них. Аналогичный код используется для изменения очередности следования фреймов анимации:
if (i == 1)
roidSprite[i].prevFrame();
else
roidSprite[i].nextFrame();
Идея этого кода заключается в том, что второй астероид вращается в направлении, противоположном остальным. Для этого достаточно пролистывать фреймы спрайта в противоположном направлении относительно других.
Оставшаяся часть кода в цикле обновления астероидов проверяет их столкновение с НЛО:
if (ufoSprite.collidesWith(roidSprite[i], true)) {
// воспроизвести предупреждающий звук
AlertType.ERROR.playSound(display);
// вернуть спрайт в исходное положение и обнулить скорости
ufoSprite.setPosition((getWidth() – ufoSprite.getWidth()) / 2, //Спрайт выводится в центре игрового экрана, его скорость равна 0
(getHeight() – ufoSprite.getHeight()) / 2);
ufoXSpeed = ufoYSpeed = 0;
for (int j = 0; j < 3; j++)
roidSprite[j].setPosition(0, 0);
// нет необходимости обновлять спрайты астероидов
break;
}
}
Если столкновение произошло, то воспроизводится стандартный звук возникновения ошибки (он зависит от конкретной модели телефона), для чего используется объект AlertType. В главе 8 вы узнаете, как использовать разнообразные звуки в играх. В этой программе столкновение возвратит НЛО в исходное положение и обнулит его скорость. Если бы вы создавали полноценную игру, то в этом месте вы бы уменьшили число жизней и проверили, не закончена ли игра. Но в этой программе вы просто изменяете положение спрайтов, и анимация продолжается.
По сравнению с мидлетом UFO в методе draw() есть только одно незначительное изменение – код, рисующий астероиды:
for (int i = 0; i < 3; i++)
roidSprite[i].paint(g);
На этом весь новый код мидлета UFO 2 завершен. В листинге 6.1 приведен полный код нового класса UFOCanvas.
Листинг 6.1. Класс UFOCanvas, выполняющий роль холста для мидлета UFO 2
import javax.microedition.lcdui.*;
import javax.microedition.lcdui.game.*;
import java.util.*;
import java.io.*;
public class UFOCanvas extends GameCanvas implements Runnable {
private Display display;
private boolean sleeping;
private long frameDelay;
private Random rand;
private Sprite ufoSprite;
private int ufoXSpeed, ufoYSpeed;
private Sprite[] roidSprite = new Sprite[3]; //В игре UFO 2 есть 3 спрайта астероида
public UFOCanvas(Display d) {
super(true);
display = d;
// установить частоту кадров (30 fps)
frameDelay = 33;
}
public void start() {
// установить холст как текущий экран
display.setCurrent(this);
// инициализировать генератор случайных чисел
rand = new Random();
// инициализировать спрайты НЛО и астероидов
ufoXSpeed = ufoYSpeed = 0;
try {
ufoSprite = new Sprite(Image.createImage("/Saucer.png"));
ufoSprite.setPosition((getWidth() – ufoSprite.getWidth()) / 2,
(getHeight() – ufoSprite.getHeight()) / 2);
Image img = Image.createImage("/Roid.png");
roidSprite[0] = new Sprite(img, 42, 35);
roidSprite[1] = new Sprite(img, 42, 35);
roidSprite[2] = new Sprite(img, 42, 35);
}
catch (IOException e) {
System.err.println("Failed loading images!");
}
// запустить поток анимации
sleeping = false;
Thread t = new Thread(this);
t.start();
}
public void stop() {
// остановить анимацию
sleeping = true;
}
public void run() {
Graphics g = getGraphics();
// игровой цикл
while (!sleeping) {
update();
draw(g);
try {
Thread.sleep(frameDelay);
}
catch (InterruptedException ie) {}
}
}
private void update() {
// обработка пользовательского ввода, изменение скорости НЛО
int keyState = getKeyStates();
if ((keyState & LEFT_PRESSED) != 0) //Клавиши со стрелками изменяют скорость НЛО по всем четырем направлениям
ufoXSpeed–;
else if ((keyState & RIGHT_PRESSED) != 0)
ufoXSpeed++;
if ((keyState & UP_PRESSED) != 0)
ufoYSpeed–;
else if ((keyState & DOWN_PRESSED) != 0)
ufoYSpeed++;
ufoXSpeed = Math.min(Math.max(ufoXSpeed, -8), 8);
ufoYSpeed = Math.min(Math.max(ufoYSpeed, -8), 8);
// переместить спрайт НЛО
ufoSprite.move(ufoXSpeed, ufoYSpeed);
checkBounds(ufoSprite);
// обновить спрайты астероидов
for (int i = 0; i < 3; i++) {
// переместить спрайты астероидов
roidSprite[i].move(i + 1, 1 – i);
checkBounds(roidSprite[i]); //Эта строка кода отвечает за отрисовку астероида при достижении границ экрана
// изменить отображаемый фрейм астероида
if (i == 1) //Индекс астероида определяет направление анимации
roidSprite[i].prevFrame();
else
roidSprite[i].nextFrame();
// проверить столкновение НЛО с астероидом
if (ufoSprite.collidesWith(roidSprite[i], true)) { //Поскольку второй параметр метода collidesWith() равен true, то выполняется пиксельное детектирование столкновения
// воспроизвести предупреждающий звук
AlertType.ERROR.playSound(display);
// восстановить исходные положения и скорости объектов
ufoSprite.setPosition((getWidth() – ufoSprite.getWidth()) / 2,
(getHeight() – ufoSprite.getHeight()) / 2);
ufoXSpeed = ufoYSpeed = 0;
for (int j = 0; j < 3; j++)
roidSprite[j].setPosition(0, 0);
// нет необходимости обновлять спрайты астероидов
break;
}
}
}
private void draw(Graphics g) {
// Clear the display
g.setColor(0x000000);
g.fillRect(0, 0, getWidth(), getHeight());
// нарисовать спрайт НЛО
ufoSprite.paint(g);
// нарисовать спрайты астероидов
for (int i = 0; i < 3; i++)
roidSprite[i].paint(g);
// отобразить содержимое буфера на экране
flushGraphics();
}
private void checkBounds(Sprite sprite) {
// проверить положение спрайта
if (sprite.getX() < -sprite.getWidth())
sprite.setPosition(getWidth(), sprite.getY());
else if (sprite.getX() > getWidth())
sprite.setPosition(-sprite.getWidth(), sprite.getY());
if (sprite.getY() < -sprite.getHeight())
sprite.setPosition(sprite.getX(), getHeight());
else if (sprite.getY() > getHeight())
sprite.setPosition(sprite.getX(), -sprite.getHeight());
}
}
Вы уже знакомы со всеми тонкостями этого кода, поэтому я избавлю вас от дальнейших рассуждений. Давайте посмотрим, как он работает.