Основы Direct Draw на Visual Basic

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


Часть вторая: фундамент для движка

Итак, мы продолжаем. Это вторая статья из серии и она посвящена в основном черновой работе, которую необходимо проделать, чтобы потом, когда начнется самое интересное, не отвлекаться по мелочам. Что же мы будем делать?

Прошлая часть являлась по своей сути введением и не несла никакой практической нагрузки, хотя я и научил вас создавать объекты DirectDraw и даже менять разрешение экрана. Один вопрос: ну и что вы со всем этим будете делать?!

Создание универсального модуля

В прошлой части я привел процедуру загрузки "битмапа" в буфер DD. Много полезного она вам вряд ли принесла, но по крайней мере дала понять, с чем приходится иметь дело, программируя DirectDraw. Итак. в этой части я хочу, чтобы вы создали модуль, назовем его mdlDirectX, в который будете пихать все стандартные процедуры, чтобы потом забыв о том, как они работают пользоваться ими с чистой душой. В ИНете есть уже готовые модули (libDD, например), однако, создав свой модуль, вы будете более подкованы и сможете сами писать более гибкий код.

Сперва, в наш модуль надо сбросить все определения API функций и переменные и ссылки на объекты, относящиеся к DD, затем, туда будут записаны все функции, служащие для работы, создания каких-либо обектов, установок, уничтожения и т. п. Далее, привожу начальный текст этого модуля с комментариями. Почему начальный? Просто сейчас, этот модуль будет на первом этапе работы, то-есть в него мы запишем минимальное количество функций, требуемых для работы. Затем, по мере дальнейшего изучения DirectX, вы сможете добавлять в этот модуль все новые и новые функции.

Option Explicit

' Объявления DirectDraw
' Если у вас проблемы,
' пробуйте изменять объявления с
' IDirectDrawxxxxx на DirectDrawxxxx
Public lpDD As IDirectDraw
Public lpDDSFront As IDirectDrawSurface
Public lpDDSBack As IDirectDrawSurface
Public lpDDSPic As IDirectDrawSurface
Public ddsd As DDSURFACEDESC
Public ddc As DDSCAPS
Public ddck As DDCOLORKEY
Public rc As RECT Public i As Long

'Некоторые другие переменные
Public bEnd As Boolean ' True = App is ending
Public X As Long, Y As Long

' 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

Это та самая функция, которая была дана в прошлой главе. Она служит для загрузки ваших картинок со спрайтами в буфер DirectDraw, с которого вы потом будете "переводить" спрайты на рабочий экран

' Загружаем Bitmap на буфер DirectDraw
Public Function
CreateDDSFromBitmap(dd As IDirectDraw, ByVal strFile As String) As IDirectDrawSurface
Dim hbm As Long ' Handle on bitmap
Dim bm As BITMAP ' Заголовок bitmap
Dim ddsd As DDSURFACEDESC ' Surface описание
Dim dds As IDirectDrawSurface ' Созданный surface
Dim hdcImage As Long ' Handle on image
Dim mhdc As Long ' Handle on surface context

'Загружаем bitmap из файла. Над этой строкой можно издеваться, если хотите получить
'bitmap из других источников
hbm = LoadImage(ByVal 0&, strFile, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE Or LR_CREATEDIBSECTION)

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

' Заполняем описание surface
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

' Создаем surface
dd.CreateSurface ddsd, dds, Nothing

' Создаем memory device
hdcImage = CreateCompatibleDC(ByVal 0&)

' Выбираем bitmap в этом memory device
SelectObject hdcImage, hbm


' Восстанавливаем surface
dds.Restore

' Получаем surface's DC
dds.GetDC mhdc

' Копируем из memory device на 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

' Возвращаем новый surface
Set CreateDDSFromBitmap = dds

End Function

' Очистка буфера
Sub ClearBuffer(ByRef lpDDS As IDirectDrawSurface)

Dim fx As DDBLTFX
' Заполняем описание операции blt
With fx
  .dwSize = Len(fx)
  .dwFillColor = RGB(0, 0, 0)
End With

' Заполняем surface цветом
Call lpDDS.Blt(ByVal 0&, Nothing, ByVal 0&, DDBLT_WAIT Or DDBLT_COLORFILL, fx)

End Sub

Эта процедура для полного копирования одного буфера на другой. В аргументах у нее два объекта DirectDrawSurface. Первый - источник, а второй - цель.

' Копируем весь буфер на другой
Sub CopyBuffer(ByRef lpDDSSrc As IDirectDrawSurface, ByRef lpDDSDest As IDirectDrawSurface)
Dim ddsd As DDSURFACEDESC
Dim rc As RECT
' Получить описание surface для исходного surface
With ddsd
  .dwSize = Len(ddsd)
  .dwFlags = DDSD_WIDTH Or DDSD_HEIGHT
End With
Call lpDDSSrc.GetSurfaceDesc(ddsd)
' Теперь копируем весь исходный буфер на буфер назначения
rc.Left = 0
rc.Top = 0
rc.Right = ddsd.dwWidth
rc.Bottom = ddsd.dwHeight
' BltFast the surface
Call lpDDSDest.BltFast(0, 0, lpDDSSrc, rc, DDBLTFAST_WAIT)
' Set this flag if that surface has a source key Or DDBLTFAST_SRCCOLORKEY)
End Sub

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

Sub WaitForVerticalBlank()
' This waits for the veritcal blank to end
Call lpDD.WaitForVerticalBlank(DDWAITVB_BLOCKEND, 0)
End Sub

Здесь происходит инициализация DirectDraw, создаются объекты, поверхности (Surface) и меняется разрешение экрана. В параметрах стоит режим дисплея (X x Y x Color, где Color задается в битах на пиксель (bpp)). Параметру trgtForm надо присвоить указатель на вашу главную форму, на которой будет происходить действие.

Public Sub DDrawInit(ByVal X As Long, ByVal Y As Long, ByVal Color As Long, ByVal trgtForm As Object)
' Initialize DirectDraw
Call DirectDrawCreate(ByVal 0&, lpDD, Nothing)
Call lpDD.SetCooperativeLevel(trgtForm.hwnd, DDSCL_EXCLUSIVE Or DDSCL_FULLSCREEN Or DDSCL_ALLOWREBOOT)
Call lpDD.SetDisplayMode(X, Y, Color)

'Create a front and a bitmap surfaces
With ddsd
.dwSize = Len(ddsd)
.dwFlags = DDSD_CAPS Or DDSD_BACKBUFFERCOUNT
.DDSCAPS.dwCaps = DDSCAPS_PRIMARYSURFACE Or DDSCAPS_FLIP Or DDSCAPS_COMPLEX
.dwBackBufferCount = 1
End With

Call lpDD.CreateSurface(ddsd, lpDDSFront, Nothing)
' Retrieve the back buffer
With ddc
.dwCaps = DDSCAPS_BACKBUFFER
End With

Call lpDDSFront.GetAttachedSurface(ddc, lpDDSBack)
End Sub

Устанавливается ColorKey, то есть цвет, который в ваших спрайтах будет прозрачным. Цвет задается в формате RGB тремя параметрами составляющих цвета (0-255)

Public Sub SetColorKey(ByVal R As Integer, ByVal G As Integer, ByVal B As Integer)
' Set the color key
With ddck
.dwColorSpaceHighValue = RGB(R, G, B)
.dwColorSpaceLowValue = .dwColorSpaceHighValue
End With
Call lpDDSPic.SetColorKey(DDCKEY_SRCBLT, ddck)
End Sub

Эта процедура выполняет уничтожение объектов DirectX. Если она не будет вызвана перед тем, как ваша программа закроется, появится веселая надпись типа "Программа выполнила недопустимую ошибку..." и среда VisualBasic со всем его интерфейсом GUI накроется.

Public Sub DestroyDirectX()
Call lpDD.RestoreDisplayMode
Set lpDDSPic = Nothing
Set lpDDSBack = Nothing
Set lpDDSFront = Nothing
Set lpDD = Nothing
End Sub

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

Элементарнейший движок и принципы Blitting'а 

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

Законченный проект можете загрузить в самом конце этой главы, а пока я объясню, как работает основной цикл прорисовки. Сначала - вот его код:

Private Sub Form_Load()
 ' Это флаг окончания цикла. Когда устанавливается в true, например после нажатия клавиши
 ' цикл прорисовки прекращается
 bEnd = False
 ' Инициализируем DD и устанавливаем разрешение
 mdlDirectX.DDrawInit 640, 480, 16, Me
 ' Загружаем картинку на поверхность DD
 Set lpDDSPic = CreateDDSFromBitmap(lpDD, App.Path & "\fir0.bmp")
 mdlDirectX.SetColorKey 0, 0, 0 
 ' Запускаем Engine
 While bEnd = False
    DoEvents    'Будем жалостливы к системе
    Call ClearBuffer(lpDDSBack)    'Чистим полотно
    'Указываем координаты прямоугольника в буфере с рисунком, которые будем копировать
    'Координаты заносятся в структуру RECT, которая передается функцией BltFast
    rc.Left = 1
    rc.Right = rc.Left + 84
    rc.Top = 1
    rc.Bottom = rc.Top + 88
    'Совершаем Blitting - копируем прямоугольник с буфера с рисунком на задний буфер
    'в указанное место (0,0)
    Call lpDDSBack.BltFast(0, 0, lpDDSPic, rc, DDBLTFAST_WAIT Or DDBLTFAST_SRCCOLORKEY)
    Call mdlDirectX.WaitForVerticalBlank  'Подождем до начала цикла обновления
    Call lpDDSFront.Flip(Nothing, DDFLIP_WAIT) ' Переводим задний буфер на передний
    Call Sleep(80) 'Задержимся на чуть-чуть. Пока бесполезно, так-как нет анимации
 Wend
 Unload Me
End Sub

Как видите, главный рисующий цикл я загнал в процедуру Form_Load. Это всего-лишь фрагмент, объекты определены не здесь. Для blitting'а, то есть перевода шаблона спрайта на задний буфер использяется метод BltFast. Он достаточно быстр и легок в использовании. В качестве параметров у метода задается координаты верхнего левого угла на поверхности на которую происводится перевод, затем указывается исходный буфер, далее, передается структура RECT, определенная в Win32 TLB. В этой тсруктуре указана позиция и размеры прямоугольника со спрайтом. И наконец, передаются флаги, влияющие на работу метода.

После перевода спрайта вызвается процедура из нашего модуля WaitForVerticalBlank, а затем, происходит "переворачивание" задней поверхности на переднюю. Позднее, для эффектов анимации можно использовать процедуру Sleep(ms as integer). Она вызывает задержку на указанное количество миллисекунд.

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

Итак, ничего сложного нет. В следующей части мы приступим к созданию анимации.

Приложения

Полный проект, содержащий код примера и модуль DirectX. Загрузить

 

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