如何用Zenject绑定状态机行为?事件未触发问题排查
状态机事件未触发:绑定实例与Animator内实例不一致问题
我是台湾同胞,英文表述欠佳,还请见谅。
我通过StateMachineBehaviour实现了状态机的进入事件通知,并用依赖注入绑定了相关实例,但进入GameStartState时事件并未触发。调试确认事件已成功订阅,但就是没有触发效果,怀疑是DI绑定的GameStartStateBehaviour实例和Animator状态机里的不是同一个。
相关代码
状态机行为实现
public class GameStartStateBehaviour : StateMachineBehaviour, IEnterState<SceneType> { public event EventHandler<SceneType> OnEnterState; public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { OnEnterState?.Invoke(this, SceneType.GamePlayScene); } }
场景加载器代码
public class SceneLoader : IInitializable, IDisposable { private IEnterState<SceneType> _enterMenuState; private IEnterState<SceneType> _enterGameStartState; public SceneLoader( [Inject(Id = "Menu")] IEnterState<SceneType> enterMenuState, [Inject(Id = "GameStart")] IEnterState<SceneType> enterGameStartState) { _enterMenuState = enterMenuState; _enterGameStartState = enterGameStartState; } public void Initialize() { _enterMenuState.OnEnterState += LoadMenuScene; _enterGameStartState.OnEnterState += LoadGamePlayScene; } }
DI绑定代码
Container.BindInterfacesAndSelfTo<SceneLoader>().AsSingle().NonLazy(); Container.Bind<IEnterState<SceneType>>().WithId("Menu").To<MenuStateBehaviour>().AsSingle().NonLazy(); Container.Bind<IEnterState<SceneType>>().WithId("GameStart").To<GameStartStateBehaviour>().AsSingle().NonLazy();
问题根源
你的怀疑完全正确:Animator状态机里的StateMachineBehaviour实例是Unity加载AnimatorController时自动创建的,和DI容器里绑定的单例是完全独立的两个实例。DI容器创建的实例没有挂载到任何Animator上,所以状态切换时,是Unity自动创建的那个实例触发OnStateEnter,但这个实例的OnEnterState事件没有被订阅,自然不会有响应。
解决思路
方案1:直接调用目标方法
修改GameStartStateBehaviour,在OnStateEnter中直接从DI容器获取SceneLoader实例并调用加载方法,绕过事件绑定的实例问题:
public class GameStartStateBehaviour : StateMachineBehaviour { public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { // 假设容器全局可访问,或通过其他方式获取 var sceneLoader = Container.Instance.Resolve<SceneLoader>(); sceneLoader.LoadGamePlayScene(this, SceneType.GamePlayScene); } }
方案2:关联Unity实例与DI实例的事件
在项目初始化阶段(比如GameManager的Start方法),找到Animator上的GameStartStateBehaviour实例,将DI实例的事件订阅转移过去:
// 初始化脚本示例 var animator = FindObjectOfType<Animator>(); var unityGameStartState = animator.GetBehaviour<GameStartStateBehaviour>(); var diGameStartState = Container.Instance.Resolve<IEnterState<SceneType>>("GameStart"); // 将DI实例的事件订阅者绑定到Unity实例上 if (diGameStartState.OnEnterState != null) { foreach (Delegate handler in diGameStartState.OnEnterState.GetInvocationList()) { unityGameStartState.OnEnterState += (EventHandler<SceneType>)handler; } }
方案3:改用全局事件总线
放弃实例级事件,改用全局事件总线传递状态切换通知,彻底规避实例不一致问题:
// 全局事件总线示例 public static class EventBus { public static event EventHandler<SceneType> OnSceneLoadRequested; public static void RaiseSceneLoadRequest(SceneType sceneType) { OnSceneLoadRequested?.Invoke(null, sceneType); } } // 修改状态机行为 public class GameStartStateBehaviour : StateMachineBehaviour { public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { EventBus.RaiseSceneLoadRequest(SceneType.GamePlayScene); } } // 修改SceneLoader的初始化逻辑 public void Initialize() { EventBus.OnSceneLoadRequested += (sender, sceneType) => { switch(sceneType) { case SceneType.MenuScene: LoadMenuScene(sender, sceneType); break; case SceneType.GamePlayScene: LoadGamePlayScene(sender, sceneType); break; } }; }
内容的提问来源于stack exchange,提问作者dandee555
相关产品推荐
相关产品推荐

