using System; using System.Collections.Generic; using Unity.Netcode; using UnityEngine; using UnityEngine.Assertions; using UnityEngine.Pool; namespace Unity.BossRoom.Infrastructure { /// /// 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. /// public class NetworkObjectPool : NetworkBehaviour { public static NetworkObjectPool Singleton { get; private set; } [SerializeField] List PooledPrefabsList; HashSet m_Prefabs = new HashSet(); Dictionary> m_PooledObjects = new Dictionary>(); 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(), $"{nameof(NetworkObjectPool)}: Pooled prefab \"{prefab.name}\" at index {i.ToString()} has no {nameof(NetworkObject)} component."); } } } /// /// Gets an instance of the given prefab from the pool. The prefab must be registered to the pool. /// /// /// 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. /// /// /// The position to spawn the object at. /// The rotation to spawn the object with. /// 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; } /// /// Return an object to the pool (reset objects before returning). /// public void ReturnNetworkObject(NetworkObject networkObject, GameObject prefab) { m_PooledObjects[prefab].Release(networkObject); } /// /// Builds up the cache for a prefab. /// void RegisterPrefabInternal(GameObject prefab, int prewarmCount) { NetworkObject CreateFunc() { return Instantiate(prefab).GetComponent(); } 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(CreateFunc, ActionOnGet, ActionOnRelease, ActionOnDestroy, defaultCapacity: prewarmCount); // Populate the pool var prewarmNetworkObjects = new List(); 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; } class PooledPrefabInstanceHandler : INetworkPrefabInstanceHandler { GameObject m_Prefab; NetworkObjectPool m_Pool; public PooledPrefabInstanceHandler(GameObject prefab, NetworkObjectPool pool) { m_Prefab = prefab; m_Pool = pool; } NetworkObject INetworkPrefabInstanceHandler.Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation) { return m_Pool.GetNetworkObject(m_Prefab, position, rotation); } void INetworkPrefabInstanceHandler.Destroy(NetworkObject networkObject) { m_Pool.ReturnNetworkObject(networkObject, m_Prefab); } } }