Unity之有限状态机(1.01版)

本版本是对有限状态机的优化,并讲了有限状态机的具体使用,要比之前的版本更好。主要是借鉴了博主向宇it的文章。


一、编写框架

首先我们新建一个Parameter.cs,用于存储我们怪物的各种参数,比如动画啊、伤害啊,并加上[Serializable]属性使其可以编辑。
1
2
3
4
5
6
7
8
9
10
11
[Serializable]
public class Parameter
{
//定义我们需要的参数
public Animator animator;
public float attack;
public NavMeshAgent nav;
public AnimatorStateInfo animatorStateInfo;//当前动画信息
public float idleTime;
public Transform targetObj;
}

之后写一个IState.cs脚本,我们所有的状态都要继承它,相等于一个规范

1
2
3
4
5
6
7
8
public abstract class IState
{
protected FSM fsm;// 当前状态机
protected Parameter parameter;// 参数
public abstract void Enter();
public abstract void Update();
public abstract void Exit();
}

之后写一个FSM.cs脚本,继承MonoBehaviour,这就是我们状态机的主体框架了。

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
public enum StateType
{
idle,
stand,
run,
attack
}
public class FSM : MonoBehaviour
{
private IState currentIState;
protected Dictionary<StateType, IState> states = new Dictionary<StateType, IState>();
//我们需要的参数
public Parameter parameter;

protected virtual void Awake()
{
//注册方法
}

protected virtual void OnEnable()
{
parameter.animator = transform.GetComponent<Animator>(); // 获取角色上的动画控制器组件
TransformState(StateType.idle); // 初始状态为Idle
currentIState.Enter();
}

//添加状态
public virtual void AddState(StateType state,IState istate)
{
if (states.ContainsKey(state))
{
Debug.Log("请勿重复添加状态");
return;
}
states.Add(state, istate);
}
//转换状态
public virtual void TransformState(StateType state)
{
if (!states.ContainsKey(state))
{
Debug.Log("没有找到此状态");
return;
}
if (currentIState != null)
{
currentIState.Exit();
}
currentIState = states[state];
currentIState.Enter();
}
public void Update()
{
parameter.animatorStateInfo = parameter.animator.GetCurrentAnimatorStateInfo(0);// 获取当前动画状态信息
currentIState.Update();
}
}

二、编写状态

在上面的FSM.cs脚本,我声明了四个状态,因此写四个脚本:
先是Idle.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
using UnityEngine;

public class Idle : IState
{
public Idle(FSM fsm)
{
this.fsm = fsm;
this.parameter = fsm.parameter;
}
public override void Enter()
{
parameter.animator.Play("Idle");
}

public override void Exit()
{

}

public override void Update()
{
if(Vector3.Distance(fsm.transform.position,parameter.targetObj.transform.position) < 20f)
{
fsm.TransformState(StateType.stand);
}
}
}

再是stand.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
using UnityEngine;

public class Stand : IState
{
float time = 5.0f;
public Stand(FSM fsm)
{
this.fsm = fsm;
this.parameter = fsm.parameter;
}
public override void Enter()
{
int x = Random.Range(1, 3);
if(x == 1)
{
parameter.animator.CrossFade("Walk_Left", 0.2f);
}
else
{
parameter.animator.CrossFade("Walk_Right", 0.2f);
}
}

public override void Exit()
{
time = 5.0f;
}

public override void Update()
{
fsm.transform.rotation = Quaternion.LookRotation(parameter.targetObj.transform.position - fsm.transform.position);
time -= Time.deltaTime;
if (time < 0f)
{
fsm.TransformState(StateType.run);
}
}
}

接着是Run.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
public class Run : IState
{
public Run(FSM fsm)
{
this.fsm = fsm;
this.parameter = fsm.parameter;
}

public override void Enter()
{
parameter.animator.CrossFade("Run", 0.2f);
}

public override void Exit()
{

}

public override void Update()
{
parameter.nav.SetDestination(parameter.targetObj.transform.position);
if (Vector3.Distance(fsm.transform.position, parameter.targetObj.transform.position) < 3f)
{
fsm.TransformState(StateType.attack);
}
}
}

最后是Attack.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
using UnityEngine;

public class Attack : IState
{
float time = 5.0f;
public Attack(FSM fsm)
{
this.fsm = fsm;
this.parameter = fsm.parameter;
}
public override void Enter()
{
parameter.animator.CrossFade("Attack", 0.2f);
}

public override void Exit()
{
time = 5.0f;
}

public override void Update()
{
if (Vector3.Distance(fsm.transform.position, parameter.targetObj.transform.position) > 3f && Vector3.Distance(fsm.transform.position, parameter.targetObj.transform.position) < 20f)
{
fsm.TransformState(StateType.run);
}

if (Vector3.Distance(fsm.transform.position, parameter.targetObj.transform.position) > 20f)
{
fsm.TransformState(StateType.idle);
}
}
}

三、状态机的使用

每一个怪物对象、都给它单独写一个脚本作为状态机控制器,我这里拿的是yousa的模型,于是新建一个yousa.cs继承FSM.cs

1
2
3
4
5
6
7
8
9
10
11
12
using UnityEngine;

public class yousa : FSM
{
protected override void Awake()
{
states.Add(StateType.idle,new Idle(this));
states.Add(StateType.stand, new Stand(this));
states.Add(StateType.run, new Run(this));
states.Add(StateType.attack, new Attack(this));
}
}

在Awake方法里注册状态就好。把这个脚本和要用到的组件挂载到怪物身上。
图片

后续维护

后续的维护并不困难,假如我们现在又有新状态Skill了,就写一个Skill.cs的脚本,仿照之前几种状态的脚本去写,然后在FSM.cs最上边的枚举,加上这个状态,再在yousa.cs注册一下新状态就好了。