You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 16:40:57