c# - 为什么在多嵌套事件函数中调用脚本时我的代码会挂起
问题描述
我正在努力让我的事件系统统一工作。我正在使用 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
不为空)之后我才能订阅该事件。
此时我们有:
- Nakama 事件通知 MatchClient 玩家已匹配。
- MatchClient 通知 MatchEventBroadcastManager(以及其他脚本)匹配现已准备就绪。
- MatchEventBroadcastManager 订阅服务器的 OnNotification 事件。
- 当此事件触发时,将调用 OnMatchStart。
到目前为止,一切似乎都正常工作。
直到
我有一个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;
}
};
}