当前位置:网站首页>状态模式 - 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
- 需要根据不同状态进行改变,有不同行为。
- 如果某个类需要根据成员变量的当前值改变自身行为,需要大量的判断条件时,可以使用状态模式。
优缺点
优点
- 将状态分离,单一职责原则
- 简化条件的判断
缺点
- 实现过于繁琐
- 类数量过多,容易造成系统复杂
与其他模式的关系
- 状态模式被视作策略模式的扩展,状态模式本身和策略模式是有区别,策略类之间是独立的,互不干涉。而状态之间需要进行切换,状态与状态之间是依赖的。但本质都是基于组合的机制。
- 桥接模式、 状态模式的接口非常相似。 桥接模式关注在实现化和抽象化上,不同实现化互不关联,实现各自业务逻辑,抽象化对象将其组合。本质两种都是基于组合机制实现的。即将工作委派给对象。
- 状态机的创建可以由工厂模式来完成。
- 状态机可以使用桥接模式来分开状态转移和状态实现。
边栏推荐
- 《产品经理必读:五种经典的创新思维模型》的读后感
- Skimage learning (2) -- RGB to grayscale, RGB to HSV, histogram matching
- Seaborn data visualization
- Read PG in data warehouse in one article_ stat
- 如何在软件研发阶段落地安全实践
- MySQL implements the query of merging two fields into one field
- User defined view essential knowledge, Android R & D post must ask 30+ advanced interview questions
- Lowcode: four ways to help transportation companies enhance supply chain management
- 国内首创!Todesk将RTC技术融入远程桌面,画质更清晰操作更流畅
- 让保险更“保险”!麒麟信安一云多芯云桌面中标中国人寿, 助力金融保险信息技术创新发展
猜你喜欢
mysql官网下载:Linux的mysql8.x版本(图文详解)
Lex & yacc of Pisa proxy SQL parsing
Sator推出Web3游戏“Satorspace” ,并上线Huobi
Leetcode brush questions day49
QT中自定义控件的创建到封装到工具栏过程(一):自定义控件的创建
Skimage learning (2) -- RGB to grayscale, RGB to HSV, histogram matching
麒麟信安云平台全新升级!
Sator launched Web3 game "satorspace" and launched hoobi
让保险更“保险”!麒麟信安一云多芯云桌面中标中国人寿, 助力金融保险信息技术创新发展
直接上干货,100%好评
随机推荐
LeetCode 120. 三角形最小路径和 每日一题
LeetCode 1626. 无矛盾的最佳球队 每日一题
Rpcms method of obtaining articles under the specified classification
Skimage learning (3) -- adapt the gray filter to RGB images, separate colors by immunohistochemical staining, and filter the maximum value of the region
Repair method of firewall system crash and file loss, material cost 0 yuan
Module VI
LeetCode 312. 戳气球 每日一题
LeetCode 1654. The minimum number of jumps to get home one question per day
浅谈 Apache Doris FE 处理查询 SQL 源码解析
Sator推出Web3游戏“Satorspace” ,并上线Huobi
Master this promotion path and share interview materials
LeetCode 1696. 跳跃游戏 VI 每日一题
Smart logistics platform: make overseas warehouses smarter
[Seaborn] combination chart: facetgrid, jointgrid, pairgrid
DAPP defi NFT LP single and dual currency liquidity mining system development details and source code
【黄啊码】为什么我建议您选择go,而不选择php?
Skimage learning (1)
How to implement safety practice in software development stage
How to choose the appropriate automated testing tools?
Leetcode brush questions day49