Unity笔记-状态机
Unity笔记-状态机
Wang ChenHan有限状态机(FSM)
有限状态:状态持续时长有限(例:人物有很多状态,如跑步,攻击,游泳。这些状态需进行切换,不可能无限进行一个状态,如人物不可能一直保持攻击状态,
不进行跑步,游泳)
切换这些状态的东西叫做状态机。
举例:下面是一个角色相关脚本
1 | void Update(){ |
该方法优点:简单
该方法缺点:繁琐,if过多,重复元素多,状态过多时可读性降低,代码简洁性低
状态机把这些状态独立出来,这时状态机只做切换,不用知道状态实现过程
这是一种面向对象思想
简单有限状态机:将每个状态独立成一个方法
优点:简单,体现面向对象思想,封装成方法使用便捷
缺点:大型使用场景依然不够方便
普通有限状态机:将每个状态独立成一个类,继承一个状态基类
优点:逻辑清晰有条理,更加简单
缺点:代码量提升
Unity
在工程项目中导入 Character Pack:Free Sample包(去Unity资源商城下,免费)
在Assets\Supercyan Character Pack Free Sample\Prefabs\Base\High Quality选择MaleFree1,创建一个平面,将模型放置到平面上。
在文件夹中找到common_people@idle(待机)common_people@run(跑步)common_people@wave(招手)再创建一个动画控制器(Player),将三个动画
拖进里面,再创建一个Bool参数IsRun。在待机和跑步中相互创建过渡,过渡条件是IsRun的值。将两个过渡的退出时间关掉。
创建一个Trigger参数Wave,将待机切换为挥手的过渡条件设置为Wave,并且将此过渡的退出时间和固定持续时间关闭(第一个和第三个参数)
代码
- 创建Script文件夹。
- 创建一个csharp脚本PlayerController
V1
不使用状态机,初始版本代码:挂载到Player身上,运行。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
40using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
// Start is called before the first frame update
//获取动画控制器
private Animator ani;
void Start()
{
ani = GetComponent<Animator>();
}
// Update is called once per frame
void Update()
{
//获取用户的水平和垂直方向的输入
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
//创建为一个方向向量
Vector3 dir = new Vector3(h,0,v);
//如果向量不为空
if(dir!=Vector3.zero){
//用户按下移动按键
//移动
transform.rotation = Quaternion.LookRotation(dir);
transform.Translate(Vector3.forward * 3 * Time.deltaTime);
//播放动画
ani.SetBool("IsRun",true);
}else{
//用户松开移动按键
ani.SetBool("IsRun",false);
}
//按下空格
if(Input.GetKeyDown(KeyCode.Space))
{
ani.SetTrigger("Wave");
} }
}
小人成功移动,除招手动画无异常,即可阅读下一模块。V2
在刚才的V1代码中,小人招手的时候可以进行移动,但不会实现移动动画。看起来很奇怪。这是因为代码是由上往下执行的。当挥手动作执行时也会
执行上面的移动。在以后代码中会改进这一问题。
创建PlayerControl2脚本
代码:解析一下:首先定义了一个枚举,定义了idle,run,wave三个成员(值分别是1,2,3)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
72using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum PlayerState{//枚举
idle,
run,
wave
}
public class PlayerContorl2 : MonoBehaviour
{
public Animator ani;
private PlayerState state = PlayerState.idle;
/// <summary>
/// Update is called every frame, if the MonoBehaviour is enabled.
/// </summary>
private void Update()
{
switch(state){
case PlayerState.idle:
idle();
break;
case PlayerState.run:
run();
break;
case PlayerState.wave:
wave();
break;
}
}
void idle(){
//获取用户的水平和垂直方向的输入
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
//创建为一个方向向量
Vector3 dir = new Vector3(h,0,v);
//如果向量不为空
if(dir==Vector3.zero){
ani.SetBool("IsRun",false);
}else{
state = PlayerState.run;//不管实现过程,只去调用
}
if(Input.GetKeyDown(KeyCode.Space)){
state = PlayerState.wave;
}
}
void run(){
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
//创建为一个方向向量
Vector3 dir = new Vector3(h,0,v);
//如果向量不为空
if(dir!=Vector3.zero){
//用户按下移动按键
//移动
transform.rotation = Quaternion.LookRotation(dir);
transform.Translate(Vector3.forward * 3 * Time.deltaTime);
//播放动画
ani.SetBool("IsRun",true);
}else{
state = PlayerState.idle;
}
}
void wave(){
//播放挥手动画
ani.SetTrigger("Wave");
if(!ani.GetCurrentAnimatorStateInfo(0).IsName("Wave")){
//如果没有播放挥手动画
state = PlayerState.idle;
}
}
}
然后就是定义三个方法,状态切换时每个状态不用像V1一样关注另一个状态,直接切换枚举值。
然后使用Switch来互相切换状态,达到状态机的作用。
在Wave方法里,使用了!ani.GetCurrentAnimatorStateInfo(0).IsName(“Wave”)判断当前动画是否播放完。V3
状态基类
代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public abstract class FSMState//状态基类
{
//当前状态ID
public int StateID;
//状态拥有者
public MonoBehaviour Mono;
//状态所属管理器
public FSMManager FsmManager;
public FSMState(int stateID,MonoBehaviour mono,FSMManager manager){
StateID = stateID;
Mono = mono;
FsmManager = manager;
}
//进入状态,会调用一次方法
public abstract void OnEnter();
//在状态中每帧调用
public abstract void OnUpdate();
}状态管理器基类
代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//状态机管理类
public class FSMManager//非单例 可以有很多个类型的状态机
{
//状态列表
public List<FSMState> StateList = new List<FSMState>();
public int CurrentIndex = -1;
//改变状态
public void ChangeState(int StateID){
CurrentIndex = StateID;
//执行一次该状态的进入方法
StateList[CurrentIndex].OnEnter();
}
//更新
public void Update()
{
if(CurrentIndex != -1){
StateList[CurrentIndex].OnUpdate();
}
}
}普通状态机
- 创建 PlayerControl3 脚本
- 创建 Idle Run Wave 脚本
Idle
代码: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
50using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Idle : FSMState
{
public enum PlayerState{//枚举
idle,
run,
wave
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
public override void OnEnter()
{
//播放站立动画
Mono.GetComponent<Animator>().SetBool("IsRun",false);
}
public override void OnUpdate()
{
//判断是否变为跑步
//获取用户的水平和垂直方向的输入
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
//创建为一个方向向量
Vector3 dir = new Vector3(h,0,v);
//如果向量不为空
if(dir!=Vector3.zero){
//切换跑步状态
FsmManager.ChangeState((int)PlayerState.run);
}
//监听是否挥手
if(Input.GetKeyDown(KeyCode.Space)){
FsmManager.ChangeState((int)PlayerState.wave);
}
}
public Idle(int stateID,MonoBehaviour mono,FSMManager manager) : base(stateID,mono,manager){
}
}Run
代码: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
45using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Run : FSMState
{
public enum PlayerState{//枚举
idle,
run,
wave
}
// Start is called before the first frame update
public Run(int stateID,MonoBehaviour mono,FSMManager manager) : base(stateID,mono,manager){
}
void Start()
{
}
// Update is called once per frame
void Update()
{
}
public override void OnEnter()
{
Mono.GetComponent<Animator>().SetBool("IsRun",true);
}
public override void OnUpdate(){
float h = Input.GetAxis("Horizontal");
float v = Input.GetAxis("Vertical");
//创建为一个方向向量
Vector3 dir = new Vector3(h,0,v);
//如果向量不为空
if(dir!=Vector3.zero){
//用户按下移动按键
//移动
Mono. transform.rotation = Quaternion.LookRotation(dir);
Mono. transform.Translate(Vector3.forward * 3 * Time.deltaTime);
}else{
FsmManager.ChangeState((int)PlayerState.idle);
}
}
}Wave
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
32using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Wave : FSMState
{
// Start is called before the first frame update
public Wave(int stateID,MonoBehaviour mono,FSMManager manager) : base(stateID,mono,manager){
}
void Start()
{
}
// Update is called once per frame
void Update()
{
}
public override void OnEnter()
{
Mono.GetComponent<Animator>().SetTrigger("Wave");
}
public override void OnUpdate()
{
if(!Mono.GetComponent<Animator>().GetCurrentAnimatorStateInfo(0).IsName("wave")){
//切换为Idle
FsmManager.ChangeState((int)PlayerState.idle);
}
}
}PlayerControl3
开始编辑V3状态机:然后将idle到wave的过渡时间设置为0,不然小人挥手时还是能动。(具体原因未知,知道的可以评论下)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
32using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerControl3 : MonoBehaviour
{
// Start is called before the first frame update
private FSMManager fsmManager;
void Start()
{
//实例化状态机管理器
fsmManager = new FSMManager();
//创建状态
Idle idle = new Idle(0,this,fsmManager);
Run run = new Run(1,this,fsmManager);
Wave wave = new Wave(2,this,fsmManager);
//状态注册
fsmManager.StateList.Add(idle);
fsmManager.StateList.Add(run);
fsmManager.StateList.Add(wave);
//给一个默认状态
fsmManager.ChangeState((int)PlayerState.idle);
}
// Update is called once per frame
void Update()
{
fsmManager.Update();//调用自己写的Update
}
}
挂载到player身上,运行。
评论
匿名评论隐私政策
✅ 你无需删除空行,直接评论以获取最佳展示效果