Главная » Программирование игр под Android » АБСТРАГИРОВАНИЕ В ИГРЕ MRNOM  – РАЗРАБОТКА ИГР ДЛЯ ОС ANDROID

0

 

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

 

Абстрагирование мира мистера Нома: модель, вид, контроллер

Если вы программируете уже давно, то, возможно, уже слышали о паттернах проектирования. Это своего рода стратегии, уместные при создании кода для данного сценария. Некоторые из них только теоретические, другие применяются на практике. Для создания игры мы можем позаимствовать некоторые идеи из паттерна проектирования Модель – вид – контроллер (Model-View-Controller, MVC). Он достаточно часто используется, например в базах данных, чтобы разделить модель данных от уровня представления и уровня управления данными. Мы не будем строго придерживаться этого паттерна проектирования, а приспособим его к нашим нуждам в упрощенной форме.

Что же это значит для мистера Нома? Первым делом нам необходимо отделить представление нашего мира от растровых изображений, звуков, фреймбу-феров и событий ввода. Вместо этого смоделируем мир мистера Нома с несколькими простыми классами в объектно-ориентрованном стиле. У нас будет класс для пятен в игровом мире и класс для самого мистера Нома. Персонаж состоит из головы и хвоста, которые мы также представим в отдельном классе. Чтобы связать все вместе, используем универсальный класс, представляющий весь мир мистера Нома, включающий пятна и самого мистера Нома. Все это будет моделью в составе MVC.

Вид (view) в MVC будет кодом, отвечающим за отображение мира мистера Нома. У нас будет класс или метод, который получает класс мира, читает его текущее состояние и визуализирует его на экране. Сам процесс визуализации не касается классов модели. Тем не менее это наиболее важный урок, который мы можем извлечь из MVC. Классы модели не зависят ни от чего, однако классы и методы вида зависят от классов модели.

В MVC нам также необходим контроллер. Он указывает классам модели изменить их состояние, основываясь на таких факторах, как пользовательский ввод чего-либо или ход времени. Классы модели предоставляют методы контроллеру (например, инструкции: Поверните мистера Нома направо), которые контроллер может позже использовать для того, чтобы модифицировать состояние модели. У нас в классах модели нет никакого кода, который имел бы прямой доступ к таким компонентам, как сенсорный экран или акселерометр. Таким образом, наши классы модели не имеют никаких внешних зависимостей.

Возможно, это звучит достаточно сложно и вы удивляетесь, почему мы действуем именно так. Тем не менее подобный подход имеет множество преимуществ. Мы можем реализовать всю логику игры без учета свойств графики, аудио или устройств ввода. Мы можем модифицировать визуализацию мира игры, и нам не придется при этом изменять сами классы модели. Мы можем даже перенести мир из 2D в 3D, а также легко поддерживать новые устройства ввода, используя контроллер. Контроллер всего лишь преобразует события ввода в вызовы методов, относящихся к классам модели. Хотите поворачивать мистера Нома с помощью акселерометра? Нет проблем – прочтите значения акселерометра в контроллере и преобразуйте их в вызов метода Повернуть мистера Нома налево или Повернуть мистера Нома направо в модели Мистер Ном. Хотите добавить поддержку устройства Zeemote? Проделайте те же самые действия, что и в случае с акселерометром. При использовании контроллеров особенно удобно то, что мы не изменим ни единой строки кода Мистера Нома для того, чтобы все это получилось.

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

На рис. 6.5 показан экран игры с наложенным на него в виде клеточек миром мистера Нома.

Рис. 6.5. Мир мистера Нома, наложенный на игровой экран

Обратите внимание, что мир мистера Нома состоит из 10 х 13 клеток. Мы адресуем клетки в координатной системе, начиная с верхнего левого угла в точке (0; 0) и передвигаясь к правому нижнему углу до (9; 12). Любая часть мистера Нома должна быть в одной из этих клеток, а также иметь целочисленные значения координат х и у внутри игрового мира. Это правило действует и для пятен в мире. Каждая часть мистера Нома занимает ровно одну клетку – 1 единицу. Запомните, что тип единицы неважен – это придуманный нами мир, который не зависит ни от системы СИ, ни от пикселов.

Мистер Ном не может путешествовать за границами этого небольшого мира. Если он дойдет до края, он просто появится с другой стороны, а за ним и все его части. (Кстати, на Земле у нас с вами все точно так же, просто идите в какую-то сторону достаточно долго, и вы вернетесь в место, откуда ушли). Мистер Ном может также передвигаться только клетка за клеткой- Все его части будут иметь целочисленные координаты. Например, он никогда не сможет занять две с половиной клетки.

ПРИМЕЧАНИЕ

Как было указано выше, здесь мы работаем с нестрогим паттерном MVC. Если вы хотите подробнее изучить классический вариант данного паттерна, можете почитать у Э. Гамма, Р. Хелм Приемы объектно-ориентированного проектирования. Паттерны проектирования. – СПб.: Питер, 2012. В этой е паттерн проектирования MVC именуется Observer (Наблюдатель).

 

Класс Stain

Самый простой объект в мире мистера Нома – это пятно. Оно просто находится в клетке мира, ожидая, пока его съедят. Когда мы проектировали мистера Нома, мы создали три различных на вид пятна. Тип пятна не имеет никакого значения в мире мистера Нома, однако мы все равно включим этот показатель в наш класс Stain. В листинге 6.8 показан класс Stain.

Листинг 6.8. Stain.java package com.badlogiс.androidgames.mrnom:

Класс Stain определяет три общедоступные статические константы, которые кодируют тип пятна. Каждый объект Stain имеет три члена, х- и у-координаты в мире мистера Нома и тип, определенный одной из констант. Чтобы упростить код, мы обойдемся без методов-установщиков и методов-получателей, хотя обычно работаем с ними. Мы заканчиваем класс небольшим конструктором, который позволяет нам легко инстанцировать экземпляр Stain.

Единственный момент, на который стоит обратить внимание, – отсутствие какой-либо связи с графикой, звуком и другими классами. Класс Stain обособлен и кодирует атрибуты пятна в мире мистера Нома.

Классы Snake и SnakePart

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

В листинге 6.9 показан класс SnakePart, который используется для того, чтобы описать обе части мистера Нома.

Листинг 6.9. SnakePart.java package com.badlogiс.androidgames.mrnom;

Этот класс практически ничем не отличается от класса Stain, мы просто убрали член type. Первый по-настоящему интересный класс в нашей модели мира мистера Нома – Snake. Посмотрим, что он должен уметь делать. Итак, здесь мы будем: хранить хвостовую и головную части; узнавать, в какую сторону мистер Ном двигается в данный момент; добавлять новый кусок хвоста, когда мистер Ном съедает пятно; передвигаться на одну клетку в выбранном направлении.

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

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

На рис. 6.6 показан мистер Ном в его начальном виде. Он состоит из трех частей: головы в точке (5; 6) и двух частей хвоста в (5; 7) и (5; 8).

Рис. 6.6. Мистер Ном в начальном виде

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

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

Рис. 6.7. Мистер Ном движется, и хвост перемещается за ним

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

Теперь, когда у нас есть вся эта информация, мы можем реализовать класс Snake, представляющий собой мистера Нома (листинг 6.10).

Листинг 6.10. Snake.Java; мистер Ном в коде package com.badlogiс.androidgames.mrnom;

Мы начинаем с определения нескольких констант, задающих направление движения мистера Нома. Запомните, что он может повернуть только направо или налево, так что очень важно, как мы определим значения константы. Позже это позволит нам с легкостью поворачивать на 90°, просто увеличивая или уменьшая текущее значение направления на единицу.

Далее определяем список parts, который содержит все части мистера Нома. Первый элемент в этом списке – голова, остальные элементы – части хвоста. Второй член класса Snake содержит направление, в котором в данный момент двигается мистер Ном.

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

Методы turnLeft и turnRight просто изменяют элемент направления в классе Snake. Чтобы повернуть налево, мы увеличиваем его на единицу, а чтобы повернуть направо, уменьшаем на единицу. Нам также необходимо убедиться, что мы правильно выполним поворот, если значение направления превысит диапазон констант, который мы определили ранее.

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

Следующий метод advance реализует логику, показанную на рис. 6.7. Сначала мы передвигаем каждую часть на позицию идущей перед ней части, начиная с последней. Мы исключаем голову из этой операции. Далее перемещаем голову в соответствии с текущим направлением передвижения мистера Нома. Наконец проверяем, не вышел ли мистер Ном за границы мира. Если вышел, перебрасываем его на противоположную часть экрана.

Последний метод checkBitten – небольшой вспомогательный код, который проверяет, не укусил ли мистер Ном свой хвост. Он проверяет, чтобы части хвоста не находились в той же позиции, что и голова. Если же это происходит, мистер Ном умирает и игра заканчивается.

Класс World

Последний класс нашей модели называется World. Он должен выполнять следующие задачи: отслеживание мистера Нома (в виде экземпляра класса Snake), а также пятен, разбросанных по миру. В мире всегда должно быть не менее одного пятна; предоставление метода, который обновит мистера Нома по хронологическому принципу (например, он должен передвигаться на одну клетку каждые 0,5 секунды). Этот метод также проверяет, съел ли мистер Ном пятно или укусил себя; отслеживание счета, что фактически является подсчетом количества пятен, съеденных на данный момент, умноженных на 10; увеличение скорости мистера Нома после каждых 10 пятен, которые он съел. Это немного усложнит игру; отслеживание, жив ли мистер Ном до сих пор. Это понадобится нам позже для окончания игры; создание нового пятна после того, как мистер Ном съел текущее (небольшое, но важное и удивительно сложное задание).

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

Перемещение мистера Нома по хронологическому принципу

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

Для начала определим скорость мистера Нома. В мире мистера Нома существует время, измеряемое в секундах. Изначально мистер Ном должен продвигаться со скоростью одна клетка в 0,5 секунды. Все, что нам нужно, – следить за тем, сколько времени прошло с того момента, как мы подвинули мистера Нома в последний раз. Если прошедшее время превышает 0,5 секунды, мы вызываем метод Snake.advance и сбрасываем счетчик времени. Откуда мы получаем эти дельты времени? Помните метод Screen update О? Он получает дельту времени для каждого кадра. Мы просто передаем эту информацию методу update нашего класса World, который ведет подсчеты. Чтобы сделать игру немного сложнее, мы уменьшаем порог на 0,05 секунды каждый раз, когда мистер Ном съедает 10 пятен. Конечно, мы должны следить за тем, чтобы порог не достиг 0, иначе мистер Ном будет передвигаться со скоростью света. Эйнштейну это бы не понравилось.

Размещение пятен

Вторая проблема, которую нам предстоит решить, – как разместить новое пятно, когда мистер Ном съел текущее. Оно должно появиться в случайной клетке мира. Так почему бы нам не инстанцировать новое пятно в случайной позиции? К сожалению, все не так просто.

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

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

Как нам проверить, свободна ли клетка? Самым простым решением будет проверить все клетки, взять х- и у-координаты каждой и сравнить все части мистера Нома с этими координатами. У нас есть 10 х 13 = 130 клеток, и мистер Ном может занимать до 55 клеток. Это будет 130х55 = 7 150 проверок. Конечно, большинство устройств сможет справиться с таким количеством операций, но мы можем сделать гораздо лучше.

Создадим двухмерный массив булевых значений, каждый элемент массива будет представлять одну клетку мира. Когда нам нужно поместить новое пятно, мы сначала проходим через все части мистера Нома и отмечаем в массиве занятые элементы как true. Когда мы затем просто выбираем случайную позицию, с которой начинаем сканировать, находим свободную клетку, в которую можем поместить новое пятно. В случае, когда мистер Ном состоит из 55 частей, это займет 130 + 55 = 185 проверок. Вот так гораздо лучше.

Определение окончания игры

Осталось еще одна вещь, о которой нам необходимо позаботиться: что делать, если все клетки заняты мистером Номом? В этом случае игра будет закончена, поскольку мистер Ном станет всем миром. Принимая во внимание, что мы добавляем 10 очков каждый раз, когда мистер Ном съедает пятно, максимально возможный рекорд 10 х (13 – 3) х 10 = 1000 очков (помните, мистер Ном начинает с трех частей).

Реализация класса World

Нам с вами предстоит реализовать еще немало вещей, так что начнем. В листинге 6.11 показан код класса World.

Листинг 6.11. World.java package com.badlogiс.androidgames.mrnom;

Как всегда, начинаем с определения пары констант. В данном случае это ширина и высота мира в клетках, значение, на которое мы увеличиваем счет каждый раз, когда мистер Ном съедает пятно, начальный временной интервал, используемый для передвижения мистера Нома (tick), и значение, на которое мы уменьшаем tick каждый раз, когда мистер Ном съедает 10 пятен, чтобы немного ускорить персонажа.

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

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

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

Метод piaceStaiп реализует стратегию размещения, описанную выше. Мы начинаем с очистки ячеек массива. Далее устанавливаем все ячейки, занятые частями мистера Нома в true. После сканируем массив в поисках свободной ячейки, начиная из случайного места. Как только мы нашли свободную ячейку, создаем пятно случайного типа. Обратите внимание, что если все клетки заняты мистером Номом, цикл никогда не закончится. Мы исключим такую ситуацию в следующем методе.

Метод update отвечает за обновление класса World и всех объектов в нем в зависимости от значения дельты времени, которое мы ему передаем. Этот метод будет вызываться для каждого кадра игрового экрана, чтобы мир игры постоянно обновлялся. Сначала проверяем, окончена ли игра. Если она окончена, то, естественно, нам не нужно ничего обновлять. Далее прибавляем дельту времени к нашему счетчику. Цикл while будет задействовать столько tick, сколько накопилось (например, когда tickTime равен 1,2, а один tick равен 0,5 секунды, необходимо обновить мир дважды, оставив 0,2 секунды в счетчике). Этот прием называется моделированием с фиксированным шагом времени (fixed-time-step simulation).

В каждой итерации мы сначала отнимаем интервал tick от счетчика. Далее приказываем мистеру Ному двигаться. Мы проверяем, не укусил ли он себя. Если укусил, устанавливаем флаг Игра окончена (gameOver). Затем проверяем, не находится ли голова мистера Нома на той же клетке, что и пятно. Если находится, увеличиваем количество набранных очков и указываем мистеру Ному вырасти на одну часть. Далее проверяем, не состоит ли мистер Ном из такого же количества частей, что и его мир. Если состоит, игра окончена. Во всех других случаях размещаем новое пятно с помощью метода pi aceSta i n . Последнее, что мы делаем, – проверяем, съел ли мистер Ном еще 10 пятен. Если съел и наш порог выше нуля, уменьшаем порог на 0,05 секунды. Наш tick станет короче, что заставит мистера Нома двигаться быстрее.

Итак, мы закончили написание множества классов модели. Теперь реализуем игровой экран.

Класс GameScreen

Нам осталось реализовать еще всего один экран. Рассмотрим, что этот экран должен делать.

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

Для каждого состояния у нас есть различные методы для реализации обновления и текущего состояния (update и present), поскольку каждое состояние отвечает за различные вещи и показывает разные пользовательские интерфейсы.

Когда игра окончена, необходимо убедиться, что мы сохранили результат, если  он является рекордным.

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

Рисунок 6.8 демонстрирует четыре различных состояния.

Рис. 6.8. Игровой экран в четырех состояниях: готовность, работа, пауза, игра окончена

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

Последний недостающий фрагмент информации касается того, как визуализировать мир мистера Нома, основываясь на его модели. В общем-то, это весьма просто. Посмотрите на рис. 6.1 и 6.5 снова. Каждая клетка имеет размер ровно 32 х 32 пиксела. Изображения пятна также имеют размер 32 х 32 пиксела, как и части мистера Нома. Размер головы мистера Нома во всех направлениях составляет 42 х 42 пиксела, так что она не помещается полностью на одной клетке.

Тем не менее это не проблема. Все, что нам надо, чтобы визуализировать мир мистера Нома, – взять каждое пятно и часть мистера Нома и умножить координаты мира на 32, чтобы попасть в центр объекта в пикселах на экране. Например, центр пятна с координатами (3; 2) в мире будет находиться на 96 х 64 экрана. В такой ситуации остается только выбрать соответствующий объект и отобразить его, центрируя по координатам. Рассмотрим код. В листинге 6.12 приведен класс GameScreen.

Листинг 6.12. GameScreen.java package com.badlogiс.androidgames.mrnom;

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

Конструктор данного класса просто вызывает конструктор своего родительского класса и создает новый экземпляр класса World. Игровой экран будет в состоянии готовности после того, как конструктор вернет управление вызывающей стороне.

Далее следует метод update экрана. Он выбирает TouchEvent и KeyEvent из модуля ввода и передает их для обновления одному из четырех соответствующих методов, которые мы реализуем для каждого состояния в зависимости от текущего состояния:

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

Метод updateRunning проверяет, нажата ли клавиша паузы в верхнем левом углу экрана. Затем этот метод проверяет, была ли нажата какая-либо из кнопок контроллера внизу экрана. Обратите внимание, что мы не проверяем здесь события отпускания (touch-up). Если какая-либо кнопка была нажата, мы сообщаем экземпляру класса Snake в классе World повернуть налево или направо. Все правильно, метод updateRunning содержит код контроллера нашей схемы MVC. После того как все события касания проверены, мы приказываем миру обновиться, передавая ему дельту времени. Если класс Worl d сигнализирует, что игра окончена, мы переходим к соответствующему состоянию, а также воспроизводим звук bitten.ogg. Далее проверяем, отличается ли прежнее количество очков, которое мы поместили в кэш, от результата, который хранит World. Если отличается, нам становятся известны две вещи: мистер Ном съел пятно и строка результатов должна быть изменена. В этом случае мы проигрываем звук eat. ogg. Вот, собственно, и все, что касается обновления текущего состояния.

Метод updatePaused опять-таки просто проверяет, была ли нажата одна из команд меню, и переходит к соответствующему состоянию.

Метод updateGameOver также просто проверяет, нажата ли кнопка в центре экрана. Если нажата, выполняется переход назад в главное меню.

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

Метод drawWorl d рисует мир примерно по тому же принципу, что был рассмотрен выше. Он начинает с выбора Pixmap для визуализации пятна, затем рисует пятно и центрирует его по горизонтали в нужном месте экрана. Далее визуализируем все части хвоста мистера Нома, что весьма просто. Затем выбираем, какой Pixmap для головы следует использовать, основываясь на направлении, в котором двигается мистер Ном. Рисуем Pi xmap в зависимости от положения головы, переведенного в экранные координаты. Размещаем его по центру, как и остальные объекты. Вот код view в MVC.

В методах drawReadUK, drawRunningUK, drawPausedUK иdrawGameOverUK нет ничего нового. Они выполняют все ту же визуализацию пользовательского интерфейса, основанную на координатах, которые показаны на рис. 6.8. Метод drawText  аналогичен методу, использованному в HighscoreScreen, так что мы не будем его обсуждать.

Есть и еще один важнейший метод – pause, который вызывается, когда активность ставится на паузу или игровой экран заменяется на какой-либо другой. Это самое подходящее место для сохранения настроек. Сначала мы устанавливаем состояние нашей игры как paused (Приостановлено). Если метод paused вызывается в связи с приостановкой работы активности, пользователю обязательно будет предложено возобновить игру с того места, на котором игра была прервана. Это весьма неплохо, так как можно сразу приступить к игре с того момента, на котором он закончил. Далее проверяем, не находится ли игра в состоянии Игра окончена. Если это так – добавляем результат пользователя в таблицу рекордов (или не добавляем, это зависит от значения) и сохраняем все настройки во внешнем хранилище.

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

Подводя итог

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

Перед тем как вы продолжите читать у, я предлагаю вам взять код игры и немного поэкспериментировать с ним. Добавьте какие-нибудь новые режимы игры, бонусы и врагов – все, что вы сможете придумать.

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

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

По теме:

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