Unity+Mirror多人游戏跨服务器同步门的异常问题排查
问题排查与修复方案
问题根源分析
- 无权限命令调用:
CmdDoorController标记了requiresAuthority = false,允许任何客户端(包括无权限的)触发命令,导致不同玩家的操作相互干扰。 - 状态同步缺失:门的开关状态未由服务器统一管理,多个客户端触发命令时会反复切换状态,出现"开了立刻关"的情况。
- 变量作用域问题:原代码中
doorIdentity作为类成员变量,可能在Update帧循环中被意外覆盖,导致传递错误的门对象到命令。
修复步骤与代码调整
1. 修正客户端射线检测与命令调用逻辑
将doorIdentity改为局部变量,仅在触发开门键时获取当前命中的门,避免成员变量被覆盖:
private void Update() { if (!isClient) return; if (SceneManager.GetActiveScene().name == "Game") { crosshair = CrossHair.instance.crosshairImage; } RaycastHit hit; Vector3 fwd = playerCamera.TransformDirection(Vector3.forward); int mask = 1 << LayerMask.NameToLayer(excludeLayerName) | doorLayer.value; bool isLookingAtDoor = Physics.Raycast(playerCamera.position, fwd, out hit, rayLength, mask) && hit.collider.CompareTag(doorTag); // 准星状态更新 if (isLookingAtDoor) { if (!doOnce) { CrosshairChange(true); doOnce = true; } isCrosshairActive = true; // 仅在按下开门键时获取当前命中的门并发送命令 if (Input.GetKeyDown(openDoorKey)) { var hitDoorIdentity = hit.collider.GetComponent<NetworkIdentity>(); if (hitDoorIdentity != null) { CmdDoorController(hitDoorIdentity); } } } else { if (isCrosshairActive) { CrosshairChange(false); isCrosshairActive = false; doOnce = false; } } }
2. 修复命令权限与服务器端验证
移除requiresAuthority = false,确保只有本地玩家能发送命令,并在服务器端重新验证玩家是否真的对准目标门(防止作弊):
// 移除requiresAuthority = false,使用默认权限验证 [Command] private void CmdDoorController(NetworkIdentity doorIdentity) { // 获取发送命令的玩家对象(替换成你的玩家脚本类名) var player = connectionToClient.identity.GetComponent<YourPlayerController>(); if (player == null) return; // 服务器端重新执行射线检测,验证玩家确实瞄准该门 RaycastHit hit; Vector3 fwd = player.playerCamera.TransformDirection(Vector3.forward); int mask = 1 << LayerMask.NameToLayer(player.excludeLayerName) | player.doorLayer.value; if (Physics.Raycast(player.playerCamera.position, fwd, out hit, player.rayLength, mask) && hit.collider.GetComponent<NetworkIdentity>() == doorIdentity) { var doorController = doorIdentity.GetComponent<DoorController>(); if (doorController != null) { doorController.ToggleDoor(); } } }
3. 门状态服务器同步(修改DoorController)
使用SyncVar让服务器统一管理门的开关状态,确保所有客户端同步状态:
public class DoorController : NetworkBehaviour { // 同步门状态,状态变化时触发钩子函数 [SyncVar(hook = nameof(OnDoorStateUpdated))] private bool _isOpen; public bool IsOpen => _isOpen; // 仅服务器能调用状态切换 public void ToggleDoor() { if (!isServer) return; _isOpen = !_isOpen; } // 状态变化时播放对应动画 private void OnDoorStateUpdated(bool oldState, bool newState) { if (newState) { // 播放开门动画 } else { // 播放关门动画 } } }
关键修复点说明
- 权限控制:移除
requiresAuthority = false后,只有本地玩家(拥有网络权限的客户端)能发送开门命令,避免不同玩家的操作互相干扰。 - 服务器验证:服务器端重新执行射线检测,防止客户端通过修改本地数据触发不属于自己视野内的门操作。
- 状态同步:通过
SyncVar让服务器统一管理门的状态,所有客户端同步这个状态,彻底解决"开了立刻关"的重复切换问题。
内容的提问来源于stack exchange,提问作者Yassir Bartal
相关产品推荐
相关产品推荐

