directdraw:DirectDraw 和 DirectInput 的游戏编程体验



  我想有关这个主题文章不算少但也不算太多但大多是分别介绍 DirectDraw 和 DirectInput而并没有将其结合起来也许你会问:“分开和合并起来并没有本质区别啊!”其实确没有本质区别但那样使那些最初对游戏编程报有极大热情爱好者感到非常失望这其中个并不能完全满足他们要求并且使其感到巨大阻力从而失去信心所以本文将 DirectDraw 和 DirectInput结合起来去讲个主题就是“游戏编程”请注意是“游戏编程”当然这只是个简单桌面游戏但这已经和先前有很大区别了这已不是简单 DirectDraw或 DirectInput编程我想你现在应该能够体会出其中区别了

  声明:在这的前需要你具有 WIN32 API 知识并且可以熟练使用和 DirectDraw知识有关DirectDraw可以参见 www.frontfree.net 中 <<动画编写——DirectDraw的旅>> 1-3)或其它文章最后是 c 语言当然也要包括面向对象那部分在 Visual C .NET 编译环境下进行开发

  首先 我们还是先简要复习下DirectDraw概念吧!
DirectDraw本质上是显存管理它最重要性能是允许员直接在显存里存储和操纵位图它使你能够利用视频硬件bliter(位块传输器)在显存内部进行位图blit(位块传输)用视频硬件blitter从显存向显存进行blit比从内存向显存更快这在64位显卡向显存提供64位数据路径今天显得尤其重要硬件独立于促CPU进行位块传输操作使得CPU得以继续工作另外DirectDraw支持显卡其他硬件加速特性例如对精灵和z -buffering硬件支持

DirectDraw工作原理

我们这里还是用图表方式展现给大家吧!

\" width=505>

  细心朋友可以很明显地注意到图示中右上角图解中介绍说明表面对象有两个宽度个是WIDTH个是PITCHWIDTH就是创建表面时所给出那个宽度而PITCH是表面实际宽度是按字节算在许多显卡上PITCH和WIDTH是相等比如在640x480高彩模式下PITCH为1280而在某些显卡上PITCH比WIDTH要大比如在640x480256色模式下当WIDTH是640时PITCH为1024而不是640这些显卡这样做是为了更好地进行数据对齐来提高性能或达到其它目所以我们在实际编程时为了保证兼容性必须按PITCH处理 但这些硬件底层问题我们不用太关心只要稍有了解就可以了

下面我们再简要叙述如何使用 DirectX 9.0 中提供 DirectDraw 类库来创建对象并使用操作对象

宏定义在先定义删除指针和释放对象

# SAFE_DELETE(p) { (p) { delete (p); (p)=NULL; } }
# SAFE_RELEASE(p) { (p) { (p)->Release; (p)=NULL; } }



先创建个 CDisplay 全局对象
CDisplay就是ddutil.h中定义用于处理表面的间拷贝翻页等操作再次定义个全局变量用于以后对指向表面的间进行操作

CDisplay* g_pDisplay = NULL;

然后创建表面当然可以创建很多表面这些表面都是离屏表面在更新画面时都可以用 CDisplay 类对象中思路方法将其拷贝到后备缓冲区表面上只要创建离屏表面就要用到 CSurface 类CSurface也是ddutil.h头文件中定义用于对表面本身进行操作如设置色彩键码在此定义图画指针
 

CSurface* g_pBackSurface = NULL;

DirectX 中就共用这两个类封装了 DirectDraw 对象大部分操作如果你觉得这还不能满足要求那么你也可以在中用 DirectDraw API 编写不过在本文中不再介绍

这的后我们会用到 InitDirectDraw 这个是我们自己创建在此中作所有 DirectDraw 对象化工作

HRESULT InitDirectDraw( HWND hWnd )
{
HRESULT hr; //接受返回值其实是long型变量
LPDIRECTDRAWPALETTE pDDPal = NULL; //定义调色板
iSprite; //定义和sprite个数有关计数器
g_pDisplay = CDisplay; //动态开辟个CDisplay类
( FAILED( hr = g_pDisplay->CreateFullScreenDisplay( hWnd, SCREEN_WIDTH,
SCREEN_HEIGHT, SCREEN_BPP ) ) ) /*设置为全屏并且 g_pDisplay 就是动态开辟个CDisplay类指针而在这个类域中个DirectDraw主表面指针个后备缓冲区表面指针在从我建议你可以先去阅读下 ddutil.h 和 ddutil.cpp 文件*/
{
MessageBox( hWnd, TEXT(\"This display card does not support 1024x768x8. \"),
TEXT(\"DirectDraw Sample\"), MB_ICONERROR | MB_OK );


hr;
}
( FAILED( hr = g_pDisplay->CreatePaletteFromBitmap( &pDDPal, MAKEINTRESOURCE( IDB_DIRECTX ) ) ) ) //顾名思义就是从bmp图片中获得调色板值并赋值在pDDPal结构指针所指向结构体中
hr;
( FAILED( hr = g_pDisplay->SetPalette( pDDPal ) ) ) //用刚才从IDB_DIRECTX中获得调色板制来设置调色板
hr;
SAFE_RELEASE( pDDPal );//释放指针在用过后定要释放这是良好编程习惯
// 用IDB_WINXP图片创建个表面并用g_pBackSurface指向这个表面
( FAILED( hr = g_pDisplay->CreateSurfaceFromBitmap( &g_pBackSurface, MAKEINTRESOURCE( IDB_WINXP ),
SCREEN_WIDTH, SCREEN_HEIGHT ) ) )
hr;//设置色彩键码为黑色0代表黑色这样在表面拷贝过程中黑色像素点将不会被拷贝这样可以产生镂空效果当然你可以任意设置关键颜色而颜色表示法可以用 RGB 宏定义例如 红色:RGB( 255,0,0 ), 黑色 RGB( 255,255,255 )
( FAILED( hr = g_pBackSurface->SetColorKey( RGB( 255,255,255 ) ) ) )
hr;
S_OK;
}

下面是用于更新画面
 

HRESULT DisplayFrame
{
HRESULT hr;
g_pDisplay->Clear( 0 ); //清空后备缓冲区表面
//将g_pBackSurface所指向图片拷贝到后备缓冲区表面
g_pDisplay->Blt( 0, 0, g_pBackSurface, NULL );//最关键地方在这里请看下面语句只要我们执行翻页操作就可以将改动了图像了显示在屏幕上了
( FAILED( hr = g_pDisplay->Present /*翻页操作*/) )
hr;
S_OK;
}

下面是用于在失去焦点时

HRESULT RestoreSurfaces
{
HRESULT hr;
LPDIRECTDRAWPALETTE pDDPal = NULL; /*当失去焦点要保存当前画面请注意这里g_pDisplay->GetDirectDraw返回才是真正 DirectDraw 对象 */
( FAILED( hr = g_pDisplay->GetDirectDraw->RestoreAllSurfaces ) )
hr;//在此我们还要重新创建调色板
( FAILED( hr = g_pDisplay->CreatePaletteFromBitmap( &pDDPal, MAKEINTRESOURCE( IDB_DIRECTX ) ) ) )
hr;//重新设置调色板
( FAILED( hr = g_pDisplay->SetPalette( pDDPal ) ) )
hr;
SAFE_RELEASE( pDDPal );//重新画出图画
( FAILED( hr = g_pLogoSurface->DrawBitmap( MAKEINTRESOURCE( IDB_WINXP ),
SPRITE_DIAMETER, SPRITE_DIAMETER ) ) )
hr;
S_OK;
}

下面这个是释放表面指针所用

VOID FreeDirectDraw
{
SAFE_DELETE( g_pBackSurface );
SAFE_DELETE( g_pDisplay );
}

我们回顾到此结束下面我们开始本文要介绍个关键技术DirectInput 使用
游戏编程可不仅仅是图形开发工作实际上包含了许多方面本文所要讲述就是有关如何使用 DirectInput 来对键盘编程问题
而我们为什么要选择用 DirectInput 来处理游戏中输入问题呢?其实用 Win32 API 也完全可以处理这些工作例如其中

GetAsyncKeyState 可以返回个指定键当前状态是按下还是松开这个还能返回该指定键在上次 GetAsyncKeyState 以后是否被按下过虽然这个听上去很不错但需要我们自己轮换查询每个键盘状态而在 DirectInput 中我们已经可以脱离这些烦琐工作只因它功能更强大

由于本文重点在 2者结合故在此只介绍 DirectInput 中比较简单而且最容易上手立即模式工作方式
而这里我们要用到 DirectInput API 有人会问为什么在 DirectDraw 中用 DirectX 提供类库编程而对于 DirectInput 却直接使用要用其 API 没有提供 DirectInput 类库吗?不是!而是使用类库并不很方便而且不灵活



OK让我们开始我们游戏编程第 2部——DirectInput编程

前面讲 DirectDraw 时并没有提到微软是按 COM 来设计DirectX所以就有了个 DIRECTINPUT 对象来表示输入设备而某个具体设备由 DIRECTINPUTDEVICE 对象来表示也许会感到很无奈如何游戏编程需要这么多知识啊其实您也无需烦恼只要知道下就可以了其实这并不;影响您设计而且就算您不知道也同样可以驾驭DIRECTINPUT

实际建立过程是先创建个 DIRECTINPUT 对象然后在通过此对象 CreateDevice 思路方法来创建 DIRECTINPUTDEVICE 对象

# <dinput.h>
# DINPUT_BUFFERSIZE 16
LPDIRECTINPUT lpDirectInput; // DirectInput 对象实际上是个com对象
LPDIRECTINPUTDEVICE lpKeyboard; // DirectInput 设备
BOOL InitDInput(HWND hWnd)
{
HRESULT hr;// 创建个 DIRECTINPUT 对象
( FAILED( hr = DirectInputCreate(hInstanceCopy, DIRECTINPUT_VERSION, &lpDirectInput, NULL)))
{// 失败提示或处理
hr;
}// 创建个 DIRECTINPUTDEVICE 界面
//参数 GUID_SysKeyboard 指明了建立是键盘对象
( FAILED( hr = lpDirectInput->CreateDevice(GUID_SysKeyboard, &lpKeyboard, NULL)))
{
// 失败提示或处理
hr;
}// 设定为通过个 256 字节返回查询状态值
( FAILED(hr = lpKeyboard->SetDataFormat(&c_dfDIKeyboard)))
{
// 失败提示或处理
hr;
}// 设定协作模式为独占模式和前台模式独占模式表面本在运行中占有所有键盘资源而前台模式指出当具有焦点时才可以占有键盘资源
( FAILED( hr = lpKeyboard->SetCooperativeLevel(hWnd, DISCL_EXCLUSIVE | DISCL_FOREGROUND)))
{
// 失败提示或处理
hr;
}
// 设定缓冲区大小
// 如果不设定缓冲区大小默认值为 0就只能按立即模式工作
// 如果要用缓冲模式工作必须使缓冲区大小超过 0
// 在此我们没有必要设定我们就用立即模式工作(还有种缓冲模式),所有我们将其注调了

/* DIPROPDWORD property;

property.diph.dwSize = (DIPROPDWORD);
property.diph.dwHeaderSize = (DIPROPHEADER);
property.diph.dwObj = 0;
property.diph.dwHow = DIPH_DEVICE;
property.dwData = DINPUT_BUFFERSIZE;

( FAILED(hr = lpKeyboard->SetProperty(DIPROP_BUFFERSIZE, &property.diph)))
{
// 失败
FALSE;
}
*/
//此处是关键我们要通过这个来锁定键盘记住所有DirectInput资源在使用前都要锁定在此即获得键盘资源在知识我们刚才设定键盘模式才能起作用
hr = lpKeyboard->Acquire;
FAILED(hr)
{
// 失败
FALSE;
}
TRUE;
}



在这段代码中我们首先定义了 lpDirectInput 和 lpKeyboard 两个指针前者指向 DIRECTINPUT 对象后者指向

DIRECTINPUTDEVICE 界面其顺序就是这样这和其它COM对象使用思路方法都即先创建 COM 对象然后创建界面然后再获得
硬件资源然后使用资源然后释放

通过 DirectInputCreate, 我们为 lpDirectInput 创建了个 DIRECTINPUT 对象然后我们 CreateDevice 来建立个DIRECTINPUTDEVICE 界面
  
完成这些工作以后我们便 DIRECTINPUTDEVICE 对象 Acquire 思路方法来激活对设备访问权限在此要特别介绍说明任何

DIRECTINPUT 设备如果未经 Acquire是无法进行访问还有当系统切换到别进程时必须用 Unacquire 思路方法来释放访问权限在系统切换回本进程时再 Acquire 来重新获得访问权限

立即模式数据查询

HRESULT ReadImmediateData( HWND hWnd )
{
HRESULT hr;
BYTE diks[256]; // 创建键盘状态数据缓冲区存取键盘信息
i; // 计数器


( NULL g_pKeyboard )
S_OK;// 键盘状态数据缓冲区清0
ZeroMemory( &diks, (diks) );// 获得键盘所有键信息这只是检查
hr = g_pKeyboard->GetDeviceState( (diks), &diks );
( FAILED(hr) )
{
// 如果键盘资源丢失我们要重新获得
hr = g_pKeyboard->Acquire;
while( hr DIERR_INPUTLOST )
hr = g_pKeyboard->Acquire;
S_OK;
}// 进行下轮循处理键盘信息
for( i = 0; i < 256; i )
{
( diks[i] & 0x80 ) //记录此键状态低字节最高位是 1 表示按下0 表示松开般用 diks[i]&0x80 来测试
{
switch(i)
{
//我们可以通过测试计数器i来判断是哪个键被按下了
//我们提供几个数据 UP:200 down:208 left:203 right:205 enter:28 space:57
//其实你可以用DirectX中Samples\\C\\DirectInput\\Bin\\Keyboard.exe来测试只不过那是用
//16进制显示
200:
;
0xc8:
;
}
}
}
S_OK;
}

请注意上面这段代码只是个举例重在使你明白其原理但并不能满足游戏需求这其中只查询了次键盘全部信息做了次轮循而在游戏中要周期性地查询并轮循这就需要你自己用Win32 API SetTimer和 KillTimer 设置化 DirectInput 对象中在相应地方设置计计时器让windows定时向发送 WM_TIMER消息你要通过此消息进行周期性地键盘查询并在相应地方解除计时器

最后是用于释放指针或DirectInput对象

void ReleaseDInput(void)
{
(lpDirectInput)
{
(lpKeyboard)
{
// Always unacquire the device before calling Release.
lpKeyboard->Unacquire;
lpKeyboard->Release;
lpKeyboard = NULL;
}
lpDirectInput->Release;
lpDirectInput = NULL;
}
}

在这些注释很明确关键在于理解其原理而怎样将他们融入到 Win32 API 基本框架中在<<动画编写——DirectDraw的旅>> 1-3中举例代码中已经解释得很明确了在此不再赘述不过我们提供其中代码举例下载同时你也可以去仔细阅读DirectX 8.0 SDK 包中 samples\\ multimedia\\ directdraw\\ fullscreenmode 或 \\ samples\\ multimedia\\ directdraw\\ windowedmode 这两个工程中文件为了我们举例也是照这两个工程改编过来读者可以通过仔细阅读代码和对比我们更改而更加了解 DirectDraw运行运行原理(请注意:是 DirectX 8.0 SDK 包中举例而在 9.0 中 DirectX SDK 已经不提供 DirectDraw举例代码了)

我们就用这 7个就已经可以创造出个小游戏了

我们下面就要利用<<动画编写——DirectDraw的旅>> 1-3 中所用代码进行进游戏开发
我们先展示下 DirectX中\\ samples\\ multimedia\\ directdraw\\ windowedmode 工程中截图
在这个动画中有黑色背景并有很多 DirectX 精灵在漂浮



这是个全屏动画而我们在<<动画编写——DirectDraw的旅>> 1-3其中做改动就是为其加了个背景改屏幕分辨率 640×480 为 1024×768.注意我们应用是全屏模式即可以独占显存资源所以我们可以更改屏幕分辨率这只是做小小改动而我们只在于让大家更加深入了解且看下面这副截图:



而我们还要继续深入编程我们思路是先将由先前全屏改编成个windows窗口然后将其所有界面翻新并改编 DirectX精灵为许多小蘑菇在漂浮还要加入DirectInput 组建用键盘控制个小娃娃可以上下左右并可以斜向飞行
我们先将此动画截图展现给大家





如何样你有什么想法是想说:“唉这还不好办就是又多加了个!”但不要光看截图不要忘记我们定让她动起来并且是可以控制这就不是那么简单事了!
什么?若有人看到这里感到有些迷茫和泄气不禁想问:“你说了这么多那么源代码在那里呢!光给我们几个又能做什么呢?”如果你这么想你也不要太急迫我们还是先分析框架吧

不过还有件重要事情我还是要重申定要将 DirectX 头文件价和lib文件夹加入到 Visual C.NET 默认目录中去这样编译器就可以正确地找到它们了
如果你不会加入就请通过工具栏上 Tool -> Option… 打开Option 对话框设置如图:



好了这样我们准备工作就算已经做好了

来看看我们工程文件结构吧还有工程中资源



在工程资源中我们 ID 号是都用串表示笔者认为这样更加方便
我想对于工程文件中 ddutil.cpp 和 dxutil.cpp 文件读者如果了解有些 DirectDraw编程是不会感到陌生我们只是将其引入到我们工程中了而我们自己实际编程文件是
outfly.cpp 文件

我们叙述如下:
首先进行宏定义结构设置和全局变量声明
后在 WinMain (windows入口点)中首先切需要物件(有windows窗口DirectDraw对象和 DirectInput对象)在此我们就前文讲过但要有写改动读者在会在后面看到然后进入消息循环在其中没有消息时会自动更新画面在有消息时处理消息
当遇到 WM_QUIT 消息后结束整个

我们在些地方有些小小改动我们来看看吧

1 我们在 HRESULT InitDirectInput( HWND hWnd )
开始加入了

KillTimer( hWnd, 0 );
FreeDirectInput;

关掉上次使用计时器并释放 DirectInput 设备
而在最后加入了

SetTimer( hWnd, 0, 1000 / 100, NULL );

用来重新设置计时器

2 我们在主窗口消息处理中加入了

WM_ACTIVATE: //当先失去焦点而现在有重新得到焦点时要重新锁定键盘资源
( WA_INACTIVE != wParam && g_pKeyboard )
{
// Make sure the device is acquired, we are gaining focus.
g_pKeyboard->Acquire;
}
;
WM_TIMER: //设置了计时器所以要处理此消息
( FAILED( ReadImmediateData( hWnd ) ) )
{
KillTimer( hWnd, 0 );
MessageBox( NULL, _T(\"Error reading input state. \")
_T(\"The sample will now exit.\"),
_T(\"Keyboard\"), MB_ICONERROR | MB_OK );
}
;
WM_DESTROY:// Cleanup and close the app
FreeDirectDraw;
FreeDirectInput; // 释放资源
PostQuitMessage( 0 );
0L;

3 在HRESULT ReadImmediateData( HWND hWnd ) 中进行了这样处理来时时改变小娃娃坐标

for( i = 0; i < 256; i )
{
( diks[i] & 0x80 )
{
switch(i)
{
200: //上键
( g_me.fPosY > g_me.fVelY)
g_me.fPosY -= g_me.fVelY;

g_me.fPosY = 0;
;
208: //下键
( g_me.fPosY <= WINDOW_HEIGHT - SPRITE_DIAMETER - g_me.fVelY)
g_me.fPosY g_me.fVelY;

g_me.fPosY = WINDOW_HEIGHT- SPRITE_DIAMETER;
;
203://左键


( g_me.fPosX > g_me.fVelX)
g_me.fPosX -= g_me.fVelX;

g_me.fPosX = 0;
;
205://右键
( g_me.fPosX <= WINDOW_WIDTH - SPRITE_DIAMETER - g_me.fVelX)
g_me.fPosX g_me.fVelX;

g_me.fPosX = WINDOW_WIDTH- SPRITE_DIAMETER;
;
}
}
}

这些只是其中些比较重要改动还有许多改动读者会在实际中看到如果你觉得:“啊!到这里就结束了可是我还是感到似乎莫不到头绪就这样草草收尾了?”其实文章并没有结束重头戏还在后面呢那就不是我工作了而是看你有没有耐心去仔细阅读代码了想要把握整体和其让我将代码放在文章中还不如读者自己在编译器中自己运行实战下好其实我们已经在第 2个工程代码中有过详细解释但记住定要按照顺序阅读 工程1工程2 工程3工程1就是 DirectX中提供原代码工程2就是我们改了个背景工程而3就是我们讨论工程



Tags:  directdrawerror directdraw不可用 directdraw加速 directdraw

延伸阅读

最新评论

发表评论