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

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不会生效,可能加剧同步异常问题。
修复方案
  1. 所有字典访问前增加存在性校验:不要直接用[]索引SpawnedObjects,改用TryGetValue做安全校验,不存在就直接退出逻辑
  2. 调整NetworkVariable的权限配置时机:在构造时就指定读写权限,不要在Start中修改
  3. 销毁对象前先更新网络变量:服务器端销毁对象前先把activePuzzlePieceNetId设为0,再执行销毁操作,尽量缩短空窗期
  4. 客户端增加本地防抖标志:调用删除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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 10:57:06