Создание аркадных игр для мобильных устройств на Java. Окончание

Создание аркадных игр для мобильных устройств на Java

Введение
В этой статье будут рассмотрены все основные аспекты разработки игр на Java. Вы сможете узнать, как правильно проектировать, программировать и настраивать Java-игры, например, для сотовых телефонов. Мы начнем с краткого обзора основных типов игр, а затем продолжим разговор уже о проблемах, возникающих в процессе разработки игровых программ. После этого приступим к работе над двумя примерами игр, которые будем разрабатывать в рамках этой серии, состоящей из двух статей. Начнем с разработки простой автономной игры, а после приступим к куда более сложной задаче — разработке игры с поддержкой возможности игры по сети. Для каждой из этих игр будет подробно описана непосредственно игровая часть (т.е. игровая логика), будут приняты некоторые дизайнерские решения, и в завершение мы погрузимся в детальное рассмотрение всех подробностей реализации этих игр на Java.
Исходный код игр, которые мы будем разрабатывать в этой статье, можно найти и скачать здесь: http://developer.java.sun.com/developer/releases/wireless_gaming_blueprints .

Типы игр
У многих людей, которые когда-либо играли в игры на мобильных телефонах, обязательно складывается некоторое мнение о типах таких игр. Среди них можно выделить основные:
• Стратегические игры (Mine Sweeper, Reversi, Bejeweled).
• Карточные игры (Solitaire, Black Jack, Poker).
• Двухмерные (2D) аркадные игры (Galaxian, PacMan, Defender, Asteroids).
• Трехмерные (3D) аркадные игры (Doom, Quake, Tony Hawk Pro Skater).
Мы не будем обсуждать 3D-игры, поскольку системные ресурсы мобильных устройств почти всегда слишком ограничены как в плане доступной памяти и скорости процессора, так и в графических возможностях. Поэтому, поскольку мы хотим превратить наше предприятие во что-то веселое и захватывающее, остановимся на разработке двухмерных (2D) аркадных игр.

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

Аппаратные ресурсы
Малые устройства обычно не имеют достаточного количества статической и динамической памяти. В общем, статическая память необходима для того, чтобы разместить в себе файлы классов вашей игры (упакованные в JAR-архив, естественно), а количество динамической памяти определяет, сколько памяти будет доступно вашей игре во время ее работы. Таким образом, необходимо очень осторожно и бережно относиться к использованию как статической, так и динамической памяти и тщательно продумывать каждый свой шаг на этапе разработки, поскольку любое неправильное решение может повлечь за собой превышение лимита использования памяти.
JAR-файл, содержащий вашу игру, должен быть достаточно мал (хорошим вариантом будет размер в пределах от 10 до 40 килобайт на одну игру), а количество потребляемой памяти во время выполнения игры должно быть ограничено в разумных пределах. Чтобы удовлетворить этим руководящим принципам, следует учитывать количество создаваемых вами объектов и частоту их создания. MIDP Wireless Toolkit 2.0 (WTK) — это эмулятор устройств, который при установке некоторых параметров может предоставить вам полную информацию об используемой памяти. Однако примите во внимание, что MIDP может самостоятельно создавать объекты без вашего на то согласия. Таким образом, не все объекты, которые вы сможете наблюдать при мониторинге памяти, обязательно создаются вашей игрой.

Процессоры, используемые в мобильных устройствах, обычно слишком маломощны по сравнению с теми высокопроизводительными гигантами, которые находятся в ваших десктопах или PDA. Частота работы процессоров мобильных устройств может быть даже 15-20 MHz. Кроме того, зачастую мобильные устройства не имеют никаких графических ускорителей. Медленные процессоры и относительно медленная графика должны заставить вас задуматься и всерьез обратить свое внимание, на что ваша программа тратит больше всего времени. Но это не значит, что вы должны полностью отдаваться проблеме предупреждения всякого рода задержек в вашей игре. Вместо этого лучше следовать проверенному временем подходу при написании программ, для которых производительность — основная причина головной боли (это могут быть не только игры): сначала сделайте так, чтоб работало, после сделайте так, чтоб работало правильно, а уже после этого сделайте так, чтоб работало быстро.
Этот подход работает не всегда. Например, если ваша игра — полноценная 3D-аркада с множеством объектных преобразований, поворотов, масштабированием и тысячами полигонов, которые нужно прорисовывать для каждого фрейма, то для того, чтобы заставить это работать быстро на небольших устройствах, потребуется гигантский труд и огромное количество времени (хотя JSR 184 позволяет разрешить некоторые проблемы посредством использования аппаратных возможностей устройств). Что касается 2D-игр, то здесь вы можете не заботиться на начальном этапе о стратегии перерисовки. (Конечно же, если вы уверены, что сможете произвести необходимые действия по оптимизации позже.) Возможные действия по оптимизации не обязательно должны включать полную перерисовку для каждого фрейма. Вкратце проанализируйте проектировку вашей игры и свободно определите для себя, какие улучшения производительности (графические, естественно) вы сможете привнести позже; но ни в коем случае не забивайте себе голову какими-либо конкретными деталями по реализации этих улучшений. Помните, что улучшение производительности кода часто оказывается намного более сложным процессом, чем непосредственное кодирование приложения. Именно по этой причине мы разделяем секции оптимизации для обеих из рассматриваемых нами игр: сначала мы определяем, в какой именно части нашей реализации проявляются явные задержки (низкая производительность) и как выбрать наиболее оптимальный и быстрый вариант решения этой проблемы.

Удовлетворенность пользователя
Играть в игры должно быть интересно, иначе бы люди просто в них не играли. В случае с калькулятором или текстовым редактором, которые не всегда так уж интересны, мы вынуждены ими пользоваться, поскольку значимость этих приложений намного перевешивает неудобность и непривлекательность их использования. Совсем иначе обстоит дело с играми. Здесь нет видимой выгоды или других причин тратить свое время и нервы на игру, если только она не интересна и не доставляет удовольствия пользователю. Таким образом, целевое назначение любой игры — быть интересной и понравиться пользователю. А в ситуации с сотовыми телефонами существует ряд факторов, которые могут очень негативно повлиять на мнение пользователя о вашей игре.
Во-первых, мы имеем слишком малый экран, на котором достаточно сложно уместить все, что необходимо, и именно так, как задумывалось. Клавиши слишком малы и неудобны в использовании. Это особенно важно в играх, для которых по игровому контексту нажатие клавиши должно быть обработано в пределах одной десятой доли секунды, чтобы игрок смог преуспеть в игре. Звуковые возможности также могут быть чрезвычайно ограничены: звука может не быть вовсе либо он будет, но всего лишь в виде небольшого набора бип-сигналов или же обычного тонального генератора. В любом случае этого недостаточно, чтобы предоставить своей игре хорошее звуковое оформление. Чтобы привлечь пользователей, игра должна иметь достойный внешний вид и удобное управление. Это означает, что различные игровые экраны (сцены в игре) должны выглядеть как часть одной игры. Это можно осуществить за счет использования постоянной гаммы цветов, фоновых изображений и узоров, иконок и прочего. Пользователи не должны видеть сообщений об ошибках во время игры, не считая критических (например: "HTTP 404: not found"). Графическое и звуковое оформление игры в идеале должно создаваться людьми с художественными и эстетическими наклонностями.

Отладка
Процесс отладки на самих мобильных устройствах очень трудоемкий. Лучше всего воспользоваться эмулятором, например, WTK, где это возможно, поскольку эмуляторы позволяют вам получить доступ к консоли, на которую вы без труда сможете выводить сообщения с помощью метода System.out.println(). Также очень хорош прием перегрузки или определения методов toString() для всех ваших объектов. С их помощью вы сможете получать полную информацию о состоянии любого объекта на конкретный момент времени. Вы также сможете просто закомментировать эту часть кода, чтобы предотвратить попадание ее в релиз игры. При этом вы оставляете за собой возможность отладки до следующих релизов. Можно, конечно, воспользоваться встроенным отладчиком вашей любимой среды разработки (IDE) на Java, но это может повлечь за собой множество проблем. Пока для нас будет вполне удобным метод println().
Другой вариант отладки — демонстрационные режимы в вашей игре. Идеально демо-режим должен вовлекать в демонстрационный процесс игры всю ее функциональность, насколько это возможно. Это позволит вам не только продемонстрировать конечному пользователю, на что способна ваша игра, но и проверить, не рухнет ли приложение при возникновении каких-либо внештатных ситуаций. Это сделает процесс отладки ошибок очень легким. Вместо того, чтобы часами сидеть и нажимать на клавиши, пытаясь проверить все возможные варианты игры, вы просто сидите и смотрите, как игра делает всю самую сложную работу (для выполнения которой она, собственно, и была написана) за вас. Вместе с выводом небольших полезных отладочных сообщений вы получаете очень мощный отладочный инструмент.

Автономный вариант игры
Описание
В качестве примера автономной игры была выбрана очень распространенная версия аркады типа арканоид. Элементами игры являются блоки разного цвета, доска и шарик. Игрок управляет доской, которую он может передвигать вправо или влево. Шарик летает в пределах игрового поля, в то время как игрок должен придерживаться двух целей, чтобы выиграть. Игровое поле ограничено стенками слева, справа и сверху. Нижняя стенка, по которой передвигается доска, открыта. Чтобы предотвратить падение шарика вниз, игрок должен передвигать доску к тому месту, куда собирается упасть шарик, который, оттолкнувшись от доски, полетит по рассчитываемой траектории. Игра организована в несколько уровней. На каждом из них на игровом поле находится определенное количество блоков. Некоторые из этих блоков сразу распадаются при попадании в них шарика, другие ведут себя более сложно. Игрок должен, воспользовавшись шариком, сбить все сбиваемые блоки на игровом поле, чтобы перейти к следующему уровню. Если шарик падает на нижнюю границу экрана, и игрок не успевает отбить его с помощью доски, то игрок теряет одну жизнь. При старте игрок получает три жизни. Как только пользователь истратил все свои жизни, игра заканчивается, и он проигрывает. Во время игры на экране отображаются индикаторы, показывающие статистическую информацию о текущей игре: количество оставшихся жизней, количество заработанных очков и рекордное количество очков.
Поскольку это сравнительно простая игра, мы не будем сохранять рекордное количество очков постоянно, а только на текущий сеанс работы программы. Игра также содержит экран-заставку, которая отображает название игры и имя ее автора. Помимо этого, есть еще и экран, который выводится при проигрыше. И, наконец, она имеет демонстрационный режим, в котором доска контролируется самой игрой. Это очень полезно как для демонстрационных целей, так и для тестирования и отладки.

Рис. 1. Иерархия объектов приложения JBricks


Проектирование
Из приведенного выше описания мы можем выделить несколько сущностей, которые будут играть ключевую роль в проектировании игры. Это шарик, доска и блоки. Причем все эти сущности имеют общие свойства: положение, размер, графическое представление, а также обработчик столкновений с другими объектами (сущностями). Все это свойственно спрайтам (sprite). Поскольку в MIDP 1.0 нет поддержки работы со спрайтами (в MIDP 2.0 она уже есть), нам придется делать все самим. Это не страшно, так как необходимая функциональность наших спрайтов сравнительно мала.
Если немного подумать, то можно прийти к выводу, что блоки и доска имеют много общего. Поэтому в плане функциональности они схожи, и мы можем реализовать эти две сущности в одном классе.
Чтобы реализовать модель определения столкновений объектов, воспользуемся упрощенческим методом прямоугольников. В этом случае каждый из объектов на игровом поле будет определяться следующими параметрами: координатой X, координатой Y, а также высотой и шириной. Для доски и блоков эти параметры полностью определяют их фактический размер. Однако для шарика такое представление — достаточно грубое приближение его натуральной формы. Чтобы достичь более натурального поведения шарика при таком представлении, нужно немного уменьшить его размеры (например, 75% диаметра шарика в качестве одной стороны его прямоугольника). По определению два объекта считаются столкнувшимися, если их прямоугольники перекрываются, т.е. накладываются друг на друга.
Для того чтобы придать нашей игре более привлекательный вид, мы нарисовали объекты в псевдотрехмерной форме. Каждый объект отбрасывает тень, которая передвигается вместе со своим объектом. Кроме того, объекты подвержены эффекту освещения. Верхние края, откуда предположительно светит источник света, более светлые, тогда как нижние немного затемнены.

Теперь на стадии проектирования введем в нашу игру еще три сущности (это очень упростит реализацию игры). Первая сущность будет группировать блоки на всех уровнях. Назовем ее "списком блоков" (brick list). Этот список представляет собой контейнер объектов, но помимо этого он будет выполнять и функции видимости объектов. Например, вместо того, чтобы перерисовывать каждый блок, мы просто даем команду списку перерисовать все блоки данного уровня. Так же и в случае с обработкой столкновений. Чтобы не определять индивидуально, не столкнулся ли шарик с каким-то из блоков, мы возлагаем эту задачу на список, который и осуществит эту проверку за нас. Второй вводимой сущностью будет "экран" (screen). Эта сущность будет координировать любые действия, происходящие на игровом поле (т.е. что нужно рисовать, и в каком порядке). Стратегию перерисовки мы обсудим далее в секциях Реализация и Оптимизация. Наконец, нам нужна сущность, которая связала бы все это вместе — что-то, что могло бы привести в действие нашу игру. По вполне понятным причинам назовем эту сущность движком (engine).
Игра на данный момент спроектирована так, что имеет всего четыре уровня. Однако их количество можно очень легко увеличить в будущем. Для того чтобы сделать функцию по расширению уровней максимально простой, мы выносим представление уровней в виде данных вне исходного кода. Если сделать так, то новые уровни могут быть прочитаны из базы данных устройства или даже загружены из сети без проблем с безопасностью. Это означает также, что уровни может разрабатывать любой желающий, а не только программист. Но для этого, естественно, нужно будет написать немного лишнего кода, который будет осуществлять чтение/загрузку уровней из внешних источников.
Другим объектом, на который нужно обязательно обратить внимание на этапе проектирования, — это размер экрана. Спецификация MIDP не устанавливает какой-то определенный размер экрана и даже не задает возможный минимальный его размер. Здесь все зависит от разработчиков устройств. Именно они устанавливают разрешение для своих устройств. Общее решение данной проблемы — допускать очень малый размер экрана. Таким образом, у большинства устройств размер экрана совпадет либо почти совпадет с выбранным вами. В случае, если разрешения совпадут, игра будет занимать все пространство экрана устройства. Если же разрешение устройства окажется больше, то игра будет центрирована внутри физической области экрана. Наша игра — типичный пример простой игры, когда можно выбрать более искушенный подход для решения этой проблемы. Поскольку размеры всех объектов кодировать не так уж сложно, мы можем вычислять их при старте игры, первоначально определив фактический размер и разрешение экрана устройства. Это значит, что наша игра всегда будет использовать максимум доступного ей экранного пространства.

Реализация
Как сказал Дональд Кнут: "Предварительная оптимизация — это источник всех зол". Всецело доверяя этому высказыванию, мы начнем с обычной реализации, чтобы не усложнять себе жизнь. Как только игра начнет работать, мы определим части программы, которые необходимо будет оптимизировать. Далее будут приведены довольно интересные элементы кода, иллюстрирующие подробности идей реализации. Эти элементы представлены по принципу "снизу-вверх". Это означает, что части более низкоуровневого кода будут рассматриваться раньше, чем высокоуровневый код.

Класс ThreeDColor
Этот класс реализует безопасный шаблон перечислений, который был ранее представлен в знаменитой книге "Эффективное программирование на Java" ("Effective Java Programming"). Все цвета, которые можно использовать, представлены в виде экземпляров переменных с модификатором public.
Private-конструктор данного класса предотвращает установку других значений этих переменных. Методы brighter() и darker() используются для выбора подходящего цвета для освещения и затемнения соответственно. Метод getRGB() возвращает целое RGB-значение цвета, который был установлен с помощью метода Graphics.setColor(int).
Код класса ThreeDColor: http://java.sun.com/blueprints/code/bricks10/com/sun/j2me/blueprints/jbricks/ThreeDColor.java.html .

Класс Sprite
Этот класс является суперклассом для классов Ball и Brick. Каждый объект класса Sprite имеет координаты X и Y, ширину, высоту, определяющие прямоугольник, а также смещение тени. Смещение тени задает, насколько далеко от объекта будет рисоваться его тень. Оно также показывает, насколько высоко над экраном будет "парить" объект. Прямоугольник, который определяется в этом классе, как мы уже говорили выше, используется для "отлова" столкновений объектов. Он создается в конструкторе, в котором также определяется значение смещения тени согласно фактическому размеру экрана. Данный класс также определяет абстрактные методы для рисования объектов и теней у спрайтов. Реализация этих методов предполагается уже в классах Ball и Brick.
Код класса Sprite: http://java.sun.com/blueprints/code/bricks10/com/sun/j2me/blueprints/jbricks/Sprite.java.html .

Класс Ball
Этот класс занимается движением и прорисовкой шарика. Радиус шарика (или его размер) определяется на основе текущих размеров экрана.
Каждый экземпляр шарика имеет ряд переменных, которые задают его скорость (dx, dy), случайные скоростные помехи (xo, yo), а также его цветовую схему (цвет, яркость и затемнение). Конструктор создает экземпляры переменных класса Sprite width (ширина) и height (высота) на основе значения радиуса шарика и, помимо этого, устанавливает цвет.
Этот класс содержит четкие инструкции по поводу того, как нужно прорисовывать шарик. Метод move() устанавливает новое положение шарика на игровом поле с учетом текущей скорости и помех. bounceHorizon-tal() и bounceVertical() подбирают и вычисляют подходящий коэффициент скорости после столкновения шарика с другим объектом или стенкой. Чтобы предотвратить тупиковую ситуацию, когда шарику некуда больше двигаться либо когда он начинает циклично двигаться по одной и той же траектории (замкнутому пути), методы bounce Horizontal() и bounceVertical() каждый раз в цикле привносят немного помех при вычислении пути. Это позволяет избежать зацикливания игры. Методы paint() и paintShadow() используются для прорисовки шарика.
Код класса Ball: http://java.sun.com/blueprints/code/bricks10/com/sun/j2me/blueprints/jbricks/Ball.java.html .

Класс Brick
Класс Brick определяет ряд основных атрибутов блоков: width (ширина), height (высота), step (шаг), gap (разрыв). Атрибуты width и height представляют собой ширину и высоту блоков по умолчанию, step — это количество движений блока при анимации, а gap — это расстояние до смежных с ним блоков. Все эти значения тоже вычисляются согласно текущему размеру экрана. Кроме того, в этом классе присутствуют определения для символических имен для каждого типа блоков: zombie (для несуществующих блоков), standart (обычные блоки), fixed (неразрушимые блоки) и slide (блоки, которые могут перемещаться на место своих соседей, если такое место еще не занято). Наконец, каждый блок имеет ряд атрибутов, касающихся его позиционирования на игровом поле, типа, цветовой схемы, количества очков, которое дается за разрушение этого блока и ссылки на список блоков, к которому он принадлежит.
Большинство атрибутов блока инициализируется конструктором. Метод clear() удаляет блок с игрового поля, hit() знает, как текущий блок должен отреагировать на следующее столкновение с шариком. IsFixed() возвращает true, если этот блок нельзя разрушить (тип fixed). Как и в предыдущем классе, методы paint() и paintShadow() предназначены для прорисовки объекта.
Код класса Brick: http://java.sun.com/blueprints/code/bricks10/com/sun/j2me/blueprints/jbricks/Brick.java.html .

Класс BrickList
BrickList — это первый класс, относящийся к высокоуровневым классам нашей игры. Этот класс занимается размещением блоков на игровом поле. Он знает, как нужно расположить блоки с учетом всех их параметров, а также с расстановкой цветов. Конструктор BrickList использует вариант расположения по умолчанию. Метод moveBrick() перемещает блок с одной позиции на другую, checkForCollision() возвращает значение — число больше нуля, если произошло столкновение шарика с одним из блоков из текущего списка блоков. Возвращаемое значение — это количество очков, которое получает игрок за сбитый блок. Это значение берется из свойства соответствующего сбитого блока. После этого количество заработанных игроком очков увеличивается на это значение. Обратите внимание, каким образом метод checkCollision() заново использует тот же прямоугольник для проверки на столкновение, тем самым исключив из процесса проверки на столкновения любой ненужный мусор (да, это есть оптимизация).
Метод isClean() возвращает true, если на игровом поле (и, соответственно, в списке) не осталось больше стандартных блоков. Таким образом, это означает, что уровень успешно пройден; в противном случае этот метод возвращает false. getNeighbor() возвращает соседствующий блок для блока, передаваемого в качестве аргумента и в соответствующем направлении. Если для этого блока по указанному направлению нет больше соседей, то этот метод возвращает null. Методы paint() и paintShadow() отображают все блоки текущего листа, поочередно вызывая методы paint() и paintShadow() для каждого из своих элементов-блоков.
Код класса BrickList: http://java.sun.com/blueprints/code/bricks10/com/sun/j2me/blueprints/jbricks/BrickList.java.html .

Класс Screen
Этот класс заправляет всеми высокоуровневыми операциями по рисованию, которые необходимы нашей игре. Он знает, как рисовать различные экраны, которые мы обсуждали в секции Описание этой статьи, а также может запрашивать состояние у класса Engine, чтобы знать, какой экран прорисовывать. В заглавном экране на имя автора наложена простенькая анимация, так что имя автора поднимается из нижней границы экрана. Для анимации этот класс использует переменную wildcard_y. Кроме того, методы keyPressed() и keyReleased() пересылают события, поступающие от клавиатуры, в класс Engine.
Код класса Screen: http://java.sun.com/blueprints/code/bricks10/com/sun/j2me/blueprints/jbricks/BrickList.java.html .

Класс Engine
Этот класс можно смело именовать сердцем нашей игры. Он хранит все шаблоны уровней в переменной pattern_list (представленные таким образом, что, посмотрев на исходные данные, можно без особого труда представить себе, что собой будет представлять тот или иной уровень) и определяет ряд различных состояний, в которых может пребывать игра. Это состояния title, play, over и demo. Игра стартует, начиная с состояния title, когда отображается заставочный экран. Если в течение определенного промежутка времени была нажата какая-нибудь клавиша, игра переходит в нормальное игровое состояние play. Иначе она стартует демонстрационный режим (состояние demo). В этом состоянии игра играет сама в себя. Выйти в обычный режим play можно, нажав любую клавишу. В случае, когда игрок теряет все выделенные ему жизни, игра переходит в состояние over. Если в течение определенного времени игрок нажимает какую-либо клавишу, игра переходит в режим play, иначе стартует режим demo. Основной цикл программы определен в методе run(). Здесь движок игры "засыпает" на некоторое время, после чего анимирует все объекты и, наконец, перерисовывает экран. Такой подход используется в огромном количестве аркадных игр и обычно называется игровым циклом.
В этом цикле игра прежде всего обновляет свое состояние. Среди элементов, которые игре нужно обновить, — пользовательский ввод, таймеры, генераторы случайных чисел и вражеский AI (Artificial Intelligence — искусственный интеллект). После того, как состояние игры было обновлено, экран перерисовывается. После перерисовки движок "спит" некоторое время и потом начинает весь этот процесс заново. Чтобы дать игре возможность работать независимо от него, модуль движка запускается в своем собственном потоке. Это реализовано посредством использования в классе Engine реализованных методов интерфейса Runnable. После чего создается новый объект класса Thread, и его конструктору передается объект нашего движка. При этом объект thread первым делом запускает сам движок посредством вызова метода run() класса Engine.
Обратите также внимание на то, как происходит обработка нажатий игроком клавиш. Код клавиши, нажатой пользователем, записывается в переменную при первом ее нажатии. Как только пользователь отпускает эту клавишу, значение этой переменной с кодом клавиши сбрасывается. Основной цикл программы передвигает доску, управляемую пользователем, в запрошенном направлении, пока клавиша не будет отпущена.
Это означает, что для того, чтобы переместить доску в другой край экрана, игроку не потребуется несколько раз нажимать на клавишу. Он ее просто удерживает до тех пор, пока доска не переместится в нужное ему место.
Код класса Engine: http://java.sun.com/blueprints/code/bricks10/com/sun/j2me/blueprints/jbricks/Engine.java.html .

Оптимизация
Как только мы запускаем игру в среде WTK или на самом устройстве, сразу бросается в глаза тот факт, что наш подход к прорисовке экрана слишком медленный. Анимация слишком резкая и рывковая. В игру совершенно невозможно играть.
Надо что-то срочно менять. В нашем случае мы просто перерисовывали весь экран для каждого фрейма. Согласно природе нашей игры понятно, что это просто абсолютно нелогичный подход, поскольку в игре у нас от фрейма к фрейму происходит изменение только двух вещей: текущей позиции шарика и доски (если пользователь активно ее перемещает). Изредка наш мячик соударяется со стандартным блоком, который после этого исчезает, либо со скользящим (slide) блоком, который может переместиться на место своего соседа, а может и не переместиться. В этом случаем нам придется перерисовывать еще и весь список блоков. Однако после более близкого изучения мы замечаем, что можно, собственно говоря, весь список и не перерисовывать. Можно просто стереть сбитый стандартный блок из списка или, если это slide-блок, удалить его в старой позиции и нарисовать в новой. Таким образом, имеет смысл рисовать список блоков в отдельном буфере изображения и только потом копировать этот буфер на экран во время перерисовки. Так, во время изменения списка (из-за сбитого или перемещенного блока) мы перерисовываем изменившуюся часть списка в отдельном буфере изображения.
В общем, оптимизация имеет смысл в нескольких областях. Графическая производительность — это одна из наиболее важных областей, и мы должны иметь это в виду. Однако перегруженность в вычислениях также очень критична для вашей игры. Если ваша игра тратит слишком много времени на прохождение игрового цикла, нужно обратить внимание на такие вещи, как проверка на столкновения и другие вычислительные расчеты игры. Для начала попробуйте отключить все кажущиеся вам полезными внутренние особенности реализации и проследите, насколько это уменьшит или увеличит нагрузку на процессор. WTK 2.0 имеет встроенный инструмент для мониторинга загруженности процессора эмулятором. Но будьте внимательны: его цифры не всегда отражают реальную картину происходящего на настоящих устройствах. Как только вы найдете что-то, что нужно исправить, сначала убедитесь, действительно ли ваша реализация обновленного кода оптимизирует работу приложения. Если нет, то вам следует поискать другой, более быстрый подход к решению этой проблемы. Также обязательно обратите внимание на элементы управления. Нет ничего более отталкивающего, а в играх особенно, чем медленное и инертное управление, которое не дает пользователям игры проявить свои игровые способности.

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

Вывод
Самый главный урок, который вы должны были извлечь из этой статьи: рисование — занятие очень ресурсоемкое. Если вы посмотрите на код изначальной реализации, а потом на оптимизированный код, то не увидите между ними большого различия. Между тем, различия в производительности игры очень велики. Если вы собираетесь действительно использовать, а тем более продавать, свою игру, вам обязательно нужно опробовать ее в действии на реальном устройстве. Хоть WTK — это и великолепный инструмент для разработки, но в игре существуют моменты, которые можно обнаружить, лишь запустив ее на реальном мобильном устройстве.
В следующий раз мы рассмотрим сетевой вариант игры для мобильных устройств и еще раз поговорим про проектирование, реализацию и оптимизирование игр на Java.

По материалам Kay Neuenhofen
Алексей Литвинюк, litvinuke@tut.by



Компьютерная газета. Статья была опубликована в номере 05 за 2004 год в рубрике программирование :: разное

©1997-2024 Компьютерная газета