Это не чо. Это - вторая
редакция моего учебника по 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.
То есть, я типа не виноват, если Вы вдруг захотите выбросить свою систему
в окно под влиянием от прочитанного тут.
Прежде всего понимание,
что детские игры закончились. Тут вам придется напрячь все свое умение, как
программиста на Basic'e, чтобы доказать, что не только CЮшники могут думать на
бумажке. Да-да, именно на бумажке. Мне пришлось разрабатывать мои супер
графические Engin'ы именно на бумаге. Почему? Да потому что существует список
достоинств и недостатков программирования DirectX на VB. Советую прочитать по
крайней мере часть "недостатки" прежде, чем отправиться в путь.
Достоинства :)
Недостатки :(
Вот такие вот дела. Но
если вы думаете, что теперь можно приступать к работе, то вы снова ошибетесь.
Просто так вот взять и программировать 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 следующие строки:
'Создаем объекты и устанавливаем режим
экрана
Set dd = dX.DirectDrawCreate("") 'Создать объект DirectDraw
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. Что каждый из них озачает, догадаться может
любой.
Однако, прежде чем начать
переносить, надо сделать еще несколько мелких вещей, например, очистить задний
буфер.
Итак, мы продолжаем. Это
вторая статья из серии и она посвящена в основном черновой работе, которую
необходимо проделать, чтобы потом, когда начнется самое интересное, не
отвлекаться по мелочам. Что же мы будем делать?
Прошлая часть являлась по
своей сути введением и не несла никакой практической нагрузки, хотя я и научил
вас создавать объекты DirectDraw и даже менять разрешение экрана. Один вопрос:
ну и что вы со всем этим будете делать?!
Итак. в этой части я
хочу, чтобы вы создали модуль, назовем его mdlDirectDraw7, в который будете
пихать все стандартные процедуры, чтобы потом забыв о том, как они работают
пользоваться ими с чистой душой. В ИНете есть уже готовые модули (libDD,
например), однако, создав свой модуль, вы будете более подкованы и сможете сами
писать более гибкий код.
Сперва, в наш модуль надо
сбросить все переменные и ссылки на объекты, относящиеся к DD, затем, туда
будут записаны все функции, служащие для работы, создания каких-либо обектов,
установок, уничтожения и т. п. Далее, привожу начальный текст этого модуля с
комментариями. Почему начальный? Просто сейчас, этот модуль будет на первом
этапе работы, то-есть в него мы запишем минимальное количество функций,
требуемых для работы. Затем, по мере дальнейшего изучения DirectDraw и DirectX,
вы сможете добавлять в этот модуль все новые и новые функции.
Я не привожу определения
функций и констант Win32 API, как и в прошлой части, мы будем пользоваться
библиотекой Win32 TLB
'======================================
' mdlDirectDraw7 - основы работы с DirectDraw7
'======================================
'Объявления объектов DirectDraw
Public dx As New DirectX7 'Объект DirectX
Public dd As DirectDraw7 'Объект DirectDraw
Public ddsPrimary As DirectDrawSurface7 'Главная поверхность
Public ddsBack As DirectDrawSurface7 'Задний буфер
Public ddsd As DDSURFACEDESC2 'Структура с описанием поверхности
Private ddsdStore As DDSURFACEDESC2 'Вспомогательная структура описания
Public rc As RECT 'Структура RECT для блиттинга
Public caps As DDSCAPS2 'Структура с аппаратными возможностями
'Некоторые другие переменные
Public Running As Boolean 'Программа все еще работает?
Следующая проведура будет
вызываться самой первой в будущих программах. Она инициализирует DirectDraw в
полноэкранном режиме. То есть создает объект DirectDraw, устанавливает режим
дисплея в dispXxdispYxdispColor
'Инициализация DirectDraw в
полноэкранном режиме
'============================================
Public Sub CreateDDFullscreen(srcHwnd As Long, ByVal dispX As Long, ByVal dispY
As Long, ByVal dispColor As Long)
'Создаем объекты и устанавливаем режим
Set dd = dx.DirectDrawCreate("")
Call dd.SetCooperativeLevel(srcHwnd, DDSCL_EXCLUSIVE Or DDSCL_FULLSCREEN Or
DDSCL_ALLOWREBOOT)
Call dd.SetDisplayMode(dispX, dispY, dispColor, 0, DDSDM_DEFAULT)
'Создаем flipping chain
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)
End Sub
Если создали, надо
уничтожить! Вот процедура, убивающая DirectDraw. Внимание: оффскринные
поверхности надо уничтожать отдельно!
'Уничтожить DirectDraw
'====================
Public Sub DestroyDD()
'Восстанавливаем
режим
Call dd.RestoreDisplayMode
Call dd.SetCooperativeLevel(0, DDSCL_NORMAL)
'Убиваем
поверхности, а ПОТОМ
объект DirectDraw
Set ddsBack = Nothing
Set ddsPrimary = Nothing
Set dd = Nothing
End Sub
Следующая функция
разбиралась в прошлой главе - загрузка битмапа из файла
'Создание
поверхности
из
картинки
в
файле
'======================================
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
Set dds=nothing
End Function
Важная вещь - очистка
буфера. Обычно очищается задний буфер перед началом сборки на нем новой сцены.
Если вы не очистите буфер, а потом попробуете "перевернуть" его на
главную поверхность, получите незабываемое впечатление об использовании
видеопамяти. Попробуйте!
'Очистка заданного буфера
'========================
Public Sub ClearBuffer(ByVal dds As DirectDrawSurface7)
Dim Color As Long
Color = RGB(0, 0, 0) 'Задаем черный цвет
dds.GetSurfaceDesc ddsdStore 'Получить описание очищаемой поверхности
'Заполняем структуру RECT, так, чтобы она охватывала всю поверхность
With rc
.Top = 0
.Left = 0
.Right = ddsdStore.lWidth 'Вот зачем нам надо было описание поверхности
.Bottom = ddsdStore.lHeight 'Высота и ширина подгоняются под нее
End With
dds.BltColorFill rc, Color 'Заполняем черным цветом область, указанную в RECT
End Sub
Понятно, что вы можете
очищать не всю поверхность, а только нужные области для улучшения
быстродействия.
Ура! Все приготовления закончены
и теперь можно чего-нибудь нарисовать.
Наша программа перейдет в
режим экрана 800x600x16, очистит задний буфер и нарисует на нем два круга,
взятых из файла. Круги будут частично наложены один на другой, чтобы вы поняли,
что такое ColorKey и прозрачный цвет.
Для упрощения задачи, не
будем сейчас делать цикл прорисовки, а просто один раз совершим блиттинг за
задний буфер.
Я подразумеваю, что вы
уже подготовили модуль mdlDirectDraw7 и подключили его к своему проекту. Также,
подключите библиотеки DX7VB и Win32.
Если все готово, тогда
начинаем:
'Объявления
Dim Exited As Boolean 'Флаг, чтобы мы только один раз уничтожали объекты
Dim ddsPic1 As DirectDrawSurface7 'Поверхность под картинку
Позаботимся о
благополучном выходе уже сейчас. Программа закончится, когда пользователь
нажмет на любую клавишу.
Private
Sub Form_KeyPress(KeyAscii As Integer)
mdlDirectDraw7.DestroyDD 'Выход
Exited = True
End
End Sub
Private
Sub Form_Unload(Cancel As Integer)
If Exited = False Then mdlDirectDraw7.DestroyDD 'Во
избежании
ошибок
End Sub
Теперь инициализация и
загрузка картинки. Это будет происходить при запуске формы.
Private Sub Form_Load()
Call mdlDirectDraw7.CreateDDFullscreen(frmMain.hWnd, 800, 600, 16)
'Инициализация
Set ddsPic1 = mdlDirectDraw7.CreateDDSFromFile(App.Path &
"\ball.bmp") 'Загрузить картинку
Exited = False
Run 'Запускаем прорисовку
End Sub
Последним вызовом в этой
подпрограмме мы вызываем процедуру рисования. Обычно именно здесь я размещаю
свои "движки", то есть циклы, постоянно обновляющие экран и
вызывающие необходимые расчеты. Сейчас "движок" мы делать не будем,
потому что задача наша предельно проста - нарисовать два шарика.
Private Sub Run()
DoEvents 'Обязательно, когда делаете цикл прорисовки
mdlDirectDraw7.ClearBuffer ddsBack 'Чистим полотно
'Заполняем структуру RECT координатами
рисунка на оффскринном буфере
rc.Left = 0
rc.Top = 0
rc.Right = 100
rc.Bottom = 100
'Рисуем два шарика
Call ddsBack.BltFast(10, 10, ddsPic1, rc, DDBLTFAST_SRCCOLORKEY Or
DDBLTFAST_WAIT)
Call ddsBack.BltFast(60, 60, ddsPic1, rc, DDBLTFAST_SRCCOLORKEY Or
DDBLTFAST_WAIT)
'Делаем Flip
Call ddsPrimary.Flip(ddsBack, DDFLIP_WAIT)
'Пока все
End Sub
Вот и вся программа.
Для blitting'а, то есть
перевода шаблона спрайта на задний буфер используется метод BltFast. Он
достаточно быстр и легок в использовании. В качестве параметров у метода
задается координаты верхнего левого угла на поверхности на которую происводится
перевод, затем указывается исходный буфер, далее, передается структура RECT. В
этой сруктуре указана позиция и размеры прямоугольника со спрайтом. И наконец,
передаются флаги, влияющие на работу метода. Флаг DDBLTFAST_SRCCOLORKEY
говорит, что используется ключевой цвет, указанный для источника, а флаг
DDBLTFAST_WAIT говорит, что надо ждать до следующего обновления экрана лучевой
трубкой.
Метод Flip применяется
для главной поверхности. В качестве параметра ему передается исходная
поверхность и флаги, влияющие на работу. Флаг DDFLIP_WAIT говорит, что нельзя
совершать какие-либо действия с поверхностью до того, как флиппинг завершится.
Итак, ничего сложного
нет.
Да уж давно бы пора! -
подумают некоторые. Ну ладно, не злитесь, в этой главе мы разработаем и
претворим в жизнь самую настоящую DirectX'овую игруху!
Не будем очень
оригинальны и сделаем PingPong; ну знаете, такая штука, где ездят две ракетки и
пинают мячик стараясь чтобы противник по нему не попал. Так вот, мы постараемся
сделать полностью (ну или почти) работающий ПингПонг для одного игрока против
компьютера. Как подключить второго игрока я надеюсь вам будет абсолютно понятно
- это останется в качестве упражнения. Кроме того, для упрощения задачи я пока
не буду рассматривать вывод текстовых сообщений - это в раздел эффектов в
следующую часть. А для усложнения задачи - выработаем такой алгоритм, в котором
поле у нас будет представлено в виде матрицы в которой есть элементы 0 -
свободно и 1 - блок. От блоков мячик соответственно отскакивает. Это сделано
для того, чтобы мяч мог передвигаться независимо от расположения блоков, вам
надо будет только поменять ссответствующее значение в матрице. Это уже будет
нечто действительно похожее на движок и вы можете делать различные игровые
поля.
Итак, приступаем
Ну с самого начала, еще даже перед тем как начать рисовать графику, надо решить один такой
маленький вопросик: а какой у нас
будет ПингПонг: вертикальный или горизонтальный? Я выбрал вертикальный; не
спрашивайте почему, вы всегда сможете сделать другой.
Разрешение экрана будет
640x480x16, а размер матрицы - 12*16, то есть одна клетка - 40*40 пикселей.
Ну вот, а теперь можно
рисовать графику.
Процесс этот утомительный
только по одной причине: кропотливость. Сначала надо выбрать размерность
каждого объекта: бита будет размером 80*20, мяч - 20*20, а блок - 40*40, чтобы
занимал одну клетку. Размеры мяча и биты абсолютно произвольные, но если вы
захотите взять другие, учтите, что придется исправлять все нижеследующие
математические расчеты.
Итак, зайдите куда-нибудь
типа FreeHand8 и сотворите шедевр в стиле Малевича! Каждый объект рисуйте по
отдельности и сохраняйте в 24-разрядном файле BMP. Не забывайте, что
потребуется две разных биты. Нарисовали - вперед в Painbrush! Выбирайте пункт
меню "Вставить из файла" и скомпонуйте все спрайты на одном рисунке.
Затем подгоните размер рисунка под границы спрайтов (Пожалуйста, не делайте
рисунок больше, чем главная поверхность. Это поддерживают не все видеокарты).
Да! И не забудьте, что нам потребуется "прозрачный" цвет. Я взял для
этого черный (RGB(0,0,0)), поэтому контуры на рисунках спрайтов у меня вроде
как черные, а на самом деле нет (RGB(12,12,12)).
Я нарисовал вот такую картину и
далее подразумевается, что координаты расположения спрайтов будут как у меня.
Примечание: как всегда я где-то напортачил,
поэтому размерности спрайтов у меня с погрешностями +/- 1 пиксел.
Давным давно, еще в
школе, нам преподавали бейсик (простой!). Поэтому я еще некоторое время то и
дело боролся с искушением свалить все в одну кучу. Это не только непрактично,
но и просто как-то неэстетично и плохо выглядит, поэтому давайте обсудим
структуру программы.
Заместо того, чтобы
вводить кучу переменных, для описания объектов используем типы BallInf и
BetInf, описывающие соответственно мячик и биту. Создадим экземпляр мячика Ball
и два экземпляра биты Bet1 и Bet2 "As BetInf".
Модуль mdlDirectDraw7 у
нас уже есть (ведь есть, правда?! Если нет, то сюда),
поэтому помимо формы frmBall код будет размещаться в модуле mdlBall, в котором
будут функции и процедуры, выполняющие основные расчеты и размещение объектов
на экране. В моих принципах освобождать модули кода форм от лишних процедур, не
относящихся непосредственно к обработке их событий поэтому я жестоко и
беспощадно БУДУ убирать все служебные процедуры в другие модули. Можете теперь
в меня чем-нибудь кинуть.
Программу будем писать
постепенно, создавая сначала костяк, а потом наращивая все более и более
завороченные фичи (не баги!).
Для начала, создаем новый
проект, добавляем в него нужные библиотеки (как всегда - dx7vb и win32). Дадим
имя проекту - VBPong, переименуем главную форму в frmBall, добавим все
вышеперечисленные модули и сохраним все хозяйство в одну новую папку. В нее же
надо перекопировать графический файл, который мы предусмотрительно приготовили
заранее.
Теперь, все готово и
можно приступать к написанию кода.
Начнем с инициализации
экрана. В модуль кода формы frmBall напишем следующие строки, которые будут
управлять экраном во время работы программы и обеспечивать нормальный выход. По
ходу работы я буду все пояснять.
Private
Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)
Select Case KeyCode
Case vbKeyEscape 'Выйти из
программы
mdlDirectX.Running=False
End Select
End Sub
'При
выходе
надо
уничтожать
все
объекты DirectX
Private Sub Form_Unload(Cancel As Integer)
mdlDirectDraw7.DestroyDD
End Sub
Private
Sub Form_Load()
mdlDirectDraw7.Running = True
'Инициализация DirectDraw
mdlDirectDraw7.CreateDDFullscreen Me.hWnd, 640, 480, 16
Set ddsPic = mdlDirectDraw7.CreateDDSFromFile(App.Path &
"\tball.bmp") 'Создаем буфер
с
нашей
графикой
'Прозрачный
цвет - черный, буфер
объявляется
чуть
позже
'Следующие строки назовем "Регион
1". Сюда будут помещаться начальные определения игры
'------- Пока пусто --------
'------- Конец региона 1 --------
'Теперь, создаем главный цикл
прорисовки экрана
While mdlDirectDraw7.Running = True
DoEvents 'Будем жалостливы к системе
Call mdlDirectDraw7.ClearBuffer(ddsBack) 'Чистим полотно
' Это место назовем "Регион
2". Здесь надо разместить последовательность рисования всех
' объектов на очередном витке цикла
'------- Пока пусто --------
' ------- Конец региона 2 ---------
'Теперь, завершающие действия витка
Call ddsPrimary.Flip(ddsBack, DDFLIP_WAIT) ' Переводим задний буфер на передний
Wend
Unload Me
End Sub
И на этом код формы пока
заканчивается. Внимательно присмотрясь к тому что будет делать программа, вы
увидите, что после инициализации, она войдет в цикл, в котором будет находиться
до самого своего конца. Идея такова, чтобы в этом цикле заставить программу
отображать все нужные нам объекты каждый раз на заданных местах. Места же эти
каждый раз перевычисляются на очередном витке цикла. Таким образом, вычисления
сводятся к определению новых координат объекта, заданию структуры RECT, о
которой говорилось в прошлой главе, и применению метода BltFast. Все эти
действия помещаются в "Регионе 2" и будут занимать все дальнейшее
время нашей работы в этой части. Теперь я думаю вам стало ясно, почему я был
категорически против размещения всей программы в модуле кода формы. Почти все
действия, относящиеся к DirectX мы уже сделали и теперь приступаем к
математике.
Переходим в модуль
mdlBall. Сперва, как я уже говорил, определим структуры BallInf и BetInf, а
также создадим нужные переменные.
Типы выглядят так:
Type BetInf
X As Integer 'координата на оси X
SpeedX As Integer 'Приращение следующего шага на оси X
End Type
Type
BallInf
X As Integer 'Координаты на
осях
Y As Integer
SpeedX As Integer 'Приращения на
осях
SpeedY As Integer
End Type
'Две
биты
и
мяч
Public Bet1 As BetInf
Private Bet2 As BetInf
Private Ball As BallInf
'Игровое поле
Private Map(11, 15)
'Буфер
для
спрайтов
Public ddsPic As DirectDrawSurface7
Инициализация закончена.
Текущее положение ракетки зависит от координаты X. Y у обеих ракеток постоянная
- у нижней - 460, у верхней - 0. Чтобы посчитать новое положение ракетки исходя
из старого, надо прибавить к координате X приращение SpeedX. Если приращение
отрицательное, то бита двигается влево и наоборот. Bet1 управляется игроком,
поэтому приращение задается нажатием клавиши "влево" или
"вправо". Если клавиша отпускается, то приращение равно нулю.
Приращение Bet2 вычисляется компьютером.
Положение мяча задается
координатами X и Y. У него также существуют два осевых приращения.
Карта размера 16*12 (16 -
по-горизонтали, 12 - по-вертикали). Учтите, что массив начинается с нуля и
сначала идут строки.
Давайте напишем функцию,
которая сбрасывает положения мяча и бит до начальных.
Public
Sub Reset()
Bet1.X = 320
Bet1.SpeedX = 0
Bet2.X
= 320
Bet2.SpeedX = 0
Ball.X
= 320
Ball.Y = 240
Ball.SpeedY = -5
Ball.SpeedX = GetRandomSpeedX
End Sub
Здесь используется
дополнительная функция GetRandomSpeedX. Она возвращает случайное приращение по
оси X, колеблющееся от -5 до 5. Вот как она описывается.
Private
Function GetRandomSpeedX() As Integer
Randomize
GetRandomSpeedX = Int(Rnd * 10) - 5
End Function
Теперь, функция, которая
инициализирует карту. Ее нужно вызывать только один раз, когда загружается
уровень. Вы можете сами поэкспериментировать, дописывая новые уровни.
Public
Sub LoadMap(ByVal Level As Integer)
Dim i As Integer
Select Case Level
Case 1 'Всего лишь
две
вертикальные
стенки
по
бокам
For i = 0 To 11
Map(i, 0) = 1
Map(i, 15) = 1
Next i
End Select
End Sub
Мы всего-лишь задали
матрицу. Нам еще понадобится процедура, которая будет рисовать карту каждый
виток цикла по этой матрице.
Public Sub DrawMap()
Dim i As Integer, j As Integer
For i = 0 To 11
For j = 0 To 15
If Map(i, j) = 1 Then
' Если встретили в матрице значение 1, переводим на это место на экране красный
блок
rc.Top = 0
rc.Left = 102
rc.Right = rc.Left + 40
rc.Bottom = 40
Call ddsBack.BltFast(40 * j, 40 * i, ddsPic, rc, DDBLTFAST_WAIT Or
DDBLTFAST_SRCCOLORKEY)
End If
Next j
Next i
End Sub
Теперь, процедура,
которая будет рисовать биты. В параметре ей указывается какая бита будет
рисоваться, 1-нижняя, 2-верхняя
Public
Sub DrawBet(ByVal Num As Integer)
Dim BY As Integer, BX As Integer
If
Num = 1 Then
rc.Left = 0
Bet1.X = Bet1.X + Bet1.SpeedX 'Новая координата X
If Bet1.X + 80 > 600 Then Bet1.X = 600 - 80 'Проверяем
на
боковые
стенки, которые д. б. всегда
If Bet1.X < 40 Then Bet1.X = 40
BX = Bet1.X
BY = 460
Else
' Поставьте
здесь
ремарку
пока
не
напишете
функцию
DoBetAI
DoBetAI 'Вычисляем приращение
по X биты компьютера
rc.Left = 142
Bet2.X = Bet2.X + Bet2.SpeedX
If Bet2.X + 80 > 600 Then Bet2.X = 600 - 80
If Bet2.X < 40 Then Bet2.X = 40
BX = Bet2.X
BY = 0
End If
'Заполняем
недостающие
элементы
структуры RECT и рисуем
биту
на
заднем
буфере
rc.Top = 0
rc.Bottom = rc.Top + 20
rc.Right = rc.Left + 80
Call ddsBack.BltFast(BX, BY, ddsPic, rc, DDBLTFAST_WAIT Or
DDBLTFAST_SRCCOLORKEY)
End Sub
Отлично, теперь
"приделаем" нашей нижней бите управление. Доведите процедуру
обработки события формы frmBall_KeyDown до следующего состояния, а затем
допишите обработку события frmBall_KeyUp:
Private
Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)
Select Case KeyCode
Case vbKeyEscape 'Выход
mdlDirectDraw7.Running = False
Case vbKeyLeft 'Управление первого
игрока
Bet1.SpeedX = -5
Case vbKeyRight
Bet1.SpeedX = 5
End Select
End Sub
Private
Sub Form_KeyUp(KeyCode As Integer, Shift As Integer)
Bet1.SpeedX = 0
End Sub
Добавим в регион 1
следующее:
mdlBall.Reset
mdlBall.LoadMap 1
А в регион 2 это:
mdlBall.DrawMap
mdlBall.DrawBet 1
Запустите программу. Вы
увидите, что уровень уже нарисован и вы можете двигать свою битку в пределах
стенок. Эти стенки по определению должны быть всегда, учитывайте это. Выходите
из программы как и раньше, с помощью Escape.
Осталось самое сложное -
запрограммировать мячик, чтобы он летал и отскакивал от тех мест, которые в
массиве помечены единичкой. Я сделал это вот каким нехитрым образом. Итак,
представьте себе двухмерный мячик. В процессе полета он может находиться
целиком в одном секторе (каждый сектор - 40*40, если помните, а мяч - 20*20), в
двух секторах одновременно, в трех не может, а вот в четырех - запросто. Теперь
представьте, что у мячика есть крайние точки, так называемые -
"контрольные точки". Можно сказать, что если одна из этих точек
находится в каком-то секторе, то некая часть мячика тоже находится в этом
секторе. Таким образом, если контрольная точка находится в секторе, который
помечен 0, то все нормально, однако, если контрольная точка попадает в сектор с
1, то он уже занят блоком и мячик должен отскочить. В какую сторону? Посмотрите
на такой рисунок:
Вы видите на нем четыре
контрольных точки, отмеченных красным, а также их координаты в системе
координат мяча. Если мяч попал в занятый сектор точкой номер 1, то очевидно,
что он стукнулся о преграду, находящуюся сверху него и должен отсочить вниз.
Это легко задать формулой SpeedY=-SpeedY. Если "просигналила" точка
номер два, то мяч отскакивает влево и т. д. При этом, мяч нужно
"отпозиционировать" то-есть поставить просигналившую точку ровно на
границу с занятым сектором, чтобы мяч не "залипал" там.
Конечно, для правильного
отображения четырех точек явно мало, мяч может уже быть в занятом секторе и ни
одна точка не просигналит, поэтому в идеале надо рассматривать каждую точку
окружности мяча, лезть в дебри тригонометрии... бррр... Я вас еще не убедил?
Хорош этот способ или
нет, решайте сами. По крайней мере он работает.
Теперь функция, которая
выдает контрольную точку, если мяч попал в занятый сектор или ноль, если все
нормально.
Private
Function CheckPoint() As Integer
Dim Ret As Integer
Ret = 0
'Проверяем
точки
If Map(Int((Ball.Y + 10) / 40), Int(Ball.X / 40)) <> 0 Then Ret = 4
If Map(Int((Ball.Y + 20) / 40), Int((Ball.X + 10) / 40)) <> 0 Then Ret =
3
If Map(Int((Ball.Y + 10) / 40), Int((Ball.X + 20) / 40)) <> 0 Then Ret =
2
If Map(Int(Ball.Y / 40), Int((Ball.X + 10) / 40)) <> 0 Then Ret = 1
CheckPoint
= Ret
End Function
Функция будет всегда
выдавать только одну точку с приоритетом по часовой стрелке.
Настал момент рисования
мяча.
Public Sub DrawBall()
MoveBall 'Вычисляем новые координаты мяча
rc.Top = 0
rc.Left = 81
rc.Bottom = 21
rc.Right = 102
' Рисуем его
Call ddsBack.BltFast(Ball.X, Ball.Y, ddsPic, rc, DDBLTFAST_WAIT Or
DDBLTFAST_SRCCOLORKEY)
End Sub
Private Sub MoveBall()
Dim Pt As Integer
'Делем следующий шаг
Ball.X = Ball.X + Ball.SpeedX
Ball.Y = Ball.Y + Ball.SpeedY
Pt = CheckPoint 'И проеряем, где после этого оказался мяч
'Проверяем попадание мяча одной из контрольных точек в занятый сектор
If Pt > 0 Then Select Case Pt
Case 1 'Верх
Ball.SpeedY = -Ball.SpeedY 'Изменение осевой скорости
Ball.Y = Int(Ball.Y / 40) * 40 + 40 'Нормализация осевой координаты
Case 2 'Право
Ball.SpeedX = -Ball.SpeedX
Ball.X = Int((Ball.X + 20) / 40) * 40 - 20
Case 3 'Низ
Ball.SpeedY = -Ball.SpeedY
Ball.Y = Int((Ball.Y + 20) / 40) * 40 - 20
Case 4 'Лево
Ball.SpeedX = -Ball.SpeedX
Ball.X = Int(Ball.X / 40) * 40 + 40
End Select
'Дополнительных точек пока нет
Else
'Нижняя ракетка
If Ball.Y + 20 > 460 Then
If (Ball.X + 15) >= Bet1.X And (Ball.X + 5) <= (Bet1.X + 80) Then 'Отбили
мяч
Ball.SpeedY = -Ball.SpeedY
Ball.SpeedX = GetDirectSpeedX
Ball.Y = 460 - 20
Else 'Мяч упал
Reset
End If
End If
'Верхняя ракетка
If Ball.Y < 20 Then
If (Ball.X + 15) >= Bet2.X And (Ball.X + 5) <= (Bet2.X + 80) Then
Ball.SpeedY = -Ball.SpeedY
Ball.SpeedX = GetRandomSpeedX
Ball.Y = 20
Else
Reset
End If
End If
End If
End Sub
Private Function GetDirectSpeedX() As
Integer
'Направление мяча по X в зависимости от того, каким местом биты отбил его игрок
GetDirectSpeedX = Int(((Ball.X + 10) - (Bet1.X + 40)) / 8)
End Function
Наконец, последняя
функция - вычисление приращения по X ракетки компьютера в зависимости от
позиции мяча. Все очень просто. Компьютер всегда старается, чтобы левый и
правый край мяча попадал в поле его ракетки.
Private
Sub DoBetAI()
If Ball.X <= Bet2.X + 10 Then Bet2.SpeedX = -5
If Ball.X + 20 >= Bet2.X + 80 Then Bet2.SpeedX = 5
If Ball.X > Bet2.X And Ball.X + 20 < Bet2.X + 80 Then Bet2.SpeedX = 0
End Sub
Громкие апплодисменты!
Написание модуля завершено. Теперь добавьте в регион две cтроки:
mdlBall.DrawBet 2
mdlBall.DrawBall
И запустите программу (Не
забудьте снять ремарку с вызова функции DoBetAI). Работает? Ура!
Надеюсь, теперь вам
немного стало ясно, как делаются игрушки на DirectX. Если нет - то это
нормально. Просто надо упражняться, упражняться и еще раз упражняться... Не
помню кто сказал...
Эта часть будет посвящена
некоторым методам и приемам, которые могут понадобиться в вашей работе.
Вы когда нибудь рисовали
на обыкновенном бейсике? Ну помните там всякие команды Line, Circle... и все
такое? Так вот, DirectDraw возвращает нас в детский сад и дает набор методов
для рисования примитивных геометрических объектов. С другой стороны, это может
оказаться очень даже полезно.
Я не стал далее
рассматривать все методы рисования, они настолько элементарны, что с ними
разберется даже ребенок. Вот пример программки, которая рисует на заднем
буфере.
Private
Sub Form_KeyPress(KeyAscii As Integer)
Unload Me 'Выход по
нажатию
клавиши
End Sub
Private
Sub Form_Unload(Cancel As Integer)
mdlDirectDraw7.DestroyDD 'Уходя,
гасите
свет :)
End Sub
Private
Sub Form_Load()
mdlDirectDraw7.CreateDDFullscreen Me.hWnd, 800, 600, 16 'Инициализируем
экран
Run 'Подпрограмма
рисования
End Sub
Private
Sub Run()
DoEvents
Call mdlDirectDraw7.ClearBuffer(ddsBack) 'Чистим
полотно
Call
ddsBack.SetForeColor(RGB(255, 0, 0)) 'Установить
цвет
рисования
Call ddsBack.DrawCircle(130, 105, 100) 'Это
круг
Call ddsBack.DrawText(100, 100, "Hello, DirectDraw!", False) 'Это
текст
Call
ddsBack.SetForeColor(RGB(0, 0, 255))
Call ddsBack.DrawLine(250, 30, 500, 30) 'Линии
Call ddsBack.DrawLine(250, 50, 500, 50)
Call ddsBack.DrawLine(250, 70, 500, 70)
Call ddsBack.DrawBox(250, 90, 500, 205) 'Прямоугольник
'Делаем Flip
ddsPrimary.Flip Nothing, DDFLIP_WAIT
End Sub
Наверняка вы знаете, что
графика - это самая привередливая вещь. На одних компьютерах она может работать
прекрасно, на других нет. Одна из частых ошибок, возникающих в программах - это
когда они пробуют переключиться в видеорежим, который не поддерживается
установленной на компьютере видеокартой. Чтобы пользователь не гадал, почему
это супер-навороченная-трехмерно-долбильно-всехктодвижется игрушка вашего
производства, помигав экраном, вылетает в систему, вы можете сами сказать ему о
том, что его видеокарта не поддерживает трехмерного буфферинга, динамического
освещения во внутренних плоскостях, разрешения больше, чем экран монитора и
вообще, пусть он выбрасывает ее и покупает Voodoo4.
Но это я так... На самом
деле, будет очень полезно, если вы перед тем, как переключиться в графический
режим проверите, поддреживается ли он видеокартой пользователя. Также, вы
можете перечислить все доступные на компьютере режимы, чтобы потом отобрать
нужные вам и показать пользователю для выбора нужного ему. Наверняка вы видели
подобное в разных играх, чаще всего в трехмерных бродилках.
Метод GetDisplayModesEnum
возвращает объект DirectDrawEnumModes, который содержит в себе доступные
видеорежимы. Как их оттуда вытащить? Метод DirectDrawEnumModes.GetCount
возвращает количество видеорежимов, а вызов метода DirectDrawEnumModes.GetCount
i,ddsd помещате информацию по i-тому режиму в структуру DDSURFASEDESC2 (ddsd),
уже знакомую вам по прошлым частям.
Давайте создадим проект,
который будет перечислять все видеорежимы.
Создайте проект,
нарисуйте форму, а на ней элемент ListBox, который назовите lstModes. Нам не
понадобятся ни модуль mdlDirectDraw7, ни библиотека Win32 TLB
Далее, включите в модуль
кода формы такую процедуру.
Sub
GetDisplayModes()
Dim dd As DirectDraw7
Dim ddsd As DDSURFACEDESC2
Dim DisplayModesEnum As DirectDrawEnumModes
Dim i As Integer
Set
dd = dx.DirectDrawCreate("") 'Создать
объект DirectDraw
dd.SetCooperativeLevel Me.hWnd, DDSCL_NORMAL 'DirectDraw в
окне
Set
DisplayModesEnum = dd.GetDisplayModesEnum(0, ddsd) 'Получить
объект DirectDrawEnumModes
Dim OutPut As String
'Теперь, методично извлекаем
по
одному
режиму
и
распечатываем
его
For i = 1 To DisplayModesEnum.GetCount()
DisplayModesEnum.GetItem i, ddsd 'Получить описание
режима
OutPut = Trim(Str(ddsd.lWidth) & " x" & Str(ddsd.lHeight)
& " x" & Str(ddsd.ddpfPixelFormat.lRGBBitCount))
lstModes.AddItem OutPut, 0 'Добавим описание
в
Лист
Next
Set
dd = Nothing 'Убиваем DD
End Sub
Процедура FormLoad
элементарна:
Private
Sub Form_Load()
Me.Show
GetDisplayModes
End Sub
Возможные применения этой
процедуры: вместо ListBox вы можете добавлять найденные режимы в массив,
который потом обрабатывать, находя или не находя режимы, нужные для программы,
распечатывая их... Фантазия может быть безгранична!
Вам наверняка мешался в
прошлых программах мышиный курсор. Причем на разных компьютерах он мешается по
разному. У меня дома он только подмелькивает, а вот у друга его видно всегда.
Так вот, покончим с ним раз и на всегда!
Смысл такой: вы рисуете
любой курсор в ПайнтБраше и записываете его в формате BMP, после чего создаете
оффскринную поверхность, запихиваете туда нарисованный курсор, отслеживаете
перемещение мыши и совершаете blitting курсора в текущие координаты мыши. При
этом, вам надо жестоко рассправиться с системным курсором - с помощью API
функции ShowCursor убрать его с поверхности долой.
Вы можете использовать
готовые профессионально нарисованные курсоры, но не забудьте переводить их в
формат BMP и рисовать им фон ключевым цветом.
Далее мы внесем некоторые
изменения в наш модуль mdlDirectDraw7 для работы с мышью.
Сперва добавьте такие
объявления:
Public
ddsMouse 'Поверхность для
курсоров
Public MouseX As Integer 'Координаты
мыши
Public MouseY As Integer
Чтобы прятать, а затем
восстанавливать курсор мыши, добавьте в процедуру CreateDDFullscreen строку
Win32.ShowCursor False 'Убить системный
курсор
а в процедуру DestroyDD
строку
Win32.ShowCursor True 'Восстанавливаем
курсор
Теперь, напишем процедуру
рисования курсора:
'Рисование
указанного
курсора
мыши
с
поверхности '=================================================
Public Sub DrawMouse(ByVal MX As Long, ByVal MY As Long, ByVal Size As Long)
rc.Top = MY
rc.Left = MX
rc.Bottom = MY + Size
rc.Right = MX + Size
Call
ddsBack.BltFast(MouseX, MouseY, ddsMouse, rc, DDBLTFAST_SRCCOLORKEY Or
DDBLTFAST_WAIT)
End Sub
Некоторые пояснения: В
процедуре DrawMouse так много параметров для того, чтобы вы могли хранить в
буфере несколько курсоров, а потом легко управлять их отображением. Например,
если вы хотите сделать анимированный курсор, вы можете сделать счетчик кадров,
а потом на основе этого счетчика передавать в процедуру DrawMouse координаты
нужного кадра курсора. (MX*i, например). Size нужен для нестандартных курсоров.
Система использует курсоры размером 32x32, а вы можете задавать свои курсоры,
но они всегда будут квадратными. Не хотите - добавляйте еще параметр.
Подкючение курсора к проекту.
Учтите, что ScaleMode
формы должен быть в пикселях (3-Pixel). И вообще, когда работаете с DirectDraw
переводите ScaleMode формы в пиксели.
Когда разобрались с
формой измерения, добавьте в событие Form_MouseMove две строки:
MouseX
= Int(X)
MouseY = Int(Y)
Это будет отслеживать
положение мыши и устанавливать новые координаты указателя.
Процедура Form_Load
должна выглядеть примерно так:
Private Sub Form_Load()
mdlDirectDraw7.CreateDDFullscreen Me.hWnd, 640, 480, 16 'Инициализируем экран
Set ddsMouse = mdlDirectDraw7.CreateDDSFromFile(App.Path &
"\cursor.bmp")
mdlDirectDraw7.Running = True
Run 'Подпрограмма рисования
End Sub
Теперь, напишем
подпрограмму Run:
Private Sub Run()
Do While mdlDirectDraw7.Running = True
DoEvents
Call mdlDirectDraw7.ClearBuffer(ddsBack) 'Чистим полотно
Call ddsBack.SetForeColor(RGB(255, 0, 0)) 'Установить цвет рисования
Call ddsBack.DrawText(100, 100,
"Этот пример показывает как использовать", False) 'Это текст
Call ddsBack.DrawText(100, 120, "собственные курсоры в DirectDraw!",
False)
Call ddsBack.DrawText(100, 200, mdlDirectDraw7.MouseX & " " &
mdlDirectDraw7.MouseY, False) 'Отображаем координаты мыши
mdlDirectDraw7.DrawMouse
0, 0, 32 'Рисуем мышь
'Делаем Flip ddsPrimary.Flip Nothing, DDFLIP_WAIT
Loop
Unload Me
End Sub
Чтобы выйти из программы,
потребуются еще две процедуры
Private
Sub Form_KeyPress(KeyAscii As Integer)
mdlDirectDraw7.Running = False 'Выход по
нажатию
клавиши
End Sub
Private
Sub Form_Unload(Cancel As Integer)
mdlDirectDraw7.DestroyDD 'Уходя,
гасите
свет
End Sub
Теперь, если ничего не
забыли, программа должна работать. Ура!
Полезный и очень часто
используемый прибамбас - счетчик количества выведенных кадров в секунду (FPS).
Чтобы потом не отвлекаться на него, допишем в модуль mdlDirectX процедуру,
которая будет выводить его в указанном месте.
Добавьте в Declarations
модуля такие определения
'FPS
Private count As Integer
Private tLast As Single
Private fps As Single
Теперь, сама процедура
вывода счетчика:
'Рисование
счетчика
количества
кадров
в
секунду '================================================
Public Sub DrawFPS(ByVal X As Long, ByVal Y As Long)
Dim tcolor As Long
If
count = 30 Then
If tLast <> 0 Then fps = 30 / (Timer - tLast)
tLast = Timer
count = 0
End If
count = count + 1
tcolor = ddsBack.GetForeColor 'Чтобы не
влиять
потом
на
установленный
цвет
Call ddsBack.SetForeColor(RGB(255, 255, 255))
Call ddsBack.DrawText(X, Y, "Frames per Second " & Format$(fps,
"#.0"), False)
Call ddsBack.SetForeColor(tcolor)
End Sub
Для примера, подключите
счетчик кадров в прошлом проекте (с мышью) перед выводом курсора с помощью
такой строчки:
mdlDirectDraw7.DrawFPS 10, 10
Если заработает (а должен
заработать!) попробуйте изменять видеорежимы. У меня уже на 800x600x24 было 14
кадров, а в 800x600x8 счетчик "зашкалило" т.е. он вышел за пределы
частоты обновления экрана.
Ну вот, разбор самых
интересных фичей вроде завершился.
Итак, скорее всего это
будет заключительная часть моего учебника. Но это не значит, что изучение
DirectDraw заканчивается! Я еще не расказал о тайлах, скроллинге, изометрии и
тому подобных вещах, но это ведь уже будут не "Основы". Все, что
каксается "продвинутого" программирования DirectDraw я буду выпускать
отдельными статьями, так что заходите на сайт почаще!
Чаще всего DD
используется в полноэкранном режиме, так как только в нем мы можем использовать
большинство функций работы с видеокартой. Однако, вам может понадобиться
использование DirectDraw в окне. Все-таки как ни крути, DirectDraw даже в окне
работает быстрее, чем BitBlt и другие "рисовательные" API функции, не
говоря уже о методах PaintPicture и им подобных.
Чуть-чуть больше об
объекте клиппер. Когда вы рисуете какой-то спрайт и он частично уходит за
пределы видимой области, DirectDraw, вместо того, чтобы выдавать ошибку, просто
прекращает рисовать весь спрайт и на экране вы видите, что он пропадает. К
счастью для оконного режима, в нем DirectDraw позволяет использовать объект
DirectDrawClipper, который действует по принципу детской формочки - он
накладывается на определенный участок, например на PictureBox, а затем, все
изображения, которые помещаются в этот PictureBox обрезаются об края клиппера и
остаюется только то, что внутри него. Ну вот - полная песочница!
Объявляется и создается
клиппер следующим образом:
'Для начала, нужно его объявить
Dim ddClipper As DirectDrawClipper
'Затем, выполняется такая
последовательность:
Set ddClipper = dd.CreateClipper(0) 'Создать клиппер из объекта DirectDraw
ddClipper.SetHwnd Object.Hwnd 'Наложить клиппер на некий объект
ddsPrimary.SetClipper DDClipper 'Установить клиппер
Ну вот, примерно так вы
будете создавать клиппер в ваших приложениях.
Но учтите, что в
полноэкранном режиме, объект клиппер не работает! Вам придется обрезать
спрайты, выходящие за границы экрана, вручную.
Ладно, с теорией, я
надеюсь, разобрались. Переходим к созданию приложений.
Мы обновим наш модуль
mdlDirectDraw7 и добавим в него новую функцию для создания DirectDraw в окне.
Сначала, поместите в
модуль новое объявление:
Public
ddClipper As DirectDrawClipper 'Объект
Clipper
Теперь, примемся за саму
функцию:
Public Sub CreateDDWindowed(srcHwnd As
Long, clipHwnd As Long)
'Создаем объекты и устанавливаем обычный режим
Set dd = dx.DirectDrawCreate("")
Call dd.SetCooperativeLevel(srcHwnd, DDSCL_NORMAL)
'Создаем главный буфер
ddsd.lFlags = DDSD_CAPS
ddsd.ddsCaps.lCaps = DDSCAPS_PRIMARYSURFACE
Set ddsPrimary = dd.CreateSurface(ddsd)
'Создаем клиппер и
"присандаливаем" его к заданному объекту: форме или
'PictureBox
Set ddClipper = dd.CreateClipper(0)
ddClipper.SetHWnd clipHwnd
ddsPrimary.SetClipper ddClipper
End Sub
Отлично! С модулем
покончено (ну в смысле закончено... ну вы меня поняли...) Теперь напишем
программу, которая будет выводить в окне спрайт, и с изменением размеров формы,
размер спрайта также будет меняться.
Создайте проект, если вы
его еще не создали и нарисуйте форму frmMain. Далее, внесите в модуль кода
формы следующий код:
Dim ddsPic As DirectDrawSurface7
'Поверхность для рисунка
Dim rc1 As RECT 'Дополнительная структура RECT
Private Sub Form_Load()
mdlDirectDraw7.CreateDDWindowed Me.hWnd, Me.hWnd 'Инициализация
'Загружаем картинку
Set ddsPic = mdlDirectDraw7.CreateDDSFromFile(App.Path &
"\demo.bmp")
End Sub
При загрузке формы будет
происходить инициализация, но не будет начинаться рисование. Процедура
рисования будет вызываться при событиях формы Paint (чтобы перерисовывать
спрайт, когда форма перекрывается другим окном, уходит за пределы экрана и
т.п.), а также Resize (ну это понятно - чтобы менять размеры спрайта вместе с
формой).
Private Sub Form_Paint()
'При перерисовке формы, например если ее перекрыло окном, обновляем картинку
Run
End Sub
Private Sub Form_Resize()
'При изменении размеров формы, перерисовываем картинку
Run
End Sub
Я думаю, вы уже
догадались, что Run - это процедура рисования. А вот и она сама:
'Процедура рисования
Private Sub Run()
DoEvents
Call dx.GetWindowRect(Me.hWnd, rc1) 'Получить координаты формы в структуру RC1
'В RC заносим координаты исходного
рисунка в буфере DD
rc.Left = 0
rc.Top = 0
rc.Right = 350
rc.Bottom = 279
'Рисуем картинку прямо на переднем
буфере
Call ddsPrimary.Blt(rc1, ddsPic, rc, DDBLT_WAIT)
End Sub
Готово! Запускайте
программу (конечно вам еще понадобится картинка с соответствующими размерами (у
меня 350x279)) и попробуйте поменять размеры формы, перекрывать ее другими
окнами, частично вытаскивать за пределы экрана. Картинка на форме должна
постоянно обновляться.
В этом примере нет
анимации. Я приготовил для вас еще один пример - с анимацией, но различия там
скорее только технические и к DirectDraw относятся мало, поэтому я предлагаю
вам его просто скачать. Посмотрите в "приложении" к этой главе.
Приятного
программирования, Antiloop