游戏开发技术总结(经典之作)第十一集 VC 中DirectX 技术的使用和"传奇"的单机版游戏制作

本集源代码下载地址---->游戏开发技术总结(经典之作)第十一集

11-1 任务

          认识DirectX,快速建立DirectX环境。重点理解页面和学习页面内容的拷贝方法。

11-2 认识DirectX

       在 Windows 之前,个人电脑上的大部分游戏都是基于MS-DOS的,游戏开发人员可

以直接对计算机的硬件如显卡、声卡、键盘、游戏手柄等进行编程。

      进入 Windows 后,Windows的设备管理程序对所有硬件都统一管理起来,这对品种

繁多又日新月异发展的硬件设备来说,这个统一的管理无疑是必需的,但同时带来的

问题就是游戏开发人员再不能直接对这些硬件设备编程了。例如在Windows下,我们要

在屏幕上显示一幅图形,用标准GDI函数就得经过无数的环节才能做到(现在计算机

的速度发展太快了,用标准GDI函数仿佛又不是什么问题了)。微软开发DirectX的首要

目的就是要提高Windows环境下游戏开发的容易性。

        利用DirectX, 游戏开发人员就可以获得DirectX的硬件独立性的优点,同时又可以

直接访问硬件。DirectX的最主要的目标就是提供MS-DOS中直接的硬件访问特性,同时

去掉个人电脑中再添加新的硬件时所带来的硬件识别问题。

因为硬件的升级是不可避免的, 所以,DirectX实际上也提供了另一种即插即用的

方法。因此,利用DirectX可以开发出高性能实时的应用程序,可以直接访问计算机中

的硬件和将来系统中新的硬件设备。DirectX在硬件和应用之间提供了一致的接口以减

少安装和配置的复杂性,并且使硬件的利用达到最优。利用DirectX提供的接口,程序

员能充分利用硬件的特性而不需要考虑其具体细节。

 

DirectX主要包括以下几个部分。

 

DirectDraw通过支持访问屏外显示内存中位图的软硬件加速技术,快速直接存取,

                   利用硬件的位块传输和缓冲区翻转功能。

DirectSound提供软硬件声音混合和录音再生功能。

 

DirectMusic提供软硬件MID 音乐的播放功能。

 

DirectPlay使得游戏在调制解调器和网络之间的连接更加简单方便。

 

Direct3D 允许程序完成一个完全的三维图形系统和完全控制着色管道。

 

DirectInput提供了基于Windows游戏的输入的API 程序,包括键盘、鼠标和操

                     纵杆,以及将来的基于Windows新的输入设备。

DirectSetup提供了DirectX的一次性安装过程。

我们现在编制的是2D( 平面)游戏,主要是和DirectDraw打交道。

11-3 建立DirectX 环境

        DirectX的版本发展很快,现在已到了9.0版;但是它的基本功能变化不大,这也是

微软在升级它的版本时一直没有抛弃它的向下兼容的原则,这恐怕也是人们能够放心

地学习微软技术的原因吧。

         不少书上对建立DirectX环境讲述不少,但初学者能搞懂的不多。真有这么复杂吗?

在职业技术教育中有一个知识体系的最小够用原则( 就像中国汉字有几万,认识千多

字就可识字习文一样)。现在我们来建立最小的DirectX环境。根据需要我们在“game.cpp

类文件中加入功能函数InitDDraw(void),作用是初始化DirectDraw环境。

11-3-1 InitDDraw(void) 初始化DirectDraw 环境

       在 InitDDraw(void)中我们的工作是初始化DirectDraw环境,并创建5DirectDraw工作页面。

主显页面 DXSPrimary

背景地图页面 DXSBack0

地图移动页面 DXSBack00

主缓存页面 DXSBack1

对象缓存页面 DXSgonh

BOOL game::InitDDraw(void)//功能:初始化DirectDraw 环境。

{//A.建立基本环境

1 ddrval=DirectDrawCreate( NULL, &DXW, NULL );

2 if(ddrval!= DD_OK ) return FALSE; //创建DirectDraw对象失败返回

3 ddrval=DXW→SetCooperativeLevel(GetActiveWindow(),DDSCL_NORMAL);

4 if(ddrval!= DD_OK) return FALSE; //设置窗口模式失败返回

5 ddrval=DXW→SetDisplayMode(800,600,16);

6 if(ddrval!= DD_OK) return FALSE; //设置显示模式失败返回

//B.创建主显页面

7 ddsd.dwSize = sizeof( ddsd );

8 ddsd.dwFlags = DDSD_CAPS;

9 ddsd.ddsCaps.dwCaps =DDSCAPS_PRIMARYSURFACE;

10 ddsd.dwBackBufferCount=1;

11 ddrval=DXW→CreateSurface( &ddsd,&DXSPrimary,NULL);

12 if( ddrval != DD_OK ) return FALSE; //创建主显页面失败返回

//C1.创建背景地图页面

13 Surface(WIDTH,HEIGHT); //创建背景地图页面,640X480

14 ddrval=DXW→CreateSurface(&ddsd,&DXSBack0,NULL);

15 if (ddrval!=DD_OK) return FALSE;

//C2.创建地图移动页面

Surface(WIDTH,HEIGHT); //创建地图移动页面,640X480

ddrval=DXW→CreateSurface(&ddsd,&DXSBack00,NULL);

if (ddrval!=DD_OK) return FALSE;

//C3.创建主缓存页面

Surface(SCRWI0,SCRHE0); //创建主缓存页面,800X600

ddrval=DXW→CreateSurface(&ddsd,&DXSBack1,NULL);

if (ddrval!=DD_OK) return FALSE;

//C4.创建对象缓存页面

Surface(210,200); //创建对象缓存页面, 大小按图片最大尺

寸。

ddrval=DXW→CreateSurface(&ddsd,&DXSgonh,NULL);

if (ddrval!=DD_OK) return FALSE;

//D.定义透明关键色

16 col.dwColorSpaceLowValue=RGB(255,255,255); //透明关键色

17 col.dwColorSpaceHighValue=RGB(255,255,255);//透明关键色

18 DXSgonh→SetColorKey(DDCKEY_SRCBLT,&col); //关键色

return TRUE;

}


11-3-2 InitDDraw(void) 注释

InitDDraw()主要分为三部分。

A.建立基本环境

12行创建DirectDraw对象,失败返回。

34行设置窗口模式,失败返回。

56行设置显示模式,失败返回;这里我们设为16位色的800X600的模式。

B.创建主显页面(主表面)

712行我们在这里创建一个最简单的主显页面DXSPrimary,所谓的主显页面

(主表面)就是我们具体的屏幕显示区。

C1C4.创建各个缓存页面。

13-15行是根据我们游戏的需要,调用Surface(int w,int h)函数创建一个指定高

宽的背景地图页面DXSBack0,用来存放那个用来拼接大地图的地面块。以下C2C4

是根据不同要求分别创建其它页面。

为了移动大地图,我们还要创建一个地图移动页面DXSBack00

和前面 GDI 模式一样,我们的游戏场景是先在一个暂存设备场景中生成,完成后

再拷贝到当前显示区。这样我们还要创建一个主缓存页面DXSBack1,也是在上面生成

好一幅场景后再翻转到主显页面上DXSPrimary。另外我们每次调入的图形还得有一个存

放点,那就再来一个对象缓存页面DXSgonh

D.关键色

1618行为了在 DirectDraw中得到透明显示效果,我们在这里定义对象缓存页

DXSgonh的关键色。

11-3-3 定义指定高宽的缓存页面Surface(… )函数

void game::Surface(int w,int h)// 定义缓存页面的大小

1{ ZeroMemory(&ddsd,sizeof(ddsd));

2 ddsd.dwSize = sizeof(ddsd);

3 ddsd.dwFlags = DDSD_CAPS|DDSD_HEIGHT |DDSD_WIDTH;

4 ddsd.dwWidth = w; //

5 ddsd.dwHeight= h; //

6//ddsd.ddsCaps.dwCaps=DDSCAPS_OFFSCREENPLAIN|DDSCAPS_VIDEOMEMORY;

//定义显示内存为缓存区

7ddsd.ddsCaps.dwCaps=DDSCAPS_OFFSCREENPLAIN|DDSCAPS_SYSTEMMEMORY;

//定义系统内存为缓存区

}


45行定义页面宽、高。

6行定义缓存页面在显示内存中。

7行定义缓存页面在系统内存中。(二者只能选一,这里是第7行有效)

11-3-4 gema.h 中与DirectX 有关的类变量

//以下是DirectX变量、函数定义

public: //公有,外部可调用

LPDIRECTDRAWSURFACE DXSBack1; //主缓存页面1

LPDIRECTDRAWSURFACE DXSBack0; //背景地图缓存页面

LPDIRECTDRAWSURFACE DXSBack00; //地图移动缓存页面

RECT rect; //定义矩形

void FreeDDraw(void ); //释放所有的DirectDraw 对象

void Bk1ToPr(HDC scrhdc,CRect lpRect);//后页1 到前页

private: //私有,类内部使用

DDSURFACEDESC ddsd; //页面描述

HRESULT ddrval;

LPDIRECTDRAW DXW; // DXDraw 对象

DDCOLORKEY col; //透明关键色

LPDIRECTDRAWSURFACE DXSPrimary;// DXDraw 主页面

LPDIRECTDRAWSURFACE DXSgonh; //对象缓存页面

BOOL InitDDraw(void); //初始化DirectDraw 环境

void Surface(int w,int h); //定义缓存页面的大小


11-3-5 还需要什么

在“常数定义.h”的头文件上加入DirectDraw的说明和标明DirectDraw的连接库。

#include <ddraw.h>//DirectDraw 的引用

#pragma comment(lib,"ddraw.lib")//调用DirectDraw连接库


我们在 game.cpp init()初始化函数中, 调用InitDDraw()即可完成DirectX的初始化

工作。

if(!InitDDraw()) returnFALSE;//初始化DirectDraw环境。

完了?就这么简单?不是还要在VC中配置DirectX的环境吗?不需要,就这样一

个可以编译DirectX的最小配置就完成了,并且这是不依赖任何DirectX版本的。至于

DirectX 的其它复杂配置,在我们熟悉了DirectX 的基本应用后,需要用到它的其它功能

时,再给大家介绍。

现在主显页面和各缓存页面都建立了 ,剩下的工作就是如何按要求将图形在这些

页面上翻转拷贝了。

11-4 页面内容的拷贝

11-4-1 将GDI 设备图形装入DX 缓存页面

设备场景 MemDC 中宽w、高h的图形装入缓存页面DXSgonhx,y)处:

HDC hdc;

if ( DXSgonh->GetDC(&hdc) != DD_OK)return FALSE;//

BitBlt(hdc,x,y,w,h, MemDC,0,0,SRCCOPY);

DXSgonh->ReleaseDC(hdc);


反之也可以将DX缓存页面图形装入GDI设备。

11-4-2 DX 缓存页面间的拷贝

DXSgonh 中矩形rect 范围内的图形拷贝到DXSBack1x,y)处:

rect.left=0,rect.top=0,rect.right=w,rect.bottom=h;

if(DXSBack1->BltFast(x,y,DXSgonh,&rect,dwTrans)!=DD_OK)return;

 

 

其中:

dwTrans =DDBLTFAST_WAIT为直接拷贝。

dwTrans=DDBLTFAST_WAIT|DDBLTFAST_SRCCOLORKEY为透明拷贝

这种在 DX 页面的拷贝速度是非常快的。我们在2D图形中引入DirectX技术,所寻

求的主要是这个拷贝速度的提升。

另外,着重说明的一点,就是缓存页面拷贝到主显页面。

rect.left=0,rect.top=0,rect.right=w,rect.bottom=h;

if(DXSPrimary->BltFast(x,y,DXSBack1,&rect, dwTrans)!=DD_OK)return;


         这是我们将处理后的图形向屏幕输送显示的过程,在DX中有一个重要的技术叫页

面的翻转,缓存页面图形翻转到主显页面几乎是不需要额外时间的。但这必须在

DirectDraw初始化时,定义主页面为非窗口式的独占模式,这种模式留在以后再给大家介

绍。

11-5 在我们的游戏中使用DirectX

       由于前面我们引入DirectX时做了两个简化:一是在MFC中引入DirectX,二是保持

窗口模式。现在我们用DirectDraw的技术对游戏做一点小小的改动,标志游戏重要的指

标显示速度马上就有很大的提升。

11-5-1 将对象图形调入对象缓存页面DXSgonh

这是对getpic(CStringcc,int p,int a)调压缩图形函数的修改,修改部分见加黑处。

保留 a=1 时的原调用功能,该功能在生成小地图时需要。

//**************************************************

// BOOL getpic(CString cc,int p,int a) 调压缩图形

// A. 调cc 指定的压缩图形包中第p 个图形和图形的尺寸w、h

// B. a=0 时取得的图形在对象缓存页面DXSgonh 中。

// C. a=1 时取得的图形在MemDC 中。

//**************************************************

BOOL game::getpic(CString cc,int p,int a)//调图片

{ int len;

if(p<0) return FALSE;

BYTE *tmp;

if(cc=="人")

{if(p>RBUF-1) return FALSE;

len=rbufadd[p+1]-rbufadd[p]; //取数据块长度

tmp=(BYTE *)new BYTE[len];

Memcpy0(tmp,rtmp+rbufadd[p],len);

}

if(cc=="兽")

⋯⋯

if(cc=="景")

⋯⋯

LPBITMAPINFOHEADER bm=(BITMAPINFOHEADER *)tmp; //图形数据转换

bm->biSize = sizeof(BITMAPINFOHEADER);

w=bm->biWidth; //图形高

h=bm->biHeight; //图形宽

if(a==0)

//DX-----------------------------------------------------------

{HDC hdc; //将图形装入动画缓存区DXSgonh

if ( DXSgonh->GetDC(&hdc) != DD_OK)return FALSE;//

StretchDIBits(hdc,0,0,w,h,0,0,w,h,tmp+256*sizeof(RGBQUAD)+bm->biSize,

(BITMAPINFO* )bm,DIB_RGB_COLORS,SRCCOPY);

DXSgonh->ReleaseDC(hdc);

}

//-------------------------------------------------------------

else

{StretchDIBits(MemDC,0,0,w,h,0,0,w,h,tmp+256*sizeof(RGBQUAD)+bm->biSize,

(BITMAPINFO* )bm,DIB_RGB_COLORS,SRCCOPY);

}

delete[] tmp;

return TRUE;

}


11-5-2 Alphasetobj(int q,int a)对象显示的修改

这是将各个对象显示到主缓存页面的关键部分,修改点就只有一行, 将原GDI

透明显示换成DX的__________带透明显示的页面拷贝就行了。

TransparentBlt2(BkDC1,x,y,w,h,MemDC,0,0,w,h,RGB(255,255,255));//透明显

示

MemDC→ BkDC1 换成 DXSgonh→ DXSBack1;

rect.left=0,rect.top=0,rect.right=w,rect.bottom=h;

DXSBack1->BltFast(x+SCRW,y+SCRH,DXSgonh,&rect,DDBLTFAST_WAIT

|DDBLTFAST_SRCCOLORKEY);

修改部分见加黑处。

void game::Alphasetobj(int q,int a)//对象显示

{ if(a==1&&man[q].lb==2) return;

⋯⋯

rect.left=0,rect.top=0,rect.right=w,rect.bottom=h;

if(a==0)//DX

{if(DXSBack1->BltFast(x+SCRW,y+SCRH,DXSgonh,&rect,DDBLTFAST_WAIT

|DDBLTFAST_SRCCOLORKEY)!=DD_OK) return; //在缓存区透明显示

return;

}

else Alpha(x,y);

mans++;

}

⋯⋯

}


 

11-5-3 Alpha(int x,int y) DX 半透明显示函数

       DX 的半透明显示函数构成稍微复杂一点,图形的通道合成本身算是一个比较复杂

的算法;这里我们还是使用WindowsGDIAlphaBlend)半透明处理函数,在这

里构成DX中的半透明处理效果。我们先再看一下在GDI中的处理方法。

BitBlt(BK,0,0,w,h,BkDC1,x,y,SRCCOPY);

A将要处理成半透明的对象区保存在BK中。

TransparentBlt2(BkDC1,x,y,w,h,MemDC,0,0,w,h,RGB(255,255,255));

B再将活动对象在BkDC1中透明显示一次。

AlphaBlend(BkDC1,x,y,w,h,BK,0,0,w,h,rBlend);

C半透明处理,将BK半透明拷贝到BkDC1上。

void game::Alpha(int x,int y)//DX 半透明显示

{ HDC hdc;

1 if ( DXSBack1→GetDC(&hdc) != DD_OK)return;

2 BitBlt(hDC0,0,0,w,h,hdc,x+SCRW,y+SCRH,SRCCOPY);

3 BitBlt(hDC1,0,0,w,h,hdc,x+SCRW,y+SCRH,SRCCOPY);

4 DXSBack1→ReleaseDC(hdc);

5 if ( DXSgonh→GetDC(&hdc) != DD_OK)return;

6TransparentBlt2(hDC1,0,0,w,h,hdc,0,0,w,h,RGB(255,255,255));//透明显示

7 AlphaBlend(hDC1,0,0,w,h,hDC0,0,0,w,h,rBlend); //半透明处理

8 BitBlt(hdc,0,0,w,h,hDC1,0,0,SRCCOPY);

9 DXSgonh→ReleaseDC(hdc);

10 if(DXSBack1→BltFast(x+SCRW,y+SCRH,DXSgonh,&rect,DDBLTFAST_WAIT

|DDBLTFAST_SRCCOLORKEY)!=DD_OK) return; //在主缓存页面上透明显

示

}


DX 半透明显示注释

这里新增加了两个设备场景用于DX半透明处理,hDC0hDC1

A.将要处理成半透明的对象区保存在hDC0中。

1行获取主缓存页面的设备场景hdc

2行将要处理成半透明的对象区保存在hDC0中。

3行在hDC1 中再保存一次。

4行释放hdc

B.再将活动对象在hDC1中透明显示一次。

5行获取对象缓存页面的设备场景hdc

6行再将hdchDC1透明显示。

C.半透明处理,将hDC0半透明拷贝到hDC1上。

7行将hDC0 半透明拷贝到hDC1上。

8行透明显示结果hDC1 存回到hdc,实质上是拷贝到对象缓存页面DXSgonh

了。

9行释放hdc

10行对象缓存页面DXSgonh中的半透明图拷贝到主缓存页面DXSBack1上。

DX 半透明处理看起来好像是有些复杂,但需要半透明处理的只是当前显示区中的

活动对象,它的数量少、图形也比较小。所以增加DX半透明处理后,对整个系统的开

销没有多大的影响。

11-5-4 Bk1ToPr(⋯)主缓存页面到主页面函数

       Bk1ToPr)是在“再快一点Dlg.cpp”的OnTimer(… ) 时钟函数中替换用 Bk1刷新

窗口。这是游戏显示品质因素提升的关键之处。

BitBlt(dc.m_hDC,2,10,WIDTH,HEIGHT,m_game.BkDC1,0,0,SRCCOPY);//用Bk1 刷新窗口

换成;

m_game.Bk1ToPr(dc.m_hDC,lpRect);//主缓存页面到主页面

void game::Bk1ToPr(HDC scrhdc,CRect lpRect)// 主缓存页面到主页面

{rect.left=SCRW,rect.top=SCRH,rect.right=WIDTH+SCRW,rect.bottom=HEIGHT+SCRH;

if( DXSPrimary→BltFast(lpRect.left+5,lpRect.top+13,

DXSBack1,&rect,DDBLTFAST_WAIT)!=DD_OK) return ;

}


 

11-5-5 地图块移动拼接的DX 方法

        对照原来GDI的地图块移动拼接函数,和在前面讲的DX缓存页面间的拷贝,这个

修改后的函数是容易理解的(这里再放上GDI设备场景和DX页面名称对照表)。

游戏开发技术总结(经典之作)第十一集 VC 中DirectX 技术的使用和"传奇"的单机版游戏制作_第1张图片

//**************************************************

// mlmap() 地图块移动拼接

// 这里使用的是单地图无缝拼接移动算法。

//**************************************************

void gamemap::mlmap()//地图块移动拼接DX

{if(movemap!=0) //地图有移动。

{int gx=scrx%WIDTH,gy=scry%HEIGHT;

//地图横向移动--------------------------------------------------------

rect.left=gx, rect.right=WIDTH, rect.top=0,rect.bottom=HEIGHT;

DXSBack00→BltFast(0,0,DXSBack0,&rect,DDBLTFAST_WAIT);

//--------------------------------------------------------------------

rect.left=0, rect.right=gx, rect.top=0,rect.bottom=HEIGHT;

DXSBack00→BltFast(WIDTH-gx,0,DXSBack0,&rect,DDBLTFAST_WAIT);

//地图纵向移动--------------------------------------------------------

rect.left=0, rect.right=WIDTH, rect.top=gy,rect.bottom=HEIGHT;

DXSBack1→BltFast(SCRW,SCRH,DXSBack00,&rect,DDBLTFAST_WAIT);

//--------------------------------------------------------------------

rect.left=0, rect.right=WIDTH, rect.top=0,rect.bottom=gy;

DXSBack1→BltFast(SCRW,SCRH+HEIGHT-gy,DXSBack00,&rect,DDBLTFAST_WAIT);

}

sort(); //按Y 坐标排序,用于在显示时分出前后位置

movemap=0; //地图移动否?

}


11-5-6 调地面块到背景地图页面DXSBack0

       在生成小地图的getsmap()中,我们调入的用于拼接大地图的地面块,不要忘记拷

贝到背景地图页面DXSBack0中。

void gamemap::getsmap()//生成小地图

{//调地面块到BkDC0 地图设备场景

char name[256];

sprintf(name,"%s%s",dir,mapbak);

loadbmp(name);

SelectObject(MemDC,bitmap);

//DX 把位图装入背景地图页面///////////////////////////

HDC hdc;

if ( DXSBack0→GetDC(&hdc) != DD_OK) return;

BitBlt(hdc,0,0,WIDTH,HEIGHT,MemDC,0,0,SRCCOPY);

DXSBack0→ReleaseDC(hdc);

DeleteObject(bitmap); //删除上次的位图内存。

⋯⋯

}


好了,这几处修改完成后,我们的游戏又可以编译运行了。这样游戏在运行中有了

几个明显的变化。

一是,游戏运行时自动切换到800X60016位色模式下;二是,显示速度明显加

快。在作者P3-800/128M内存的机器上,每屏显示时间从“快一点”的130ms降至本章

47ms(这个时间是相对的,它与当前屏上的对象数有关)。

详细内容请看本章实例程序:“再快一点”。

11-6 小结

在这一章里,我们学了以下知识和方法。

1.认识DirectX

2.建立DirectX环境和创建工作页面的方法

3.各种页面内容的拷贝方法。

4GDIDirectX图形处理方法的简单比较。

你可能感兴趣的:(游戏,bitmap,游戏开发,游戏引擎,游戏编程)