Создание простой сцены

Xtreme3D

Урок 2
Создание простой сцены

Уровень: начинающий
Версия Xtreme3D: 3.0.x
Автор урока: Gecko

Перед тем, как начать практические занятия, позвольте кое-что разъяснить. Xtreme3D - это библиотека динамической компоновки (DLL). А DLL - не что иное, как скомпилированный набор инструкций, написанных на каком-либо языке программирования, с целью их использования программами на любом другом языке, поддерживающим DLL. Xtreme3D, например, написан на Delphi, и в нем содержится около 580 таких инструкций. Назовем из для простоты функциями. При помощи интерфейса Game Maker мы можем создать скрипты, каждый из которых будет вызывать определенную функцию из библиотеки. После этого можно будет вызывать функции через код GML по названиям скриптов. Вот типичный пример функции Xtreme3D:
ObjectSetMaterial(object,'material'). Некоторые функции возвращают различные числовые и строковые значения. Например, при создании объекта возвращается его идентификатор (id), который необходимо записать в переменную для дальнейшей работы с объектом.

Следующий важный момент: константы. Многие функции используют в качестве аргументов числовые коды, и не всегда легко запомнить, какой код нужен для достижения нужного эффекта или включения нужного режима. Поэтому можно вместо кодов вводить соответствующие названия констант (список констант и их числовых значений можно увидеть в Global Game Settings, вкладка Constants). По негласной традиции константы Xtreme3D выглядят так: tmmCubeMapReflection. Строчные буквы в начале (tmm) обозначают свойство, к которому относится константа. В данном случае это TextureMappingMode, а функция, задающая его - MaterialSetTextureMappingMode('material',tmm), где вместо tmm подставляется нужная tmm-константа.
Помните, что константы являются частью Game Maker/GML и к самой Xtreme3D.dll они отношения не имеют.

Для начала работы вам понадобится файл *.gm6 (или *.gmk для Game Maker 7), с готовым набором функций и констант Xtreme3D. Такой файл вы можете взять из официального дистрибутива движка. Для подготовки к работе достаточно удалить из него все объекты. Назовем его условно project.gm6. Скопируйте его в отдельную папку и добавьте туда же файлы xtreme3d.dll и ode.dll.

Откройте project.gm6. Создайте новый объект Game Maker и назовите его o_engine. Добавьте событие Create и перетащите действие Execute a piece of code со вкладки Control. Если вы уже работали с GML, проблем не будет. Если нет, настоятельно рекомендуем оставить пока Xtreme3D и изучить язык на встроенной графике Game Maker.

Следующий код загружает функции из библиотеки xtreme3d.dll в память и запускает работу движка:

dll_init('xtreme3d.dll');
EngineCreate();

Идем дальше:

view = ViewerCreate(window_handle, 0, 0, 640, 480);
ViewerSetLighting(view, 1);

Для того, чтобы наблюдать что-либо в окне с игрой, понадобится Вид (Viewer). Вид - это прямоугольник, в котором происходит отрисовка сцены Xtreme3D. Все, что за пределами этого прямоугольника, "принадлежит" встроенной графике Game Maker. Мы создали Вид разрешением 640х480, под размер окна, так что графики Game Maker и вовсе не будет видно. Позиция нашего Вида на экране - (0,0). По существу, это координата левого верхнего угла Вида, относительно левого верхнего угла окна.
Также в функцию ViewerCreate передается window_handle(), функция GML, возвращающая идентификатор главного окна игры. Таким образом, Вид будет "привязан" к окну игры Game Maker, что нам и нужно.
Как ни странно, Вид - это тоже объект, поэтому при создании мы заносим его идентификатор в переменную, в нашем случае - view. Мы можем использовать идентификатор Вида для изменения его свойств. В данный момент нас интересует только одно - использование освещения (ViewerSetLighting). Если выключить освещение (0), все объекты будут выглядеть плоскими и необъемными. Поэтому мы его включаем (1). Правда, для того, чтобы освещение работало, надо еще создать источники света:

light = LightCreate(lsOmni, 0);
ObjectSetPosition(light, 0, 18, 0);

Функция LightCreate создает источник света и возвращает его идентификатор, так как свет - это тоже объект. В Xtreme3D есть три типа источников света - точечный (константа lsOmni), направленный (lsSpot) и параллельный (lsParallel). Точечный излучает свет равномерно во всех направлениях (как, например, лампочка), направленный светит в пределах конуса (как фонарик), параллельный испускает параллельные лучи в направлении одной оси (имитация солнечного света). Мы можем назначить источнику света родителя, но, поскольку никаких сценических объектов у нас пока нет, подставляем вместо родителя 0. Создав точечный источник света, можно уточнить его положение в пространстве - точка (0,18,0).

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

global.back = DummycubeCreate(0);
global.scene = DummycubeCreate(0);
global.front = DummycubeCreate(0);

Функция DummycubeCreate создает Манекен (Dummycube) и возвращает его идентификатор. Объект, носящий это забавное название, играет важную роль в формировании иерархии. Манекен невидим, это как бы объект-призрак. Но, в то же время, он обладает всеми обычными свойствами объектов, которые мы рассмотрели в предыдущей главе - координатами в пространстве, векторами Direction, Up, Left и т.д. Его можно свободно перемещать, вращать и масштабировать. Манекен может иметь родителя и потомков. В данном случае мы создали три корневых Манекена. Корневых - потому что выше их в иерархии ничего не будет. Все остальные сценические объекты будут потомками этих трех Манекенов:
global.back - родитель для объектов на заднем плане (небо, фон и т.д.)
global.scene - родитель для объектов на сценическом плане (все трехмерные объекты)
global.front - родитель для объектов на экране (спрайты, текст и т.д.)
Важно соблюсти именно этот порядок создания Манекенов - сначала задний план, потом сцена, потом экран. Это необходимо для того, чтобы движок мог отрисовать объекты в нужном порядке. Этот порядок называется сортировкой: все объекты отрисовываются в том порядке, в котором были созданы, они сами или их родители.

Создадим первый объект сценического плана - плоскость:

plane = PlaneCreate(0,64,64,8,8,global.scene);
ObjectPitch(plane,90);

Плоскость - один из примитивов, простых геометрических тел, которые генерируются движком. Функция PlaneCreate создает плоскость и возвращает ее идентификатор. Рассмотрим ее аргументы:
0 - определяет, нужно ли представить плоскость одним квадратом (сокращенно - квадом), или разбить на несколько; нам для красивого освещения нужно несколько, поэтому указываем 0;
64,64 - размер плоскости;
8,8 - количество квадов. В общей сложности плоскость будет разбита на 8 * 8 = 64 квадов;
global.scene - родитель.
Созданная нами плоскость по умолчанию вертикальна, поэтому надо ее повернуть на 90 градусов по оси X. Если помните, поворот по оси X называется Pitch, поэтому нам нужна функция ObjectPitch.

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

camPos = DummycubeCreate(global.scene);
ObjectSetPosition(camPos, 0, 10, 0);
camera=CameraCreate(camPos);
ViewerSetCamera(view, camera);

Перед созданием камеры мы создали для нее родителя - еще один Манекен. Это сделано для того, чтобы сама камера могла свободно вращаться, а ее движение контролировалось через этот Манекен.
Функция ViewerSetCamera указывает Виду, какую Камеру использовать для передачи изображения.

Как видите, пока все достаточно просто. Осталось только последнее:

set_automatic_draw(0);

Этой функцией мы отключаем автоматическую отрисовку графики Game Maker - все равно Вид ее полностью перекроет, нет смысла тратить ресурсы системы на ее обработку.

Сцену мы создали, осталось заставить ее работать. Добавьте событие Step и в нем добавьте следующий код:

if keyboard_check(vk_left) ObjectTurn(camPos,-2);
if keyboard_check(vk_right) ObjectTurn(camPos,2);
if keyboard_check(vk_up) ObjectMove(camPos,-1);
if keyboard_check(vk_down) ObjectMove(camPos,1);

Теперь, когда пользователь нажмет клавишу, скажем, "Вверх", Камера будет двигаться вперед (соответственно и для клавиши "Назад"). Для поворота Камеры используются клавиши "Влево" и "Вправо". Поворот осуществляется по оси Y (Turn), поэтому используется функция ObjectTurn. Заметьте, что для трансформации мы используем не саму Камеру, а ее родителя. Благодаря этому мы потом сможем вращать Камеру при помощи мыши.

Update(1.0/room_speed);
ViewerRender(view);

Эти две функции надо обязательно вызвать, иначе движок будет "парализован". Update обновляет состояние объектов сцены, ViewerRender совершает отрисовку указанного Вида. В функцию Update необходимо передать шаг времени для обновления анимации. Он измеряется в секундах и может быть равен времени между двумя кадрами рендеринга. Обычно в Game Maker это время ограничивается в настройках комнаты - задается так называемая "скорость комнаты", максимальная кадровая частота, измеряемая в кадрах в секунду. Обычно ее устанавливают равой 60 - это значение соответствует частоте обновления монитора. Мы можем вычислить временной промежуток между кадрами, поделив единицу (1 секунду) на это значение.

Это все! Теперь можно поместить наш объект o_engine в комнате и запускать.



Поздравляю, вы создали свою первую рабочую программу Xtreme3D! Вот ее полный исходный код:

В событии Create:

dll_init();
EngineCreate(window_handle());
view = ViewerCreate(0, 0, 640, 480);
ViewerSetLighting(view, 1);
light=LightCreate(lsOmni, 0);
ObjectSetPosition(light, 0, 18, 0);
global.back = DummycubeCreate(0);
global.scene = DummycubeCreate(0);
global.front = DummycubeCreate(0);
plane = PlaneCreate(0, 64, 64, 8, 8, global.scene);
ObjectPitch(plane, 90);
camPos = DummycubeCreate(global.scene);
ObjectSetPosition(camPos, 0, 10, 0);
camera = CameraCreate(camPos);
ViewerSetCamera(view, camera);
set_automatic_draw(0);

В событии Step:

if keyboard_check(vk_left) ObjectTurn(camPos, -2);
if keyboard_check(vk_right) ObjectTurn(camPos, 2);
if keyboard_check(vk_up) ObjectMove(camPos, -1);
if keyboard_check(vk_down) ObjectMove(camPos, 1);
Update();
ViewerRender(view);