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

Unity Mirror多玩家卡牌游戏卡牌计数同步异常问题求助

问题分析

你的核心问题是每个Player实例都独立维护一套卡牌计数变量,包括主牌库deckCount、己方手牌myDeck、敌方手牌enemyDeck。这导致:

  • 新玩家加入时,OnStartClient会重置自己的计数为初始值(50/0/0),覆盖正确的同步状态
  • 抽卡逻辑在客户端全量执行(RpcSetDeck会让所有客户端各自修改本地计数),不同客户端的计数无法统一

SyncVar没生效是因为变量没有放在唯一的服务器权威源上,而是分散在每个Player对象中,自然无法跨玩家同步。

解决方案

把全局卡牌状态(主牌库、双方手牌数)移到一个服务器端的GameManager中,用SyncVar自动同步到所有客户端。Player只负责触发抽卡命令,由服务器统一处理逻辑并更新全局状态。

步骤1:创建服务器端GameManager

这个脚本仅在服务器运行,维护全局卡牌数据并同步到客户端:

using UnityEngine;
using UnityEngine.Networking;

public class GameManager : NetworkBehaviour
{
    // 全局主牌库计数,SyncVar自动同步到所有客户端,变化时触发钩子更新UI
    [SyncVar(hook = nameof(OnDeckCountChanged))]
    public int globalDeckCount = 50;

    // 玩家1手牌数
    [SyncVar(hook = nameof(OnPlayer1DeckChanged))]
    public int player1Deck = 0;

    // 玩家2手牌数
    [SyncVar(hook = nameof(OnPlayer2DeckChanged))]
    public int player2Deck = 0;

    // 单例,方便Player直接调用
    public static GameManager Instance;

    private void Awake()
    {
        Instance = this;
    }

    // 服务器端专属抽卡逻辑
    [Server]
    public void HandleDrawCard(NetworkIdentity playerIdentity)
    {
        if (globalDeckCount <= 0) return;

        globalDeckCount--;
        // 根据玩家连接ID判断抽卡者身份
        if (playerIdentity.connectionToClient.connectionId == 0)
        {
            player1Deck++;
        }
        else
        {
            player2Deck++;
        }
    }

    // 主牌库变化时更新所有客户端UI
    private void OnDeckCountChanged(int oldValue, int newValue)
    {
        GameObject.Find("Deck").GetComponent<UnityEngine.UI.Text>().text = "Deck: " + newValue;
    }

    // 玩家1手牌变化时触发UI更新
    private void OnPlayer1DeckChanged(int oldValue, int newValue)
    {
        UpdatePlayerUI(newValue, player2Deck);
    }

    // 玩家2手牌变化时触发UI更新
    private void OnPlayer2DeckChanged(int oldValue, int newValue)
    {
        UpdatePlayerUI(player1Deck, newValue);
    }

    // 根据本地玩家身份,动态分配"我的手牌"和"敌方手牌"显示内容
    private void UpdatePlayerUI(int p1Count, int p2Count)
    {
        Player localPlayer = FindObjectOfType<Player>();
        if (localPlayer == null) return;

        GameObject myHandText = GameObject.Find("Me");
        GameObject enemyText = GameObject.Find("Enemy");

        bool isLocalPlayer1 = localPlayer.connectionToClient.connectionId == 0;
        myHandText.GetComponent<UnityEngine.UI.Text>().text = "My Deck: " + (isLocalPlayer1 ? p1Count : p2Count);
        enemyText.GetComponent<UnityEngine.UI.Text>().text = "Enemy: " + (isLocalPlayer1 ? p2Count : p1Count);
    }
}

步骤2:修改Player脚本

移除本地计数变量,改为调用GameManager的服务器端逻辑:

using UnityEngine;
using UnityEngine.Networking;

public class Player : NetworkBehaviour
{
    private void Update()
    {
        // 仅本地玩家能触发抽卡操作
        if (hasAuthority && Input.GetKeyDown("e"))
        {
            CmdDrawCard();
        }
    }

    [Command]
    public void CmdDrawCard()
    {
        // 委托服务器端GameManager处理抽卡逻辑
        GameManager.Instance.HandleDrawCard(this.netIdentity);
    }
}

关键改动说明

  1. 统一权威源:所有卡牌计数由服务器端的GameManager维护,确保所有客户端状态完全一致
  2. SyncVar自动同步:无需手动发送Rpc更新UI,SyncVar会自动将服务器状态同步到所有客户端,并通过钩子触发UI更新
  3. 身份统一判断:通过玩家的connectionId区分身份,避免每个Player自己维护敌我手牌的混乱逻辑
  4. 移除本地重置:删除Player中OnStartClient的计数重置代码,改为从GameManager同步初始状态
额外注意事项
  • 确保GameManager对象在服务器端生成(添加到NetworkManager的Spawnable Prefabs列表,或在服务器启动时手动实例化)
  • 测试时先启动服务器,再加入客户端,避免connectionId分配混乱
  • 可给Player添加[SyncVar] public int playerId;,在服务器端为每个Player分配唯一ID,替代connectionId判断(更稳定)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 11:40:37