当前位置:网站首页>状态模式 - Unity(有限状态机)
状态模式 - Unity(有限状态机)
2022-07-07 15:38:00 【有趣就行】
状态模式(有限状态机)
状态模式是一种对象型模式,他将复杂的逻辑判断提取到不同状态对象中,允许状态对象在其内部状态发生改变时,改变其行为。状态的改变和各个状态的行为是状态模式的核心。
这里模式就需要谈到一个游戏中经常使用的状态模式的形式。它就是有限状态机,有限状态机是作为对象不同状态的管理而使用的(游戏AI也经常使用有限状态机)。其主要思想是程序在任意时刻仅可处在有限的几个状态中。在任意状态中,程序行为将随状态的改变,发生改变。而状态和状态之间的切换都是预先设计好的。
预先定义的状态切换规则称为转移。
结构
说明
- 状态接口 - 定义状态的行为和状态切换
- 具体状态 - 实现状态的行为和状态切换
- 系统 - 负责状态的切换和状态的实际调用以及状态的管理。
实现(有限状态机)
这里将实现一个有限状态机,状态机分为四个部分
- 状态枚举
- 状态接口及状态类
- 条件接口及条件类
- 状态系统
我们使用了泛型来增强有限状态机的复用性,使用工厂来创建状态机(避免复杂的创建过程,停留在调用端来污染代码)
为了方便考虑,我只枚举了 Idle(闲置)和 Chase(追击)两种状态
- 第一部分
状态枚举
public enum StateId
{
Idle,
Chase,
}
- 第二部分
状态接口
public interface IState<T>
where T : Enum
{
//获得对应状态类的Id
T Id {
get; }
//状态进入前,调用此函数
void OnEnterState();
//状态时,调用此函数
void OnUpdateState();
//状态退出前,调用此函数
void OnExitState();
//状态转换函数,通过此函数判断是否转移状态
bool TransitionState(out T id);
}
敌人抽象状态
public abstract class EnemyState : IState<StateId>
{
private readonly StateId _id;
private readonly ITransitionState<StateId> _transitionState;
protected EnemyState(StateId id, ITransitionState<StateId> transitionState)
{
_id = id;
_transitionState = transitionState;
}
public StateId Id => _id;
public virtual void OnEnterState() {
}
public abstract void OnUpdateState();
public virtual void OnExitState() {
}
public bool TransitionState(out StateId id) => _transitionState.Transition(out id);
}
之所以多出来这个抽象类,是为了将状态条件逻辑和状态行为逻辑分开,使得子类无需关注状态转移,只关注实现即可。而状态的转移实现转交给条件类完成(实现状态判断和状态行为实现的分离)。
具体状态类(Idle,Chase)
public class EnemyIdleState : EnemyState
{
public EnemyIdleState(ITransitionState<StateId> transitionState) :
base(StateId.Idle, transitionState)
{
}
public override void OnUpdateState()
{
}
}
public class EnemyChaseState : EnemyState
{
private float _chaseSpeed;
private float _chaseRange;
private GameObject _go;
private GameObject _chaseTarget;
//物理缓存
private Collider[] _colliders = new Collider[1];
public EnemyChaseState(float chaseSpeed, float chaseRange, GameObject go, ITransitionState<StateId> transitionState) :
base(StateId.Chase, transitionState)
{
_go = go;
_chaseSpeed = chaseSpeed;
_chaseRange = chaseRange;
}
public override void OnEnterState()
{
_chaseTarget = null;
int num = Physics.OverlapSphereNonAlloc(_go.transform.position, _chaseRange,
_colliders, 1 << LayerMask.NameToLayer("Player"));
if (num != 0) _chaseTarget = _colliders[0].gameObject;
}
public override void OnUpdateState()
{
//移动
var position = _go.transform.position;
position += _chaseSpeed * Time.deltaTime * (_chaseTarget.transform.position - position).normalized;
_go.transform.position = position;
//旋转
_go.transform.LookAt(_chaseTarget.transform);
}
public override void OnExitState()
{
_chaseTarget = null;
}
}
- 第三部分
条件接口
public interface ITransitionState<T>
where T : Enum
{
bool Transition(out T id);
}
具体条件接口(IdleTransition,Chase Transition)
public class EnemyIdleStateTransition : ITransitionState<StateId>
{
//自身游戏对象
private GameObject _go;
//侦察范围
private float _scoutingRange;
//侦察范围间隔(每帧调用,不利于程序性能)
private readonly float _scoutingTime = 0.2f;
private float _currentTime;
public EnemyIdleStateTransition(GameObject go, float scoutingRange)
{
_scoutingRange = scoutingRange;
_go = go;
}
public bool Transition(out StateId id)
{
_currentTime += Time.deltaTime;
if (_currentTime >= _scoutingTime)
{
_currentTime = 0f;
if (Physics.CheckSphere(_go.transform.position, _scoutingRange, 1 << LayerMask.NameToLayer("Player")))
{
id = StateId.Chase;
return true;
}
}
id = StateId.Idle;
return false;
}
}
public class EnemyChaseStateTransition : ITransitionState<StateId>
{
//脱离追击的距离
private float _outChaseDistance;
//自身游戏对象
private GameObject _go;
//脱离范围间隔(每帧调用不利于程序性能)
private readonly float _outChaseTime = 0.2f;
private float _currentTime;
public EnemyChaseStateTransition(GameObject go, float outChaseDistance)
{
_outChaseDistance = outChaseDistance;
_go = go;
}
public bool Transition(out StateId id)
{
_currentTime += Time.deltaTime;
if (_currentTime >= _outChaseTime)
{
_currentTime = 0f;
if (!Physics.CheckSphere(_go.transform.position, _outChaseDistance, 1 << LayerMask.NameToLayer("Player")))
{
id = StateId.Idle;
return true;
}
}
id = StateId.Chase;
return false;
}
}
- 第四部分
有限状态机系统类
public class FsmSystem<T>
where T : Enum
{
private Dictionary<T, IState<T>> _stateDic;
private T _currentStateId;
private IState<T> _currentState;
public T Id => _currentStateId;
public FsmSystem()
{
_stateDic = new Dictionary<T, IState<T>>();
}
public void Add(IState<T> state)
{
if (state == null) return;
if (_stateDic.ContainsKey(state.Id)) return;
_stateDic.Add(state.Id, state);
}
public void Remove(T id)
{
if (!_stateDic.ContainsKey(id)) return;
_stateDic.Remove(id);
}
public bool Enable(T id)
{
if (!_stateDic.ContainsKey(id)) return false;
_currentStateId = id;
_currentState = _stateDic[id];
_currentState.OnEnterState();
return true;
}
public void Update()
{
if (_currentState.TransitionState(out T id))
TransferState(id);
_currentState.OnUpdateState();
}
//转移状态函数
private void TransferState(T id)
{
if (!_stateDic.ContainsKey(id)) return;
_currentState.OnExitState();
_currentState = _stateDic[id];
_currentStateId = id;
_currentState.OnEnterState();
}
}
工厂类
public class FsmFactory
{
public static FsmSystem<StateId> CreateEnemyFsm(GameObject go, float chaseRange, float chaseSpeed, float outChaseRange)
{
var fsm = new FsmSystem<StateId>();
//创建条件,并添加条件所需对应参数
var idleStateTransition = new EnemyIdleStateTransition(go, chaseRange);
var chaseStateTransition = new EnemyChaseStateTransition(go, outChaseRange);
//创建状态,并添加状态所需参数,而且将条件与状态绑定
var idleState = new EnemyIdleState(idleStateTransition);
var chaseState = new EnemyChaseState(chaseSpeed, chaseRange, go, chaseStateTransition);
fsm.Add(idleState);
fsm.Add(chaseState);
fsm.Enable(StateId.Idle);
return fsm;
}
}
调用端
public class StateExample : MonoBehaviour
{
//追击速度
[SerializeField] private float _chaseSpeed = 3.0f;
//追击范围
[SerializeField] private float _chaseRange = 4.0f;
//脱离追击距离
[SerializeField] private float _outChaseRange = 5.0f;
//此时状态
[SerializeField] private StateId _stateId;
private FsmSystem<StateId> _system;
private void Awake()
{
_system = FsmFactory.CreateEnemyFsm(gameObject, _chaseRange, _chaseSpeed, _outChaseRange);
}
private void Update()
{
_system.Update();
_stateId = _system.Id;
}
}
将玩家对象设置为 Player 层
效果图
由于不会作动图只能将就这看吧,代码没有问题,效果也还不错。
应用场景
- 游戏敌人AI
- 需要根据不同状态进行改变,有不同行为。
- 如果某个类需要根据成员变量的当前值改变自身行为,需要大量的判断条件时,可以使用状态模式。
优缺点
优点
- 将状态分离,单一职责原则
- 简化条件的判断
缺点
- 实现过于繁琐
- 类数量过多,容易造成系统复杂
与其他模式的关系
- 状态模式被视作策略模式的扩展,状态模式本身和策略模式是有区别,策略类之间是独立的,互不干涉。而状态之间需要进行切换,状态与状态之间是依赖的。但本质都是基于组合的机制。
- 桥接模式、 状态模式的接口非常相似。 桥接模式关注在实现化和抽象化上,不同实现化互不关联,实现各自业务逻辑,抽象化对象将其组合。本质两种都是基于组合机制实现的。即将工作委派给对象。
- 状态机的创建可以由工厂模式来完成。
- 状态机可以使用桥接模式来分开状态转移和状态实现。
边栏推荐
- LeetCode 152. 乘积最大子数组 每日一题
- LeetCode 1626. 无矛盾的最佳球队 每日一题
- 【Seaborn】组合图表:PairPlot和JointPlot
- 直接上干货,100%好评
- Smart logistics platform: make overseas warehouses smarter
- LeetCode 1696. Jumping game VI daily question
- LeetCode 1186. 删除一次得到子数组最大和 每日一题
- 自定义View必备知识,Android研发岗必问30+道高级面试题
- LeetCode 1626. The best team without contradiction
- LeetCode 213. 打家劫舍 II 每日一题
猜你喜欢
Arduino 控制的双足机器人
SlashData开发者工具榜首等你而定!!!
skimage学习(3)——Gamma 和 log对比度调整、直方图均衡、为灰度图像着色
【Seaborn】组合图表:FacetGrid、JointGrid、PairGrid
PLC:自动纠正数据集噪声,来洗洗数据集吧 | ICLR 2021 Spotlight
How to add aplayer music player in blog
赋能智慧电力建设 | 麒麟信安高可用集群管理系统,保障用户关键业务连续性
QT picture background color pixel processing method
如何选择合适的自动化测试工具?
QML初学
随机推荐
MySQL usage notes 1
Flash build API service
Repair method of firewall system crash and file loss, material cost 0 yuan
LeetCode 1155. N ways to roll dice one question per day
QML beginner
LeetCode 312. Poke balloon daily
Flask build API service SQL configuration file
The computer cannot add a domain, and the Ping domain name is displayed as the public IP. What is the problem? How to solve it?
Process from creation to encapsulation of custom controls in QT to toolbar (I): creation of custom controls
A tour of gRPC:03 - proto序列化/反序列化
Flask搭建api服务-生成API文档
DNS series (I): why does the updated DNS record not take effect?
The server is completely broken and cannot be repaired. How to use backup to restore it into a virtual machine without damage?
第二十四届中国科协湖南组委会调研课题组一行莅临麒麟信安调研考察
麒麟信安操作系统衍生产品解决方案 | 存储多路径管理系统,有效提高数据传输可靠性
赋能智慧电力建设 | 麒麟信安高可用集群管理系统,保障用户关键业务连续性
LeetCode 1186. 删除一次得到子数组最大和 每日一题
LeetCode 1981. 最小化目标值与所选元素的差 每日一题
国内首创!Todesk将RTC技术融入远程桌面,画质更清晰操作更流畅
浅谈 Apache Doris FE 处理查询 SQL 源码解析