求助:如何用Photon Fusion制作Unity回合制多人桌游
使用Photon Fusion实现回合制多人桌游核心功能
一、核心思路:用状态同步替代预测移动
桌游不需要FPS类游戏的预测/插值同步逻辑,直接用Fusion的状态同步即可——所有核心游戏状态(回合归属、物品持有、物体位置)通过[Networked]标记自动同步,玩家操作通过RPC触发服务器验证后执行,避免状态不一致。
二、步骤实现
1. 创建回合管理核心(NetworkBehaviour)
新建TurnManager.cs,挂载到一个NetworkObject上作为全局游戏状态管理器,核心是同步当前回合的玩家标识:
using Fusion; public class TurnManager : NetworkBehaviour { // 同步当前回合玩家的PlayerRef(Fusion内置的玩家唯一标识) [Networked] public PlayerRef CurrentTurnPlayer { get; set; } // 初始化:设置第一个回合玩家(取房间内第一个加入的玩家) public override void Spawned() { if (Runner.IsServer) // 仅服务器能初始化全局状态 { CurrentTurnPlayer = Runner.SessionInfo.PlayerIds[0]; } } // RPC:切换到下一个回合玩家(仅当前回合玩家可调用) [Rpc(RpcSources.InputAuthority, RpcTargets.StateAuthority)] public void RpcNextTurn() { var playerIds = Runner.SessionInfo.PlayerIds; int currentIndex = System.Array.IndexOf(playerIds, CurrentTurnPlayer); // 循环切换到下一位玩家 CurrentTurnPlayer = playerIds[(currentIndex + 1) % playerIds.Length]; } }
2. 按钮权限控制(商店/下一回合按钮)
给按钮绑定脚本,仅允许当前回合玩家操作:
using Fusion; using UnityEngine.UI; public class TurnButtonHandler : MonoBehaviour { public Button shopButton; public Button nextTurnButton; private TurnManager _turnManager; private NetworkRunner _runner; void Start() { _runner = FindObjectOfType<NetworkRunner>(); _turnManager = FindObjectOfType<TurnManager>(); } void Update() { bool isCurrentTurn = _runner.LocalPlayer == _turnManager.CurrentTurnPlayer; // 动态控制按钮可交互状态 shopButton.interactable = isCurrentTurn; nextTurnButton.interactable = isCurrentTurn; } // 商店按钮点击事件 public void OnShopButtonClicked() { _turnManager.RpcPurchaseItem("治疗药水"); // 传入具体物品名 } // 下一回合按钮点击事件 public void OnNextTurnClicked() { _turnManager.RpcNextTurn(); } }
3. 购买物品同步逻辑
在TurnManager中添加物品购买的RPC,确保所有客户端同步物品状态:
// 用网络字典存储每个玩家的物品(自动同步) [Networked, Capacity(8)] private NetworkDictionary<PlayerRef, NetworkString<_16>> _playerItems => GetNetworkDictionary<PlayerRef, NetworkString<_16>>(); // RPC:购买物品(仅当前回合玩家可调用,服务器执行) [Rpc(RpcSources.InputAuthority, RpcTargets.StateAuthority)] public void RpcPurchaseItem(string itemName) { // 验证调用者是否为当前回合玩家,防止作弊 if (Runner.LocalPlayer != CurrentTurnPlayer) return; // 将物品添加到调用者的物品列表 if (_playerItems.ContainsKey(Runner.LocalPlayer)) { _playerItems[Runner.LocalPlayer] = itemName; } else { _playerItems.Add(Runner.LocalPlayer, itemName); } }
4. GameObject移动同步
要移动的物体必须是NetworkObject,添加NetworkTransform组件(Fusion自带的位置同步组件),通过RPC触发服务器端移动:
using Fusion; public class MovableObject : NetworkBehaviour { private TurnManager _turnManager; private NetworkRunner _runner; void Start() { _runner = FindObjectOfType<NetworkRunner>(); _turnManager = FindObjectOfType<TurnManager>(); } // RPC:移动物体(仅当前回合玩家可调用) [Rpc(RpcSources.InputAuthority, RpcTargets.StateAuthority)] public void RpcMoveToPosition(Vector3 targetPos) { // 权限验证 if (_turnManager.CurrentTurnPlayer != Runner.LocalPlayer) return; // 服务器端执行移动,Fusion自动同步到所有客户端 GetComponent<NetworkTransform>().TeleportToPosition(targetPos); } }
若需要平滑移动,可在
FixedUpdateNetwork中根据同步的目标位置做插值,不要直接在客户端修改位置。
5. 回合专属文本框显示
给文本框绑定脚本,仅当前回合玩家可见:
using Fusion; using UnityEngine.UI; public class TurnExclusiveText : MonoBehaviour { public Text turnPromptText; private TurnManager _turnManager; private NetworkRunner _runner; void Start() { _runner = FindObjectOfType<NetworkRunner>(); _turnManager = FindObjectOfType<TurnManager>(); } void Update() { // 根据回合状态切换文本框显示 turnPromptText.gameObject.SetActive(_runner.LocalPlayer == _turnManager.CurrentTurnPlayer); } }
三、关键注意事项
- 所有需要跨客户端同步的状态必须用
[Networked]标记,避免客户端直接修改同步变量导致状态不一致。 - 所有玩家操作必须通过RPC触发,并在RPC内做权限验证(如检查调用者是否为当前回合玩家),防止恶意作弊。
- 确保
NetworkRunner正确初始化,所有NetworkObject均通过Runner生成(不要直接用Instantiate)。
内容的提问来源于stack exchange,提问作者ViaticShadow
相关产品推荐
相关产品推荐

