Unity下简易状态机(FSM)的实现

Unity 下简易状态机的实现

国庆到处都是人,我的建议是要么出国旅游,要么就宅在家把时间留给自己或家人。朋友约我去星巴克坐着重构代码,想想也是程序猿之间才有的默契。

关于状态机,我想都知道它很常用,就像单例一样,缺了它们,我们在游戏编程世界里呼吸都不畅快。网上也有很多大神写的状态机,比如PlayMaker这样重量级的插件,功能很强大,依赖也不少。程序猿的胃口有时候就是很挑剔,吃惯了大鱼大肉,也想整点清粥小菜。应该有很多朋友跟我一样,希望有一套简易的状态机实现,每行代码自己都了如指掌,实现我们一些简单的状态控制的需求。In hand,伸手就来的方便!

下面是我的实现,有错误或者low的地方,欢迎大家留言,一起进步吧!

状态机流程图

讲到状态机,首当其冲的肯定是状态,我们把状态细分为不同的阶段,每个阶段处理各自的事情。状态里最基本要有OnEnter,OnExit,OnUpdate三个阶段的处理。在 Unity下,我把OnUpdate分得更细,原因在于有些操作需要放在特定的帧下面执行,比如相机的跟随,角色的移动等。

    /// <summary>
    /// 状态基类.
    /// </summary>
    public class FiniteState {
        public string Name {
            get {
                return GetType().Name;
            }
        }

        public virtual void OnEnter(object context){}

        public virtual void OnExit(object context){}

        public virtual void OnUpdate(object context){}

        public virtual void OnFixedUpdate(object context){}

        public virtual void OnLateUpdate(object context){}
    }
}

Transition是有限状态机里面不可或缺的三要素之一,它主要约束状态之间的过渡,A和B两个状态之间是否存在过渡关系,以及过渡的条件是什么,就由FiniteStateTransition来决定。

using UnityEngine;
using System.Collections;

namespace PLY.StateMachine{
    /// <summary>
    /// 状态机过渡约束基类.
    /// </summary>
    public class FiniteStateTransition {
        public string EventName;
        public string LastStateName;
        public string NextStateName;

        public FiniteStateTransition(string eventName, string lastStateName, string nextStateName){
            EventName = eventName;
            LastStateName = lastStateName;
            NextStateName = nextStateName;
        }
    }

最后是管理所有状态及其之间 Transition关系的状态机,它提供映射 Transition的方法,接收触发事件并过渡到对应的状态。

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

namespace PLY.StateMachine {
    /// <summary>
    /// 有限状态机基类.
    /// </summary>
    [System.Serializable]
    public class FiniteStateMachine : MonoBehaviour {
        private object Context_;
        public string CurrentStateName = "EmptyState";

        private FiniteState CurrentState_;
        protected FiniteState CurrentState{
            get{
                return CurrentState_;
            }
            set{
                CurrentState_ = value;
                CurrentStateName = CurrentState_.Name;
            }
        }

        protected FiniteState defaultState;

        private Dictionary<string, FiniteState> allStates_ = new Dictionary<string, FiniteState>();
        private List<FiniteStateTransition> allTransitions_ = new List<FiniteStateTransition>();

        protected virtual void Init(){}

        protected void AddState(FiniteState state){
            allStates_.Add(state.Name, state);
        }

        protected void RemoveState(FiniteState state){
            allStates_.Remove(state.Name);
        }

        /// <summary>
        /// 添加过渡.
        /// </summary>
        /// <param name="eventName">Event name.</param>
        /// <typeparam name="T1">源状态类型.</typeparam>
        /// <typeparam name="T2">目标状态类型.</typeparam>
        protected void AddTransition<T1,T2>(string eventName)where T1 : FiniteState where T2 : FiniteState{
            string lastStateName = typeof(T1).Name;
            string nextStateName = typeof(T2).Name;
            FiniteStateTransition transition = new FiniteStateTransition(eventName, lastStateName, nextStateName );
            allTransitions_.Add(transition);
        }

        protected void AddTransition(FiniteStateTransition transition){
            allTransitions_.Add(transition);
        }

        protected void RemoveTransation(FiniteStateTransition transition){
            allTransitions_.Remove(transition);
        }

        public void SetContext(object context){
            Context_ = context;
        }

        /// <summary>
        /// 发送事件,触发下一状态的切换
        /// </summary>
        /// <param name="eventName">事件名.</param>
        public void SendEvent(string eventName){
            for (int i = 0; i < allTransitions_.Count; i++) {
                FiniteState nextState;
                if (TryGetNextStateByEventName(allTransitions_[i], eventName, out nextState)){
                    ChangeState(nextState);
                    break;
                }
            }
        }

        private bool TryGetNextStateByEventName(FiniteStateTransition transation, string eventName, out FiniteState state){
            state = null;
            if (CurrentState == null || !CurrentState.Name.Equals(transation.LastStateName)) return false;
            if (!eventName.Equals(transation.EventName)) return false;
            return (allStates_.TryGetValue(transation.NextStateName, out state));
        }

        /// <summary>
        /// 切换状态.
        /// </summary>
        /// <param name="newState">New state.</param>
        private void ChangeState(FiniteState newState){
            if (CurrentState != null){
                CurrentState.OnExit(Context_);
            }
            else{
                Debug.LogError("当前状态为空,退出状态失败!", gameObject);
            }

            CurrentState = newState;

            if(CurrentState != null){
                CurrentState.OnEnter(Context_);
            }
            else{
                Debug.LogError("当前状态为空,进入状态失败!", gameObject);
            }
        }

        private void SetDefaultToCurrent(){
            CurrentState = defaultState;
        }

        private void Awake(){
            Init();
            SetDefaultToCurrent();
        }

        private void Start () {
            if (CurrentState != null){
                CurrentState.OnEnter(Context_);
            }       
        }

        private void Update () {
            if (CurrentState != null){
                CurrentState.OnUpdate(Context_);
            }
        }

        private void FixedUpdate(){
            if (CurrentState != null){
                CurrentState.OnFixedUpdate(Context_);
            }
        }

        private void LateUpdate(){
            if (CurrentState != null){
                CurrentState.OnLateUpdate(Context_);
            }
        }
    }
}

下面是我的测试用例了,简单写了一个关于锁的状态机,两种状态:锁住,未锁住。

using UnityEngine;
using System.Collections;
using PLY.StateMachine;

public class PlayerStateMachine : FiniteStateMachine{

    protected override void Init (){
        Locked state1 = new Locked();
        AddState(state1);

        Unlocked state2 = new Unlocked();
        AddState(state2);

        defaultState = state1;

        AddTransition<Locked, Unlocked>("UNLOCK");
        AddTransition<Unlocked, Locked>("LOCK");
    }
}

public class Locked : FiniteState{
    public override void OnEnter (object target){
        Player owner = target as Player;
        owner.Show(true);
    }

    public override void OnExit (object target){}

    public override void OnUpdate (object target){}
}

public class Unlocked : FiniteState{
    public override void OnEnter (object target){
        Player owner = target as Player;
        owner.Show(false);
    }

    public override void OnExit (object target){}

    public override void OnUpdate (object target){}
}
using UnityEngine;
using System.Collections;
using PLY.StateMachine;

public class Player : MonoBehaviour{
    public FiniteStateMachine StateMachine;
    private bool isShowingLockedView_;

    private Texture LockTex_;
    private Texture UnlockTex_;

    private void Awake(){
        StateMachine.SetContext(this);

        LockTex_ = Resources.Load("tex_locked") as Texture;
        UnlockTex_ = Resources.Load("tex_unlocked") as Texture;
    }

    private void OnGUI(){
        if(GUI.Button(new Rect(0,0,120,30), "UNLOCK")){
            StateMachine.SendEvent("UNLOCK");
        }

        if(GUI.Button(new Rect(0,40,120,30), "LOCK")){
            StateMachine.SendEvent("LOCK");
        }

        if(isShowingLockedView_){
            GUI.Label(new Rect(Screen.width/2,50,128,128), new GUIContent(LockTex_));
        }
        else{
            GUI.Label(new Rect(Screen.width/2,50,128,128), new GUIContent(UnlockTex_));
        }
    }

    public void Show(bool isLocked){
        isShowingLockedView_ = isLocked;
    }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
The complete Visual Behaviour Authoring framework for Unity, empowering you to create advanced AI Behaviours and Logic, including three separate, fully featured, yet seamlessly interchangeable modules for you to choose and easily add in your game: • Behaviour Trees • Hierarchical State Machines • Dialogue Trees NodeCanvas is a production ready framework used by many awesome games including Kim, Pamela, Hurtworld, Shroud of the Avatar, Kingdom and Ghost of a Tale. [Games Showcase] Feature Highlights ------------------------------------- • Choose the right tool for the task at hand. • Design reactive Behaviour Trees and Hierarchical State Machines within an intuitive, designer friendly visual node editor: (Full Undo/Redo, Zoom In/Out, Multi-selection, Duplication, Copy/Pasting, JSON Import/Export, Groups, Comments and more) • Use local & global variables of any type, visually or in code, for creating reusable and actor-oriented, parametric behaviours, optionally saving and loading those variables between gaming sessions. • Data Bind variables with any component property of any type directly. • Sync variables automatically over the network using UNET, for creating multiplayer games. • Visually Debug behaviours with realtime, colorful and informative runtime debugging. • Live Edit everything while in play mode to perfectly understand your design goals and how to achieve them. • Utilize any existing code directly with advanced and extremely fast Reflection Tasks, automatically integrating Unity's and 3rd Party asset APIs. • Work with Lists/Arrays, Enums, Interfaces and pretty much ANY Variable Type you need out-of-the-box. • React to world changes and transfer data using the built-in Event System. • Reuse and Bind made behaviours among any number of different agents. • Organize your designs using Behaviour Sub-Trees and Sub-State Machines. • Extend NodeCanvas Framework to create your own Actions, Conditions, Nodes or even completely new modules with the e
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值