状态模式 – Unity(有限状态机)

状态模式(有限状态机)

状态模式是一种对象型模式,他将复杂的逻辑判断提取到不同状态对象中,允许状态对象在其内部状态发生改变时,改变其行为。状态的改变和各个状态的行为是状态模式的核心

这里模式就需要谈到一个游戏中经常使用的状态模式的形式。它就是有限状态机,有限状态机是作为对象不同状态的管理而使用的(游戏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
  • 需要根据不同状态进行改变,有不同行为。
  • 如果某个类需要根据成员变量的当前值改变自身行为,需要大量的判断条件时,可以使用状态模式。

优缺点

优点

  • 将状态分离,单一职责原则
  • 简化条件的判断

缺点

  • 实现过于繁琐
  • 类数量过多,容易造成系统复杂

与其他模式的关系

  • 状态模式被视作策略模式的扩展,状态模式本身和策略模式是有区别,策略类之间是独立的,互不干涉。而状态之间需要进行切换,状态与状态之间是依赖的。但本质都是基于组合的机制。
  • 桥接模式状态模式的接口非常相似。 桥接模式关注在实现化和抽象化上,不同实现化互不关联,实现各自业务逻辑,抽象化对象将其组合。本质两种都是基于组合机制实现的。即将工作委派给对象。
  • 状态机的创建可以由工厂模式来完成。
  • 状态机可以使用桥接模式来分开状态转移和状态实现。
本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇

)">
下一篇>>