编程初学者:游戏编程起源(初学者)Ⅶ



★ 第 4章 DirectX入门

☆ 介绍
啊哈!今天是个好日子知道为什么吗?今天我们要接触到令人敬畏DirectX它比Windows GDI要快好几倍可用于区别语言和多种平台支持从绘制象素到高级3D图象从播放简单声音到数字音乐从键盘控制到反震手柄……它给你游戏编程所需切(有点夸张)当然了它是巨大需要好几本书才能含盖它全部先不要去担心我在这里所教给你的外数不清知识毕竟我把你推到了起跑线上
阅读本章你需要前几章知识和C语言知识由于我们还要谈到组件对象模型(COM)它是面向对象系统基础你最好还要有点儿C++知识没有也不太要紧我在讲到这处时会照顾你反正你记住使用DirectX并不需要多少C++知识开始吧!

☆ 什么是DirectX?
DirectX是游戏制作者API(Application Development Interface)它是组允许你直接控制计算机硬件设备软件Software如果你硬件支持DirectX并且你用硬件加速你这就意味着个字——快不用担心你硬件知识你不会真正接触到它们我们是通过硬件抽象层(HAL)和硬件仿真层(HEL)来保证设备无关性和让你正常运行
DirectX由很多组件构成个都有特定用途组件DirectDraw是最为重要所有图形都要用到它它是2D图形引擎3D图形也同样离不开它DirectDraw是我们今天就要说其它组件是:

▲ DirectSound:提供硬件和软件Software声音混合和回放
▲ DirectMusic:处理基于消息音乐数据它支持乐器数字接口(MIDI)并为创建交互式音乐提供创作工具
▲ DirectPlay:使得通过调制解调器链接或通过网络来和应用相连成为可能
▲ Direct3D:是个 3维图形包它提供个高级保留模式(Retained Mode)接口这使得你能够实现个完整 3维图形系统它还包含个低级即时模式(Immediate Mode)接口使得应用获得对渲染管线完全控制
▲ DirectInput:为包括游戏杆、鼠标、键盘和游戏控制器在内输入设备提供支持它还为反馈游戏设备提供支持
▲ DirectSetup:为DirectX提供了个简单安装过程它简化了更新显示和音频驱动过程并且确保没有硬件或软件Software冲突存在
▲ AutoPlay:让你能够制作旦插入驱动器就能自动安装光盘AutoPlay并非DirectX所独有它是Microsoft Win32 API部分

组件对象模型(COM)是DirectX基础些窍门技巧建立COM对象——别问我如何做——但你知道点点还是有好处我只是简单说如果你有兴趣具体细节就自己查资料吧!可能下节你有些困惑但不要紧我所说你不用太明白毕竟我们是使用COM对象这可比创建容易多了

☆ 组件对象模型(COM)
COM接口是DirectX技术基础没有COM就没有DirectX(不用担心你只需要对COM技术有个粗浅了解就可以使用DirectX——只要你在编写DirectX应用时遵循步骤甚至都可以在不了解COM情况下使用DirectX
DirectX大多数API都是基于COM结构COM为软件Software模块化和软件Software重用提供了最坚实基础最重要概念就是接口(erface)接口是软件Software重用最基本思路方法更专业接口是系列操作规范标准描述即接口规范标准
所有COM接口都是从Iunknown接口继承而来IUnknown接口是所有COM接口IUnknown接口具有3个思路方法:

※ QueryInterface:此思路方法查询新接口并在新接口存在时返回的
※ AddRef:此思路方法在接口或其它应用连编到此COM对象上时将引用计数值递加1
※ Release():此思路方法将COM对象引用计数递减1当引用计数递减到0时该COM对象自动释放
所有COM对象都具有这 3个思路方法虽然DirectX应用般不需要考虑引用计数问题但引用计数确实是存在它已经由DirectX自动完成了我们所要做就是创建DirectX对象然后在使用完毕后Release思路方法释放引用

☆ 设置
用DirectX创建你需要有 3件主要事要做件事是COM对象本身它们包含在.DLL文件里这些.DLL文件需要在Windows里注册这在安装DirectX软件Software包时已经完成了这些对象是我们创建DirectX应用时用到接口例如IdirectDraw但这还不够在COM层上直接使用DirectX是令人沮丧和乏味我们希望有更容易办法解决它利用静态库(.LIB文件)是个好办法它是DirectX软件Software包部分你可以从Microsoft免费获得它有个“打包”使你工作更轻松使用DirectX区别组件你需要链接区别静态库例如你要使用DirectDraw组件你就需要ddraw.lib
最后你还需要DrectX头文件它包含原形、宏、常量和你需要用到各种类型对于DirectDraw这个头文件是ddraw.h
要确认你使用了正确文件版本你还得让编译器包含软件Software开发包目录具体做法是:
首先点击Tool菜单选择Options然后点击Directories在Show Directories for 组合框下拉菜单中选择Include files增加个新目录将你DirectX路径填入(例如:C:DXSDK)然后将它移到列表使编译时第个寻找它(防止寻找老版本)然后选择Show Directories for组合框下拉菜单中Library files思路方法同前只是把改成lib现在你已经设置完了DirectX你仍然需要手动增加些库文件到你项目中但先不急我将在以后讲它我们将使用DirectX 7.0

☆ DirectX版本号
你可能认为版本号没有什么好讲但我们确实要说Microsoft在DirectX里创建了令人难以置信科技但它并不代表不使人迷惑对于每个DirectX版本并不是所有接口都次次升级因此尽管DirectX有了7个版本(我写文章时DirectX8.0正准备发布)但DirectDraw并没有7个版本当DirectX6是最新版本时DirectDraw最新接口版本是IDirectDraw4不是IDirectDraw6现在最新版本是DirectX7所以我们要用IDrectDraw7很奇怪是不是?我想你已经明白了我意思请不用以后看到感到困惑了
最后件事当我写这篇文章时DirectX7是最新可用版本但或许现在你已经有了DirectX8并且或许你还听说了DirectDraw将不再升级了取代它是DirectX Graphics这是个功能强大图形API但DirectDraw不升级就意味着我们不学习它了毕竟都离不开COM如果你想用DirectX8接口写2D游戏你需要用3D思路方法去创建2D观点听起来很棒确如此使用3D接口将给你更多硬件支持例如阿尔发混合但这也恰恰是个问题如果机器没有相应硬件设备会以更慢速度运行
DirectDraw是很容易学由于DirectX中3D图形是基于DirectDraw3D应用在DirectDraw环境中执行;极少有应用专门使用3D大多数使用3D些对象建模而另些对象诸如背景和精灵是以2D图形渲染所以本系列将使用DirectDraw有关DirectX8我还没有太多了解因此我只能对DirecX7做详细介绍来说你使用DirectX还是离不开DirectDraw



☆ DirectDraw概述
在你中使用DirectDraw你至少要做 4件事它们是:
1、建立个DirectDraw对象
2、设置协作等级
3、设置显示模式和色彩深度(全屏模式)
4、至少创建个DirectDraw表面
在讲怎样完成以上步骤的前先让我们了解下每含义要创建个DirectDraw对象这意味着我们要建立个指向IDirectDraw7接口指针这很简单不是吗?有 3种办法可以实现它你可以直接使用COM或使用DirectDraw两个 3种办法各有千秋我们过会儿将详细介绍第 2个设置操作等级这可能对你来说比较新鲜协作是由于Windows是个多任务操作系统而产生概念意思是所有运行都要随时告知Windows它们将要或正在使用资源这将保证你所要使用资源不会被windows再分配给别应用不用担心个很简单完成它
第 3个你是否有点熟悉?如果你要写个全屏通常是游戏你需要设置显示模式和色彩深度在Windows应用里做这些通常不是个好主意它能导致其它同时运行出现问题当你结束自己你当然要恢复到改变前状态设置全屏模式只是个单独DirectDraw结束后要恢复原来状态
最后也是最重要是DirectDraw表面概念操纵表面是DirectDraw全部简单DirectDraw表面是个用于存储图象数据线性内存区域DirectDraw表面大小就是以象素为单位用宽和高来定义所以你可以认为表面是个用来画图矩形区域它有自己接口称作IDirectDrawSurface7有 3种主要表面我们将在本章和下章分别用到它们

※ 主表面:每个DirectDraw应用都有个主表面主表面就相当于用户显示器内容是可见同理主表面就是根据显示器显示模式设定宽和高
※ 后缓冲区:后缓冲区是紧随主表面表面但它不可见它是动画没有闪烁主因通常你在后缓冲区画好每然后把后缓冲区内容拷贝到主表面使它显示出来由于它紧随着主表面所以它大小同主表面相同(你就理解为楼上和楼下关系)
※ 离屏缓冲区:它很象后缓冲区只是它不是紧挨着主表面尽管你可以用它作任何事但它经常被用来存储位图离屏缓冲区你可以任意设置大小限制是你内存大小
DirectDraw表面可以在系统内存中建立或直接建立在显示内存中如果你都建立在显示内存中速度效果将是最好如在系统内存就要慢些了如果你把个表面存储在显示内存中个在系统内存中性能会有些损失尤其是显示卡和主板的间有个令人恶心带宽总的如果能把表面都建立在显示内存中你或许应该尽力做到

OK我们总算有了点儿认知让我们看看具体如何做吧!这儿有个计划我们将建立个全屏模式下16位色彩640×480分辨率我将告诉你全部你需要做但开始前你需要对Windows编程有点了解想必你看过了前面几章应该对创建窗口已经熟悉了由于这是个全屏你不需要任何地窗口控制所以你窗口风格应该用WS_POUP|WS_VISIBLE弄好了吗?All right出发吧!

☆ 建立DIRECTDRAW对象
象我前面说过有 3种思路方法我们可以用两个DIRECTDRAW任何或者直接COM对象让我们每个都试试使我们自己熟悉它们我将告诉你最后个思路方法可能是目前为止最简单可能你会喜欢用它至于另外两个打眼儿你会觉得有些奇怪首先看看DirectDrawCreate():

HRESULT WINAPI DirectDrawCreate(
GUID FAR *lpGUID,
LPDIRECTDRAW FAR *lplpDD,
IUnknown FAR *pUnkOuter
);

看起来是不是有点儿复杂?HRESULT返回类型是DirectDraw标准如果成功返回值是DD_OK如果失败将返回常量有几个常量供选择但我不想细讲更不想列出这些常量反正你可以通过帮助文件随时查阅它们但有件事儿我得告诉你有两个非常有用宏可以帮助你知道成功和否:SUCCEEDED和FAILED从字面上你就知道它们分工了是不是?只要把放到宏里面你就知道结果了无论如何我们还得看看参数:
GUID FAR *lpGUID:是个全局唯标识符(GUID)地址代表将要创建驱动如果该参数是NULL那么该指向当前显示驱动新版本DirectDraw允许向该参数传递下列两种标志的以控制当前显示行为:
◎ DDCREATE_EMULATIONONLY:DirectDraw只使用仿真(HEL)不使用硬件支持特性
◎ DDCREATE_HARDWAREONLY:DirectDraw对象不使用仿真特性只能使用硬件抽象层(HAL)如果硬件不能支持应用将不再寻求硬件仿真层(HEL)支持而返回信号
LPDIRECTDRAW FAR *lplpDD:表示如果成功则返回有效DirectDraw对象指针地址它是DirectDraw对象指针指针(“DD”表示DirectDraw“lp”表示32位长指针“lplp”表示长指针长指针)应用般需要使用此指针地址(即DirectDraw对象指针)化DirectDraw对象
IUnknown FAR *pUnkOuter:这是为高级COM应用保留参数设置为NULL好了
不要被我罗里罗嗦解释吓倒实际应用起来很简单解释这么多不过是为了让你明白根本道理现在有个问题这个给你个指向IDirectDraw接口指针但我们想要个指向IDirectDraw7接口指针我们应该如何做呢?旦DirectDraw应用通过DirectDrawCreate()获得了指向DirectDraw对象指针COM就有种机制可以用来查看该对象是否支持其它接口IUnknownQueryInterface思路方法使得你能够确定个对象是否支持个特定接口:

HRESULT QueryInterface(
REFIID iid, // Identier of the requested erface
void **ppvObject // Address of output variable that receives the
);

个参数是个要查询对象引用标识符对于IDirectDraw7来说就是IID_IDirectDraw7使用它你必须把dxguid.lib链接入你项目中;第 2个参数是个变量地址我们应该在头部先声明个LPDIRECTDRAW7类型指针再把指针地址传递给这个参数如果你使用是Visual C++6.0你在这儿或许还需要个类型强制符如果机器支持你指定接口将返回个指向该接口指针通过该指针代码就获得对新接口思路方法访问如果成功返回值是S_OK
现在我们有了两个接口指针:个是IDirectDraw接口个是IDirectDraw7个是我们想要;前个就没有用了我们注意在代码中每当找到个新有效对象时个对象就通过Release被释放掉这个很简单:



ULONG Release(void);

返回值是个参考数字只有在测试和调试时才用得着这个数字为了安全起见你还应该把释放了指针赋值为NULL我们也通常在声明这样指针时设置它为NULL你跟上我节奏了吗?可能要记忆东西太多了但是你不得不记忆让我们把谈到这些做个例子吧例子是得到IDirectDraw7接口指针:

LPDIRECTDRAW lpdd = NULL; // poer to IDirectDraw (temporary)
LPDIRECTDRAW7 lpdd7 = NULL; // poer to IDirectDraw7 (what we want)

// get the IDirectDraw erface poer
(FAILED(DirectDrawCreate(NULL, &lpdd, NULL)))
{
// error-handling code here
}

// query for IDirectDraw7 poer
(FAILED(lpdd->QueryInterface(IID_IDirectDraw7, (void**)&lpdd7)))
{
// error-handling code here
}

{
// success! release IDirectDraw since we don\'t need it anymore
lpdd->Release;
lpdd = NULL;
}

现在如果你是个C你可能被QueryInterface和Release这两个思路方法弄得有点模糊你以前可能看过“->”这个符号在C语言结构部分当结构声明了个指针变量结构成员时就用“结构指针名->结构成员”同样道理只是这里把结构成员换成了既然说到这个话题我就介绍下另个C++符号范围定义符号“::”它是表示从属关系符号举个例子你就明白了:比如QueryInterface是属于IUnknown类就可以表示为IUnknown::QueryInterface我们将来会经常用到这个符号所以记住它
说实在以上主要目是为了演示怎样使用QueryInterface思路方法它是所有DirectX接口部分所以让我们往下进行我们将直接使用COM思路方法获得接口指针这种思路方法好处是你可以立即获得IDirectDraw7接口指针不用象刚才那么麻烦首先你必须得化COM象这样:

HRESULT CoInitialize(LPVOID pvReserved);

不能在容易了你必须把参数设置为NULL当你结束COM你需要抛弃它也很简单:

void CoUninitialize(void);

我通常在DirecX开始就CoInitialize最末端当我释放了所有DirectX对象后使用CoUninitialize旦你化了COM你就可以用CoCreateInterface得到你想要指针它看起来有点丑陋:

STDAPI CoCreateInstance(
REFCLSID rclsid, // Class identier (CLSID) of the object
LPUNKNOWN pUnkOuter, // Poer to whether object is or isn\'t part
// of an aggregate
DWORD dwClsContext, // Context for running executable code
REFIID riid, // Reference to the identier of the erface
LPVOID *ppv // Address of output variable that receives
); // the erface poer requested in riid

如果成功返回值是S_OK参数需要好好解释看下面:
REFCLSID rclsid:这是个类标识符(不要同GUID搞混了哦)有为它准备好常量标识符供你选择对于IDirectDraw7来说使用CLSID_DirectDraw注意没有版本号它是类标识符不是接口标识符
LPUNKNOWN pUnkOuter:这个同我们在DirectDrawCreate中看到设置为NULL
DWORD dwClsContext:这个必需值叫作执行上下文它定义了控制新生成对象代码将要执行方式这个值可以从CLSCTX列表中选取对于我们现在情况我们用CLSCTX_ALL它包含了所有可能
REIID riid:我们在QueryInterface中看过它这个IID是IID_DirectDraw7
LPVOID *ppv:依然同DirectDrawCreate是指向接口指针地址
这个将取代我们上个思路方法中DirectDrawCreate、QueryInterface和Release() 3个所以简捷当然使用哪种随便你了直接COM比我们先前用思路方法少了个多于地接口指针旦你用CoCreateInstance()建立了个对象你还得Initialize化这个对象在C++里可能写成这样IDirectDraw7::Initialize以下是它原形:



HRESULT Initialize(GUID FAR *lpGUID);

将使用同DirectDrawCreateGUID就是NULL在我们继续前让我给你看个使用COM创建DirectDraw对象例子:

LPDIRECTDRAW7 lpdd7; // erface poer

// initialize COM
CoInitialize(NULL);

// create the object
CoCreateInstance(CLSID_DirectDraw, NULL, CLSCTX_ALL, IID_IDirectDraw7, (void**)&lpdd7);

// initialize the object
lpdd7->Initialize(NULL);

直接看例子可能使你更容易理解好了建立DirectDraw对象最难两种思路方法你已经学会了那就让我们看看最简单思路方法吧!^_^ 它只有没有多于接口指针不用设置COM什么都没有就是下面这个:

DirectDrawCreateEx(
GUID FAR *lpGuid,
LPVOID *lplpDD,
REFIID iid,
IUnknown FAR *pUnkOuter
);

所有参数我们看起来都比较熟悉我们刚才看过它们了第 2个和第 4个参数同DirectDrawCreate只是这里需要用(void**)来修饰下我们接口指针地址——别问我为什么这不是我主意第 3个参数riid是我们在CoCreateInstance中传递接口ID所以我们就用IID_IDirectDraw7就这样无论用哪种思路方法我们得到了我们DirectDraw对象我们可以继续使用这个对象了要做头两件事是设置协作等级和显示协议

☆ 设置协作等级和显示模式
待续

Tags:  网络游戏的起源 网络游戏起源 游戏的起源 编程初学者

延伸阅读

最新评论

发表评论