ГЛАВА 4. Пример работы с DirectDraw в среде Visual Basic
Visual Basic является в первую очередь клиентом COM и такой графический API как DirectX, являющийся сервером COM, так же предоставляет свои интерфейсы для программирования графики. В API GDI32 предусмотрены функции для работы с изображениями на основе массива пикселей, но для быстрой работы с битовой графикой Microsoft предлагает API DirectX, основанный на технологии COM. В седьмой версии DirectX за работу с битовой графикой отвечает интерфейс DirectDraw, который физически находятся в библиотеках динамической компоновки dx7vb.dll (DirectX версии 7).
(Если Вы работаете в WinXP – DirectX7 уже установлен)
Для работы с DirectX часто достаточно только интерфейса отвечающего за битовую графику, а именно DirectDraw. Инициализация DirectDraw требует ряда поэтапных стандартных действий, описанных в приведенной ниже простой программе.
1.Создайте проект Standard EXE (см. проект папка N8/). Подключите к проекту библиотеку типов DirectX:
Project/References/DirectX7 for VB Type Library (Рис. 1)
Рис. 1
2. Введите в раздел General после Option Explicit следующий код:
Option Explicit
Dim dx As New DirectX7
Dim dd As DirectDraw7
Dim di As DirectInput
'''''''''''''''''''''
Dim diDev As DirectInputDevice
Dim diState As DIKEYBOARDSTATE
'''''''''''''''''''''
Dim Prim As DirectDrawSurface7
Dim PrimBufer As DirectDrawSurface7
Dim OsnovaBMP As DirectDrawSurface7
Dim SpriteBMP As DirectDrawSurface7
'''''''''''''''''''''
Dim ddsdPrim As DDSURFACEDESC2
Dim ddsdPrimBufer As DDSURFACEDESC2
Dim ddsdOsnovaBMP As DDSURFACEDESC2
Dim ddsdSpriteBMP As DDSURFACEDESC2
Dim ddsdcapsPrimBufer As DDSCAPS2
''''''''''''''''''''
Dim Key As DDCOLORKEY
В переменной dx хранится ссылка на “верхний” объект – собственно на всю библиотеку DirectX7. Объектная переменная dd нужна для получения доступа к сервисам предоставляемым интерфейсом DirectDraw, а переменная di для доступа к DirectInput – интерфейсу обеспечивающему работу мыши и клавиатуры.
3.Введите следующий код в событие формы Private Sub Form_Load():
Private Sub Form_Load()
Set dd = dx.DirectDrawCreate("")
Set di = dx.DirectInputCreate()
Set diDev = di.CreateDevice("GUID_SysKeyboard")
''''''''''''''''''''''
dd.SetCooperativeLevel Me.hWnd, DDSCL_FULLSCREEN _
Or DDSCL_ALLOWMODEX Or DDSCL_EXCLUSIVE
dd.SetDisplayMode 800, 600, 16, 0, DDSDM_DEFAULT
''''''''''''''''''''''
diDev.SetCommonDataFormat (DIFORMAT_KEYBOARD)
diDev.SetCooperativeLevel Form1.hWnd, DISCL_NONEXCLUSIVE _
Or DISCL_BACKGROUND
diDev.Acquire
''''''''''''''''''''''
ddsdPrim.lFlags = DDSD_CAPS Or DDSD_BACKBUFFERCOUNT
ddsdPrim.ddsCaps.lCaps = DDSCAPS_PRIMARYSURFACE _
Or DDSCAPS_FLIP Or DDSCAPS_COMPLEX
ddsdPrim.lBackBufferCount = 1
Set Prim = dd.CreateSurface(ddsdPrim)
''''''''''''''''''''''
ddsdcapsPrimBufer.lCaps = DDSCAPS_BACKBUFFER
Set PrimBufer = Prim.GetAttachedSurface(ddsdcapsPrimBufer)
PrimBufer.GetSurfaceDesc ddsdPrimBufer
''''''''''''''''''''''
ddsdOsnovaBMP.lFlags = DDSD_CAPS Or DDSD_HEIGHT Or DDSD_WIDTH
ddsdOsnovaBMP.ddsCaps.lCaps = DDSCAPS_OFFSCREENPLAIN
ddsdOsnovaBMP.lWidth = 800 'ddsdPrimBufer.lWidth
ddsdOsnovaBMP.lHeight = 600 'ddsdPrimBufer.lWidth
Set OsnovaBMP = dd.CreateSurfaceFromFile(App.Path & "\Osnova.bmp", ddsdOsnovaBMP)
''''''''''''''''''''''
ddsdSpriteBMP.lFlags = DDSD_CAPS
ddsdSpriteBMP.ddsCaps.lCaps = DDSCAPS_OFFSCREENPLAIN
Set SpriteBMP = dd.CreateSurfaceFromFile(App.Path & "\Fase.bmp", ddsdSpriteBMP)
''''''''''''''''''''''
Key.low = 0
Key.high = 0
SpriteBMP.SetColorKey DDCKEY_SRCBLT, Key
Dim Kvadrat As RECT
Dim Kvadrat1 As RECT
Kvadrat.Top = 0
Kvadrat.Left = 0
Kvadrat.Bottom = 600 'ddsdPrimBufer.lHeight
Kvadrat.Right = 800 'ddsdPrimBufer.lWidth
Kvadrat1.Top = 0
Kvadrat1.Left = 0
Kvadrat1.Bottom = ddsdSpriteBMP.lHeight
Kvadrat1.Right = ddsdSpriteBMP.lWidth
PrimBufer.BltFast 0, 0, OsnovaBMP, Kvadrat, DDBLTFAST_WAIT
'PrimBufer.BltFast 50, 50, OsnovaBMP, Kvadrat, DDBLTFAST_WAIT
PrimBufer.BltFast 0, 0, SpriteBMP, Kvadrat1, DDBLTFAST_SRCCOLORKEY Or DDBLTFAST_WAIT
Prim.Flip Nothing, DDFLIP_WAIT
End Sub
В первой части кода приведенного в событии Form_Load производится инициализация объектных переменных dd,di и diDev. Интерфейс доступ к которому предоставляет переменная diDev отвечает в данном случае за клавиатуру (флаг GUID_SysKeyboard). От di можно так же “родить” мышь и джойстик которые являются устройствами (DEVICE) поддерживаемыми технологией DirectX(тип - Dim diDev As DirectInputDevice ).
Примечание. В первой версии DirectX которая называлась Game SDK работа с мышью и клавиатурой производилась через стандартные оконные сообщения Windows, а DirectInput нужен, чтобы обойти сообщения окну. В VB для работы в форме можно использовать стандартные события мыши и клавиатуры. Хотя они и медленней, но если Вы не пишите высокоскоростную игру стандартные сообщения вполне подойдут.
В функции SetCooperativeLevel устанавливается разрешение экрана 800/600 пикселей и цветовой режим 16 бит на пиксель.
Примечание. Режим 265 цветов (работа с палитрами) в данной статье не рассматривается, хотя все файлы формата “ВМР” в примерах являются палитровыми. Просто если установить режим 16бит/пиксель все преобразования по правильному отображению палитры за Вас сделает Windows.
Найдите функции Set OsnovaBMP = dd.CreateSurfaceFromFile(App.Path & "\Osnova.bmp", ddsdOsnovaBMP) и Set SpriteBMP = dd.CreateSurfaceFromFile(App.Path & "\Fase.bmp", ddsdSpriteBMP), прежде чем ими воспользоваться и загрузить массивы пикселей в память из файлов, необходимо создать поверхности с которыми может работать DirectDraw (тип - DirectDrawSurface7). В данном примере будет четыре поверхности: Dim Prim As DirectDrawSurface7-первичная поверхность; Dim PrimBufer As DirectDrawSurface7- буфер прикрепленный к первичной поверхности на котором в памяти строится картинка перед выводом; Dim OsnovaBMP As DirectDrawSurface7 - картинка для фона созданная из файла (Osnova.bmp); Dim SpriteBMP As DirectDrawSurface7 - картинка для лица человечка(Fase.bmp). Создают поверхности функции описанные в интерфейсе DirectDraw – CreateSurface(для первичной поверхности и ее буфера) и CreateSurfaceFromFile(поверхности на основе файлов), которым в качестве параметров передаются структуры с описанием поверхностей(тип – DDSURFACEDESC2).
Найдите в коде примера две функции BltFast – здесь происходит загрузка картинок(спрайтов) в первичный буфер. В функции Flip буфер “выстреливается” на свою первичную поверхность. Если этот процесс “зациклить” меняя при этом координаты вывода спрайтов в функциях BltFast - получится анимация. Изменение координат в функциях BltFast может инициализировать нажатие клавиш, мыши или событие таймера.
4.Добавьте на форму элемент “Timer” со следующим обработчиком события:
Private Sub Timer1_Timer()
Dim iKeyCounter As Integer
Dim Kvadrat As RECT
Dim Kvadrat1 As RECT
Kvadrat.Top = 0
Kvadrat.Left = 0
Kvadrat.Bottom = 600 'ddsdPrimBufer.lHeight
Kvadrat.Right = 800 'ddsdPrimBufer.lWidth
Kvadrat1.Top = 0
Kvadrat1.Left = 0
Kvadrat1.Bottom = ddsdSpriteBMP.lHeight
Kvadrat1.Right = ddsdSpriteBMP.lWidth
diDev.GetDeviceStateKeyboard diState
If diState.Key(2) Then '1'<> 0 Then
PrimBufer.BltFast 0, 0, OsnovaBMP, Kvadrat, DDBLTFAST_WAIT
PrimBufer.BltFast 100, 100, SpriteBMP, Kvadrat1, DDBLTFAST_SRCCOLORKEY Or DDBLTFAST_WAIT
Prim.Flip Nothing, DDFLIP_WAIT
End If
If diState.Key(3) Then '1'<> 0 Then
PrimBufer.BltFast 0, 0, OsnovaBMP, Kvadrat, DDBLTFAST_WAIT
PrimBufer.BltFast 0, 0, SpriteBMP, Kvadrat1, DDBLTFAST_SRCCOLORKEY Or DDBLTFAST_WAIT
Prim.Flip Nothing, DDFLIP_WAIT
End If
End Sub
Нажимая на клавиши “1” и “2” лицо человечка будет перемещаться. Обратите внимание, что один глаз у человечка “закрыт”. Происходит это потому, что прозрачными будут только черные пиксели файла из файла Fase.bmp. Отвечает за прозрачность черного цвета следующий код:
Key.low = 0
Key.high = 0
SpriteBMP.SetColorKey DDCKEY_SRCBLT, Key
5.Что бы безопасно выйти из приложения, с помощью клавиши “Esc”, добавьте следующий обработчик :
Private Sub Form_KeyPress(KeyAscii As Integer)
If KeyAscii = 27 Then
diDev.Unacquire
dd.RestoreDisplayMode
dd.SetCooperativeLevel Me.hWnd, DDSCL_NORMAL
End
End If
End Sub
Если не хотите связываться с DirectInput обработку нажатия клавиш для перемещения человечка можно поместить в событие Form_KeyPress (коды клавиш: “Esc”- код 27; “1”- код 49; ”2”- код 50).
На рисунке 2 представлено работающее приложение, описанное выше, оно не предусматривает наличие привычного для Windows-программы окна (см. проект папка N8/).
Рис. 2
Для создания оконных приложений DirectX, последовательность действий должна быть несколько иной. Приведем простой пример приложения DirectX обладающего окном (см. проект папка N9/). Приложение в работе представлено на рисунке 3.
Рис. 3
При нажатии на кнопку “PageUp”(стрелка вверх) винтовка начинает стрелять. Как и для безоконного приложения для каждого спрайт нужно создать поверхность. Для спрайта выстрела создается последовательность рисунков. На рисунке
4 представлена последовательность из файла “gun.bmp”.
Рис. 4
Полностью код приложения Вы можете просмотреть в среде Visual Basic. Приведем ключевые моменты. Основные глобальные объекты создаются как было показано в прошлом примере, но отсутствует DirectInput. Добавляется глобальное объявление переменной Dim objDDClip As DirectDrawClipper которая инициализируется в событии Form_Load:
Call objDD.SetCooperativeLevel(Me.hWnd, DDSCL_NORMAL)
ddsdScreen.lFlags = DDSD_CAPS
ddsdScreen.ddsCaps.lCaps = DDSCAPS_PRIMARYSURFACE
Set objDDScreen = objDD.CreateSurface(ddsdScreen)
Set objDDClip = objDD.CreateClipper(0)
objDDClip.SetHWnd Picture1.hWnd
objDDScreen.SetClipper objDDClip
Это нужно для ограничения области вывода графики прямоугольной областью которое занимает окно (так как приложение оконное). Обратите внимание как изменились флаги в функции SetCooperativeLevel, а так же на вызов функции SetClipper через объект поверхности objDDScreen (которой соответствует первичная поверхность Prim из прошлого примера). После считывания этого кода приложение знает о существовании и размерах окна на которое выводится графика.
Последние на что следует обратить внимание это вызов функции Call objDX.GetWindowRect(Picture1.hWnd, rPrim). Она вызывается перед вызовом функции блитинга для “подложки” на которую лягут спрайты. Это вызвано тем, что нужно знать положение ограничивающего прямоугольника окна(ведь окно может перемещаться по экрану).
Важным отличием от предыдущего примера является добавление в событие Form_Load “бесконечного” цикла Do While в котором происходит “зацикливание”, необходимое для анимации.
Изменилось и назначение таймера – теперь в нем происходит последовательная выборка вспышек выстрелов. Такая техника является заменой многопоточности в приложениях на VB.
В следующем примере (см. проект папка N10/) на этом принципе построено приложения которое может являться прототипом несложного тренажера или системой подсказки оператора, управляющего техническим процессом. Приведем относительно простой пример - за основу возьмем ввод в действие гипотетической атомного установки. Экран программы приведен на рисунке
5
Рис. 5
Код этой программы имеет значительный размер по сравнению с приложениями приведенными ранее, поэтому рассмотрим только некоторые из его отличительных особенностей. Программа разделена на четыре модуля – модуль инициализации DirectDraw; модуль для всех функций BltFast выводящих спрайты на буфер; модуль для объявления функций API, переменных и структур(тип RECT и POINTAPI); модуль в котором находится стартовая функция Main(), загружается форма и включается “бесконечный” цикл Do While. Программа состоит из трех форм Visual Basic. Первая форма реагирует на стандартные события мыши и клавиатуры, на ней так же установлены два таймера(имитируют многопоточность). Две другие формы служат хранилищами ресурсов графики.
Примечание. Часто для хранения ресурсов используют DLL.
Ресурсы нужны для создания поверхностей DirectDraw. В каталоге приложения Вы не найдете файлов типа *.BMP и соответственно поверхности не могут быть созданы с помощью функции CreateSurfaceFromFile, в место этого применен функция CreateSurface. В тексте программы это выглядит так:
ddsdResBMP.lFlags = DDSD_CAPS Or DDSD_HEIGHT Or DDSD_WIDTH
ddsdResBMP.ddsCaps.lCaps = DDSCAPS_OFFSCREENPLAIN
ddsdResBMP.lWidth = 742
ddsdResBMP.lHeight = 313
Set ResBMP = dd.CreateSurface(ddsdResBMP)
ddrDC1 = ResBMP.GetDC()
BitBlt ddrDC1, 0, 0, 742, 313, Form3.hDC, 0, 0, &HCC0020
ResBMP.ReleaseDC ddrDC1
Как видите и у поверхности DirectDraw
имеется контекст устройства. Получить доступ к ресурсам вспомогательных форм
можно было бы и таким способом:
MBM = CreateCompatibleBitmap(Form2.hDC, 800, 600) 'Образ в памяти
MDC = CreateCompatibleDC(Form2.hDC) 'Создание контекста
BitBlt(ddrDC1, 216, 58, 65, 130, MDC, 64, 0, &HCC0020).
В этом случае в памяти создается образ из
массива пикселей на основе которого строится совместимый контекст памяти,
передаваемый функции API BitBlt. Эта функция заполняет поверхность DirectDraw
пикселями.
Вращающиеся стрелки приборов созданы с помощью таких функций
GDI как: Polygon, SelectObject, CreatePen, DeleteObject, CreateSolidBrush.
Изменение координат стрелки в функции Polygon производилось с помощью нехитрых
тригонометрических преобразований вида:
With MassivTotsek2(0)
.x = ((Cos(ugpovRes1 / 180 * 3.14) * -2) - (Sin(ugpovRes1 / 180 * 3.14) * 10)) + 475
.y = ((Sin(ugpovRes1 / 180 * 3.14) * -2) + (Cos(ugpovRes1 / 180 * 3.14) * 10)) + 554
End With
With MassivTotsek2(1)
.x = ((Cos(ugpovRes1 / 180 * 3.14) * 0) - (Sin(ugpovRes1 / 180 * 3.14) * 30)) + 475
.y = ((Sin(ugpovRes1 / 180 * 3.14) * 0) + (Cos(ugpovRes1 / 180 * 3.14) * 30)) + 554
End With
'''''
With MassivTotsek2(2)
.x = ((Cos(ugpovRes1 / 180 * 3.14) * 2) - (Sin(ugpovRes1 / 180 * 3.14) * 10)) + 475
.y = ((Sin(ugpovRes1 / 180 * 3.14) * 2) + (Cos(ugpovRes1 / 180 * 3.14) * 10)) + 554
End With
Все навыки которые Вы получите изучая графические примитивы GDI с большой пользой можно использовать и в DirectDraw. Если бы не удалось воспользоваться GDI для рисования стрелок, пришлось бы применять спрайт для каждого положения шкалы кругового прибора или “придумывать математику” вращения изображения попиксельно, а без искажений сделать это очень трудно.
Примечание. Платформе .NET использует для работы с графикой интерфейс GDI+, который имеет быстрые функции для поворота битовой графики.