Unity簡單有限狀態機實現
【前言】 本篇來說一下關於簡單有限狀態機的演算法實現,當然我們的幾個狀態會比較簡單,而且本身我也是處於入門狀態,所以當成一個簡單的知識積累。有限狀態機,顧名思義,狀態是有限個的,而且狀態之間是關聯的,本篇寫的狀態機,其實是類似寫遊戲裡面的AI機器人,就是那些遊戲裡面的怪啊,npc啊,本篇也是針對幾個行為或者是狀態,進行設計,編寫相關指令碼。
1.新建·一個專案,再資源商店搜尋Zombie,匯入敵人的模型和動畫,搜尋Yurowm匯入玩家的模型和動畫,至於場景,搜尋_Barking_Dog,匯入場景
2.新建之後,先把這個放一邊,首先我們來分析怪物狀態以及過渡條件
如下圖:
3.根據以上簡單的集中關係組合,我們來進行設計。首先新建一個資料夾,取名Animators,建立一個動畫控制器,把Zombie資料夾下面的動畫idle,attack,walk拉進動畫控制器,設定以下關係以及新建引數。至於動畫之間的過渡,idle過渡到walk和attack,就設定相應的為true就可以了,反之回到idle設定為false,而walk到attack或者是attack到walk,則設定目標為true,自身為false。這樣,簡單的控制器就完成了。
4.分析敵人獲取主角的演算法,這裡我採用的是獲取角度獲取視野的方式,當然這個不限於這種簡單的方式,我這裡就簡單用一下這種演算法。獲取主角的原理如下,很簡單:
判斷角度a即可,然後判斷距離,獲取是否在攻擊範圍之內。
5。還有一個,在移動的方式上,我選擇了unity自帶的AI尋路移動,所以不可避免地要在Zombie的模型上新增一個Nav Mesh Agent元件,到時候直接呼叫元件移動即可。把_Barking_Dog檔案下的Test_Map拉進場景,然後把_Level和攝像機拉進我們自己的Scene,當然原來的攝像機和燈光要刪除。檢查一下_Level下面的子物體Floor_01是否都設定為靜態,如果是就可以開始烘焙尋路了。
6.為Zombie新增Animator元件,把控制器賦上,然後在資源庫裡面找到Yurowm的模型,拉進場景裡面,取名Player,設定標籤為Player。
7.接下來上指令碼,新建一個指令碼為FsmDemo,賦給敵人,解釋在腳本里面:
using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.AI; public enum EnemyState//狀態列舉 { Patrol,//巡邏 Attack,//攻擊 Rest,//休息 Chase//追擊 } public class FsmDemo : MonoBehaviour { public EnemyState enemyState; private Transform Player;//主角 public float EnemyRestTime = 3;//控制休息時間 private float RestTime;//休息時間 private Animator EnemyAnimator;//動畫控制器 public float MaxSightDistance = 10;//最大的視野範圍 public float CanAttackDistance = 1;//攻擊範圍 public Transform MoveTarget;//最終要移動到的目標 public Transform point;//路點父物體 public List<Transform> Points = new List<Transform>();//路點的連結串列陣列 private NavMeshAgent m_navmeshment;//尋路元件 private int index = 0;//路點索引 public bool canmove = true;//是否可以移動 void Start() { m_navmeshment = GetComponent<NavMeshAgent>();//獲取尋路元件 RestTime = EnemyRestTime;//初始化休息時間 EnemyAnimator = GetComponent<Animator>();//獲取動畫控制器 Player = GameObject.FindWithTag("Player").transform;//獲取主角 enemyState = EnemyState.Rest;//初始化狀態 for (int i = 0; i < point.childCount; i++)//獲取父物體下面的路點 { Points.Add(point.GetChild(i)); } MoveTarget = Points[0];//初始化移動目標 if (m_navmeshment == null || Player == null || EnemyAnimator == null)//以防出錯 { Debug.LogError("未新增完整元件或者是標籤"); enabled = false; return; } } // Update is called once per frame void Update () { DealWithEnemyState(); //Debug.DrawLine(transform.position, transform.forward, Color.red); //Debug.DrawLine(transform.position, Player.position - transform.position, Color.red); } private void DealWithEnemyState()//處理狀態的方法 { switch (enemyState) { case EnemyState.Patrol://巡邏 OnPatrolState(); ChangePatrolToOtherState(); break; case EnemyState.Attack://攻擊 OnAttackState(); ChangeAttackToOtherState(); break; case EnemyState.Rest://休息 OnRestState(); ChangeRestToOtherState(); break; case EnemyState.Chase://追擊 OnChaseState(); ChangeChaseToOtherState(); break; default: break; } } private void OnRestState()//處理休息 { RestTime -= Time.deltaTime;//休息時間倒計時 EnemyAnimator.SetBool("walk", false); EnemyAnimator.SetBool("attack", false);//播放動畫 m_navmeshment.speed = 0;//移動速度設定為0 } private void OnAttackState()//處理攻擊 { m_navmeshment.speed = 0;//速度設定為0 EnemyAnimator.SetBool("attack", true); EnemyAnimator.SetBool("walk",false);//播放動畫 } private void OnChaseState()//處理追擊 { EnemyAnimator.SetBool("walk", true); EnemyAnimator.SetBool("attack", false);//播放動畫 MoveTarget = Player;//設定目標位Player m_navmeshment.speed = 0.1f;//設定移動速度 MoveToTarget(MoveTarget);//開始移動追擊 } private void OnPatrolState()//處理巡邏 { //移動到目標點,進行位移 m_navmeshment.speed = 0.1f; if (canmove) { MoveToTarget(MoveTarget); } EnemyAnimator.SetBool("walk", true); EnemyAnimator.SetBool("attack", false); //播放移動動畫 } private void ChangeRestToOtherState()//休息狀態到別的狀態 { if (IsOnSightOfEnemy())//在視野範圍內 { MoveTarget = Player; //設定目標MoveToTarget print("在視野內"); if (IsOnAttackDistance())//在攻擊範圍內 { print("在攻擊範圍內"); enemyState = EnemyState.Attack;//改變狀態 } else { print("不在攻擊範圍內"); enemyState = EnemyState.Chase; } } else//不在視野範圍內 //判斷倒計時,且目標點還是巡邏點 { //設定目標巡邏點 if (RestTime <= 0)//倒計時結束 { RestTime = EnemyRestTime;//重置倒計時時間 canmove = true;//可以移動 if (index == Points.Count - 1)//迴圈路點 { index = 0; } else { index++; } MoveTarget = Points[index]; enemyState = EnemyState.Patrol;//改變狀態 } else//倒計時未結束,不能移動 { canmove = false; } } } private void ChangeAttackToOtherState() { //時刻監視狀態 if (IsOnSightOfEnemy()) { MoveTarget = Player; //設定目標MoveToTarget print("在視野內"); if (IsOnAttackDistance()) { print("在攻擊範圍內"); enemyState = EnemyState.Attack; } else { print("不在攻擊範圍內"); enemyState = EnemyState.Chase; } } else { enemyState = EnemyState.Patrol;//不在視野內,也就是說主角逃走了,設定為巡邏 } //判斷血量 //判斷目標 }//攻擊到別的狀態 private void ChangeChaseToOtherState() { if (IsOnSightOfEnemy())//在視野內 { MoveTarget = Player; //設定目標MoveToTarget print("在視野內"); if (IsOnAttackDistance()) { print("在攻擊範圍內"); enemyState = EnemyState.Attack; } else { print("不在攻擊範圍內"); enemyState = EnemyState.Chase; } } else { MoveTarget = Points[index]; enemyState = EnemyState.Patrol; } }//追擊到別的狀態 private void ChangePatrolToOtherState() { //監測狀態 if (IsOnSightOfEnemy()) { MoveTarget = Player; //設定目標MoveToTarget print("在視野內"); if (IsOnAttackDistance()) { print("在攻擊範圍內"); enemyState = EnemyState.Attack; } else { print("不在攻擊範圍內"); enemyState = EnemyState.Chase; } } else { float dis = Vector3.Distance(transform.position, MoveTarget.position); if (dis < 0.5f) { enemyState = EnemyState.Rest; } } //如果不在視野內,目標還是巡邏點 }//巡邏到別的狀態 private bool IsOnSightOfEnemy()//是否在可視範圍內 { bool onsight=false; float dis = Vector3.Distance(Player.position, transform.position); float angle = Vector3.Angle(transform.forward, Player.position - transform.position); if (dis < MaxSightDistance) { if (angle < 70) { onsight = true; } } return onsight; } private bool IsOnAttackDistance()//判斷是否在攻擊範圍內的方法 { if (IsOnSightOfEnemy()) { float dis = Vector3.Distance(Player.position, transform.position); if (dis < CanAttackDistance) { return true; } } return false; } private void MoveToTarget(Transform target)//移動的方法 { m_navmeshment.SetDestination(target.transform.position); } }
8.回到Unity中,設定巡邏路點,我這裡設定3個點,其實設定多少個都可以,演算法裡會進行迴圈執行,如下圖
9.把父路點賦給FsmDemo腳本里面的point,調整Zombie的位置,執行。主角在這裡沒有進行指令碼控制,所以你可以在Scene面板中移動主角進行測試。
10.總結:其實總結下來,這種簡單的有限狀態機實現起來很簡單,但是遇到複雜一點的狀態機,就要考慮優化的問題了。所以說,學習的時候要有很強的邏輯性,也要研究一下自己的實現方式,是用什麼模式實現,畢竟如果真是做專案,需要團隊合作,這一點還是要考慮的。