// ===================================================================== // Copyright 2013-2017 Fluffy Underware // All rights reserved // // http://www.fluffyunderware.com // ===================================================================== using UnityEngine; #if UNITY_EDITOR using UnityEditor; #endif using System.Collections; using System; using System.Collections.Generic; using FluffyUnderware.DevTools.Extensions; namespace FluffyUnderware.DevTools { /// /// A pool of instances of a prefab GameObject /// [RequireComponent(typeof(PoolManager))] [HelpURL(DTUtility.HelpUrlBase + "dtprefabpool")] public class PrefabPool : DTVersionedMonoBehaviour, IPool { [FieldCondition(nameof(m_Identifier), "", false, ActionAttribute.ActionEnum.ShowWarning, "Please enter an identifier! (Select a prefab to set automatically)")] [SerializeField] string m_Identifier; [SerializeField] List m_Prefabs = new List(); [Inline] [SerializeField] PoolSettings m_Settings; PoolManager mManager; public string Identifier { get { return m_Identifier; } set { if (m_Identifier != value) { string v = value; if (string.IsNullOrEmpty(m_Identifier)) v = Manager.GetUniqueIdentifier(v); m_Identifier = value; } } } public List Prefabs { get { return m_Prefabs; } set { if (m_Prefabs != value) m_Prefabs = value; } } public PoolSettings Settings { get { return m_Settings; } set { if (m_Settings != value) m_Settings = value; if (m_Settings != null) m_Settings.OnValidate(); } } public PoolManager Manager { get { if (mManager == null) mManager = GetComponent(); return mManager; } } List mObjects = new List(); double mLastTime; double mDeltaTime; void Start() { if (Settings.Prewarm) Reset(); } #if UNITY_EDITOR void OnValidate() { Settings = m_Settings; } #endif public void Initialize(string ident, PoolSettings settings, params GameObject[] prefabs) { Identifier = ident; m_Settings = settings; Prefabs = new List(prefabs); mLastTime = DTTime.TimeSinceStartup + UnityEngine.Random.Range(0, Settings.Speed); if (Settings.Prewarm) Reset(); } public void Update() { if (Application.isPlaying) { mDeltaTime += DTTime.TimeSinceStartup - mLastTime; mLastTime = DTTime.TimeSinceStartup; if (Settings.Speed > 0) { int c = (int)(mDeltaTime / Settings.Speed); mDeltaTime -= c; if (Count > Settings.Threshold) { c = Mathf.Min(c, Count - Settings.Threshold); while (c-- > 0) { if (Settings.Debug) log("Threshold exceeded: Deleting item"); destroy(mObjects[0]); mObjects.RemoveAt(0); } } else if (Count < Settings.MinItems) { c = Mathf.Min(c, Settings.MinItems - Count); while (c-- > 0) { if (Settings.Debug) log("Below MinItems: Adding item"); mObjects.Add(create()); } } } else mDeltaTime = 0; } } public void Reset() { if (Application.isPlaying) { while (Count < Settings.MinItems) { mObjects.Add(create()); } while (Count > Settings.Threshold) { destroy(mObjects[0]); mObjects.RemoveAt(0); } if (Settings.Debug) log("Prewarm/Reset"); } } public void Clear() { if (Settings.Debug) log("Clear"); for (int i = 0; i < Count; i++) destroy(mObjects[i]); mObjects.Clear(); } public int Count { get { return mObjects.Count; } } public GameObject Pop(Transform parent = null) { GameObject item = null; if (Count > 0) { item = mObjects[0]; mObjects.RemoveAt(0); } else { if (Settings.AutoCreate || !Application.isPlaying) { if (Settings.Debug) log("Auto create item"); item = create(); } } if (item) { item.gameObject.hideFlags = HideFlags.None; item.transform.parent = parent; if (Settings.AutoEnableDisable) item.SetActive(true); sendAfterPop(item); if (Settings.Debug) log("Pop " + item); } return item; } public virtual void Push(GameObject item) { if (Settings.Debug) log("Push " + item); #if UNITY_EDITOR if (!Application.isPlaying) { item.gameObject.Destroy(false, true); } else #endif if (item != null) { sendBeforePush(item); mObjects.Add(item); item.transform.parent = transform; item.gameObject.hideFlags = (Settings.Debug) ? HideFlags.DontSave : HideFlags.HideAndDontSave; if (Settings.AutoEnableDisable) item.SetActive(false); } } GameObject create() { if (Prefabs.Count == 0) throw new InvalidOperationException(String.Format("[Curvy] The Prefab Pool '{0}' in game object '{1}' could not create a pool element because its Prefabs list is empty", this.Identifier, this.gameObject.name)); //TODO should this Random.Range call be deterministic? GameObject prefab = Prefabs[UnityEngine.Random.Range(0, Prefabs.Count)]; GameObject result; { #if UNITY_EDITOR bool isPrefabAsset; //We have to check GetPrefabInstanceStatus first, because GetPrefabAssetType returns (as far as I understand) the same thing for both a prefab asset and a prefab instance if (PrefabUtility.GetPrefabInstanceStatus(prefab) == PrefabInstanceStatus.NotAPrefab) { PrefabAssetType prefabAssetType = PrefabUtility.GetPrefabAssetType(prefab); isPrefabAsset = prefabAssetType == PrefabAssetType.Regular || prefabAssetType == PrefabAssetType.Variant; } else isPrefabAsset = false; result = isPrefabAsset ? PrefabUtility.InstantiatePrefab(prefab) as GameObject : Instantiate(prefab); #else result = Instantiate(prefab); #endif } result.name = prefab.name; result.transform.parent = transform; if (Settings.AutoEnableDisable) result.SetActive(false); return result; } void destroy(GameObject go) { go.Destroy(false, true); } void log(string msg) { Debug.Log(string.Format("[{0}] ({1} items) {2}", Identifier, Count, msg)); } void setParent(Transform item, Transform parent) { if (item != null) item.parent = parent; } void sendAfterPop(GameObject item) { item.SendMessage(nameof(IPoolable.OnAfterPop), SendMessageOptions.DontRequireReceiver); } void sendBeforePush(GameObject item) { item.SendMessage(nameof(IPoolable.OnBeforePush), SendMessageOptions.DontRequireReceiver); } } }