Вы здесь

Создание игр для мобильных телефонов. Часть II. Основы программирования мобильных игр (Майкл Моррисон)

Часть II

Основы программирования мобильных игр

Глава 4

Мобильная графика 101

Архив Аркад

В 1979 году компания Atari совершила первую попытку создания аркады с векторной графикой и выпустила игру Lunar Lander. Хотя эта игра была не столь успешна, как Asteroids, вышедшая вскоре, тем не менее она занимает важное место в истории видеоигр. Lunar Lander была переделана во множестве форматов на различных компьютерных системах. Первую версию игры отличает продуманное управление двигателями, используемыми при посадке аппарата на лунную поверхность.

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

В этой главе вы узнаете:

► о системах координат MIDP;

► почему цвет так важен для MIDP-графики;

► как применять классы Graphics и Canvas;

► как построить графические мидлеты, отображающие примитивы, текст и картинки.

Основы мобильной графики

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

Понятие о графической системе координат

Все графические системы используют какие-либо системы координат, чтобы определить расположение точки в окне или на экране. Обычно графические системы координат имеют начало – точку (0,0), а также определяют направления каждой из осей. Если вы далеки от математики, важно просто понять, что система координат определяет способ указания точки на экране с помощью двух координат X и Y. Традиционная математическая система координат, которая знакома многим из нас, показана на рис. 4.1.

Рис. 4.1. Традиционная система координат XY, широко применяемая в математике


Графика MIDP использует похожую систему координат для определения, как и где будут выполняться графические построения. Поскольку подобные построения в мидлете происходят в рамках холста, то система координат связана именно с ним. Начало системы координат MIDP располагается в верхнем левом углу холста, положительное направление оси X – вправо, а оси Y – вниз. Все координаты в MIDP-системе – положительные целые числа. На рис. 4.2 показан вид такой системы координат.

Рис. 4.2. Координатная система MIDP XY похожа на традиционную математическую систему координат за исключением того, что она связана с игровым холстом мидлета


В копилку Игрока

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

Если у вас возникли сложности с пониманием графической системы координат MIDP, представьте классическую игру «Морской бой». В этой игре вы стараетесь подбить корабли противника, посылая торпеду в определенную точку игровой сетки. Корабли используют собственные системы координат, позволяющие определять их местоположение на игровом поле. Аналогично, когда вы создаете графику в мидлете, вы указываете положение на холсте, которое представляет не что иное, как маленький квадрат – пиксель.

Важно отметить одну интересную деталь, касающуюся координатной системы MIDP. Система представляет расстояние между пикселями, а не сами пиксели. Иначе говоря, верхний левый угол пикселя, расположенного в верхнем левом углу холста, имеет координаты (0,0), а его правый нижний угол – (1,1). Это помогает избежать путаницы при заливке графических примитивов, например, прямоугольников. Координаты прямоугольника являются границами области заливки.

Понятие о цветах

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

Цветные мониторы компьютеров, вероятно, лучше всего дают понять, как компьютерное программное обеспечение реализует работу с цветом. Цветной монитор имеет три электронные пушки: красную, зеленую и синюю. Лучи каждой трубки покрывают все пиксели экрана, заставляя фосфор создавать нужный цвет. Суммарная интенсивность потока каждой трубки определяет результирующий цвет пикселя. Такое смешивание различных лучей из пушек аналогично смешиванию красок на палитре. Хотя работа LCD-экранов мобильных телефонов основана на других физических принципах, идея смешивания цветов по-прежнему заложена в основу.

В копилку Игрока

С технической точки зрения, результат смешивания цветов на мониторе отличается от смешивания цветов на палитре. Дело в том, что на мониторе смешивание цветов обладает свойством аддитивности. Это означает, что при смешивании всех цветов в результате будет получен белый цвет. А смешивание цветов на палитре субстрактивно; это означает, что смешивание всех цветов в результате даст черный цвет. То, каким свойством обладает смешивание цветов (аддитивным или субстрактивным), определяется физическими свойствами средства.

Цветовая система Java очень похожа на физическую систему, применяемую цветными мониторами. Так цвета формируются, используя различные интенсивности красного, зеленого и синего цветов. Следовательно, цвета в Java реализуются указанием численной интенсивности трех цветов (красного, зеленого и синего). Такая цветовая система известна как RGB (Red Green Blue) и является стандартом для большинства компьютерных систем.

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

Таблица 4.1. Числовые значения компонентов RGB наиболее часто используемых цветов

Обратите внимание, что интенсивность каждого цветового компонента варьируется в диапазоне от 0 до 255. Это означает, что каждый из цветов занимает 8 бит памяти, а результат смешения трех цветовых компонентов – 24 бита. Поэтому цветовая система MIDP является 24-битной. Конечно, это не имеет отношения к большому числу применяемых черно-белых дисплеев, но имеет огромное значение, когда речь идет о программировании мобильных игр.

Стоит отметить, что графический API MIDP не включает знакомый класс Color, являющийся частью стандартного Java Advanced Windowing Toolkit (AWT). Исключение класса Color – это результат стремления сделать MIDP API как можно более компактным. На самом деле класс Color служит лишь организационной структурой для красного, зеленого и синего компонентов цвета. В программировании MIDP-графики вы работаете с этими компонентами как с отдельными целочисленными переменными, а не как с объектом класса Color.

Во многих приложениях для редактирования изображений можно экспериментировать с компонентами RGB и получать новые цвета. Например, чтобы узнать соотношение компонентов цвета, в стандартной программе Paint для Windows в цветовой палитре дважды щелкните по интересующему вас цвету. В диалоговом окне Edit Colors (Редактор цвета) щелкните по кнопке Define Custom Colors (Определить цвет) и в полях Red (Красный), Green (Зеленый) и Blue (Синий) введите числовые значения интенсивности компонентов (рис. 4.3).

Рис. 4.3. В стандартной программе Windows Paint вы можете определить значения компонентов нужного цвета


В копилку Игрока

Чтобы увидеть еще одну точку зрения на RGB-цвета, посетите сайт http://www.rgb-game.com/.

Работа с графикой в J2ME

Если у вас уже есть опыт программирования на стандартном Java, вы, несомненно, знакомы с классом Graphics, который дает возможность вывода графических примитивов (линий, прямоугольников и т. п.), текста и изображений как на дисплей, так и в буфер. Для выполнения операций с графикой вы вызываете методы объекта Graphics, параметра метода мидлета paint(). Объект Graphics() передается в метод paint(), а затем используется для вывода графики на экран мидлета или в буфер. Поскольку объект Graphics() автоматически передается в метод paint(), нет необходимости создавать его вручную.

Совет Разработчику

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

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

Этот метод принимает три целочисленных параметра, которые соответствуют трем цветовым компонентам. Подобно setColor() работает метод setGrayScale(), который принимает один целочисленный параметр из диапазона от 0 до 255. Если оттенок серого создается на цветном экране, то всем трем компонентам присваивается одинаковое значение, результатом чего является оттенок серого.

Объекты Graphics также имеют атрибут шрифта, который определяет размер выводимого текста. Метод setFont() принимает в качестве параметра объект Font и применяется для настройки шрифта выводимого текста. Подробнее о выводе текста речь пойдет позже в этой главе.

Совет Разработчику

Другая версия метода setColor() принимает единственный целочисленный параметр, определяющий цвет. Отдельные цветовые компоненты (красный, зеленый и синий) определены внутри значения цвета в соответствии со следующим шестнадцатеричным форматом: 0x00RRGGBB. Иначе говоря, красный (RR), зеленый (GG) и синий (BB) компоненты хранятся в трех младших байтах 32-битной целой величины.

Большинство операций с графикой, выполняемых классом Graphics, можно свести к следующим трем категориям:

► рисование графических примитивов;

► вывод изображений.

► вывод текста;

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

Рисование графических примитивов

Графические примитивы состоят из линий, прямоугольников и дуг. Вы можете создавать весьма сложные объекты, используя эти примитивы. Класс Graphics содержит методы рисования примитивов. Также методы этого класса можно использовать для заполнения внутренних областей примитивов. Хотя графика, созданная при помощи примитивов, не сравнится с растровыми изображениями, добавив немного воображения, можно творить чудеса!

Линии

Линия – это простейший графический примитив, а следовательно, его проще всего создать. Тем не менее даже самые популярные аркады, например, Asteroids, используют векторную графику, которая состоит только из линий. Метод drawLine() строит линии, он объявлен так:


void drawLine(int x1, int y1, int x2, int y2)


Первые два параметра x1 и y1 определяют первую точку линии, другие два параметра – конечную. Важно понять, что эти координаты определяют границы начала и конца отрезка. Предположим, что вы рисуете линию в положительном направлении осей X и Y, тогда x1 и y1 указывают на верхний левый угол первой точки линии, а x2 и y2 указывают на нижний правый угол последней точки прямой. Чтобы нарисовать линию в мидлете, вызовите функцию drawLine() в методе мидлета paint(), как показано в примере:


public void paint(Graphics g) {

g.drawLine(5,10,15,55);

}


Этот код проводит линию из точки с координатами (5,10) в точку с координатами (15,55). Вы можете изменить стиль линии, воспользовавшись методом setStrokeStyle(). Этот метод принимает одну из двух констант Graphics.SOLID или Graphics.DOTTED, определяющих вид линии: сплошная или точечная. Если вы явно не укажете стиль линии, то по умолчанию будет использоваться Graphics.SOLID.

Совет Разработчику

MIDP API не поддерживает рисование многоугольников. Вы должны рисовать многоугольники самостоятельно, используя команду drawLine().

Прямоугольники

Прямоугольники также очень просто нарисовать в мидлете. Метод drawRect() позволяет рисовать прямоугольники, указывая координаты верхнего левого угла, высоту и ширину прямоугольника. Этот метод объявлен так:


void drawRect(int x, int y, int width, int height)


Параметры x и y определяют положение верхнего левого угла прямоугольника, а параметры width и height определяют размеры прямоугольника в пикселях. Чтобы использовать метод drawRect(), вызовите метод paint():


public void paint(Graphics g) {

g.drawRect(5, 10, 15, 55);

}


В результате выполнения этого кода будет нарисован прямоугольник шириной 15 пикселей и высотой 55 пикселей, верхний левый угол которого имеет координаты (5,10). Существует также метод drawRoundRect(), который позволяет рисовать прямоугольники с округленными углами:


void drawRoundRect(int x, int y, int width, int height, int arcWidth,

int arcHeight)


Метод drawRoundRect() требует два дополнительных параметра по сравнению с drawRect(): arcWidth и arcHeight. Эти параметры определяют ширину и высоту дуги, округляющей углы прямоугольника. Если вы хотите нарисовать овал, то параметры arcWidth и arcHeight должны быть равны половине ширины и высоты прямоугольника соответственно. Ниже приведен пример вызова метода drawRoundRect(), результатом выполнения кода будет овал:


public void paint(Graphics g) {

g.drawRoundRect(5, 10, 15, 55, 6, 12);

}


В этом примере представлен прямоугольник, ширина которого 15 пикселей, а высота 55 пикселей, левый верхний угол в точке с координатами (5,10). Углы округлены дугами, высотой 12 пикселей и шириной 6 пикселей. Также в классе Graphics есть методы для рисования прямоугольников, которые заливают внутреннюю область примитива текущим цветом: fillRect() и fillRoundRect().

Совет Разработчику

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

Дуги

Дуги намного сложнее, чем линии и прямоугольники. Дуга – это часть эллипса. Удалите часть эллипса – и вы получите дугу. Если вы не можете представить себе дугу, представьте колобка из игры Pac-Man, когда он съедает очередную точку. Дуга является частью эллипса. Чтобы задать дугу, вы должны задать эллипс и указать его часть. Метод рисования дуги объявлен так:


void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle)


Первые четыре параметра метода drawArc() определяют эллипс, частью которого является дуга. Оставшиеся два параметра определяют дугу как часть овала. На рис. 4.4 показана дуга.

Рис. 4.4. В MIDP-графике дуга – это часть эллипса


Как видно из рисунка, дуга эллипса определяется начальным углом (в градусах), а также градусной мерой в определенном направлении. Положительное направление – по часовой стрелке, а отрицательное – против. Дуга, показанная на рис. 4.4, начинается с угла 95° и величины дуги 115°, в результате получается угол 210°.

Ниже приведен пример использования метода drawArc():


public void paint(Graphics g) {

g.drawArc(5, 10, 150, 75, 95, 115);

}


В результате будет нарисована дуга, являющаяся частью эллипса шириной 150° пикселей и высотой 75° пикселей. Она расположена в точке с координатами (5,10) и простирается в положительном направлении на 115°.

Вы, вероятно, будете удивлены, узнав, что в классе Graphics нет метода drawOval(). Чтобы нарисовать эллипс, вы должны использовать метод drawArc(), для чего в качестве последнего параметра передайте 360°. Это означает, что дуга будет не частью эллипса, а полностью эллипсом.

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

Совет Разработчику

Чтобы нарисовать идеальную окружность с помощью методов drawArc() или fillArc(), просто введите одинаковые значения ширины и высоты, и задайте угол 360°.

Вывод текста

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


void setFont(Font font)


Объект Font моделирует текстовый шрифт и определяет вид, стиль и его размер. Объект Font поддерживает четыре разных стиля, которые определяются следующими константными членами класса: STYLE_PLAIN, STYLE_BOLD, STYLE_ITALIC и STYLE_UNDERLINED. Эти стили действительно являются константами, а последние три можно использовать в совокупности. Стиль STYLE_PLAIN отменяет все примененные к шрифту стили. Чтобы создать объект класса Font, вызовите статический метод getFont() и передайте в него вид, стиль и размер текста, каждый из этих параметров представляет собой целое число:


static Font getFont(int face, int style, int size)


Поскольку шрифты ограниченны в мидлетах, вы должны использовать целочисленные константы для определения каждого параметра. Например, вид шрифта должен быть определен одним из следующих значений: FACE_SYSTEM, FACE_MONOSPACE или FACE_PROPORTINAL. Аналогично, стиль шрифта должен быть определен одной из констант, о которых я упоминал ранее: STYLE_PLAIN или комбинацией STYLE_BOLD, STYLE_ITALIC и STYLE_UNDERLINED. Наконец, размер шрифта задается одной из предопределенных констант: SIZE_SMALL, SIZE_MEDIUM или SIZE_LARGE. Ниже приведен пример создания крупного полужирного подчеркнутого шрифта:


Font myFont = Font.getFont(Font.MONOSPACE, Font.LARGE,

Font.BOLD | Font.UNDERLINED);


Обратите внимание, что в MIDP-графике вы не можете создавать собственные шрифты, что вполне обоснованно ввиду ограниченных возможностей дисплея мобильного устройства. Также помните, что тот или иной шрифт доступен на мобильном телефоне, это хороший довод тестировать приложение на реальном устройстве.

Совет Разработчику

Если возникнет необходимость вернуться к настройкам шрифта, принятым по умолчанию, воспользуйтесь методом getDefaultFont() класса Font.


После того как вы определили шрифт методом getFont(), вы должны применить его к выводимому тексту, для чего используйте метод setFont():


g.setFont(myFont);


Теперь вы готовы к тому, чтобы вывести текст с нужными настройками. Метод drawString(), определенный в классе Graphics, – это как раз то, что нужно. Этот метод объявлен так:


void drawstring(String str, int x, int y, int anchor)


Метод drawString() в качестве первого параметра принимает объект класса String, который содержит выводимый текст. Следующие два параметра x и y определяют точку вывода текста. Особое значение этой точке придает параметр anchor. Чтобы упростить вывод текста и изображений, MIDP API предусматривает анкеры, которые помогают сэкономить массу сил при выводе текста и изображений и избавляют от излишних вычислений. Анкер (или точка привязки) ассоциирован с горизонтальной и вертикальной константами, каждая из которых определяет соответственно горизонтальное и вертикальное положения текста по отношению к анкеру. Горизонтальные константы, используемые для описания анкера, – это LEFT, RIGHT и HCENTER. Одна из этих констант в сочетании с вертикальной константой полностью описывает положение выводимого объекта. Вертикальные константы – это TOP, BASELINE и BOTTOM.

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


g.drawStirng(«Look up here!», getWidth() / 2, 0, Graphics.HCENTER | Graphics.TOP);


В этом коде текст выводится на точке экрана, расположенной в его верхней части в середине, – getWidth() / 2. Я предположил, что этот код помещен внутри класса, производного от Canvas, поэтому я смог воспользоваться методом getWidth() и получить значение ширины экрана. Это положение дополняется анкером, который является комбинацией двух констант Graphics.HCENTER и Graphics.TOP. Это означает, что выводимый текст форматируется по центру в горизонтальном направлении, а также, что верхняя граница текста имеет координату y.

Кроме метода drawString(), есть еще ряд методов, предназначенных для вывода текста. Методы drawChar() и drawChars() используются для вывода отдельных символов:


void drawChar(char character, int x, int y, int anchor)

void drawChars(char[] data, int offset, int length, int x, int y,

int anchor)


Оба метода работают аналогично методу drawString(), они запрашивают координаты точки вывода и анкер. Существует метод drawSubString(), с помощью которого можно выводить часть строки:


void drawSubString(String str, int offset, int len, int x, int y,

int anchor)


Этот метод содержит дополнительные параметры offset и len, определяющие подстроку в строке, передаваемой через параметр str.

Вывод изображений

Изображения очень важны для программирования игр, если, конечно, речь не идет о текстовых играх или играх, в которых применяется векторная графика. Изображения – это прямоугольные графические объекты, составленные из цветных пикселей. Каждый пиксель изображения описывает цвет определенной части изображения. Пиксели имеют уникальные цвета, описываемые цветовой системой RGB. Цветные изображения в MIDP-графики – это 24-битные изображения, следовательно, каждый пиксель изображения описывается 24 битами. Красный, зеленый и синий компоненты хранятся внутри этих четырех битов как самостоятельные 8-битовые значения.

В копилку Игрока

Вы, вероятно, слышали о 32-битной графике, где дополнительные 8 бит используются альфа-компонентами цвета, определяющих прозрачность пикселя. MIDP поддерживает прозрачность, а, следовательно, может использовать дополнительные 8 бит для альфа-компонентов на телефонных аппаратах, поддерживающих 8-битовую альфа-прозрачность. Если вы вспомните, мидлет Skeleton, который мы создали в предыдущей главе, сообщал о количестве альфа-уровней, поддерживаемых телефоном. При работе с книгой вы научитесь эффективно использовать 1-битовую альфа-прозрачность, т. е. простую прозрачность цвета изображения.

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


public static Image createImage(String name) throws IOException


Чтобы создать изображения, используя метод createImage(), необходимо передать название файла с изображением:


Image img = Image.createImage(«Explosion.png»);


Метод createImage() возвращает объект класса Image, который впоследствии можно использовать для работы с изображением в MIDP API. Также можно создать пустое изображение, вызвав другую версию метода createImage(), который принимает ширину и высоту изображения. Класс Image представляет графические изображения (файлы форматов PNG, GIF или JPEG) и предоставляет ряд методов для определения размеров изображения. Также этот класс реализует метод, с помощью которого вы можете создать объект Graphics для картинки и рисовать на существующем изображении.

В копилку Игрока

Если вы не знаете, формат изображений PNG (Portable Network Graphics – Переносимая сетевая графика) – это улучшение формата GIF, постепенно распространяющегося как альтернатива GIF. Изображения в формате PNG сжимаются лучше GIF-изображений, в результате их файлы имеют меньший размер. Также, PNG-изображения удобнее использовать при создании игр, поскольку они поддерживают переменные альфа-уровни. Класс Image полностью поддерживает формат PNG.

В классе Graphics есть единственный метод для вывода изображения на экран – drawImage():


boolean drawImage(Image img, int x, int y, int anchor)


Вероятно, этот метод покажется вам знакомым, поскольку в нем, также как и в методе drawString(), используются анкеры. Подобно drawString() метод drawImage() выводит изображение в точке с координатами (x,y) и с учетом параметра anchor. Для вывода изображений можно использовать те же константы, что и при выводе текста.

Итак, чтобы нарисовать изображение, сначала необходимо вызвать статический метод Image.createImage() и создать и загрузить изображение. Затем следует вызвать метод drawImage() и вывести изображение на экран. Ниже приведен код, который загружает и выводит изображение:


public void paint(Graphics g) {

//очистить экран

g.setColor(255,255,255); //белый

g.FillRect(0, 0, getWidth(), getHeight());

//создать и загрузить изображение

Image img = Image.createImage("Splash.png");

//вывести изображение

g.drawImage(img, getWidth() / 2, getHeight() / 2, //Поскольку используются атрибуты HCENTER и VCENTER,

Graphics.HCENTER | Graphics.VCENTER); //изображение выводится в центре экрана

}


В этом примере сначала очищается дисплей, для чего выбирается белый цвет, которым заполняется весь экран. Перед рисованием необходимо получить чистую поверхность. Затем с помощью метода createImage() загружается и создается изображение Splash.png. После того как изображение создано, вызывается метод drawImage(), и картинка выводится на дисплей, константы HCENTER и VCENTER определяют анкер.

Создание программы Olympics

Теперь у вас есть представление о MIDP-графике и, вероятно, вам не терпится посмотреть, как это все работает в контексте мидлета. Вы узнали, что графика обрабатывается методом paint(). Однако класс мидлета не содержит этого метода, поэтому для выполнения операций с графикой вы должны использовать класс Canvas. Класс Canvas представляет собой абстрактную поверхность и должен быть включен в класс мидлета. Графические операции выполняются с помощью класса Graphics. Класс, производный от Canvas, можно использовать для вывода изображений на экран.

Чтобы начать работу над графическим мидлетом, вы должны выполнить следующее:

1. создать класс, производный от Canvas и ассоциированный с мидлетом;

2. создать объект класса Canvas как член-переменную класса мидлета;

3. установить объект класса Canvas как текущий экран мидлета, для чего вызвать метод setCurrent().

Самый лучший способ понять этот процесс – создать пример программы. Хороший пример простой программы с MIDP-графикой – программа, рисующая олимпийский символ, состоящий из пяти пересекающихся колец.

Написание программного кода

Давайте начнем с класса OCanvas, который создаст холст для использования в мидлете Olympics. Код класса OCanvas приведен в листинге 4.1.

Листинг 4.1. Класс OCanvas служит настраиваемым холстом для мидлета Olympics

import javax.microedition.lcdui.*;

public class OCanvas extends Canvas {

private Display display;

public OCanvas(Display d) {

super();

display = d;

}

void start() {

display.setCurrent(this);

repaint();

}

public void paint(Graphics g) {

// Clear the display

g.setColor(255, 255, 255); // White

g.fillRect(0, 0, getWidth(), getHeight());

// Draw the first row of circles //Дуги выводятся как идеальные окружности, если задать одинаковые значения высоты и ширины, а также изменение угла от 0° до 360°

g.setColor(0, 0, 255); // Blue

g.drawArc(5, 5, 25, 25, 0, 360);

g.setColor(0, 0, 0); // Black

g.drawArc(35, 5, 25, 25, 0, 360);

g.setColor(255, 0, 0); // Red

g.drawArc(65, 5, 25, 25, 0, 360);

// Draw the second row of circles

g.setColor(255, 255, 0); // Yellow

g.drawArc(20, 20, 25, 25, 0, 360);

g.setColor(0, 255, 0); // Green

g.drawArc(50, 20, 25, 25, 0, 360);

}

}


Этот класс расширяет класс Canvas и устанавливает себя как экран мидлета. Конструктор вызывает конструктор родительского класса Canvas и инициализирует переменную display. Метод start() устанавливает холст текущим экраном мидлета и обновляет изображение. Наиболее важный код содержится в методе paint(), он вызывает функции setColor() и drawArc() и рисует олимпийский символ. Обратите внимание, что все аргументы углов в функциях drawArc() равны 0 0 и 360°, в результате чего будут нарисованы полные эллипсы.

Когда вы определили класс OCanvas, можно объявить член-переменную класса мидлета OlympicsMIDlet:


private OCanvas canvas;


Член-переменная класса должна быть инициализирована конструктором класса:


canvas = new Ocanvas (Display.getDisplay(this));


Это весь код для обработки графики, который необходим в мидлете Olympics. В листинге 4.2 представлен полный код класса OlympicsMIDlet.

Листинг 4.2. Код класса OlympicsMIDlet содержится в файле OlympicsMIDlet.java

import javax.microedition.midlet.*;

import javax.microedition.lcdui.*;

public class OlympicsMIDlet extends MIDlet implements CommandListener {

private OCanvas canvas;

public void startApp() {

if (canvas == null) {

canvas = new OCanvas(Display.getDisplay(this));

Command exitCommand = new Command("Exit", Command.EXIT, 0);

canvas.addCommand(exitCommand);

canvas.setCommandListener(this);

}

// инициализация

canvas.start(); //Метод start() холста запускает мидлет

}

public void pauseApp() {}

public void destroyApp(boolean unconditional) {}

public void commandAction(Command c, Displayable s) {

if (c.getCommandType() == Command.EXIT) {

destroyApp(true);

notifyDestroyed();

}

}

}


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

Тестирование готового приложения

Чтобы собрать и протестировать мидлет Olympics, скопируйте папку Olympics в папку apps, расположенную в папке установки J2ME Wireless Toolkit. Чтобы собрать мидлет, щелкните по кнопке Build (Собрать), а чтобы запустить эмулятор J2ME, щелкните по кнопке Run (Запустить). На рис. 4.5 показан мидлет The Olympics MIDlet.

Рис. 4.5. Мидлет The Olympics MIDlet демонстрирует построение основных геометрических фигур

Создание слайд-шоу

Хотя мидлет Olympics очень интересен и полезен для знакомства с программированием графики мидлета, вы, вероятно, хотите большего. Например, увидеть, как выводится текст и графика в контексте мидлета. В этой части книги вы разработаете слайд-шоу, что поможет вам попрактиковаться в выводе изображений и текста. Очевидно, слайд-шоу – не игра, однако этот мидлет поможет вам изучить основные приемы создания графики игр, например, комбинирование изображений и текста, а также обработку пользовательского ввода.

Написание программного кода

Мидлет Slideshow загружает несколько изображений и текст, после чего выводится на экран. Пользователь может пролистывать страницы слайд-шоу, используя клавиши со стрелками. Поскольку слайд-шоу относится к графической части мидлета, большая часть кода сосредоточена в классе SSCanvas, который хранит изображения и подписи под ними:


private Display display;

private Image[] slides;

private String[] captions = { "Love Circle Bowl", "Double Wide Spine",

"Flume Zoom Over-vert", "Kulp De Sac Bowl",

"Louie's Ledge" };

private int curSlide = 0;


Переменная sliders – это массив объектов Image, она инициализируется в конструкторе класса SSCanvas. Ниже приведен код этого конструктора:


public SSCanvas(Display d) {

super();

display = d;

// загрузить изображения слайд-шоу

try {

slides = new Image[5];

slides[0] = Image.createImage("/LoveCircle.jpg");

slides[1] = Image.createImage("/DoubleWide.jpg");

slides[2] = Image.createImage("/FlumeZoom.jpg");

slides[3] = Image.createImage("/KulpDeSac.jpg");

slides[4] = Image.createImage("/LouiesLedge.jpg");

}

catch (IOException e) {

System.err.println("Failed loading images!");

}

}


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

В копилку Игрока

Если вам интересно, что за изображения помещены в слайд-шоу, то это фотографии общественного скейт-парка, строящегося в моем городе, Нэшвилле, штат Теннеси. Я помогал с оформлением документов на строительство парка нашему сообществу скейтбордистов.

Основные действия выполняются в методе paint() класса SSCanvas, который выводит текущее изображение слайд-шоу и подпись на экран. Ниже приведен код метода paint():


public void paint(Graphics g) {

// очистить экран

g.setColor(255, 255, 255); // белый

g.fillRect(0, 0, getWidth(), getHeight());

// вывести текущее изображение

g.drawImage(slides[curSlide], getWidth() / 2, getHeight() / 2, //Текущее изображение выводится в центре экрана

Graphics.HCENTER | Graphics.VCENTER);

// настроить шрифт

Font f = Font.getFont(Font.FACE_PROPORTIONAL, Font.STYLE_BOLD,

Font.SIZE_MEDIUM);

g.setFont(f);

// вывести текущее изображение

g.setColor(0, 0, 0); // Черный //Текущая надпись центрирована и выводится вдоль верхней границы экрана

g.drawString(captions[curSlide], getWidth() / 2, 0,

Graphics.HCENTER | Graphics.TOP);

}


Метод paint() сначала очищает экран, тем самым удаляя то, что осталось от предыдущего слайда. Затем в центре экрана выводится текущее изображение. Далее выполняется настройка шрифта – полужирный, среднего размера, пропорциональный. Наконец, устанавливается черный цвет, и по центру у нижнего края экрана выводится текст.

Когда вывод графики завершен, последняя часть кода класса SSCanvas занимается обработкой пользовательского ввода. Нажимая клавиши со стрелками влево и вправо, пользователь может перелистывать слайды. Технически до главы 6 вы не научитесь обрабатывать ввод в играх, но здесь я познакомлю вас с основами. Ниже приведен код метода keyPressed():


public void keyPressed(int keyCode) {

// Get the game action from the key code

int action = getGameAction(keyCode);

// Process the left and right buttons

switch (action) {

case LEFT:

if (–curSlide < 0) //Перейти к последнему слайду, если первый уже показан

curSlide = slides.length – 1;

repaint();

break;

case RIGHT:

if (++curSlide >= slides.length) //Перейти к первому слайду, если последний уже показан

curSlide = 0;

repaint();

break;

}

}


Метод keyPressed() открывает новые горизонты программирования игровых мидлетов – обработку игровых событий. Игровое событие – это особое событие, которое ассоциировано с клавишами, обычно используемыми в играх. Смысл заключается в том, что вы можете привязать действия к определенным клавишам, чтобы настроить пользовательский интерфейс. В методе keyPressed() с помощью метода getGameAction() определяется игровое событие, ассоциированное с клавишами. Константы LEFT и RIGHT используются для описания нажатий клавиш со стрелками влево и вправо. Если значение action совпадает со значением одной из констант, то номер текущего слайда увеличивается или уменьшается, а затем отображается новый слайд.

Листинг 4.3. Так выглядит класс SSCanvas, который выполняет большую часть работы мидлета Slideshow. В листинге 4.3 приведен полный код этого класса:

import javax.microedition.lcdui.*;

import java.io.*;

public class SSCanvas extends Canvas {

private Display display;

private Image[] slides;

private String[] captions = { "Love Circle Bowl", "Double Wide Spine", //Индексы массива соответствуют изображениям

"Flume Zoom Over-vert", "Kulp De Sac Bowl",

"Louie's Ledge" };

private int curSlide = 0;

public SSCanvas(Display d) {

super();

display = d;

// загрузить изображения

try {

slides = new Image[5];

slides[0] = Image.createImage("/LoveCircle.jpg");

slides[1] = Image.createImage("/DoubleWide.jpg");

slides[2] = Image.createImage("/FlumeZoom.jpg");

slides[3] = Image.createImage("/KulpDeSac.jpg");

slides[4] = Image.createImage("/LouiesLedge.jpg");

}

catch (IOException e) {

System.err.println("Failed loading images!");

}

}

void start() {

display.setCurrent(this);

repaint();

}

public void keyPressed(int keyCode) {

// получить игровое событие

int action = getGameAction(keyCode);

// обработать нажатия клавиш

switch (action) {

case LEFT:

if (–curSlide < 0)

curSlide = slides.length – 1;

repaint();

break;

case RIGHT:

if (++curSlide >= slides.length)

curSlide = 0;

repaint();

break;

}

}

public void paint(Graphics g) {

// очистить экран

g.setColor(255, 255, 255); // белый

g.fillRect(0, 0, getWidth(), getHeight());

// вывести текущее изображение

g.drawImage(slides[curSlide], getWidth() / 2, getHeight() / 2,

Graphics.HCENTER | Graphics.VCENTER);

// установить шрифт

Font f = Font.getFont(Font.FACE_PROPORTIONAL, Font.STYLE_BOLD,

Font.SIZE_MEDIUM);

g.setFont(f);

// вывести текущее содержание

g.setColor(0, 0, 0); // черный

g.drawString(captions[curSlide], getWidth() / 2, 0,

Graphics.HCENTER | Graphics.TOP);

}

}


Чтобы интегрировать холст в мидлет, необходимо создать объект класса SSCanvas в классе SlideshowMIDlet:


private SSCanvas canvas;


Затем в конструкторе класса SlideshowMIDlet эта переменная инициализируется. Полный код мидлета Slideshow приведен в листинге 4.4.

Листинг 4.4. Код мидлета Slideshow

import javax.microedition.midlet.*;

import javax.microedition.lcdui.*;

public class SlideshowMIDlet extends MIDlet implements CommandListener {

private SSCanvas canvas; //Использование настраиваемого холста – это уникальный фрагмент кода мидлета

public void startApp() {

if (canvas == null) {

canvas = new SSCanvas(Display.getDisplay(this));

Command exitCommand = new Command("Exit", Command.EXIT, 0);

canvas.addCommand(exitCommand);

canvas.setCommandListener(this);

}

// Start up the canvas

canvas.start();

}

public void pauseApp() {}

public void destroyApp(boolean unconditional) {}

public void commandAction(Command c, Displayable s) {

if (c.getCommandType() == Command.EXIT) {

destroyApp(true);

notifyDestroyed();

}

}

}


Как вы, вероятно, заметили, этот код практически идентичен классу мидлета Olympics, созданного нами ранее. Это подтверждает, что большая часть функций возложена на класс, производный от Canvas.

Тестирование готового приложения

После того как мидлет Slideshow собран, вы непременно захотите протестировать его в J2ME-эмуляторе. Не забудьте, что кнопки Влево и Вправо используются для навигации по слайд-шоу. На рис. 4.6 показан фрагмент работы мидлета Slideshow.

Рис. 4.6. Мидлет Slideshow реализует интерактивное слайд-шоу, в котором выводятся изображение и описание

Резюме

В этой главе вы узнали многое о программировании графики в MIDP API. Большая часть главы была посвящена объектам Graphics и Canvas, которые просты в применении. Вы познакомились с координатными системами и их использованием в мидлетах. Затем вы научились рисовать графические примитивы, настраивать шрифты и применять анкеры. Наконец, глава завершилась рассмотрением вывода изображений. Но, вероятно, самое важное в этой главе – написание двух примеров программ, демонстрирующих все приобретенные навыки работы с графикой.

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

Заключение

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

1. вызовите методы getWidth() и getHeight() и определите размеры эк рана;

2. вычислите диаметр каждой окружности символа как одну четвертую высоты экрана устройства;

3. измените код метода paint() так, чтобы окружности имели вычисленный диаметр и располагались соответственно, все изображение должно быть центрировано;

4. соберите и запустите мидлет в эмуляторе J2ME. Измените эмулируемое устройство на QwertyDevice и посмотрите, как символ будет изображен на экране.

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

Глава 5

Использование спрайтовой анимации

Архив Аркад

Единогласно признанная классикой почти всеми поклонниками аркад игра Battlezone была выпущена в 1980 году компанией Atari. В этой игре применяется фреймовая векторная графика. Игрок видел картинку через видоискатель, имитировавший наблюдательный прибор танка. Трехмерная перспектива в Battlezone была новинкой для игроков во время игрового бума. Изначально Battlezone разрабатывалась для военных как тренажер боевой машины Bradley, бывшей на вооружении у США. Хотя официального подтверждения этому нет ни со стороны Atari, ни со стороны военных, военная версия игры со специально оборудованным местом была выставлена в Atari годы спустя после зенита славы Battlezone.

Сердце почти любой компьютерной графики – это анимация. Без анимации не было бы движения, и мы бы так и играли в текстовые и примитивные игры. В этой главе вы познакомитесь с основополагающими концепциями анимации в мобильных играх, спрайтовой анимацией. После того как вы познакомитесь с теорией создания анимации, вы узнаете, как использовать спрайты в MIDP 2.0 API. Речь о спрайтовой анимации пойдет и далее, однако в этой главе вы познакомитесь с основами анимации в мобильных играх и узнаете, что необходимо для ее реализации.

Из этой главы вы узнаете:

► об основах анимации и принципах ее работы;

► об отличиях 2D– и BD-анимаций;

► о разных типах 2D-анимации, о том когда целесообразно применять конкретный метод;

► как использовать MIDP-класс Sprite для разработки анимационных мидлетов;

► как применять класс GameCanvas для обеспечения плавной анимации в мидлете.

Понятие об анимации

Перед тем как начать изучение анимации как части мобильной игры, необходимо уяснить основы. Давайте начнем с фундаментального вопроса: «Что такое анимация?» Если говорить просто, анимация – это иллюзия движения. Действительно ли вся анимация, которую вы видите, – всего лишь иллюзия? Именно так! И, вероятно, самая удивительная иллюзия, захватившая внимание человека задолго до появления компьютеров, – это телевидение. Когда вы смотрите телевизор, то видите множество вещей, передвигающихся по экрану. Но то, что вы воспринимаете как движение, – лишь трюк, разыгрываемый у вас на глазах.

Анимация и частота обновления кадров

В случае телевидения иллюзия движения создается за счет быстрой смены изображений, немного отличающихся друг от друга. Человек из-за низкой остроты зрения воспринимает эти изменения как движение. Наши глаза очень легко обмануть и заставить поверить в иллюзию анимации. Если говорить более подробно, то человеческий глаз воспринимает смену изображений как анимацию, если изменения происходят не менее 12 раз в секунду. Неудивительно, что это значение минимально для большей части компьютерной анимации. Скорость анимации измеряется в кадрах в секунду (fps, frames per second).

Хотя 12 кадров в секунду – технически достаточная скорость, чтобы обмануть ваши глаза, такая анимация будет очень прерывистой. Следовательно, для создания более качественной анимации требуется более высокая частота обновления кадров. Например, телевидение работает на частоте 30 кадров в секунду. В кино вы смотрите фильмы, в которых частота смены кадров равна 24 кадрам в секунду. Очевидно, что этого вполне достаточно, чтобы привлечь ваше внимание и создать иллюзию движения.

В копилку Игрока

Разница в частоте обновления кадров на телевидении и в кино связана с техническими причинами, а не с организационными. Раньше частота обновления в фильмах составляла лишь 16 кадров в секунду, но, в конечном счете, стандартом была принята частота 24 кадра в секунду, что оказалось наиболее подходящей скоростью для воспроизведения звука. Американское телевидение, также известное как NTSC (National Television Standards Committee – Национальный комитет по телевизионным стандартам), за эталон времени выбрала частоту тока в электросети (60 Гц), таким образом была получена частота 30 кадров в секунду. Тот факт, что телевидение работает на более высокой частоте, означает, что для показа фильма по телевидению его необходимо конвертировать.

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

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

Шаг в направлении компьютерной анимации

Большинство методик, применяемых для создания компьютерной анимации, были заимствованы или основаны на аналогичных техниках создания анимации в мультфильмах. Классический подход при создании анимации – это создавать фон отдельно от движущихся объектов. Объекты анимации рисуются на отдельных целлулоидных листах так, чтобы их можно было разместить поверх фоновой картинки и перемещать независимо от нее. Этот тип анимации носит название буферной анимации (cel animation). Такая анимация помогает художникам сэкономить массу времени, необходимо лишь перерисовывать объекты, которые изменяют свою форму, размер или положение от фрейма к фрейму. Это объясняет, почему во многих мультфильмах хорошо проработано фоновое изображение, а герои столь просты. Спрайты, применяемые в компьютерных играх, о которых вы узнали чуть ранее в этой главе, – это те же самые традиционные движущиеся объекты в буферной анимации.

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

В копилку Игрока

Современные мультипликационные фильмы доказали, что компьютерную анимацию можно применять не только в компьютерных играх. Популярные мультфильмы, например, «История игрушек» (Toy Story), «Ледниковый период» (Ice Age), «Корпорация монстров»(Monsters, Inc.), «В поисках Немо» (Finding Nemo), – лучшие примеры того, как традиционные анимационные методы применяются на компьютерах. Вероятно, наиболее совершенная компьютерная анимация была создана для фильма «Последняя фантазия» (Final Fantasy) – это первый фильм, в котором компьютерная анимация применялась на протяжении всего фильма.

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

2D против 3D

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

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

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

Смесь 2D– и 3D-графики дает хорошие результаты в игре Doom, поскольку плоские изображения выглядят достаточно реалистично в 3D-окружении. Конечно, со времен Doom многое изменилось. Так, Quake и другие современные трехмерные игры этого жанра используют теперь трехмерные объекты.

Оставшаяся часть главы и книга в целом сфокусированы на рассмотрении 2D-анимации, поскольку она проста и более эффективна, а следовательно, лучше приспособлена для написания мобильных игр. Хорошая новость – вы можете создавать великолепные эффекты с использованием 2D-анимации.

Анализ 2D спрайтовой анимации

Хотя эта глава целиком посвящена спрайтовой анимации, необходимо понять основные типы анимации, используемые в программировании игр. Существует множество различных типов, каждый из которых целесообразно применять в определенных случаях. Рассматривая анимацию в контексте создания мобильных игр, я разбил ее на два типа: фреймовую (frame-based animation) и композиционную анимации (cast-based animation). С технической точки зрения, существует еще и третий тип анимации – анимация палитрой (palette animation), – анимация цвета объекта, однако он не является основным.

Фреймовая анимация

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

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

Рис. 5.1. При использовании фреймовой анимации для создания иллюзии движения изменяется весь фрейм


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

Композиционная анимация

Более совершенная методика создания анимации, используемая в большинстве игр, – это композиционная анимация, которая также известна как спрайтовая. При этом объект анимации – это графический объект, который может перемещаться независимо от фона. С этой точки зрения, вы можете быть немного озадачены применением термина «графический объект», когда речь идет о различных частях анимации. В данном случае графический объект – это объект, который логически может быть отделен от фона. Например, при создании анимации для космического шутера корабли пришельцев – это отдельные графические объекты, которые логически отделены от фона.

В копилку Игрока

Термин «композиционная анимация» (cast-based animation) происходит потому, что спрайт можно представить как часть композиции (изображения). Можно провести аналогию между компьютерной анимацией и театром. Представьте спрайт актером, а фон сценой, тогда о спрайтовой анимации можно думать как о театральном действе. Это не так далеко от истины, потому как цель театрального представления – развлечь зрителя, рассказывая какую-нибудь историю и разыгрывая спектакль. То же можно сказать и про спрайтовую анимацию, которая развлекает пользователя, повествуя историю.

Каждый графический объект композиционной анимации, называемый спрайтом, имеет определенное положение, которое может меняться с течением времени. Другими словами, спрайт может иметь скорость, которая и определяет, как изменяется его положение с течением времени. Почти все видеоигры используют спрайты. Например, каждый объект в классической игре Asteroids – это спрайт, перемещающийся независимо от фона. Несмотря на то, что в игре Asteroids применяется векторная графика, игровые объекты – спрайты. На рис. 5.2 показано, как композиционная графика упрощает пример с парашютистом, который вы видели в предыдущей главе.

Рис. 5.2. Композиционная анимация, графический объект может перемещаться независимо от фона, создавая эффект движения


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

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

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

Прозрачные объекты

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

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

Создание глубины с помощью Z-слоев

Часто возникает необходимость поместить одни спрайты поверх других. Например, в военной игре могут летать самолеты и сбрасывать бомбы. Если спрайт самолета будет пролетать над спрайтом танка, то, вероятно, вы захотите, чтобы самолет оказался поверх танка, и следовательно, танк оказался позади самолета. Чтобы решить эту проблему, вы можете определить для каждого спрайта глубину или Z-слой (Z-order).

Z-слой – это относительная глубина спрайта на экране. Глубина спрайта называется Z-слоем по аналогии с другим измерением, z-осью. Спрайт может передвигаться по экрану в осях XY. Аналогично z-ось определяет глубину экрана или то, как спрайты перекрывают друг друга. Иначе говоря, Z-слой определяет глубину спрайта на экране. Из-за того что используется третья ось, вы можете подумать, что такие спрайты объемны. Но это не так. Дело в том, что третья ось используется лишь для определения взаимного перекрытия спрайтов.

Совет Разработчику

Самый простой способ управлять Z-слоем в игре – это внимательно следить за тем, как вы создаете графику. К счастью, в MIDP API есть класс LayerManager, который упрощает управление множеством графических объектов (слоев) и их относительными Z-слоями. В главе 11 вы узнаете, как применять этот класс в мидлетах.

Чтобы вы лучше поняли, как работает Z-слой, давайте вернемся в старые добрые времена традиционной анимации. Вы уже знаете, что при создании традиционной анимации, например, мультфильмов Disney, для анимации объектов использовались целлулоидные листы, поскольку их можно было накладывать на фоновое изображение и перемещать независимо друг от друга. Такая анимация – ранняя версия спрайтовой анимации. Каждому целлулоидному листу соответствовал один Z-слой, определявший место листа в стопке. Если случалось так, что спрайт, который находится в верхних слоях, совпадал со спрайтом из нижних слоев, то он перекрывал его. Положение листов в стопке – это Z-слой, определяющий его видимость. То же самое относится и к спрайтам при использовании композиционной анимации, за исключением того, что Z-слои определяют порядок, в котором спрайты выводятся на экран, а не место в стопке листов.

Определение столкновений объектов

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

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

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

Рис. 5.3. Детектирование столкновений с помощью ограничивающих прямоугольников сводится к простой проверке их перекрытия


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

Рис. 5.4. Детектирование столкновений с помощью уменьшенных ограничивающих прямоугольников аналогично обычному методу проверки


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

Рис. 5.5. Детектирование столкновений с помощью данных спрайтовых изображений проверяет перекрытие особых пикселей двух спрайтов


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

Использование спрайтовой анимации в мобильных играх

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

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

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

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

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

Работа с классами Layer и Sprite

Я упоминал, что MIDP 2.0 API включает поддержку спрайтовой анимации. Два основных класса, которые делают спрайтовую анимацию возможной, – это Layer и Sprite. Класс Layer моделирует главный графический объект – слой (layer), который служит основой для спрайтов и прочих графических объектов игры. Каждый отдельный видимый элемент игры – это слой. С точки зрения программирования, класс Layer отслеживает такую информацию как положение, ширину и видимость элемента.

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

Совет Разработчику

Начальное положение слоя – (0,0), оно задается в системе координат объекта Graphics, передаваемого в метод paint().

Ниже перечислены методы класса Layer, которые очень важны для работы со спрайтами и слоями:

► getX() – возвращает координату X верхнего левого угла слоя;

► getY() – возвращает координату Y верхнего левого угла слоя;

► getWidth() – возвращает ширину слоя;

► getHeight() – возвращает значение высоты слоя;

► setPosition() – устанавливает координаты XY левого верхнего угла слоя;

► move() – переместить слой на заданное расстояние в осях XY;

► isVisible() – проверяет, видим ли слой;

► setVisible() – устанавливает свойства видимости;

► paint() – переопределяется в классах-потомках.

Класс Sprite построен на классе Layer, он реализует методы, необходимые для создания двухмерных графических объектов. В класс Sprite добавлены следующие основные функции:

► спрайты основаны на изображениях, они могут состоять из нескольких фреймов;

► изображения спрайта можно преобразовывать (поворачивать, отображать и т. п.);

► для спрайтов, состоящих из нескольких фреймов, можно точно определить последовательность отображения фреймов;

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

Вы видите, что класс Sprite предлагает массу возможностей для программирования графики в мобильных играх. В этой главе вы не затронете все указанные аспекты, но вскоре восполните этот пробел. Пока мы сосредоточимся на создании и основах работы со спрайтом. Чтобы создать спрайт из одного изображения, передайте созданный объект Image конструктору класса Sprite:


Sprite monsterSprite = new Sprite(Image createImage(«/monster.png»));


В этом примере изображение монстра используется как основа для создания спрайта. Проблема заключается в том, что если вы поместите этот код в мидлет, то получите сообщение компилятора об ошибке, поскольку исключение, вызываемое вводом-выводом, не обрабатывается. Это исключение может быть обработано с помощью метода createImage() в случае ошибки загрузки изображения. Ниже приведен код структуры try-catch, выполняющей это:


try {

monterSprite = new Sprite(image.createImage("/Monster.png");

monsterSprite.setPosition(0,0);

}

catch (IOException e) {

System.err.println("Failed loading image!");

}


Несмотря на то что класс Layer инициализирует положение каждого слоя в точке (0,0), полезно инициализировать положение каждого спрайта, как показано в коде. Когда вы загрузили спрайт и он готов к использованию, вы можете перемещать его по экрану, вызывая метод setPosition() или move(). Ниже объясняется, как это сделать:

1. пример использования метода setPosition() для центрирования спрайта на экране:


monterSprite.setPosition((getWidth – monsterSprite.getWidth()) / 2,

(getHeight – monsterSprite.getHeight()) / 2);


Этот метод для вычисления положения центра используют высоту и ширину холста и размеры спрайта.

2. перемещение спрайта работает несколько иначе – необходимо указать расстояния вдоль осей, на которые необходимо переместить спрайт:


monsterSprite.move(-5, 10);


В этом примере спрайт перемещается на 5 пикселей влево и 10 пикселей вниз. Отрицательные смещения задают перемещения влево или вверх, а положительные – вправо или вниз.

3. поскольку c каждым объектом класса Sprite ассоциировано изображение, то метод paint() рисует изображение в заданном месте:


monsterSprite.paint(g).


В этом коде предполагается, что у вас есть объект класса Graphics с именем g, такой объект обязательно должен присутствовать в любой игре.

Вы познакомились с основами спрайтовой анимации в MIDP API. Нам осталось только рассмотреть класс GameCanvas, специально предназначенный для анимации благодаря двойной буферной анимации.

Создание плавной анимации с помощью класса GameCanvas

Если бы вы попытались использовать все, что узнали о программировании спрайтовой анимации, и создали бы мидлет с использованием обычного класса Canvas,TO в результате получили бы прерывистую анимацию. Такой эффект возникает вследствие того, что перед отображением картинки экран очищается. Иначе говоря, при каждом перемещении объекты анимации стираются и перерисовываются. Поскольку отображение и стирание происходит непосредственно на экране, возникает эффект прерывности анимации. Для наглядности представьте себе фильм, в котором между двумя последовательными кадрами отображается белый экран. Несмотря на то что иллюзия движения будет создаваться по-прежнему, между фреймами будет выводиться пустой экран.

Вы можете решить эту проблему, используя методику, известную как «двойная буферизация». При двойной буферизации выполняется стирание и рисование на невидимом для пользователя экране. По окончании рисования результат выводится непосредственно на игровой экран. Поскольку видимая очистка экрана не выполняется, в результате вы получаете гладкую анимацию. На рис. 5.6 показана разница между традиционной однобуферной анимацией и анимацией с двойной буферизацией.

Рис. 5.6. Анимация с двойной буферизацией устраняет эффект прерывистости, возникающей при использовании однобуферной анимации


В копилку Игрока

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

На рис. 5.6 показано, как используется буфер в памяти для выполнения всех необходимых действий. Это может показаться хитрым приемом программирования, однако все делается очень просто (благодаря MIDP 2.0 API).

Кроме стандартного класса Canvas в MIDP API существует класс GameCanvas, поддерживающий графику с двойной буферизацией. Чтобы воспользоваться преимуществами класса GameCanvas, образуйте игровой класс холста от класса GameCanvas, после чего вы сможете работать с этим объектом в обычном режиме. Однако теперь все построения будут производиться в буфере. Чтобы вывести результат на экран, воспользуйтесь методом flushGraphics().

Давайте посмотрим, как работает буфер в памяти класса GameCanvas. Когда вы делаете что-либо с объектом Graphics, ассоциированным с холстом, все построения выполняются в буфере, на экране изменения не будут видны. Вызов метода flushGraphic() позволяет отобразить все, что было построено в памяти, на экране. При этом содержимое буфера не изменяется и не стирается, а просто выводится на экран.

В копилку Игрока

Когда вы создаете объект класса GameCanvas, при инициализации буфер заполняется пикселями белого цвета.

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

Построение программы UFO

Хотя можно привести массу примеров написания программы с применением спрайтовой анимации, UFO (НЛО) – актуален всегда. Если вы когда-нибудь столкнетесь с тем, что в вашей игре чего-то не хватает, добавьте неопознанный летающий объект – это помогает всегда! В этом разделе вы научитесь использовать возможности MIDP для создания мидлета, в котором неопознанный летающий объект будет перемещаться по экрану. Пример UFO демонстрирует основы спрайтовой анимации, показывает, как применять эту методику на практике. По мере изучения материала книги вы будете знакомиться с новыми более интересными возможностями классов анимации MIDP.

Конец ознакомительного фрагмента.