Основы DirectDraw
Microsoft посвящается....
Часть первая: что-то вместо введения
А чо это такое?
Это не чо. Это - вторая редакция моего учебника по DirectDraw, написанная только благодаря стараниям некой малоизвестной осиротевшей и полуразвалившейся корпорации Microsoft.
Думаю вы уже догадались, что дело пойдет о DirectX7. Писать другие части на DirectX7, когда все основы написаны на DirectX 5-6, оказалось довольно проблематично, из-за того, что уже упоминаемая контора решила поменять все интерфейсы с аналогов Си-шных (как в PS-TLB) на перекоряченные, но VB-шные.
Это SE переписано с учетом непреодолимого желания народа юзать седьмой DX, а также дополнено некоторыми уточнениями, пояснениями и полупофиксенными багами :)
Конечно, вы можете посмотреть старую версию учебника, загрузив его здесь
Введение
Ну что ж, привет всем, кто здесь. Надеюсь, это маленькое руководство поможет вам осознать куда катится наш и Ваш любимый Visual Basic. Именно туда... Туда, куда программирующий на Си дотягивается только после многих лет изучения, кряхтения и потения. Ну да ладно, я никого обидеть не хочу, а хочу только показать, как с помощью Visual Basic можно создать свою собственную игру с помощью библиотеки DirectX.
Note: I don't accept any responiblity for anything that might happen to your system.
То есть, я типа не виноват, если Вы вдруг захотите выбросить свою систему в окно под влиянием от прочитанного тут.
А что мне надо, чтобы начать программировать Direct Draw?
Прежде всего понимание, что детские игры закончились. Тут вам придется напрячь все свое умение, как программиста на Basic'e, чтобы доказать, что не только CЮшники могут думать на бумажке. Да-да, именно на бумажке. Мне пришлось разрабатывать мои супер графические Engin'ы именно на бумаге. Почему? Да потому что существует список достоинств и недостатков программирования DirectX на VB. Советую прочитать по крайней мере часть "недостатки" прежде, чем отправиться в путь.
Достоинства :)
- Программировать легко. То, что на Си требует долгого и продолжительного изучения, на VB легко и доступно.
- Краткость кода
- Быстрая компиляция. Не надо ждать полчаса , пока редактор будет перекомпилировать вышу программу из-за какого-то исправления. Особенно хорошо, когда у вас не Athlon 1,1
Недостатки :(
- Все таки графика не такая уж и быстрая, так что Квейка у вас скорее всего не получится. (Хотя если получится, быстрее несите показывать мне)
- Чуть что не так - ругается и выкидывает в систему
- Если какая-то ошибка в ходе выполнения, то предыдущий пункт. Причем, программа зависает, на кнопки не реагирует, Debug выполнить невозможно, а можно только сбросить программу по C-A-D вместе с VB
- Если вы хотите срочно прервать программу, то просто так вам сделать это не удастся. Надо обязательно правильно сделать Terminate объектам DirectDraw, иначе происходит пункт два
- Вот и исходя изо всех предыдущих пунктов, приходится сначала писать программу ручками на бумаге, смотреть, нет ли ошибки, правильно ли организован выход, а потом уж, глубоко вздохнув, нажимать клавишу F5
Вот такие вот дела. Но если вы думаете, что теперь можно приступать к работе, то вы снова ошибетесь. Просто так вот взять и программировать DirectX вам не удастся. Прежде всего, вам узнать про одного человека - его имя Patrice Scribe и он вроде как Француз. Точно говорить не буду - сам не знаю. Так вот, именно этот хороший человек придумал библиотеки для работы с DirectX на VB, за что огромное ему спасибо. Так вот, раз уж мы решили использовать DirectX7, тогда его TLB для DirectX 5-6 нам не понадобятся. Однако, было бы неплохо использовать Win32 TLB. В этой библиотеке описаны всевозможные типы, которые встречаются при работе с DirectX. Так-что советую вам скачать эту библиотеку с моего сайта.
Второй шаг при подготовке к работе - посмотрите, а установлен ли у вас DirectX7? Нет???!!! Бегите в магазин и покупайте диск с супер-навороченной игрой Half-Life 2 World с датой выпуска еще через полгода, наверняка там уже будет DX7. А если серьезно, то найдите где-нибудь дистрибутив DX7 и учстановите его себе.
И не в коем случае не поддавайтесь на провокации Microsoft, пытающихся заставить вас скачать DirectX SDK с ихнего сайта! Знаете, что это такое? Это куча макулатуры, примеров и бесполезных программ, которые если и будут кому-то полезны, но только не начинающим. Причем все это в их любимом формате HTML Help, которые вместо того, чтобы сжимать текст раздувает его до размеров видеоклипа.
Вам не нужен SDK, чтобы полноценно пользовать DirectX7 из Visual Basic!!!!!!!!
Ну вот, теперь вроде бы и все. Осталось только сказать, что у меня на данный момент установлен Visual Basic 6.0 и все проги я тестировал на нем. Кое-кто в Сети использует и VB5 (в основном америкашки), так что на нем тоже пойдет. А вот уже что касается более ранних версий, то могу сказать владельцам таковых, что апргейд вам не только не повредит но и пойдет на пользу в несколько раз.
Ага, теперь я могу уже начать!?
Прелестно, вы доставли необходимые библиотеки, и в полной вооруженной готовности хотите начать творить. У меня такой вопрос. А вы знаете, как устроен и как работает DirectDraw? Если знаете, можете с умным видом пропустить следующий абзац. Если нет, вот вам маленькое разъяснение.
Что касается того, как DD устроен могу сказать только одно: понятия не имею. Зато как работает - знаю. И вам советую. Итак, самым главным, что вы должны знать, является то как проиходит анимация. Существуют такие буфера или поверхности (surface), с помощью которых все это безобразие происходит. Существует два основных буфера: передний и задний. Передний - это видимый буфер, он представляет собой то, что вы видите на экране. Задний буфер - невидимый на нем вы рисуете. Отсюда напрашивается правильный вопрос: "На кой мне рисовать на невидимом буфере?" Ответ будет очень ученым. Представьте себе, как работает ваш монитор. Сзади стекла установлена магнитная пушка, плюющаяся электронами из трубки. (У меня по физике не очень хорошо было, так что не надо кидаться всякими тухлыми продуктами в мою сторону) Эти электроны создают изображение на экране, причем луч трубки имеет тенденцию идти с левого верхнего ула по строчкам и вниз. Соответственно, он кончает прорисовку экрана в правом нижнем углу, после чего, уже ничего не рисуя, он направляется назад в стартовую позицию. Это действие называется Vertical Blank. Так как делается это с бешеной скоростью, вы не замечаете, как ваш экран обновляется.
Так вот, пока экран занят своими делами, вы рисуете на невидимом заднем буфере кадр номер 1 повешения любимого учителя по информатике, и затем, пока трубка преходит на начало следующего цикла обновления, быстренько шлепаете сцену на передний буфер, с которого пушка теперь будет обновлять экран, и пока происходит обновление, подготавливаете кадр 2. Теоретически, вы можете рисовать вашу анимацию со скоростью обновления экрана. То-есть какая установлена у вашего монитора частота, с такой и будет происходит обновление экрана, например 70 раз в секунду. Практически же тут происходит много накладок. Во-первых, на это влияет быстродействие компьютера, то-есть пока вы обработаете очередной кадр, обновление экрана пройдет уже несколько раз, и вы начинаете проигрывать в скорости. Во-вторых, если дядюшка Си работает сам по себе и в десять раз быстрее, то VB постоянно опирается на свои подпорки - Runtime-библиотеки, с помощью которых он, собственно говоря и работает. Это занимает время, и последствия очевидны.
Ну вот, обобщая, вывожу принцип действия. Сначала вы рисуете сцену на заднем буфере, а затем переводите ее на передний буфер, пока он прорисовывается, вы не теряя времени очищаете задний буфер и снова рисуете на нем следующий кадр. Потом переворачиваете... И так до упора.
Для особо вредных объясняю, что если бы вы собирали сцену на переднем буфере без мороки со флиппингом, как с BitBlt тогда бы было такое мерцание, что построить приложение, которое бы привлекало пользователя было бы на VB невозможно.
Ну а теперь-то ???!!!...
Да! Теперь можно. Начнем с самого простого. Создадим объект DirectDraw и установим разрешение экрана как 640x480x16, полюбуемся сотворенным и при нажатии любой клавиши выйдем обратно в редактор.
Прежде всего добавьте в разделе References необходимые TLB. Ну а далее наберите программу. Заметьте, что никакие контролы на форму ставить не надо. И вообще, форма тут совсем ни при чем, можете ее хоть квадратной сделать - все равно, потому что работать программа будет в полноэкранном режиме.
Итак, начнем с объявления объектов DD:
Private dx As New DirectX7 'Главный объект DirectX Private dd As DirectDraw7 'Объект DirectDraw Private ddsPrimary As DirectDrawSurface7 'Передняя поверхность Private ddsBack As DirectDrawSurface7 'Задний буфер Private ddsd As DDSURFACEDESC2 'Тип, с описанием поверхности Private caps As DDSCAPS2 'Тип с возможностями железа (Hardware)
Теперь, добавьте в процедуру Form_Load следующие строки:
'Создаем объекты и устанавливаем режим экрана 'Создать объект DirectDraw Set dd = dX.DirectDrawCreate("") 'Полноэкранный Call dd.SetCooperativeLevel(me.Hwnd, DDSCL_EXCLUSIVE Or DDSCL_FULLSCREEN Or DDSCL_ALLOWREBOOT) ' эксклюзивный режим с разрешенным CAD Call dd.SetDisplayMode(640, 480, 16, 0, DDSDM_DEFAULT) 'Вот такой режим экрана 'Теперь создаем главную поверхность с одним задним буфером 'Заполняем описание создаваемой поверхности 'Поверхность с задним буфером ddsd.lFlags = DDSD_CAPS Or DDSD_BACKBUFFERCOUNT 'Комплексная главная поверхность с возможным флиппингом ddsd.ddsCaps.lCaps = DDSCAPS_PRIMARYSURFACE Or DDSCAPS_FLIP Or DDSCAPS_COMPLEX ddsd.lBackBufferCount = 1 'Один задний буфер (схема двойной буферизации) 'Двойная буферизация - главный буфер и один задний буфер Set ddsPrimary = dd.CreateSurface(ddsd) 'Создать поверхность 'Получить BackBuffer caps.lCaps = DDSCAPS_BACKBUFFER Set ddsBack = ddsPrimary.GetAttachedSurface(caps)
Итак, перед вами способ создания объектов DD. Именно с этого вам надо начинать, когда будете писать свою великую DirectX игру. Обратите внимание, как создается структура буферов: сначала мы создаем главную поверхность, которая является комплексной, то-есть содержит несколько буферов. В нашем случае - главный и задний. Однако этого недостаточно. Вам еще надо получить задний буфер в свое пользование - GetAttachedSurface, что буквально переводится, как "получить прикрепленную поверхность".
Такая комплексная поверхность, состоящая из передней поверхности и задних буферов называется цепью флиппинга (flipping chain). Задних буферов может быть больше, чем один, но не будем забивать себе голову.
Ура, теперь вы умеете менять разрешение экрана и создавать какие-то объекты, однако, как я уже замечал ранее, мало того, что эти объекты создать, их еще надо и правильно уничтожить. Так вот как сделать им это самое харакири я сейчас и покажу. Добавьте в процедуру Form_Unload следующий код:
Call dd.RestoreDisplayMode 'Восстановить разрешение экрана Call dd.SetCooperativeLevel(0, DDSCL_NORMAL) 'Обратно в оконный режим 'Внимание! Сначала убиваем оффскринные буфера, потом задний буфер, 'ПОТОМ главную поверхность и В ПОСЛЕДНЮЮ ОЧЕРЕДЬ объект DirectDraw Set ddsBack = Nothing Set ddsPrimary = Nothing Set dd = Nothing
Осталось только предусмотреть выход для того случая, если вы в
полноэкранном режиме не увидите окна. Я тестил на разных компьютерах и на
всех было по разному.
Добавьте в программу процедуру Form_KeyPress:
Private Sub Form_KeyPress(KeyAscii As Integer) Call Form_Unload(0) End End Sub
Запускаем программу и... Красота!!!! Режим экрана поменялся и с удовльствием наблюдаем противный черный экран (Может и не черный - у кого как).
А как же анимация и все такое?
Гм. Тут то и начинается самое интересное. Как делать анимацию? - на этот вопрос есть множество ответов. Сам факт анимации, как вы должны знать состоит в том, что если вы хотите, например, изобразить ходящего человечка, то вы рисуете его в положении 1, затем поднимаете ему одну ногу, опускаете, поднимаете вторую и так до упора. Потом, вы все кадры помещаете в один файл таким образом, что получается что-то вроде таблицы. Рисуя анимацию на экране, вы вырезаете нужный в данный момент спрайт, помещаете его на экран, стираете, вырезаете другой и в том же духе. Как это работает в DirectDraw: таблицу спрайтов вы загружаете в буфер объекта DD, созданный специально для этого файла, то-есть загружаете набор спрайтов в память. Когда надо рисовать, вы перемещаете прямоугольник с указанными координатами на Задний буфер, с другого буфера, содержащего другой набор спрайтов помещаете другой спрайт на Задний буфер... когда все закончено, делаете "флип" - переводите задний буфер на передний, очищаете задний, повторяете все сначала. Уфф. Наверное я вас утомил.
Проблема тут в том, что допустим вы нарисовали прелестного человечка - вылитого Неверхуда - изобразили его во всех мыслимых и немыслимых позициях и уже собрались делать анимацию, когда вдруг до вас доходит: "Стоп! А как же фон?!" Да, да! Про фон, то мы и забыли. Кому нужен человечек, бродящий по унылому серому творению Микрософта - окну?! OK. Нарисовали прекрасный фон, но ведь спрайт не повторяет формы какого-то ограничивающего контура - он прямоугольный. И место в прямоугольнике не занятое фигурой будет лишним. А вот бы его сделать прозрачным. Сказано - сделано! Берем любой цвет фона, лучше какой-нибудь простой, например, черный, и рисуем Неверхуда какими только угодно цветами, но только нечерным. Все что будет черным - бедет просвечиваться как только что вымытое окно.
Программно это делается просто. Соответствующему свойству присваивается соответствующее значение цвета, затем вызывается функция, которая совершает побитовый перенос изображения всех цветов кроме фонового с источника на рисуемую поверхность. Вот и все.
Источником, как правило является некая оффскринная поверхность.
Оффскринная (невидимая) поверхность - это участок видеопамяти, в котором
хранятся изображения, с которыми вы собираетесь работать. Частным случаем
оффскринной поверхности является задний буфер. Обычно такие поверхности
(кроме заднего буфера) служат для хранения набора спрайтов, текстур или
еще чего-нибудь. Оффскринный поверхности имеют размеры (ширину и высоту)
как и главная поверхность.
Вы всегда можете создавать оффскринные
поверхности размером не большим, чем главная поверхность, однако если вы
хотите создать невидимую поверхность большего размера, чем задний буфер,
вам необходимо сначала проверить, поддерживает ли аппаратура компьютера
большие оффскринные поверхности. Но об этом не сейчас.
Итак, как создать простую оффскринную поверхность:
'Сначала, естесственно, объявим поверхность Dim ddsSample as DirectDrawSurface7 Dim ddsd as DDSURFACEDEDSC2 'Это структура с описанием поверхности 'Теперь, указываем, что за поверхность мы создаем ddsd.lFlags = DDSD_CAPS Or DDSD_WIDTH Or DDSD_HEIGHT 'Необходимые флаги ddsd.ddsCaps.lCaps = DDSCAPS_OFFSCREENPLAIN 'Поверхность оффскринная ddsd.lHeight = 100 'Высота поверхности в пикселах ddsd.lWidth = 100 'Ширина поверхности в пикселах 'И наконец, вызываем метод, создающий поверхность Set ddsSample=dd.CreateSurface(ddsd) 'Здесь, dd - объект DirectDraw7
Вот так создается самая простая поверхность. В этом примере создалась поверхность 100x100. Для того, чтобы уничтожить поверхность, выполните операцию
ddsSample=Nothing
Нам понадобится создать очень часто применяемую процедуру, которая
будет создавать оффскринную поверхность и загружать в нее графический
файл. При этом, поверхность будет соответствовать размерам графического
файла, поэтому, далее я покажу как можно выудить такую информацию из
загруженного файла растра.
Прежде, чем приступить, сделаю примечание: в
дальнейшем коде вы увидите строчку наподобии "Win32.BITMAP". Это
означает использование струкутры или функции из той самой библиотеки
Win32.tlb, которую я посоветовал вам скачать. Вы можете, конечно сами
определить все подобные функции и структуры, пользуясь API Loader,
но зачем изобретать велосипед второй раз?! Поэтому, подключайте Win32,
если хотите пользоваться следующим кодом без изменений
Итак, вот она - функция, создающая поверхность из файла растра:
Function CreateDDSFromFile(ByVal FileName as String, Optional CKey as Long=0) as DirectDrawSurface7 'Объявления '============ Dim dds as DirectDrawSurface7 'Временная вспомогательная поверхность Dim ddsd as DDSURFACEDESC2 'Описание временной поверхности Dim StorePic as stdPicture 'Временное хранилище картинки Dim Bmp as Win32.Bitmap 'Тип BITMAP, описывающий растровое изображение Dim hDCPicture as Long, hDCSurface as Long 'DC картинки и поверхности Dim ddCK as DDCOLORKEY 'Для установки ключевого цвета 'Загружаем картинку и получаем объект картинки '========================================== StorePic=LoadPicture(FileName) 'Загружаем картинку из файла 'Получаем описание картинки в структуру BITMAP Call Win32.GetObject(StorePic.Handle, Len(Bmp), Bmp) 'Получаем DC картинки hDCPicture=Win32.CreateCompatibleDC(ByVal 0&) Call Win32.SelectObject(hDCPicture, StorePic.Handle) 'Теперь, создаем поверхность '========================== ddsd.lFlags = DDSD_CAPS Or DDSD_WIDTH Or DDSD_HEIGHT 'Необходимые флаги ddsd.ddsCaps.lCaps = DDSCAPS_OFFSCREENPLAIN 'Поверхность оффскринная ddsd.lHeight = bmp.bmHeight 'Высота поверхности как у картинки ddsd.lWidth = bmp.bmWidth 'Ширина поверхности как у картинки 'Вызываем метод, создающий поверхность Set dds=dd.CreateSurface(ddsd) 'dd - глобальный объект DirectDraw7 'Переводим картинку на поверхность '================================ Call dds.Restore 'Подготовка к прямому доступу к поверхности hDCSurface = dds.GetDC 'Это копирует картинку в буфер Call Win32.StretchBlt(hDCSurface, 0, 0, bmp.bmWidth, bmp.bmHeight, hDCPicture, 0, 0, bmp.bmWidth, bmp.bmHeight, SRCCOPY) 'Конец прямого доступа к поверхности Call dds.ReleaseDC(hDCSurface) 'Уничтожить объект картинки - больше не нужен Call Win32.DeleteDC(hDCPicture) 'Устанавливаем ключевой цвет '=========================== ddCK.low=CKey 'Работает правильно только в 24-битном цвете ddCK.high=ddCK.low 'Но для простых случаев пойдет Call ddы.SetColorKey(DDCKEY_SRCBLT, ddCK) 'Возвращаем объект '================== Set CreateDDSFromFile=dds End Function
И чего мне с этим делать?
Теперь, у нас есть поверхность, с содержащимся на ней спрайтом. Значит пора этот спрайт показать народу, то есть перевести его на задний буфер, который затем "перевернуть" на передний. (Помните еще процесс?)
Перенос спрайта с одного буфера на другой называется блиттинг
(blit - перенос битов). Для этой процедуры лучше всего использовать
метод
DirectDrawSurface7.BltFast (X, Y, srcSurface, srcRECT,
Flags)
Обычно BltFast применяют к заднему буферу.
- X и Y - это координаты верхнего левого угла на цели, куда будет помещен спрайт
- srcSurface - повернхность, содержащая спрайт
- srcRECT - структура RECT, содержащая координаты переносимого прямоугольника на источнике
- Flags - DDBLTFAST_SRCCOLORKEY - с учетом ключевого цвета, DDBLTFAST_NOCOLORKEY - без него
Структура RECT состоит из элементов Top, Left, Bottom, Right. Что каждый из них озачает, догадаться может любой.
Однако, прежде чем начать переносить, надо сделать еще несколько мелких вещей, например, очистить задний буфер.
Все это описано в следующей части.