#if CINEMACHINE_PHYSICS || CINEMACHINE_PHYSICS_2D using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.Events; using UnityEngine.Serialization; #if CINEMACHINE_TIMELINE using UnityEngine.Playables; #endif namespace Unity.Cinemachine { /// /// A multi-purpose script which causes an action to occur when /// a trigger collider is entered and exited. /// [SaveDuringPlay] [AddComponentMenu("Cinemachine/Helpers/Cinemachine Trigger Action")] [HelpURL(Documentation.BaseURL + "manual/CinemachineTriggerAction.html")] public class CinemachineTriggerAction : MonoBehaviour { /// Only triggers generated by objects on these layers will be considered. [Header("Trigger Object Filter")] [Tooltip("Only triggers generated by objects on these layers will be considered")] [FormerlySerializedAs("m_LayerMask")] public LayerMask LayerMask = 1; /// If set, only triggers generated by objects with this tag will be considered [TagField] [Tooltip("If set, only triggers generated by objects with this tag will be considered")] [FormerlySerializedAs("m_WithTag")] public string WithTag = string.Empty; /// Triggers generated by objects with this tag will be ignored [TagField] [Tooltip("Triggers generated by objects with this tag will be ignored")] [FormerlySerializedAs("m_WithoutTag")] public string WithoutTag = string.Empty; /// Skip this many trigger entries before taking action [NoSaveDuringPlay] [Tooltip("Skip this many trigger entries before taking action")] [FormerlySerializedAs("m_SkipFirst")] public int SkipFirst = 0; /// Repeat the action for all subsequent trigger entries [Tooltip("Repeat the action for all subsequent trigger entries")] [FormerlySerializedAs("m_Repeating")] public bool Repeating = true; /// What action to take when an eligible object enters the collider or trigger zone [Tooltip("What action to take when an eligible object enters the collider or trigger zone")] [FormerlySerializedAs("m_OnObjectEnter")] public ActionSettings OnObjectEnter = new(ActionSettings.ActionModes.EventOnly); /// What action to take when an eligible object exits the collider or trigger zone [Tooltip("What action to take when an eligible object exits the collider or trigger zone")] [FormerlySerializedAs("m_OnObjectExit")] public ActionSettings OnObjectExit = new(ActionSettings.ActionModes.EventOnly); HashSet m_ActiveTriggerObjects = new(); /// Defines what action to take on trigger enter/exit [Serializable] public struct ActionSettings { /// What action to take public enum ActionModes { /// Use the event only EventOnly, /// Boost priority of virtual camera target PriorityBoost, /// Activate the target GameObject Activate, /// Deactivate target GameObject Deactivate, /// Enable a component Enable, /// Disable a component Disable, #if CINEMACHINE_TIMELINE /// Start animation on target Play, /// Stop animation on target Stop #endif } /// Serializable parameterless game event [Serializable] public class TriggerEvent : UnityEvent {} /// What action to take [Tooltip("What action to take")] [FormerlySerializedAs("m_Action")] public ActionModes Action; /// The target object on which to operate. If null, then the current behaviour/GameObject will be used [Tooltip("The target object on which to operate. If null, then the current behaviour/GameObject will be used")] [FormerlySerializedAs("m_Target")] public UnityEngine.Object Target; /// If PriorityBoost, this amount will be added to the virtual camera's priority [Tooltip("If PriorityBoost, this amount will be added to the virtual camera's priority")] [FormerlySerializedAs("m_BoostAmount")] public int BoostAmount; /// If playing a timeline, start at this time [Tooltip("If playing a timeline, start at this time")] [FormerlySerializedAs("m_StartTime")] public float StartTime; /// How to interpret the start time public enum TimeModes { /// Offset after the start of the timeline FromStart, /// Offset before the end of the timeline FromEnd, /// Offset before the current timeline time BeforeNow, /// Offset after the current timeline time AfterNow }; /// How to interpret the start time [Tooltip("How to interpret the start time")] [FormerlySerializedAs("m_Mode")] public TimeModes Mode; /// This event will be invoked [Tooltip("This event will be invoked")] [FormerlySerializedAs("m_Event")] public TriggerEvent Event; /// Standard Constructor /// Action to set public ActionSettings(ActionModes action) { Action = action; Target = null; BoostAmount = 0; StartTime = 0; Mode = TimeModes.FromStart; Event = new TriggerEvent(); } /// Invoke the action. Depending on the mode, different action will /// be performed. The embedded event will always be invoked, in addition to the /// action specified by the Mode. public void Invoke() { UnityEngine.Object currentTarget = Target; if (currentTarget != null) { var targetGameObject = currentTarget as GameObject; var targetBehaviour = currentTarget as Behaviour; if (targetBehaviour != null) targetGameObject = targetBehaviour.gameObject; switch (Action) { case ActionModes.EventOnly: break; case ActionModes.PriorityBoost: { if (targetGameObject.TryGetComponent(out var vcam)) { vcam.Priority.Value += BoostAmount; vcam.Prioritize(); } break; } case ActionModes.Activate: if (targetGameObject != null) { targetGameObject.SetActive(true); if (targetGameObject.TryGetComponent(out var vcam)) vcam.Prioritize(); } break; case ActionModes.Deactivate: if (targetGameObject != null) targetGameObject.SetActive(false); break; case ActionModes.Enable: { if (targetBehaviour != null) targetBehaviour.enabled = true; break; } case ActionModes.Disable: { if (targetBehaviour != null) targetBehaviour.enabled = false; break; } #if CINEMACHINE_TIMELINE case ActionModes.Play: { if (targetGameObject.TryGetComponent(out var playable)) { double startTime = 0; double duration = playable.duration; double current = playable.time; switch (Mode) { default: case TimeModes.FromStart: startTime += StartTime; break; case TimeModes.FromEnd: startTime = duration - StartTime; break; case TimeModes.BeforeNow: startTime = current - StartTime; break; case TimeModes.AfterNow: startTime = current + StartTime; break; } playable.time = startTime; playable.Play(); } else { if (targetGameObject.TryGetComponent(out var ani)) ani.Play(); } break; } case ActionModes.Stop: { if (targetGameObject.TryGetComponent(out var playable)) playable.Stop(); else { if (targetGameObject.TryGetComponent(out var ani)) ani.Stop(); } break; } #endif } } Event.Invoke(); } } private bool Filter(GameObject other) { if (!enabled) return false; if (((1 << other.layer) & LayerMask) == 0) return false; if (WithTag.Length != 0 && !other.CompareTag(WithTag)) return false; if (WithoutTag.Length != 0 && other.CompareTag(WithoutTag)) return false; return true; } void InternalDoTriggerEnter(GameObject other) { if (!Filter(other)) return; --SkipFirst; if (SkipFirst > -1) return; if (!Repeating && SkipFirst != -1) return; m_ActiveTriggerObjects.Add(other); OnObjectEnter.Invoke(); } void InternalDoTriggerExit(GameObject other) { if (!m_ActiveTriggerObjects.Contains(other)) return; m_ActiveTriggerObjects.Remove(other); if (enabled) OnObjectExit.Invoke(); } #if CINEMACHINE_PHYSICS void OnTriggerEnter(Collider other) => InternalDoTriggerEnter(other.gameObject); void OnTriggerExit(Collider other) => InternalDoTriggerExit(other.gameObject); void OnCollisionEnter(Collision other) => InternalDoTriggerEnter(other.gameObject); void OnCollisionExit(Collision other) => InternalDoTriggerExit(other.gameObject); #endif #if CINEMACHINE_PHYSICS_2D void OnTriggerEnter2D(Collider2D other) => InternalDoTriggerEnter(other.gameObject); void OnTriggerExit2D(Collider2D other) => InternalDoTriggerExit(other.gameObject); void OnCollisionEnter2D(Collision2D other) => InternalDoTriggerEnter(other.gameObject); void OnCollisionExit2D(Collision2D other) => InternalDoTriggerExit(other.gameObject); #endif void OnEnable() {} // For the Enabled checkbox } } #endif