首页 > 解决方案 > 为什么在多嵌套事件函数中调用脚本时我的代码会挂起

问题描述

我正在努力让我的事件系统统一工作。我正在使用 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;
        }
    };
}

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

这需要在一个事件中,因为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在事件方面真的以这种方式受到限制还是我错过了什么?

提前感谢您的帮助。

标签: c#unity3deventsevent-handlingnakama

解决方案


使用 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;
        }
    };
}

推荐阅读