Unity3D日常开发有限状态机的简单实现
Posted 恬静的小魔龙
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity3D日常开发有限状态机的简单实现相关的知识,希望对你有一定的参考价值。
推荐阅读
一、前言
Hello,大家好,我是☆恬静的小魔龙☆,正所谓学而不思则罔,思而不学则殆,最近项目开发中,人物的动作特别多,用状态机去切换感觉太麻烦,然后切换的效果也并不理想。
比如下面的状态机:
每次“走→站立→跑”,都一些卡顿,没有那么丝滑,所以就想学习一下FSM(有限状态机)。
二、有限状态机
什么是有限状态机:
如其名有限状态机,就是可以枚举出有限个状态,然后状态直接可以进行切换的状态机。总体来说,有限状态机就是在不同阶段呈现出不同的运行状态的系统,这些状态是有限的、不重叠的。这样的系统在某一时刻响应其状态中的一个状态。
为啥需要有限状态机:
在开发中,一个角色或者AI会有很多的状态,也会产生很多的动画,比如攻击、移动、巡逻、追击、逃跑、死完,这些动画状态会不停地切换,简单的方式就是使用Switch进行判断,然后每一个case去进行相应的状态处理和转换条件判断。
这种方式有点事简单方便,缺点就是不利于扩展,所以就出现了有限状态机去管理。
总结来说就是,为了解决游戏过于麻烦的状态转换
有限状态机与设计模式
细节依赖抽象,抽象不依赖细节,基于抽象编程,让框架先跑起来。
有限状态机参考的是状态设计模式的理念,允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类,类的行为是基于它的状态改变的。
有限状态机,类比于Switch就是将cast中的逻辑封装到各个State对象中了,这样做可以方便扩展,后期如果需要增加新状态,只需要继承基类,添加实现就好,不用修改原来的代码,这就是开闭原则。
源工程下载:
三、有限状态机的实现
3-1、有限状态机基类
首先,我们需要一个基类,让其他的State对象进行继承:
/// <summary>
/// 状态抽象类
/// </summary>
public abstract class FSMState
{
// 所属的状态机
protected FSMSystem m_FSM = null;
protected FSMState(FSMSystem fsm)
{
m_FSM = fsm;
}
/// <summary>
/// 进入状态
/// </summary>
public virtual void OnEnter() { }
/// <summary>
/// 状态中进行的动作
/// </summary>
public abstract void Action();
/// <summary>
/// 检测状态转换
/// </summary>
public abstract void Check();
/// <summary>
/// 退出状态
/// </summary>
public virtual void OnExit() { }
}
3-2、有限状态机系统
接着,需要一个有限状态机系统去管理控制这些状态机,包含添加状态、删除状态、改变状态、状态改变等。
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 有限状态机系统
/// </summary>
public class FSMSystem
{
/// <summary>
/// 状态机所属游戏物体
/// </summary>
public GameObject OwnerGo { get; private set; }
public Animation Ani { get; private set; }
// 用字典存储每个状态ID对应的状态
private Dictionary<StateID, FSMState> m_StateMap = new Dictionary<StateID, FSMState>();
public FSMSystem(GameObject ownerGo)
{
OwnerGo = ownerGo;
Ani = OwnerGo.GetComponent<Animation>();
}
/// <summary>
/// 当前状态ID
/// </summary>
public StateID CurrentStateID { get; private set; }
/// <summary>
/// 当前状态
/// </summary>
public FSMState CurrentState { get; private set; }
/// <summary>
/// 添加状态
/// </summary>
/// <param name="id">添加的状态ID</param>
/// <param name="state">对应的状态对象</param>
public void AddState(StateID id, FSMState state)
{
// 如果当前状态为空,就设置为默认状态
if (CurrentState == null)
{
CurrentStateID = id;
CurrentState = state;
}
if (m_StateMap.ContainsKey(id))
{
Debug.LogErrorFormat("状态ID:{0}已经存在,不能重复添加!", id);
return;
}
m_StateMap.Add(id, state);
}
/// <summary>
/// 移除状态
/// </summary>
/// <param name="id">要移除的状态ID</param>
public void RemoveState(StateID id)
{
if (!m_StateMap.ContainsKey(id))
{
Debug.LogWarningFormat("状态ID:{0}不存在,不需要移除", id);
return;
}
m_StateMap.Remove(id);
}
/// <summary>
/// 改变状态
/// </summary>
/// <param name="id">需要转换到的目标状态ID</param>
public void ChangeState(StateID id)
{
if (id == CurrentStateID) return;
if (!m_StateMap.ContainsKey(id))
{
Debug.LogErrorFormat("状态ID:{0}不存在!", id);
return;
}
if (CurrentState != null)
CurrentState.OnExit();
CurrentStateID = id;
CurrentState = m_StateMap[id];
CurrentState.OnEnter();
}
/// <summary>
/// 更新,在状态机持有者物体的Update中调用
/// </summary>
public void Update()
{
CurrentState.Action();
CurrentState.Check();
}
}
3-3、添加状态
这时候,就需要看一下我们的模型有多少个动画了,比如我这个模型:
共12个动画片段,但是我只需要里面的9个动画片段,所以先定义一个枚举,将所有用到的动画片段枚举出来:
/// <summary>
/// 状态ID
/// </summary>
public enum StateID
{
Stand,
Standwait,
Walk,
Jump1,
Run,
Jump2,
Down,
Take,
Work
}
接着就添加状态:
/// <summary>
/// 站立
/// </summary>
public class StandState : FSMState
{
private float m_Timer = 0f;
public StandState(FSMSystem fsm) : base(fsm)
{
Debug.Log("StandState");
}
public override void Action()
{
m_FSM.Ani.Play("Stand");
}
public override void Check()
{
m_Timer += Time.deltaTime;
// 如果旋转达到1秒
if (m_Timer > 0.25f)
{
// 转换到移动状态
m_FSM.ChangeState(StateID.Standwait);
}
}
public override void OnExit()
{
m_Timer = 0;
}
}
/// <summary>
/// 站立等待
/// </summary>
public class StandWaitState : FSMState
{
public StandWaitState(FSMSystem fsm) : base(fsm)
{
Debug.Log("StandWaitState");
}
public override void Action()
{
m_FSM.Ani.Play("Stand-wait");
}
public override void Check() { }
}
/// <summary>
/// 走
/// </summary>
public class WalkState : FSMState
{
public WalkState(FSMSystem fsm) : base(fsm)
{
Debug.Log("WalkState");
}
public override void Action()
{
m_FSM.Ani.Play("walk");
}
public override void Check() { }
}
/// <summary>
/// 小跳
/// </summary>
public class Jump1State : FSMState
{
private float m_Timer = 0f;
public Jump1State(FSMSystem fsm) : base(fsm)
{
Debug.Log("Jump1State");
}
public override void Action()
{
m_FSM.Ani.Play("jump1");
}
public override void Check()
{
m_Timer += Time.deltaTime;
// 如果旋转达到1秒
if (m_Timer > 0.6f)
{
// 转换到移动状态
m_FSM.ChangeState(StateID.Standwait);
}
}
public override void OnExit()
{
m_Timer = 0;
}
}
/// <summary>
/// 跑
/// </summary>
public class Run2State : FSMState
{
public Run2State(FSMSystem fsm) : base(fsm)
{
Debug.Log("Run2State");
}
public override void Action()
{
m_FSM.Ani.Play("run");
}
public override void Check() { }
}
/// <summary>
/// 大跳
/// </summary>
public class Jump2State : FSMState
{
private float m_Timer = 0f;
public Jump2State(FSMSystem fsm) : base(fsm)
{
Debug.Log("Jump2State");
}
public override void Action()
{
m_FSM.Ani.Play("jump2");
}
public override void Check()
{
m_Timer += Time.deltaTime;
// 如果旋转达到1秒
if (m_Timer > 0.75f)
{
// 转换到移动状态
m_FSM.ChangeState(StateID.Standwait);
}
}
public override void OnExit()
{
m_Timer = 0;
}
}
/// <summary>
/// 拿取
/// </summary>
public class TakeState : FSMState
{
public TakeState(FSMSystem fsm) : base(fsm)
{
Debug.Log("TakeState");
}
public override void OnEnter()
{
m_FSM.Ani.Play("take");
}
public override void Action() { }
public override void Check() { }
public override void OnExit() { }
}
/// <summary>
/// 工作
/// </summary>
public class WorkState : FSMState
{
public WorkState(FSMSystem fsm) : base(fsm)
{
Debug.Log("WorkState");
}
public override void OnEnter()
{
m_FSM.Ani.Play("work");
}
public override void Action() { }
public override void Check() { }
public override void OnExit() { }
}
/// <summary>
/// 蹲
/// </summary>
public class DownState : FSMState
{
public DownState(FSMSystem fsm) : base(fsm)
{
Debug.Log("DownState");
}
public override void OnEnter()
{
m_FSM.Ani.Play("Down");
}
public override void Action() { }
public override void Check() { }
public override void OnExit() { }
}
我为了方便,将有限状态机的基类、状态枚举、状态对象都写到一个脚本里面了,大家觉得不爽也可以分开来。
3-4、调用动画
新建一个Character类,用来调用动画:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Character : MonoBehaviour
{
// 有限状态机
private FSMSystem m_FSM = null;
private void Start()
{
// 实例化有限状态机
m_FSM = new FSMSystem(this.gameObject);
// 添加状态到有限状态机中
m_FSM.AddState(StateID.Stand, new StandState(m_FSM));
m_FSM.AddState(StateID.Standwait, new StandWaitState(m_FSM));
m_FSM.AddState(StateID.Walk, new WalkState(m_FSM));
m_FSM.AddState(StateID.Jump1, new Jump1State(m_FSM));
m_FSM.AddState(StateID.Run, new Run2State(m_FSM));
m_FSM.AddState(StateID.Jump2, new Jump2State(m_FSM));
m_FSM.AddState(StateID.Down, new DownState(m_FSM));
m_FSM.AddState(StateID.Take, new TakeState(m_FSM));
m_FSM.AddState(StateID.Work, new WorkState(m_FSM));
}
private void Update()
{
// 调用有限状态机中的Update方法
if (m_FSM != null)
m_FSM.Update();
if (Input.GetKey(KeyCode.Alpha1))
{
m_FSM.ChangeState(StateID.Stand);
}
if (Input.GetKey(KeyCode.Alpha2))
{
m_FSM.ChangeState(StateID.Standwait);
}
if (Input.GetKey(KeyCode.Alpha3))
{
m_FSM.ChangeState(StateID.Walk);
}
if (Input.GetKey(KeyCode.Alpha4))
{
m_FSM.ChangeState(StateID.Jump1);
}
if (Input.GetKey(KeyCode.Alpha5))
{
m_FSM.ChangeState(StateID.Run);
}
if (Input.GetKey(KeyCode.Alpha6))
{
m_FSM.ChangeState(StateID.Jump2);
}
if (Input.GetKey(KeyCode.Alpha7))
{
m_FSM.ChangeState(StateID.Down);
}
if (Input.GetKey(KeyCode.Alpha8))
{
m_FSM.ChangeState(StateID.Take);
}
if (Input.GetKey(KeyCode.Alpha9))
{
m_FSM.ChangeState(StateID.Work);
}
}
}
挂载到对象上:
效果图:
四、后言
这个例子没有太多炫技的代码,作为一个学习有限状态机的入门例子来看还是很不错的。
希望大家今天也有学习哦。
FSM有限状态机的实现