Smooth 3C & simplify hallucination trigger
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unheimlich.Core.Audio
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for components that react to an AudioSource's playback state.
|
||||
/// Supports three fire conditions: OnPlay, OnFinish, and OnTimeReached.
|
||||
/// Subclasses implement Execute() to define what happens when the condition is met.
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(AudioSource))]
|
||||
public abstract class AudioTriggerBase : MonoBehaviour
|
||||
{
|
||||
public enum FireCondition
|
||||
{
|
||||
/// <summary>Fire as soon as the audio starts playing.</summary>
|
||||
OnPlay,
|
||||
/// <summary>Fire when the audio clip finishes.</summary>
|
||||
OnFinish,
|
||||
/// <summary>Fire when playback reaches a specific timestamp.</summary>
|
||||
OnTimeReached
|
||||
}
|
||||
|
||||
[Header("Audio Trigger")]
|
||||
[Tooltip("When to fire the effect")]
|
||||
[SerializeField] protected FireCondition _fireCondition = FireCondition.OnPlay;
|
||||
|
||||
[Tooltip("Playback time in seconds that triggers the effect (OnTimeReached only)")]
|
||||
[SerializeField] protected float _triggerTime = 1f;
|
||||
|
||||
[Header("Behaviour")]
|
||||
[Tooltip("Prevent firing more than once per session")]
|
||||
[SerializeField] protected bool _fireOnlyOnce = true;
|
||||
|
||||
[Header("Debug")]
|
||||
[SerializeField] protected bool _debugMode;
|
||||
|
||||
protected AudioSource AudioSource { get; private set; }
|
||||
|
||||
private bool _hasFired;
|
||||
private bool _isMonitoring;
|
||||
private Coroutine _finishWatcher;
|
||||
|
||||
protected virtual void Awake()
|
||||
{
|
||||
AudioSource = GetComponent<AudioSource>();
|
||||
}
|
||||
|
||||
protected virtual void OnEnable()
|
||||
{
|
||||
_hasFired = false;
|
||||
_isMonitoring = false;
|
||||
|
||||
if (AudioSource != null && AudioSource.isPlaying)
|
||||
StartMonitoring();
|
||||
}
|
||||
|
||||
protected virtual void OnDisable()
|
||||
{
|
||||
StopMonitoring();
|
||||
}
|
||||
|
||||
// ─── Public API ────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Notify this component that the AudioSource has started playing.
|
||||
/// Wire this to whatever code or event starts the AudioSource.
|
||||
/// </summary>
|
||||
public void OnAudioPlay()
|
||||
{
|
||||
StartMonitoring();
|
||||
|
||||
if (_fireCondition == FireCondition.OnPlay)
|
||||
TryExecute();
|
||||
}
|
||||
|
||||
/// <summary>Resets the fired state so this can fire again.</summary>
|
||||
public void ResetFired()
|
||||
{
|
||||
_hasFired = false;
|
||||
StopMonitoring();
|
||||
}
|
||||
|
||||
// ─── Monitoring ────────────────────────────────────────────────────────────
|
||||
|
||||
private void StartMonitoring()
|
||||
{
|
||||
_isMonitoring = true;
|
||||
|
||||
if (_fireCondition == FireCondition.OnFinish)
|
||||
{
|
||||
StopMonitoring();
|
||||
_finishWatcher = StartCoroutine(WatchForFinish());
|
||||
}
|
||||
}
|
||||
|
||||
private void StopMonitoring()
|
||||
{
|
||||
_isMonitoring = false;
|
||||
|
||||
if (_finishWatcher != null)
|
||||
{
|
||||
StopCoroutine(_finishWatcher);
|
||||
_finishWatcher = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!_isMonitoring || (_fireOnlyOnce && _hasFired))
|
||||
return;
|
||||
|
||||
if (_fireCondition != FireCondition.OnTimeReached)
|
||||
return;
|
||||
|
||||
if (AudioSource != null && AudioSource.isPlaying && AudioSource.time >= _triggerTime)
|
||||
TryExecute();
|
||||
}
|
||||
|
||||
// Uses a coroutine instead of Update polling so the finish detection is frame-accurate
|
||||
// even for very short clips that could end between two Update calls.
|
||||
private IEnumerator WatchForFinish()
|
||||
{
|
||||
if (AudioSource == null)
|
||||
yield break;
|
||||
|
||||
// Wait until the clip actually has a length to work with
|
||||
yield return null;
|
||||
|
||||
if (AudioSource.clip != null)
|
||||
yield return new WaitForSeconds(AudioSource.clip.length);
|
||||
else
|
||||
yield return new WaitUntil(() => !AudioSource.isPlaying);
|
||||
|
||||
TryExecute();
|
||||
_finishWatcher = null;
|
||||
}
|
||||
|
||||
// ─── Internal ──────────────────────────────────────────────────────────────
|
||||
|
||||
private void TryExecute()
|
||||
{
|
||||
if (_fireOnlyOnce && _hasFired)
|
||||
return;
|
||||
|
||||
_hasFired = true;
|
||||
StopMonitoring();
|
||||
Execute();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the fire condition is met. Implement the payload here.
|
||||
/// </summary>
|
||||
protected abstract void Execute();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 16d04d63a4903d54dbd0eee78c2ba121
|
||||
Reference in New Issue
Block a user