U3D客户端框架之商业项目中的 FSM 有限状态机 实现代码

一、有限状态机介绍

有限状态机(Finite State Machine, FSM),又称有限状态自动机,简称状态机,是指在有限个状态之间按照一定规律转换的逻辑状态。

状态机有 3 个组成部分:状态、事件、动作

状态:所有可能存在的状态。包括当前状态和条件满足后要迁移的状态。
事件:也称为转移条件,当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。
动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是* 必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。

FSM有限状态机在游戏中的作用主要是做场景的流程管理,进入场景状态后 加载资源初始化,更新状态时执行更新逻辑,离开场景状态时销毁场景资源,数据清理、角色动作状态切换,进入时播放动作,离开时播放下一个当作等。

二、有限状态机的设计

设计的时候考虑了可能同时存在多个状态机,所以把状态机单独的分成了一个类;状态机里面有具体的状态,负责具体状态的生命周期;状态机多了之后就需要有一个管理者去管理状态机的整个生命周期:创建、删除、释放内存;

主要分成了4个类文件:

1.状态机基类 FsmBase.cs

包含状态机所需要的通用数据,所有的基类其实都是设计模式中的模板方法模式,因为基类里面的所有方法对于子类来说,都是一样的,所以叫做模板方法。

FsmBase.cs 代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Myh
{
    /// <summary>
    /// 状态机基类
    /// </summary>
    public abstract class FsmBase
    {
        //状态机编号
        public int FsmId { private set; get; }

        //当前状态的类型
        public sbyte CurrStateType;

        public FsmBase(int fsmId)
        {
            FsmId = fsmId;
        }

        //关闭状态机
        public abstract void ShutDown();

    }
}

2.状态机类 Fsm.cs

状态机的具体类,继承了FsmBase类;内部实现了所有具体状态的管理、添加、更新、切换等。

Fsm.cs实现代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Myh
{
    //拥有者,拥有这个状态的类型
    public class Fsm<T> : FsmBase where T : class
    {
        //拥有者
        public T Owner { private set; get; }

        //当前状态
        private FsmState<T> m_CurrState;

        //状态字典
        private Dictionary<sbyte, FsmState<T>> m_dicState;

        //参数字典(??切换状态的时候还传参数??)
        private Dictionary<string, VariableBase> m_dicParam;



        /* 
         * 构造函数  需要以下参数
         * fsmId:状态机的id
         * owner:拥有者
         * states:状态机里的所有状态
         */
        public Fsm(int fsmId, T owner, FsmState<T>[] states) : base(fsmId)
        {
            m_dicState = new Dictionary<sbyte, FsmState<T>>();
            m_dicParam = new Dictionary<string, VariableBase>();

            Owner = owner;

            //把状态放入字典
            int len = states.Length;
            for (int i = 0; i < len; ++i)
            {
                FsmState<T> state = states[i];
                if (null != state)
                {
                    state.CurrFsm = this;
                }
                m_dicState[(sbyte)i] = state;
            }

            CurrStateType = -1;
        }

        //获取状态 by 状态类型
        public FsmState<T> GetState(sbyte stateType)
        {
            FsmState<T> state = null;
            m_dicState.TryGetValue(stateType,out state);
            return state;
        }

        //状态机更新
        public void OnUpdate()
        {
            if (null != m_CurrState)
            {
                m_CurrState.OnUpdate();
            }
        }

        //状态机切换 by newStateType
        public void ChangeState(sbyte newState)
        {
            //如果两个状态一致,不再进入
            if (CurrStateType == newState)
                return;

            //先让当前状态执行OnLeave流程,执行上一个状态离开的逻辑
            if (null != m_CurrState)
                m_CurrState.OnLeave();

            CurrStateType = newState;
            m_CurrState = m_dicState[CurrStateType];

            //然后再让新状态执行OnEnter流程,执行新状态的进入逻辑
            m_CurrState.OnEnter();
        }

        //设置参数值
        public void SetData<TData>(string key, TData value)
        {
            VariableBase itemBase = null;
            if (m_dicParam.TryGetValue(key, out itemBase))
            {
                //参数原来存在,更新值
                Variable<TData> item = itemBase as Variable<TData>;
                item.Value = value;
                //其实这里不重新写入也行,itemBase本来就是个class,拿到的本来就是引用
                //m_dicParam[key] = item;
            }
            else
            {
                //参数原来不存在,插入
                Variable<TData> item = new Variable<TData>();
                item.Value = value;
                m_dicParam[key] = item;
            }
        }

        //获取参数值
        public TData GetData<TData>(string key)
        {
            VariableBase itemBase = null;
            if (m_dicParam.TryGetValue(key, out itemBase))
            {
                Variable<TData> item = itemBase as Variable<TData>;
                if (null == item)
                    return default(TData);
                
                return item.Value;
            }
            
            //返回该结构的实例?
            return default(TData);
        }


        public override void ShutDown()
        {
            if (null != m_CurrState)
                m_CurrState.OnLeave();

            //循环遍历,执行所有状态的OnDestroy函数,执行销毁逻辑,销毁所有的状态
            foreach (KeyValuePair<sbyte, FsmState<T>> kvPair in m_dicState)
            {
                if (null != kvPair.Value)
                {
                    kvPair.Value.OnDestroy();
                }
            }

            m_dicState.Clear();
            m_dicParam.Clear();
        }
    }
}

3.状态机 状态抽象类 FsmState.cs

状态抽象类不可以直接使用,在实际的开发中使用一个具体的状态来继承 状态抽象类FsmState ,具体的例子可以看测试代码中的实现。abstract改成接口也行,但是后来感觉没啥必要,有些派生类未必一定需要实现所有虚函数,所以就写成了抽象类,4个事件都写成了虚函数。派生类实现不实现都可。

FsmState.cs 代码实现

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Myh
{
    //状态机的状态
    public abstract class FsmState<T> where T : class
    {
        //状态对应的状态机
        public Fsm<T> CurrFsm;

        //进入状态
        public virtual void OnEnter() { }

        //更新状态
        public virtual void OnUpdate() { }

        //离开状态
        public virtual void OnLeave() { }

        //状态机销毁时调用(销毁状态)
        public virtual void OnDestroy() { }

    }
}

4.状态机管理器 FsmManager.cs

状态机管理器内缓存下来来了所有的,状态机;主要负责创建、销毁,有点像是一个模型类,大部分的职责都是增删状态机。

FsmManager.cs代码实现

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Myh
{
    //状态机管理器
    public class FsmManager : ManagerBase, IDisposable
    {
        //状态机字典
        private Dictionary<int, FsmBase> m_dicFsm;

        //状态机的临时编号
        private int m_TemFsmId = 0;

        public FsmManager()
        {
            m_dicFsm = new Dictionary<int, FsmBase>();
        }

        //初始化
        public override void Init()
        {
            
        }

        #region Create
        /*
         * 创建状态机实例
         * T:拥有者类型
         * fsmId:状态机Id
         * owner:拥有者实例
         * states:状态数组
         */
        public Fsm<T> Create<T>(int fsmId, T owner, FsmState<T>[] states) where T : class 
        {
            Fsm<T> fsm = new Fsm<T>(fsmId, owner, states);
            m_dicFsm[fsmId] = fsm;
            return fsm;
        }

        public Fsm<T> Create<T>(T owner, FsmState<T>[] states) where T : class
        {
            return Create(++m_TemFsmId, owner, states);
        }

        #endregion


        #region 销毁状态机
        //销毁状态机
        public void DestroyFsm(int fsmId)
        {
            FsmBase fsm = null;
            if (m_dicFsm.TryGetValue(fsmId, out fsm))
            {
                fsm.ShutDown();
                m_dicFsm.Remove(fsmId);
            }
        }
        #endregion

        public void Dispose()
        {
            IEnumerator<KeyValuePair<int, FsmBase>> iter= m_dicFsm.GetEnumerator();
            for (; iter.MoveNext();)
            {
                iter.Current.Value.ShutDown();
            }
            m_dicFsm.Clear();
        }
    }
}

三、测试与总结

因为刚开始写,还没办法在实际的工程中去做测试,所以主要通过按下按键就切换状态,测了一下状态切换是否符合预期,结果很好,符合预期。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Myh;
using YouYou;
using UnityEngine;

public class FmsTest : ITest
{
    static private void PrintStateLog(int id, string strEvent)
    {
        Debug.Log("状态机:" + id + " 触发了:" + strEvent + " 事件");
    }

    #region 测试状态类

    enum EnumTestClassType
    {
        eTest1,
        eTest2,
        eTest3,
        eTest4,
    }
    
    class FsmTestState1 : FsmState<FmsTest>
    {
        public override void OnEnter()
        {
            base.OnEnter();
            PrintStateLog(1, "OnEnter");
        }
        
        public override void OnUpdate()
        {
            base.OnUpdate();
            PrintStateLog(1, "OnUpdate");
        }

        public override void OnLeave()
        {
            base.OnLeave();
            PrintStateLog(1, "OnLeave");
        }

        public override void OnDestroy()
        {
            base.OnDestroy();
            PrintStateLog(1, "OnDestroy");
        }
    }

    class FsmTestState2 : FsmState<FmsTest>
    {
        public override void OnEnter()
        {
            base.OnEnter();
            PrintStateLog(2, "OnEnter");
        }

        public override void OnUpdate()
        {
            base.OnUpdate();
            PrintStateLog(2, "OnUpdate");
        }

        public override void OnLeave()
        {
            base.OnLeave();
            PrintStateLog(2, "OnLeave");
        }

        public override void OnDestroy()
        {
            base.OnDestroy();
            PrintStateLog(2, "OnDestroy");
        }
    }


    class FsmTestState3 : FsmState<FmsTest>
    {
        public override void OnEnter()
        {
            base.OnEnter();
            PrintStateLog(3, "OnEnter");
        }

        public override void OnUpdate()
        {
            base.OnUpdate();
            PrintStateLog(3, "OnUpdate");
        }

        public override void OnLeave()
        {
            base.OnLeave();
            PrintStateLog(3, "OnLeave");
        }

        public override void OnDestroy()
        {
            base.OnDestroy();
            PrintStateLog(3, "OnDestroy");
        }
    }

    class FsmTestState4 : FsmState<FmsTest>
    {
        public override void OnEnter()
        {
            base.OnEnter();
            PrintStateLog(4, "OnEnter");
        }

        public override void OnUpdate()
        {
            base.OnUpdate();
            PrintStateLog(4, "OnUpdate");
        }

        public override void OnLeave()
        {
            base.OnLeave();
            PrintStateLog(4, "OnLeave");
        }

        public override void OnDestroy()
        {
            base.OnDestroy();
            PrintStateLog(4, "OnDestroy");
        }
    }

    #endregion


    private Fsm<FmsTest> m_fsm=null;
    
    public void OnTestStart()
    {
        FsmState<FmsTest>[] arrStates = new FsmState<FmsTest>[4];
        arrStates[(int)EnumTestClassType.eTest1] = new FsmTestState1();
        arrStates[(int)EnumTestClassType.eTest2] = new FsmTestState2();
        arrStates[(int)EnumTestClassType.eTest3] = new FsmTestState3();
        arrStates[(int)EnumTestClassType.eTest4] = new FsmTestState4();

        m_fsm = GameEntry.FSM.Create(this, arrStates);
    }

    public void OnTestUpdate()
    {
        if (null == m_fsm)
            return;

        //m_fsm.OnUpdate();

        if (Input.GetKeyDown(KeyCode.Keypad1))
        {
            m_fsm.ChangeState((int)EnumTestClassType.eTest1);
        }
        else if (Input.GetKeyDown(KeyCode.Keypad2))
        {
            m_fsm.ChangeState((int)EnumTestClassType.eTest2);
        }
        else if (Input.GetKeyDown(KeyCode.Keypad3))
        {
            m_fsm.ChangeState((int)EnumTestClassType.eTest3);
        }
        else if (Input.GetKeyDown(KeyCode.Keypad4))
        {
            m_fsm.ChangeState((int)EnumTestClassType.eTest4);
        }

    }
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unity3D,我们可以通过调整摄像机的轨迹来实现不同的效果。下面简单介绍一下如何调整u3D摄像机的轨迹。 首先,我们需要在场景创建一个摄像机对象。可以通过点击“GameObject”菜单,然后选择“Camera”来创建一个摄像机。 接下来,我们可以选择调整摄像机的旋转或位置来调整摄像机的轨迹。在Unity编辑器,我们可以直接拖拽摄像机对象来移动它的位置,也可以通过旋转工具来改变它的方向。 如果我们想要实现沿着一条路径移动的摄像机轨迹,我们可以使用动画系统来实现。首先,我们需要在摄像机上添加一个Animator组件。然后,我们可以为摄像机添加一个位移动画。在动画窗口,我们可以创建一个新的动画剪辑,并在剪辑设置摄像机的位置关键帧。我们可以通过调整关键帧的位置来控制摄像机沿着路径移动的速度和方向。 如果我们想要实现固定点的旋转轨迹,我们可以通过在摄像机上添加一个脚本来实现。我们可以在脚本定义一个旋转轴,然后在每一帧更新按照一定的速度和方向来旋转摄像机。 除了以上方法外,我们还可以使用插件或工具来辅助调整u3D摄像机的轨迹。例如,我们可以使用Asset Store提供的轨迹编辑器插件,可以通过可视化界面来调整摄像机的轨迹。 总而言之,通过调整摄像机的位置、旋转或者使用动画系统来实现轨迹调整是较为常见的方法。根据具体需求,我们可以选择不同的方法来实现自己想要的摄像机轨迹效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值