为什么在多嵌套事件函数中调用脚本时我的代码会挂起

Why does my code hang when calling a script in a multi-nested event function

我正在努力让我的事件系统统一工作。我正在使用 Nakama 服务器制作在线回合制游戏。

Nakama 服务器使用事件通知客户端。我有一个 MatchClient.cs 脚本来处理连接到服务器和配对。当匹配器匹配玩家并且游戏准备开始时,此脚本调用 MatchReady(一个 UnityEvent)。

MatchEventBroadcastManager.cs 脚本处理服务器发送给客户端的匹配事件。游戏特定事件在此脚本中声明(例如其他脚本订阅的 OnOpponentMove 等)它有一个 MatchReady() 函数,在调用 MatchClient 的 MatchReady 事件时调用。

到目前为止一切正常。

这是 MatchEventBroadcastManager.cs 脚本的一部分:

public delegate void MatchStartedEvent();
public static event MatchStartedEvent OnMatchStart;

public void MatchReady()
{        
    //Handle notifications from the server
    _client.Socket.OnNotification += (_, notification) =>
    {
        switch (notification.Code)
        {
            case 1:
                OnMatchStart?.Invoke();
                break;
        }
    };
}

我尝试为 OnMatchStart 使用另一个 UnityEvent,但它不会触发检查器中引用的任何函数(我相信这可能与事件嵌套有关,但我不确定) .

这需要在一个事件中进行,因为我无法订阅 Socket.OnNotification 事件,直到玩家匹配后(并且 Socket 不为空)。

此时我们有:

到目前为止一切正常。

直到

我有一个 WaitingScreenMenu.cs 脚本来处理等待屏幕(显然)。

它订阅了 OnMatchStart 事件,只是想转换到下一个菜单。

private void Start()
{
    MatchEventBroadcastManager.OnMatchStart += MatchStarted;

    Debug.Log("Menu manager: ");
    Debug.Log(_menuManager.name);
}

public void MatchStarted()
{
    Debug.Log("Menu manager: ");
    Debug.Log(_menuManager.name);
    Debug.Log("Test after");
}

我删除了到下一个菜单的过渡,只是尝试访问外部对象进行测试,但这是执行挂起的地方。

在游戏启动时的调试输出中 _menuManager.name 正确打印到控制台。

当调用 MatchStarted 时 "Menu manager: " 被打印, 但没有别的。

如果我注释掉 Debug.Log(_menuManager.name); 那么它会打印:

"Menu manager: "
"Test after"

我试过其他脚本,结果相同。我已经从订阅了一个未嵌套的事件的函数中调用了该函数,并且它运行正常。

我正试图将游戏逻辑分成可管理的部分,但似乎处理事件的唯一方法是让所有事情都由 MatchClient.cs 处理,并且有一个巨大的、笨拙的混乱。

Unity 在事件方面是否真的以这种方式受到限制,还是我遗漏了什么?

提前感谢您的帮助。

使用 Visual Studio 调试器发现了错误。中断事件并检查 _menuManager 的值显示了 UnityException。 UnityException: get_gameObject can only be called from the main thread.

正在从异步方法调用事件。仔细研究,我发现您无法从单独的线程与 UnityEngine 通信。

我没有在 Socket.OnNotification 事件中直接调用我的事件,而是设置了一个在 Update() 中选中的标志,随后在主线程上调用我的事件。

MatchEventBroadcastManager.cs脚本的替换部分:

private bool notifMatchStarted;
public UnityEvent MatchStarted;

void Update()
{
    //Invoke events here to guarantee they are called from the main thread.
    if(notifMatchStarted)
    {
        MatchStarted.Invoke();
        notifMatchStarted = false;
    }
}

public void MatchReady()
{
    //Handle notifications from the server
    _client.Socket.OnNotification += (_, notification) =>
    {
        switch (notification.Code)
        {
            case 1:
                notifMatchStarted = true;
                break;
        }
    };
}