using System; using System.Collections; using UnityEngine; /// /// Reusable action execution system with busy state management. /// Replaces repeated coroutine busy-state patterns across the codebase. /// public class ActionExecutor : MonoBehaviour { public bool IsBusy { get; private set; } private Coroutine _currentAction; /// /// Event fired when an action starts. /// public event Action OnActionStarted; /// /// Event fired when an action completes. /// public event Action OnActionCompleted; /// /// Event fired when an action is cancelled. /// public event Action OnActionCancelled; /// /// Try to execute an action. Returns false if already busy. /// /// The action request to execute /// True if action started, false if busy public bool TryExecute(ActionRequest request) { if (IsBusy) return false; _currentAction = StartCoroutine(ExecuteRoutine(request)); return true; } /// /// Cancel the current action if one is running. /// public void Cancel() { if (_currentAction != null) { StopCoroutine(_currentAction); _currentAction = null; IsBusy = false; OnActionCancelled?.Invoke(); } } /// /// Force reset the busy state. Use with caution. /// public void ForceReset() { if (_currentAction != null) { StopCoroutine(_currentAction); _currentAction = null; } IsBusy = false; } private IEnumerator ExecuteRoutine(ActionRequest request) { IsBusy = true; OnActionStarted?.Invoke(); // Pre-action callback request.OnStart?.Invoke(); // Animation trigger if (request.Animator != null && !string.IsNullOrEmpty(request.AnimTrigger)) { if (request.AnimSpeed > 0) { request.Animator.SetFloat("ActionSpeed", request.AnimSpeed); } request.Animator.SetTrigger(request.AnimTrigger); } // Wait for impact point if (request.ImpactDelay > 0) { float adjustedDelay = request.AnimSpeed > 0 ? request.ImpactDelay / request.AnimSpeed : request.ImpactDelay; yield return new WaitForSeconds(adjustedDelay); } // Execute main effect request.OnImpact?.Invoke(); // Wait for remaining duration float remainingTime = request.TotalDuration - request.ImpactDelay; if (request.AnimSpeed > 0) { remainingTime /= request.AnimSpeed; } if (remainingTime > 0) { yield return new WaitForSeconds(remainingTime); } // Completion callback request.OnComplete?.Invoke(); IsBusy = false; _currentAction = null; OnActionCompleted?.Invoke(); } } /// /// Configuration for an action to be executed by ActionExecutor. /// [Serializable] public struct ActionRequest { /// /// Animator to trigger animations on. /// public Animator Animator; /// /// Animation trigger name. /// public string AnimTrigger; /// /// Animation playback speed multiplier. /// public float AnimSpeed; /// /// Time delay before the impact/effect happens (for syncing with animation). /// public float ImpactDelay; /// /// Total duration of the action. /// public float TotalDuration; /// /// Callback invoked when action starts. /// public Action OnStart; /// /// Callback invoked at the impact moment. /// public Action OnImpact; /// /// Callback invoked when action completes. /// public Action OnComplete; /// /// Create a simple action request with just timing. /// public static ActionRequest Simple(float duration, Action onComplete) { return new ActionRequest { TotalDuration = duration, AnimSpeed = 1f, OnComplete = onComplete }; } /// /// Create an animated action request. /// public static ActionRequest Animated(Animator animator, string trigger, float duration, float impactDelay, Action onImpact, float speed = 1f) { return new ActionRequest { Animator = animator, AnimTrigger = trigger, AnimSpeed = speed, ImpactDelay = impactDelay, TotalDuration = duration, OnImpact = onImpact }; } /// /// Create a full action request with all callbacks. /// public static ActionRequest Full(Animator animator, string trigger, float duration, float impactDelay, float speed, Action onStart, Action onImpact, Action onComplete) { return new ActionRequest { Animator = animator, AnimTrigger = trigger, AnimSpeed = speed, ImpactDelay = impactDelay, TotalDuration = duration, OnStart = onStart, OnImpact = onImpact, OnComplete = onComplete }; } }