当前位置:网站首页>状态模式 - 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
- 需要根据不同状态进行改变,有不同行为。
- 如果某个类需要根据成员变量的当前值改变自身行为,需要大量的判断条件时,可以使用状态模式。
优缺点
优点
- 将状态分离,单一职责原则
- 简化条件的判断
缺点
- 实现过于繁琐
- 类数量过多,容易造成系统复杂
与其他模式的关系
- 状态模式被视作策略模式的扩展,状态模式本身和策略模式是有区别,策略类之间是独立的,互不干涉。而状态之间需要进行切换,状态与状态之间是依赖的。但本质都是基于组合的机制。
- 桥接模式、 状态模式的接口非常相似。 桥接模式关注在实现化和抽象化上,不同实现化互不关联,实现各自业务逻辑,抽象化对象将其组合。本质两种都是基于组合机制实现的。即将工作委派给对象。
- 状态机的创建可以由工厂模式来完成。
- 状态机可以使用桥接模式来分开状态转移和状态实现。
边栏推荐
- Arduino 控制的双足机器人
- LeetCode 312. Poke balloon daily
- Flask搭建api服务-SQL配置文件
- How to choose the appropriate automated testing tools?
- skimage学习(1)
- mysql官网下载:Linux的mysql8.x版本(图文详解)
- With the latest Alibaba P7 technology system, mom doesn't have to worry about me looking for a job anymore
- Linux 安装mysql8.X超详细图文教程
- LeetCode 1049. 最后一块石头的重量 II 每日一题
- 第九届 蓝桥杯 决赛 交换次数
猜你喜欢
鲲鹏开发者峰会2022 | 麒麟信安携手鲲鹏共筑计算产业新生态
NeRF:DeepFake的最终替代者?
让保险更“保险”!麒麟信安一云多芯云桌面中标中国人寿, 助力金融保险信息技术创新发展
skimage学习(2)——RGB转灰度、RGB 转 HSV、直方图匹配
With the latest Alibaba P7 technology system, mom doesn't have to worry about me looking for a job anymore
Skimage learning (2) -- RGB to grayscale, RGB to HSV, histogram matching
The top of slashdata developer tool is up to you!!!
Shallow understanding Net core routing
科普达人丨一文弄懂什么是云计算?
QT 图片背景色像素处理法
随机推荐
QT中自定义控件的创建到封装到工具栏过程(一):自定义控件的创建
A tour of grpc:03 - proto serialization / deserialization
Jenkins发布uniapp开发的H5遇到的问题
Flask搭建api服务-SQL配置文件
LeetCode 120. Triangle minimum path and daily question
Flash build API Service - generate API documents
Rpcms method of obtaining articles under the specified classification
LeetCode 120. 三角形最小路径和 每日一题
Matplotlib绘制三维图形
Flash build API service
LeetCode 1626. The best team without contradiction
How to mount the original data disk without damage after the reinstallation of proxmox ve?
Flask build API service SQL configuration file
How to add aplayer music player in blog
Seaborn data visualization
Proxmox VE重装后,如何无损挂载原有的数据盘?
Notes on installing MySQL in centos7
Lex & yacc of Pisa proxy SQL parsing
LeetCode 152. 乘积最大子数组 每日一题
LeetCode 1654. 到家的最少跳跃次数 每日一题