🤔
- 有限状态机,FSM,简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的模型。
- 在unity中我们经常会判断一个人物(或怪物)的运动状态,如巡逻、追逐、攻击、死亡等,然后作出相应的操作,这些时候我们可以使用有限状态机来集中管理这些状态。
- 为了解决上述问题,我们经常会使用fsm有限状态机、分层有限状态机以及行为树,这里我们主要了解fsm有限状态机
- FSM在unity中算是一个比较重要的技巧
以下是我写的一个简单的FSM状态机的例子:
FSMState.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103 using System.Collections;
using System.Collections.Generic;
using UnityEngine.SceneManagement;
using UnityEngine;
public abstract class FSMState {
//字典,字典中每一项都记录了一个“转换-状态”对 的信息
protected Dictionary<Transition, FSMStateID> map = new Dictionary<Transition, FSMStateID>();
//状态编号ID
protected FSMStateID stateID;
public FSMStateID ID { get { return stateID; } }
//目标点位置
protected Vector3 destPos;
//巡逻点数组
protected Transform[] waypoints;
//旋转速度
protected float curRotSpeed;
//移动速度
protected float curSpeed;
//发现对面的距离
public float chaseDistance{
get {
return 10f;
}
}
//攻击距离
public float SRAttackDistance{
get {
return 1f;
}
}
//目标点距离
protected float arriveDistance{
get {
return 2f;
}
}
/// <summary>
/// 向字典添加项,每项是一个"转换--状态"对
/// </summary>
/// <param name="transition"></param>
/// <param name="id"></param>
public void AddTransition(Transition transition, FSMStateID id)
{
if (map.ContainsKey(transition))
{
return;
}
map.Add(transition, id);
}
/// <summary>
/// 从字典中删除项
/// </summary>
/// <param name="trans"></param>
public void DeleteTransition(Transition trans)
{
if (map.ContainsKey(trans))
{
map.Remove(trans);
return;
}
}
/// <summary>
/// 通过查询字典,确定在当前状态下,发生trans转换时,应该转换到新的状态编号并返回
/// </summary>
/// <param name="trans"></param>
/// <returns></returns>
public FSMStateID GetOutputState(Transition trans)
{
return map[trans];
}
/// <summary>
/// 用来确定是否需要转换到其他状态,应该发生哪个转换
/// </summary>
/// <param name="player"></param>
/// <param name="npc"></param>
public abstract void Reason(Transform hero, Transform monster);
/// <summary>
/// 定义了在本状态的角色行为,移动,动画等
/// </summary>
/// <param name="player"></param>
/// <param name="npc"></param>
public abstract void Act(Transform hero, Transform monster);
public virtual void Enter (Transform hero, Transform monster){
}
/// <summary>
/// 选择随机逻辑点
/// </summary>
public void FindNextPoint(){
int rndIndex = Random.Range (0, waypoints.Length);
Vector3 rndPosition = Vector3.zero;
destPos = waypoints [rndIndex].position + rndPosition;
}
}
FSM.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36 using UnityEngine;
using System.Collections;
public class FSM : MonoBehaviour{
// 玩家位置
protected Transform playerTranform;
// 下一个巡逻点
protected Vector3 destPos;
// 巡逻点表单
protected GameObject[] pointList;
//
protected float elapsedTime;
protected virtual void Initialize(){}
protected virtual void FSMUpdate(){}
protected virtual void FSMFixedUpdate(){}
// 初始化
void Start(){
Initialize ();
}
//循环执行
void Update(){
FSMUpdate ();
}
void FixedUpdate(){
FSMFixedUpdate ();
}
}
AdvancedFSM.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103 using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum Transition{
SawPlayer = 0, //是否看到玩家
ReachPlayer, //接近玩家
LostPlayer, //玩家离开视线
NoHealth, //死亡
}
public enum FSMStateID{
Patrolling = 0, // 巡逻编号
Chasing, // 追踪编号
Attacking,
Dead, // 死亡编号
}
public class AdvancedFSM : FSM
{
//FSM中的所有状态组成的列表
private List<FSMState> fsmStates;
//当前状态的编号
private FSMStateID currentStateID;
public FSMStateID CurrentStateID { get { return currentStateID; } }
//当前状态
private FSMState currentState;
public FSMState CurrentState { get { return currentState; } }
public AdvancedFSM()
{
//新建一个空的状态列表
fsmStates = new List<FSMState>();
}
/// <summary>
///向状态列表中加入一个新的状态
/// </summary>
public void AddFSMState(FSMState fsmState)
{
if (fsmState == null)
{
Debug.LogError("状态为空");
return;
}
if (fsmStates.Count == 0)
{
fsmStates.Add(fsmState);
currentState = fsmState;
currentStateID = fsmState.ID;
return;
}
foreach (FSMState state in fsmStates)
{
if (state.ID == fsmState.ID)
{
Debug.LogError("状态已存在");
return;
}
}
//如果要加入的状态不在列表中,将它加入列表
fsmStates.Add(fsmState);
}
//从状态中删除一个状态
public void DeleteState(FSMStateID fsmState)
{
// 搜索整个状态列表,如果要删除的状态在列表中,那么将它移除,否则报错
foreach (FSMState state in fsmStates)
{
if (state.ID == fsmState)
{
fsmStates.Remove(state);
return;
}
}
Debug.LogError("要删除的状态不在列表中");
}
/// <summary>
/// 根据当前状态,和参数中传递的转换,转换到新状态
/// </summary>
public void PerformTransition(Transition trans)
{
FSMStateID id = currentState.GetOutputState(trans);
currentStateID = id;
foreach (FSMState state in fsmStates)
{
if (state.ID == currentStateID)
{
currentState = state;
break;
}
}
}
}
AttackState.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59 using System.Collections;
using UnityEngine;
public class AttackState : FSMState {
Animator monsterAnimator;
AnimatorStateInfo stateInfo;
public AttackState(Transform[] wp){
waypoints = wp;
stateID = FSMStateID.Attacking;
curRotSpeed = 12;
curSpeed = 100;
FindNextPoint ();
}
public override void Enter (Transform hero, Transform monster)
{
monsterAnimator = monster.GetComponent<Animator>();
}
public override void Reason(Transform hero, Transform monster){
if (hero != null) {
stateInfo = monsterAnimator.GetCurrentAnimatorStateInfo (0);
float dist = Vector3.Distance (monster.position, hero.position);
if (dist >= SRAttackDistance && dist < chaseDistance) {
if (stateInfo.IsName ("skill") && stateInfo.normalizedTime % 1 > 0.9f)
monster.GetComponent<MonsterAIController> ().SetTransition (Transition.SawPlayer);
} else if (dist >= chaseDistance) {
if (stateInfo.IsName ("skill") && stateInfo.normalizedTime % 1 > 0.9f)
monster.GetComponent<MonsterAIController> ().SetTransition (Transition.LostPlayer);
}
}else{
monster.GetComponent<MonsterAIController> ().SetTransition (Transition.LostPlayer);
}
}
public override void Act(Transform hero, Transform monster){
destPos = hero.position;
Quaternion targetRotation = Quaternion.LookRotation (destPos - monster.position);
monster.rotation = Quaternion.Slerp (monster.rotation, targetRotation, Time.deltaTime * curRotSpeed);
// CharacterController controller = monster.GetComponent<CharacterController> ();
// controller.SimpleMove (monster.transform.forward * Time.deltaTime * curSpeed);
//播放攻击动画
}
}
ChaseState.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60 using System.Collections;
using System.Collections.Generic;
using UnityEngine.AI;
using UnityEngine;
using UnityEngine.SceneManagement;
public class ChaseState : FSMState {
NavMeshAgent monsterAgent;
Animator monsterAnimator;
public ChaseState(Transform[] wp){
waypoints = wp;
stateID = FSMStateID.Chasing;
curRotSpeed = 7;
curSpeed = 300;
FindNextPoint ();
}
public override void Enter (Transform hero, Transform monster)
{
monsterAnimator = monster.GetComponent<Animator>();
monsterAgent = monster.GetComponent<NavMeshAgent> ();
}
public override void Reason(Transform hero, Transform monster){
if (hero != null) {
destPos = hero.position;
float dist = Vector3.Distance (monster.position, destPos);
if (dist <= SRAttackDistance) {
AudioManager.Instance.PlayFXAudio ("Sound_MonsterPatrolPlayer", monster.transform.position);
monster.GetComponent<MonsterAIController> ().SetTransition (Transition.ReachPlayer);
} else if (dist >= chaseDistance) {
monster.GetComponent<MonsterAIController> ().SetTransition (Transition.LostPlayer);
}
}else{
monster.GetComponent<MonsterAIController> ().SetTransition (Transition.LostPlayer);
}
}
public override void Act(Transform hero, Transform monster){
destPos = hero.position;
Quaternion targetRotation = Quaternion.LookRotation (destPos - monster.position);
monster.rotation = Quaternion.Slerp (monster.rotation, targetRotation, Time.deltaTime * curRotSpeed);
monsterAgent.SetDestination (destPos);
monster.GetComponent<NavMeshAgent> ().speed = 2;
//播放奔跑动画
}
}
PatrolState.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53 using System.Collections;
using System.Collections.Generic;
using UnityEngine.AI;
using UnityEngine;
using UnityEngine.SceneManagement;
public class PatrolState : FSMState{
Animator monsterAnimator;
NavMeshAgent monsterAgent;
public PatrolState(Transform[] wp){
waypoints = wp;
stateID = FSMStateID.Patrolling;
curRotSpeed = 6;
curSpeed = 200;
FindNextPoint ();
}
public override void Enter (Transform hero, Transform monster)
{
}
public override void Reason(Transform hero, Transform monster){
if (hero != null) {
if (Vector3.Distance (monster.position, hero.position) <= chaseDistance) {
AudioManager.Instance.PlayFXAudio ("Sound_MonsterSawPlayer", monster.transform.position);
monster.GetComponent<MonsterAIController> ().SetTransition (Transition.SawPlayer);
}
}
}
public override void Act(Transform hero, Transform monster){
monsterAgent = monster.GetComponent<NavMeshAgent> ();
if (!(monsterAgent.pathPending || monsterAgent.remainingDistance > (monsterAgent.stoppingDistance + 0.5f) || monsterAgent.velocity != Vector3.zero)) {
FindNextPoint ();
}
// Physics.SphereCastAll ();
// Quaternion targetRotation = Quaternion.LookRotation (destPos - monster.position);
// monster.rotation = Quaternion.Slerp (monster.rotation, targetRotation, Time.deltaTime * curRotSpeed);
monsterAgent.SetDestination (destPos);
monster.GetComponent<NavMeshAgent> ().speed = 1;
//播放行走动画
}
}
DeadState.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29 using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;
public class DeadState : FSMState {
public DeadState(){
stateID = FSMStateID.Dead;
}
public override void Enter (Transform hero, Transform monster)
{
monster.GetComponent<NavMeshAgent> ().enabled = false;
monster.GetComponent<Collider> ().enabled = false;
GameManager.Instance.AddScore ();
}
public override void Reason(Transform hero, Transform monster){
}
public override void Act (Transform hero, Transform monster){
//播放死亡动画
}
}
MonsterAIController.css
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82 using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class MonsterAIController : AdvancedFSM {
private Transform objHero;
protected override void Initialize(){
//初始化血量、刚体等
// CurrentState.Enter (playerTranform, transform);
//实现有限状态机
ConstructFSM ();
}
protected override void FSMUpdate(){
elapsedTime += Time.deltaTime;
}
protected override void FSMFixedUpdate(){
if (objHero == null) {
if (FindTarget.FindHero (this.transform, radiu).Length == 1) {
objHero = FindTarget.FindHero (this.transform, radiu) [0].transform;
} else if (FindTarget.FindHero (this.transform, radiu).Length > 1) {
for (int i = 0; i < FindTarget.FindHero (this.transform, radiu).Length - 1; i++) {
objHero = Vector3.Distance (this.transform.position, FindTarget.FindHero (this.transform, radiu) [i].transform.position) < Vector3.Distance (this.transform.position, FindTarget.FindHero (this.transform, radiu) [i + 1].transform.position) ? FindTarget.FindHero (this.transform, radiu) [i].transform : FindTarget.FindHero (this.transform, radiu) [i + 1].transform;
}
}
}
// Transform objHero = FindTarget.FindEnemy (out hit, this.transform, radiu, maxDistance);
playerTranform = objHero;
CurrentState.Reason (playerTranform, this.transform);
CurrentState.Act (playerTranform, this.transform);
private void ConstructFSM(){
pointList = GameObject.FindGameObjectsWithTag ("PatrolPoint");
Transform[] waypoints = new Transform[pointList.Length];
int k = 0;
foreach (GameObject obj in pointList) {
waypoints [k] = obj.transform;
k++;
}
//吧对应的状态加入字典
//巡逻
PatrolState patrol = new PatrolState (waypoints);
//发现玩家,并追踪
patrol.AddTransition(Transition.SawPlayer, FSMStateID.Chasing);
patrol.AddTransition (Transition.NoHealth, FSMStateID.Dead);
//追踪
ChaseState chase = new ChaseState(waypoints);
//丢失玩家,转为巡逻
chase.AddTransition(Transition.LostPlayer, FSMStateID.Patrolling);
//接近后攻击
chase.AddTransition(Transition.ReachPlayer, FSMStateID.Attacking);
chase.AddTransition (Transition.NoHealth, FSMStateID.Dead);
//攻击
AttackState attack = new AttackState (waypoints);
attack.AddTransition (Transition.LostPlayer, FSMStateID.Patrolling);
attack.AddTransition (Transition.SawPlayer, FSMStateID.Chasing);
attack.AddTransition (Transition.NoHealth, FSMStateID.Dead);
//死亡
DeadState dead = new DeadState();
dead.AddTransition (Transition.NoHealth, FSMStateID.Dead);
AddFSMState (patrol);
AddFSMState (chase);
AddFSMState (attack);
AddFSMState (dead);
}
/// <summary>
/// 设置状态转移
/// </summary>
/// <param name="t">T.</param>
public void SetTransition(Transition t){
PerformTransition (t);
CurrentState.Enter(playerTranform , transform);
}
}