使用Unity製作遊戲AI
下面是《Radiant Blade》的演示畫面。
使用遊戲AI的原因
首先,我們要思考為什麼要給遊戲新增AI?
長期以來,我們都在幻想著為遊戲開發令人驚奇的AI,讓AI給玩家帶來印象深刻的體驗。這種AI可以預料到玩家的每一個操作,幾乎無法被打敗。但說實話,這種AI毫無對抗的樂趣。
值得玩家去玩的遊戲應該是玩家可以獲得樂趣的遊戲。因此,AI必須可以和玩家旗鼓相當。AI可以作為夥伴,讓玩家通過特別的方法進行互動。
顯然,只有樂趣的遊戲不會是一個優秀的遊戲。遊戲也必須有炫酷的機制,深刻的含義以及精美的外觀。但對AI而言,我們希望AI具有娛樂性,因此我們要進一步縮小這個概念的範圍。
遊戲設計
遊戲中的娛樂性是什麼?我們的開發團隊花了一些時間思考這個問題,結論可以總結為一個詞:學習,具備娛樂性的遊戲是玩家可以從中學習和利用知識的遊戲。
娛樂性源於小小的好奇心,在玩家看到新事物時,好奇心會佔據玩家的頭腦,並會不斷增長,直到玩家完全理解這項新事物。也就是說,具有娛樂性的AI必須是可以被玩家學習的。
這個簡單的概念形成了所有遊戲中AI的廣泛解讀,包括:《超級瑪麗》、《毀滅戰士》、《魔獸世界》和《以撒的結合》。如果分析這些遊戲的AI,我們會發現它們都是可以預測的。
由於加入了一些隨機元素,這些遊戲AI不是完全固定不變的,但仍有預測的可能。這樣又出現了另一個問題:如何製作出可預測的遊戲AI?
答案很簡單:使用狀態機。
狀態機
狀態機是包含狀態和過渡的數學工具。
基本的狀態機
在確定性狀態機中,我們會處於一個特定狀態,在移動時,我們會隨著其中一個可用過渡轉變到新狀態。過渡可能會受到條件限制,例如:只有在擁有特定法術時,AI才可以到達指定狀態。
狀態機的優點是:它們具有表現力和可預測性。假設狀態包括“攻擊”、“受擊”、“奔跑至目標”和“逃跑”,我們可以使用一些過渡,建立出模擬AI基本行為的狀態機。
下面是簡單的AI示例。
我們製作開發的AI可以用下面三句話描述:
如果AI的生命值在10%以下時,它就會逃跑。
AI可以受到攻擊。
玩家處在AI範圍內時,AI會向玩家跑去,然後攻擊玩家。
這意味著我們的AI很簡單。如果無法簡單地描述自己設計的AI,那麼我們可能需要對自己的AI做進一步思考。
狀態機與Unity
我們在Unity使用狀態機大致的方法有三種:
- 自己開發。
- 使用Animator實現。
- 從Asset Store資源商店獲取相應資源。
狀態機是遊戲中很常見的工具,所以我們不建議開發者自己編寫程式碼開發狀態機。除非開發者希望學習如何通過編碼實現狀態機,否則我們可以獲取可直接使用的狀態機。
第二種方法是使用Unity的內建Animator功能,它其實是一種可以播放動畫的狀態機。但在Animator中,我們不一定要使用動畫,如果不使用動畫的話,它的工作方式和狀態機一樣。
Animator使用起來快捷而直觀。下圖是《Radiant Blade》中使用Unity Animator實現的弓箭手AI。
第三種方法是從Asset Store資源商店獲取相關資源,不少資源有和Animator一樣不錯的效果。
Animator
或許你使用過Animator在Unity中實現標準動畫,但我們會根據需求調整一些方法。
狀態
通常,Animator的狀態包含動畫。我們沒有這樣使用,而是把狀態關聯到描述行為的程式碼。
為了展示這個方法,我們現在檢視定義弓箭手的遊戲物件。Behaviours物件的子物件是AI行為,它們其實是小型控制器,在對應狀態啟用時,它們會控制弓箭手。
在Shoot狀態啟用時,會在弓箭手上使用Shoot Behaviour指令碼。
這是基於狀態的物件。在完成行為後,Shoot Behaviour會通知Animator。Animator內建的藍色進度條可能會讓人迷惑,但它只在外觀上起到作用。
變數
我們的AI設計是響應式系統,它會隨條件而變化,條件是玩家和環境。
Animator的變數用於描述遊戲的狀態,以及作出已知決策。下圖是弓箭手使用的變數,它們描述了形成AI的所有要素。
在以傳統方法使用Animator時,大多數狀態過渡會隨著關聯動畫結束而結束。對於AI來說,狀態就是行為,它會在未定義的時間內儲存遊戲邏輯。
我們使用了兩個變數behaviour_ended和behaviour_error,作用是通知狀態的結束。它們是狀態的輸出結果,表示狀態成功結束,或是出現錯誤。
過渡
過渡定義了AI行為的改變過程。例如,過渡可以表示:當AI完成向目標行走的過程後,它應該要做什麼。
下圖是示例過渡:如果目標在近戰範圍內,AI會進行攻擊。
對Unity的Animator,有些開發者可能不知道的是:過渡是有先後順序的。特定過渡會被首先評估,僅在它的相關條件為假時,第二個過渡才會進行評估。
如下圖所示,選中Neutral狀態時,我們可以檢視過渡的優先順序。
這項功能很不錯,它允許我們把AI設計為中心大腦,根據優先順序來作出合適的選擇。
在我們的弓箭手AI中,需要注意AI的順序和中心部分。Neutral節點是決策中心,它的主要工作過程如下:
- 如果沒有玩家的話,AI停止戰鬥;
- 如果玩家距離較遠,AI向玩家移動,進入射擊範圍;
- 如果玩家不在AI的視線方向,AI向玩家移動,從而能夠進行射擊;
- 如果處於近戰範圍,則進行近戰攻擊;
- 如果玩家過於接近AI,AI可能會向後退;
- AI有可能隨機改變和玩家的方向;
- AI會向玩家射擊。
該功能的好處是每個單獨的過渡都非常簡單:過渡會歸結為一次測試,或甚至不進行測試。使用後續過渡的前提是之前的過渡條件必須為假。
實現方法
現在,我們開始瞭解具體操作。你應該會注意到,我們還未提供過任何相關程式碼。
這意味著我們的框架有足夠高的抽象級,不必處理任何技術細節,就可以很好進行解釋。在程式碼部分完成後,設計AI的過程非常直觀。
我們需要什麼
下面是實現AI的三個任務:
編寫AI行為。
將Animator和可用行為關聯。
為Animator更新遊戲相關變數的列表。
行為
開始處理前,回顧行為的功能:
行為會和遊戲的角色控制器配合使用。
行為可以被識別。
行為可以被啟用。
行為可以成功完成。
行為也可以出現錯誤。
行為可以被中斷。
現在我們知道了想要的功能,我們要把它編寫為API。
- public abstract class AbstractAIBehaviour : MonoBehaviour {
-
- // 角色由行為控制
- [SerializeField]
- protected CharController charController;
-
- // 必須返回對應行為的Animator狀態的短雜湊值。
- abstract public int GetBehaviourHash();
-
- // 在行為成功結束時呼叫的事件。
- public event Action OnBehaviourEnded;
-
- // 在行為失敗時,要呼叫的事件
- public event Action OnBehaviourError;
-
- // OnDisable()
- // enable = true/false;
- }
對於啟用和禁用部分,我們會利用Unity的內建方法,這裡不必自己編寫方法。我們會使用簡潔的API。
對於識別符,我們建立了帶有特殊名稱的方法:GetBehaviourHash。因為Animator狀態的識別方式是:使用狀態的識別符號,也就是其名稱的雜湊值。
因此對於Shoot狀態,對應的識別符號是Animator.StringToHash(“Shoot”)。
為了弄清楚物件,避免再次計算相同的雜湊值,我們可以把它們儲存為靜態變數:
- /**
- *該類是預計算雜湊值的佔位符。
- * 目的是建立Animator狀態名稱和AI行為之間的關聯。
- * 下面定義的整數應該用於GetBehaviourHash中繼承自AbstractAIBehaviour的類。
- */
- public class BehaviourHashes {
-
- //該行為會讓角色向目標移動。
- static public readonly int OBJ_MOVETO_STATE = Animator.StringToHash("Obj MoveTo");
-
- // 該行為會讓角色什麼都不做。
- static public readonly int IDLE_STATE = Animator.StringToHash("Idle");
-
- // 此時角色會漫無目的地四處移動。
- static public readonly int ROAM_STATE = Animator.StringToHash("Roam");
-
- // ...
- }
考慮到這點,AbstractAIBehaviour的實現程式碼如下。
- // 必須返回對應行為的Animator狀態的短雜湊值。
- public override int GetBehaviourHash()
- {
- // State name in the Animator is “Idle”
- // Animator中的狀態名稱為Idle。
- return BehaviourHashes.IDLE_STATE;
- }
我們將每個雜湊值存到對應的指令碼中,因此ROAM_STATE可以儲存在RoamBehaviour類中。
唯一的問題是,由於我們暗中把每個行為關聯到名稱,因此開啟每個行為類收集Animator狀態的授權名稱可能很麻煩。
現在,我們的工作是為真實行為編寫實際的程式碼,我們需要做的是實現AbstractAIBehaviour的子類。
關聯行為和Animator
我們的AI的行為可以被識別、監聽、啟用和禁用,現在我們要利用行為。
我們從控制器開始。由於我們有多個彼此獨立的實體,因此我們需要同步它們,從而實現流暢的工作效果。該控制器的目的是確保每次只啟用一個行為,並提供修改當前行為的切入點。
一些開發者可能不知道應該何時給遊戲新增新控制器的類。好的習慣是把控制器看作用來同步多個較小功能的程式碼。
- /**
- * AIBehaviourController應該關聯AI的Animator和相應行為。
- */
- public class AIBehaviourController
-
- /**
- * 這部分包含可用行為
- *
- * 行為的關鍵是GetBehaviourHash方法返回的數值
- */
- protected Dictionary<int, AbstractAIBehaviour> behaviours = new Dictionary<int, AbstractAIBehaviour>();
-
- // AI的Animator
- private Animator stateMachine;
-
- // 該變數表示正在執行的行為
- private AbstractAIBehaviour currentBehaviour;
-
- // 下面是必須存在AI Animator中的觸發器
- public static readonly int BEHAVIOUR_ENDED = Animator.StringToHash("behaviour_ended");
- public static readonly int BEHAVIOUR_ERROR = Animator.StringToHash("behaviour_error");
-
- /**
- * 強制某個行為中斷正在執行的行為
- */
- public void SetBehaviour(int behaviorHash)
- {
- // 安全地禁用當前行為
- if (currentBehaviour)
- currentBehaviour.enabled = false;
-
- try
- {
- // 開始新的行為
- currentBehaviour = behaviours[behaviorHash];
- currentBehaviour.enabled = true;
- }
- catch (KeyNotFoundException)
- {
- currentBehaviour = null;
- }
- }
-
- void Awake()
- {
- stateMachine = GetComponent<Animator>();
- // 對於每個子物件
- foreach (AbstractAIBehaviour behaviour in GetComponentsInChildren<AbstractAIBehaviour>())
- {
- // 註冊行為
- behaviours.Add(behaviour.GetBehaviourHash(), behaviour);
-
- // 監聽行為
- behaviour.OnBehaviourEnded += OnBehaviourEnded;
- behaviour.OnBehaviourError += OnBehaviourError;
- }
- }
-
- /**
- * 在行為結束時,通知AI的Animator
- */
- private void OnBehaviourEnded()
- {
- stateMachine.SetTrigger(BEHAVIOUR_ENDED);
- }
-
- /**
- * 在行為失敗時,通知AI的Animator
- */
- private void OnBehaviourError()
- {
- stateMachine.SetTrigger(BEHAVIOUR_ERROR);
- }
- }
這個類比較長,但是程式碼其實很簡單:
- 字典包含我們已知的行為。
- 方法可以啟用特定行為。
- 兩個事件用於在行為結束時通知Animator。
有了切入點,我們可以把它和Animator連線起來。我們會使用一個不常用的功能:StateMachineBehaviour。
如下圖所示,選中Animator時,如果在空白處單擊左鍵,我們會聚焦Animator本身,並顯示Animator的隱藏檢視視窗。
StateMachineBehaviour允許我們向Animator插入自定義程式碼。我們會在Animator的狀態變化時,呼叫我們的AIBehaviourController。
- /**
- * 該類會插入AI的Animator。
- *
- * 它的唯一作用是監視Animator中的狀態轉換。
- */
- public class AIStateController : StateMachineBehaviour {
-
- /**
- * 在Animator進入新狀態時,通知AI控制器。
- */
- override public void OnStateEnter(Animator animator, AnimatorStateInfo info, int layerIndex)
- {
- if (!animator.GetComponent<AIBehaviourController>().SetBehaviour(animatorStateInfo.shortNameHash))
- {
- // 如果狀態不存在,那麼把它設為決策中心。
- // 強制Animator直接評估該狀態。
- animator.Update(0f);
- }
- }
- }
這些程式碼非常直觀,它會處理Unity的一個特別之處:Animator無法在每幀處理多個狀態,因此在我們遍歷決策中心時,會造成短暫的延遲。
幸運的是,解決方法很簡單,我們可以強行執行Update方法,強制Animator處理狀態。
通過使用我們的新類,我們可以把功能結合起來,只要把該指令碼新增到AI的Animator即可。現在進入新狀態時,我們的AI Animator會呼叫AIBehaviourController。
最後,我們在框架中包含三個類,子類以及一個角色控制器,它們包含著實際的遊戲邏輯。
下圖是一個組合成AI框架的小型類圖示。
處理遊戲邏輯
總而言之,技術解決方案可以總結為三個類,每個類都非常簡潔。
我們還需要什麼呢?當然是遊戲本身了。但這個部分必須由開發者自己製作,實現自己的AI需要的內容如下:
一個角色控制器,負責角色和其渲染的實際邏輯。
變數以及讓變數與Animator保持同步的程式碼。
自定義行為,例如:攻擊,移動。
此時我們要處理的都是常見的Unity標準程式碼。
小結
如何在Unity中製作遊戲AI的方法為大家介紹到這裡,行動起來,在你的遊戲中,實現自定義行為的AI吧。
作者:Synnaxium Studio
來源:Unity官方平臺
原地址:https://mp.weixin.qq.com/s/hkBmBAn7YMOnfjLygz-4Jw
相關文章
- Unity遊戲積分/計分系統製作方法Unity遊戲
- 利用NEO與Unity製作遊戲(第3部分)Unity遊戲
- 我們跟Unity聊了聊,如何助力3A遊戲製作Unity遊戲
- 用unity製作簡單的太空遊戲(2)-簡單炮臺Unity遊戲
- unity入門—五分鐘製作一個理論上的遊戲Unity遊戲
- 小遊戲的製作遊戲
- 製作遊戲的遊戲:創作樂趣的樂趣遊戲
- 使用 Flutter 與 Firebase 製作 I/O 彈球遊戲Flutter遊戲
- 遊戲雜談:大製作遊戲和小製作遊戲,在開發思路方式上的差別遊戲
- Unity製作特寫鏡頭Unity
- Unity製作一個小星球Unity
- 揭祕《Sherman》:使用Unity製作影視級光照效果Unity
- 使用Unity製作起霧的窗戶效果著色器Unity
- 如何低成本製作遊戲音效遊戲
- Suno AI:使用文字提示製作音樂AI
- 使用THREE.js製作一款3D遊戲JS3D遊戲
- Python製作太空射擊小遊戲!Python遊戲
- 遊戲角色寫實頭髮製作遊戲
- 如何用Python製作自己的遊戲Python遊戲
- 製作遊戲載入進度條遊戲
- 【Unity 框架】 QFramework v1.0 使用指南 工具篇: 16. LiveCodingKit 寫程式碼不用停止執行的利器 | Unity 遊戲框架 | Unity 遊戲開發 | Unity 獨立遊戲Unity框架Framework遊戲開發
- Unity遊戲示例來了,用Unity開源遊戲資源做遊戲,遊戲開發不再難!Unity遊戲開發
- 巨人網路製作人聊遊戲長留: 提高遊戲長留是遊戲製作終極追求遊戲
- 底層搭建:踏入動作遊戲的製作階段遊戲
- 遊戲特效有哪些製作的分類遊戲特效
- Microsoft + Perforce:遊戲製作的全新構想ROS遊戲
- 使用Azure Congnitive Services 技術製作AI故事機AI
- 微信吸粉小遊戲怎麼製作?中秋節微信公眾號吸粉小遊戲製作教程遊戲
- 《最終幻想7:重製版》遊戲音訊製作分享遊戲音訊
- Unity首款遊戲《Gigaya》劃重點!創作團隊親述創作歷程Unity遊戲
- 遊戲音樂的認識與製作三遊戲
- 製作3D小汽車遊戲(上)3D遊戲
- 製作3D小汽車遊戲(下)3D遊戲
- 【Unity3D開發小遊戲】《戰棋小遊戲》Unity開發教程Unity3D遊戲
- 【Unity 3D遊戲開發】在Unity使用NoSQL資料庫方法介紹Unity3D遊戲開發SQL資料庫
- 遊戲AI:AI的遊戲還是遊戲的未來遊戲AI
- AI作畫—如何快速進行遊戲人物設計AI遊戲
- three.js 製作邏輯轉體遊戲(上)JS遊戲