Photon Fusion中NetworkDictionary记录玩家距离出现重复值问题
解决Photon Fusion NetworkDictionary重复键值对问题
问题根源分析
输出出现重复键值对,本质是以下几个问题叠加导致:
- NetworkDictionary归属错误:如果字典挂载在每个玩家的NetworkObject上,每个玩家都会维护自己的字典副本,打印时遍历所有玩家的字典就会出现重复输出。
- 无权限修改网络集合:Fusion的Networked集合仅允许**拥有状态权限的端(通常是服务器)**修改,客户端直接修改会导致本地状态与网络同步状态冲突,产生异常条目。
- PlayerPrefs本地性问题:PlayerPrefs是本地存储,用它作为字典键无法保证网络上的一致性,多客户端同时修改同一个字典时,会引发重复添加或覆盖异常。
修复步骤
1. 重构NetworkDictionary存储位置
把playerDistances移到**全局共享的权威NetworkObject(比如GameManager)**上,确保整个游戏只有一个字典实例:
using Fusion; using UnityEngine; public class GameManager : NetworkBehaviour { [Networked] [Capacity(4)] // 根据实际玩家数量调整容量 public NetworkDictionary<NetworkString<_32>, float> PlayerDistances { get; } = new(); }
2. 改用Networked字段存储玩家名称
替换本地的PlayerPrefs,用Fusion的NetworkString<_32>同步玩家名称,确保网络上所有端看到的名称一致:
public class PlayerMovement : NetworkBehaviour { [Networked] public NetworkString<_32> PlayerName { get; set; } public GameObject finishObject; public override void OnSpawned() { // 仅拥有输入权限的本地玩家,从本地Prefs读取名称并同步 if (HasInputAuthority) { PlayerName = PlayerPrefs.GetString("PlayerName"); } } // 保留原距离计算逻辑 private float CalculateDistancePercentage() { return Vector3.Distance(transform.position, finishObject.transform.position); } }
3. 仅让服务器权威端更新字典
有两种可靠的更新方式,选择其一即可:
方式一:服务器主动计算所有玩家距离(推荐,逻辑更集中)
在GameManager的FixedUpdateNetwork中,服务器遍历所有玩家并更新距离:
public override void FixedUpdateNetwork() { // 仅服务器/权威端执行 if (!HasStateAuthority) return; foreach (var playerRef in Runner.ActivePlayers) { // 获取玩家对应的NetworkBehaviour if (playerRef.TryGetBehaviour<PlayerMovement>(out var playerBehaviour)) { var playerName = playerBehaviour.PlayerName; var distance = playerBehaviour.CalculateDistancePercentage(); // 更新字典:存在则覆盖,不存在则添加 if (PlayerDistances.ContainsKey(playerName)) { PlayerDistances.Set(playerName, distance); } else { PlayerDistances.Add(playerName, distance); } } } }
方式二:客户端通过RPC请求服务器更新
在玩家脚本中,客户端向服务器发送距离更新请求,由服务器修改字典:
public class PlayerMovement : NetworkBehaviour { public GameObject finishObject; public override void FixedUpdateNetwork() { // 仅本地玩家(拥有输入权限)发送更新请求 if (HasInputAuthority) { float distance = CalculateDistancePercentage(); Runner.SendRpc(RpcUpdatePlayerDistance, RpcTargets.StateAuthority, PlayerName, distance); } } // 仅服务器/权威端接收并处理RPC [Rpc(RpcSources.InputAuthority, RpcTargets.StateAuthority)] private void RpcUpdatePlayerDistance(NetworkString<_32> playerName, float distance) { var gameManager = Runner.FindObjectOfType<GameManager>(); if (gameManager.PlayerDistances.ContainsKey(playerName)) { gameManager.PlayerDistances.Set(playerName, distance); } else { gameManager.PlayerDistances.Add(playerName, distance); } } private float CalculateDistancePercentage() { return Vector3.Distance(transform.position, finishObject.transform.position); } }
4. 修改打印逻辑,仅打印全局字典内容
确保打印的是GameManager中的全局字典,而非每个玩家的本地字典:
public class UIManager : MonoBehaviour { void Update() { if (Input.GetKeyDown(KeyCode.U)) { var gameManager = FindObjectOfType<GameManager>(); foreach (var kvp in gameManager.PlayerDistances) { Debug.Log($"{gameManager.PlayerDistances.Count}/{gameManager.PlayerDistances.Capacity} Key:{kvp.Key} Value:{kvp.Value:F3}"); } } } }
额外注意事项
- 原代码中
playerDistances用=> default定义,会导致每次访问都返回空实例,必须改为自动属性并实例化(如步骤1中的写法)。 - 确保GameManager的NetworkObject在场景中被正确设置为权威对象(Fusion默认服务器拥有状态权限)。
内容的提问来源于stack exchange,提问作者andyching168
相关产品推荐
相关产品推荐

