using System; using System.Collections; using System.Collections.Generic; using UnityEngine; namespace RayFire { // TODO TAG LAYER FILTERS // TODO OPTIMIZE OVERLAP || BBOX overlap [AddComponentMenu ("RayFire/Rayfire Activator")] [HelpURL ("https://rayfirestudios.com/unity-online-help/components/unity-activator-component/")] public class RayfireActivator : MonoBehaviour { public enum ActivationType { OnTriggerEnter = 0, OnTriggerExit = 1, OnCollision = 2 } public enum AnimationType { ByGlobalPositionList = 0, ByStaticLine = 1, ByDynamicLine = 2, ByLocalPositionList = 5 } public enum GizmoType { Box = 1, Sphere = 0, Collider = 2, ParticleSystem = 5 } // UI public GizmoType gizmoType; public float sphereRadius = 5f; public Vector3 boxSize = new Vector3 (5f, 2f, 5f); public bool checkRigid = true; public bool checkRigidRoot = true; public ActivationType type; public float delay; public bool demolishCluster; public bool apply; public Vector3 velocity; public Vector3 spin; public ForceMode mode; public bool coord; public bool showAnimation; public float duration = 3f; public float scaleAnimation = 1f; public AnimationType positionAnimation; public LineRenderer line; public List positionList; public bool showGizmo = true; public Collider activatorCollider; public ParticleSystem ps; public List collisionEvents; // Non serialized [NonSerialized] bool animating; [NonSerialized] float pathRatio; [NonSerialized] float lineLength; [NonSerialized] float[] checkpoints; [NonSerialized] float delta; [NonSerialized] float deltaRatioStep; [NonSerialized] float distDeltaStep; [NonSerialized] float distRatio; [NonSerialized] float timePassed; [NonSerialized] int activeSegment; [NonSerialized] Vector3 positionStart; [NonSerialized] Vector3 scaleStart; // TODO list of objects to check or all colliders objects /// ///////////////////////////////////////////////////////// /// Common /// ///////////////////////////////////////////////////////// // Start is called before the first frame update void Awake() { // Check collider and triggers if (gizmoType != GizmoType.ParticleSystem) SetCollider(); else SetParticleSystem(); // TODO no target check positionStart = transform.position; scaleStart = transform.localScale; } /// ///////////////////////////////////////////////////////// /// Collision /// ///////////////////////////////////////////////////////// // Collider collision activation void OnCollisionEnter (Collision collision) { if (type == ActivationType.OnCollision && gizmoType != GizmoType.ParticleSystem) ActivationCheck (collision.collider); } // Particle collision activation void OnParticleCollision (GameObject other) { if (type == ActivationType.OnCollision && gizmoType == GizmoType.ParticleSystem) { int numCollisionEvents = ps.GetCollisionEvents (other, collisionEvents); for (int i = 0; i < numCollisionEvents; i++) ActivationCheck (collisionEvents[i].colliderComponent as Collider); } } /// ///////////////////////////////////////////////////////// /// Trigger /// ///////////////////////////////////////////////////////// // Activate on collider enter void OnTriggerEnter (Collider coll) { // TODO to avoid multiple checks at one frame // After first frame EnEnter set check state and avoid other OnEnter check at the same frame. // Use OverlapColliders to ge all colliders at once. // Add Target list to compare colliders with target colliders. // use for massive sims. if (type == ActivationType.OnTriggerEnter) ActivationCheck (coll); } // Activate on collider exit void OnTriggerExit (Collider coll) { if (type == ActivationType.OnTriggerExit) ActivationCheck (coll); } /// ///////////////////////////////////////////////////////// /// Set /// ///////////////////////////////////////////////////////// // Prepare collider and triggers void SetCollider() { // Sphere collider if (gizmoType == GizmoType.Sphere) { SphereCollider col = gameObject.AddComponent(); col.isTrigger = type != ActivationType.OnCollision; col.radius = sphereRadius; activatorCollider = col; } // Box collider if (gizmoType == GizmoType.Box) { BoxCollider col = gameObject.AddComponent(); col.isTrigger = type != ActivationType.OnCollision; col.size = boxSize; activatorCollider = col; } // Custom colliders if (gizmoType == GizmoType.Collider) { Collider[] colliders = GetComponents(); if (colliders.Length == 0) Debug.Log ("RayFire Activator: " + name + " has no activation collider", gameObject); if (type != ActivationType.OnCollision) for (int i = 0; i < colliders.Length; i++) colliders[i].isTrigger = type != ActivationType.OnCollision; } } // Prepare particle system void SetParticleSystem() { collisionEvents = new List(); ps = GetComponent(); if (ps != null) { ParticleSystem.CollisionModule cm = ps.collision; cm.enabled = true; cm.enableDynamicColliders = true; cm.sendCollisionMessages = true; } if (type != ActivationType.OnCollision) { type = ActivationType.OnCollision; Debug.Log ("Rayfire Activator: " + name + " Particle System Gizmo Type supports only On Collision Activation type. Set Activation Type to On Collision." , gameObject); } } /// ///////////////////////////////////////////////////////// /// Activation /// ///////////////////////////////////////////////////////// // Check for activation void ActivationCheck (Collider coll) { if (checkRigid == true) RigidListActivationCheck (coll); if (checkRigidRoot == true) RigidRootActivationCheck (coll); } /// ///////////////////////////////////////////////////////// /// Rigid Activation /// ///////////////////////////////////////////////////////// // Check for Rigid activation void RigidListActivationCheck(Collider coll) { // Get rigid from collider or rigid body RayfireRigid rigid = coll.attachedRigidbody == null ? coll.GetComponent() : coll.attachedRigidbody.GetComponent(); // Has no rigid if (rigid == null) return; // Mesh Root rigid if (rigid.objectType == ObjectType.MeshRoot) return; // Activation TODO ??? only for mesh type ??? if (rigid.activation.act == true) if (rigid.simulationType == SimType.Inactive || rigid.simulationType == SimType.Kinematic) { if (delay <= 0) Activate(rigid); else StartCoroutine (DelayedActivationCor (rigid)); } // Connected cluster one fragment detach if (rigid.objectType == ObjectType.ConnectedCluster) if (demolishCluster == true) { if (delay <= 0) { RFDemolitionCluster.DemolishConnectedCluster (rigid, new[] { coll }); // Init particles // RFParticles.InitDemolitionParticles(rigid); } else StartCoroutine (DelayedClusterCor (rigid, coll)); } } // Exclude from simulation and keep object in scene IEnumerator DelayedActivationCor (RayfireRigid rigid) { // Wait life time yield return new WaitForSeconds (delay); // Activate if (rigid != null) Activate(rigid);; } // Demolish cluster IEnumerator DelayedClusterCor (RayfireRigid rigid, Collider coll) { // Wait life time yield return new WaitForSeconds (delay); // Activate if (rigid != null && coll != null) RFDemolitionCluster.DemolishConnectedCluster (rigid, new[] {coll}); } // ActivateRigid void Activate(RayfireRigid rigid) { // Activate rigid.Activate(); // Add force AddForce (rigid.physics.rigidBody); } /// ///////////////////////////////////////////////////////// /// Rigid Root Activation /// ///////////////////////////////////////////////////////// // Check for Rigid activation void RigidRootActivationCheck(Collider coll) { // TODO cache activated collider and skip them before get component in parent // TODO register all RigidRoots and their gameobjects in manager and check for them by coll.gameobject // Has no rigid root as parent if (coll.transform.parent == null) return; // Get rigid root RayfireRigidRoot rigidRoot = null; if (coll.transform.parent != null) rigidRoot = coll.transform.parent.GetComponentInParent(); // Has no rigid if (rigidRoot == null) return; // Activation if (rigidRoot.activation.act == true) if (rigidRoot.simulationType == SimType.Inactive || rigidRoot.simulationType == SimType.Kinematic) { if (delay <= 0) ActivateCollider(rigidRoot, coll); else StartCoroutine (DelayedActivationCor (rigidRoot, coll)); } } // Exclude from simulation and keep object in scene IEnumerator DelayedActivationCor (RayfireRigidRoot rigidRoot, Collider coll) { // Wait life time yield return new WaitForSeconds (delay); // Activate if (rigidRoot != null) ActivateCollider(rigidRoot, coll); } // Activate shard by collider void ActivateCollider (RayfireRigidRoot rigidRoot, Collider coll) { for (int i = rigidRoot.inactiveShards.Count - 1; i >= 0; i--) { if ( rigidRoot.inactiveShards[i].col == coll) { // Activate and remove if activated if (RFActivation.ActivateShard (rigidRoot.inactiveShards[i], rigidRoot) == true) { // Add force AddForce (rigidRoot.inactiveShards[i].rb); // Remove from list rigidRoot.inactiveShards.RemoveAt (i); } // Break because collider matched shard break; } } } /// ///////////////////////////////////////////////////////// /// Force /// ///////////////////////////////////////////////////////// // Add force to rigidbody void AddForce(Rigidbody rb) { if (apply == true) { // Velocity if (velocity != Vector3.zero) { if (coord == false) rb.AddForce (velocity, mode); else rb.AddForce (transform.TransformDirection (velocity), mode); } // Angular velocity if (spin != Vector3.zero) { rb.AddTorque (spin, mode); } } } /// ///////////////////////////////////////////////////////// /// Animation /// ///////////////////////////////////////////////////////// // Trigger animation start public void TriggerAnimation() { // Already animating if (animating == true) return; // Set animation data SetAnimation(); // Positions check if (positionList.Count < 2 && scaleAnimation == 1f) { Debug.Log ("Position list is empty and scale is not animated"); return; } // Start animation StartCoroutine (AnimationCor()); } // Set animation adata void SetAnimation() { // Set points if (ByLine == true) SetWorldPointsByLine(); // Set points if (positionAnimation == AnimationType.ByLocalPositionList) SetWorldPointsByLocal(); // Set ration checkpoints SetCheckPoints(); } // Set points by line void SetWorldPointsByLine() { // Null check if (line == null) { Debug.Log ("Path line is not defined"); return; } // Set points positionList = new List(); for (int i = 0; i < line.positionCount; i++) positionList.Add (line.transform.TransformPoint (line.GetPosition (i))); // Add first point if looped if (line.loop == true) positionList.Add (positionList[0]); } // Set points by line void SetWorldPointsByLocal() { // Positions check if (positionList.Count < 2) return; // List of world positions with current position as start List worldPoints = new List(){transform.position}; for (int i = 1; i < positionList.Count; i++) worldPoints.Add (transform.position + positionList[i]); // Set to position list positionList.Clear(); positionList = worldPoints; /* // List of world positions with current position as start positionList.Insert(0, transform.position); for (int i = 1; i < positionList.Count; i++) positionList[i] += transform.position; */ } // Set ration checkpoints void SetCheckPoints() { // Positions check if (positionList.Count < 2) return; // Total and segments length lineLength = 0f; List segmentsLength = new List(); if (positionList.Count >= 2) { for (int i = 0; i < positionList.Count - 1; i++) { float length = Vector3.Distance (positionList[i], positionList[i + 1]); segmentsLength.Add (length); lineLength += length; } } // Get segments ration checkpoints float sum = 0f; checkpoints = new float[segmentsLength.Count + 1]; for (int i = 0; i < segmentsLength.Count; i++) { float localRation = segmentsLength[i] / lineLength * 100f; checkpoints[i] = sum; sum += localRation; } checkpoints[segmentsLength.Count] = 100f; } //Animation over line coroutine IEnumerator AnimationCor() { // Stop if (animating == true) yield break; // Set state On animating = true; // Set starting position if (positionList.Count >= 2) transform.position = positionList[0]; while (timePassed < duration) { // Stop if (animating == false) yield break; // Update all info for dynamic line if (positionAnimation == AnimationType.ByDynamicLine) SetAnimation(); // Prepare info delta = Time.deltaTime; timePassed += delta; // Position animation if (positionList.Count >= 2) { // Increase time and path ratio deltaRatioStep = delta / duration; distDeltaStep = lineLength * deltaRatioStep; distRatio = distDeltaStep / lineLength * 100f; pathRatio += distRatio; // Get active line segment activeSegment = GetSegment (pathRatio); float segmentRate = (checkpoints[activeSegment + 1] - pathRatio) / (checkpoints[activeSegment + 1] - checkpoints[activeSegment]); Vector3 stepPos = Vector3.Lerp (positionList[activeSegment + 1], positionList[activeSegment], segmentRate); transform.position = stepPos; } // Scale animation if (scaleAnimation > 1f) { float scaleRate = timePassed / duration; Vector3 maxScale = new Vector3 (scaleAnimation, scaleAnimation, scaleAnimation); Vector3 newScale = Vector3.Lerp (scaleStart, maxScale, scaleRate); transform.localScale = newScale; } // Wait yield return null; } // Reset data ResetData(); } // Get active segment id int GetSegment (float ration) { if (checkpoints.Length > 2) { for (int i = 0; i < checkpoints.Length - 1; i++) if (ration > checkpoints[i] && ration < checkpoints[i + 1]) return i; return checkpoints.Length - 2; } return 0; } // Reset animation info void ResetData() { animating = false; pathRatio = 0f; lineLength = 0f; checkpoints = null; delta = 0f; deltaRatioStep = 0f; distDeltaStep = 0f; distRatio = 0f; timePassed = 0f; activeSegment = 0; } // Stop animation public void StopAnimation() { animating = false; } // Stop animation public void ResetAnimation() { // Reset info ResetData(); // Reset position transform.position = positionStart; } // Add new position public void AddPosition (Vector3 newPos) { // Only for global and local if (ByLine == true) { Debug.Log ("Position can be saved only for Global and Local Position animation type."); return; } // Create list if (positionList == null) positionList = new List(); // Same position if (positionList.Count > 0 && newPos == positionList[positionList.Count - 1]) { Debug.Log ("Activator at the same position."); return; } // Save global position if (positionAnimation == AnimationType.ByGlobalPositionList) { // Check for empty list or same position if (positionList.Count == 0 || newPos != positionList[positionList.Count - 1]) positionList.Add (newPos); } // Save global position if (positionAnimation == AnimationType.ByLocalPositionList) { // First position in world space to save other position in local space relative to first position if (positionList.Count == 0) positionList.Add (newPos); // Other positions in local space relative to first else positionList.Add (newPos - positionList[0]); } } /// ///////////////////////////////////////////////////////// /// Gizmo /// ///////////////////////////////////////////////////////// // Set gizmo public void SetGizmoType (GizmoType gizmo) { gizmoType = gizmo; // Set new collider if (Application.isPlaying == true) { // Destroy existing collider if (activatorCollider != null) Destroy (activatorCollider); // Set new collider SetCollider(); } } /// ///////////////////////////////////////////////////////// /// Getters /// ///////////////////////////////////////////////////////// public bool ByPositions { get { return positionAnimation == AnimationType.ByLocalPositionList || positionAnimation == AnimationType.ByGlobalPositionList; } } public bool ByLine { get { return positionAnimation == AnimationType.ByStaticLine || positionAnimation == AnimationType.ByDynamicLine; } } } }