在Unity网络中同步复杂的游戏对象 - UNET

4
我正在开发一款第一人称游戏,玩家可以建造复杂的物体。结构示例:
Train
- Wagon
  - Table
  - Chair
  - Chest (stores items)
  - Workshop (manufactures items, has build queue)

玩家可以创建火车,添加车厢,将物品放入车厢中,并修改已放置的物品。整个火车可以移动,物品位于变换层次结构中。
玩家可以与放置的物品交互(例如将物品放入箱子中,修改工作台建造队列),因此我需要一种在网络中识别它们的方法。这表明所有物品都应该有“NetworkIdentity”。一些物品也有它们的状态需要同步(存储的物品,建造队列)。
建议采用哪种同步方法?哪些物品应该具有“NetworkIdentity”?
将“NetworkIdentity”添加到所有物品中会防止我在编辑器中创建火车预制件(预制件只能在根上具有“NetworkIdentity”),但我可能可以接受这种情况。当车厢或物品在客户端生成时,我还必须“手动”设置父级。
另一个解决方案可能是仅将“NetworkIdentity”添加到“Train”中,然后通过“train”内的某个ID来识别物品。我无法想象如何使用“SyncVar”来实现这种方法,因为所有内容都必须在“Train”上。
1个回答

4

解决方案

  1. 将所有层级中的对象添加NetworkIdentity
  2. 忽略警告Prefab 'xxx' has several NetworkIdentity components attached to itself or its children, this is not supported.
  3. 通过脚本手动处理网络层级

我们需要确保客户端只在有父对象时才接收到子对象。同时,我们需要确保客户端在收到父对象后能够尽快地接收到子对象。

这可以通过OnRebuildObserversOnCheckObserver实现。这些方法检查客户端是否有父对象,如果有,则将玩家连接添加到观察者列表中,从而导致玩家接收该对象。

当生成父对象时,还需要调用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和Destroy消息到客户端时会通知MultiplayerGame


注:本文中的“Spawn”和“Destroy”是指游戏中创建或销毁角色或物品的操作。
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);
            }
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接