Определение столкновений объектов
Нельзя завершить разговор об анимации, не рассмотрев вопрос о детектировании столкновений объектов. Определение столкновений – это метод детектирования столкновения спрайтов. Хотя этот метод напрямую не связан с созданием иллюзии движения, он тесно связан со спрайтовой анимацией и очень важен для игр.Детектирование столкновений используется для определения физического взаимодействия друг с другом. Например, в игре Asteroids, если спрайт корабля сталкивается со спрайтом астероида, корабль разрушается и происходит взрыв. Детектирование столкновений – это механизм, встроенный для проверки столкновения корабля и астероида. Вероятно, вы подумаете, что это несложно. Нужно просто знать их местоположение на экране и проверить, совпадают ли они, не так ли? Все именно так, но подумайте, сколько проверок необходимо осуществить, если перемещается большое число спрайтов, каждый из которых нужно проверить на столкновение. Нетрудно представить, насколько сложной становится задача.
Неудивительно, что существует множество различных методик определения столкновений. Самый простой способ – это сравнить ограничивающие прямоугольники спрайтов. Этот метод весьма эффективен, но если ваши спрайты непрямоугольной формы, то столкновения будут определяться с погрешностями. Углы прямоугольников могут перекрываться, и таким образом будет определено столкновение, в то время как сами изображения не перекрываются. Чем меньше форма объекта похожа на прямоугольник, тем больше погрешность определения столкновения. На рис. 5.3 показано, как работает этот метод определения столкновений.
Рис. 5.3. Детектирование столкновений с помощью ограничивающих прямоугольников сводится к простой проверке их перекрытия
На рисунке перекрывающиеся области затемнены. Вы можете видеть, насколько неточен этот метод, если объекты непрямоугольной формы. Улучшить эту технику можно, немного уменьшив размер ограничивающего прямоугольника, тем самым снижая ошибку. Этот метод дает небольшое улучшение, однако он может повлечь за собой появление другой ошибки, не определяя истинное столкновение спрайтов. На рис. 5.4 показано, как уменьшение ограничивающего прямоугольника может уменьшить ошибку определения столкновения объектов. Этот метод эффективен ровно настолько, насколько эффективен метод, применяющий обычные ограничивающие прямоугольники, поскольку в обоих случаях проверяется наложение прямоугольников.
Рис. 5.4. Детектирование столкновений с помощью уменьшенных ограничивающих прямоугольников аналогично обычному методу проверки
Более точная техника детектирования столкновений основана на данных изображения спрайта. В ее основе лежит проверка перекрытия прозрачных частей спрайтовых изображений. Вы получаете сообщение о столкновении только в том случае, если спрайтовые изображения перекрываются. Это идеальный метод детектирования столкновений, поскольку он позволяет объектам произвольной формы перемещаться относительно друг друга без ошибок. На рис. 5.5 показано детектирование столкновений с помощью данных спрайтовых изображений.
Рис. 5.5. Детектирование столкновений с помощью данных спрайтовых изображений проверяет перекрытие особых пикселей двух спрайтов
К сожалению, техника, показанная на рис. 5.5, требует больших ресурсов мобильного устройства по сравнению с методикой определения столкновений с помощью ограничивающих прямоугольников и может существенно замедлить скорость выполнения игры. Какой метод определения столкновений выбрать, зависит от конкретной игры, насколько в ней важно точное детектирование, и какой объем необходимых ресурсов не приведет к снижению производительности. Вы узнаете, что для большинства игр целесообразно применять детектирование столкновений с помощью уменьшенных ограничивающих прямоугольников.
Использование спрайтовой анимации в мобильных играх
Теперь, когда вы имеете общее представление об основных типах анимации, вы, вероятно, хотите узнать, какой из них используется в мобильных играх. Я уже несколько предвосхитил события, сказав, что композиционная анимация наиболее эффективна и дает большую свободу действий, чем какой-либо другой метод. Но в реальности в большинстве игр применяется комбинация двух анимационных техник. Каждая из этих техник дает вам возможности, которые трудно применить, не используя их в совокупности.
Хорошим примером того, что в играх необходимо применять более одного метода создания анимации, может служить имитация ходьбы человека. Очевидно, что для имитации передвижения человека по ландшафту потребуется изменять положение его тела. Однако если вы больше ничего не сделаете, создастся ощущение, что человек скользит вдоль экрана, поскольку он не будет совершать никаких движений, имитирующих ходьбу. Чтобы эффективно имитировать походку человека, необходимо двигать его руки и ноги, как это происходит в реальности.
Для того чтобы сделать добиться этого эффекта, необходимо использовать фреймовую анимацию, поскольку вам необходимо показать ряд фреймов, имитирующих движения руками и ногами. В итоге вы получите объект, который может двигаться и изменять свой вид. При этом сочетаются две различные техники анимации.
Спрайты невероятно важны практически во всех двухмерных играх, поскольку они предоставляют простые, но очень эффективные средства имитации движения, а также позволяют объектам взаимодействовать друг с другом. Моделируя игровые объекты спрайтами, вы можете создавать интересные игры, в которых различные объекты могут взаимодействовать друг с другом. Самый простой пример игры, в которой применяются спрайты, – это игра Pong, состоящая из трех спрайтов: мяча и двух платформ (вертикальных полос) вдоль вертикальных сторон экрана. Все эти объекты должны быть моделированы спрайтами, поскольку они перемещаются и взаимодействуют друг с другом. Мяч летает по полю и ударяется о платформы, управляемые двумя игроками.
С усложнением игр роль спрайтов несколько изменилась, но их значимость только возросла. Например, в танковой игре спрайты целесообразно использовать для моделирования танков и выпускаемых снарядов. Однако вы можете использовать спрайты и для моделирования неподвижных объектов, например, стен и зданий. Несмотря на то что неподвижные объекты статичны, они только выигрывают от того, что моделируются спрайтами, потому как вы можете определить столкновение танка с такими объектами и ограничить перемещения танка. Аналогично, если пуля попадает в здание, то оно может быть разрушено, или пуля может рикошетом отлететь от него под определенным углом. Таким образом, моделируя здание спрайтом, вы можете определить попадание пули в здание и выполнить соответствующие действия.
Хорошим примером того, что в играх необходимо применять более одного метода создания анимации, может служить имитация ходьбы человека. Очевидно, что для имитации передвижения человека по ландшафту потребуется изменять положение его тела. Однако если вы больше ничего не сделаете, создастся ощущение, что человек скользит вдоль экрана, поскольку он не будет совершать никаких движений, имитирующих ходьбу. Чтобы эффективно имитировать походку человека, необходимо двигать его руки и ноги, как это происходит в реальности.
Для того чтобы сделать добиться этого эффекта, необходимо использовать фреймовую анимацию, поскольку вам необходимо показать ряд фреймов, имитирующих движения руками и ногами. В итоге вы получите объект, который может двигаться и изменять свой вид. При этом сочетаются две различные техники анимации.
Спрайты невероятно важны практически во всех двухмерных играх, поскольку они предоставляют простые, но очень эффективные средства имитации движения, а также позволяют объектам взаимодействовать друг с другом. Моделируя игровые объекты спрайтами, вы можете создавать интересные игры, в которых различные объекты могут взаимодействовать друг с другом. Самый простой пример игры, в которой применяются спрайты, – это игра Pong, состоящая из трех спрайтов: мяча и двух платформ (вертикальных полос) вдоль вертикальных сторон экрана. Все эти объекты должны быть моделированы спрайтами, поскольку они перемещаются и взаимодействуют друг с другом. Мяч летает по полю и ударяется о платформы, управляемые двумя игроками.
С усложнением игр роль спрайтов несколько изменилась, но их значимость только возросла. Например, в танковой игре спрайты целесообразно использовать для моделирования танков и выпускаемых снарядов. Однако вы можете использовать спрайты и для моделирования неподвижных объектов, например, стен и зданий. Несмотря на то что неподвижные объекты статичны, они только выигрывают от того, что моделируются спрайтами, потому как вы можете определить столкновение танка с такими объектами и ограничить перемещения танка. Аналогично, если пуля попадает в здание, то оно может быть разрушено, или пуля может рикошетом отлететь от него под определенным углом. Таким образом, моделируя здание спрайтом, вы можете определить попадание пули в здание и выполнить соответствующие действия.
Работа с классами Layer и Sprite
Я упоминал, что MIDP 2.0 API включает поддержку спрайтовой анимации. Два основных класса, которые делают спрайтовую анимацию возможной, – это Layer и Sprite. Класс Layer моделирует главный графический объект – слой (layer), который служит основой для спрайтов и прочих графических объектов игры. Каждый отдельный видимый элемент игры – это слой. С точки зрения программирования, класс Layer отслеживает такую информацию как положение, ширину и видимость элемента.
Важно отметить, что класс Layer – это абстрактный класс, поэтому напрямую создавать экземпляры этого класса нельзя. Но вы можете создавать экземпляры классов, производных от Layer, например, Sprite, или его производных. Производные классы от 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, специально предназначенный для анимации благодаря двойной буферной анимации.
Важно отметить, что класс Layer – это абстрактный класс, поэтому напрямую создавать экземпляры этого класса нельзя. Но вы можете создавать экземпляры классов, производных от Layer, например, Sprite, или его производных. Производные классы от Layer должны реализовывать свой собственный метод, чтобы их можно было нарисовать.
Совет РазработчикуНиже перечислены методы класса Layer, которые очень важны для работы со спрайтами и слоями:
Начальное положение слоя – (0,0), оно задается в системе координат объекта Graphics, передаваемого в метод paint().
► 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. Анимация с двойной буферизацией устраняет эффект прерывистости, возникающей при использовании однобуферной анимации
Кроме стандартного класса Canvas в MIDP API существует класс GameCanvas, поддерживающий графику с двойной буферизацией. Чтобы воспользоваться преимуществами класса GameCanvas, образуйте игровой класс холста от класса GameCanvas, после чего вы сможете работать с этим объектом в обычном режиме. Однако теперь все построения будут производиться в буфере. Чтобы вывести результат на экран, воспользуйтесь методом flushGraphics().
Давайте посмотрим, как работает буфер в памяти класса GameCanvas. Когда вы делаете что-либо с объектом Graphics, ассоциированным с холстом, все построения выполняются в буфере, на экране изменения не будут видны. Вызов метода flushGraphic() позволяет отобразить все, что было построено в памяти, на экране. При этом содержимое буфера не изменяется и не стирается, а просто выводится на экран.
Вы можете решить эту проблему, используя методику, известную как «двойная буферизация». При двойной буферизации выполняется стирание и рисование на невидимом для пользователя экране. По окончании рисования результат выводится непосредственно на игровой экран. Поскольку видимая очистка экрана не выполняется, в результате вы получаете гладкую анимацию. На рис. 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.
Конец бесплатного ознакомительного фрагмента