Наконец-то настала пора заняться трехмерной версией движка ЛКИ-Creator. Ее вы найдете на нашем диске, в разделе «Игра своими руками» и на сайте.
Для начала разберем в подробностях классический пример — тот самый, с плывущим дельфином. На нем мы научимся делать вот что:
строить оконное или полноэкранное трехмерное приложение;
задавать фон;
загружать в память трехмерные текстурированные модели, созданные в 3D Studio Max;
отображать их;
двигать и поворачивать объекты;
анимировать объекты.
В результате мы должны получить плывущего над морским дном по кругу дельфина, поворачивающегося и шевелящего хвостом. Пример в конечном счете займет всего пару-тройку десятков строк, но в нем «уместится» весь этот набор важных для нас механизмов.
Структуру ЛКИ-Creator 3D мы постарались сделать похожей на структуру двумерного движка, но есть некоторые существенные изменения, вызванные особенностями трехмерной графики.
Старые статьи раздела, включая микроучебник Delphi, вы можете найти на диске журнала.
Подготовка к работе
Как обычно, нам надо начать с установки пакета. На этот раз необязательно удалять старую версию: просто откройте файл LKI.dpk и добавьте в него новый модуль — LKI3dEngine.pas из распакованного файла ЛКИ Creator 3D, после чего скомпилируйте пакет.
На закладке LKI добавился всего один новый компонент — TLKIMeshList. Это потому, что главный класс движка — трехмерный игровой экран — из компонента стал формой. Но об этом чуть ниже.
Кроме того, распакуем тестовый пример Dolphin.
В случае ошибки: если ваша программа не обнаруживает каких-то из нужных файлов, войдите в меню Project — Options — Directories/Conditionals и явно укажите в SearcHPatHкаталоги двумерного и трехмерного движков.
Форма — TLKI3dForm
Первое, что мы делаем, если хотим собрать приложение на ЛКИ-Creator 3D, — создав новый проект, заменяем в его главной форме строку
type TForm1 = class(TForm)
на строку
type TForm1 = class(TLKI3dForm)
Кроме того, чтобы компилятор не выдал ошибки, нам надо указать в списке используемых модулей Uses нашу библиотеку — LKI3dEngine.
Итак, главный трехмерный экран — не компонент, а форма, то есть окно, на котором строится программа. Это ускоряет работу наших приложений, делает более корректным переход между оконным и полноэкранным режимами и дает еще ряд преимуществ. Но есть у такого решения и неудобства:
Свойства трехмерного экрана нельзя редактировать инспектором объектов (Object Inspector). В принципе, разумеется, средства Delphi это позволяют, но разные версии Delphi требуют делать это совершенно по-разному, и, чтобы не порождать несовместимость, мы решили просто убрать такую возможность.
Совмещать стандартные интерфейсные элементы с экраном Direct3D в одном окне не получится. Что поделаешь. Самые важные элементы интерфейса (кнопки, текстовые строки и так далее) мы в любом случае продублируем (об этом — в одном из ближайших занятий). Возможно, если это окажется нужным, мы разработаем специальную панельную версию движка (для редактора карт, к примеру).
Свойства формы
У TLKI3dForm есть несколько новых (или измененных старых) свойств, которые мы сейчас рассмотрим.
Windowed, как и ранее, означает выбор между оконным и полноэкранным режимом. True — работаем в окне, false — на полном экране. Можно переключать прямо во время работы программы, хотя, если при этом изменятся размеры окна, что-то может уползти за его край. По умолчанию — true.
ShowFPS — если установить в true, будет показываться разрешение игрового окна, количество кадров в секунду и так далее. По умолчанию — false.
BkColour — это, как и раньше, цвет «фона». По умолчанию это — также и цвет тумана. Для нашей задачи установим его в $007090 — это приблизительно «цвет морской волны». Дело ведь у нас происходит под водой...
Camera — запись, которая состоит из трех векторов (см. ниже), определяющих положение камеры:
— Eye — точка, с которой смотрит камера;
— Look — точка, в направлении которой она смотрит;
— UpVec — направление вверх.
Менять параметры камеры мы научимся потом, а пока обойдемся стандартными установками.
Контейнер — TLKIMeshList
Единственный пока что новый компонент — TLKIMeshList. Это — контейнер для наших трехмерных объектов.
ЛКИ-Creator работает пока что с двумя форматами трехмерных файлов. Это так называемые Х-файлы, которые «умеют» многие редакторы моделей, например — 3D Studio Max. Х-файлы бывают двух видов — текстовые (скрипты, где параметры модели прописаны открытым текстом) и двоичные; те и другие имеют обычно расширение .х. TLKIMeshList «понимает» оба этих формата.
Установим наш контейнер на форму (куда угодно, при работе программы его все равно видно не будет) и займемся его свойствами в инспекторе объектов. Нас интересует параметр Items: в нем мы укажем имена файлов, которые надо подгрузить. Без расширения, по одному имени на строчку. Сейчас в нем будет вот что:
seafloor
dolphin
Это, соответственно, морское дно и дельфин.
Если мы будем хранить модели не в каталоге проекта, а где-то еще — надо указать это «где-то» в параметре Dir.
Вовсе необязательно загружать все модели с самого начала — это, ко всему, и неэкономично. У нашего компонента есть такие методы: AddMesH добавляет модель в контейнер, RemoveMesH убирает. Параметр у обоих методов один — строка-имя модели.
Обращаться к моделям в контейнере можно двумя способами: через массив Meshes, где они лежат в порядке установки, от 0-го до последнего, и через имя — посредством функции MeshKey.
О формате модели (тип TLKIMesh) и том, что с ней можно проделывать, поговорим чуть позже.
Игровой объект — TLKI3dGameObject
Модель — это графический объект, трехмерная картинка, которая призвана изображать собой тот или иной игровой объект. У этого объекта есть какие-то свои свойства: положение в пространстве, прочность, скорость и так далее. Такой объект представлен типом TLKI3dGraphicObject, а изображающая его модель — только одно из его полей, Mesh.
Это важно: надо четко уяснить себе разницу между объектом и моделью, его изображающей. Внешний вид — текстура, форма, анимации — это свойства модели, а все остальное — движение, игровые характеристики — свойства объекта. Одна и та же модель может соответствовать сотне разных игровых объектов: выглядеть эти объекты будут одинаково (с точностью до фазы анимации), а по сути будут различны. Точно так же организован и двумерный движок, только там вместо модели — спрайт, плоская картинка.
Из многочисленных методов объекта нас пока интересуют два: Turn и Send.
Первый задает поворот объекта в пространстве: его параметры — углы по отношению к осям Y и Z. Эти параметры однозначно задают угол поворота в трех измерениях.
Второй — Send — переносит объект в точку с координатами x, y, z — параметрами процедуры.
Это интересно: в двумерном движке мы оперировали в основном методом Move, который не переносил объект мгновенно, а только задавал ему направление движения. Здесь он тоже присутствует, но им мы займемся на следующем занятии, потому что в трех измерениях движение часто бывает криволинейным, и управляться с ним несколько сложнее. Пока же нам хватит операции мгновенного перемещения.
Это важно: если в двух измерениях координаты и углы у нас были целочисленными, то здесь они вещественны, то есть — с плавающей точкой (в основном используется тип single). Дело в том, что перемещение на полпикселя в двумерном пространстве большого смысла не имеет, а здесь возможно любое масштабирование. По той же причине удобства ради углы мы здесь измеряем не в градусах, а в радианах.
Игровой мир — TLKI3dGameWorld
Cовокупность игровых объектов содержится в контейнере, называемом игровым миром, — TLKI3dGameWorld. Он организован практически так же, как и его двумерный собрат, о котором вы можете прочитать в первых статьях по движку ЛКИ-Creator (на диске).
Объекты делятся там на две категории — активные и пассивные, хранятся соответственно в массивах Objects и PassiveObj. Вторая категория характерна тем, что не принимает никакого участия в жизни игрового мира и служит просто декорацией.
Для создания нового объекта в игровом мире, как и раньше, служит метод
procedure AddObj(active: boolean; aID : integer; ax, ay, az, asp : single; NS, ahits : integer; arem : integer = -1; ad : integer = 0);
Два последних параметра необязательны.
Первый параметр — активный объект или нет (true — активный); второй — идентификационный номер или код (впоследствии будем работать с ним, а пока можно вспомнить, что это такое, по примеру «Звездный эскорт» («Занятие первое»); далее следуют координаты и скорость, потом — номер модели в контейнере MeshList и число хитов (последнее можно пока задать любым, лишь бы больше 0).
В целом — параметры такие же, как у двумерного TLKIGameWorld, только координат три и они нецелые, скорость тоже нецелая, а вместо номера спрайта — номер модели.
Процедура RemoveObj(active: boolean; n : integer) удаляет объект номер n из выбранного списка.
Начало работы
Итак, у нас есть окно с формой, порожденной от TLKI3dForm, на которую мы установили контейнер TLKIMeshList (назовем его, скажем, XFiles — ведь в нем содержатся файлы формата Х...).
Как и в случае с двумерным движком, первое, что нам нужно сделать — это проинициализировать окно DirectX. Для этой цели в окне Object Inspector откроем закладку событий для формы и определим обработчик события OnCreate. В полученную процедуру FormCreate введем все операции, которые нужны для старта процесса. Посмотрим на панельку «Инициализация».
Инициализацияprocedure TDolphinForm.FormCreate(Sender: TObject); begin BkColour := $007090; Init3D; InitMeshes(XFiles); World := TLKI3dGameWorld.Create; SetGameWorld(World); // Морское дно World.AddObj(false, 0, 0,-10,0,0,0,1); // Дельфин World.AddObj(true, 1, 0,0,0, 0, 1, 1); end; |
Начинаем мы с определения свойств формы. Только одно из них у нас отличается от стандартного — BkColour; зададим его значение, как было сказано выше.
Теперь вызываем процедуру Init3D. Этот метод трехмерной формы создает DirectX-овский порт вывода и делает все подготовительные операции.
Далее, надо сообщить форме, где хранятся трехмерные объекты. Это делает процедура InitMeshes, параметр которой — название контейнера.
Затем создаем игровой мир (не забыв выше описать переменную World), вызывая конструктор TLKI3dGameWorld.Create.
«Привяжем» игровой мир к форме операцией SetGameWorld.
Это важно: последовательность этих действий менять нельзя ни в коем случае, иначе ждите всяческих странностей и ошибок.
Все, инициализация закончена, теперь можно создать игровые объекты. Вызовем дважды AddObj — для морского дна и дельфина.
Запускаем. И что же мы видим? Вместо голубого фона — какой-то серый, а дельфина не видать. Такое случается: дельфин просто не отмасштабирован, очень уж велик. Но этому горю помочь нетрудно. Определим переменную DolpHс моделью дельфина (тип TLKIMesh) и отмасштабируем ее, уменьшив в 100 раз, простейшей операцией:
DolpH:= XFiles.MeshKey('DOLPHIN');
Dolph.Scale(0.01);
Все. Дельфин появился, теперь попробуем его подвигать.
Обработка такта: метод Process
Если мы хотим, чтобы наш игровой мир ожил, нам придется создать класс-потомок TLKI3dGameWorld. Назовем его, скажем, TDolphinWorld. Наша задача — переопределить метод Process, который заведует изменениями, происходящими в игровом мире с течением времени.
Замечание: в двумерном варианте движка мы с вами тоже делали аналогичную операцию. Смотрите занятие первое, глава «Обрабатываем такт».
Метод Processprocedure TDolphinWorld.Process(Tick : single); var Phase, Kick : single; begin Phase := Tick/3; Kick := Tick*2; Objects[0].Turn(Phase,-cos(Kick)/6); Objects[0].Send(-5*sin(Phase), sin(Kick)/2, 10-10*cos(Phase)); Inherited Process(Tick); end; |
Простенький код, который вы видите на панели «Метод Process», заставляет дельфина кружиться, то опускаясь, то поднимаясь. Процедура Turn обеспечивает, чтобы он все время был повернут по направлению движения.
Как видите, это устроено почти так же, как метод Process в двумерном мире, только время тоже стало из целочисленного вещественным.
Это интересно: в этом примере мы задали координаты «в лоб», напрямую. В следующем занятии мы изучим более «интеллигентные» методы определения траекторий движения объектов.
Красиво? По-моему, не очень. Все бы ничего, но только двигается наш дельфин, как пластмассовая игрушка, — не гнется. Придется заняться анимациями.
Анимирование
В принципе, существует немало методов анимации. Мы освоим пока не самый быстрый, но обеспечивающий достаточно плавную анимацию.
Суть его в том, что для каждого движения у нас есть начальная и конечная фаза, а мы их каждый такт смешиваем в пропорции, зависящей от прошедшего времени.
Для этого нам понадобится, для начала, многофазный файл с дельфином. Он у нас есть — называется dolphin_group.x. Итак, добавляем в список моделей название dolphin_group.
Теперь надо присоединить анимации к модели. Определим переменную Anim типа TLKIMesHи напишем такие две строки:
Anim := XFiles.Meshes[2];
Dolph.GetAnim(Anim, ['Dolph01','Dolph02','Dolph03']);
То, что в кавычках — Dolph01 и так далее — это названия фаз.
Это важно: вставить эти строки нужно в строго определенное место кода: после присвоения значения переменной DolpH(иначе к чему будем присоединять анимации?), но до масштабирования дельфина — не то анимации останутся неотмасштабированными.
Второе изменение, которое нам надо внести — в методе Process. Это собственно анимация (cм. фрагмент программы).
АнимацияWeight := sin(Kick); if Weight < 0 then Dolph.BlendAnim(2,1,-Weight) else Dolph.BlendAnim(0,1,Weight); |
Что делает этот код? Переменная Weight колеблется от -1 до 1; она служит так называемой весовой функцией, определяющей, к чему результирующая анимация ближе — к первой или второй из предлагаемых фаз. Пока Weight отрицателен, мы движемся от второй анимации к первой (мы берем -Weight, значит, если Weight растет, то параметр убывает); при достижении нуля получается строго первая анимация; затем с положительным Weight движемся от первой анимации к нулевой. Потом Weight начинает убывать (он у нас представлен синусоидой), и мы возвращаемся к первой анимации. И так далее.
Попробуем запустить программу. Дельфин зашевелил хвостом и плавниками.
Вот, собственно, и все, что нужно, чтобы в пакете ЛКИ-Creator создать трехмерный мир и анимировать его.
В ближайших номерахВ ближайших номерах ЛКИ в разделе «Игра своими руками» вы увидите: более интересные способы работы с движением и траекториями объекта; способы размещения интерфейсных элементов на трехмерном экране; управление трехмерными объектами; выбор объектов мышью; проверки столкновений; работу с глобальным освещением и локальными источниками света; операции над камерой; излучатели частиц и другие эффекты; примеры разнообразных игровых программ. |
Полный текст примера с дельфином вы можете увидеть на этой странице; как видите, он совсем маленький, а тех строк, которые вам пришлось написать лично (а не автосозданных) — и вовсе меньше трех десятков.
Вы можете сказать, что это пока не игровой код: в нем заведомо недостает, например, интерактивности. Но дело в том, что большинство функций, которые были для этой цели в двумерном движке, работают и здесь. Подробнее мы поговорим об этом в будущих номерах, но пока что можно попробовать управлять дельфином при помощи, например, клавиш-стрелок: способы найдутся в предыдущих статьях этого раздела.
Но, конечно, это не все возможности пакета. Более полную его версию, новые примеры и описания ждите в следующих номерах.
До встречи!
Полный текст программыUnit DolphinMain; {----------------------------------------------------} {------------} INTERFACE {------------} {----------------------------------------------------}
Uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, LKI3dEngine, StdCtrls; type TDolphinForm = class(TLKI3dForm) XFiles: TLKIMeshList; procedure FormCreate(Sender: TObject); end; TDolphinWorld = class(TLKI3dGameWorld) procedure Process(Tick : single); override; end; var DolphinForm: TDolphinForm; Dolph, Anim : TLKIMesh; World : TDolphinWorld; {----------------------------------------------------} {------------} IMPLEMENTATION {------------} {----------------------------------------------------}
{$R *.dfm}
procedure TDolphinForm.FormCreate(Sender: TObject); begin BkColour := $007090; Init3D; InitMeshes(XFiles); DolpH:= XFiles.MeshKey('DOLPHIN'); Anim := XFiles.Meshes[2]; Dolph.GetAnim(Anim, ['Dolph01','Dolph02','Dolph03']); Dolph.Scale(0.01); World := TDolphinWorld.Create; SetGameWorld(World); World.AddObj(false, 0, 0,-10,0,0,0,1); // Морское дно World.AddObj(true, 1, 0,0,0, 0, 1, 1); // Дельфин end; procedure TDolphinWorld.Process(Tick : single); var Phase, Kick, Weight : single; begin Phase := Tick/3; Kick := Tick*2; Weight := sin(Kick); if Weight < 0 then Dolph.BlendAnim(2,1,-weight) else Dolph.BlendAnim(0,1,weight); Objects[0].Turn(Phase,-cos(Kick)/6); Objects[0].Send(-5*sin(Phase), sin(Kick)/2, 10-10*cos(Phase)); Inherited Process(Tick); end; END. |