本文介绍了 SharedFSM 框架的使用和设计思路
gitee地址
github地址
目录
使用示例
安装
-
找到项目的manifest.json
-
在dependencies中加一句:
“com.shared.sharedfsm”: “https://gitee.com/TheSourceCode/SharedFSM.git?path=src/SharedFSM/Assets”,
实战示例:好吃懒做的Bob 和 勤劳的Elsa
场景剧本
-
Elsa做家务
-
Bob回家睡觉
-
Elsa停止家务,并开始做饭
-
Elsa做好饭,通知Bob吃饭,并开始做家务
-
Bob 收到消息,从睡觉状态切换到吃饭状态
-
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运行
新建一个空场景,加两个空对象,分别挂载 MessageController 和GameController
设计思路
状态机核心(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 的实现

699

被折叠的 条评论
为什么被折叠?



