unity NGO 代码教程:网络上动态生成物体

前言

生成一个网络对象有多种办法,但始终

  1. 只能由Server/Host生成/销毁
  2. 必须有network  object组件
  3. 要在NetworkManager中的NetworkPrefebList中注册

建议先看完第一章:unity netcode for gameobject(NGO)逻辑代码教程-CSDN博客

最简单直接的方法

 简单的Debug程序:

private void Update() {
        if (!IsOwner) {
            return;
        }
        if (Input.GetKeyDown(KeyCode.F)) {
            Spawn();
        }
        if (Input.GetKeyDown(KeyCode.G)) {
            DeSpawn();
        }
    }

生成

//  NetworkObject networkObject;
    private void Spawn() {
        //只有服务器可以创建/销毁对象
        if (IsServer) {
//先在本地实例化
            GameObject prefebInstance = Instantiate(SpawnPrefeb);
//获取NetworkObject组件
            networkObject = prefebInstance.GetComponent<NetworkObject>();
//调用Spawn函数,它的参数含义为是否随着场景销毁而销毁,默认为false
            networkObject.Spawn();
        }
    }

销毁

注意Despawn会默认销毁对象,可以理解为在其后调用了Destroy(networkObject.gameObject)

之后我们会修改这个特点让它对象池化

private void DeSpawn() {
        //只有服务器可以创建/销毁对象
        if (IsServer) {
            Debug.Log("Despawn");
            networkObject.Despawn();
        }
    }

演示:注意到右边Hierarchy中NetObject的销毁

使用对象池

INetworkPrefabInstanceHandler接口介绍

首先,你可以很轻松地验证:在host/server中调用networkObject.Despawn()后,不仅networkObject消失(despawn,不应该理解为摧毁),networkObject.gameObject摧毁

那么INetworkPrefabInstanceHandler允许你取消networkObject.gameObject摧毁的过程,改为networkObject.gameObject.SetActive(false)

这样的过程就像是:物体在主机生成本地实例,出池时networkObject.Spawn()在网络上生成,入池时networkObject.Despawn(),在网络上消失,同时主机本地实例隐藏,但他仍然存在,你可以激活该实例然后重复这个过程。

//官方定义
namespace Unity.Netcode {
    public interface INetworkPrefabInstanceHandler {

        //network object调用Despawn()后,在Host和Client中调用
        void Destroy(NetworkObject networkObject);


        //当networkObject.Spawn()调用后,在Client中调用,注意不会在Host和Server中调用。
        //为什么不在Host中调用?我的解释是Host中存在network.gameobject的Inactive实体,Host在本地调用Spawn()就够了
        NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation);
    }
}

我们直接来看实际使用场景。

单物体对象池

unity官方给出了单物体对象池的示例代码,它的通用性不强,建议了解就好,完全可以用多物体对象池替代

官方代码

public class SinglePooledDynamicSpawner : NetworkBehaviour, INetworkPrefabInstanceHandler
{
    public GameObject PrefabToSpawn;
    public bool SpawnPrefabAutomatically;

    private GameObject m_PrefabInstance;
    private NetworkObject m_SpawnedNetworkObject;


    private void Start()
    {
        // Instantiate our instance when we start (for both clients and server)
        m_PrefabInstance = Instantiate(PrefabToSpawn);

        // Get the NetworkObject component assigned to the Prefab instance
        m_SpawnedNetworkObject = m_PrefabInstance.GetComponent<NetworkObject>();

        // Set it to be inactive
        m_PrefabInstance.SetActive(false);
    }

    private IEnumerator DespawnTimer()
    {
        yield return new WaitForSeconds(2);
        m_SpawnedNetworkObject.Despawn();
        StartCoroutine(SpawnTimer());
        yield break;
    }

    private IEnumerator SpawnTimer()
    {
        yield return new WaitForSeconds(2);
        SpawnInstance();
        yield break;
    }

    /// <summary>
    /// Invoked only on clients and not server or host
    /// INetworkPrefabInstanceHandler.Instantiate implementation
    /// Called when Netcode for GameObjects need an instance to be spawned
    /// </summary>
    public NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation)
    {
        m_PrefabInstance.SetActive(true);
        m_PrefabInstance.transform.position = transform.position;
        m_PrefabInstance.transform.rotation = transform.rotation;
        return m_SpawnedNetworkObject;
    }

    /// <summary>
    /// Client and Server side
    /// INetworkPrefabInstanceHandler.Destroy implementation
    /// </summary>
    public void Destroy(NetworkObject networkObject)
    {
        m_PrefabInstance.SetActive(false);
    }

    public void SpawnInstance()
    {
        if (!IsServer)
        {
            return;
        }

        if (m_PrefabInstance != null && m_SpawnedNetworkObject != null && !m_SpawnedNetworkObject.IsSpawned)
        {
            m_PrefabInstance.SetActive(true);
            m_SpawnedNetworkObject.Spawn();
            StartCoroutine(DespawnTimer());
        }
    }

    public override void OnNetworkSpawn()
    {
        // We register our network Prefab and this NetworkBehaviour that implements the
        // INetworkPrefabInstanceHandler interface with the Prefab handler
        NetworkManager.PrefabHandler.AddHandler(PrefabToSpawn, this);

        if (!IsServer || !SpawnPrefabAutomatically)
        {
            return;
        }

        if (SpawnPrefabAutomatically)
        {
            SpawnInstance();
        }
    }

    public override void OnNetworkDespawn()
    {
        if (m_SpawnedNetworkObject != null && m_SpawnedNetworkObject.IsSpawned)
        {
            m_SpawnedNetworkObject.Despawn();
        }
        base.OnNetworkDespawn();
    }

    public override void OnDestroy()
    {
        // This example destroys the
        if (m_PrefabInstance != null)
        {
            // Always deregister the prefab
            NetworkManager.Singleton.PrefabHandler.RemoveHandler(PrefabToSpawn);
            Destroy(m_PrefabInstance);
        }
        base.OnDestroy();
    }
}

我们把目光放在INetworkPrefebInstanceHandler接口的2个函数:Instantiate,Destroy

以及2个控制循环生成销毁对象的协程

 private IEnumerator DespawnTimer() {
        yield return new WaitForSeconds(2);
        m_SpawnedNetworkObject.Despawn();
//Destroy在这里自动调用
        StartCoroutine(SpawnTimer());
        yield break;
    }

    private IEnumerator SpawnTimer() {
        yield return new WaitForSeconds(2);
        SpawnInstance();
        yield break;
    }
    public void SpawnInstance() {
        if (!IsServer) {
            return;
        }

        if (prefabInstance != null && m_SpawnedNetworkObject != null && !m_SpawnedNetworkObject.IsSpawned) {
            prefabInstance.SetActive(true);
            m_SpawnedNetworkObject.Spawn();
            //Instantiate在这里自动调用
            StartCoroutine(DespawnTimer());
        }
    }

想象一下:Host/Server 实例化一个对象->Spawn(Sever and Client) ,SetAcitve(true)(Server)->Despawn(Sever and Client),SetActive(false)(Server),Not Destroy

之后重复这个过程

演示

协程等待时间修改成了1秒

多物体对象池

Untiy 官方给出了一份多物体网络对象池的通用代码,我们来看看它如何使用

Object Pooling | Unity Multiplayer Networking (unity3d.com)

官方代码

这是一份通用性很强的组件,你可以复制并且通过一点点的配置就能让它管理你的对象

using System;
using System.Collections.Generic;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.Pool;

namespace Unity.BossRoom.Infrastructure {
    /// <summary>
    /// Object Pool for networked objects, used for controlling how objects are spawned by Netcode. Netcode by default
    /// will allocate new memory when spawning new objects. With this Networked Pool, we're using the ObjectPool to
    /// reuse objects.
    /// Boss Room uses this for projectiles. In theory it should use this for imps too, but we wanted to show vanilla spawning vs pooled spawning.
    /// Hooks to NetworkManager's prefab handler to intercept object spawning and do custom actions.
    /// </summary>
    public class NetworkObjectPool : NetworkBehaviour {
        public static NetworkObjectPool Singleton { get; private set; }

        [SerializeField]
        List<PoolConfigObject> PooledPrefabsList;
        //已生成对象池的预制体
        HashSet<GameObject> m_Prefabs = new HashSet<GameObject>();
        //预制体与管理它的对象池
        Dictionary<GameObject, ObjectPool<NetworkObject>> m_PooledObjects = new Dictionary<GameObject, ObjectPool<NetworkObject>>();

        public void Awake() {
            if (Singleton != null && Singleton != this) {
                Destroy(gameObject);
            }
            else {
                Singleton = this;
            }
        }

        public override void OnNetworkSpawn() {
            // Registers all objects in PooledPrefabsList to the cache.
            foreach (var configObject in PooledPrefabsList) {
                RegisterPrefabInternal(configObject.Prefab, configObject.PrewarmCount);
            }
        }

        public override void OnNetworkDespawn() {
            // Unregisters all objects in PooledPrefabsList from the cache.
            foreach (var prefab in m_Prefabs) {
                // Unregister Netcode Spawn handlers
                NetworkManager.Singleton.PrefabHandler.RemoveHandler(prefab);
                m_PooledObjects[prefab].Clear();
            }
            m_PooledObjects.Clear();
            m_Prefabs.Clear();
        }
        //这段代码会在你试图添加一个非网络对象时报错
        public void OnValidate() {
            for (var i = 0; i < PooledPrefabsList.Count; i++) {
                var prefab = PooledPrefabsList[i].Prefab;
                if (prefab != null) {
                    Assert.IsNotNull(prefab.GetComponent<NetworkObject>(), $"{nameof(NetworkObjectPool)}: Pooled prefab \"{prefab.name}\" at index {i.ToString()} has no {nameof(NetworkObject)} component.");
                }
            }
        }

        /// <summary>
        /// Gets an instance of the given prefab from the pool. The prefab must be registered to the pool.
        /// </summary>
        /// <remarks>
        /// To spawn a NetworkObject from one of the pools, this must be called on the server, then the instance
        /// returned from it must be spawned on the server. This method will then also be called on the client by the
        /// PooledPrefabInstanceHandler when the client receives a spawn message for a prefab that has been registered
        /// here.
        /// </remarks>
        /// <param name="prefab"></param>
        /// <param name="position">The position to spawn the object at.</param>
        /// <param name="rotation">The rotation to spawn the object with.</param>
        /// <returns></returns>
        public NetworkObject GetNetworkObject(GameObject prefab, Vector3 position, Quaternion rotation) {
            var networkObject = m_PooledObjects[prefab].Get();

            var noTransform = networkObject.transform;
            noTransform.position = position;
            noTransform.rotation = rotation;

            return networkObject;
        }

        /// <summary>
        /// Return an object to the pool (reset objects before returning).
        /// </summary>
        public void ReturnNetworkObject(NetworkObject networkObject, GameObject prefab) {
            m_PooledObjects[prefab].Release(networkObject);
        }

        /// <summary>
        /// Builds up the cache for a prefab.
        /// </summary>
        void RegisterPrefabInternal(GameObject prefab, int prewarmCount) {
            NetworkObject CreateFunc() {
                return Instantiate(prefab).GetComponent<NetworkObject>();
            }

            void ActionOnGet(NetworkObject networkObject) {
                networkObject.gameObject.SetActive(true);
            }

            void ActionOnRelease(NetworkObject networkObject) {
                networkObject.gameObject.SetActive(false);
            }

            void ActionOnDestroy(NetworkObject networkObject) {
                Destroy(networkObject.gameObject);
            }

            m_Prefabs.Add(prefab);

            // Create the pool
            m_PooledObjects[prefab] = new ObjectPool<NetworkObject>(CreateFunc, ActionOnGet, ActionOnRelease, ActionOnDestroy, defaultCapacity: prewarmCount);
            // Populate the pool
            var prewarmNetworkObjects = new List<NetworkObject>();
            for (var i = 0; i < prewarmCount; i++) {
                prewarmNetworkObjects.Add(m_PooledObjects[prefab].Get());
            }
            foreach (var networkObject in prewarmNetworkObjects) {
                m_PooledObjects[prefab].Release(networkObject);
            }

            // Register Netcode Spawn handlers
            NetworkManager.Singleton.PrefabHandler.AddHandler(prefab, new PooledPrefabInstanceHandler(prefab, this));
        }
    }

    [Serializable]
    struct PoolConfigObject {
        public GameObject Prefab;
        public int PrewarmCount;
    }
    //PooledPrefabInstanceHandler will handle it on the client(s) when the network object's Spawn or Despawn method is called,
    //via its Instantiate and Destroy methods. Inside those methods, the PooledPrefabInstanceHandler simply calls the pool to get the corresponding object, or to return it.

    class PooledPrefabInstanceHandler : INetworkPrefabInstanceHandler {
        GameObject m_Prefab;
        NetworkObjectPool m_Pool;

        public PooledPrefabInstanceHandler(GameObject prefab, NetworkObjectPool pool) {
            m_Prefab = prefab;
            m_Pool = pool;
        }
        //你可以很轻易的验证这2个函数的调用时机
        //当networkObject.Spawn()调用后,在Client中调用,注意不会在Host和Server中调用。
        NetworkObject INetworkPrefabInstanceHandler.Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation) {
            Debug.Log("  NetworkObject INetworkPrefabInstanceHandler.Instantiate");
            return m_Pool.GetNetworkObject(m_Prefab, position, rotation);
        }
        //network object调用Despawn()后,在Host和Client中调用
        void INetworkPrefabInstanceHandler.Destroy(NetworkObject networkObject) {
            Debug.Log("void INetworkPrefabInstanceHandler.Destroy");
            m_Pool.ReturnNetworkObject(networkObject, m_Prefab);
        }
    }

}

配置

创建一个空物体,挂载脚本,添加NetworkObject组件

设置池化预制体,它需要在NetworkPrefebList中注册。

PrewarmCount指的是预制体初始时拥有多少实例,他们在生成时都会被设置成Inactive

使用它

在unity官方示例游戏中,它的使用非常简单

以下是我写的Debug版本

生成对象:从池中取出对象,再调用Spawn()即可

private void Spawn() {
        //只有服务器可以创建/销毁对象
        if (IsServer) {
            //public GameObject SpawnPrefeb;
            networkObject= NetworkObjectPool.Singleton.GetNetworkObject(SpawnPrefeb, transform.position, Quaternion.identity);

            Debug.Log("Spawn");
            networkObject.Spawn();
        }
    }

销毁对象:获得networkObject的引用,直接调用Despawn()

注意NetworkObjectPoo.ReturnNetworkObject是不需要的,它应该被改成private,只为内部的INetworkPrefabInstanceHandler服务。

private void DeSpawn() {
        //只有服务器可以创建/销毁对象
        if (IsServer) {
            Debug.Log("Despawn");
            networkObject.Despawn();
        }
    }

演示

在控制栏中能够看到各个函数的调用情况,INetworkPrefabInstanceHandler.Instantiate明显没有在Host中调用,可以验证它会在client中调用,这里不再展示

RPC实现法?

RPC也能够实现,但是不清楚性能怎么样

基本思路是把SetAcitve同步给其他客户端就行

这里就留个悬念吧,日后有空尝试一下。

如果有人能有兴趣实现,请一定联系我,我会毫不吝啬地加上你的大名!

  • 19
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值