Главная » Программирование игр под Android » Простая солнечная система, центром которой является ящик – РАЗРАБОТКА ИГР ДЛЯ ОС ANDROID

0

Создадим небольшой пример, очень простую солнечную систему, центром которой будет являться ящик. Допустим, имеется один ящик, расположенный в точке (0; 0; -6) системы координат создаваемого мира. Вокруг этого ящика-солнца на расстоянии, равном 3 единицы, мы хотим поместить ящик-планету, вращающийся вокруг солнца. Планета должна быть меньше солнца, выберем для нее размер 0,2 единицы. Вокруг ящика-планеты поместим ящик-спутник. Расстояние между планетой и спутником будет равно 1 единице, а ящик-спутник будет уменьшен до 0,1 единицы. Все объекты вращаются вокруг их относительных предков в плоскости xz, а также вокруг собственных осей у. На рис. 10.14 показан примерный вид этой модели.

Рис. 10.14. Наша солнечная система

Класс HierarchicalObject

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

Класс Hi erarchi cal Object должен обновлять свои углы поворота и углы поворота потомков, а также отрисовывать себя и всех своих потомков. Этот процесс рекурсивен, поскольку каждый потомок отрисовывает собственных потомков. Мы будем использовать методы gl PushMatriх и gl PopMatri х для сохранения преобразований, примененных к предкам, чтобы их потомки могли перемещаться вместе с ними. Код этого класса показан в листинге 10.7.

Листинг 10.7. Класс HierarchicalObject.java, представление объекта внутри системы ящика

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

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

Конструктор просто получает экземпляр класса Vertices3, а также булеву переменную, определяющую, имеет ли объект предка.

В методе update сначала обновляются члены rotationY и rotationParent. Каждый объект будет поворачиваться на 45° в секунду вокруг себя и на 20° в секунду вокруг своего предка. Этот метод будет также вызываться рекурсивно для каждого потомка объекта.

Метод render является самым интересным. Первое, что в нем происходит, – текущая ВС модельно-видовой матрицы, которая станет активной извне объекта, помещается глубже в стек. Поскольку метод рекурсивен, таким образом сохранятся все преобразования предков.

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

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

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

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

ПРИМЕЧАНИЕ

Позицию объекта, представляемого классом HierarchicalObject, следует хранить в виде вектора для более простой работы с ним. Однако нам следует еще написать класс Vector3.

Собираем все воедино

Используем только что созданный нами класс HierarchicalObject в подходящей программе. Для этого я просто скопировал весь код примера CubeTest, в котором также есть метод createCube. Его мы используем повторно. Я переименовал класс HierarchyTest, а также CubeScreen в HierarchyScreen. Нужно создать иерархию объектов и вызвать методы HierarchicalObject .update и HierarchicalObject. render в подходящих местах программы. В листинге 10.8 приведены самые важные фрагменты примера Hi erarchyTest.

Листинг 10.8. Фрагменты класса HierarchyTest.Java: реализация простой иерархической системы

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

В конструкторе инициализируется наша иерархическая система. Сначала загружается текстура и создается сеть куба, которая будет использована всеми объектами. Далее создается солнце. У него нет предка, оно находится в точке (0; 0; -5) относительно начала системы координат создаваемого мира (места, где располагается виртуальная камера). Далее создается ящик-планета, который вращается вокруг солнца. Он находится в точке (0; 0; 3) относительно солнца и имеет масштаб, равный 0,2. Поскольку длина грани ящика в пространстве моделей равна 1, после применения коэффициента масштаба ящик будет отрисован с гранью, длина которой равна 0,2 единицы. Важный момент – планета добавляется в качестве потомка солнца. Для спутника также будет осуществлено что-то похожее. Он находится в позиции (0; 0; 1) относительно планеты и имеет масштаб, равный 0,1 единицы. Он добавляется к планете в качестве потомка. Результат инициализации вы можете увидеть на рис. 10.14, имеющем такую же систему координат.

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

Наконец, рассмотрим метод render. Начнем с простой инициализации окна просмотра и очистки буферов цвета и глубины. Кроме того, инициализируется матрица перспективной проекции, а также единичная матрица загружается в качестве видовой матрицы OpenGL ES. Последующий вызов метода glTransl atef  довольно интересен: это сместит солнечную систему вниз по оси у на 2 единицы. Таким образом мы сможем посмотреть на систему сверху. Это действие можно рассматривать и как перемещение камеры на 2 единицы вверх. Такая интерпретация является ключевой для выбора подходящей системы камер.

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

Рис. 10.15. Наша солнечная система в действии

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

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

ПРИМЕЧАНИЕ

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

 

Простая система камер

В последнем примере мы увидели подсказку, как можно реализовать систему камер в 3D.

Мы использовали метод gl Trans1atef , чтобы переместить весь мир вниз на две единицы по оси у. Поскольку расположение камеры фиксировано (она находится в начале координат и обращена вдоль отрицательной половины оси z), такой подход создает впечатление того, что это камера была передвинута на 2 единицы. Координаты всех объектов по оси у по-прежнему равны 0.

Это очень похоже на выражение Если гора не идет к Магомету, Магомет идет к горе. Вместо перемещения камеры мы передвинули весь мир. Предположим, что камеру необходимо расположить в позиции (10; 4; 2). Все, что для этого нужно, – использовать метод glTranslatef  следующим образом:

Если же нужно повернуть камеру вокруг оси у на 45°, этот метод следует вызвать именно так:

Можно совместить эти два метода, как мы поступаем для нормальных объектов:

Секрет заключается в том, что аргументы метода преобразования инвертируются. Вспомним предыдущий пример. Мы знаем, что настоящая камера обречена находиться в начале координат и смотреть в одну сторону. Применяя обратные преобразования, мы изменяем картинку, отображаемую камерой. Использование виртуальной камеры, повернутой на 45°, является равнозначным применению фиксированной камеры и повороту мира на -45°. Это же верно и для параллельного переноса. Виртуальная камера должна быть помещена в точку (10; 4; 2). Но поскольку реальная камера всегда находится в начале координат, нам нужно лишь перенести все объекты мира используя инвертированный вектор этой позиции, равный (-10; -4; -2).

Если мы изменим следующие три строки метода present  предыдущего примера:

- на эти четыре:

то получим результат, показанный на рис. 10.16.

По задумке наша камера теперь находится в точке (0; 3; 0) и смотрит вниз на сцену под углом -45° (аналогично повороту камеры на -45° вокруг оси х). На рис. 10.17 показана сцена, отображаемая из новой точки.

Можно определить очень простую камеру с четырьмя атрибутами: позиция в пространстве мира; поворот вокруг оси х – аналогично наклону головы вверх и вниз; О поворот вокруг оси у – аналогично повороту головы влево и вправо; О поворот вокруг оси z – аналогично наклону головы влево и вправо.

Рис. 10.16. Смотрим на мир сверху из точки (0; 3; 0)

Рис. 10.17. Размещение и ориентация камеры

Основываясь на этих атрибутах, мы можем использовать методы OpenGL ES для создания матрицы камеры. Такая камера называется камерой поворота на углы Эйлера. Множество игр жанра FPS используют подобную камеру для эмуляции поворота головы. Обычно значение координаты по оси z не изменяется, в отличие от координат по осям х и у. Порядок, в котором применяются преобразования, очень важен. В играх жанра FPS сначала производится поворот по оси х, а затем по оси у:

Такая упрощенная модель используется во многих играх. Если бы мы применяли преобразование и по оси z, то смогли бы наблюдать эффект, называемый шарнирный замок (gimbal lock). Этот эффект отменит один из поворотов, основываясь на конкретной конфигурации.

ПРИМЕЧАНИЕ

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

Второй подход к созданию очень простой системы камер – использование метода GLU. glLookAtО.

Как и метод GLU.gluPerspectiveO, он умножит текущую активную матрицу на матрицу преобразования. В этом случае таковой матрицей является матрица камер, которая преобразует мир:

gl – это просто экземпляр класса GL10, который используется для отрисовки;

еуех, еуеу и eyez – определяют позицию камеры в мире;

centerx, centery и centerz – задают точку мира, на которую смотрит камера;

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

Верхний вектор обычно устанавливается равным (0; 1; 0), даже если это и не совсем верно. Метод gl uLookAt  может заново нормализовать данный вектор в большинстве случаев. На рис. 10.18 показана наша сцена, камера которой располагается в точке (3; 3; 0) и смотрит на точку (0; 0; -5), как и действительный верхний вектор.

Мы можем заменить код метода HierarchyScreen.presentO, который изменили ранее, следующим сниппетом:

Рис. 10.18. Камера находится в позиции (3; 3; 0) и смотрит в точку (0; 0; -3)

В этот раз я также закомментировал вызов метода sun. update, поэтому иерархия будет выглядеть так, как и на рис. 10.18. На рис. 10.19 показан результат использования камеры.

Рис. 10.19. Камера в действии

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

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

Подводя итог

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

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

По теме:

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