如何建立一个完整的游戏AI
http://blog.friskit.me/2012/04/how-to-build-a-perfect-game-ai/
人工智能(Artificial Intelligence)在游戲中使用已經(jīng)很多年了,并且到現(xiàn)在越來越完善。如果你不在你的游戲中加入完善的游戲智能,那么別人就認(rèn)為你的游戲缺少可玩性。
在游戲中,AI并不一定要包括神經(jīng)網(wǎng)絡(luò),學(xué)習(xí)系統(tǒng)和復(fù)雜的數(shù)學(xué)結(jié)構(gòu),游戲AI只是游戲中一個(gè)重要部分,它是活動(dòng)的,并不是科學(xué)性質(zhì)的。
我認(rèn)為如何建立一個(gè)游戲AI,最主要的就是要明白你想在游戲中實(shí)現(xiàn)什么效果,就是你想讓玩家看見什么;如果游戲中什么也沒有發(fā)生,那你的游戲AI什么都沒有做。
在本篇中,我們將討論一個(gè)實(shí)時(shí)策略游戲(RTS)中游戲AI,不管怎樣這些理論都可以很好地移植到其它的系統(tǒng)當(dāng)中,所有的代碼全部用c語言寫成。
狀態(tài)機(jī)
有限狀態(tài)機(jī)
有限狀態(tài)機(jī)(FSM)是一個(gè)只有有限的幾個(gè)狀態(tài)的系統(tǒng)。在一個(gè)實(shí)際的例子當(dāng)中,狀態(tài)的觸發(fā)是通過一個(gè)擁有開或關(guān)狀態(tài)的開關(guān),或通過一個(gè)鬧鐘來調(diào)用時(shí)間決定的。通過有限狀態(tài)機(jī),我們可以在游戲中定義一些事件,然后由玩家在游戲中游動(dòng)時(shí),通過觸發(fā)來實(shí)現(xiàn)某些事件。
有限狀態(tài)機(jī)是游戲中最常用的游戲AI。下面,我們將講解如何在游戲中使用有限狀態(tài)機(jī)。
使用有限狀態(tài)機(jī)
在游戲中,我們最常用的就是用有限狀態(tài)機(jī)來模擬人的某些智能行為。比如,當(dāng)一個(gè)NPC受到攻擊時(shí),它應(yīng)該怎么辦,當(dāng)發(fā)現(xiàn)敵人時(shí),它應(yīng)該實(shí)行哪些行為,當(dāng)敵多我寡時(shí),應(yīng)該實(shí)行哪些行為,等等。都可以用有限狀態(tài)機(jī)來解決。
你可以在游戲中,將你的游戲AI設(shè)計(jì)的完美無疵,它可以萬全模擬一個(gè)人的思維,能夠自我思考,自我學(xué)習(xí),但你要記住一點(diǎn),在游戲中,我們應(yīng)盡量將AI簡單化,不要太科學(xué)化。
并不要以為,我們在這里反對神經(jīng)網(wǎng)絡(luò),遺傳算法等等,你要在游戲中使用哪種算法,完全由你自己根據(jù)你在游戲中要實(shí)現(xiàn)一個(gè)什么效果,完成一個(gè)什么目標(biāo)來決定。相反,我們在這里最主要就是講解如何在游戲中使用有限狀態(tài)機(jī)。
游戲狀態(tài)機(jī)
要在游戲中實(shí)現(xiàn)一些動(dòng)作行為,你必須考慮很多方面,比如你就不能從你自身的角度來考慮問題,你就得從玩家的角度來考慮,玩家他到底要在游戲中干什么?要想達(dá)到玩家的想法,你就不得不在游戲中反復(fù)測試,以便于達(dá)到玩家想要的效果。
在一個(gè)實(shí)際的游戲當(dāng)中,一般存在兩種狀態(tài)機(jī):
第一種狀態(tài)機(jī)主要就是完成游戲界面的轉(zhuǎn)換,比如,玩家在游戲中暫停,應(yīng)該顯示什么界面,游戲中哪些UI應(yīng)該讓玩家可見,哪些又應(yīng)該隱藏。
第二個(gè)狀態(tài)機(jī)主要就是改變當(dāng)前運(yùn)行時(shí)的環(huán)境,比如當(dāng)前玩家處于哪個(gè)游戲地圖,關(guān)卡中NPC的出現(xiàn),玩家任務(wù)完成的狀態(tài):成功或失敗。還有就是我們可能在游戲中要引導(dǎo)玩家的一些參數(shù)或變量。
你可能需要一個(gè)描述NPC的系統(tǒng),這個(gè)系統(tǒng)主要就是用于判斷如果玩家點(diǎn)擊NPC或者向NPC開火,NPC應(yīng)該怎么辦,我們完全可以用下面的結(jié)構(gòu)來表示:
struct GameLevelState{
int alert; //NPC的當(dāng)前狀態(tài)
struct Positionshot; //玩家向NPC開火的位置
int shotTime; //子彈發(fā)射出去后的游戲循環(huán)
int hostage; //誰需要幫助
int explosives; //子彈是否爆炸
int tank; //自身是否受到破壞
int dialogue; //對話參數(shù)
int compete; //任務(wù)是否完成
};
|
擴(kuò)展性
保證你的AI擁有良好的擴(kuò)展性是重要的。在編程的時(shí)候,你要能夠使你的AI最重要的部分能夠很容易的擴(kuò)展。因?yàn)槟阋靼子螒駻I的設(shè)計(jì)是一個(gè)迭代的過程,你需要不斷地去測試它,完善它。
群體動(dòng)作
在游戲中,是由一個(gè)玩家來控制群體,什么是群體動(dòng)作呢?比如,在中,玩家選中一群士兵,然后然它們朝一個(gè)目的地前進(jìn),如果沒有良好的群體控制,那么這些士兵可能就會越走越散。這樣就完全不符合玩家的思想。
解剖101
下面,我們來詳細(xì)地看一下一個(gè)角色的數(shù)據(jù)結(jié)構(gòu):
struct Character{
struct Positionpos; //玩家所在的地圖位置
int screenX,screenY; //玩家所在的屏幕位置
int animDir, animaction, animNum; //動(dòng)畫信息,角色動(dòng)作和動(dòng)畫幀數(shù)
int rank; //軍銜
int health; //生命值
int num; //在當(dāng)前群體中有多少人
int group; //部隊(duì)編號
int style; //部隊(duì)的性質(zhì)(步兵,騎兵等)
struct AnimationObject animObj; //復(fù)雜動(dòng)畫的動(dòng)畫對象
};
|
現(xiàn)在,我們來詳細(xì)講解每一個(gè)參數(shù):
pos參數(shù)用于決定部隊(duì)在屏幕和游戲世界中的位置,這樣是為了便于我們在屏幕的相應(yīng)位置播放精靈動(dòng)畫,顯示精靈信息等等。
AnimDir,animaction,animnum是用于決定我們當(dāng)前應(yīng)該在屏幕上顯示哪段精靈動(dòng)畫。
Rank ,health 就不用說了。
Num 參數(shù)用于決定該角色在部隊(duì)中的ID號。
Group參數(shù)用于決定該角色所在部隊(duì)在所有部隊(duì)中的編號。
Style,animobj參數(shù)用于決定部隊(duì)的圖像在屏幕上的顯示。
建立過去基本
一旦你利用前面的方法建立了一個(gè)初始化部隊(duì)的函數(shù),那么現(xiàn)在就需要你對這些部隊(duì)賦予生命。
這時(shí)候,你就需要思考你想讓你的部隊(duì)擁有什么樣的動(dòng)作,有什么樣的反應(yīng)。你需要思考,你的部隊(duì)是否感情用事?是否像一個(gè)瘋子?是否需要被凍結(jié)?
要做到這些,你就必須首先思考當(dāng)人遇到某某事情時(shí),他應(yīng)該怎樣做,然后再把你的思考變成代碼,觀看其效果,不對就再修改。
群組
群組或者非群組
如果你是制作第一人稱射擊游戲,你就不用考慮群組的問題了,因?yàn)榫椭挥心阋粋€(gè)。但當(dāng)你想建立一個(gè)RTS類形的游戲,你想同時(shí)控制很多人的動(dòng)作,那你就不得不考慮下面的問題:
我是否需要我的部隊(duì)按照一種比較協(xié)調(diào)的方式行進(jìn)?
如果回答是“是”,那么恭喜你,群組適合你。
群組的優(yōu)點(diǎn)
1. 部隊(duì)能夠按照一個(gè)主要的移動(dòng)信息列表前進(jìn)。這樣做的好處就是群組中的任何一個(gè)單元當(dāng)受到其他信息,比如目標(biāo)的改變等,其它成員還是可以按照先定的移動(dòng)信息前進(jìn)。
2. 多個(gè)成員自我調(diào)節(jié)行為。 比如,我們控制一個(gè)部隊(duì)包圍一個(gè)建筑物,部隊(duì)中的成員能夠相互協(xié)作完成對建筑物的包圍。
3. 群組能夠自動(dòng)保持他們的結(jié)構(gòu),這樣,當(dāng)一個(gè)部隊(duì)遇到一些不明情況時(shí),他能夠自適應(yīng)的告訴部隊(duì)中的每個(gè)成員該干什么。比如,行進(jìn)當(dāng)中要注意保持對形,先頭部隊(duì)首先遭遇敵人,應(yīng)奮起阻擊。
4. 依賴于群組的構(gòu)成,你可以直接控制群組來控制群組中的其他成員,這樣就簡化了找路徑等問題的麻煩程度。
大圖片
如果你想在游戲AI中判斷你所有部隊(duì)的動(dòng)作,你就不得不把這些部隊(duì)的信息全部群組起來。一個(gè)好注意就是建立一個(gè)地方,用于共享所有部隊(duì)的信息。
根據(jù)我的經(jīng)練,這個(gè)部分應(yīng)該分兩步完成:第一部分就是將群組中的每個(gè)成員應(yīng)該做什么事存儲在每個(gè)成員自己的數(shù)據(jù)結(jié)構(gòu)中,而其他需要組織和共享的信息,比如移動(dòng),行動(dòng)等動(dòng)作,應(yīng)存儲在群組數(shù)據(jù)結(jié)構(gòu)中。
這意味著部隊(duì)不擁有移動(dòng)的信息,他們總是在群組的權(quán)利范圍之下做出決策。如果硬要說每個(gè)部隊(duì)都是獨(dú)立的,那他們也只是在群組下獨(dú)立。
許多中的一個(gè)
當(dāng)我們?yōu)榱诉m應(yīng)系統(tǒng)的變化時(shí),尋找一個(gè)群組去行動(dòng),那系統(tǒng)將建立一個(gè)單獨(dú)的部分,并且最重要的是保持每個(gè)部隊(duì)的行動(dòng)信息。這樣做的目的是便于我們打亂我們的結(jié)構(gòu)然后完成一些特殊的任務(wù)。這樣的數(shù)據(jù)結(jié)構(gòu)如下:
struct GroupUnit{
int unitNum; //角色I(xiàn)D
struct Unit *unit; //部隊(duì)角色數(shù)據(jù)
struct Positionwaypoint[50]; //所有部隊(duì)的路徑
int action[50]; //部隊(duì)在路徑上應(yīng)該的動(dòng)作
int stepX,stepY; //各別部隊(duì)的步驟
int run,walk,sneak,fire,hurt,sprint,crawl; //行動(dòng)
int target; //所有部隊(duì)的目標(biāo)
struct Position targetPos; //目標(biāo)位置
};
|
下面,我們來具體描述這些參數(shù):
unitNum是部隊(duì)在群組中的ID。如果群組容納的最大部隊(duì)數(shù)是10,那么最小的部隊(duì)編號是0,最大的是9。
Unit 是一個(gè)指向部隊(duì)角色數(shù)據(jù)的指針,它存儲了包括角色當(dāng)前的位置,生命值和其它相關(guān)信息。
Waypoint 包含了一個(gè)部隊(duì)可以去的所有地方。所有行動(dòng)和位置信息是包含在GroupUnit結(jié)構(gòu)中,如果群組不是在一個(gè)結(jié)構(gòu)中,那么部隊(duì)中的所有成員將按照它們自己的方式移動(dòng)。
Action數(shù)組包含了移動(dòng)到目標(biāo)的所有動(dòng)作。這允許你建立一個(gè)詳細(xì)的動(dòng)作鏈,以便于玩家控制部隊(duì)在森林里潛行,然后匍匐著靠近目標(biāo)。
StepX ,stepY用于存儲簡單的速度信息;因?yàn)槊總€(gè)兵種,它們的移動(dòng)速度都是不一樣的,總不可能步兵的移動(dòng)速度比火箭飛行兵快吧。
Target ,targetpos參數(shù)用于存儲當(dāng)我們選中了一個(gè)敵人部隊(duì)時(shí),敵人部隊(duì)的編號和位置。關(guān)于敵人的其它信息,我們可以通過每次的游戲循環(huán)來找出。
群體心理
我們還需要一個(gè)中央集權(quán)的群組來管理我們所有的群組,下面,我們來看一下一個(gè)簡單的結(jié)構(gòu)。
struct Group{
int numUnits; //群組中的部隊(duì)數(shù)
struct GroupUnit unit[4]; //部隊(duì)信息
int formation; //部隊(duì)和群組的結(jié)構(gòu)信息
struct Position destPos; //目標(biāo)
int destPX,destPY; //目標(biāo)屏幕坐標(biāo)
struct Postion wayX[50]; //群組的所有可去的目標(biāo)點(diǎn)
float formstepX,formstepY; //群組移動(dòng)的速度
int formed; //如果這個(gè)值為真,那么部隊(duì)獨(dú)立行動(dòng),否則按照群組的規(guī)則移動(dòng)
int action,plan; //群組行動(dòng)和計(jì)劃
int run,walk ,sneak,sprint,crawl,sniper; //具體動(dòng)作
struct Position spotPos; //攻擊地點(diǎn)
int strategyMode; //群組的戰(zhàn)越模式
int orders[5]; //群組的順序
int goals[5]; //群組的目標(biāo)
int leader; //群組的領(lǐng)導(dǎo)者
struct SentryInfo sentry; //哨兵列表
struct AIState aiState; //Ai狀態(tài)
};
|
numUnits是指在群組中有多少部隊(duì),部隊(duì)數(shù)組存儲在GroupUnit結(jié)構(gòu)中,在本例中,我們存儲的最大數(shù)量是4.
Formation標(biāo)志決定了群組是采用了什么隊(duì)形,它們可以根據(jù)自身的狀況分別采用柱,楔和三角等隊(duì)形。
DestPos,destPX,destPY描述了群組在趕往目的地中途的什么地方。Waypoints,steps以相同的方式工作,它們都是描述了部隊(duì)行進(jìn)的速度。在這里,并不需要部隊(duì)中每個(gè)成員來設(shè)置自己的速度,這個(gè)事完全由群組決定。
Formed 是最為重要的參數(shù)之一,它決定了部隊(duì)是否按照對形行進(jìn)或單獨(dú)行進(jìn)。如果那個(gè)群組被設(shè)置成按對形行進(jìn),那么群組中所有的部隊(duì)就必須按照群組設(shè)置的對形行進(jìn),但是當(dāng)遇到某些特殊的原因,比如遭受攻擊或?qū)π卧獾搅似茐模敲床筷?duì)就按照他們自己的思路前進(jìn)。
Actions參數(shù)就是表示部隊(duì)在到達(dá)目的地中間的動(dòng)作,比如跑,走,潛行等等,完全由玩家決定。
Strategymode 決定了當(dāng)部隊(duì)遇到敵人后應(yīng)該怎么辦,部隊(duì)是反抗還是逃跑?
Orders,goals數(shù)組是為了便于群組之間的消息傳輸。
Sentry , aiState存儲了哨兵信息和更為詳細(xì)的AI信息,這些信息是為了更詳細(xì)的模型匹配。
把它放在一起
到現(xiàn)在,關(guān)于我們的群組,我們已經(jīng)有了一些結(jié)構(gòu)。然后我們應(yīng)該做什么呢?下一步就是找出我們在游戲中如何用編程來實(shí)現(xiàn)這些信息。
值得注意一點(diǎn)的就是,盡量將你的AI程序模塊化,這樣做的好處就是便于以后更好地?cái)U(kuò)充它們。詳細(xì)一點(diǎn)就是說,你在編寫函數(shù)時(shí),盡量做到一個(gè)函數(shù)只做一件事情。
關(guān)鍵部分
在你準(zhǔn)備寫你的游戲之前,請不斷的學(xué)習(xí)一些基本的東西,如果你的游戲需要一些功能函數(shù),那么就為這些功能編寫相應(yīng)的函數(shù)。
不要學(xué)習(xí)某些高手用一個(gè)函數(shù)完成所有的功能,可能一個(gè)函數(shù)對他很管用,但對你就不那么行的通。
你要不斷的創(chuàng)新,不斷的挑戰(zhàn)傳統(tǒng),不要看一些看似經(jīng)典的程序你需要自己創(chuàng)造。如果你寫的東西,它能正常工作,那么用它。在游戲編程中有一句經(jīng)典的話是這樣說的:“如果你覺得它是對的,它就是對的”。
不要被其它的思想絆住了腳,因?yàn)槲覀兿嘈拍闶亲顝?qiáng)的。
其實(shí),AI的意思就是如何讓決策變得更聰明,在我們這篇文章中,就是要讓玩家感覺游戲中的人物像真的一樣。
在一個(gè)RTS(實(shí)時(shí)策略游戲)游戲中,我們所謂的動(dòng)作包括移動(dòng),巡邏,避開障礙物,打擊敵人和追趕它們。讓我們來看一下每個(gè)動(dòng)作的詳細(xì)內(nèi)容。
移動(dòng)
移動(dòng),最簡單的一種形式就是,在某一段時(shí)間之內(nèi)從一個(gè)點(diǎn)到達(dá)另一個(gè)點(diǎn)。這個(gè)很容易實(shí)現(xiàn),你能通過找一個(gè)距離向量,然后乘以這個(gè)部隊(duì)移動(dòng)的速度和所用的時(shí)間,就可以得到移動(dòng)的位置。
因?yàn)槲覀兊挠螒蚴峭ㄟ^鼠標(biāo)來驅(qū)動(dòng)的,所以,我們并不能期望用戶通過游戲桿之內(nèi)的東西來驅(qū)動(dòng)游戲人物前進(jìn)時(shí)繞開障礙物,首先,你要明白一點(diǎn),我們這里說的并不是第一人稱射擊類游戲。
我們應(yīng)該怎樣做呢?比如,玩家在一棵大樹旁點(diǎn)擊了鼠標(biāo),我們首先必須建立一個(gè)動(dòng)作隊(duì)列保存這個(gè)角色行動(dòng)的路徑,但是當(dāng)角色到達(dá)大樹時(shí),他不可能穿墻而過吧。這個(gè)時(shí)候,我們就必須為角色添加一個(gè)動(dòng)作,以便于角色繞過大樹。
巡邏
巡邏是一個(gè)特別的移動(dòng),因?yàn)樗艘幌盗蓄A(yù)設(shè)的坐標(biāo)。在移動(dòng)中,一個(gè)部隊(duì)它走到一個(gè)目標(biāo)以后,它就不會走了,但巡邏不一樣,當(dāng)部隊(duì)完成一個(gè)目標(biāo)以后,他就會從自身的巡邏列表中抽出下一個(gè)坐標(biāo),然后又朝下一個(gè)坐標(biāo)前進(jìn)。
在我們這個(gè)實(shí)例當(dāng)中,盡量不要讓部隊(duì)一直站立或等待,因?yàn)槲覀冞@個(gè)游戲是RTS游戲,如果一直站立或等待就不能突出游戲的戰(zhàn)爭氣氛。
避開障礙物
避開障礙物的算法首先要看你的地圖是如何工作的,還有就是你的部隊(duì)在移動(dòng)時(shí)應(yīng)該讓他們?nèi)绾蜗嗷ソ换ァT诒纠校覀兪褂玫氖且粋€(gè)擁有很多小的建筑物和物體的室外環(huán)境。在這個(gè)游戲中,你不能進(jìn)入建筑物,這些建筑物都是用一個(gè)凸出的擁有4個(gè)頂點(diǎn)的多邊形表示。
在下面這個(gè)例子當(dāng)中,部隊(duì)和目標(biāo)并不是成直線時(shí)。首先,我們先移動(dòng)到和目標(biāo)最近的一個(gè)坐標(biāo),這個(gè)坐標(biāo)盡量和目標(biāo)呈直角。然后再從這個(gè)點(diǎn)移動(dòng)到目標(biāo)。這樣做的好處就是留下了很大的緩沖空間讓我們繞過障礙物。
說白了,障礙物避開算法是由你將遇到的障礙物決定的。如果你想了解一些更好的路徑尋找算法,我建議你看一下A*算法。A*算法是一個(gè)非常流行的最短路徑尋找算法。
打擊敵人
和其它部隊(duì)作戰(zhàn)通常依賴于你想你的玩家在游戲中能干什么。你可能想你的玩家的部隊(duì)在見到敵人時(shí)能夠自動(dòng)開火,以便于玩家能夠?qū)⒆⒁饬Ψ旁谡麄€(gè)戰(zhàn)場上。你可能還想你的部隊(duì)能夠監(jiān)視周圍的環(huán)境,如果出現(xiàn)敵人就馬上開火。
比如,一個(gè)角色站在一個(gè)坐標(biāo)上,可以將它的四周分為8個(gè)部分,這8個(gè)部分分別代表了角色的8個(gè)視域,當(dāng)然你也可以設(shè)置這些視域的距離,也就是角色所能看到的距離。然后就循環(huán)監(jiān)測這些視域中是否有敵人。當(dāng)然,也有一種特殊情況,比如,敵人和障礙物同時(shí)出現(xiàn)在你的視域中,障礙物在前,敵人在后,你總不可能也看見敵人了吧!關(guān)于這個(gè)問題,我將在另一篇文章中講解。
追趕
當(dāng)一個(gè)敵人發(fā)現(xiàn)了一個(gè)獵物,也就是它自身的視域中出現(xiàn)了一個(gè)角色,這時(shí)候,它就要判斷,這個(gè)角色是自己人嗎?如果是,就不干什么。如果是獵物,就要檢測它是否在視域內(nèi),如果是,就開槍射擊。如果不是在視域內(nèi)呢?那么就需要追蹤它。
那將如何實(shí)現(xiàn)呢?首先,你需要保存獵物最后的一個(gè)坐標(biāo),然后將這個(gè)坐標(biāo)設(shè)置成你的第一個(gè)移動(dòng)目標(biāo),到達(dá)目標(biāo)后,你就需要檢測獵物是否還在視域范圍內(nèi),如果在,就開槍射擊。如果不在,就按照獵物的方向隨機(jī)移動(dòng)一個(gè)距離,然后繼續(xù)檢測。
在本篇的例子當(dāng)中,我們假設(shè)角色能夠在第一次移動(dòng)過后找到獵物。但如果他移動(dòng)后沒有發(fā)現(xiàn)獵物,那么我們就將他的狀態(tài)設(shè)置成巡邏狀態(tài),然后朝我們第一次得到的獵物的方向移動(dòng)一個(gè)隨機(jī)的距離。
總結(jié)
當(dāng)你看懂了這篇文章,并且在程序中將它實(shí)現(xiàn)以后,你就會發(fā)現(xiàn),其實(shí)游戲AI也沒那么神秘,不是挺簡單的嗎?
總結(jié)
以上是生活随笔為你收集整理的如何建立一个完整的游戏AI的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 交通银行重庆火锅主题卡额度多少?额度太低
- 下一篇: 招聘要求