当前位置:   article > 正文

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

networkobjectdespawner

前言

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

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

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

最简单直接的方法

 简单的Debug程序:

  1. private void Update() {
  2. if (!IsOwner) {
  3. return;
  4. }
  5. if (Input.GetKeyDown(KeyCode.F)) {
  6. Spawn();
  7. }
  8. if (Input.GetKeyDown(KeyCode.G)) {
  9. DeSpawn();
  10. }
  11. }

生成

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

销毁

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

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

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

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

使用对象池

INetworkPrefabInstanceHandler接口介绍

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

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

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

  1. //官方定义
  2. namespace Unity.Netcode {
  3. public interface INetworkPrefabInstanceHandler {
  4. //network object调用Despawn()后,在Host和Client中调用
  5. void Destroy(NetworkObject networkObject);
  6. //当networkObject.Spawn()调用后,在Client中调用,注意不会在Host和Server中调用。
  7. //为什么不在Host中调用?我的解释是Host中存在network.gameobject的Inactive实体,Host在本地调用Spawn()就够了
  8. NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation);
  9. }
  10. }

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

单物体对象池

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

官方代码

  1. public class SinglePooledDynamicSpawner : NetworkBehaviour, INetworkPrefabInstanceHandler
  2. {
  3. public GameObject PrefabToSpawn;
  4. public bool SpawnPrefabAutomatically;
  5. private GameObject m_PrefabInstance;
  6. private NetworkObject m_SpawnedNetworkObject;
  7. private void Start()
  8. {
  9. // Instantiate our instance when we start (for both clients and server)
  10. m_PrefabInstance = Instantiate(PrefabToSpawn);
  11. // Get the NetworkObject component assigned to the Prefab instance
  12. m_SpawnedNetworkObject = m_PrefabInstance.GetComponent<NetworkObject>();
  13. // Set it to be inactive
  14. m_PrefabInstance.SetActive(false);
  15. }
  16. private IEnumerator DespawnTimer()
  17. {
  18. yield return new WaitForSeconds(2);
  19. m_SpawnedNetworkObject.Despawn();
  20. StartCoroutine(SpawnTimer());
  21. yield break;
  22. }
  23. private IEnumerator SpawnTimer()
  24. {
  25. yield return new WaitForSeconds(2);
  26. SpawnInstance();
  27. yield break;
  28. }
  29. /// <summary>
  30. /// Invoked only on clients and not server or host
  31. /// INetworkPrefabInstanceHandler.Instantiate implementation
  32. /// Called when Netcode for GameObjects need an instance to be spawned
  33. /// </summary>
  34. public NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation)
  35. {
  36. m_PrefabInstance.SetActive(true);
  37. m_PrefabInstance.transform.position = transform.position;
  38. m_PrefabInstance.transform.rotation = transform.rotation;
  39. return m_SpawnedNetworkObject;
  40. }
  41. /// <summary>
  42. /// Client and Server side
  43. /// INetworkPrefabInstanceHandler.Destroy implementation
  44. /// </summary>
  45. public void Destroy(NetworkObject networkObject)
  46. {
  47. m_PrefabInstance.SetActive(false);
  48. }
  49. public void SpawnInstance()
  50. {
  51. if (!IsServer)
  52. {
  53. return;
  54. }
  55. if (m_PrefabInstance != null && m_SpawnedNetworkObject != null && !m_SpawnedNetworkObject.IsSpawned)
  56. {
  57. m_PrefabInstance.SetActive(true);
  58. m_SpawnedNetworkObject.Spawn();
  59. StartCoroutine(DespawnTimer());
  60. }
  61. }
  62. public override void OnNetworkSpawn()
  63. {
  64. // We register our network Prefab and this NetworkBehaviour that implements the
  65. // INetworkPrefabInstanceHandler interface with the Prefab handler
  66. NetworkManager.PrefabHandler.AddHandler(PrefabToSpawn, this);
  67. if (!IsServer || !SpawnPrefabAutomatically)
  68. {
  69. return;
  70. }
  71. if (SpawnPrefabAutomatically)
  72. {
  73. SpawnInstance();
  74. }
  75. }
  76. public override void OnNetworkDespawn()
  77. {
  78. if (m_SpawnedNetworkObject != null && m_SpawnedNetworkObject.IsSpawned)
  79. {
  80. m_SpawnedNetworkObject.Despawn();
  81. }
  82. base.OnNetworkDespawn();
  83. }
  84. public override void OnDestroy()
  85. {
  86. // This example destroys the
  87. if (m_PrefabInstance != null)
  88. {
  89. // Always deregister the prefab
  90. NetworkManager.Singleton.PrefabHandler.RemoveHandler(PrefabToSpawn);
  91. Destroy(m_PrefabInstance);
  92. }
  93. base.OnDestroy();
  94. }
  95. }

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

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

  1. private IEnumerator DespawnTimer() {
  2. yield return new WaitForSeconds(2);
  3. m_SpawnedNetworkObject.Despawn();
  4. //Destroy在这里自动调用
  5. StartCoroutine(SpawnTimer());
  6. yield break;
  7. }
  8. private IEnumerator SpawnTimer() {
  9. yield return new WaitForSeconds(2);
  10. SpawnInstance();
  11. yield break;
  12. }
  13. public void SpawnInstance() {
  14. if (!IsServer) {
  15. return;
  16. }
  17. if (prefabInstance != null && m_SpawnedNetworkObject != null && !m_SpawnedNetworkObject.IsSpawned) {
  18. prefabInstance.SetActive(true);
  19. m_SpawnedNetworkObject.Spawn();
  20. //Instantiate在这里自动调用
  21. StartCoroutine(DespawnTimer());
  22. }
  23. }

想象一下: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)

官方代码

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

  1. using System;
  2. using System.Collections.Generic;
  3. using Unity.Netcode;
  4. using UnityEngine;
  5. using UnityEngine.Assertions;
  6. using UnityEngine.Pool;
  7. namespace Unity.BossRoom.Infrastructure {
  8. /// <summary>
  9. /// Object Pool for networked objects, used for controlling how objects are spawned by Netcode. Netcode by default
  10. /// will allocate new memory when spawning new objects. With this Networked Pool, we're using the ObjectPool to
  11. /// reuse objects.
  12. /// 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.
  13. /// Hooks to NetworkManager's prefab handler to intercept object spawning and do custom actions.
  14. /// </summary>
  15. public class NetworkObjectPool : NetworkBehaviour {
  16. public static NetworkObjectPool Singleton { get; private set; }
  17. [SerializeField]
  18. List<PoolConfigObject> PooledPrefabsList;
  19. //已生成对象池的预制体
  20. HashSet<GameObject> m_Prefabs = new HashSet<GameObject>();
  21. //预制体与管理它的对象池
  22. Dictionary<GameObject, ObjectPool<NetworkObject>> m_PooledObjects = new Dictionary<GameObject, ObjectPool<NetworkObject>>();
  23. public void Awake() {
  24. if (Singleton != null && Singleton != this) {
  25. Destroy(gameObject);
  26. }
  27. else {
  28. Singleton = this;
  29. }
  30. }
  31. public override void OnNetworkSpawn() {
  32. // Registers all objects in PooledPrefabsList to the cache.
  33. foreach (var configObject in PooledPrefabsList) {
  34. RegisterPrefabInternal(configObject.Prefab, configObject.PrewarmCount);
  35. }
  36. }
  37. public override void OnNetworkDespawn() {
  38. // Unregisters all objects in PooledPrefabsList from the cache.
  39. foreach (var prefab in m_Prefabs) {
  40. // Unregister Netcode Spawn handlers
  41. NetworkManager.Singleton.PrefabHandler.RemoveHandler(prefab);
  42. m_PooledObjects[prefab].Clear();
  43. }
  44. m_PooledObjects.Clear();
  45. m_Prefabs.Clear();
  46. }
  47. //这段代码会在你试图添加一个非网络对象时报错
  48. public void OnValidate() {
  49. for (var i = 0; i < PooledPrefabsList.Count; i++) {
  50. var prefab = PooledPrefabsList[i].Prefab;
  51. if (prefab != null) {
  52. Assert.IsNotNull(prefab.GetComponent<NetworkObject>(), $"{nameof(NetworkObjectPool)}: Pooled prefab \"{prefab.name}\" at index {i.ToString()} has no {nameof(NetworkObject)} component.");
  53. }
  54. }
  55. }
  56. /// <summary>
  57. /// Gets an instance of the given prefab from the pool. The prefab must be registered to the pool.
  58. /// </summary>
  59. /// <remarks>
  60. /// To spawn a NetworkObject from one of the pools, this must be called on the server, then the instance
  61. /// returned from it must be spawned on the server. This method will then also be called on the client by the
  62. /// PooledPrefabInstanceHandler when the client receives a spawn message for a prefab that has been registered
  63. /// here.
  64. /// </remarks>
  65. /// <param name="prefab"></param>
  66. /// <param name="position">The position to spawn the object at.</param>
  67. /// <param name="rotation">The rotation to spawn the object with.</param>
  68. /// <returns></returns>
  69. public NetworkObject GetNetworkObject(GameObject prefab, Vector3 position, Quaternion rotation) {
  70. var networkObject = m_PooledObjects[prefab].Get();
  71. var noTransform = networkObject.transform;
  72. noTransform.position = position;
  73. noTransform.rotation = rotation;
  74. return networkObject;
  75. }
  76. /// <summary>
  77. /// Return an object to the pool (reset objects before returning).
  78. /// </summary>
  79. public void ReturnNetworkObject(NetworkObject networkObject, GameObject prefab) {
  80. m_PooledObjects[prefab].Release(networkObject);
  81. }
  82. /// <summary>
  83. /// Builds up the cache for a prefab.
  84. /// </summary>
  85. void RegisterPrefabInternal(GameObject prefab, int prewarmCount) {
  86. NetworkObject CreateFunc() {
  87. return Instantiate(prefab).GetComponent<NetworkObject>();
  88. }
  89. void ActionOnGet(NetworkObject networkObject) {
  90. networkObject.gameObject.SetActive(true);
  91. }
  92. void ActionOnRelease(NetworkObject networkObject) {
  93. networkObject.gameObject.SetActive(false);
  94. }
  95. void ActionOnDestroy(NetworkObject networkObject) {
  96. Destroy(networkObject.gameObject);
  97. }
  98. m_Prefabs.Add(prefab);
  99. // Create the pool
  100. m_PooledObjects[prefab] = new ObjectPool<NetworkObject>(CreateFunc, ActionOnGet, ActionOnRelease, ActionOnDestroy, defaultCapacity: prewarmCount);
  101. // Populate the pool
  102. var prewarmNetworkObjects = new List<NetworkObject>();
  103. for (var i = 0; i < prewarmCount; i++) {
  104. prewarmNetworkObjects.Add(m_PooledObjects[prefab].Get());
  105. }
  106. foreach (var networkObject in prewarmNetworkObjects) {
  107. m_PooledObjects[prefab].Release(networkObject);
  108. }
  109. // Register Netcode Spawn handlers
  110. NetworkManager.Singleton.PrefabHandler.AddHandler(prefab, new PooledPrefabInstanceHandler(prefab, this));
  111. }
  112. }
  113. [Serializable]
  114. struct PoolConfigObject {
  115. public GameObject Prefab;
  116. public int PrewarmCount;
  117. }
  118. //PooledPrefabInstanceHandler will handle it on the client(s) when the network object's Spawn or Despawn method is called,
  119. //via its Instantiate and Destroy methods. Inside those methods, the PooledPrefabInstanceHandler simply calls the pool to get the corresponding object, or to return it.
  120. class PooledPrefabInstanceHandler : INetworkPrefabInstanceHandler {
  121. GameObject m_Prefab;
  122. NetworkObjectPool m_Pool;
  123. public PooledPrefabInstanceHandler(GameObject prefab, NetworkObjectPool pool) {
  124. m_Prefab = prefab;
  125. m_Pool = pool;
  126. }
  127. //你可以很轻易的验证这2个函数的调用时机
  128. //当networkObject.Spawn()调用后,在Client中调用,注意不会在Host和Server中调用。
  129. NetworkObject INetworkPrefabInstanceHandler.Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation) {
  130. Debug.Log(" NetworkObject INetworkPrefabInstanceHandler.Instantiate");
  131. return m_Pool.GetNetworkObject(m_Prefab, position, rotation);
  132. }
  133. //network object调用Despawn()后,在Host和Client中调用
  134. void INetworkPrefabInstanceHandler.Destroy(NetworkObject networkObject) {
  135. Debug.Log("void INetworkPrefabInstanceHandler.Destroy");
  136. m_Pool.ReturnNetworkObject(networkObject, m_Prefab);
  137. }
  138. }
  139. }

配置

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

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

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

使用它

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

以下是我写的Debug版本

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

  1. private void Spawn() {
  2. //只有服务器可以创建/销毁对象
  3. if (IsServer) {
  4. //public GameObject SpawnPrefeb;
  5. networkObject= NetworkObjectPool.Singleton.GetNetworkObject(SpawnPrefeb, transform.position, Quaternion.identity);
  6. Debug.Log("Spawn");
  7. networkObject.Spawn();
  8. }
  9. }

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

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

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

演示

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

RPC实现法?

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

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

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

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/116532
推荐阅读
相关标签
  

闽ICP备14008679号