Unity有限状态机 一个支持消息调度系统的泛型状态机

本文介绍了 SharedFSM 框架的使用和设计思路
gitee地址
github地址

使用示例

安装

  1. 找到项目的manifest.json

  2. 在dependencies中加一句:

“com.shared.sharedfsm”: “https://gitee.com/TheSourceCode/SharedFSM.git?path=src/SharedFSM/Assets”,

实战示例:好吃懒做的Bob 和 勤劳的Elsa

场景剧本
  1. Elsa做家务

  2. Bob回家睡觉

  3. Elsa停止家务,并开始做饭

  4. Elsa做好饭,通知Bob吃饭,并开始做家务

  5. Bob 收到消息,从睡觉状态切换到吃饭状态

  6. Bob吃饭完后,回到第2条,继续睡觉。

游戏状态定义

推荐在项目中都建立这两个类

public class GameFSM : FSM<IGameState, BaseGameEntity>
{
    public GameFSM(BaseGameEntity owner, IGameState globalState = null) : base(owner, globalState)
    {
    }
}

public interface IGameState : IState<BaseGameEntity>
{
    // 一些自定义方法
}
实体定义
public class BaseGameEntity : FSMEntity<IGameState, BaseGameEntity>
{
    // 一些自定义方法
}
public class Bob : BaseGameEntity
{
    private void Awake()
    {
        _fsm = new GameFSM(this);
        ChangeState(ServiceProvider.Get<GoHomeAndSleepTilRested>());
    }
}
public class Elsa : BaseGameEntity
{
    public bool IsCooking { get; set; }

    private void Awake()
    {
        _fsm = new GameFSM(this, ServiceProvider.Get<WifeGlobalState>());
        ChangeState(ServiceProvider.Get<WifeDoHouseWork>());
    }
}
游戏控制器
using System.Collections;
using System.Reflection;
using SharedFSM;
using SharedFSM.Entities;
using SharedFSM.Msgs;
using UnityEngine;

public class GameController : MonoBehaviour
{
    private IFSMEntityManager _entityManager;

    private void Awake()
    {
        // 注册所有state
        SimpleServiceContainer.Instance.RegisterAllImplementations<IGameState>(
            // 当前程序集
            Assembly.GetExecutingAssembly()
            //Assembly.Load("Other")  // 其他程序集)
            );

        SimpleServiceContainer.Instance.Register<IFSMEntityManager, FSMEntityManager>();
        SimpleServiceContainer.Instance.Register<MessageDispatcher>();

        DontDestroyOnLoad(gameObject);
    }

    private void Start()
    {
        // 通过接口获取管理器,不依赖具体实现
        _entityManager = ServiceProvider.Get<IFSMEntityManager>();

        // 初始化妻子Elsa
        var elsaObject = new GameObject("Elsa");
        var elsa = elsaObject.AddComponent<Elsa>();
        elsa.ID = 2;
        _entityManager.RegisterEntity(elsa);

        StartCoroutine(InitBob());
    }

    private IEnumerator InitBob()
    {
        // 一段时间后初始化Bob
        yield return new WaitForSeconds(2);
        var bobObject = new GameObject("Bob");
        var bob = bobObject.AddComponent<Bob>();
        bob.ID = 1;
        _entityManager.RegisterEntity(bob);
    }
}
此处省略状态类
在unity运行

新建一个空场景,加两个空对象,分别挂载 MessageControllerGameController

设计思路

状态机核心(FSM)

public class FSM<TState, TOwner> where TState : IState<TOwner>
{
    protected readonly TOwner Owner;

    public TState PreviousState { get; protected set; }
    public TState CurrentState { get; protected set; }
    public TState GlobalState { get; protected set; }

    public FSM(TOwner owner, TState globalState = default)
    {
        Owner = owner;
        GlobalState = globalState;
    }

    public void ChangeState(TState newState)
    {
        // 先执行上一个状态的退出,在执行当前状态的进入。
        PreviousState = CurrentState;
        CurrentState?.ExitState(Owner);

        CurrentState = newState;
        CurrentState.EnterState(Owner);
    }

    public void Update()
    {
        CurrentState?.UpdateState(Owner);
        GlobalState?.UpdateState(Owner);
    }

    public void RevertToPreviousState()
    {
        if (PreviousState != null)
        {
            ChangeState(PreviousState);
        }
    }

    public bool HandleMessage(Telegram msg)
    {
        if (CurrentState != null && CurrentState.OnMessage(Owner, msg))
        {
            return true;
        }

        if (GlobalState != null && GlobalState.OnMessage(Owner, msg))
        {
            return true;
        }

        return false;
    }
}

public interface IState<TOwner>
{
    void EnterState(TOwner owner = default);
    void UpdateState(TOwner owner = default);
    void ExitState(TOwner owner = default);
    bool OnMessage(TOwner owner, Telegram msg);
}
设计思路
  • 全局状态:例如,在《模拟人生》中,Sim可能会感到本能的迫切要求,不得不去洗手间去方便。这种急切的需求会发生在Sim的任何状态或任何可能的时间。最好创建一个全局状态,这样每次FSM更新时就会被调用。

  • 记录上一个状态:例如,还是在《模拟人生》中,Sim去完洗手间,还需要回到之前的状态,这种需求就需要记录上一个状态。

  • 消息系统:HandleMessage函数处理外部消息。例如,《刺客信条》中,玩家进入了敌人的视野范围,敌人就会停止巡逻,开始攻击追击玩家。

消息系统(Message Dispatcher)

public class MessageDispatcher
{
    // 通过接口依赖,而非具体类
    private readonly IFSMEntityManager _entityManager;

    public MessageDispatcher()
    {
        _entityManager = ServiceProvider.Get<IFSMEntityManager>();
    }

    public SortedSet<Telegram> PriorityQueue { get; private set; } =
        new SortedSet<Telegram>(Comparer<Telegram>.Create((t1, t2) => t1.DispatchTime.CompareTo(t2.DispatchTime)));

    public void DispatchMessage(double delay, int sender, int receiver, int msg, object extraInfo = null)
    {
        if (delay <= 0)
        {
            var receiverEntity = _entityManager?.GetEntityFromID(receiver);
            receiverEntity?.HandleMessage(new Telegram(sender, receiver, msg, 0, extraInfo));
        }
        else
        {
            var telegram = new Telegram(sender, receiver, msg, Time.time + delay, extraInfo);
            PriorityQueue.Add(telegram);
        }
    }
}

    public class MessageController : MonoBehaviour
    {
        private IFSMEntityManager _entityManager;
        private MessageDispatcher _messageDispatcher;

        private void Awake()
        {
            DontDestroyOnLoad(gameObject);
        }

        private void Start()
        {
            _entityManager = ServiceProvider.Get<IFSMEntityManager>();
            _messageDispatcher = ServiceProvider.Get<MessageDispatcher>();
        }

        private void Update()
        {
            DispatchDelayedMessages();
        }

        private void DispatchDelayedMessages()
        {
            while (_messageDispatcher.PriorityQueue.Count > 0 && _messageDispatcher.PriorityQueue.Min.DispatchTime <= Time.time)
            {
                var telegram = _messageDispatcher.PriorityQueue.Min;
                var receiverEntity = _entityManager?.GetEntityFromID(telegram.Receiver);
                receiverEntity?.HandleMessage(telegram);
                _messageDispatcher.PriorityQueue.Remove(telegram);
            }
        }
    }
设计思路
  • 支持"5秒后通知我"这样的时序逻辑

  • 使用 SortedSet 保证 O(log n) 的插入和 O(1) 的最小值获取

  • 通过实体管理器获取实体,_entityManager.GetEntityFromID

实体管理

public class FSMEntity<TState, TOwner> : MonoBehaviour, IFSMEntity
    where TState : IState<TOwner>
    where TOwner : IFSMEntity
{
    protected FSM<TState, TOwner> _fsm;

    public int ID { get; set; }

    private void Update()
    {
        _fsm?.Update();
    }

    public void ChangeState(TState state)
    {
        _fsm?.ChangeState(state);
    }

    public void RevertToPreviousState()
    {
        _fsm?.RevertToPreviousState();
    }

    public virtual bool HandleMessage(Telegram msg)
    {
        return _fsm?.HandleMessage(msg) ?? false;
    }
}

public class FSMEntityManager : IFSMEntityManager
{
    private Dictionary<int, IFSMEntity> _entityMap = new Dictionary<int, IFSMEntity>();

    public FSMEntityManager() { }

    public void RegisterEntity(IFSMEntity entity)
    {
        if (entity != null)
        {
            _entityMap[entity.ID] = entity;
        }
    }

    public IFSMEntity GetEntityFromID(int id)
    {
        if (_entityMap.TryGetValue(id, out var entity))
        {
            return entity;
        }
        return null;
    }

    public void RemoveEntity(IFSMEntity entity)
    {
        if (entity != null)
        {
            _entityMap.Remove(entity.ID);
        }
    }
}
设计思路
  • 一个智能体要做的事情就是去拥有一个StateMachine类的实例,并且为了得到完全的FSM功能,实现一个方法来更新状态机。

  • 在实体管理器中注册,注销,查找实体。

一个简单的服务定位器

当然,你应该使用更加完善的服务定位器或依赖注入框架,而不是这个简易版。

单例的状态类,导致了状态内部不能存储与具体实例相关的数据,否则当多个实体共享同一个状态实例时,就会出现数据污染的问题。

 public class SimpleServiceContainer
 {
     private static SimpleServiceContainer instance;
     public static SimpleServiceContainer Instance
     {
         get
         {
             if (instance == null)
             {
                 lock (typeof(SimpleServiceContainer))
                 {
                     if (instance == null)
                     {
                         instance = new SimpleServiceContainer();
                     }
                 }
             }
             return instance;
         }
     }

     private readonly Dictionary<Type, Lazy<object>> _services = new Dictionary<Type, Lazy<object>>();

     public void Register<TService, TImplementation>() where TImplementation : TService, new()
     {
         _services[typeof(TService)] = new Lazy<object>(() => new TImplementation());
     }

     public void Register<TImplementation>() where TImplementation : new()
     {
         _services[typeof(TImplementation)] = new Lazy<object>(() => new TImplementation());
     }

     public T Get<T>() where T : class
     {
         var key = typeof(T);

         if (_services.TryGetValue(key, out var retInstance))
         {
             return retInstance.Value as T;
         }

         return null;
     }

     public void RegisterAllImplementations<TService>(params Assembly[] assemblies)
     {
         var serviceType = typeof(TService);

         var implementations = assemblies.SelectMany(a => a.GetTypes())
                 .Where(t => serviceType.IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract && t.GetConstructors().Length > 0);

         foreach (var implementation in implementations)
         {
             _services[implementation] = new Lazy<object>(() => Activator.CreateInstance(implementation));
         }
     }
     // 自动注册泛型接口 IState<T> 的实现类
     public void AutoRegisterGenericInterfaces(params Assembly[] assemblies)
     {
         var types = assemblies.SelectMany(a => a.GetTypes())
             .Where(t => t.GetInterfaces()
                 .Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IState<>)));

         foreach (var type in types)
         {
             _services[type] = new Lazy<object>(() => Activator.CreateInstance(type));
         }
     }
 }

 public static class ServiceProvider
 {
     public static T Get<T>() where T : class
     {
         return SimpleServiceContainer.Instance.Get<T>();
     }
 }
设计思路
  • Lazy:服务只在首次获取时才实例化,避免启动时的大量对象创建

  • 双检锁单例模式:确保线程安全的同时保持高性能

  • 自动注册机制:通过反射自动扫描并注册 IState 的实现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值