namespace Zinnia.Tracking.Collision.Active { using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; using Zinnia.Extension; using Zinnia.Rule; /// /// Holds a collection of the current collisions raised by a . /// public class ActiveCollisionsContainer : MonoBehaviour { /// /// Holds data about a event. /// [Serializable] public class EventData { [Tooltip("The current active collisions.")] [SerializeField] private List activeCollisions = new List(); /// /// The current active collisions. /// public List ActiveCollisions { get { return activeCollisions; } set { activeCollisions = value; } } public EventData Set(EventData source) { return Set(source.ActiveCollisions); } public EventData Set(List activeCollisions) { ActiveCollisions.Clear(); ActiveCollisions.AddRange(activeCollisions); return this; } public void Clear() { ActiveCollisions.Clear(); } } /// /// Defines the event with the . /// [Serializable] public class ActiveCollisionUnityEvent : UnityEvent { } #region Validity Settings [Header("Validity Settings")] [Tooltip("Determines whether the collision is valid and to add it to the active collision collection.")] [SerializeField] private RuleContainer collisionValidity; /// /// Determines whether the collision is valid and to add it to the active collision collection. /// public RuleContainer CollisionValidity { get { return collisionValidity; } set { collisionValidity = value; } } #endregion #region Collection Events /// /// Emitted when the collision count has changed. /// [Header("Collection Events")] public ActiveCollisionUnityEvent CountChanged = new ActiveCollisionUnityEvent(); /// /// Emitted when the collision contents have changed. /// public ActiveCollisionUnityEvent ContentsChanged = new ActiveCollisionUnityEvent(); #endregion #region Collision Events /// /// Emitted when the first collision occurs. /// [Header("Collision Events")] public CollisionNotifier.UnityEvent FirstStarted = new CollisionNotifier.UnityEvent(); /// /// Emitted when a collision is added. /// public CollisionNotifier.UnityEvent Added = new CollisionNotifier.UnityEvent(); /// /// Emitted when a collision is removed. /// public CollisionNotifier.UnityEvent Removed = new CollisionNotifier.UnityEvent(); /// /// Emitted when there are no more collisions occuring. /// public UnityEvent AllStopped = new UnityEvent(); #endregion /// /// The current active collisions. /// public List Elements { get; protected set; } = new List(); /// /// A containing collection with a nested collection of each collision occuring. /// protected Dictionary> containingTransformCollisions = new Dictionary>(); /// /// The event data emitted on active collision events. /// protected readonly EventData eventData = new EventData(); /// /// Clears . /// public virtual void ClearCollisionValidity() { if (!this.IsValidState()) { return; } CollisionValidity = default; } /// /// Adds the given collision as an active collision. /// /// The collision data. public virtual void Add(CollisionNotifier.EventData collisionData) { if (!this.IsValidState()) { return; } Transform currentCollisionContainingTransform = collisionData.ColliderData.GetContainingTransform(); if (!IsValidCollision(currentCollisionContainingTransform)) { return; } if (!AreExistingCollisions(collisionData, currentCollisionContainingTransform)) { CollisionNotifier.EventData clonedCollisionData = CloneEventData(collisionData); AddUniqueElement(clonedCollisionData); if (!containingTransformCollisions.TryGetValue(currentCollisionContainingTransform, out List existingTransformCollisions)) { existingTransformCollisions = new List(); containingTransformCollisions[currentCollisionContainingTransform] = existingTransformCollisions; } existingTransformCollisions.Add(clonedCollisionData); Added?.Invoke(collisionData); if (Elements.Count == 1) { FirstStarted?.Invoke(collisionData); } CountChanged?.Invoke(eventData.Set(Elements)); ProcessContentsChanged(); } } /// /// Removes the given collision from being an active collision. /// /// The collision data. public virtual void Remove(CollisionNotifier.EventData collisionData) { if (!this.IsValidState()) { return; } Transform currentCollisionContainingTransform = collisionData.ColliderData.GetContainingTransform(); if (!containingTransformCollisions.TryGetValue(currentCollisionContainingTransform, out List foundCollisionElements)) { return; } foundCollisionElements.Remove(collisionData); Elements.Remove(collisionData); if (!HasRemainingCollisions(foundCollisionElements)) { containingTransformCollisions.Remove(currentCollisionContainingTransform); Removed?.Invoke(collisionData); EmitEmptyEvents(); } } /// /// Processes any changes to the contents of existing collisions. /// public virtual void ProcessContentsChanged() { if (!this.IsValidState()) { return; } ContentsChanged?.Invoke(eventData.Set(Elements)); } protected virtual void OnDisable() { if (Elements.Count == 0) { return; } foreach (CollisionNotifier.EventData element in Elements) { Removed?.Invoke(element); } Elements.Clear(); containingTransformCollisions.Clear(); EmitEmptyEvents(); } /// /// Emits the appropriate events when the collection is emptied. /// protected virtual void EmitEmptyEvents() { if (Elements.Count == 0) { AllStopped?.Invoke(); } CountChanged?.Invoke(eventData.Set(Elements)); ProcessContentsChanged(); } /// /// Clones the given event data. /// /// The data to clone. /// The cloned data. protected virtual CollisionNotifier.EventData CloneEventData(CollisionNotifier.EventData input) { return new CollisionNotifier.EventData().Set(input); } /// /// Determines if the current containing in the collision is valid. /// /// The to check. /// The validity result of the collision. protected virtual bool IsValidCollision(Transform containingTransform) { return containingTransform != null && CollisionValidity.Accepts(containingTransform.gameObject); } /// /// Adds the given data to the collection if it is not already present. /// /// The data to add. protected virtual void AddUniqueElement(CollisionNotifier.EventData data) { if (!Elements.Contains(data)) { Elements.Add(data); } } /// /// Updates current containing collision occurring by removing any existing nested collisions and adding the current collision in the given collision data. /// /// The collision data to add. /// The existing collision data to remove from . protected virtual void UpdateContainingTransformCollisions(CollisionNotifier.EventData collisionData, List existingCollisionData) { foreach (CollisionNotifier.EventData data in existingCollisionData) { Elements.Remove(data); } CollisionNotifier.EventData clonedCollisionData = CloneEventData(collisionData); existingCollisionData.Add(clonedCollisionData); AddUniqueElement(clonedCollisionData); } /// /// Determines if the containing is already being collided with by a different nested . /// /// /// Updates the collection with the latest collision data for an existing containing that is being collided with. /// /// The collision data containing the to check. /// The containing for the current collider. /// Whether there are any existing collisions occuring with the containing . protected virtual bool AreExistingCollisions(CollisionNotifier.EventData collisionData, Transform currentCollisionContainingTransform) { if (!containingTransformCollisions.TryGetValue(currentCollisionContainingTransform, out List foundCollisionElements)) { return false; } if (foundCollisionElements.Contains(collisionData)) { return true; } UpdateContainingTransformCollisions(collisionData, foundCollisionElements); return true; } /// /// Determines if the collisions collection still contains any valid collisions and updates the collection with the next available collision data. /// /// The collection to check. /// Whether the collection has remaining collisions. protected virtual bool HasRemainingCollisions(List foundCollisionElements) { if (foundCollisionElements.Count > 0) { AddUniqueElement(foundCollisionElements[foundCollisionElements.Count - 1]); return true; } return false; } } }