directdraw:DirectDraw编程基础



  本文面向有几个月学习编程经历初学者:看过C教程懂基本C语法;有点点VC使用经验知道如何去组建个工程;理解些windows编程基本概念比如窗口、消息循环等;还有不懂地方会去查资料:)
   看过几本有关DirectDraw这些书都不错在此感谢她们作者美中不足是这些书部分起点较高虽然我们仍然能够清晰理解些概念但在组织这些文件上会有不少困惑在此我重申下书中概念也借此梳理下自己思路废话少说言归正传
首先说些不可不说东西我认为它们不可不提这些东西也许太基础高手们往往忽略这些东西对新手作用作为个新手我觉得掌握框架及组织思路方法比多熟悉几个APIs更迫切Now lets begin:
   写个游戏要熟悉其流程另外要锻炼组织文件能力对新手来说我建议按部就班来处理及分析要写不主张这个时候你在搞思维跳跃这是个良好习惯当然也有利于我们尽快掌握编程思想思路方法下面来看个概括流程及相应框架

(框架显示不出来)

   那么如何利用上面流程来构建我们大体框架呢?

   我们已经知道些windows编程方面东西了也许你还比较了解MFC我们这里不提倡用MFC尽管它封装了好多有用模式但对我们编游戏来说倒是累赘了接着说既然采用windowsAPI可以建立个文件WinMain.cpp来处理windows编程中有关窗口些问题这样我们在该文件中应该完成创建窗口处理基本消息(比如按“esc”退出等)控制退出等游戏过程中窗口消息是不是也要在这处理呢?当然不过游戏当中窗口就不仅是windows窗口了显示部分要靠DirectDraw来控制那么我们只好在WinMain.cpp中相关模块来处理这么看来在WinMain.cpp中几乎囊括了整个流程,不错它就控制了整个框架为你内核提供了个平台平台有了那么下GameMain.cpp要诞生了这个主要用来控制整个游戏各个组件协调各部分工作完成游戏设置游戏中消息循环控制游戏退出才华就在这儿来尽情发挥了游戏会有几个固定组件:显示音乐信息输入在DirectX中提供了很方便组件DirectDrawDirectSound和DirectMusicDirectInput相应我们建立MyDirectDraw.cppMyDirectAudio.cppMyDirectInput.cpp来控制各部分组件相应功能
显然这3部分都是为GameMain.cpp服务被GameMain.cpp那么我们可以看出我们应该包括文件及其包含关系为:

(图表显示不出来了555)

   文件如何去组织应该由这个表可以看出来这么我们发现WinMain.cpp好像是个投资者提供开发平台他只关注整个项目总进程不关注细节GameMain.cpp好像个项目负责人整个项目细节过程由他来策划来控制向上和WinMain.cpp交互来完成项目向下协调MyDirectDraw.cppMyDirectAudio.cppMyDirectInput.cpp的间工作MyDirectDraw.cppMyDirectAudio.cppMyDirectInput.cpp这 3个家伙就是员工了负责各自工作完成相应功能给GameMain.cpp

   组织应该就是这么个思路当然具体问题具体分析那么我们下面来开始看DirectDraw部分了

   首先做准备工作安装DirectX SDK在VC中添加dxguid.lib和ddraw.lib(本来不想说这个看到有个教程它少加了dxguid.lib郁闷了我好阵子害人颇深感觉)这样directdraw才能通过编译dxguid.lib中定义了DirectX中会用到所有全局句柄ddraw.lib是DirectDraw使用

下面就可以写代码了这里我们当然主要看MyDirectDraw.cpp该如何写了
为此我选出了几个源代码做参考研究它们会和本文起打包
我还是习惯先从整体上鸟瞰下:

   在MyDirectDraw.cpp(注意不要忘记引用头文件ddraw.h)中至少要有两部分:化和结束先看所谓化无非是个准备工作需要东西定义创建出来摆在手边以备后用来看看MyDirectDrawInit(void)该如何写首先定义个指向DirectDraw对象指针创建DirectDraw对象查询以获取最新DirectDraw接口设置协作等级设置显示模式通过这些步骤可以创建个黑色屏幕了也就是说已经开辟了我们需要空间了当然DirectDraw化不会这么简单要操作2d图形我们还要接着创建主页面和缓冲页面以及离屏页面总的根据需要凡是需要在操作前需要准备好东西都可以放在这里那么结束 MyDirectDrawShut(void)就应该释放我们开辟东西般要释放主页面指针和DirectDraw接口等

大体就是这么个样子go _disibledevent=>先定义指针:LPDIRECTDRAW lpDDraw_temp;代表整个显示系统
创建对象: (FAILED(DirectDrawCreate(NULL, &lpDDraw_temp, NULL)))
{
MessageBox(NULL,TEXT(\"Direct Draw Create error!\"),
TEXT(\"Wrong!\"),MB_OK);
(0);
}

这里用了个FAILED宏来检测是否创建成功这可以帮我们跟踪

DirectDrawCreate(NULL, &lpDDraw_temp, NULL)完成创建个参数是显示驱动全局唯标志符这里null表示目前显示设备;第 2个参数用来接受创建出来DirectDraw对象地址这里用&lpDDraw_temp接受;第 3个参数?不要问就给它null不想惹麻烦

查询DirectDraw接口:(FAILED(lpDDraw_temp->QueryInterface(IID_IDirectDraw7, (LPVOID *)&lpDDraw7)))
{
MessageBox(NULL,TEXT(\"DirectDraw QueryInterface error!\"),
TEXT(\"Wrong!\"),MB_OK);
(0);
}

通过QueryInterface思路方法来获取新接口这里是IDirectDraw7而不是IDirectDraw8指向IDirectDraw7指针放在lpDDraw7中这是个全局变量可以这样定义LPDIRECTDRAW7 lpDDraw7=NULL;



顺便说般情况下你是应该知道你使用接口这和SDK有关所以说这步不是必须

设置协作等级: (FAILED(lpDDraw7->SetCooperativeLevel(_window_handle, DDSCL_FULLSCREEN | DDSCL_ALLOWMODEX | DDSCL_EXCLUSIVE | DDSCL_ALLOWREBOOT)))
{
MessageBox(NULL,TEXT(\"DirectDraw SetCooperativeLevel error!\"),
TEXT(\"Wrong!\"),MB_OK);
(0);
}

决定你这个和windows关系它向windows申请所用资源比如它要全屏独占等个参数是主窗口句柄就是你WinMain()中创建那个了第 2个参数有几个控制标志常用使用方法如下:

DDSCL_FULLSCREEN:全屏模式必须和DDSCL_EXCLUSIVE同时使用
DDSCL_EXCLUSIVE:请求独占级别须和DDSCL_FULLSCREEN同时使用
DDSCL_ALLOWREBOOT:允许系统检测ctrl+alt+del按键消息(这很有用)

我想这 3个就够用了其他就先不用管了
设置显示模式:(FAILED(lpDDraw7->SetDisplayMode(800, 600, 16,0,0)))
{
MessageBox(NULL,TEXT(\"DirectDraw SetDisplayMode error!\"),
TEXT(\"Wrong!\"),MB_OK);
(0);
}

游戏中要使用显示模式可能和用户当前显示模式不要在此统设置SetDisplayMode()强制使用它设置模式前 3个参数很容易懂吧第 4个用0表示使用默认刷新率第 5个参数这里是0有书上说必须用DDSDM_STANDVGAMODE(可以理解只是不知道这个0什么意思我想应该是default意思吧

到此为止我想已经创建出来我们需要空间了以后随着我们要求提高再逐步完善now看看结束:
释放接口: (lpDDraw7)
{
lpDDraw7->Release;
lpDDraw7 = NULL;
}

以后还要释放主页面缓冲页面等需要注意定要释放你申请资源这是个好习惯更应该注意点是先创建定要后释放后创建可能是在先创建环境下工作

到此为止我们只是做好了最基础准备工作什么还都不能做呢
想做点什么吗?歇会吧说点不得不说题外话:

那么我们来看看颜色吧有关色彩分这么几种256色(8位)16位增强色24位真彩和32位真彩256色估计很少用了16位目前还是主流所以我们着重看下16位增强色通常16位增强色有两种格式:5.5.5和5.6.5般用RGB表示法表示其中:
5.5.5格式最高位为Alpha位表示是不是透明其余15位表示颜色红绿蓝各5位这种格式可以表示32786种颜色通过宏

_RGB16BIT555(r,g,b)((b%32)+((g%32)<<5)+((r%32)<<10))来转变成5.5.5格式

对5.6.5格式显然红蓝各5位绿6位这样可以表示65536种颜色同样

_RGB16BIT565(r,g,b) ((b%32)+((g%64)<<6)+((r%32)<<11))来转变成5.6.5格式

中间移位我也搞不清楚是如何回事姑且先不看了越多可能越胡涂哦
那么到底该用哪种格式?看机器了大部分可以用5.6.5当然你可以检测至于如何检测嘛我就不说了查查相关资料就可以了24位呢?红绿蓝各8位呗32位?添个Alpha位其余同24位好了颜色就说到这里

下面想干嘛?想在屏幕上搞点颜色出来参看附源代码code1
   你会不会发现我们还应该在上面基础上添点什么?对应该在里创建页面,也就是DirectDrawSurface对象那它和DirectDraw对象什么区别?DirectDraw对象我们知道是表示整个显示系统也就是你显卡和显屏构成那个系统你能在显示器屏幕上直接画点东西吗?不行显屏上东西是通过显存和内存操作把里面东西显示出来那么相对应于显屏内存中就应该有张矩形白纸供你作画然后才能把它在显屏上显示那张白纸就是DirectDrawSurface对象代表了显存或内存里个连续线性数据区这个数据区可以被代表显示硬件DirectDraw对象所识别和确认可以创建页面有4种我们常用有主页面(primary surface)和离屏页面(offscreen plain)先说主页面就是块显存在主页面中图形会显示到屏幕中直接在主页面上操作会有个问题数据图象就会不连续为此可以采用缓冲技术即建立个Back buffer(后台缓冲)说白了就是在内存中再开辟块区域和主页面区域对应这样就可以不直接操作主页面先把数据写入到这里然后通过换页成为可见离屏页面区别了它是和主页面画面但是它永远不在屏幕上表现出来通常被用来存储位图用于将后来位图图象Blit到主页面或后台缓冲上那么我们来看下这几个页面在工作当中位置及作用:

(此处有图表显示不出来)

这样我们大体了解了页面作用那么化时就应该创建好以等待到时对页面操作于是我们中就应该再添加:

mem(&ddsd,0,(ddsd));
ddsd.dwSize=(ddsd);
//设置dwFlags告诉DirectDraw哪些成员可用
ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
//定义ddsCaps.dwCaps请求个带后台缓冲主页面
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_COMPLEX | DDSCAPS_FLIP;
//定义设置后台缓冲数量为1
ddsd.dwBackBufferCount = 1;
//创建主页面
(FAILED(lpDDraw7->CreateSurface(&ddsd, &lpDDprimary, NULL)))
{
MessageBox(NULL,TEXT(\"DirectDraw Create primary Surface error!\"),
TEXT(\"Wrong!\"),MB_OK);
(0);
}
//设置ddsCaps.dwCaps
ddsd.ddsCaps.dwCaps = DDSCAPS_BACKBUFFER;
//连接主页面及后台缓冲
(FAILED(lpDDprimary->GetAttachedSurface(&ddsd.ddsCaps, &lpDDback)))
{


MessageBox(NULL,TEXT(\"DirectDraw Create back Surface error!\"),
TEXT(\"Wrong!\"),MB_OK);
(0);
}

在这里我们要定义几个全局变量:
extern LPDIRECTSURFACE7 lpDDprimary;
extern LPDIRECTSURFACE7 lpDDback;
extern DDSURFACEDESC2 ddsd;

这是个指向主页面指针个指向后台缓冲指针个页面描述结构不用说这些定义你可以放在MyDirectDraw.h中通过填充ddsd结构成员来申明你所想创建页面类型这里我们没创建离屏页面用主页面及后台缓冲可以完成些相对简单数据不是很多图形显示数据过于复杂就应该创建离屏页面了

相应在结束时除了释放DirectDraw7接口外还要依次释放后台缓冲指针和主页面指针还是提醒先创建定要后释放不然你会死很难堪如何去Release这些东西看看code1中代码很容易明白

顺便我们看下如何创建离屏页面看下面代码:
DDSURFACEDESC2 ddsd;
LPDIRECTSURFACE7 lpDDopl; //这两个定义不用说了吧
mem(&ddsd,0,(ddsd)); //清空结构内容
ddsd.dwSize=(ddsd); //设置大小
ddsd.dwFlags = DDSD_CAPS |DDSD_HEIGHT|DDSD_WIDTH;
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;//指定页面类型
ddsd.dwWidth=600;
ddsd.dwHeight=800; //设置离屏页面大小
(FAILED(lpDDraw7->CreateSurface(&ddsd, &lpDDopl, NULL)))
{
MessageBox(NULL,TEXT(\"DirectDraw Create offscreen plain error!\"),
TEXT(\"Wrong!\"),MB_OK);
(0);
} //创建离屏页面

Okay!离屏页面就创建好了离屏页面是个独立页面不隶属于任何其他页面所以你必须指定它大小

有关页面创建我们就说到这到这儿是不是有种万事具备只欠东风感觉啊?

抬头天亮了该睡觉了睡醒咱们再接着说先去呼呼了

……n小时后……

好了既然只欠东风我们就来说东风

简单画图我们可以参看code1(在屏幕上打点)

   有关页面运用位图操作(作图也就这两个东西)我还组织不起来无法把理解到东西组织到中(汗!还没真正理解就好意思在这说)我也在学嘛多理解几遍说不定就能够组织了那么那么我们只能像我看过几本资料来拆开来说了开始照单全收抄书希望抄完后能有点组织眉目

   从以前那个页面表可以看出这里操作无非是载入位图贴图翻页显示以及对画面进行剪贴那我们步步来说吧这里就事论事就模块论模块代码段和以前文件没多大关联了大家看不明白了不要骂我理解万岁

先看载入位图即将位图load到离屏页面中,要通过windowsHDC来进行存取用windowsAPI配合DirectX来完成我们看代码段:

HDC hdchdc1; //声明HDC对象hdc用来存储位图hdc1代表离屏页面DC
HBITMAP bitmap; //声明HBITMAP对象
hdc=::CreateCompatatibleDC(NULL);//建立和目前显示模式兼容DC(参数为null)
bitmap=(HBITMAP)::LoadImage(NULL,”bgroud.bmp”,IMAGE_BITMAP,640,480,LR_LOADFROMFILE);
//加载640*480位图
::SelectObject(hdc,bitmap); //使用windows设置hdc中内容为bitmap

现在把位图加载到了DC中下面就要把DC中位图贴到离屏页面中了

LPDIRECTSURFACE7 lpDDopl; //这个定义不用说了吧
HRESULT result;//干嘛用?往下看
lpDDopl->GetSurfaceDesc(&ddsd);//ddsd和我们前面定义过
result= lpDDopl->GetDC(&hdc1);//用GetDC()来取得离屏页面DC
(result!=DD_OK)
MessageBox(“取得暂存区DC失败”);//是否取得成功了解result做这个用
::Bitblt(hdc100ddsd.dwWidth,ddsd.dwHeight,hdc,0,0,SRCCOPY);
//这个就是贴图用windows
lpDDopl->releaseDC(hdc1);//释放离屏页面DC定要释放

到此我们已经把位图贴到离屏页面中了下面应该把离屏页面DC中位图填充到back buffer中然后通过换页显示出来先来了解两个DirectDraw贴图Blt和BltFast这两个原型在老王翻译directx开发手册中有详细介绍说明在我主页上可以down到你可以查阅这里我简单说下:

HRESULT Blt( LPRECT lpDestRect, //目标页面区域lpDestRect定义其左上右下点坐标
LPDIRECTDRAWSURFACE7 lpDDSrcSurface,//源页面指针
LPRECT lpSrcRect, //源页面区域
DWORD dwFlags,//控制标志详见老王手册
LPDDBLTFX lpDDBltFx)//图形变换信息结构详情请自己查阅
HRESULT BltFast( DWORD dwX, //目区域左上x坐标
DWORD dwY, //目区域左上y坐标
LPDIRECTDRAWSURFACE7 lpDDSrcSurface//源页面指针
LPRECT lpSrcRect, //源页面区域
DWORD dwTrans//转换参数见老王手册

这两者差别就是Blt多了图形放缩功能但是BltFast效率较高如何选用已经很清楚了这两个个就能够实现从离屏页面到back buffer贴图代码如下:

lpDDback->BltFast(0,0,lpDDopl,CRect(0,0,640,480),DDBLTFAST_WAIT);

//lpDDback是我们以前声明过后台缓冲CRect(…)是个CRect类对象如果我们已声明了个CRect rect;这里就可用&rect来代替
单看贴图这步操作还是很easy
看起来好像离显示只有步的遥了啊right只要翻页(flip)下就okay了

先看翻页:
HRESULT Flip( LPDIRECTDRAWSURFACE7 lpDDDestSurface,//你想翻到目标页
DWORD dwFlags) //通常设为DDFLIP_WAIT

介绍说明下:第个参数为null时表示翻到目前页面所连接个页面当换页对象是可见页面比如主页面换页链进行换页Flip和系统CPU是异步执行这就是说在这些可见页面上Flip它只是简单告诉显示硬件该进行换页了并不需要等待换页操作在硬件设备中实际完成后才返回这是显示硬件(显示器)只有在完成次垂直刷新后才能进行次换页所以Flip成功并不意味着换页已经完成在实际换页操作进行的前对即将成为主页面后台缓存Cache是不能锁定和进行Blit操作要让Flip成为和系统CPU同步操作时指定DDFLIP_WAIT标志即可



代码同样简单:
lpDDprimary->Flip(NULL,DDFLIP_WAIT);

   小功告成到这儿我们已经把个指定位图bgroud.bmp在屏幕上显示出来了这个就可以作为你游戏背景图比如潜水艇游戏那张大海图

需要介绍说明如果我们要在哪个页面上操作(般是back buffer)最好操作前先锁定用完再解锁防止其他GDI干扰举个例子:
lpDDback->Lock(NULL,&ddsd,DDLOCK-WAIT|DDLOCK_SURFACEMEMORYPTR, NULL); //锁定后台缓冲
lpDDback->Unlock(NULL);//解锁后台缓冲

把这两句代码分别添加到相应位置即可

   再往下我们该做什么了?背景有了应该引进我们精灵了(精灵这个术语真是可爱)然后想想看如何能让我们精灵动起来老实说到这我快崩溃了下面内容应该属于陌生部分吧(如果前面内容我还有点熟悉话)好在我这个人是很执着所以只有继续硬着头皮往下写了理解不到位地方还请大家包涵同时希望大家指教

先做做准备工作去吃饭先休息会再来…………

……又是n个小时……

有饭吃日子真爽啊珍惜吧朋友们:)深吸口气let’s go on

运用DirectDraw来做动画我们把区别图片载入到离屏页面中然后定时贴入到back buffer中翻页显示就出现动画效果了来看个例程:

(待续)

Tags:  directdrawerror directdraw不可用 directdraw加速 directdraw

延伸阅读

最新评论

发表评论