美文网首页
Unity中,角色状态控制

Unity中,角色状态控制

作者: 全新的饭 | 来源:发表于2023-10-29 10:24 被阅读0次

说明

角色根据条件在各状态之间不断切换。
限制角色任意时刻只能处于一种状态。

思路

用1个抽象类RoleState表示状态

实际的各具体状态类需实现该类
子类需明确指定自己对应的类型枚举:实现1个只读属性

    public abstract RoleStates State { get; }
    public override RoleStates State => RoleStates.A;

提供2个虚方法:进入和退出状态。

    public virtual void EnterState(EnterRoleStateInfo info)
    {
        // 若左侧为null,则将右侧赋给左侧
        info ??= new EnterRoleStateInfo();
        info.State = State;
        EnterStateEvent?.Invoke(info);
    }
    public virtual void ExitState(ExitRoleStateInfo info)
    {
        info ??= new ExitRoleStateInfo();
        info.State = State;
        ExitStateEvent?.Invoke(info);
    }

public class EnterRoleStateInfo
{
    public RoleStates State;
}
public class ExitRoleStateInfo
{
    public RoleStates State;
    // 用途:若该值为true,则立即执行:自动切换状态
    public bool IsArriveEnd = false;
}

子类需实现这2个方法,并在实现的最后调用基类的该方法(为了发出相应事件,传递相应信息)。

每个子类的进入和退出状态的方法实际所需的参数类型应是对应基类的子类,供各子类使用各自专用的参数作为状态内部运行时所需的信息。

    public override void EnterState(EnterRoleStateInfo info)
    {
        // 如果没有专用参数,则可直接进入状态(下同)
        if (info is EnterRoleAStateInfo myInfo)
        {
            _roleStateACoroutine = RoleAStateCoroutine();
            MonoSys.Instance.StartCoroutine(_roleStateACoroutine);
            // 需调用基类的方法以触发相应事件
            base.EnterState(info);
        }
        else
        {
            Debug.Log($"{nameof(Role)}进入{nameof(RoleAState)}状态失败:因为进入状态时传入的info的类型不正确!");
        }
    }
    public override void ExitState(ExitRoleStateInfo info)
    {
        if (info is ExitRoleAStateInfo myInfo)
        {
            StopCoroutine();
            base.ExitState(info);
        }
        else
        {
            Debug.Log($"{nameof(Role)}退出{nameof(RoleAState)}状态失败:因为退出状态时传入的info的类型不正确!");
        }
    }
public class EnterRoleAStateInfo : EnterRoleStateInfo
{
    // 本状态的一些专用参数
}
public class ExitRoleAStateInfo : ExitRoleStateInfo
{
    // 本状态的一些专用参数
}
状态切换类RoleStateCtr:控制状态之间的切换

创建一个字典保存所有状态

    void InitStateDict()
    {
        _stateDict = new Dictionary<RoleStates, RoleState>
        {
            {RoleStates.A, new RoleAState(_role)},
            {RoleStates.B, new RoleAState(_role)},
            {RoleStates.C, new RoleAState(_role)}
        };
    }

不断地自行检测条件并切换至相应状态

    public void StartAutoSwitchStateCheck()
    {
        _autoSwitchStateCheckCoroutine = AutoSwitchStateCheckCoroutine();
        MonoSys.Instance.StartCoroutine(_autoSwitchStateCheckCoroutine);

        IEnumerator AutoSwitchStateCheckCoroutine()
        {
            var curWaitForSeconds = new WaitForSeconds(AutoSwitchStateCheckIntervalSeconds);
            while (true)
            {
                AutoSwitchStateCheck();
                yield return curWaitForSeconds;
            }
        }
    }

可提供方法供外部调用直接切换至指定状态

实现

角色状态
RoleState.cs

using System;
using UnityEngine.Animations;

public abstract class RoleState
{
    protected Role Role;
    public abstract RoleStates State { get; }

    public RoleState(Role role)
    {
        Role = role;
    }
    public virtual void MyDestroy()
    {
        Role = null;
    }

    public event Action<EnterRoleStateInfo> EnterStateEvent;
    public event Action<ExitRoleStateInfo> ExitStateEvent;
    public virtual void EnterState(EnterRoleStateInfo info)
    {
        // 若左侧为null,则将右侧赋给左侧
        info ??= new EnterRoleStateInfo();
        info.State = State;
        EnterStateEvent?.Invoke(info);
    }
    public virtual void ExitState(ExitRoleStateInfo info)
    {
        info ??= new ExitRoleStateInfo();
        info.State = State;
        ExitStateEvent?.Invoke(info);
    }
}

public enum RoleStates
{
    None,
    A,
    B,
    C
}
public class EnterRoleStateInfo
{
    public RoleStates State;
}
public class ExitRoleStateInfo
{
    public RoleStates State;
    // 用途:若该值为true,则立即执行:自动切换状态
    public bool IsArriveEnd = false;
}

具体实际的角色状态
RoleAState.cs

using System.Collections;
using UnityEngine;

public class RoleAState : RoleState
{
    public override RoleStates State => RoleStates.A;
    public RoleAState(Role role) : base(role)
    {
    }
    public override void MyDestroy()
    {
        StopCoroutine();
        base.MyDestroy();
    }

    private void StopCoroutine()
    {
        if (_roleStateACoroutine != null)
        {
            MonoSys.Instance.StopCoroutine(_roleStateACoroutine);
            _roleStateACoroutine = null;
        }
    }


    private IEnumerator _roleStateACoroutine;
    public override void EnterState(EnterRoleStateInfo info)
    {
        // 如果没有专用参数,则可直接进入状态(下同)
        if (info is EnterRoleAStateInfo myInfo)
        {
            _roleStateACoroutine = RoleAStateCoroutine();
            MonoSys.Instance.StartCoroutine(_roleStateACoroutine);
            // 需调用基类的方法以触发相应事件
            base.EnterState(info);
        }
        else
        {
            Debug.Log($"{nameof(Role)}进入{nameof(RoleAState)}状态失败:因为进入状态时传入的info的类型不正确!");
        }



        IEnumerator RoleAStateCoroutine()
        {
            // todo:本状态具体做的内容,通常需调用Role的一些方法
            yield return null;

            /* 如果在本状态运行过程中,可以自行退出本状态(而非外部处理退出,需将ExitState参数的IsArriveEnd设置为true)
               写法如下所示。
               注意:并非所有状态都需要自行退出!
            */
            ExitState(new ExitRoleAStateInfo() { IsArriveEnd = true });

        }
    }
    public override void ExitState(ExitRoleStateInfo info)
    {
        if (info is ExitRoleAStateInfo myInfo)
        {
            StopCoroutine();
            base.ExitState(info);
        }
        else
        {
            Debug.Log($"{nameof(Role)}退出{nameof(RoleAState)}状态失败:因为退出状态时传入的info的类型不正确!");
        }
    }
}

public class EnterRoleAStateInfo : EnterRoleStateInfo
{
    // 本状态的一些专用参数
}
public class ExitRoleAStateInfo : ExitRoleStateInfo
{
    // 本状态的一些专用参数
}

状态切换
RoleStateCtr.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

public class RoleStateCtr
{
    private Role _role;
    Dictionary<RoleStates, RoleState> _stateDict;

    // 每隔多久自动执行一次自动切换状态检测 -> 自动根据当前情况切换状态
    private const float AutoSwitchStateCheckIntervalSeconds = 0.2f;

    private RoleState _curState;
    private RoleStates CurState => _curState == null ? RoleStates.None : _curState.State;

    public event Action<EnterRoleStateInfo> EnterStateEvent;
    public event Action<ExitRoleStateInfo> ExitStateEvent;

    public RoleStateCtr(Role role)
    {
        _role = role;
        InitStateDict();
        RegisterStateEvent();

        void InitStateDict()
        {
            _stateDict = new Dictionary<RoleStates, RoleState>
            {
                {RoleStates.A, new RoleAState(_role)},
                {RoleStates.B, new RoleAState(_role)},
                {RoleStates.C, new RoleAState(_role)}
            };
        }

        void RegisterStateEvent()
        {
            foreach (var state in _stateDict.Values)
            {
                state.EnterStateEvent += OnEnterState;
                state.ExitStateEvent += OnExitState;
            }
        }
    }

    public void MyDestroy()
    {
        _curState = null;
        StopAutoSwitchStateCheck();
        UnregisterStateEvent();
        DestroyStateDict();
        _role = null;

        void DestroyStateDict()
        {
            var catStates = _stateDict.Values.ToArray();
            for (int i = 0; i < catStates.Length; i++)
            {
                catStates[i].MyDestroy();
            }
            _stateDict.Clear();
            _stateDict = null;
        }
        void UnregisterStateEvent()
        {
            foreach (var state in _stateDict.Values)
            {
                state.EnterStateEvent -= OnEnterState;
                state.ExitStateEvent -= OnExitState;
            }
        }
    }

    #region 不断地自行检测条件并切换至相应状态
    private IEnumerator _autoSwitchStateCheckCoroutine;
    public void StartAutoSwitchStateCheck()
    {
        _autoSwitchStateCheckCoroutine = AutoSwitchStateCheckCoroutine();
        MonoSys.Instance.StartCoroutine(_autoSwitchStateCheckCoroutine);

        IEnumerator AutoSwitchStateCheckCoroutine()
        {
            var curWaitForSeconds = new WaitForSeconds(AutoSwitchStateCheckIntervalSeconds);
            while (true)
            {
                AutoSwitchStateCheck();
                yield return curWaitForSeconds;
            }
        }
    }
    private void StopAutoSwitchStateCheck()
    {
        if (_autoSwitchStateCheckCoroutine != null)
        {
            MonoSys.Instance.StopCoroutine(_autoSwitchStateCheckCoroutine);
            _autoSwitchStateCheckCoroutine = null;
        }
    }
    // todo:根据条件,进入相应状态(EnterState)
    private void AutoSwitchStateCheck()
    {
    }
    #endregion

    // forceEnter为true时,允许重复进入同一状态
    private bool EnterState(RoleStates state, EnterRoleStateInfo info, bool forceEnter = false)
    {
        bool ret = false;
        if (CurState != state || forceEnter)
        {
            ret = true;
            _curState?.ExitState(null);
            _stateDict[state].EnterState(info);
        }
        return ret;
    }

    private void OnEnterState(EnterRoleStateInfo info)
    {
        // 真正进入该状态后,才设置curState
        _curState = _stateDict[info.State];
        EnterStateEvent?.Invoke(info);
    }
    private void OnExitState(ExitRoleStateInfo info)
    {
        // 真正退出该状态后,才移除curState
        _curState = null;
        ExitStateEvent?.Invoke(info);
        // 当状态是自然结束(而非被其他情况打断)时,重新自动选择状态。
        if (info.IsArriveEnd)
        {
            AutoSwitchStateCheck();
        }
    }
}

角色
Role.cs

using System;
using UnityEngine;

public class Role : MonoBehaviour
{
    private RoleStateCtr _stateCtr;

    public event Action<EnterRoleStateInfo> EnterStateEvent;
    public event Action<ExitRoleStateInfo> ExitStateEvent;

    public void Init()
    {
        InitStateCtr();

        void InitStateCtr()
        {
            _stateCtr = new RoleStateCtr(this);
            _stateCtr.EnterStateEvent += OnEnterState;
            _stateCtr.ExitStateEvent += OnExitState;
            _stateCtr.StartAutoSwitchStateCheck();
        }
    }

    public void MyDestroy()
    {
        DestroyStateCtr();

        void DestroyStateCtr()
        {
            _stateCtr.EnterStateEvent -= OnEnterState;
            _stateCtr.ExitStateEvent -= OnExitState;
            _stateCtr.MyDestroy();
            _stateCtr = null;
        }
    }

    private void OnEnterState(EnterRoleStateInfo info)
    {
        EnterStateEvent?.Invoke(info);
    }
    private void OnExitState(ExitRoleStateInfo info)
    {
        ExitStateEvent?.Invoke(info);
    }
}

相关文章

网友评论

      本文标题:Unity中,角色状态控制

      本文链接:https://www.haomeiwen.com/subject/rsqvidtx.html