Visual Basic - мастерская разработчика
Библиотеки

DirectX

Обзоры
DirectDraw
Direct3D
DirectX Audio
DirectPlay
DirectInput
Fido Topics
SourceCode
Tools&Libs

OpenGL

Статьи и учебники
Fido Topics
SourceCode
Tools&Libs

Архив по Glide

Движки

Обзоры
Учебники
SourceCode
Downloads

Создание игр

Ваши игры

Обзорные статьи
Учебники
Fido Topics
SourceCode
Download

Stuff

Программер-Чат

Псевдо-FTP
Disclaimer
Оффтопик

 

Основы DirectDraw на C++

Second Edition      

[Следующая часть] [Приложения к этой части]


Ну наконец-то....

Часть первая: что-то вместо введения

 

А чо это такое?

Это не чо. Это - вторая редакция моего учебника по DirectDraw, переписанная специально для С++
Я планирую и дальше "переводить" свои VB-шные туториалы на C++ так что заглядывайте на сайт почаще :)

И еще. Так как очень долгое время я программировал на VB, поэтому мой стиль программирования на Си может показаться "матерым" си-шникам несколько странным. Ну да ладно. Вы же всегда сможете переделать все на свой лад!

Итак, не лучше ли начать?

 

Введение

Ну что ж, привет всем, кто здесь. Надеюсь, это маленькое руководство поможет вам создать свою собственную игру с помощью библиотеки DirectX.

Note: I don't accept any responiblity for anything that might happen to your system.

То есть, я типа не виноват, если Вы вдруг захотите выбросить свою систему в окно под влиянием от прочитанного тут.

 

А что мне надо, чтобы начать программировать Direct Draw?

Так как DirectDraw входит в состав библиотеки DirectX, а библиотека сами знаете - это куча lib'ов и h'ов, поэтому вам понадобятся эти самые lib и h. Где их взять? Вообще-то они входят в состав Microsoft DirectX7 SDK и если у вас есть такой диск, то вам повезло. Ежели нет, то не отчаивайтесь по поводу как бы вам скачать 200 мегов с сайта microsoft.com Дело в том, что полная SDK представляет собой помимо lib и h еще документацию (5 мегов), а также примеры, утилиты, да вдобавок еще и рунтаймы самомого DirectX. Оно вам надо? Так вот на том же сайте microsoft.com вам надо найти в разделе downloads ссылку типа DirectX7 Libraries & Headers (1.4 мегабайта) - качайте. Далее, по желанию и возможностям, советую скачать DirectX7 Documentation (5 мегов). Остальное... Можете конечно качать... :)

Так. Допустим вы уже обзавелись заветным архивом с библиотеками и инклудами. Распакуйте это куда-нть, например d:\sdk\directx7.
А теперь такой вот вопрос. На чем вы прогаете? Лично я на VC6. Если вы тоже, тогда - отлично, все ниженаписанное пройдет у вас без запинки. Ежели вы предпочитаете альтернативный компилятор, то возможно будут проблемы. Все-таки прочитайте этот параграф до конца и может быть вы придумаете как сделать то же со своим компилятором. Как только придумаете, напишите мне как, и я включу "поддержку" вашего компилятора в эту статью.

Так вот. В VC зайдите в Options и во вкладке Directories установите путь к lib'ам и h'ам Это будет выглядеть примерно так: d:\sdk\directx7\lib и d:\sdk\directx7\include. После этого ОБЯЗАТЕЛЬНО переместите добавленные строки в самый верх списка. Это надо для того, чтобы VC брал инклуды и либы из указанной нами папки, а не из своей собственной инклудной директории, так-как в ней уже установлены библиотеки для DirectX 5-й версии.

Ага, теперь я могу уже начать!?

Прелестно, вы доставли необходимые библиотеки, и в полной вооруженной готовности хотите начать творить. У меня такой вопрос. А вы знаете, как устроен и как работает DirectDraw? Если знаете, можете с умным видом пропустить следующий абзац. Если нет, вот вам маленькое разъяснение.

Что касается того, как DD устроен могу сказать только одно: понятия не имею. Зато как работает - знаю. И вам советую. Итак, самым главным, что вы должны знать, является то как проиходит анимация. Существуют такие буфера или поверхности (surface), с помощью которых все это безобразие происходит. Существует два основных буфера: передний и задний. Передний - это видимый буфер, он представляет собой то, что вы видите на экране. Задний буфер - невидимый на нем вы рисуете. Отсюда напрашивается правильный вопрос: "На кой мне рисовать на невидимом буфере?" Ответ будет очень ученым. Представьте себе, как работает ваш монитор. Сзади стекла установлена магнитная пушка, плюющаяся электронами из трубки. (У меня по физике не очень хорошо было, так что не надо кидаться всякими тухлыми продуктами в мою сторону) Эти электроны создают изображение на экране, причем луч трубки имеет тенденцию идти с левого верхнего ула по строчкам и вниз. Соответственно, он кончает прорисовку экрана в правом нижнем углу, после чего, уже ничего не рисуя, он направляется назад в стартовую позицию. Это действие называется Vertical Blank. Так как делается это с бешеной скоростью, вы не замечаете, как ваш экран обновляется.

Так вот, пока экран занят своими делами, вы рисуете на невидимом заднем буфере кадр номер 1 повешения любимого учителя по информатике, и затем, пока трубка преходит на начало следующего цикла обновления, быстренько шлепаете сцену на передний буфер, с которого пушка теперь будет обновлять экран, и пока происходит обновление, подготавливаете кадр 2. Теоретически, вы можете рисовать вашу анимацию со скоростью обновления экрана. То-есть какая установлена у вашего монитора частота, с такой и будет происходит обновление экрана, например 70 раз в секунду. Практически же тут происходит много накладок. Во-первых, на это влияет быстродействие компьютера, то-есть пока вы обработаете очередной кадр, обновление экрана пройдет уже несколько раз, и вы начинаете проигрывать в скорости. Во-вторых, если дядюшка Си работает сам по себе и в десять раз быстрее, то VB постоянно опирается на свои подпорки - Runtime-библиотеки, с помощью которых он, собственно говоря и работает. Это занимает время, и последствия очевидны.

Ну вот, обобщая, вывожу принцип действия. Сначала вы рисуете сцену на заднем буфере, а затем переводите ее на передний буфер, пока он прорисовывается, вы не теряя времени очищаете задний буфер и снова рисуете на нем следующий кадр. Потом переворачиваете... И так до упора.

Для особо вредных объясняю, что если бы вы собирали сцену на переднем буфере без мороки со флиппингом, как с BitBlt тогда бы было такое мерцание, что построить приложение, которое бы привлекало пользователя было бы довольно сложно.

 

Ну а теперь-то ???!!!...

Да! Теперь можно. Начнем с самого простого. Создадим объект DirectDraw и установим разрешение экрана как 640x480x16, полюбуемся сотворенным и при нажатии клавиши Esc выйдем обратно.

Далее наберите программу.

Как мы будем ее делать. Создадим два файла. Первый - startup.cpp, как видно из названия будет предназначен для инициализации самого приложения - функции WinMain, WndProc и т.п. Далее - файл main.cpp В нем будет расположена наша программа и все DirectX-процедуры. Для начала файл startup.cpp:

//*************************************************************
// Name: startup.c
// Autor: Antiloop
// Desc: Файл создания окна, главного цикла сообщений и т.п.
//*************************************************************


#include <windows.h>

//Функции, которые инициализируют DD, уничтожают DD,
//и обновляют экран

extern BOOL Start(HWND hwin);
extern BOOL Update();
extern void End();

//WindowProc
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg,
WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_CREATE:
//Вызывается Start - инициализация DD
if (!Start(hwnd))
return -1;
return 0;
case WM_DESTROY:
End(); //Вызов End - уничтожение DD
PostQuitMessage(0);
return 0;
case WM_SETCURSOR:
SetCursor(FALSE); //Курсор нам не нужен
return TRUE;
case WM_ACTIVATEAPP:
if (!wParam)
DestroyWindow(hwnd);
return 0;
case WM_KEYUP:
if (wParam == VK_ESCAPE)
DestroyWindow(hwnd); //Выход по Esc
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

//WinMain
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE,
LPSTR, int nCmdShow)
{
static char name[] = "DirectDraw Test";
WNDCLASS wc;
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WindowProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = NULL;
wc.lpszMenuName = NULL;
wc.lpszClassName = name;
RegisterClass(&wc);

HWND hwnd = CreateWindow(
name,
name,
WS_POPUP,
0, 0,
GetSystemMetrics(SM_CXSCREEN),
GetSystemMetrics(SM_CYSCREEN),
NULL,
NULL,
hInstance,
NULL);

if (hwnd == NULL)
return FALSE;
ShowWindow(hwnd, nCmdShow);

MSG msg;
while (TRUE)
{
while (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE))
{
if (!GetMessage(&msg, NULL, 0, 0))
return msg.wParam;
DispatchMessage(&msg);
}
if (!Update()) DestroyWindow(hwnd); //Обновить экран
}
}

В принципе ничего особенного. Мы имеем три внешних функции для инициализации, обновления и уничтожения. Первая функция создает объект DirectDraw и все поверхности. Последняя их уничтожает. Так как мы не собираемся ничего рисовать, поэтому вторая функция (Update()) у нас будет пустой. Обратите внимание также, что мы прячем курсор при создании окна, а при нажатии Esc, выходим из программы.

Начинаем писать main.cpp
Сперва объявления объектов:

//***********************************************************
// Name: main.c
// Autor: Antiloop
// Desc: Вся оставшаяся программа
//***********************************************************


#include <ddraw.h>
#pragma comment (lib,"dxguid.lib")
#pragma comment (lib,"ddraw.lib")

LPDIRECTDRAW7 lpDD = NULL; //Объект IDirectDraw7
LPDIRECTDRAWSURFACE7 ddsPrimary;//Главная поверхность
LPDIRECTDRAWSURFACE7 ddsBack; //Задний буфер
DDSCAPS2 caps; //Возможности "железа"
DDSURFACEDESC2 ddsd; //Описание поверхности
HRESULT rval; //Что нам функция вернула

Это все объекты DirectX, которые нам понадобятся. Теперь, функция, которая инициализирует DD:

//Инициализация DirectDraw
int Start(HWND hwin)
{
//Создать объект IDirectDraw7
DirectDrawCreateEx(NULL, (VOID**)&lpDD,
IID_IDirectDraw7, NULL);
//Установить полноэкранный режим
lpDD->SetCooperativeLevel(hwin, DDSCL_EXCLUSIVE |
DDSCL_FULLSCREEN | DDSCL_ALLOWREBOOT);
lpDD->SetDisplayMode(640, 480, 16, 0, 0);
//Создать главную поверхность
ZeroMemory(&ddsd, sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE |
DDSCAPS_FLIP | DDSCAPS_COMPLEX;
ddsd.dwBackBufferCount = 1;
lpDD->CreateSurface(&ddsd, &ddsPrimary, NULL);
//Получить задний буфер
ZeroMemory(&caps, sizeof(caps));
caps.dwCaps = DDSCAPS_BACKBUFFER;
ddsPrimary->GetAttachedSurface(&caps, &ddsBack);
return 1;
}

Итак, перед вами способ создания объектов DD. Именно с этого вам надо начинать, когда будете писать свою великую DirectX игру. Обратите внимание, как создается структура буферов: сначала мы создаем главную поверхность, которая является комплексной, то-есть содержит несколько буферов. В нашем случае - главный и задний. Однако этого недостаточно. Вам еще надо получить задний буфер в свое пользование - GetAttachedSurface, что буквально переводится, как "получить прикрепленную поверхность".

Такая комплексная поверхность, состоящая из передней поверхности и задних буферов называется цепью флиппинга (flipping chain). Задних буферов может быть больше, чем один, но не будем забивать себе голову.

Ура, теперь вы умеете менять разрешение экрана и создавать какие-то объекты, однако, как я уже замечал ранее, мало того, что эти объекты создать, их еще надо и правильно уничтожить. Так вот как сделать им это самое харакири я сейчас и покажу.

//Убить все объекты DirectDraw
void End()
{
if (lpDD!=NULL)
{
if (ddsPrimary!=NULL)
{
ddsPrimary->Release();
ddsPrimary=NULL;
}
lpDD->Release();
lpDD=NULL;
}
}

Так как мы ничего не делаем, процедура обновления пуста.

 

Запускаем программу и... Красота!!!! Режим экрана поменялся и с удовльствием наблюдаем противный черный экран (Может и не черный - у кого как).

//Очередной кадр
int Update()
{
  return 1;
}

А как же анимация и все такое?

Гм. Тут то и начинается самое интересное. Как делать анимацию? - на этот вопрос есть множество ответов. Сам факт анимации, как вы должны знать состоит в том, что если вы хотите, например, изобразить ходящего человечка, то вы рисуете его в положении 1, затем поднимаете ему одну ногу, опускаете, поднимаете вторую и так до упора. Потом, вы все кадры помещаете в один файл таким образом, что получается что-то вроде таблицы. Рисуя анимацию на экране, вы вырезаете нужный в данный момент спрайт, помещаете его на экран, стираете, вырезаете другой и в том же духе. Как это работает в DirectDraw: таблицу спрайтов вы загружаете в буфер объекта DD, созданный специально для этого файла, то-есть загружаете набор спрайтов в память. Когда надо рисовать, вы перемещаете прямоугольник с указанными координатами на Задний буфер, с другого буфера, содержащего другой набор спрайтов помещаете другой спрайт на Задний буфер... когда все закончено, делаете "флип" - переводите задний буфер на передний, очищаете задний, повторяете все сначала. Уфф. Наверное я вас утомил.

Проблема тут в том, что допустим вы нарисовали прелестного человечка - вылитого Неверхуда - изобразили его во всех мыслимых и немыслимых позициях и уже собрались делать анимацию, когда вдруг до вас доходит: "Стоп! А как же фон?!" Да, да! Про фон, то мы и забыли. Кому нужен человечек, бродящий по унылому серому творению Микрософта - окну?! OK. Нарисовали прекрасный фон, но ведь спрайт не повторяет формы какого-то ограничивающего контура - он прямоугольный. И место в прямоугольнике не занятое фигурой будет лишним. А вот бы его сделать прозрачным. Сказано - сделано! Берем любой цвет фона, лучше какой-нибудь простой, например, черный, и рисуем Неверхуда какими только угодно цветами, но только нечерным. Все что будет черным - бедет просвечиваться как только что вымытое окно.

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

Источником, как правило является некая оффскринная поверхность. Оффскринная (невидимая) поверхность - это участок видеопамяти, в котором хранятся изображения, с которыми вы собираетесь работать. Частным случаем оффскринной поверхности является задний буфер. Обычно такие поверхности (кроме заднего буфера) служат для хранения набора спрайтов, текстур или еще чего-нибудь. Оффскринный поверхности имеют размеры (ширину и высоту) как и главная поверхность.
Вы всегда можете создавать оффскринные поверхности размером не большим, чем главная поверхность, однако если вы хотите создать невидимую поверхность большего размера, чем задний буфер, вам необходимо сначала проверить, поддерживает ли аппаратура компьютера большие оффскринные поверхности. Но об этом не сейчас.

Итак, как создать простую оффскринную поверхность:

//Сначала, естесственно, объявим поверхность
LPDIRECTDRAWSURFACE7 ddsSample;
DDSURFACEDEDSC2 ddsd //Это структура с описанием поверхности;

//Теперь, указываем, что за поверхность мы создаем
ddsd.lFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT; //Необходимые флаги
//Поверхность оффскринная
ddsd.ddsCaps.lCaps = DDSCAPS_OFFSCREENPLAIN;
ddsd.lHeight = 100; //Высота поверхности в пикселах
ddsd.lWidth = 100; //Ширина поверхности в пикселах

//И наконец, вызываем метод, создающий поверхность
rval=lpDD->CreateSurface(&ddsd, &ddsSample, NULL);
//Здесь, lpDD - объект IDirectDraw7

Вот так создается самая простая поверхность. Если rval==DD_OK, значит все в порядке. Ежели нет... То нет. :)
В этом примере создалась поверхность 100x100. Для того, чтобы уничтожить поверхность, выполните операцию

ddsSample->Release();
ddsSample=NULL;

Нам понадобится создать очень часто применяемую процедуру, которая будет создавать оффскринную поверхность и загружать в нее графический файл. При этом, поверхность будет соответствовать размерам графического файла, поэтому, далее я покажу как можно выудить такую информацию из загруженного файла растра.
Эта функция родилась после долгого синтеза всех возможных источников, включая Microsoft'овские примеры и VB-шные функции и теперь я могу с гордостью утверждать, что сия функция проверена в работе и является самой лучшей! :)

Итак, вот она - функция, создающая поверхность из файла растра:


//*****CreateDDSFromBitmap*****
//Универсальная функция для создания поверхности DD из
//файла растра
//Сама создает оффскринную поверхность с размерами
//подходящими под загруженный bmp-файл.
// Возвращает эту самую созданную поверхность

IDirectDrawSurface7 *CreateDDSFromBitmap(LPCSTR szBitmap)
{
//Внутренние переменные
HBITMAP hbm;
BITMAP bm;
HDC hdcImage;
HDC hdc;
LPDIRECTDRAWSURFACE7 ddsTemp;
DDSURFACEDESC2 ddsdTemp;
DDCOLORKEY ddCK;

//Загрузить картинку из файла в оперативную память
hbm=(HBITMAP)LoadImage(NULL, szBitmap, IMAGE_BITMAP,
0, 0, LR_LOADFROMFILE | LR_CREATEDIBSECTION);
if (hbm==NULL)
{
MSG("Не могу загрузить картинку");
return NULL;
}
//Получить объект картинки
GetObject(hbm, sizeof(bm), &bm);
hdcImage=CreateCompatibleDC(NULL);
HBITMAP tmp_select=(HBITMAP)SelectObject(hdcImage, hbm);

//Создать оффскринную поверхность с размерами,
//подходящими под картинку

ZeroMemory(&ddsdTemp, sizeof(ddsdTemp));
ddsdTemp.dwSize = sizeof(ddsd);
ddsdTemp.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH;
ddsdTemp.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
ddsdTemp.dwWidth = bm.bmWidth;
ddsdTemp.dwHeight = bm.bmHeight;
lpDD->CreateSurface(&ddsdTemp, &ddsTemp, NULL);
ddsTemp->Restore();

//Перенести картинку из памяти на созданную поверхность
ddsTemp->GetDC(&hdc);
StretchBlt(hdc, 0, 0, ddsdTemp.dwWidth,
ddsdTemp.dwHeight, hdcImage, 0, 0,
ddsdTemp.dwWidth, ddsdTemp.dwHeight,
SRCCOPY);
ddsTemp->ReleaseDC(hdc);

//Убрать картинку из памяти
SelectObject(hdcImage, tmp_select);
DeleteObject(hbm);
DeleteDC(hdcImage);

//Установить ключевой цвет - черный.
ddCK.dwColorSpaceLowValue=0;
ddCK.dwColorSpaceHighValue=0;
ddsTemp->SetColorKey(DDCKEY_SRCBLT, &ddCK);

//Вернуть созданную поверхность с загруженной картинкой
return ddsTemp;
};

 

И чего мне с этим делать?

Теперь, у нас есть поверхность, с содержащимся на ней спрайтом. Значит пора этот спрайт показать народу, то есть перевести его на задний буфер, который затем "перевернуть" на передний. (Помните еще процесс?)

Перенос спрайта с одного буфера на другой называется блиттинг (blit - перенос битов). Для этой процедуры лучше всего использовать метод
IDirectDrawSurface7->BltFast (X, Y, srcSurface, srcRECT, Flags)

Обычно BltFast применяют к заднему буферу.
X и Y - это координаты верхнего левого угла на цели, куда будет помещен спрайт
srcSurface - повернхность, содержащая спрайт
srcRECT - структура RECT, содержащая координаты переносимого прямоугольника на источнике
Flags - DDBLTFAST_SRCCOLORKEY - с учетом ключевого цвета, DDBLTFAST_NOCOLORKEY - без него

Структура RECT состоит из элементов Top, Left, Bottom, Right. Что каждый из них озачает, догадаться может любой.

Однако, прежде чем начать переносить, надо сделать еще несколько мелких вещей, например, очистить задний буфер.

Все это описано в следующей части.

 

Приложения

Пример1

[Вверх]

Приятного программирования, Antiloop

Posted: 23.01.2k1
Autor: Antiloop
<anti_loop@mail.ru>

 

 


Проект
Создание Народного Учебника по OpenGL

Участвовать!
Поиск
Найдите статью или файл:


Рассылка
Новости сайта
La Vision в вашем почтовом ящике








Программирование на С++ Delphi и Паскаль
Центр демо-искусства в России