using System.Collections; using System.Collections.Generic; using UnityEngine; namespace Lightbug.Utilities { /// /// This component offers a set of useful functionalities for 2D and 3D physics. /// public abstract class PhysicsComponent : MonoBehaviour { public List HitsBuffer { get; protected set; } = new List(20); /// /// Gets a list with all the current contacts. /// public List Contacts { get; protected set; } = new List(20); /// /// Gets a list with all the current triggers. /// public List Triggers { get; protected set; } = new List(20); protected abstract LayerMask GetCollisionLayerMask(); /// /// Ignores the collision between this object and some other collider. /// public abstract void IgnoreCollision(in HitInfo hitInfo, bool ignore); /// /// Ignores the collision between this object and some other collider. /// public abstract void IgnoreCollision(Transform otherTransform, bool ignore); /// /// Ignores the collision between this object and a layer. /// public abstract void IgnoreLayerCollision(int targetLayer, bool ignore); /// /// Ignores the collision between this object and a layer mask. /// public abstract void IgnoreLayerMaskCollision(LayerMask layerMask, bool ignore); protected abstract int FilterOverlaps(int overlaps, LayerMask ignoredLayerMask, HitFilterDelegate hitFilter); public void ClearContacts() => Contacts.Clear(); protected abstract void GetClosestHit(out HitInfo hitInfo, int hits, Vector3 castDisplacement, in HitInfoFilter filter, bool allowOverlaps, HitFilterDelegate hitFilter); protected abstract List GetAllHits(int hits, Vector3 castDisplacement, in HitInfoFilter filter, bool allowOverlaps, HitFilterDelegate hitFilter); protected abstract int InternalRaycast(Vector3 origin, Vector3 castDisplacement, int layerMask, bool ignoreTriggers, bool allowOverlaps); protected abstract int InternalSphereCast(Vector3 center, float radius, Vector3 castDisplacement, int layerMask, bool ignoreTriggers, bool allowOverlaps); protected abstract int InternalCapsuleCast(Vector3 bottom, Vector3 top, float radius, Vector3 castDisplacement, int layerMask, bool ignoreTriggers, bool allowOverlaps); protected abstract int InternalBoxCast(Vector3 center, Vector3 size, Vector3 castDisplacement, Quaternion orientation, int layerMask, bool ignoreTriggers, bool allowOverlaps); protected abstract int InternalOverlapSphere(Vector3 center, float radius, int layerMask, bool ignoreTriggers); protected abstract int InternalOverlapCapsule(Vector3 bottom, Vector3 top, float radius, int layerMask, bool ignoreTriggers); protected abstract int InternalOverlapBox(Vector3 center, Vector3 size, Quaternion orientation, int layerMask, bool ignoreTriggers); /// /// Returns true if collisions between this collider and the target GameObject is valid at the physics simulation level. /// public abstract bool CheckCollisionsWith(GameObject gameObject); RigidbodyComponent rigidbodyComponent = null; Coroutine postSimulationCoroutine = null; #region Physics queries /// /// Performs a Raycast and gets the closest valid hit. /// public int Raycast(out HitInfo hitInfo, Vector3 origin, Vector3 castDisplacement, in HitInfoFilter filter, bool allowOverlaps = false, HitFilterDelegate hitFilter = null) { var hits = InternalRaycast( origin, castDisplacement, filter.collisionLayerMask, filter.ignoreTriggers, allowOverlaps ); if (hits != 0) GetClosestHit(out hitInfo, hits, castDisplacement, in filter, allowOverlaps, hitFilter); else hitInfo = new HitInfo(); return hits; } /// /// Performs a Raycast and gets all the valid hits. /// public List Raycast(Vector3 origin, Vector3 castDisplacement, in HitInfoFilter filter, bool allowOverlaps = false, HitFilterDelegate hitFilter = null) { var hits = InternalRaycast( origin, castDisplacement, filter.collisionLayerMask, filter.ignoreTriggers, allowOverlaps ); if (hits != 0) return GetAllHits(hits, castDisplacement, in filter, allowOverlaps, hitFilter); else return null; } /// /// Performs a SphereCast and gets the closest valid hit. /// public int SphereCast(out HitInfo hitInfo, Vector3 center, float radius, Vector3 castDisplacement, in HitInfoFilter filter, bool allowOverlaps = false, HitFilterDelegate hitFilter = null) { var hits = InternalSphereCast( center, radius, castDisplacement, filter.collisionLayerMask, filter.ignoreTriggers, allowOverlaps ); if (hits != 0) GetClosestHit(out hitInfo, hits, castDisplacement, in filter, allowOverlaps, hitFilter); else hitInfo = new HitInfo(); return hits; } /// /// Performs a SphereCast and gets all the valid hits. /// public List SphereCast(Vector3 center, float radius, Vector3 castDisplacement, in HitInfoFilter filter, bool allowOverlaps = false, HitFilterDelegate hitFilter = null) { var hits = InternalSphereCast( center, radius, castDisplacement, filter.collisionLayerMask, filter.ignoreTriggers, allowOverlaps ); if (hits != 0) return GetAllHits(hits, castDisplacement, in filter, allowOverlaps, hitFilter); else return null; } /// /// Performs a CapsuleCast and gets the closest valid hit. /// public int CapsuleCast(out HitInfo hitInfo, Vector3 bottom, Vector3 top, float radius, Vector3 castDisplacement, in HitInfoFilter filter, bool allowOverlaps = false, HitFilterDelegate hitFilter = null) { var hits = InternalCapsuleCast( bottom, top, radius, castDisplacement, filter.collisionLayerMask, filter.ignoreTriggers, allowOverlaps ); if (hits != 0) GetClosestHit(out hitInfo, hits, castDisplacement, in filter, allowOverlaps, hitFilter); else hitInfo = new HitInfo(); return hits; } /// /// Performs a CapsuleCast and gets all the valid hits. /// public List CapsuleCast(Vector3 bottom, Vector3 top, float radius, Vector3 castDisplacement, in HitInfoFilter filter, bool allowOverlaps = false, HitFilterDelegate hitFilter = null) { var hits = InternalCapsuleCast( bottom, top, radius, castDisplacement, filter.collisionLayerMask, filter.ignoreTriggers, allowOverlaps ); if (hits != 0) return GetAllHits(hits, castDisplacement, in filter, allowOverlaps, hitFilter); else return null; } /// /// Performs a BoxCast and gets the closest valid hit. /// public int BoxCast(out HitInfo hitInfo, Vector3 center, Vector3 size, Vector3 castDisplacement, Quaternion orientation, in HitInfoFilter filter, bool allowOverlaps = false, HitFilterDelegate hitFilter = null) { var hits = InternalBoxCast( center, size, castDisplacement, orientation, filter.collisionLayerMask, filter.ignoreTriggers, allowOverlaps ); if (hits != 0) GetClosestHit(out hitInfo, hits, castDisplacement, in filter, allowOverlaps, hitFilter); else hitInfo = new HitInfo(); return hits; } /// /// Performs a BoxCast and gets all the valid hits. /// public List BoxCast(Vector3 center, Vector3 size, Vector3 castDisplacement, Quaternion orientation, in HitInfoFilter filter, bool allowOverlaps = false, HitFilterDelegate hitFilter = null) { var hits = InternalBoxCast( center, size, castDisplacement, orientation, filter.collisionLayerMask, filter.ignoreTriggers, allowOverlaps ); if (hits != 0) return GetAllHits(hits, castDisplacement, in filter, allowOverlaps, hitFilter); else return null; } #endregion #region Overlaps /// /// Performs an OverlapSphere and returns true if any of the results is valid. /// public bool OverlapSphere(Vector3 center, float radius, in HitInfoFilter filter, HitFilterDelegate hitFilter = null) { var overlaps = InternalOverlapSphere( center, radius, filter.collisionLayerMask, filter.ignoreTriggers ); var filteredOverlaps = FilterOverlaps(overlaps, filter.ignorePhysicsLayerMask, hitFilter); return filteredOverlaps != 0; } /// /// Performs an OverlapCapsule and returns true if any of the results is valid. /// public bool OverlapCapsule(Vector3 bottom, Vector3 top, float radius, in HitInfoFilter filter, HitFilterDelegate hitFilter = null) { var overlaps = InternalOverlapCapsule( bottom, top, radius, filter.collisionLayerMask, filter.ignoreTriggers ); var filteredOverlaps = FilterOverlaps(overlaps, filter.ignorePhysicsLayerMask, hitFilter); return filteredOverlaps != 0; } /// /// Performs an OverlapBox and returns true if any of the results is valid. /// public bool OverlapBox(Vector3 center, Vector3 size, Quaternion orientation, in HitInfoFilter filter, HitFilterDelegate hitFilter = null) { var overlaps = InternalOverlapBox( center, size, orientation, filter.collisionLayerMask, filter.ignoreTriggers ); var filteredOverlaps = FilterOverlaps(overlaps, filter.ignorePhysicsLayerMask, hitFilter); return filteredOverlaps != 0; } #endregion /// /// Returns a layer mask with all the valid collisions associated with the object, based on the collision matrix (physics settings). /// public LayerMask CollisionLayerMask { get; protected set; } = 0; protected virtual void Awake() { this.hideFlags = HideFlags.None; CollisionLayerMask = GetCollisionLayerMask(); } protected virtual void Start() { rigidbodyComponent = GetComponent(); } public static PhysicsComponent CreateInstance(GameObject gameObject) { Rigidbody2D rigidbody2D = gameObject.GetComponent(); Rigidbody rigidbody3D = gameObject.GetComponent(); if (rigidbody2D != null) return gameObject.GetOrAddComponent(); else if (rigidbody3D != null) return gameObject.GetOrAddComponent(); return null; } protected bool ignoreCollisionMessages = false; void OnEnable() { rigidbodyComponent = GetComponent(); if (rigidbodyComponent != null) rigidbodyComponent.OnBodyTypeChange += OnBodyTypeChange; postSimulationCoroutine ??= StartCoroutine(PostSimulationUpdate()); } void OnDisable() { if (rigidbodyComponent != null) rigidbodyComponent.OnBodyTypeChange -= OnBodyTypeChange; if (postSimulationCoroutine != null) { StopCoroutine(PostSimulationUpdate()); postSimulationCoroutine = null; } } void OnBodyTypeChange() { ignoreCollisionMessages = true; } void FixedUpdate() { // Update the collision layer mask (collision matrix) of this object. // CollisionLayerMask = GetCollisionLayerMask(); // --> Performance cost! This has been replaced by an internal mask that's modified every time IgnoreCollision is called. <-- // If there are null triggers then delete them from the list for (int i = Triggers.Count - 1; i >= 0; i--) { if (Triggers[i].gameObject == null) Triggers.RemoveAt(i); } } IEnumerator PostSimulationUpdate() { YieldInstruction waitForFixedUpdate = new WaitForFixedUpdate(); while (true) { yield return waitForFixedUpdate; ignoreCollisionMessages = false; } } protected bool wasKinematic = false; } }