我正在开发第一人称游戏,玩家可以在其中构建复杂的对象。结构示例:

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.
  • 通过脚本手动处理网络上的层次结构

  • 我们需要确保客户端只有在它有父对象时才能接收子对象。我们还需要确保客户端在收到父对象时尽快收到子对象。

    这是通过 OnRebuildObserversOnCheckObserver 实现的。这些方法检查客户端是否有父对象,当它添加玩家连接到观察者列表时,这会导致玩家接收对象。

    我们还需要在生成父对象时调用 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/

    10-12 02:07