收藏 分享(赏)

vc++游戏编程指南.doc

上传人:精品资料 文档编号:10791985 上传时间:2020-01-09 格式:DOC 页数:42 大小:276.50KB
下载 相关 举报
vc++游戏编程指南.doc_第1页
第1页 / 共42页
vc++游戏编程指南.doc_第2页
第2页 / 共42页
vc++游戏编程指南.doc_第3页
第3页 / 共42页
vc++游戏编程指南.doc_第4页
第4页 / 共42页
vc++游戏编程指南.doc_第5页
第5页 / 共42页
点击查看更多>>
资源描述

1、第八章 支撑游戏的基石 1第七章 我没有想好名字如果你只靠上面几章所讲述的知识编了个游戏,喜欢的人恐怕会不多,为什么?因为没有人会玩一个控制不流畅而且声音效果不佳的游戏。为了在游戏中更好地管理各种输入设备,我们需要使用 DirectInput。而通过使用 DirectX Audio 可以在游戏中实现各种更逼真的音乐效果。它们都是 DirectX 的重要组成部分。使用 DirectInput 前我们需要#include 并在工程中加入 dinput8.lib 和 dxguid.lib,而使用 DirectX Audio 前我们需要#include 并在工程中加入 dsound.lib 和 dxg

2、uid.lib。7.1 读取键盘数据首先,我们必须创建一个 DirectInput8 对象(DirectX 9.0 并没有对 DInput 和 DAudio 做多大改动) ,就像这样:LPDIRECTINPUT8 pInput; DirectInput8Create(GetModuleHandle(NULL), DIRECTINPUT_VERSION,IID_IDirectInput8,(void*) 然后,我们需要创建一个 DirectInput 设备:LPDIRECTINPUTDEVICE8 pDev;pInput-CreateDevice(GUID_SysKeyboard, 设置好它的数

3、据格式:pDev-SetDataFormat( 设置它的协作级,这里设为独占设备+前台:pDev-SetCooperativeLevel(hwnd,DISCL_EXCLUSIVE|DISCL_FOREGROUND);获取设备:pDev-Acquire();像上面那样初始化后,我们就已经把 Windows 对键盘的控制权剥夺了,以后的键盘消息将不会被送入消息循环,我们可以把消息循环中处理键盘消息的语句拿掉了。当然,这时我们需要在程序的适当地方,比如说在刷新游戏时,加入对键盘数据进行读取和处理的语句,就像下面的一段程序:第八章 支撑游戏的基石 2#define KEYDOWN(key) (buff

4、erkey /键盘数据pDev-GetDeviceState(sizeof(buffer),(LPVOID) /得到键盘数据if (KEYDOWN(DIK_XXX) /如果 XXX 键被按下(请参阅附录二) /处理之 /处理其它键哈哈,真是挺方便的。有时候真的有点怀疑 DirectX 是不是一种回到遥远的可爱的 DOS 时代的“倒退” 。因为无论是 DirectInput 还是 DirectDraw 都是太像 DOS 下的做法了。7.2 读取鼠标数据读取鼠标数据和读取键盘数据的步骤差不多,首先也是要创建设备:pInput-CreateDevice(GUID_SysMouse, 设置数据格式:p

5、Dev-SetDataFormat(设置协作级:pDev-SetCooperativeLevel(hwnd,DISCL_EXCLUSIVE | DISCL_FOREGROUND);获取设备:pDev-Acquire();那么怎样读取鼠标数据呢?如果要取得鼠标的当前状态,这样即可:DIMOUSESTATE mouse_stat; /鼠标状态/得到鼠标状态pDev-GetDeviceState(sizeof(DIMOUSESTATE),(LPVOID)得到的 mouse_stat 是一个 DIMOUSESTATE 类型的结构,它有四个成员:lX,lY,lZ 和rgbButtons4。其中 lX、l

6、Y 和 lZ 分别是自上次调用此函数以来鼠标在 X 轴、Y 轴和 Z 轴(滚轮)方向上移动的距离,而不是鼠标此时的坐标;其距离单位不是像素,但你完全可以把它看做以像素为单位。所以,我们需要定义两个变量 mousex=0 和 mousey=0,然后把 lX 和 lY 累加上去即可。这样做的好处是鼠标坐标不再受屏幕的制约,而且屏幕中心的 mousex 和 mousey 值可以永远是 0,不随屏幕分辨率而改变。rgbButtons 是一个存储哪些鼠标键被按下的数组,我们可以这样做来读取它:/定义一个宏,方便处理鼠标数据第八章 支撑游戏的基石 3#define MOUSEBUTTONDOWN(b) (

7、mouse_stat.rgbButtonsbSAFE_RELEASE(pDev);SAFE_RELEASE(pInput);7.4 初始化和关闭 DirectX Audio7.4.1 初始化 DirectX Audio使用 DirectX Audio 前,按规矩还是要先初始化。在下面的这段初始化程序中要用到三个DXAudio 提供的对象:IDirectMusicLoader8、IDirectMusicPerformance8 和IDirectMusicSegment8。IDirectMusicLoader8 顾名思义是用来调入音乐的,IDirectMusicPerformance8 可以认为是

8、音频设备,而 IDirectMusicSegment8 就是代表音乐。#include IDirectMusicLoader8* pLoader= NULL;IDirectMusicPerformance8* pPerf = NULL;IDirectMusicSegment8* pSeg = NULL;CoInitialize(NULL); /初始化 COM第八章 支撑游戏的基石 4CoCreateInstance(CLSID_DirectMusicLoader, NULL, CLSCTX_INPROC, IID_IDirectMusicLoader8,(void*) /创建 pLoader

9、对象CoCreateInstance(CLSID_DirectMusicPerformance, NULL,CLSCTX_INPROC, IID_IDirectMusicPerformance8,(void*) /创建 pPerf 对象pPerf-InitAudio( NULL, /这里可以是一个指向 IDirectMusic*对象的指针NULL, /这里可以是一个指向 IDirectSound*对象的指针hwnd, /窗口句柄DMUS_APATH_SHARED_STEREOPLUSREVERB, /AudioPath 类型/这里打开了立体声及混响,效果很不错64, /音乐通道数DMUS_AU

10、DIOF_ALL, /使用声卡的所有特性NULL /可以指向一个 DMUS_AUDIOPARAMS 对象,更详细地说明各种参数);7.4.2 关闭 DirectX Audio关闭 DXAudio 还是老思路,先按 7.5.3 节的办法停止音乐,然后 Release 即可:pPerf-CloseDown(); /关闭SAFE_RELEASE(pLoader); /释放对象SAFE_RELEASE(pPerf);SAFE_RELEASE(pSeg);CoUninitialize(); /停止使用 COM7.5 播放 MIDI 和 WAV 音乐MIDI 音乐和 WAV 音乐在游戏编程中经常用到。其中

11、前者一般是用作背景音乐,而后者多是用在各种音效方面,如发射导弹等等。虽然我们可以用 3.4.4 节的方法,但使用 DXAudio 可以更充分地利用硬件资源,从而实现更少的 CPU 占用率。方法如下:第八章 支撑游戏的基石 57.5.1 调入 MIDI 和 WAV 文件在播放音乐之前,第一步当然是调入音乐文件:CHAR strSoundPathMAX_PATH; /存储音乐所在路径GetCurrentDirectory(MAX_PATH, strSoundPath); /得到程序所在路径strcat(strSoundPath, “Sounds“); /这里设置音乐在程序路径下的 Sounds 子

12、目录WCHAR wstrSoundPathMAX_PATH; /存储 UNICODE 形式的路径/将路径转为 UNICODE 形式MultiByteToWideChar(CP_ACP, 0,strSoundPath, -1, wstrSoundPath, MAX_PATH);pLoader-SetSearchDirectory(GUID_DirectMusicAllTypes, /搜索所有支持的格式wstrSoundPath,FALSE);WCHAR wstrFileNameMAX_PATH; /存储 UNICODE 形式的文件名/将文件名转为 UNICODE 形式MultiByteToWid

13、eChar(CP_ACP, 0, “a.mid“, -1, wstrFileName, MAX_PATH);pLoader-LoadObjectFromFile(CLSID_DirectMusicSegment, /文件类型IID_IDirectMusicSegment8, /目标对象类型wstrFileName, /文件名,同样应为 UNICODE 形式(LPVOID*) 7.5.2 播放 MIDI 和 WAV 文件调入完音乐之后,在适当的时候可以开始播放音乐:pSeg-SetRepeats(音乐要重复的次数); /DMUS_SEG_REPEAT_INFINITE 为无限次pSeg-Down

14、load( pPerf ); /将音乐数据传给 pPerfpPerf-PlaySegmentEx(pSeg, /要播放的音乐NULL, /现在只能是 NULLNULL,0,第八章 支撑游戏的基石 60,NULL,NULL,NULL /Audiopath,现在先不要管它是什么); 7.5.3 停止播放停止播放音乐也是非常简单的:pPerf-Stop(NULL, /停止哪个通道, NULL 代表所有通道NULL,0, /经过多少时间才停止播放0);7.6 在 3D 空间中播放音乐我们的下一个问题是如何使音乐更逼真,最好能使音乐 3D 化,这将大大加强真实性。要实现这个其实也不难,首先我们要设定所谓

15、的 AudioPath,它可以指定音乐的播放方式。IDirectMusicAudioPath8* pPath; /AudioPathpPerf-CreateStandardAudioPath( /创建 AudioPathDMUS_APATH_DYNAMIC_3D, /3D 的64, /通道数TRUE, /是否立即激活 AudioPath然后,我们要从 AudioPath 中得到一个“3D 缓存“,它将存储音乐的播放位置等信息。IDirectSound3DBuffer8* pBuffer; /3D缓存pPath-GetObjectInPath(DMUS_PCHANNEL_ALL, /搜索全部通道

16、第八章 支撑游戏的基石 7DMUS_PATH_BUFFER, /为 DirectSound 缓存0,GUID_NULL,0,IID_IDirectSound3DBuffer8, /缓存类型(LPVOID*) 下一步是设定音乐在 3D 空间的何处播放。例如:pBuffer-SetPosition( -0.1f, 0.0f, 0.0f, DS3D_IMMEDIATE );这条指令把音乐的播放位置设为(-0.1f, 0.0f, 0.0f),由于默认的听众位置在坐标(0, 0, 0)处,脸朝着 Z 轴的正方向,头朝着 Y 轴正方向,所以其效果将是听众的右耳听得到声音但左耳听不到声音,就像音乐是从自己的

17、正右方发出。如果把最后一个参数设为 DS3D_DEFERRED,此操作将被挂起直到调用IDirectSound3DListener8 : CommitDeferredSettings( )为止,这样可以一次处理多个设定防止反复计算。我们还可以设置音乐源的速度及其播放角度:pBuffer-SetVelocity(vx,vy,vz,DS3D_IMMEDIATE);/设置在 x,y,z 轴方向的速度/设置播放角度大小(度),inncone 为内角度,outcone 为外角度/音乐在内角度范围内不衰减,在内外角度之间慢慢衰减,超出外角度时消失pBuffer-SetConeAngles(inncone,

18、 outcone, DS3D_IMMEDIATE);pBuffer-SetConeOrientation(x, y, z, DS3D_IMMEDIATE); /设置朝哪个方向播放那么我们如何设定听众的位置呢?可以这样:IDirectSound3DListener8* pListener;pPath-GetObjectInPath( /创建听众0,DMUS_PATH_PRIMARY_BUFFER,0,GUID_NULL,0,IID_IDirectSound3DListener8, (LPVOID*) 第八章 支撑游戏的基石 8/设置听众面向(x1,y1,z1),头朝着(x2,y2,z2)pLis

19、tener-SetOrientation(x1,y1,z1,x2,y2,z2,DS3D_IMMEDIATE);pListener-SetPosition(x,y,z,DS3D_IMMEDIATE); /听众位置pListener-SetVelocity(vx,vy,vz,DS3D_IMMEDIATE); /听众速度7.7 播放 MP3 音乐MIDI 音乐的问题是对声卡的依赖性过大,好声卡和差声卡的播放效果实在相差太远。WAV音乐虽然绝对足够精确,但占用的空间之大不可小视。MP3 恐怕是一个较好的解决方案。值得注意的是,播放 MP3 并不需要 DirectX Audio,需要的是 DirectS

20、how。所以,我们要#include ,并在工程中加入 strmiids.lib。7.7.1 调入 MP3 文件下面把初始化 DirectShow 和调入 MP3 合起来说说吧。首先,我们要定义三个对象,其中IGraphBuilder*类型的可以认为是媒体播放设备,IMediaControl*类型的变量负责媒体的播放控制,而 IMediaPosition*类型的变量负责媒体的播放位置设定。IGraphBuilder* pGBuilder;IMediaControl* pMControl;IMediaPosition* pMPos;CoInitialize(NULL); /初始化 COM/创建各

21、个对象CoCreateInstance(CLSID_FilterGraph, NULL,CLSCTX_INPROC, IID_IGraphBuilder, (void*)pGBuilder-QueryInterface(IID_IMediaControl, (void*)pGBuilder-QueryInterface(IID_IMediaPosition, (void*)CHAR strSoundPathMAX_PATH; /存储音乐所在路径WCHAR wstrSoundPathMAX_PATH; /存储 UNICODE 形式的路径GetCurrentDirectory(MAX_PATH,

22、strSoundPath);strcat(strSoundPath, “Sounds“);strcat(strSoundPath, “a.mp3“); /假设要播放的是 Sounds 子目录下的 a.mp3MultiByteToWideChar(CP_ACP, 0, strSoundPath, -1,wstrSoundPath, MAX_PATH);pGBuilder-RenderFile(wstrSoundPath, NULL); /调入文件第八章 支撑游戏的基石 97.7.2 播放 MP3 文件播放 MP3 的方法十分简单:pMPos-put_CurrentPosition(0); /移动

23、到文件头pMControl-Run(); /播放7.7.3 停止播放和释放对象最后,我们要停止播放音乐并释放各个对象:pMControl-Stop(); /停止播放/释放对象SAFE_RELEASE(pMControl);SAFE_RELEASE(pMPos);SAFE_RELEASE(pGBuilder);CoUninitialize(); /释放 COM第八章 支撑游戏的基石 10第八章 支撑游戏的基石在这一章中,我们将看看数据结构,算法和人工智能在游戏中的应用。我想每个人都知道“人工智能”这个字眼吧,但数据结构和算法是干什么的呢?说简单点,数据结构就是在程序中各种数据的组织形式,而算法就

24、是处理这些数据的方法。Niklaus Wirth 曾说过“数据结构+算法=程序” ,可见其重要性。8.1 链表链表是一种灵活的数据结构,它可以说是把指针用到了极致。最简单的链表是由一个个像这样的节点组成的:struct node /节点int data; /节点数据node* next; /指向下一个节点的指针;一个个链表的节点就像一节节火车车厢一样通过 next 指针一个接一个地连接着,当我们在链表中查找数据时,我们也要一个接一个地往下找。可以想象,在链表的任何位置添加新节点都是十分简单的,而删除链表中的某个节点时也只要把它的父节点指向它的子节点即可。正因为链表有这些特点,它被广泛地应用于各

25、种元素的个数或是元素的排列顺序经常需要改变的场合。我们还可以使用双向链表,即再使用一个指向上一个节点的指针。这将使链表变得更加方便可以从后往前查找节点,但同时也增大了链表的大小。链表在游戏编程中有不少应用,例如组织游戏中像精灵(Sprite,指游戏中会移动的东西)这样的经常需要修改的元素。8.2 哈希表使用哈希表(Hash Table)可以大大减少查找工作的时间。举一个简单的例子,如果你要在一本字典中找某单词,那你应该怎样做呢?如果不使用哈希表,那么你似乎只能一个个找下去。当然,我们知道字典是排好序的,所以大可使用二分查找等更快的方法。但如果是职工编号等第八章 支撑游戏的基石 11完全无序的数

26、据呢?这时,我们需要一张哈希表。怎么建立哈希表呢?所谓哈希表,其实是一个很简单的东西,它可以说是为数据建立了一个索引。还是上面那个例子,我们首先应该通过某一个函数的变换,把字典里的所有单词变成一些尽量不相同的数。如果能做到完全不相同的话,这个函数就是一个完美的哈希函数。当然,这显然比较难。一个比较糟糕的哈希函数 我们给它起个名字叫 f(x) 就是按单词的头一个字母,把单词转换成 0 到 25 之间的数,就像我们平常查字典时那样。好一点的解决方案是把单词的所有字母都这样转换一下,然后再加起来,对某一个大数取模。下一步,我们建立一个大数组 HashTable,它的大小要能容纳所有哈希函数的可能取值

27、,对于 f(x),我们要开一个HashTable26。然后我们把这个数组的每一个元素都变成一个链表,把对应的单词一个接一个地放进去(其实把单词转换成数后就应该立刻把它放进数组) 。此时 HashTable0的内容就像这样:“a“Aachen“Aalborg“aardvark“现在大家看出来了吧,是的,我们只要把我们要找的单词通过 f(x)转换成一个数,然后再在 HashTablef(x)中查找即可。哈希函数取得越好,相同哈希函数的单词就越少,我们的查找就越快。然而不容忽视的是,数组很可能也变大了。所以哈希表是用空间换时间。关于哈希函数的选取,和如果有两个元素哈希函数值相同时的处理,现在都研究得

28、比较多。比如说有一种办法是:在 HashTablef(x)已被其它元素占据时,看看 HashTableg(f(x)是否是空的,如果还不行就看 HashTableg(g(f(x),直到行为止。 g(x)可以是 x+1 之类。显然,如果使用这种办法,HashTable的大小要比元素的个数多。这种办法避免了链表的使用,但增加了计算 g(x)的花费。总之,一切要根据实际情况选择。最后给一个比较好用的 Hash 函数给大家吧,它能将一个单词转为一个整数:int Hashf(const char *s)int hash, i, l;hash = 0;l = strlen(s);for(i=0; imid)

29、 q-; /数组右边的数大于等于标准数if (pbegin) QuickSort(begin,q); /继续对前半部分排序if (pZ-a-o) : 图 8.1下面就是标准的深度优先搜索程序:#include #include using namespace std;#define SX 10 /宽#define SY 10 /长int dx4=0,0,-1,1; /四种移动方向对 x 和 y 坐标的影响int dy4=-1,1,0,0;char BlockSYSX= /障碍表 0,1,0,0,0,0,0,0,0,0 , 0,1,1,0,1,1,1,0,0,0 , 0,0,0,0,0,0,0,

30、0,0,0 , 1,1,1,0,1,0,0,0,1,0 , 0,1,0,0,1,0,1,1,1,0 , 0,1,0,0,1,1,1,1,1,0 , 0,0,0,1,1,0,0,0,1,0 , 0,1,0,0,0,0,1,0,1,0 , 0,1,1,1,0,1,1,0,1,1 , 0,0,0,0,0,0,1,0,0,0 ;第八章 支撑游戏的基石 14int MaxAct=4; /移动方向总数char TableSYSX=0; /是否已到过int Level=-1; /第几步int LevelComplete=0; /这一步的搜索是否完成int AllComplete=0; /全部搜索是否完成ch

31、ar Act1000=0; /每一步的移动方向,搜索 1000 步int x=0,y=0; /现在的 x 和 y 坐标int TargetX=9,TargetY=9; /目标 x 和 y 坐标void Test( );void Back( );int ActOK( );void main( )Tableyx=1; /做已到过标记while (!AllComplete) /是否全部搜索完Level+;LevelComplete=0; /搜索下一步while (!LevelComplete)ActLevel+; /改变移动方向if (ActOK( ) /移动方向是否合理Test( ); /测试是否

32、已到目标LevelComplete=1; /该步搜索完成elseif (ActLevelMaxAct) /已搜索完所有方向Back( ); /回上一步if (LevelMaxAct) /方向错误?return 0;if (tx=SX)|(tx=SY)|(ty#include using namespace std;#define SX 10 /宽#define SY 10 /长int dx4=0,0,-1,1; /四种移动方向对 x 和 y 坐标的影响int dy4=-1,1,0,0;char BlockSYSX= /障碍表 0,1,0,0,0,0,0,0,0,0 , 0,1,1,0,1,1,

33、1,0,0,0 , 0,0,0,0,0,0,0,0,0,0 , 1,1,1,0,1,0,0,0,1,0 , 0,1,0,0,1,0,1,1,1,0 , 0,1,0,0,1,1,1,1,1,0 , 0,0,0,1,1,0,0,0,1,0 , 0,1,0,0,0,0,1,0,1,0 , 0,1,1,1,0,1,1,0,1,1 ,第八章 支撑游戏的基石 17 0,0,0,0,0,0,1,0,0,0 ;int AllComplete=0; /全部完成标志int LevelNow=1, /搜索到第几层Act, /现在的移动方向ActBefore, /现在的节点的父节点MaxAct=4, /移动方向总数A

34、ctNow, /现在的节点tx,ty; /当前坐标int LevelFoot200 = 0, /每一层最后的节点ActHead3000 = 0; /每一个节点的父节点char AllAct3000 = 0; /每一个节点的移动方向char ActX3000 = 0, ActY3000 = 0; /按节点移动后的坐标char TableSYSX = 0; /已到过标记char TargetX=9,TargetY=9; /目标点int ActOK( );int Test( );int ActOK( )tx=ActXActBefore+dxAct-1; /将到点的 x 坐标ty=ActYActBef

35、ore+dyAct-1; /将到点的 y 坐标if (tx=SX)|(tx=SY)|(ty3,那么 8 升桶内将会有 5 升水,3 升桶会被装满;然后 3-5,那么 3 升桶将被倒空,5 升桶内将有 3 升水。你的目标是平分这 8 升水,即使 5 升桶和 8升桶内均有 4 升水。8.6 启发式搜索大家听过一个叫 A*的东东吗?A*就是一种启发式搜索方法。当然,你没听过也不要紧,下面就讲讲启发式搜索。启发式搜索的核心是一个估价函数 F(x),扩展节点时先扩展 F(x)值小的节点。F(x) 又等于 G(x)+H(x),其中 G(x)为从起始状态到当前状态的代价,一般就是已经搜索了多少步;而 H(x

36、)则是当前状态到目标状态的估计代价,即估计还有多少步就可到目标。为了保证搜索到的是最优解,H(x)必须大于或等于实际代价,F(子节点)也必须大于或等于 F(父节点)。就找路的问题来说,我们可以把 H(x)定为离目标的直线距离,显然这样就可以满足上面的两个条件,保证找到的是最短路径。启发式搜索的好处是速度快而且占用空间不多。显然,这个算法的关键有两点:一,如何快速地在大量节点中找到 F(x)最小的节点;二,如何在节省空间的情况下快速判断一个节点是否已扩展过(其实这也是前面的 DFS 和 BFS 应用于寻路时需要解决的,靠一个数组 TableSYSX对空间占用过大,不过话又说会来,如果你的搜索限定

37、在小范围内,例如几个屏幕,用数组的办法更好) 。首先,我们来看看要使用的数据结构:第八章 支撑游戏的基石 20#includeusing namespace std;int node_count = 0; /目前的待扩展节点数int allnode_count = 0; /目前的节点数#define SX 10 /地图宽#define SY 10 /地图长#define MAX_NODE 100 /允许同时存在多少待扩展节点#define MAX_ALLNODE 1000 /允许节点数#define MAX_HASH 1999 /Hash 表大小,最好是质数int tx = 9, ty = 9

38、; /目标坐标int dx4 = 0,0,-1,1;/四种移动方向对 x 和 y 坐标的影响int dy4 = -1,1,0,0;char BlockSYSX = /障碍表 0,1,0,0,0,0,0,0,0,0 , 0,1,1,0,1,1,1,0,0,0 , 0,0,0,0,0,0,0,0,0,0 , 1,1,1,0,1,0,0,0,1,0 , 0,1,0,0,1,0,1,1,1,0 , 0,1,0,0,1,1,1,1,1,0 , 0,0,0,1,1,0,0,0,1,0 , 0,1,0,0,0,0,1,0,1,0 , 0,1,1,1,0,1,1,0,1,1 , 0,0,0,0,0,0,1,0

39、,0,0 ;struct NODE /待扩展节点的资料int x,y,f,level,n;nodeMAX_NODE;struct /节点的资料第八章 支撑游戏的基石 21int act,father;allnodeMAX_ALLNODE;struct /Hash 表,用来判断节点是否已访问过int x,y;HashMAX_HASH;所有待扩展节点储存在一个数组 node 中。node 的每一个元素都是一个结构 NODE int x,y,f,level,n; ,其中 x 和 y 是节点坐标,f 是节点的 F 函数值,level 是节点位于何层,n 是节点在 allnode 中的位置。node1存

40、储的永远是 F 函数值最小且未扩展的节点,这是通过AddNode( )和 GetNode( )实现的。最后还有一个 node_count,存储目前节点的实际数量。数组 node 的实际结构是类似这样的:图 8.2有点像一棵树吧,这棵树的最大特点是父节点的 F 函数值永远比子节点的小,例如 node1.fnodex/2.f(x1) 。那么我们如何将一个新节点加入才能保持这棵树的性质呢?首先,我们要把 node_count+,这时nodenode_count(按照图 8.x 就是 node10,它是 node5的子节点)空了出来。然后,我们将这个新节点和它的父节点的 F 函数值比较。如果新节点的较

41、小,就把它的父节点复制到新节点处(比如 node10=node5;) ,新节点的预备位置上移到父节点处,这样一直做下去;如果父节点的较小,子节点的位置就已可确定。代码是这样的:void AddNode(int x, int y, int act, int level, int father)if (x=SX) | (x=SY) | (y 1 )q = p 1;if( f using namespace std;const int n=6; /有多少个地方/两地之间的距离,若为很大的数,如 99999,表示两地间无道路/自己到自己的距离应定为 0int disnn = 0, 5, 8, 9999

42、9, 99999, 99999 , 5, 0, 2, 7, 99999, 99999 , 8, 2, 0, 1, 6, 99999 , 99999, 7, 1, 0, 99999, 4 , 99999, 99999, 6, 99999, 0, 3 , 99999, 99999, 99999, 4, 3, 0 ;int dn; /某地离 A 地的距离void main()d0=0; /A 地离 A 地的距离是 0for (int i=1;idk+disjk) /如果距离可改进dj=dk+disjk; /改进之coutdn-1; /输出结果其实这个程序有点大材小用,因为我们只要稍加修改即可用同样的

43、算法求出地图中任意两点间的最短距离。这个算法的主要缺点大家也许都看出来了,就是实在太慢了。只要节点数一多,三重循环就会耗去大量的时间。8.8 神经网络相信大家第一次玩 WorldCraft3 时会输给电脑吧,但是和电脑打得多了,摸透了它的战术之后就可以轻易取胜,因为人有学习的能力,电脑却不然。那么用什么办法才能使电脑的思考方式接近于人类,并拥有自我学习的能力呢?神经网络(Neural Networks)是一种目前很热门的实现方法。我们知道,人类的神经系统是由神经元构成的,神经网络也不例外,只不过在神经网络中神经元的反应被大大地简化了。一个典型的神经网络的神经元由任意多个输入端和输出端组成,其输

44、出等于 1/(1+e-s),s 为所有输入值*权重(每个输入值都有一个相应的权重,待会训练时改变的就是权重的值)之和。例如,如果一个神经元的输入分别为:x 1=0.5,x 2=0.8,x 3=0;对应的权重为:w1=0.1,w 2=0,w 3=1,那么 s=0.5*0.1+0.8*0+0*1=0.05,神经元的输出为 1/(1+e-0.05)第八章 支撑游戏的基石 28=0.512。用多个神经元可以构建起复杂的网络,就像这样:图 8.4如果我们给 x1 输入 1,x 2 输入 0,那么位于左上的神经元会输出 0.881,位于左下的神经元会输出 0.500,这两个结果和 1 输入最后一个神经元中

45、,最终结果 f 为 0.655。下面我们来看看如何训练网络。还是上面这个例子,如果我们想用这个网络实现奇偶检验(如果 x1 与 x2 均为 0 或 1,那么 f=1;否则,即如果 x1 与 x2 一个为 0 一个为 1 的话,f=0 ) ,那么我们可以这样训练此网络:计算最后一个神经元的误差 ,它等于(d-f)*f*(1-f),其中 d 为期望输出,f 为实际输出。在这个例子里,=(0-0.655)*0.655*(1-0.655)=-0.148。倒推上一层网络的每一个神经元的误差。举个例子,左上角的神经元只有一个输出端,我们首先要计算其指向的神经元的 与此路径的 w 之积(如果有多个输出端,将

46、所有输出端的这个积加起来) ,在这里是-0.148*3=-0.444。这个神经元的输出是 0.881,那么我们将-0.444*0.881*(1-0.881),得到此神经元的 为-0.047 。同样的道理,左下角神经元的 为0.074。对于每条路径,其新 w=w+x, 为此路径指向的神经元的 ,x 为此路径的输入值。这样,左上角神经元的 w 变成了 1.953,-2 和-0.047 ;左下角神经元的 w 变成了 1.074,3 和-0.926;最后一个神经元的 w 变成了 2.870,-2.074 和-1.148。如果这时我们再输入刚才的数据,会发现 f 变成了 0.563,的确比训练前的 0.

47、655 更接近期望值 0。8.9 遗传规划遗传规划(Genetic Programming)又是一个天才的构想,它从进化论中吸取灵感,通过就像第八章 支撑游戏的基石 29自然生物的“过度繁殖,生存斗争,遗传和变异“ 的方法实现算法的自我进化。还是用一个实际问题来说明 GP 的方法吧:如何使精灵自动找到附近的墙并永远绕着墙走?在这里我们假定精灵所在的世界是二维的,而且是一格格的,精灵有八个传感器:n, ne, e, se, s, sw, w, nw,当其对应的方向不能行走时传感器返回 1 否则返回 0,精灵有四条指令:north,east,south,west,代表向相应的方向移动一格。那么这个程序可以实现我们想要的功能:图 8.5这里 IF 操作符的意义是:如果最左边的分支的运算结果为 1 那么执行中间的分支否则执行最右边的分支。事实上你还可以定义其它的操作符和指令,把任何程序都写成这种格式。现在的问题是,怎样用 GP 的方法找到这样的

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 企业管理 > 管理学资料

本站链接:文库   一言   我酷   合作


客服QQ:2549714901微博号:道客多多官方知乎号:道客多多

经营许可证编号: 粤ICP备2021046453号世界地图

道客多多©版权所有2020-2025营业执照举报