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