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

Unity Mirror拖拽门跨服务器同步问题求助

多人同步拖拽门问题解决方案

问题描述

制作了类似《恐鬼症》的拖拽门,玩家按住MB1拖拽开关,但多人游戏中门的状态无法跨服务器同步,所有玩家可操作但状态不一致。已添加Network Identity和Network Transform组件,尝试使用[Command]和[ClientRpc]后,非主机玩家拖拽时门仅微动或完全不动,主机操作后才恢复(已设置RequireAuthority = false)。

原代码

public class DoorOpenScript : NetworkBehaviour
{
    private Transform playerCamera;
    [SerializeField] Transform distCheck;
    [SerializeField] Transform hinge;
    [SerializeField] private float maxDistance = 3f;

    [SerializeField] float moveSpeed;
    [SerializeField] Vector2 rotationConstraints;

    bool movingDoor;
    float rotation;
    Vector3 targetPosition;

    void Start()
    {
        targetPosition = distCheck.position;

        // Find the player's camera at runtime
        playerCamera = Camera.main.transform; // Assuming your camera is tagged as "MainCamera" or is the main camera in the scene
    }

    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            Ray ray = playerCamera.GetComponent<Camera>().ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;

            int layerMask = ~LayerMask.GetMask("IgnoreRaycast");

            if (Physics.Raycast(ray, out hit, maxDistance, layerMask))
            {
                if (hit.collider.gameObject.CompareTag("Door") && hit.collider.gameObject == this.gameObject)
                {
                    movingDoor = true;
                }
            }
        }

        if (movingDoor)
        {
            if (Input.GetMouseButtonUp(0))
            {
                movingDoor = false;
            }
            targetPosition = playerCamera.position + playerCamera.forward * 2f;
        }

        rotation += Mathf.Clamp(-GetRotation() * 5000 * Time.deltaTime, -moveSpeed, moveSpeed);
        rotation = Mathf.Clamp(rotation, rotationConstraints.x, rotationConstraints.y);
        hinge.localRotation = Quaternion.Euler(0, rotation, 0);
    }

    float GetRotation()
    {
        float firstDistance = (distCheck.position - targetPosition).sqrMagnitude;
        hinge.Rotate(Vector3.up);
        float secondDistance = (distCheck.position - targetPosition).sqrMagnitude;
        hinge.Rotate(-Vector3.up);
        return secondDistance - firstDistance;
    }
}

核心解决思路

1. 确立服务器状态权威

所有门的状态必须由服务器唯一计算和同步,客户端仅负责发送输入请求,不直接修改门的状态。将rotation变量标记为[SyncVar],让服务器自动同步该值到所有客户端,确保状态一致性。

2. 重构输入与同步逻辑

  • 客户端仅处理本地玩家的输入,当拖拽门时,将目标位置通过[Command]发送给服务器。
  • 服务器端负责根据客户端传来的输入计算门的旋转,并更新rotation变量(SyncVar会自动同步到所有客户端)。
  • 客户端通过SyncVar的hook方法,在收到服务器同步的旋转值时更新门的显示。

3. 修正权限与Command调用

  • 给操作门的玩家临时分配门的客户端权限(可选但提升安全性),避免非法请求。
  • 确保Command中使用服务器的Time.deltaTime计算旋转,避免不同客户端帧率差异导致的计算误差。

修改后的代码示例

public class DoorOpenScript : NetworkBehaviour
{
    private Transform playerCamera;
    [SerializeField] Transform distCheck;
    [SerializeField] Transform hinge;
    [SerializeField] private float maxDistance = 3f;

    [SerializeField] float moveSpeed;
    [SerializeField] Vector2 rotationConstraints;

    bool movingDoor;
    // SyncVar同步旋转值,变化时触发OnRotationChanged更新客户端显示
    [SyncVar(hook = nameof(OnRotationChanged))] 
    float rotation; 
    Vector3 targetPosition;

    void Start()
    {
        targetPosition = distCheck.position;
        playerCamera = Camera.main.transform;
    }

    void Update()
    {
        // 仅本地玩家处理输入
        if (!isLocalPlayer) return;

        if (Input.GetMouseButtonDown(0))
        {
            Ray ray = playerCamera.GetComponent<Camera>().ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            int layerMask = ~LayerMask.GetMask("IgnoreRaycast");

            if (Physics.Raycast(ray, out hit, maxDistance, layerMask) && hit.collider.gameObject == gameObject)
            {
                movingDoor = true;
                // 给当前玩家分配门的临时权限
                var playerNetId = playerCamera.GetComponentInParent<NetworkIdentity>();
                if (playerNetId != null)
                {
                    GetComponent<NetworkIdentity>().AssignClientAuthority(playerNetId.connectionToClient);
                }
            }
        }

        if (movingDoor)
        {
            if (Input.GetMouseButtonUp(0))
            {
                movingDoor = false;
                // 释放门的权限
                GetComponent<NetworkIdentity>().RemoveClientAuthority();
            }
            targetPosition = playerCamera.position + playerCamera.forward * 2f;
            // 发送目标位置到服务器计算旋转
            CmdUpdateDoorRotation(targetPosition);
        }
    }

    [Command(RequireAuthority = false)]
    void CmdUpdateDoorRotation(Vector3 clientTargetPos)
    {
        // 服务器端计算旋转增量
        float rotDelta = Mathf.Clamp(-GetRotation(clientTargetPos) * 5000 * Time.deltaTime, -moveSpeed, moveSpeed);
        rotation += rotDelta;
        // 限制旋转范围
        rotation = Mathf.Clamp(rotation, rotationConstraints.x, rotationConstraints.y);
    }

    // SyncVar值变化时,客户端更新门的旋转
    void OnRotationChanged(float oldRotation, float newRotation)
    {
        hinge.localRotation = Quaternion.Euler(0, newRotation, 0);
    }

    // 接收服务器端传来的目标位置计算旋转方向
    float GetRotation(Vector3 targetPos)
    {
        float firstDistance = (distCheck.position - targetPos).sqrMagnitude;
        hinge.Rotate(Vector3.up);
        float secondDistance = (distCheck.position - targetPos).sqrMagnitude;
        hinge.Rotate(-Vector3.up);
        return secondDistance - firstDistance;
    }
}

额外注意事项

  • 移除Network Transform组件或关闭其对hinge的同步,避免与SyncVar的旋转同步产生冲突。
  • 若存在网络延迟,可在客户端添加本地预测逻辑(比如临时本地旋转,待服务器同步后修正),提升操作手感。
  • 可在Command中添加距离验证,确保玩家确实在门的有效操作范围内,防止远程作弊。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 11:59:59