Unity Netcode多人游戏:玩家靠近行星时如何重设父对象使其随行星移动?
Unity Netcode 实现玩家随行星移动的正确方案
核心原则:Netcode 中对象层级变更必须由服务器权威控制,客户端不能私自修改父对象,否则会出现跨客户端状态不同步的问题。以下是两种可靠的实现方式:
方式一:服务器判定 + ClientRpc 同步
1. 行星端服务器逻辑(检测距离并触发同步)
在行星的 NetworkBehaviour 脚本中,仅让服务器执行距离检测,当玩家进入/离开范围时调用 ClientRpc 通知所有客户端更新父子关系:
public class Planet : NetworkBehaviour { public float captureRange = 5f; private void Update() { if (!IsServer) return; // 仅服务器执行检测 foreach (var client in NetworkManager.Singleton.ConnectedClientsList) { var playerObj = client.PlayerObject; if (playerObj == null) continue; float distance = Vector3.Distance(transform.position, playerObj.transform.position); bool isInRange = distance <= captureRange; bool isAlreadyChild = playerObj.transform.parent == transform; if (isInRange && !isAlreadyChild) { SetPlayerAsChildRpc(playerObj.NetworkObject, transform); } else if (!isInRange && isAlreadyChild) { RemovePlayerFromChildRpc(playerObj.NetworkObject); } } } [ClientRpc] private void SetPlayerAsChildRpc(NetworkObject playerNetObj, Transform planetTransform) { playerNetObj.transform.SetParent(planetTransform); playerNetObj.transform.localPosition = Vector3.zero; // 避免穿模,可根据需求调整 } [ClientRpc] private void RemovePlayerFromChildRpc(NetworkObject playerNetObj) { playerNetObj.transform.SetParent(null); } }
2. 注意事项
- 必须通过
IsServer限制检测逻辑仅在服务器运行,防止客户端私自触发同步。 - 加状态判断(
isAlreadyChild)避免重复调用 RPC,减少网络开销。
方式二:NetworkVariable 状态同步(更稳定)
用 NetworkVariable 同步玩家的父行星 ID,客户端监听变量变化自动更新层级,适合需要持续同步的状态:
1. 玩家端脚本
public class Player : NetworkBehaviour { // 存储父行星的 NetworkObjectId,0 表示无父对象 private readonly NetworkVariable<ulong> _parentPlanetNetId = new NetworkVariable<ulong>(0, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server); private void Awake() { // 监听父行星 ID 变化,自动更新层级 _parentPlanetNetId.OnValueChanged += OnParentPlanetUpdated; } private void OnParentPlanetUpdated(ulong oldId, ulong newId) { if (newId == 0) { transform.SetParent(null); return; } // 通过 ID 获取行星对象并设置父级 if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(newId, out var planetNetObj)) { transform.SetParent(planetNetObj.transform); transform.localPosition = Vector3.zero; } } // 服务器调用:设置父行星 [ServerRpc(RequireOwnership = false)] public void SetParentPlanetServerRpc(ulong planetNetId) { _parentPlanetNetId.Value = planetNetId; } // 服务器调用:解除父行星 [ServerRpc(RequireOwnership = false)] public void ClearParentPlanetServerRpc() { _parentPlanetNetId.Value = 0; } }
2. 行星端脚本(服务器检测并更新玩家状态)
public class Planet : NetworkBehaviour { public float captureRange = 5f; private void Update() { if (!IsServer) return; foreach (var client in NetworkManager.Singleton.ConnectedClientsList) { var player = client.PlayerObject.GetComponent<Player>(); if (player == null) continue; float distance = Vector3.Distance(transform.position, client.PlayerObject.transform.position); bool isInRange = distance <= captureRange; if (isInRange && player._parentPlanetNetId.Value != NetworkObject.NetworkObjectId) { player.SetParentPlanetServerRpc(NetworkObject.NetworkObjectId); } else if (!isInRange && player._parentPlanetNetId.Value == NetworkObject.NetworkObjectId) { player.ClearParentPlanetServerRpc(); } } } }
通用注意事项
- 玩家移动逻辑需适配父对象坐标系:如果玩家原本用世界坐标移动,设为行星子对象后需改为局部坐标移动,否则会出现移动异常。
- 测试必须用多客户端模式:比如在 Unity Editor 中开启「Multiplayer Mode」,同时运行 Host 和 Client 实例,验证状态同步是否一致。
- 避免频繁同步:通过状态判断(如是否已经是子对象)减少 RPC 或 NetworkVariable 的更新次数,降低网络负载。
内容的提问来源于stack exchange,提问作者Podhakkin
相关产品推荐
相关产品推荐

