#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