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
相关产品推荐
相关产品推荐

