Files
HorrorStories/Assets/Scripts/Trigger.cs
2026-08-25 16:24:29 +01:00

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);
}
}
}