namespace Zinnia.Tracking.Query
{
using System;
using UnityEngine;
using UnityEngine.Events;
using Zinnia.Cast;
using Zinnia.Data.Type;
using Zinnia.Extension;
using Zinnia.Process;
///
/// Determines whether any obscures a line between two s.
///
///
/// The check is done by utilizing physics and as such a is needed on .
///
public class ObscuranceQuery : MonoBehaviour, IProcessable
{
///
/// Defines the event with the of s.
///
[Serializable]
public class HitEvent : UnityEvent> { }
public class MissingColliderException : Exception
{
public MissingColliderException(UnityEngine.Object owner, GameObject missingColliderGameObject) : base($"The configured {nameof(Target)} '{missingColliderGameObject}' on '{owner}' needs a {nameof(Collider)} on it or if it has a {nameof(Rigidbody)} on it then a child {nameof(Collider)} is required.") { }
}
[Tooltip("Defines the source location that the RayCast will originate from towards the Target location.")]
[SerializeField]
private GameObject source;
///
/// Defines the source location that the RayCast will originate from towards the location.
///
public GameObject Source
{
get
{
return source;
}
set
{
source = value;
}
}
[Tooltip("Defines the target location that the RayCast will attain to reach from the originating Source location.")]
[SerializeField]
private GameObject target;
///
/// Defines the target location that the RayCast will attain to reach from the originating location.
///
public GameObject Target
{
get
{
return target;
}
set
{
target = value;
if (this.IsMemberChangeAllowed())
{
OnAfterTargetChange();
}
}
}
[Tooltip("Optional settings to use when doing the RayCast.")]
[SerializeField]
private PhysicsCast physicsCast;
///
/// Optional settings to use when doing the RayCast.
///
public PhysicsCast PhysicsCast
{
get
{
return physicsCast;
}
set
{
physicsCast = value;
}
}
///
/// Emitted when the RayCast from to is obscured by another .
///
public HitEvent TargetObscured = new HitEvent();
///
/// Emitted when the RayCast from is reaching and is not obscured by another .
///
public UnityEvent TargetUnobscured = new UnityEvent();
///
/// Whether the RayCast from to was previously obscured by another .
///
protected bool? wasPreviouslyObscured;
///
/// Clears .
///
public virtual void ClearSource()
{
if (!this.IsValidState())
{
return;
}
Source = default;
}
///
/// Clears .
///
public virtual void ClearTarget()
{
if (!this.IsValidState())
{
return;
}
Target = default;
}
///
/// Clears .
///
public virtual void ClearPhysicsCast()
{
if (!this.IsValidState())
{
return;
}
PhysicsCast = default;
}
///
/// Casts a ray from the origin location towards the destination location and determines whether the RayCast is blocked by another .
///
public virtual void Process()
{
Vector3 sourcePosition = Source.transform.position;
Vector3 difference = Target.transform.position - sourcePosition;
Ray ray = new Ray(sourcePosition, difference);
HeapAllocationFreeReadOnlyList raycastHits = PhysicsCast.RaycastAll(
PhysicsCast,
ray,
difference.magnitude,
Physics.IgnoreRaycastLayer);
bool isObscured = false;
foreach (RaycastHit hit in raycastHits)
{
GameObject hitGameObject = hit.transform.gameObject;
if (hitGameObject != Source && hitGameObject != Target)
{
isObscured = true;
break;
}
}
if (isObscured == wasPreviouslyObscured)
{
return;
}
wasPreviouslyObscured = isObscured;
if (isObscured)
{
TargetObscured?.Invoke(raycastHits);
}
else
{
TargetUnobscured?.Invoke();
}
}
protected virtual void OnEnable()
{
CheckTarget();
}
protected virtual void OnDisable()
{
wasPreviouslyObscured = null;
}
///
/// Throws a if is missing a or if the has a and is missing a child .
///
protected virtual void CheckTarget()
{
if (Target != null
&& (Target.TryGetComponent() == null || Target.TryGetComponent(true) == null)
&& Target.TryGetComponent() == null)
{
throw new MissingColliderException(this, Target);
}
}
///
/// Called after has been changed.
///
protected virtual void OnAfterTargetChange()
{
CheckTarget();
}
}
}