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

Unity UNet点击时Transform更新异常:双人联机绘图游戏问题排查

问题分析与解决方案

核心问题拆解

你的代码里有几个关键问题导致客户端绘图的Transform无法同步:

  1. 服务器端无Camera导致Raycast失败:CmdTrailUpdate和CmdSpawn里直接使用Camera.main,但服务器运行时没有场景Camera,这会导致mRay无效,无法计算出正确的位置,自然无法更新Transform。
  2. Local Player Authority的使用错误:你给预制体勾选了Local Player Authority,意味着客户端拥有该对象的控制权,应该由客户端直接修改对象位置,再通过NetworkTransform同步到服务器和其他客户端,而不是通过Command让服务器修改位置。
  3. 客户端与服务器的currentTrail引用不一致:CmdSpawn在服务器上实例化并赋值了currentTrail,但客户端本地的currentTrail变量并没有关联到同步生成的本地对象,导致后续客户端调用CmdTrailUpdate时,服务器操作的可能不是该客户端对应的trail对象。

修正后的代码实现

1. 调整TrailDrawer逻辑,客户端本地计算位置

把Raycast操作放在客户端完成,只把计算好的位置传给服务器,同时利用Local Player Authority让客户端直接控制trail对象:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;

public class TrailDrawer : NetworkBehaviour {
    private Plane objPlane;
    private GameObject currentTrail;
    public GameObject trailPrefab; // 重命名变量更清晰

    public override void OnStartLocalPlayer() {
        // 只在本地玩家初始化时设置Plane,避免服务器执行
        objPlane = new Plane(Camera.main.transform.forward * -1, transform.position);
    }

    void FixedUpdate() {
        if (!isLocalPlayer) return; // 非本地玩家直接跳过

        if (Input.GetMouseButtonDown(0)) {
            // 客户端本地计算初始位置
            Ray mRay = Camera.main.ScreenPointToRay(Input.mousePosition);
            if (objPlane.Raycast(mRay, out float rayDistance)) {
                Vector3 spawnPos = mRay.GetPoint(rayDistance);
                CmdSpawnTrail(spawnPos); // 把位置传给服务器
            }
        } else if (Input.GetMouseButton(0) && currentTrail != null) {
            // 客户端直接修改trail位置,Local Player Authority会自动同步
            Ray mRay = Camera.main.ScreenPointToRay(Input.mousePosition);
            if (objPlane.Raycast(mRay, out float rayDistance)) {
                currentTrail.transform.position = mRay.GetPoint(rayDistance);
            }
        }
    }

    [Command]
    private void CmdSpawnTrail(Vector3 spawnPos) {
        // 服务器实例化trail,因为客户端已经计算好位置,不需要再Raycast
        GameObject newTrail = Instantiate(trailPrefab, spawnPos, Quaternion.identity);
        NetworkServer.SpawnWithClientAuthority(newTrail, connectionToClient); // 明确赋予客户端权限
        RpcAssignLocalTrail(newTrail.netId); // 通知客户端关联本地对象
    }

    [ClientRpc]
    private void RpcAssignLocalTrail(NetworkInstanceId trailNetId) {
        // 只有本地玩家才需要关联currentTrail
        if (!isLocalPlayer) return;
        currentTrail = ClientScene.FindLocalObject(trailNetId);
    }
}

2. 预制体配置确认

确保你的trail预制体满足以下配置:

  • 挂载NetworkIdentity组件,勾选Local Player Authority
  • 挂载NetworkTransform组件,保持默认配置即可(因为Local Player Authority下,客户端的位置修改会自动同步)

关键修改说明

  • 客户端本地计算位置:所有和Camera相关的Raycast都在客户端完成,避免服务器无Camera的问题,同时减少网络传输的数据量。
  • Spawn时明确赋予权限:使用NetworkServer.SpawnWithClientAuthority替代NetworkServer.Spawn,确保生成的trail对象归当前客户端所有,拥有修改权限。
  • ClientRpc关联本地对象:通过RpcAssignLocalTrail把服务器生成的trail的NetworkID传给客户端,客户端通过ID找到本地实例并赋值给currentTrail,解决引用不一致的问题。
  • 直接修改客户端对象:因为拥有Local Player Authority,客户端修改trail的位置后,NetworkTransform会自动把位置同步到服务器和其他客户端,不需要再用Command传递位置。

这样修改后,客户端玩家的绘图操作就能正确同步到主机和其他客户端了。

内容的提问来源于stack exchange,提问作者Le olde Wire

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 04:38:50