Основы DirectDraw на Visual Basic

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


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

Ну что ж, привет всем, кто здесь. Надеюсь, это маленькое руководство поможет вам осознать куда катится наш и Ваш любимый 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. Советую прочитать по крайней мере часть "недостатки" прежде, чем отправиться в путь.

Достоинства :)

Недостатки :(

Вот такие вот дела. Но если вы думаете, что теперь можно приступать к работе, то вы снова ошибетесь. Просто так вот взять и программировать DirectX вам не удастся. Прежде всего, вам узнать про одного человека - его имя Patrice Scribe и он вроде как Француз. Точно говорить не буду - сам не знаю. Так вот, именно этот хороший человек придумал библиотеки для работы с DirectX на VB, за что огромное ему спасибо. Так что прежде всего, вам надо достать эти библиотеки. В них входят:

Можно скачать и по Интернету, ссылки смотрите в соответствующем разделе, и еще, по моему DirectX 5.0 Library есть в наборе библиотек Visual Basic 6.0 В любом случае, вам понадобится еще и Win32 TL, если только вам самим не охота вколачивать в разделе Declarations все многочисленные типы для работы с DD.

Ну вот, теперь вроде бы и все. Осталось только сказать, что у меня на данный момент установлен Visual Basic 6.0 и все проги я тестировал на нем. Кое-кто в Сети использует и VB5 (в основном америкашки), так что на нем тоже пойдет. А вот уже что касается более ранних версий, то могу сказать владельцам таковых, что апргейд вам не только не повредит но и пойдет на пользу в несколько раз.

Примечание: не так давно компания Microsoft выпустила DirectX 7.0, и объявила, что теперь в нем есть официальная поддержка VB, то есть, если у вас есть DirectX7 SDK, вы можете программировать для DirectX 7. Чем же это отличается от того, что я здесь сейчас буду объяснять? Да ничем. Поменяются только определения объектов: вместо DirectDraw вам придется писать DirectDraw7, предварительно добавив соответствующую библиотеку к проекту. В остальном же, все процедуры и принципы работы те же. Конечно, что-то изменилось, но самые основы остались неизменными еще со времен DirectX 3.0

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

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

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

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

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

 

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

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

Прежде всего добавьте в разделе References необходимые TLB

ну а далее наберите программу. Заметьте, что никакие контролы на форму ставить не надо. И вообще, форма тут совсем ни при чем, можете ее хоть квадратной сделать - все равно, потому что работать программа будет в полноэкранном режиме.

И да будет код! Хотя прежде чем начать, сделаю вот такое примечание: если у вас не пойдут абсолютно все объявления объектов DirectDraw, измените в их объявлении строку IDirectDrawxxxx на DirectDrawxxxx (уберите I в начале строки)

Итак, начнем с объявления объектов DD:

Dim lpDD as IDirectDraw2   'Самый главный объект DirectDraw
Dim lpDDSFront as IDirectDrawSurface2   'Передний буфер
Dim lpDDSBack as IDirectDrawSurface2    'Задний буфер
Dim ddsd as DDSURFACEDESC  'Информация поверхности
Dim ddc as DDSCAPS  'Информация о совместимости "железа" (Hardware)

Теперь добавьте в процедуру Form_Load следующие строки:

'Подготавливаем экран
Call DirectDrawCreate(ByVal 0&,lpDD,Nothing)  'Создаем новый объект DirectDraw
Call lpDD.SetCooperativeLevel(Me.hWnd, DDSCL_EXCLUSIVE Or DDSCL_FULLSCREEN or DDSCL_ALLOWREBOOT)  'Полноэкранный режим с разрешенным C-A-D
Call lpDD.SetDisplayMode (640, 480, 16, 0 ,0)  'Устанавливаем разрешение 640x480x16

'Теперь создаем главную поверхность с одним задним буфером
'Описываем передний буфер
With ddsd
   .dwSize=Len(ddsd)  'Заполняйте любые структуры DirectX с указания свойства dwSize
   .dwFlags=DDSD_CAPS or DDSD_BACKBUFFERCOUNT
   .DDSCAPS.dwCaps=DDSCAPS_PRIMARYSURFACE Or DDSCAPS_FLIP Or DDSCAPS_COMPLEX Or _
     DDSCAPS_SYSTEMMEMORY  'Главная поверхность с разрешенным Перебросом (Flip)
   .dwBackBufferCount=1 Задний буфер
End With

'Создаем его
Call lpDD.CreateSurface(ddsd, lpDDSDFront, Nothing)

'Заполняем структуру DDSCAPS
ddc.dwCaps=DDSCAPS_BACKBUFFER

'Получить задний буфер
Call lpDDSFront.GetAttachedSurface(ddc, lpDDSBack)

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

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

'Восстанавливаем режим дисплея
Call lpDD.RestoreDisplayMode
Call lpDD.SetCooperativeLevel(0, DDSCL_NORMAL) 'Восстанавливаем разрешение

'Обнуляем объекты DirectDraw
Set lpDDSBack=Nothing 'Сначала задний буфер
Set lpDDSFront=Nothing
Set lpDD= Nothing  'То же с передним буфером и самым главным объектом

'Ну и все...
End

 

А как же анимация?

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

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

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

Далее я дам стандартную функцию загрузки графического файла в буфер DD.

' API Declarations
' Win32
Const IMAGE_BITMAP = 0
Const LR_LOADFROMFILE = &H10
Const LR_CREATEDIBSECTION = &H2000
Const SRCCOPY = &HCC0020


Private Type BITMAP
bmType As Long
bmWidth As Long
bmHeight As Long
bmWidthBytes As Long
bmPlanes As Integer
bmBitsPixel As Integer
bmBits As Long
End Type

' GDI32
Private Declare Function GetPixel Lib "gdi32" (ByVal hdc As Long, ByVal x As Long, ByVal y As Long) As Long
Private Declare Function CreateCompatibleDC Lib "gdi32" (ByVal hdc As Long) As Long
Private Declare Function DeleteDC Lib "gdi32" (ByVal hdc As Long) As Long
Private Declare Function DeleteObject Lib "gdi32" (ByVal hObject As Long) As Long
Private Declare Function GetObject Lib "gdi32" Alias "GetObjectA" (ByVal hObject As Long, ByVal nCount As Long, lpObject As Any) As Long
Private Declare Function SelectObject Lib "gdi32" (ByVal hdc As Long, ByVal hObject As Long) As Long

' USER32
Private Declare Function GetDC Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function LoadImage Lib "user32" Alias "LoadImageA" (ByVal hInst As Long, ByVal lpsz As String, ByVal un1 As Long, ByVal n1 As Long, ByVal n2 As Long, ByVal un2 As Long) As Long
Private Declare Function StretchBlt Lib "gdi32" (ByVal hdc As Long, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hSrcDC As Long, ByVal xSrc As Long, ByVal ySrc As Long, ByVal nSrcWidth As Long, ByVal nSrcHeight As Long, ByVal dwRop As Long) As Long

' Загрузить рисунок из файла на поверхность DD
Public Function CreateDDSFromBitmap(dd As IDirectDraw, ByVal strFile As String) As IDirectDrawSurface
Dim hbm As Long ' Handle on bitmap
Dim bm As BITMAP ' Bitmap header
Dim ddsd As DDSURFACEDESC ' Surface description
Dim dds As IDirectDrawSurface ' Created surface
Dim hdcImage As Long ' Handle on image
Dim mhdc As Long ' Handle on surface context

' Загружаем
hbm = LoadImage(ByVal 0&, strFile, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE Or LR_CREATEDIBSECTION)

' Получаем информацию
GetObject hbm, Len(bm), bm

' Fill surface description
With ddsd
.dwSize = Len(ddsd)
.dwFlags = DDSD_CAPS + DDSD_HEIGHT + DDSD_WIDTH
.DDSCAPS.dwCaps = DDSCAPS_OFFSCREENPLAIN
.ddpfPixelFormat.dwSize = 8
.dwWidth = bm.bmWidth
.dwHeight = bm.bmHeight
End With

' Create surface
dd.CreateSurface ddsd, dds, Nothing

' Create memory device
hdcImage = CreateCompatibleDC(ByVal 0&)

' Select the bitmap in this memory device
SelectObject hdcImage, hbm

' Restore the surface
dds.Restore

' Get the surface's DC
dds.GetDC mhdc

' Copy from the memory device to the DirectDrawSurface
StretchBlt mhdc, 0, 0, ddsd.dwWidth, ddsd.dwHeight, hdcImage, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY

' Release the surface's DC
dds.ReleaseDC mhdc

' Release the memory device and the bitmap
DeleteDC hdcImage
DeleteObject hbm

' Returns the new surface
Set CreateDDSFromBitmap = dds
End Function

Побитовый перенос происходит с помощью метода Blt или еще лучше - BltFast. Как? - это уже вопрос следующей статьи.

Ну вот, в этом Tutorial я рассказал в основном, как все происходит в DirectDraw и даже показал как делать предварительные приготовления. В следующей статье, которая выйдет в самое ближайшее время, я подробно разберу методы Blitting'a и совершения анимации.

Приложения:

TLB DirectX 5.0 & Win32 ~360 кб

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