363 lines
10 KiB
C#
363 lines
10 KiB
C#
using System.Collections;
|
|
using UnityEngine;
|
|
using UnityEngine.Events;
|
|
|
|
/// <summary>
|
|
/// A flexible trigger zone script for horror events, cutscenes, and level interactions.
|
|
/// Can close/lock doors, turn off/flicker lights, play dialogue/audio, spawn/despawn objects,
|
|
/// and execute custom UnityEvents.
|
|
/// </summary>
|
|
[RequireComponent(typeof(Collider))]
|
|
public class Trigger : MonoBehaviour
|
|
{
|
|
[Header("Trigger Activation Rules")]
|
|
[Tooltip("If true, this trigger will only execute once and then deactivate.")]
|
|
[SerializeField] private bool triggerOnce = true;
|
|
|
|
[Tooltip("Delay in seconds before executing the actions after player enters the trigger.")]
|
|
[SerializeField] private float delay = 0f;
|
|
|
|
[Tooltip("Minimum time between triggers (ignored if Trigger Once is true).")]
|
|
[SerializeField] private float cooldown = 0f;
|
|
|
|
[Tooltip("Trigger when the player enters the collider.")]
|
|
[SerializeField] private bool triggerOnEnter = true;
|
|
|
|
[Tooltip("Trigger when the player exits the collider.")]
|
|
[SerializeField] private bool triggerOnExit = false;
|
|
|
|
[Header("Detection Filters")]
|
|
[Tooltip("Tag required to activate the trigger (leave empty to ignore tag check).")]
|
|
[SerializeField] private string requiredTag = "Player";
|
|
|
|
[Tooltip("If true, requires the entering object to have a PlayerController component.")]
|
|
[SerializeField] private bool requirePlayerController = true;
|
|
|
|
[Tooltip("Allowed physics layers.")]
|
|
[SerializeField] private LayerMask detectionLayers = ~0;
|
|
|
|
[Header("--- Horror & Level Actions ---")]
|
|
|
|
[Header("Doors")]
|
|
[Tooltip("Doors to automatically close when triggered.")]
|
|
[SerializeField] private Door[] doorsToClose;
|
|
|
|
[Tooltip("Doors to automatically open when triggered.")]
|
|
[SerializeField] private Door[] doorsToOpen;
|
|
|
|
[Tooltip("Doors to lock when triggered.")]
|
|
[SerializeField] private Door[] doorsToLock;
|
|
|
|
[Tooltip("Doors to unlock when triggered.")]
|
|
[SerializeField] private Door[] doorsToUnlock;
|
|
|
|
[Header("Lights")]
|
|
[Tooltip("Lights to immediately turn off.")]
|
|
[SerializeField] private Light[] lightsToTurnOff;
|
|
|
|
[Tooltip("Lights to immediately turn on.")]
|
|
[SerializeField] private Light[] lightsToTurnOn;
|
|
|
|
[Tooltip("Lights to flicker (for spooky horror effect).")]
|
|
[SerializeField] private Light[] lightsToFlicker;
|
|
[SerializeField] private int flickerCount = 4;
|
|
[SerializeField] private float flickerSpeed = 0.08f;
|
|
|
|
[Header("GameObjects (Spawning / Despawning)")]
|
|
[Tooltip("GameObjects to activate (e.g. monster, jumpscare prop, blood decal).")]
|
|
[SerializeField] private GameObject[] objectsToActivate;
|
|
|
|
[Tooltip("GameObjects to deactivate.")]
|
|
[SerializeField] private GameObject[] objectsToDeactivate;
|
|
|
|
[Header("Audio & Jumpscares")]
|
|
[Tooltip("Audio clip to play upon trigger.")]
|
|
[SerializeField] private AudioClip soundEffect;
|
|
[SerializeField] private AudioSource audioSource;
|
|
[Range(0f, 1f)]
|
|
[SerializeField] private float soundVolume = 1f;
|
|
|
|
[Header("Dialogue & Subtitles")]
|
|
[TextArea(2, 4)]
|
|
[Tooltip("Dialogue or monologue line displayed on the player's screen.")]
|
|
[SerializeField] private string dialogueText = "";
|
|
[Tooltip("How long the dialogue stays on screen.")]
|
|
[SerializeField] private float dialogueDuration = 4f;
|
|
|
|
[Header("Custom Events")]
|
|
[Tooltip("UnityEvent invoked when the trigger is activated.")]
|
|
[SerializeField] private UnityEvent onTriggered;
|
|
|
|
[Tooltip("UnityEvent passing the entering GameObject.")]
|
|
[SerializeField] private UnityEvent<GameObject> onTriggeredByGameObject;
|
|
|
|
[Tooltip("UnityEvent invoked when the player exits the trigger.")]
|
|
[SerializeField] private UnityEvent onTriggerExitEvent;
|
|
|
|
[Header("Editor Visualization")]
|
|
[SerializeField] private Color gizmoColor = new Color(1f, 0.3f, 0.1f, 0.35f);
|
|
[SerializeField] private bool showGizmos = true;
|
|
|
|
// Runtime state
|
|
private bool hasTriggered = false;
|
|
private float lastTriggerTime = -999f;
|
|
private Collider triggerCollider;
|
|
|
|
private void Reset()
|
|
{
|
|
triggerCollider = GetComponent<Collider>();
|
|
if (triggerCollider != null)
|
|
{
|
|
triggerCollider.isTrigger = true;
|
|
}
|
|
}
|
|
|
|
private void Awake()
|
|
{
|
|
triggerCollider = GetComponent<Collider>();
|
|
if (triggerCollider != null)
|
|
{
|
|
triggerCollider.isTrigger = true;
|
|
}
|
|
}
|
|
|
|
private void OnTriggerEnter(Collider other)
|
|
{
|
|
if (!triggerOnEnter) return;
|
|
|
|
if (IsValidTarget(other.gameObject))
|
|
{
|
|
TryExecuteTrigger(other.gameObject);
|
|
}
|
|
}
|
|
|
|
private void OnTriggerExit(Collider other)
|
|
{
|
|
if (!IsValidTarget(other.gameObject)) return;
|
|
|
|
onTriggerExitEvent?.Invoke();
|
|
|
|
if (triggerOnExit)
|
|
{
|
|
TryExecuteTrigger(other.gameObject);
|
|
}
|
|
}
|
|
|
|
private bool IsValidTarget(GameObject target)
|
|
{
|
|
// Check LayerMask
|
|
if (((1 << target.layer) & detectionLayers) == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Check Tag
|
|
if (!string.IsNullOrEmpty(requiredTag) && !target.CompareTag(requiredTag))
|
|
{
|
|
// Also check parent tag in case collider is on child object
|
|
if (target.transform.parent == null || !target.transform.parent.CompareTag(requiredTag))
|
|
{
|
|
// If tag fails but requirePlayerController is checked, check for PlayerController
|
|
if (requirePlayerController)
|
|
{
|
|
if (target.GetComponentInParent<PlayerController>() == null)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check PlayerController requirement
|
|
if (requirePlayerController)
|
|
{
|
|
if (target.GetComponentInParent<PlayerController>() == null)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private void TryExecuteTrigger(GameObject target)
|
|
{
|
|
if (triggerOnce && hasTriggered) return;
|
|
if (Time.time < lastTriggerTime + cooldown) return;
|
|
|
|
hasTriggered = true;
|
|
lastTriggerTime = Time.time;
|
|
|
|
if (delay > 0f)
|
|
{
|
|
StartCoroutine(ExecuteActionsDelayed(target, delay));
|
|
}
|
|
else
|
|
{
|
|
ExecuteActions(target);
|
|
}
|
|
}
|
|
|
|
private IEnumerator ExecuteActionsDelayed(GameObject target, float waitTime)
|
|
{
|
|
yield return new WaitForSeconds(waitTime);
|
|
ExecuteActions(target);
|
|
}
|
|
|
|
private void ExecuteActions(GameObject target)
|
|
{
|
|
// 1. Doors Actions
|
|
if (doorsToClose != null)
|
|
{
|
|
foreach (var door in doorsToClose)
|
|
{
|
|
if (door != null) door.CloseDoor();
|
|
}
|
|
}
|
|
|
|
if (doorsToOpen != null)
|
|
{
|
|
foreach (var door in doorsToOpen)
|
|
{
|
|
if (door != null) door.OpenDoor();
|
|
}
|
|
}
|
|
|
|
if (doorsToLock != null)
|
|
{
|
|
foreach (var door in doorsToLock)
|
|
{
|
|
if (door != null) door.Lock();
|
|
}
|
|
}
|
|
|
|
if (doorsToUnlock != null)
|
|
{
|
|
foreach (var door in doorsToUnlock)
|
|
{
|
|
if (door != null) door.Unlock();
|
|
}
|
|
}
|
|
|
|
// 2. Lights Actions
|
|
if (lightsToTurnOff != null)
|
|
{
|
|
foreach (var light in lightsToTurnOff)
|
|
{
|
|
if (light != null) light.enabled = false;
|
|
}
|
|
}
|
|
|
|
if (lightsToTurnOn != null)
|
|
{
|
|
foreach (var light in lightsToTurnOn)
|
|
{
|
|
if (light != null) light.enabled = true;
|
|
}
|
|
}
|
|
|
|
if (lightsToFlicker != null && lightsToFlicker.Length > 0)
|
|
{
|
|
StartCoroutine(FlickerLightsRoutine(lightsToFlicker));
|
|
}
|
|
|
|
// 3. GameObjects
|
|
if (objectsToActivate != null)
|
|
{
|
|
foreach (var go in objectsToActivate)
|
|
{
|
|
if (go != null) go.SetActive(true);
|
|
}
|
|
}
|
|
|
|
if (objectsToDeactivate != null)
|
|
{
|
|
foreach (var go in objectsToDeactivate)
|
|
{
|
|
if (go != null) go.SetActive(false);
|
|
}
|
|
}
|
|
|
|
// 4. Audio
|
|
if (soundEffect != null)
|
|
{
|
|
if (audioSource != null)
|
|
{
|
|
audioSource.PlayOneShot(soundEffect, soundVolume);
|
|
}
|
|
else
|
|
{
|
|
AudioSource.PlayClipAtPoint(soundEffect, transform.position, soundVolume);
|
|
}
|
|
}
|
|
|
|
// 5. Dialogue / Subtitle
|
|
if (!string.IsNullOrEmpty(dialogueText))
|
|
{
|
|
if (InteractionUI.Instance != null)
|
|
{
|
|
InteractionUI.Instance.ShowDialogue(dialogueText, dialogueDuration);
|
|
}
|
|
else
|
|
{
|
|
Debug.Log($"[Trigger Dialogue]: {dialogueText}");
|
|
}
|
|
}
|
|
|
|
// 6. Custom Events
|
|
onTriggered?.Invoke();
|
|
if (target != null)
|
|
{
|
|
onTriggeredByGameObject?.Invoke(target);
|
|
}
|
|
}
|
|
|
|
private IEnumerator FlickerLightsRoutine(Light[] lights)
|
|
{
|
|
for (int i = 0; i < flickerCount; i++)
|
|
{
|
|
foreach (var l in lights)
|
|
{
|
|
if (l != null) l.enabled = !l.enabled;
|
|
}
|
|
yield return new WaitForSeconds(flickerSpeed);
|
|
}
|
|
|
|
// Ensure lights end up turned off after flickering
|
|
foreach (var l in lights)
|
|
{
|
|
if (l != null) l.enabled = false;
|
|
}
|
|
}
|
|
|
|
public void ResetTrigger()
|
|
{
|
|
hasTriggered = false;
|
|
}
|
|
|
|
private void OnDrawGizmos()
|
|
{
|
|
if (!showGizmos) return;
|
|
|
|
Gizmos.color = gizmoColor;
|
|
|
|
Collider col = GetComponent<Collider>();
|
|
if (col is BoxCollider box)
|
|
{
|
|
Gizmos.matrix = transform.localToWorldMatrix;
|
|
Gizmos.DrawCube(box.center, box.size);
|
|
Gizmos.DrawWireCube(box.center, box.size);
|
|
}
|
|
else if (col is SphereCollider sphere)
|
|
{
|
|
Gizmos.matrix = transform.localToWorldMatrix;
|
|
Gizmos.DrawSphere(sphere.center, sphere.radius);
|
|
Gizmos.DrawWireSphere(sphere.center, sphere.radius);
|
|
}
|
|
}
|
|
}
|