You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

182 lines
6.4 KiB
C#

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;
}
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);
}
}
}