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