Unity MLAPI 0.1.0销毁网络对象后持续抛出KeyNotFoundException问题
问题原因分析
- NetworkVariable同步延迟:你在服务器端执行
DeleteActivePuzzlePieceServerRpc时,销毁对象、修改activePuzzlePieceNetId为0的操作中,网络变量的修改需要经过网络同步才能到达客户端,中间存在1~2帧的空窗期。这段时间客户端的activePuzzlePieceNetId还未更新为0,Update逻辑会继续调用MakeActivePuzzlePieceFollowMouseServerRpc,触发后续的ClientRpc执行。 - 字典直接索引无校验:
MakeActivePuzzlePieceFollowMouseClientRpc中直接用NetworkSpawnManager.SpawnedObjects[activePuzzlePieceNetId.Value]访问字典,对象销毁后SpawnedObjects会立刻移除对应键值对,此时还未同步到0的网络ID访问字典就会抛出KeyNotFoundException。 - NetworkVariable权限配置不生效:MLAPI 0.1.0版本中,
NetworkVariable的权限配置需要在构造时指定,在Start中修改Settings不会生效,可能加剧同步异常问题。
修复方案
- 所有字典访问前增加存在性校验:不要直接用
[]索引SpawnedObjects,改用TryGetValue做安全校验,不存在就直接退出逻辑 - 调整NetworkVariable的权限配置时机:在构造时就指定读写权限,不要在Start中修改
- 销毁对象前先更新网络变量:服务器端销毁对象前先把
activePuzzlePieceNetId设为0,再执行销毁操作,尽量缩短空窗期 - 客户端增加本地防抖标志:调用删除RPC时本地先设置临时标记,避免空窗期内重复发起无用RPC
修改后的核心代码示例
using UnityEngine; using MLAPI; using MLAPI.Messaging; using MLAPI.Spawning; using MLAPI.NetworkVariable; public class PlayerTest : NetworkBehaviour { [SerializeField] Transform[] puzzlePiecePrefabs; // 构造时直接指定权限 NetworkVariable<ulong> activePuzzlePieceNetId = new NetworkVariable<ulong>(new NetworkVariableSettings { ReadPermission = NetworkVariablePermission.Everyone, WritePermission = NetworkVariablePermission.Everyone }, 0); // 本地防抖标记 private bool _isDeletingActivePiece = false; void Update() { if (!IsLocalPlayer) return; // 增加防抖标记判断 if (!activePuzzlePieceNetId.Value.Equals(0) && !_isDeletingActivePiece) { if (Input.GetMouseButtonDown(1)) { _isDeletingActivePiece = true; DeleteActivePuzzlePieceServerRpc(); } else { MakeActivePuzzlePieceFollowMouseServerRpc(); } } // 网络变量同步到0后重置防抖标记 if (activePuzzlePieceNetId.Value.Equals(0) && _isDeletingActivePiece) { _isDeletingActivePiece = false; } } [ServerRpc] void DeleteActivePuzzlePieceServerRpc() { // 先校验对象是否存在 if (NetworkSpawnManager.SpawnedObjects.TryGetValue(activePuzzlePieceNetId.Value, out var puzzlePiece)) { // 先改网络变量,再销毁对象 activePuzzlePieceNetId.Value = 0; Destroy(puzzlePiece.gameObject); } } [ServerRpc] public void SpawnPuzzlePieceServerRpc(int puzzlePieceId) { Transform puzzlePiece = Instantiate(puzzlePiecePrefabs[puzzlePieceId]); var netObj = puzzlePiece.GetComponent<NetworkObject>(); netObj.SpawnWithOwnership(OwnerClientId); activePuzzlePieceNetId.Value = netObj.NetworkObjectId; } [ServerRpc] void MakeActivePuzzlePieceFollowMouseServerRpc() { MakeActivePuzzlePieceFollowMouseClientRpc(); } [ClientRpc] void MakeActivePuzzlePieceFollowMouseClientRpc() { // 双重校验:网络ID不为0 + 对象存在 if (activePuzzlePieceNetId.Value == 0) return; if (NetworkSpawnManager.SpawnedObjects.TryGetValue(activePuzzlePieceNetId.Value, out var puzzlePiece)) { puzzlePiece.transform.position = GetMousePositionInWorld(); } } Vector3 GetMousePositionInWorld() { var mousePos = Input.mousePosition; mousePos.z = 19; return Camera.main.ScreenToWorldPoint(mousePos); } }
内容的提问来源于stack exchange,提问作者Paulius Petrauskas
相关产品推荐
相关产品推荐

