iniwf

风是温柔的,雨是伤心的,云是快乐的,月是多情的,爱是迷失的,恋是醉人的,情是难忘的,天是长久的,地是永恒的

DirectDraw编程基础

http://dev.gameres.com/Program/Visual/2D/DDrawBase1.htm

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中提供了很方便的组件DirectDraw,DirectSound和DirectMusic,DirectInput。相应的我们建立MyDirectDraw.cpp,MyDirectAudio.cpp,MyDirectInput.cpp来控制各部分组件的相应功能。
显然,这3部分都是为GameMain.cpp服务的,被GameMain.cpp调用。那么我们可以看出我们的程序应该包括的文件及其包含关系为:

(图表显示不出来了,555)

  程序文件怎么去组织,应该由这个表可以看出来。这么一看,我们发现,WinMain.cpp好像是一个投资者,提供开发平台,他只关注整个项目总的进程,不关注细节。GameMain.cpp好像个项目负责人,整个项目的细节过程由他来策划,来控制,向上与WinMain.cpp交互,来完成项目,向下协调MyDirectDraw.cpp,MyDirectAudio.cpp,MyDirectInput.cpp之间的工作。MyDirectDraw.cpp,MyDirectAudio.cpp,MyDirectInput.cpp这三个家伙就是员工了,负责各自的工作,完成相应的功能给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)中至少要有两部分:初始化和结束。先看初始化,所谓初始化无非是个准备工作,需要的东西定义创建出来摆在手边以备后用。来看看初始化函数intMyDirectDrawInit(void)该怎么写。首先定义一个指向DirectDraw对象的指针,创建DirectDraw对象,查询以获取最新的DirectDraw接口,设置协作等级,设置显示模式。通过这些步骤可以创建一个黑色的屏幕了,也就是说已经开辟了我们需要的空间了,当然DirectDraw程序的初始化不会这么简单。要操作2d图形,我们还要接着创建主页面和缓冲页面以及离屏页面,总之根据需要,凡是需要在操作前需要准备好的东西都可以放在这里。那么结束 int MyDirectDrawShut(void)就应该释放我们开辟的东西,一般要释放主页面指针,和DirectDraw接口等。

大体就是这么个样子,go on,该细一点了,呵呵

先定义指针:LPDIRECTDRAW lpDDraw_temp;代表整个显示系统
创建对象: if (FAILED(DirectDrawCreate(NULL, &lpDDraw_temp, NULL)))
{
    MessageBox(NULL,TEXT("Direct Draw Create error!"),
    TEXT("Wrong!"),MB_OK);
    return(0);
}

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

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

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


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

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

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


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

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


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

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

到此为止,我想已经创建出来我们需要的空间了,以后,随着我们要求的提高,再逐步完善初始化函数,now看看结束函数:
释放接口: if (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种颜色。通过宏

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

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

#define _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到主页面或后台缓冲上。那么,我们来看一下这几个页面在工作当中的位置及作用:

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

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

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


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


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

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

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


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

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

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

……n小时后……

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

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

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

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

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

HDC hdc,hdc1; //声明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
if(result!=DD_OK)
    MessageBox(“取得暂存区DC失败”);//是否取得成功,了解result做这个用
::Bitblt(hdc1,0,0,ddsd.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函数调用成功,并不意味着换页已经完成,在实际的换页操作进行之前,对即将成为主页面的后台缓存是不能锁定和进行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中,翻页显示就出现动画效果了。来看个例程:

(待续) From:活着为了游戏 — GameRes Blog

posted on 2009-06-01 23:43 iniwf 阅读(914) 评论(0)  编辑 收藏 引用 所属分类: 图形图像多媒体


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   博问   Chat2DB   管理


导航

统计

常用链接

留言簿(2)

随笔分类

随笔档案

收藏夹

IT技术

积分与排名

最新评论

阅读排行榜

评论排行榜