Silverlight 2.5D RPG游戲技巧與特效處理:(十一)AI系統
談到人工智能(AI),這個話題就太大了;大學里有《人工智能教程》專門講這方面的知識,什么大名鼎鼎的人工神經網絡、遺傳算法等等均可一窺究竟,這里如贅述似乎有些班門弄斧,我們暫且丟它一邊去吧。
本節,我的主要目的是與大家共同探討AI在RPG游戲中的應用。看過之前教程的朋友一定不會陌生,A*算法就是其中的一個重要組成部分;而本系列Demo中則使用了更為高級的改進型A*算法,不僅優化了性能,同時也大幅提升了玩家的操控體驗。除此之外,AI更常見于RPG游戲中的角色,接下來我將引領大家循著AI的足跡,逐步探尋Silverlight網頁游戲中的AI系統設計與Demo實現。
以一份網上見到的AI系統設計文檔作為開始,我們不妨首先分析下它的主體思路,大可斷定其為直觀的解析當前國內即時類MMORPG主流角色AI設計:以古老而傳統的有限狀態機FSM(Finite State Machine)固定決策樹AI為原型,簡單而直接。當然,為了讓游戲體驗更加有趣且不至于顯得一成不變,我會考慮引入基于Random的各類概率機制,即使用隨機性和靈活性更強一些的模糊狀態機FuSM(Fuzzy State Machine)。所有非控角色的行為完全無規律可循難道不會更吸引人嗎?
有了大體思路后,接下來我們要做的就是將復雜而龐大的AI系統簡化,使之更好理解并最終轉化成Silverlight游戲程序代碼。縱橫剖析,我更青睞于按照策略AI (tactic)與行為AI (action)兩部分予以劃分。所謂“策略AI”即角色何時采取何種決策,而“行為AI”則為角色按決策執行何種行動。
基于以上方案,我首先在角色控件的內部添加一個計時器用于AI邏輯循環,其中策略AI間隔3秒執行一次,用于敵對角色的搜捕(索敵);行為AI與角色動作切幀率同步,根據實時情判斷應該執行警戒/追擊/攻擊/轉向/逃逸/或其他等行動;而這兩種AI在角色控件中分別對應兩個事件,對于主角來說,玩家的操作已相當于主角的策略,因此主角只需注冊行為AI事件,而其他非控角色則需要同時兼具兩者以具備完整的AI能力:
/// 動作(幀)切換時觸發
/// </summary>
public event EventHandler ActionTrigger;
/// <summary>
/// 以1秒為基礎單位的時間間隔觸發器
/// </summary>
public event EventHandler<IntervalTriggerArgs> IntervalTrigger;
/// <summary>
/// 間隔觸發器參數
/// </summary>
public class IntervalTriggerArgs : EventArgs {
public int Count { get; set; }
}
int tickCount = 0;
IntervalTriggerArgs intervalTriggerArgs = new IntervalTriggerArgs();
void intervalTrigger_Tick(object sender, EventArgs e) {
intervalTriggerArgs.Count = tickCount;
if (IntervalTrigger != null) { IntervalTrigger(this, intervalTriggerArgs); }
tickCount = tickCount == Int32.MaxValue ? 0 : tickCount + 1;
}
接下來又是一個難題,我們該如何編寫這兩類AI?困惑時不妨借鑒一下前輩們的經驗,嘗試回憶一下小時候玩過的那些RPG游戲:有的游戲喜歡以復雜程度來定義怪物的AI,打個比方:
簡單AI:角色搜索目標 -> 攻擊 -> 頻死時回血、逃跑
中級AI:尋找最近目標 -> 攻擊 -> 半血逃逸,呼叫同伴協助
高級AI:檢索周圍狀況 -> 尋找最弱目標 -> 呼叫同伴,集體攻擊
以上僅僅是一個粗略的AI流程圖,然而卻生動而有趣的比擬了目前網游大同小異的AI設計規律。或許其中的一些環節仍可被細分成若干枝節,同時有的還會根據游戲本身功能不斷的進行拓展;萬變不離其宗,性能與趣味性兼具的AI設計總是倍受玩家們迷戀。
又如《博德之門》、《冰風谷》等龍與地下城類型經典游戲,其中的角色在創建時會根據善惡與種族的選擇決定性格,而性格往往就用于確定角色在游戲中的策略AI與行為AI。
到此,角色AI邏輯編寫的切入點已清晰顯露。綜合以上所有,相信您的思維也一樣會變得豁然開朗。接下來在本系列教程Demo的基礎上進行代碼編寫便是非常輕松的事:
/// 策略AI
/// </summary>
public enum TacticAIs {
/// <summary>
/// 目標主角
/// </summary>
GoalLeader = 0,
/// <summary>
/// 自由戰斗
/// </summary>
FreeFighting = 1,
/// <summary>
/// 小組作戰
/// </summary>
TeamAttack = 2,
/// <summary>
/// 門派對陣
/// </summary>
MartialBattle = 3
}
/// <summary>
/// 行為AI
/// </summary>
public enum ActionAIs {
/// <summary>
/// 簡單
/// </summary>
Simple = 0,
/// <summary>
/// 機敏
/// </summary>
Alertness = 1,
/// <summary>
/// 固執
/// </summary>
Persistent = 2,
/// <summary>
/// 怯懦
/// </summary>
Cowardice = 3,
}
/// <summary>
/// 角色策略判定
/// </summary>
void role_TacticDecide(object sender, IntervalTriggerArgs e) {
//每間隔3秒執行一次策略檢索
if (e.Count % 3 == 0) {
RoleBase role = sender as RoleBase;
//異步判斷角色是否處于屏幕顯示區域中,是的話且無目標則進行索敵判斷:在視線范圍內則追擊,否則警戒
this.Dispatcher.BeginInvoke(() => {
space.SetSpriteVisible(role, role.InSight(leader));
if (role.Target == null && role.IsVisible) {
bool targetFound = false;
switch (role.TacticAI) {
case TacticAIs.GoalLeader:
if (role.Target == null && role.IsVisible) {
if (role.IsHostileTo(leader) && leader.InCircle(role.Position, role.SightRange)) {
role.Target = leader;
targetFound = true;
}
}
break;
case TacticAIs.FreeFighting:
int count = 0;
int roleNum = space.AllRoles().Count;
while (count < roleNum) {
RoleBase target = space.AllRoles()[ObjectBase.RandomSeed.Next(roleNum)];
if (role.IsHostileTo(target) && target.InCircle(role.Position, role.SightRange)) {
role.Target = target;
targetFound = true;
break;
}
count++;
}
break;
case TacticAIs.TeamAttack:
for (int i = space.AllRoles().Count - 1; i >= 0; i--) {
RoleBase target = space.AllRoles()[i];
if (role.IsHostileTo(target) && target.InCircle(role.Position, role.SightRange)) {
role.Target = target;
targetFound = true;
break;
}
}
break;
case TacticAIs.MartialBattle:
count = 0;
roleNum = space.AllRoles().Count;
while (count < roleNum) {
RoleBase target = space.AllRoles()[ObjectBase.RandomSeed.Next(roleNum)];
if (role.IsHostileTo(target) && role.Profession != target.Profession && target.InCircle(role.Position, role.SightRange)) {
role.Target = target;
targetFound = true;
break;
}
count++;
}
break;
}
if (!targetFound) { role.Guard(role.Position); }
}
});
}
}
/// <summary>
/// 角色行為執行
/// </summary>
void role_ActionDecide(object sender, EventArgs e) {
RoleBase attacker = sender as RoleBase;
RoleBase target = attacker.Target;
if (attacker.IsHostileTo(target) && attacker.IsVisible) {
//怯懦行為簡單表現為不主動追擊,發現被攻擊時立刻逃跑
if (target.ActionAI == ActionAIs.Cowardice) {
target.Target = null;
target.MoveTo(new Point(ObjectBase.RandomSeed.Next((int)(target.Position.X - target.SightRange), (int)(target.Position.X + target.SightRange)), ObjectBase.RandomSeed.Next((int)(target.Position.Y - target.SightRange), (int)(target.Position.Y + target.SightRange))));
}
if (target.InCircle(attacker.Position, attacker.AttackRange)) {
attacker.TurnTowardsTo(target.Position);
attacker.Attack();
//機智行為簡單表現為受到攻擊時會反擊,受到兩個以上敵人同時攻擊時將會試圖逃離現場
if (target != leader) {
if (target.ActionAI == ActionAIs.Alertness) {
if (target.Target != null) {
target.Target = null;
target.MoveTo(new Point(ObjectBase.RandomSeed.Next((int)(target.Position.X - attacker.SightRange), (int)(target.Position.X + attacker.SightRange)), ObjectBase.RandomSeed.Next((int)(target.Position.Y - attacker.SightRange), (int)(target.Position.Y + attacker.SightRange))));
} else {
target.Target = attacker;
}
}
}
} else if (target.InCircle(attacker.Position, attacker.SightRange) || attacker.ActionAI == ActionAIs.Persistent) {
//固執行為簡單表現為抓住一個目標一直追擊,直至自己或目標死亡為止
attacker.MoveTo(new Point(ObjectBase.RandomSeed.Next((int)(target.Position.X - attacker.AttackRange), (int)(target.Position.X + attacker.AttackRange)), ObjectBase.RandomSeed.Next((int)(target.Position.Y - attacker.AttackRange), (int)(target.Position.Y + attacker.AttackRange))));
} else {
attacker.Target = null;
attacker.Stop();
}
}
}
隨手寫下數十行代碼,非常非常簡單,外行人都能看得出不外乎if/else/switch/case/random,然而卻實現了4種策略 + 4種行為,外帶上角色職業與參數的不同將產生至少20多種不同的AI組合搭配,更重要的是非常易于新功能的拓展與維護。沒錯,Silverlight開發游戲就是這樣簡單~,讓我們一同來領略下Silverlight為網游開發所帶來的革命性奇跡吧(在線演示地址http://silverfuture.cn/)!
當然,這些所有的效果也僅僅算作是AI的入門;類似仿真技術的角色AI最早出現于歐美網游大作中,比如基于仇恨系統的角色索敵AI,經典代表作便是大家游耳熟能詳的《無盡的任務》(EQ)。這類游戲的AI系統設計通常以“仇恨值”最高的敵對陣營角色確定為攻擊對象,所有角色的仇恨值都儲存于一個動態表/庫中隨時檢索。仇恨值的程度又與很多因素相關聯,比如距離遠近、等級差距、所剩生命值多少、是否在施展“嘲諷”等挑釁技能、當前行為狀態(冥想/步行/跑動)、是否正在屠殺同族、時間流逝等等,且每個要素的權重值也不同,國內網游通常都喜歡將距離遠近定為最高權重。當AI啟動時會對同一區域內所有對象進行以上數據的統和,仇恨值得分最高的角色將被視為最有價值攻擊對象,且仇恨值往往會被持續累積,直到角色死亡或施展特殊技能比如假死后方回到原點。當然,仇恨值的累積也并非都是簡單的1+1,對同一怪物連續施展3次火球術將會徹底將其惹毛,沒錯,你太小看它了,它真的很生氣;是的,游戲技術一直以來都在模仿真實的人類世界,試想下如果你被某人不小心踩上一腳相比被其連續踩上三腳我相信這樣的憤怒不僅僅是簡單的3倍加成吧?當你把生活中的類似環境置位到游戲中的角色AI時,你會覺得其實游戲設計原來是件多么有趣的事情。
追求高仿真虛擬現實的大型游戲甚至愿意花費超過20%的CPU以處理逼真的角色AI邏輯,其中往往包含著復雜的人工神經網絡和遺傳算法;當然對于更強調操控與實戰效果的網游來說,AI不需要太復雜,但是性效比該如何取舍也必定是一個值得仔細斟酌考量的環節。Silverlight 5即將發布了~,帶著超強大的功能與無限喜悅,我由衷期待!!
本節源碼請到目錄中下載。
在線演示地址:http://silverfuture.cn