|
Учимся работать с DelphiX (создание игр DirectX) |
::..::..:: |
Краткий
обзор DelphiX
Говоря техническим языком, DirectX - набор
объектов COM (Component Object
Model), которые реализуют интерфейсы для
облегчения работы с видеоаппаратурой,
звуком, межкомпьютерными соединениями и
некоторыми системными сервисами.
DirectX был создан для решения проблемы
совместимости аппаратуры, пополняющейся
все новыми образцами с новыми возможностями и
функциями, и программ, этой
аппаратурой управляющих. Также применение
DirectX с аппаратурой, имеющей функции
аппаратного ускорения (3Dfx, NVidia и подобные)
позволяет разгрузить основной
процессор.
DirectX состоит из 7 основных компонент:
DirectDraw - позволяет напрямую работать с
видеопамятью и аппаратными
функциями оборудования, при этом сохраняя
совместимость с
Windows-приложениями.
DirectInput - интерфейс для устройств ввода
(мышь, клавиатура, джойстик и
т.д.).
DirectPlay - интерфейс для многопользовательских
приложений (TCP/IP, Direct
Dial, локальное подключение).
DirectSound - интерфейс для звуковой аппаратуры
(WAV, MIDI и др.).
DirectSound3D - позвляет позиционировать
звуковые источники в любой точке
трехмерного пространства, создавая таким образом
реальный объемный звук.
Direct3D - интерфейс к 3D - аппаратуре.
Все эти компоненты спроектированы таким образом,
чтобы дать программисту прямой
доступ к аппаратуре.
Терминология
Перед началом работы с DirectX необходимо
остановиться на терминологии.
Поверхность (surface) - участок видеопамяти,
который используется для хранения
различных картинок. Все видеобуферы ссылаются на
поверхности. Поверхность,
которая отображается на экране в текущий момент
называется основной (primary)
поверхностью. Эта поверхность занимает столько
памяти, сколько нужно для
текущего разрешения и глубины цвета. Так, если
установлен видеорежим 640 x 480 x
256 цветов (8 bpp), тогда основная поверхность
будет занимать 307200 байт
видеопамяти. Обычно вам нужна еще одна
поверхность такого же размера, что и
основная, используемая для флиппинга (что это
такое будет объяснено чуть позже).
Это значит, что нужно 614400 байт видеопамяти
просто чтобы начать работать, не
загружая никаких картинок. Если количества
видеопамяти не хватает, поверхности
будут создаваться в системной памяти, теряя
преимущества аппаратного ускорения.
В настоящий момент вам необходима видеокарта с
2MB видеопамяти - это абсолютный
минимум для простых игр.
Двойная буферизация (double buffering) -
техника, применяемая для получения
быстрой, плавной анимации. Обычно используется
дополнительный буфер, где
создается сцена, которая затем отображается на
экране.
Анимация с флиппингом страниц (page flipping
animation) - для понимания этой
техники вам достаточно нарисовать что-нибудь в
блокноте и быстро пролистать
страницы. При этом получится изменяющаяся
картинка. В нашем случае сцена
создается копированием картинок и спрайтов на
поверхность (буфер), которая затем
отображается на экране. Флиппинг - очень быстрая
операция из-за того, что
реально не происходит копирования больших
объемов информации. При флиппинге
происходит изменение одного регистра видеокарты,
который содержит адрес участка
памяти, отображающегося в текущий момент. В
основном флиппинг состоит из 3-х
шагов:
1. Создание сцены в буфере.
2. Переключение буфера для отображения, при этом
поверхность, которая
отображалась до этого становится буфером.
3. Повторяем с шага 1.
Кадры в секунду (frames per second) - обычно
записывается как FPS, и обозначает
количество кадров анимации, необходимое для
получения плавного реального
движения. Обычно для игр достаточно 24-25 FPS
для получения приемлемых
результатов (хотя для некоторых игр это значение
может быть еще меньше).
Эффект разрывания экрана (tearing) - Большинство
мониторов обновляют экран с
частотой примерно 70 раз/сек (70 Гц) сверху
вниз. Проблема возникает когда вы
пытаетесь отобразить новую картинку где-то в
середине процесса обновления
экрана. При этом верхняя половина экрана
отображает старую картинку, а нижняя -
новую. Во время существования DOS для
предотвращения этого эффекта вам нужно
было бы синхронизироваться с вертикальной
разверткой. DirectX освобождает вас от
этой процедуры.
Картинки (битмапы) и спрайты - спрайты
отличаются от битмапов тем, что они имеют
прозрачные области. Термин "спрайт" также
используется для анимированных
изображений в играх. Обычно спрайт состоит из
нескольких фреймов (кадров), как в
следующем примере:
Отметим, что каждый фрейм отображает
определенный угол поворота объекта. Если
показывать эти картинки одна за другой, то
получится что-то тип этого:
Также отметим, что прозрачный цвет спрайта -
черный, поэтому фон получающегося
спрайта - белый.
Клиппинг (clipping) - эта техника применяется
для того, чтобы приложение смогло
рисовать только в отведенный ему прямоугольник
на экране. Если клиппинг не
применяется, приложение может рисовать на всем
экране. Это прекрасно работает
если ваше приложение полноэкранное, но если оно
оконное - клиппинг должен
применяться обязательно.
Теория цвета - цвет в Windows обычно
представляется моделью RGB (красный,
зеленый, синий). Используя эти три основных
цвета позволяет получить все
мыслимые оттенки комбинацией основных. Обычно
цвет хранится в виде 3-х байтов -
каждый байт представляет относительную
интенсивность основного цвета (от 0 до
255 включительно).
В Delphi цвет представляется в виде класса
TColor, объявленном в модуле
Graphics. Вы можете определить цвет используя
консттанты типа clBlue, clLime и
т.п. или определяя TColor как 4-хбайтовое число,
где 3 младших байта
представляют RGB цвет. Так, 0x00FF0000 - синий,
0x0000FF00 - зеленый, 0х000000FF
- красный, 0x00000000 - черный, 0х00FFFFFF -
белый и т.д. По поводу старшего
байта в помощи по VCL сказано: "Если старший
байт равен $00, получаемый цвет -
ближайший подходящий цвет системной палитры,
если он равен $01 - берется
ближайший цвет из текущей палитры, если $02 -
подбирается ближайший цвет,
соответствующий контексту устройства".
Обзор DelphiX
Для работы с DelphiX необходимы следующие
компоненты:
DirectX Run-time
Delphi 3, 4, 5, 6
DelphiX
DelphiX - набор бесплатных компонент для Delphi
для упрощения использования
DirectX. Компоненты и их назначение представлены
ниже:
TDXDraw дает доступ к поверхностям DirectDraw и
включает весь код,
необходимый для работы с DirectDraw и
DirectDraw.
TDXDib позволяет хранить DIB (Device Independent
Bitmap) подробнее
TDXImageList позволяет хранить серии DIB-файлов,
что очень удобно для
программ, содержащих спрайты. Позволяет
загружать серию с диска во время
выполнения программы.
TDX3D оставлен для совместимости с предыдущими
версиями DelphiX,
используйте TDXDraw.
TDXSound позволяет легко проигрывать wav-файлы.
TDXWave "Хранилище" для wav-файла.
TDXWaveList "Хранилище" для серии wav-файлов.
TDXInput позволяет получить доступ к объекту
DirectInput и, соответственно,
к мыши, клавиатуре и т.д.
TDXPlay позволяет разработчику легко подсоединить
данные, находящиеся на
другом компьютере, в том числе через Internet
или LAN.
TDXSpriteEngine облегчает и автоматизирует работу
со спрайтами. Поддержка
методов Move, Kill и т.д.
TDXTimer дает более высокую точность, чем при
использовании обычного
таймера (TTimer). Используются потоки,
синхронизация.
TDXPaintBox DIB-версия стандартной компоненты
TImage.
Замечания по инсталляции
Предположим, вы уже распаковали DelphiX на
жесткий диск и готовы инсталлировать
ее на палитру компонент. Если библиотека пришла
в виде исходников, желательно
перекомпилировать ее. В любом случае для
инсталляции выберите из меню File пункт
Open и найдите каталог, в который вы распаковали
библиотеку. Там должны быть
файлы Delphi_for3.dpk, Delphi_for4.dpk и
Delphi_for5.dpk, для соответствующей
версии Delphi.
Появится диалог подобный
следующему:
Нажмите кнопку Compile и после компиляции
нажмите кнопку Install. Если все
прошло успешно, появится диалог, сообщающий об
этом. Все готово для
экспериментов с DirectX.
Если у Вас имеется каталог DelphiX\Bin то для
подключения компонент проще
использовать утилитки Install_for3.exe,
Install_for4.exe и Install_for5.exe -
они не только установят DelphiX, но и подключат
файл помощи.
Начало программирования
В качестве примера создадим следующий эффект:
Создание поверхности
Для использования DirectDraw нужно создать
поверхность, на которой мы будем
рисовать. Просто перетащите компоненту TDXDraw
на вашу форму. Дайте ей имя
DXDraw. В инспекторе объектов вы увидите 4
свойства, которые нас интересуют. Это
Align, Autoinitialize, Display и
Options.
Установите свойство Align в alClient, т.к. мы
хотим, чтобы весь экран стал
поверхностью DirectDraw.
Autoinitialize всегда должно быть установлено в
true, только если мы не хотим
инициализировать поверхность вручную, для чего,
наверное, нужно использовать
метод DXDraw. Initialize в обработчике
OnCreate
формы.
Свойство Display поможет вам выбрать размер
области рисования. Допустимые
видеорежимы показаны в выпадающем списке. Для
нашего примера установите свойство
в 640x480x8.
Свойство Options дает доступ к 18 атрибутам.
Таблица объясняет их назначение.
Атрибут Описание
doFullScreen запускает приложение в полноэкранном
режиме. Видеорежим может
быть указан в свойстве Display.
doNoWindowChange если выбрана эта опция и
doFullScreen, приложение сначала
максимизирует свое окно, а затем устанавливает
режим, указанный в свойстве
Display.
doAllowReboot определяет, можно ли в программе
использовать комбинацию
Alt+Ctrl+Del. Это полезно во время отладки.
doWaitForBlank определяет, будет ли ожидаться
вертикальная развертка при
выполнении операции флиппинга. Опция немного
уменьшает FPS.
doAllowPalette256 будет ли использоваться
256-цветная палитра doSystemMemory определяет, использовать ли
системную память вместо
видеопамяти. Опция немного уменьшает FPS.
doStretch если ваша игра использует область
отображения большую (или
меньшую), чем указано в свойстве Display, с
помощью этой опции можно сжать
(растянуть) изображение на весь экран.
doCenter поверхность отобразится в центре экрана.
doFlip применяется только для полноэкранных
режимов. Если используется
двойная буферизация и требуется отобразить
буфер, то в случае
установленной опции это происходит очень быстро
(применяется операция
флиппинга).
Замечание: размер буфера должен равняться
размеру основной поверхности.
do3D позволено ли использовать 3D акселерацию.
doHadrware если видеоадаптер поддерживает
аппаратное ускорение типа 3D или
2D, то полезно установить опцию в true.
Замечание: Если опция установлена в true, а
видеокарта не поддерживает
акселерацию, опция будет установлена в false.
это можно использовать для
определения поддержки аппаратного ускорения.
doRetainedMode опция имеет эффект только если
установлена опция do3D. Если
опция равна true, используется режим Direct3D
Retained, иначе - Immediate.
doSelectDriver в полноэкранном режиме определяет
будет ли использоваться
драйвер DirectDraw. Для Voodoo и подобных
видеоадаптеров опция должна быть
установлена в True.
doDrawPrimitive использовать рисование
примитивов.
doZBuffer использовать ли Z-буфер. Эта опция
может устранить некоторые
проблемы с пропаданием объектов или наоборот, с
появлением объектов,
которые должны находиться на заднем плане.
Требует часть процессорного
времени. Некоторые карты поддерживают эту
функцию аппаратно.
doTexture будем ли мы использовать текстуры на
3D
объектах?
doRGB определяет, станет ли использоваться
цветовая модель RGB. Может
улучшить внешний вид 3D объектов, но отнимает
процессорное время. Если
карта аппаратно поддерживает эту функцию, опция
не влияет на работу.
doMono использовать ли черно-белую цветовую
модель.
doDither определяет будет ли подбираться
ближайший цвет из палитры, если в
ней не окажется запрашиваемого нами цвета. В
основном используется с
атрибутом doAllowPalette256.
Наши установки буду выглядеть следующим образом:
doFullScreen=False (программа будет стартовать в
обычном окне)
doAllowReboot=True (Возможно, возникнет
ситуация, когда нам нужно будет снять
задачу из-за ошибок)
doWaitVBlank=True/False (Попробуйте оба
значения. Возможно, вы получите
приемлемое качество при установке False, при
этом возрастет FPS) do3D=False (Наше приложение будет использовать
только 2D) doHardware=True (Нам нужно определить,
поддерживает ли аппаратура
акселерацию.).
Остальные атрибуты оставлены как есть.
Добавление в программу изображений
Работа с битмапами в DelphiX обычно трудностей
не вызывает. Все, что вам нужно
сделать - создать картинку и затем добавить
компоненту TDXDib или TDXImageList.
Для нашего примера перетащим на форму
TDXImageList (и назовем ее DXImageList). В
инспекторе объектов вы увидите 2 свтойства:
DXDraw и Items.
DXDraw определяет поверхность DirectDraw, на
которой будет рисоваться эта
картинка (или серия картинок). Возможно
использование нескольких поверхностей. В
нашем случае просто выберите уже созданную
поверхность DXDraw.
Items содержит все изображения в серии. Для
добавления новых изображений нажмите
на кнопку "..." и добавьте свои картинки.
Изучаем таймер
Следующий шаг в нашем простом примере -
добавление таймера (назовем его
DXTimer). Как вы наверное знаете, SystemTimer,
входящий в стандартный набор
компонент Delphi, не очень точен для
использования в играх. DXTimer имеет
разрешение, близкое к миллисекунде, что вполне
достаточно для наших (и более
серьезных) целей. Установите его свойства
следующим образом:
ActiveOnly = true (таймер всегда активен)
Enabled=false (мы сами будем управлять им -
запускать и останавливать)
Interval=0 (максимальная частота срабатывания
таймера)
Еще одна очень приятная фишка DXTimer'а -
свойство DXTimer.FrameRate оно
позволяет получить значение FPS в любой момент
времени. Событие таймера обычно
используется для методов типа DXDraw.Flip,
DXDraw.Render, DXDraw.Update и др. В
нашем примере используется метод DXDraw.Flip.
Подготовительные действия
Перед тем как собственно что-нибудь нарисовать,
нужно подготовить кое-какую
информацию для нормальной работы приложения.
Во-первых, в закрытую секцию необходимо добавить
некоторые переменные и
процедуры:
SineMove : array[0..255] of integer;
{ Таблица
синусов для движения }
CosineMove : array[0..255] of integer; { Таблица
косинусов }
SineTable : array[0..449] of integer; { Таблица
синусов }
CenterX, CenterY : Integer; { Для координат
центра черной дыры, которую мы
будем рисовать }
procedure CalculateTables; { Заполнение таблиц
синусов и косинусов }
procedure PlotPoint( XCenter, YCenter, Radius,
Angle : Word); { Рисование
точки на бэк-буфере }
Таблицы содержат заранее рассчитываемые значения
синусов и косинусов,
используемые для моделирования волн при
движении. Зачем эти значения
рассчитываются заранее? Вообще, в
программировании игр всегда существует
компромисс между скоростью и объемом. Если
приложение занимает немного памяти,
то оно все просчитывает само, следовательно,
отнимается какое-то время. Либо
можно просчитать все до цикла рисования,
сохранить результаты и затем
использовать их, выиграв в скорости. Процедура
заполнения таблиц может выглядеть
следующим образом (хотя ваша реализация,
несомненно, будет работать намного
лучше):
procedure TMainForm.CalculateTables;
var
wCount : Word;
begin
{ Precalculted Values for movement }
for wCount := 0 to 255 do
begin
SineMove[wCount] := round( sin( pi*wCount/128 )
* 45 );
CosineMove[wCount] := round( cos( pi*wCount/128
) * 60 );
end;
{ Precalculated Sine table. Only One table
because cos(i) = sin(i + 90) }
for wCount := 0 to 449 do
begin
SineTable[wCount] := round( sin( pi*wCount/180 )
* 128);
end;
end;
К процедуре построения точки мы вернемся позже.
Следующее, что нужно сделать -
это добавить кое-какой код в событие OnCreate
формы:
procedure TMainForm.FormCreate(Sender: TObject);
begin
CenterX := Width div 2;
CenterY := Height div 2;
CalculateTables;
end;
И для того, чтобы форма завершилась по нажатию
на клавишу ESC, нужно добавить
код в обработчик события OnKeyDown:
procedure TMainForm.FormKeyDown(Sender: TObject;
var Key: Word; Shift:
TShiftState);
begin
if Key=VK_ESCAPE then Close;
end;
Теперь выбираем компоненту DXDraw, которую мы
поместили на форму и создаем
обработчик события OnFinalize:
DXTimer.Enabled := False;
Этот кусок кода останавливает таймер. Создайте
обработчик события OnInitialize и
добавьте в него строчку:
DXTimer.Enabled := True;
что запускает таймер, т.е. начинает отображение
картинки (конечно, если
поверхность готова к этому).
Самое главное это время
Как видно, для подготовки к работе с DirectX
нужно совсем немного. Теперь
перейдем собственно к рисованию. Создайте
обработчик события OnTimer таймера и
добавьте в него следующий код:
procedure TMainForm.DXTimerTimer(Sender:
TObject; LagCount: Integer);
const
x : Word = 0;
y : Word = 0;
IncAngle = 12;
XMove = 7;
YMove = 8;
var
CountAngle : Word;
CountLong : Word;
IncLong :Word;
begin
if not DXDraw.CanDraw then exit;
IncLong := 2;
CountLong := 20;
DXDraw.Surface.Fill( 0 );
repeat
CountAngle := 0;
repeat
PlotPoint(CosineMove[( x + ( 200 - CountLong ))
mod 255],
SineMove[( y + ( 200 - CountLong )) mod 255],
CountLong,
CountAngle);
inc(CountAngle, IncAngle);
until CountAngle >= 360;
inc(CountLong, IncLong);
if ( CountLong mod 3 ) = 0 then inc(IncLong);
until CountLong >= 270;
x := XMove + x mod 255;
y := YMove + y mod 255;
with DXDraw.Surface.Canvas do
begin
Brush.Style := bsClear;
Font.Color := clWhite;
Font.Size := 12;
Textout( 0, 0, 'FPS: '+inttostr(
DXTimer.FrameRate ) );
Release;
end;
DXDraw.Flip;
end;
Это основной код нашего приложения. Рассмотрим
некоторые важные моменты:
Вызов процедуры Fill поверхности DXDraw.Surface
(DXDraw.Surface.Fill(0);)
заполняет буфер цветом, который передается ей в
качестве параметра (в нашем
случае - черный).
Рассмотрим теперь процедуру PlotPoint. Все
таблицы для ее работы были заполнены
на подготовительном этапе, так что ничто не
мешает нам нарисовать все, что мы
хотим. Итак,
procedure TMainForm.PlotPoint(XCenter, YCenter,
Radius, Angle: Word);
var
X, Y : Word;
begin
X := ( Radius * SineTable[90 + Angle]);
asm
sar x,7
end;
X := CenterX + XCenter + X;
Y := ( Radius * SineTable[Angle] );
asm
sar y,7
end;
Y := CenterY + YCenter + Y;
if (X < Width ) and ( Y < Height ) then
begin
DXDraw.Surface.Canvas.Pixels[X, Y] := clBlue;
DXImageList.Items[0].Draw( DXDraw.Surface, X, Y,
0 );
end;
end;
Основная строка в этой процедуре:
DXImageList.Items[0].Draw( DXDraw.Surface, X, Y,
0 );
Как было сказано выше DXImageList содержит
массив картинок, с которыми мы
работаем. Доступ к элементам осуществляется
через индекс, начинающийся с 0 т.е.
указывая DXImageList.Items[0], мы получаем
первую картинку, и т.д. Свойство Items имеет метод
Draw, в который нужно передать
4 параметра. Первый параметр
определяет поверхность, на которой будет
рисоваться эта картинка. Второй и
третий параметры - X и Y - определяют позицию, в
которую будет выведено
изображение. Последний параметр - флаг,
определяющий прозрачность выводимой
картинки. Так что строка кода, приведенная выше
может прочитаться как "Вывести
картинку с индексом 0 на поверхность
DXDraw.Surface в позицию X, Y со значением
прозрачности 0".
Также можно использовать свойство Pixels объекта
Canvas для указания цвета
определенной точки на экране (эта строка
закомментирована, так как в нашем случае
используется картинка. Эксперименты со свойством
Pixels даются вам в качестве
домашнего задания).
После того, как мы нарисовали нашу картинку, мы
выводим значение FPS используя
свойство FrameRate таймера. Вывод производится с
помощью свойства Canvas объекта
DXDraw.Surface.
Наконец вызывается метод DXDraw.Flip для
отображения картинки на экране. При
этом основная поверхность становится буфером.
Все, компилируйте и запускайте ваше приложение.
На P120 получается порядка
12-15FPS (в зависимости от
полноэкранного/оконного режима).Разместил
Razer | |
|
| | | | | | |
|