namespace Zinnia.Tracking.Collision { using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; using Zinnia.Data.Attribute; using Zinnia.Extension; using Zinnia.Rule; /// /// Allows emitting collision data via events. /// public class CollisionNotifier : MonoBehaviour { /// /// Holds data about a event. /// [Serializable] public class EventData : IEquatable { [Tooltip("The source of this event in case it was forwarded.")] [SerializeField] private Component forwardSource; /// /// The source of this event in case it was forwarded. /// /// if this event wasn't forwarded from anything. public Component ForwardSource { get { return forwardSource; } set { forwardSource = value; } } [Tooltip("Whether the collision was observed through a Collider with Collider.isTrigger set.")] [SerializeField] private bool isTrigger; /// /// Whether the collision was observed through a with set. /// public bool IsTrigger { get { return isTrigger; } set { isTrigger = value; } } [Tooltip("The observed Collision. null if IsTrigger is true.")] [SerializeField] private Collision collisionData; /// /// The observed . if is . /// public Collision CollisionData { get { return collisionData; } set { collisionData = value; } } [Tooltip("The observed Collider.")] [SerializeField] private Collider colliderData; /// /// The observed . /// public Collider ColliderData { get { return colliderData; } set { colliderData = value; } } /// /// Clears . /// public virtual void ClearForwardSource() { ForwardSource = default; } public EventData Set(EventData source) { return Set(source.ForwardSource, source.IsTrigger, source.CollisionData, source.ColliderData); } public EventData Set(Component forwardSource, bool isTrigger, Collision collision, Collider collider) { ForwardSource = forwardSource; IsTrigger = isTrigger; CollisionData = collision; ColliderData = collider; return this; } public void Clear() { Set(default, default, default, default); } /// public override string ToString() { string[] titles = new string[] { "ForwardSource", "IsTrigger", "CollisionData", "ColliderData" }; object[] values = new object[] { ForwardSource, IsTrigger, CollisionData, ColliderData }; return StringExtensions.FormatForToString(titles, values); } /// public bool Equals(EventData other) { if (ReferenceEquals(null, other)) { return false; } if (ReferenceEquals(this, other)) { return true; } return Equals(ForwardSource, other.ForwardSource) && Equals(ColliderData, other.ColliderData); } /// public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } if (ReferenceEquals(this, obj)) { return true; } return obj is EventData other && Equals(other); } /// public override int GetHashCode() { return ((ForwardSource != null ? ForwardSource.GetHashCode() : 0) * 397) ^ (ColliderData != null ? ColliderData.GetHashCode() : 0); } public static bool operator ==(EventData left, EventData right) => Equals(left, right); public static bool operator !=(EventData left, EventData right) => !Equals(left, right); } /// /// Defines the event with the . /// [Serializable] public class UnityEvent : UnityEvent { } /// /// The types of collisions that events will be emitted for. /// [Flags] public enum CollisionTypes { /// /// A regular, non-trigger collision. /// Collision = 1 << 0, /// /// A trigger collision /// Trigger = 1 << 1 } /// /// The states of a tracked collision. /// [Flags] public enum CollisionStates { /// /// When a new collision occurs. /// Enter = 1 << 0, /// /// When an existing collision continues to exist. /// Stay = 1 << 1, /// /// When an existing collision ends. /// Exit = 1 << 2 } #region Collision Settings [Header("Collision Settings")] [Tooltip("Whether the collisions should be processed when this component is disabled.")] [SerializeField] private bool processCollisionsWhenDisabled = true; /// /// Whether the collisions should be processed when this component is disabled. /// public bool ProcessCollisionsWhenDisabled { get { return processCollisionsWhenDisabled; } set { processCollisionsWhenDisabled = value; } } [Tooltip("The types of collisions that events will be emitted for.")] [SerializeField] [UnityFlags] private CollisionTypes emittedTypes = (CollisionTypes)(-1); /// /// The types of collisions that events will be emitted for. /// public CollisionTypes EmittedTypes { get { return emittedTypes; } set { emittedTypes = value; } } [Tooltip("The CollisionStates to process.")] [SerializeField] [UnityFlags] private CollisionStates statesToProcess = (CollisionStates)(-1); /// /// The to process. /// public CollisionStates StatesToProcess { get { return statesToProcess; } set { statesToProcess = value; } } [Tooltip("Allows to optionally determine which forwarded collisions to react to based on the set rules for the forwarding sender.")] [SerializeField] private RuleContainer forwardingSourceValidity; /// /// Allows to optionally determine which forwarded collisions to react to based on the set rules for the forwarding sender. /// public RuleContainer ForwardingSourceValidity { get { return forwardingSourceValidity; } set { forwardingSourceValidity = value; } } #endregion #region Collision Events /// /// Emitted when a collision starts. /// [Header("Collision Events")] public UnityEvent CollisionStarted = new UnityEvent(); /// /// Emitted when the current collision changes. /// public UnityEvent CollisionChanged = new UnityEvent(); /// /// Emitted when the current collision stops. /// public UnityEvent CollisionStopped = new UnityEvent(); #endregion /// /// A reused instance to use when raising any of the events. /// protected readonly EventData eventData = new EventData(); /// /// A reused instance to use when looking up components on start. /// protected readonly List startCollisionNotifiers = new List(); /// /// A reused instance to use when looking up components on change. /// protected readonly List changeCollisionNotifiers = new List(); /// /// A reused instance to use when looking up components on stop. /// protected readonly List stopCollisionNotifiers = new List(); /// /// Whether the collection is being processed on start. /// protected bool isProcessingStartNotifierCollection; /// /// Whether the collection is being processed on change. /// protected bool isProcessingChangeNotifierCollection; /// /// Whether the collection is being processed on stop. /// protected bool isProcessingStopNotifierCollection; /// /// The of the notifier for the given collider data. /// protected Transform notifierColliderTransform; /// /// Clears . /// public virtual void ClearForwardingSourceValidity() { if (!this.IsValidState()) { return; } ForwardingSourceValidity = default; } /// /// Processes any collision start events on the given data and propagates it to any linked . /// /// The collision data. public virtual void OnCollisionStarted(EventData data) { if (!CanProcess() || (StatesToProcess & CollisionStates.Enter) == 0 || !CanEmit(data)) { return; } CollisionStarted?.Invoke(data); if (isProcessingStartNotifierCollection) { return; } isProcessingStartNotifierCollection = true; foreach (CollisionNotifier notifier in GetNotifiers(data, startCollisionNotifiers)) { notifier.OnCollisionStarted(data); } isProcessingStartNotifierCollection = false; } /// /// Processes any collision change events on the given data and propagates it to any linked . /// /// The collision data. public virtual void OnCollisionChanged(EventData data) { if (!CanProcess() || (StatesToProcess & CollisionStates.Stay) == 0 || !CanEmit(data)) { return; } CollisionChanged?.Invoke(data); if (isProcessingChangeNotifierCollection) { return; } isProcessingChangeNotifierCollection = true; foreach (CollisionNotifier notifier in GetNotifiers(data, changeCollisionNotifiers)) { notifier.OnCollisionChanged(data); } isProcessingChangeNotifierCollection = false; } /// /// Processes any collision stop events on the given data and propagates it to any linked . /// /// The collision data. public virtual void OnCollisionStopped(EventData data) { if (!CanProcess() || (StatesToProcess & CollisionStates.Exit) == 0 || !CanEmit(data)) { return; } CollisionStopped?.Invoke(data); if (isProcessingStopNotifierCollection) { return; } isProcessingStopNotifierCollection = true; foreach (CollisionNotifier notifier in GetNotifiers(data, stopCollisionNotifiers)) { notifier.OnCollisionStopped(data); } isProcessingStopNotifierCollection = false; } /// /// Whether to process the collision check. /// /// if the collision should be processed. protected virtual bool CanProcess() { return enabled || ProcessCollisionsWhenDisabled; } /// /// Determines whether events should be emitted. /// /// The data to check. /// if events should be emitted. protected virtual bool CanEmit(EventData data) { return (data.IsTrigger && (EmittedTypes & CollisionTypes.Trigger) != 0 || !data.IsTrigger && (EmittedTypes & CollisionTypes.Collision) != 0) && (data.ForwardSource == null || ForwardingSourceValidity.Accepts(data.ForwardSource.gameObject)); } /// /// Returns a collection for the given containing . /// /// The that holds the containing /// A collection for items found on the containing component. protected virtual List GetNotifiers(EventData data, List collisionNotifiers) { if (this == null || data == null || data.ColliderData == null) { return collisionNotifiers; } notifierColliderTransform = data.ColliderData.GetContainingTransform(); if (transform.IsChildOf(notifierColliderTransform)) { collisionNotifiers.Clear(); } else { notifierColliderTransform.GetComponentsInChildren(collisionNotifiers); } return collisionNotifiers; } } }