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

Unity+Mirror多人游戏跨服务器同步门的异常问题排查

问题排查与修复方案

问题根源分析

  1. 无权限命令调用:CmdDoorController标记了requiresAuthority = false,允许任何客户端(包括无权限的)触发命令,导致不同玩家的操作相互干扰。
  2. 状态同步缺失:门的开关状态未由服务器统一管理,多个客户端触发命令时会反复切换状态,出现"开了立刻关"的情况。
  3. 变量作用域问题:原代码中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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 07:28:14