在 Unity Networking - UNET 中同步复杂的游戏对象
Posted
技术标签:
【中文标题】在 Unity Networking - UNET 中同步复杂的游戏对象【英文标题】:Synchronizing complex GameObjects in Unity Networking - UNET 【发布时间】:2018-02-05 02:01:25 【问题描述】:我正在开发玩家可以构建复杂物体的第一人称游戏。结构示例:
Train
- Wagon
- Table
- Chair
- Chest (stores items)
- Workshop (manufactures items, has build queue)
玩家可以创建火车,添加货车,将对象放入货车,修改放置的 >对象。整列火车可以移动,物体在变换层次中。
玩家可以与放置的对象进行交互(例如将物品放入箱子,修改车间构建队列),因此我需要一种跨网络识别它们的方法。这表明所有对象都应该有NetworkIdentity
。一些对象也有需要同步的状态(存储项目、构建队列)。
建议的同步方法是什么?哪些对象应该有NetworkIdentity
?
将NetworkIdentity
添加到所有这些会阻止我在编辑器中创建火车预制件(预制件只能在根目录上拥有NetworkIdentity
),但我可能会接受。当客户端上生成旅行车或对象时,我还必须“手动”设置父级。
另一种解决方案可能是将NetworkIdentity
仅添加到Train,然后通过train 中的某个ID 来识别对象。我无法想象如何通过这种方法使用SyncVar
,因为一切都必须在Train上。
【问题讨论】:
【参考方案1】:解决方案
-
将
NetworkIdentity
添加到层次结构中的所有对象
忽略警告Prefab 'xxx' has several NetworkIdentity components attached to itself or its children, this is not supported.
通过脚本手动处理网络上的层次结构
我们需要确保客户端只有在有父对象时才接收子对象。我们还需要确保客户端在收到父对象时尽快收到子对象。
这是通过OnRebuildObservers
和OnCheckObserver
实现的。这些方法检查客户端是否有父对象,当它有父对象时,它会将玩家连接添加到观察者列表中,这会导致玩家接收对象。
当父对象生成时,我们还需要调用NetworkIdentity.RebuildObservers
。这是通过自定义连接类实现的,当对象在客户端生成时通知MultiplayerGame
(连接发送 Spawn 消息)。
完整的脚本如下。
NetworkChild
子对象上的网络组件的基类,例如马车,马车里的物体。
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
/// <summary>
/// Base component for network child objects.
/// </summary>
public abstract class NetworkChild : NetworkBehaviour
private NetworkIdentity m_networkParent;
[SyncVar(hook = "OnNetParentChanged")]
private NetworkInstanceId m_networkParentId;
public NetworkIdentity NetworkParent
get return m_networkParent;
#region Server methods
public override void OnStartServer()
UpdateParent();
base.OnStartServer();
[ServerCallback]
public void RefreshParent()
UpdateParent();
GetComponent<NetworkIdentity>().RebuildObservers(false);
void UpdateParent()
NetworkIdentity parent = transform.parent != null ? transform.parent.GetComponentInParent<NetworkIdentity>() : null;
m_networkParent = parent;
m_networkParentId = parent != null ? parent.netId : NetworkInstanceId.Invalid;
public override bool OnCheckObserver(NetworkConnection conn)
// Parent id might not be set yet (but parent is)
m_networkParentId = m_networkParent != null ? m_networkParent.netId : NetworkInstanceId.Invalid;
if (m_networkParent != null && m_networkParent.observers != null)
// Visible only when parent is visible
return m_networkParent.observers.Contains(conn);
return false;
public override bool OnRebuildObservers(HashSet<NetworkConnection> observers, bool initialize)
// Parent id might not be set yet (but parent is)
m_networkParentId = m_networkParent != null ? m_networkParent.netId : NetworkInstanceId.Invalid;
if (m_networkParent != null && m_networkParent.observers != null)
// Who sees parent will see child too
foreach (var parentObserver in m_networkParent.observers)
observers.Add(parentObserver);
return true;
#endregion
#region Client Methods
public override void OnStartClient()
base.OnStartClient();
FindParent();
void OnNetParentChanged(NetworkInstanceId newNetParentId)
if (m_networkParentId != newNetParentId)
m_networkParentId = newNetParentId;
FindParent();
OnParentChanged();
/// <summary>
/// Called on client when server sends new parent
/// </summary>
protected virtual void OnParentChanged()
private void FindParent()
if (NetworkServer.localClientActive)
// Both server and client, NetworkParent already set
return;
if (!ClientScene.objects.TryGetValue(m_networkParentId, out m_networkParent))
Debug.AssertFormat(false, "NetworkChild, parent object 0 not found", m_networkParentId);
#endregion
NetworkNotifyConnection
当 Spawn and Destroy 消息发送到客户端时通知 MultiplayerGame
的自定义连接类。
using System;
using UnityEngine;
using UnityEngine.Networking;
public class NetworkNotifyConnection : NetworkConnection
public MultiplayerGame Game;
public override void Initialize(string networkAddress, int networkHostId, int networkConnectionId, HostTopology hostTopology)
base.Initialize(networkAddress, networkHostId, networkConnectionId, hostTopology);
Game = NetworkManager.singleton.GetComponent<MultiplayerGame>();
public override bool SendByChannel(short msgType, MessageBase msg, int channelId)
Prefilter(msgType, msg, channelId);
if (base.SendByChannel(msgType, msg, channelId))
Postfilter(msgType, msg, channelId);
return true;
return false;
private void Prefilter(short msgType, MessageBase msg, int channelId)
private void Postfilter(short msgType, MessageBase msg, int channelId)
if (msgType == MsgType.ObjectSpawn || msgType == MsgType.ObjectSpawnScene)
// NetworkExtensions.GetObjectSpawnNetId uses reflection to extract private 'netId' field
Game.OnObjectSpawn(NetworkExtensions.GetObjectSpawnNetId(msg), this);
else if (msgType == MsgType.ObjectDestroy)
// NetworkExtensions.GetObjectDestroyNetId uses reflection to extract private 'netId' field
Game.OnObjectDestroy(NetworkExtensions.GetObjectDestroyNetId(msg), this);
多人游戏
NetworkManager
上的组件,在服务器启动时设置自定义网络连接类。
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
/// <summary>
/// Simple component which starts multiplayer game right on start.
/// </summary>
public class MultiplayerGame : MonoBehaviour
HashSet<NetworkIdentity> m_dirtyObj = new HashSet<NetworkIdentity>();
private void Start()
var net = NetworkManager.singleton;
var host = net.StartHost();
if (host != null)
NetworkServer.SetNetworkConnectionClass<NetworkNotifyConnection>();
/// <summary>
/// Reliable callback called on server when client receives new object.
/// </summary>
public void OnObjectSpawn(NetworkInstanceId objectId, NetworkConnection conn)
var obj = NetworkServer.FindLocalObject(objectId);
RefreshChildren(obj.transform);
/// <summary>
/// Reliable callback called on server when client loses object.
/// </summary>
public void OnObjectDestroy(NetworkInstanceId objectId, NetworkConnection conn)
void RefreshChildren(Transform obj)
foreach (var child in obj.GetChildren())
NetworkIdentity netId;
if (child.TryGetComponent(out netId))
m_dirtyObj.Add(netId);
else
RefreshChildren(child);
private void Update()
NetworkIdentity netId;
while (m_dirtyObj.RemoveFirst(out netId))
if (netId != null)
netId.RebuildObservers(false);
【讨论】:
以上是关于在 Unity Networking - UNET 中同步复杂的游戏对象的主要内容,如果未能解决你的问题,请参考以下文章
Unity5.1 新的网络引擎UNET(十五) Networking 引用--上
Unity 5 - UNET - 将拖动的游戏对象同步为客户端(HL2 重力枪风格)