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

Unity多人游戏player.transform.childCount客户端计数异常求助

多人游戏物品拾取同步问题:客户端Player子物体计数异常

我正在开发一款多人游戏,实现物品拾取与丢弃功能时遇到问题:向Player节点添加子物体后,player.transform.childCount在客户端返回3,但预期应为4。添加Debug.Log调试后,客户端输出“3: 1”和“3”,主机端输出“4: 0”——主机端功能正常,但客户端计数错误。

相关代码

public void AddItem(ItemData itemData)
{
    if(itemData == null)
        return;

    this.itemData = itemData;

    icon.sprite = itemData.icon;
    icon.enabled = true;

    if (player == null)
        player = NetworkManager.Singleton.SpawnManager.GetLocalPlayerObject().gameObject;

    if (player != null)
    {
        NetworkObject networkObject = player.GetComponent<NetworkObject>();

        if (networkObject != null)
        {
            Debug.Log(player.transform.childCount + ": 1");
            AddItemToHandServerRpc(itemData.index, networkObject.NetworkObjectId);
            Debug.Log(player.transform.childCount);
            if (player.transform.childCount == 4)
            {
                GameObject item = player.transform.GetChild(player.transform.childCount - 1).gameObject;
                item.GetComponent<SetPossionToHand>().enabled = true;
                item.transform.localScale = new Vector3(.01f, .01f, .01f);
            }
            else
            {
                Debug.Log("fsjfhshuyferh");
            }
        }
        else
            Debug.Log("none");
    }
    else
    {
        Debug.LogError("Hand object is not properly initialized or is disabled.");
    }
}

public void ClearSlot(bool delete)
{
    itemData = null;

    icon.sprite = null;
    icon.enabled = false;

    if (player.transform.childCount == 4 && delete)
    {
        if (player == null)
            player = NetworkManager.Singleton.SpawnManager.GetLocalPlayerObject().gameObject;
        GameObject item = player.transform.GetChild(player.transform.childCount - 1).gameObject;
        DespawnObjectServerRpc(item.GetComponent<NetworkObject>().NetworkObjectId);
    }
}

[ServerRpc]
void AddItemToHandServerRpc(int index, ulong handId)
{
    GameObject item = list.prefabs[index];
    var instance = Instantiate(item);
    var instanceNetworkObject = instance.GetComponent<NetworkObject>();

    Transform player = NetworkManager.Singleton.SpawnManager.SpawnedObjects[handId].gameObject.transform;

    instanceNetworkObject.SpawnWithOwnership(handId);

    instanceNetworkObject.TrySetParent(player, false);

    Debug.Log(player.transform.childCount + ": 0");
}

客户端侧

  • 层级视图:客户端层级视图
  • 控制台输出:客户端控制台

主机侧

  • 层级视图:主机层级视图
  • 控制台输出:主机控制台

解决方案

1. 修复ServerRpc调用后的同步时机问题

你在调用AddItemToHandServerRpc后立刻读取player.transform.childCount,但ServerRpc是异步执行的:客户端发送请求到服务器,服务器完成物品实例化、父节点设置后,才会把同步数据发回客户端。这中间的延迟导致客户端调用Rpc后马上读计数,服务器的修改还没同步过来,自然得到旧值。

不要在Rpc调用后立刻执行依赖子物体状态的逻辑,改用以下两种方式处理:

方式一:用NetworkVariable同步物品数量

给Player的NetworkBehaviour添加一个NetworkVariable<int>跟踪手持物品数量,服务器修改后自动同步到所有客户端,客户端监听变量变化再执行逻辑:

public class PlayerInventory : NetworkBehaviour
{
    public NetworkVariable<int> HeldItemCount = new NetworkVariable<int>(0);

    public void AddHeldItem()
    {
        if (IsServer)
            HeldItemCount.Value++;
    }

    void Awake()
    {
        HeldItemCount.OnValueChanged += OnHeldItemCountChanged;
    }

    void OnHeldItemCountChanged(int oldValue, int newValue)
    {
        if (IsLocalPlayer && newValue == 4)
        {
            GameObject item = transform.GetChild(transform.childCount - 1).gameObject;
            item.GetComponent<SetPossionToHand>().enabled = true;
            item.transform.localScale = new Vector3(.01f, .01f, .01f);
        }
    }
}

然后在AddItemToHandServerRpc中调用player.GetComponent<PlayerInventory>().AddHeldItem();。

方式二:监听物品Spawn事件

客户端监听NetworkObject的Spawn事件,当物品被同步到本地时再执行初始化逻辑:

void OnEnable()
{
    NetworkManager.Singleton.SpawnManager.OnSpawnedObject += OnSpawnedObject;
}

void OnDisable()
{
    NetworkManager.Singleton.SpawnManager.OnSpawnedObject -= OnSpawnedObject;
}

void OnSpawnedObject(NetworkObject obj)
{
    if (IsLocalPlayer && obj.transform.parent == player.transform)
    {
        if (player.transform.childCount == 4)
        {
            obj.GetComponent<SetPossionToHand>().enabled = true;
            obj.transform.localScale = new Vector3(.01f, .01f, .01f);
        }
    }
}

2. 修正ClearSlot的空引用风险

ClearSlot中先访问player.transform.childCount再判断player是否为空,会触发空指针异常,调整代码顺序:

public void ClearSlot(bool delete)
{
    itemData = null;
    icon.sprite = null;
    icon.enabled = false;

    if (delete)
    {
        if (player == null)
            player = NetworkManager.Singleton.SpawnManager.GetLocalPlayerObject().gameObject;
        
        if (player != null && player.transform.childCount == 4)
        {
            GameObject item = player.transform.GetChild(player.transform.childCount - 1).gameObject;
            DespawnObjectServerRpc(item.GetComponent<NetworkObject>().NetworkObjectId);
        }
    }
}

3. 验证物品Prefab的网络配置

  • 确保物品Prefab带有NetworkObject组件,且已添加到NetworkManager的Spawnable Prefabs列表中;
  • 服务器端instanceNetworkObject.TrySetParent(player, false)的参数false是正确的,避免修改父物体本地坐标,确保父节点关系能正确同步到客户端。

内容的提问来源于stack exchange,提问作者Jonda_MC

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 14:20:23