我正在开发第一人称游戏,玩家可以在其中构建复杂的对象。结构示例:
Train
- Wagon
- Table
- Chair
- Chest (stores items)
- Workshop (manufactures items, has build queue)
玩家可以创建 trains ,添加 wagons ,将 对象 放入 wagons ,修改放置的 对象 。整列火车可以移动,物体处于变换层次。
玩家可以与放置的对象进行交互(例如,将物品放入箱子中,修改车间构建队列),因此我需要一种方法来跨网络识别它们。这表明所有对象都应该有
NetworkIdentity
。一些对象也有需要同步的状态(存储的项目、构建队列)。什么是建议的同步方法?哪些对象应该有
NetworkIdentity
?将
NetworkIdentity
添加到所有这些会阻止我在编辑器中创建 Train 预制件(预制件只能在 root 上具有 NetworkIdentity
),但我可能可以接受。当货车或对象在客户端上产生时,我还必须“手动”设置父级。另一种解决方案可能是仅将
NetworkIdentity
添加到 Train ,然后通过 train 中的某个 ID 识别对象。我无法想象如何通过这种方法使用 SyncVar
,因为一切都必须在 Train 上。 最佳答案
解决方案
NetworkIdentity
添加到层次结构中的所有对象 Prefab 'xxx' has several NetworkIdentity components attached to itself or its children, this is not supported.
我们需要确保客户端只有在它有父对象时才能接收子对象。我们还需要确保客户端在收到父对象时尽快收到子对象。
这是通过
OnRebuildObservers
和 OnCheckObserver
实现的。这些方法检查客户端是否有父对象,当它添加玩家连接到观察者列表时,这会导致玩家接收对象。我们还需要在生成父对象时调用
NetworkIdentity.RebuildObservers
。这是通过自定义连接类实现的,它在客户端生成对象时通知 MultiplayerGame
(连接发送 Spawn 消息)。完整脚本如下。
网络 child
作为子对象的网络组件的基类,例如货车,货车中的对象。
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
}
网络通知连接
当 Spawn 和 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);
}
}
}
}
关于unity3d - 在 Unity Networking 中同步复杂的游戏对象 - UNET,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/45908172/