코드 리팩토링
재사용성 및 확장성을 고려하여 코드 전반을 리팩토링함
This commit is contained in:
215
Assets/Scripts/Utilities/ActionExecutor.cs
Normal file
215
Assets/Scripts/Utilities/ActionExecutor.cs
Normal file
@@ -0,0 +1,215 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Reusable action execution system with busy state management.
|
||||
/// Replaces repeated coroutine busy-state patterns across the codebase.
|
||||
/// </summary>
|
||||
public class ActionExecutor : MonoBehaviour
|
||||
{
|
||||
public bool IsBusy { get; private set; }
|
||||
|
||||
private Coroutine _currentAction;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when an action starts.
|
||||
/// </summary>
|
||||
public event Action OnActionStarted;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when an action completes.
|
||||
/// </summary>
|
||||
public event Action OnActionCompleted;
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when an action is cancelled.
|
||||
/// </summary>
|
||||
public event Action OnActionCancelled;
|
||||
|
||||
/// <summary>
|
||||
/// Try to execute an action. Returns false if already busy.
|
||||
/// </summary>
|
||||
/// <param name="request">The action request to execute</param>
|
||||
/// <returns>True if action started, false if busy</returns>
|
||||
public bool TryExecute(ActionRequest request)
|
||||
{
|
||||
if (IsBusy) return false;
|
||||
|
||||
_currentAction = StartCoroutine(ExecuteRoutine(request));
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancel the current action if one is running.
|
||||
/// </summary>
|
||||
public void Cancel()
|
||||
{
|
||||
if (_currentAction != null)
|
||||
{
|
||||
StopCoroutine(_currentAction);
|
||||
_currentAction = null;
|
||||
IsBusy = false;
|
||||
OnActionCancelled?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Force reset the busy state. Use with caution.
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configuration for an action to be executed by ActionExecutor.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public struct ActionRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Animator to trigger animations on.
|
||||
/// </summary>
|
||||
public Animator Animator;
|
||||
|
||||
/// <summary>
|
||||
/// Animation trigger name.
|
||||
/// </summary>
|
||||
public string AnimTrigger;
|
||||
|
||||
/// <summary>
|
||||
/// Animation playback speed multiplier.
|
||||
/// </summary>
|
||||
public float AnimSpeed;
|
||||
|
||||
/// <summary>
|
||||
/// Time delay before the impact/effect happens (for syncing with animation).
|
||||
/// </summary>
|
||||
public float ImpactDelay;
|
||||
|
||||
/// <summary>
|
||||
/// Total duration of the action.
|
||||
/// </summary>
|
||||
public float TotalDuration;
|
||||
|
||||
/// <summary>
|
||||
/// Callback invoked when action starts.
|
||||
/// </summary>
|
||||
public Action OnStart;
|
||||
|
||||
/// <summary>
|
||||
/// Callback invoked at the impact moment.
|
||||
/// </summary>
|
||||
public Action OnImpact;
|
||||
|
||||
/// <summary>
|
||||
/// Callback invoked when action completes.
|
||||
/// </summary>
|
||||
public Action OnComplete;
|
||||
|
||||
/// <summary>
|
||||
/// Create a simple action request with just timing.
|
||||
/// </summary>
|
||||
public static ActionRequest Simple(float duration, Action onComplete)
|
||||
{
|
||||
return new ActionRequest
|
||||
{
|
||||
TotalDuration = duration,
|
||||
AnimSpeed = 1f,
|
||||
OnComplete = onComplete
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an animated action request.
|
||||
/// </summary>
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a full action request with all callbacks.
|
||||
/// </summary>
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Utilities/ActionExecutor.cs.meta
Normal file
2
Assets/Scripts/Utilities/ActionExecutor.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 27e07922480ecf44faf5135ad4872531
|
||||
228
Assets/Scripts/Utilities/PhysicsQueryUtility.cs
Normal file
228
Assets/Scripts/Utilities/PhysicsQueryUtility.cs
Normal file
@@ -0,0 +1,228 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
/// <summary>
|
||||
/// Utility class for common physics queries.
|
||||
/// Eliminates repeated OverlapSphere patterns across the codebase.
|
||||
/// </summary>
|
||||
public static class PhysicsQueryUtility
|
||||
{
|
||||
// Reusable buffer to avoid allocations (32 should be enough for most cases)
|
||||
private static readonly Collider[] OverlapBuffer = new Collider[32];
|
||||
|
||||
/// <summary>
|
||||
/// Find the closest object implementing interface T within radius.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Interface or component type to search for</typeparam>
|
||||
/// <param name="origin">Center point of the search</param>
|
||||
/// <param name="radius">Search radius</param>
|
||||
/// <param name="layerMask">Layer mask to filter objects</param>
|
||||
/// <returns>Closest object of type T, or null if none found</returns>
|
||||
public static T FindClosest<T>(Vector3 origin, float radius, LayerMask layerMask) where T : class
|
||||
{
|
||||
int count = Physics.OverlapSphereNonAlloc(origin, radius, OverlapBuffer, layerMask);
|
||||
|
||||
T closest = null;
|
||||
float minDistSqr = float.MaxValue;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
T component = OverlapBuffer[i].GetComponentInParent<T>();
|
||||
if (component != null)
|
||||
{
|
||||
float distSqr = (origin - OverlapBuffer[i].transform.position).sqrMagnitude;
|
||||
if (distSqr < minDistSqr)
|
||||
{
|
||||
minDistSqr = distSqr;
|
||||
closest = component;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return closest;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find the closest object implementing interface T within radius, also returning distance.
|
||||
/// </summary>
|
||||
public static T FindClosest<T>(Vector3 origin, float radius, LayerMask layerMask, out float distance) where T : class
|
||||
{
|
||||
int count = Physics.OverlapSphereNonAlloc(origin, radius, OverlapBuffer, layerMask);
|
||||
|
||||
T closest = null;
|
||||
float minDistSqr = float.MaxValue;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
T component = OverlapBuffer[i].GetComponentInParent<T>();
|
||||
if (component != null)
|
||||
{
|
||||
float distSqr = (origin - OverlapBuffer[i].transform.position).sqrMagnitude;
|
||||
if (distSqr < minDistSqr)
|
||||
{
|
||||
minDistSqr = distSqr;
|
||||
closest = component;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
distance = closest != null ? Mathf.Sqrt(minDistSqr) : 0f;
|
||||
return closest;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute action on all objects of type T within radius.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Interface or component type to search for</typeparam>
|
||||
/// <param name="origin">Center point of the search</param>
|
||||
/// <param name="radius">Search radius</param>
|
||||
/// <param name="layerMask">Layer mask to filter objects</param>
|
||||
/// <param name="action">Action to execute on each found object</param>
|
||||
public static void ForEachInRadius<T>(Vector3 origin, float radius, LayerMask layerMask, Action<T> action) where T : class
|
||||
{
|
||||
int count = Physics.OverlapSphereNonAlloc(origin, radius, OverlapBuffer, layerMask);
|
||||
|
||||
// Track processed objects to avoid duplicates (same object hit by multiple colliders)
|
||||
var processed = new HashSet<T>();
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
T component = OverlapBuffer[i].GetComponentInParent<T>();
|
||||
if (component != null && !processed.Contains(component))
|
||||
{
|
||||
processed.Add(component);
|
||||
action(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Execute action on all objects of type T within radius, with additional context.
|
||||
/// </summary>
|
||||
public static void ForEachInRadius<T>(Vector3 origin, float radius, LayerMask layerMask,
|
||||
Action<T, Collider> action) where T : class
|
||||
{
|
||||
int count = Physics.OverlapSphereNonAlloc(origin, radius, OverlapBuffer, layerMask);
|
||||
|
||||
var processed = new HashSet<T>();
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
T component = OverlapBuffer[i].GetComponentInParent<T>();
|
||||
if (component != null && !processed.Contains(component))
|
||||
{
|
||||
processed.Add(component);
|
||||
action(component, OverlapBuffer[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all objects of type T within radius.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Interface or component type to search for</typeparam>
|
||||
/// <param name="origin">Center point of the search</param>
|
||||
/// <param name="radius">Search radius</param>
|
||||
/// <param name="layerMask">Layer mask to filter objects</param>
|
||||
/// <returns>List of all objects of type T within radius</returns>
|
||||
public static List<T> FindAllInRadius<T>(Vector3 origin, float radius, LayerMask layerMask) where T : class
|
||||
{
|
||||
var results = new List<T>();
|
||||
int count = Physics.OverlapSphereNonAlloc(origin, radius, OverlapBuffer, layerMask);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
T component = OverlapBuffer[i].GetComponentInParent<T>();
|
||||
if (component != null && !results.Contains(component))
|
||||
{
|
||||
results.Add(component);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Find all objects of type T within radius, sorted by distance (closest first).
|
||||
/// </summary>
|
||||
public static List<T> FindAllInRadiusSorted<T>(Vector3 origin, float radius, LayerMask layerMask) where T : class
|
||||
{
|
||||
int count = Physics.OverlapSphereNonAlloc(origin, radius, OverlapBuffer, layerMask);
|
||||
|
||||
var resultsWithDistance = new List<(T component, float distSqr)>();
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
T component = OverlapBuffer[i].GetComponentInParent<T>();
|
||||
if (component != null)
|
||||
{
|
||||
bool alreadyAdded = false;
|
||||
foreach (var item in resultsWithDistance)
|
||||
{
|
||||
if (ReferenceEquals(item.component, component))
|
||||
{
|
||||
alreadyAdded = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!alreadyAdded)
|
||||
{
|
||||
float distSqr = (origin - OverlapBuffer[i].transform.position).sqrMagnitude;
|
||||
resultsWithDistance.Add((component, distSqr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by distance
|
||||
resultsWithDistance.Sort((a, b) => a.distSqr.CompareTo(b.distSqr));
|
||||
|
||||
var results = new List<T>(resultsWithDistance.Count);
|
||||
foreach (var item in resultsWithDistance)
|
||||
{
|
||||
results.Add(item.component);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if any object of type T exists within radius.
|
||||
/// </summary>
|
||||
public static bool AnyInRadius<T>(Vector3 origin, float radius, LayerMask layerMask) where T : class
|
||||
{
|
||||
int count = Physics.OverlapSphereNonAlloc(origin, radius, OverlapBuffer, layerMask);
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (OverlapBuffer[i].GetComponentInParent<T>() != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Count objects of type T within radius.
|
||||
/// </summary>
|
||||
public static int CountInRadius<T>(Vector3 origin, float radius, LayerMask layerMask) where T : class
|
||||
{
|
||||
int count = Physics.OverlapSphereNonAlloc(origin, radius, OverlapBuffer, layerMask);
|
||||
|
||||
var processed = new HashSet<T>();
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
T component = OverlapBuffer[i].GetComponentInParent<T>();
|
||||
if (component != null)
|
||||
{
|
||||
processed.Add(component);
|
||||
}
|
||||
}
|
||||
|
||||
return processed.Count;
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Utilities/PhysicsQueryUtility.cs.meta
Normal file
2
Assets/Scripts/Utilities/PhysicsQueryUtility.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e1262d37bb47b7d4ea78d6426675ae9d
|
||||
Reference in New Issue
Block a user