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