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