using System.Collections; using UnityEngine; namespace BulletHellTemplate { /// /// Manages range indicators for a top-down game. Handles circle, arrow, cone, AoE, etc. /// [ExecuteAlways] public class IndicatorManager : MonoBehaviour { [Header("Circle Indicators")] public SpriteRenderer circleRangeIndicator; // Large circle for max range public SpriteRenderer castCircleIndicator; // Animated cast circle public SpriteRenderer damageIndicator; // Position circle for AoE center public SpriteRenderer castDamageIndicator; // Animated AoE cast circle [Header("Arrow Indicators")] public SpriteRenderer arrowIndicator; public SpriteRenderer castArrowIndicator; [Header("Cone Indicators")] public SpriteRenderer coneIndicator; public SpriteRenderer castConeIndicator; [Header("Extra")] public SpriteRenderer aoeCurveLine; // Optional curve line from character to AoE center public bool showCircleGizmo = false; public bool showArrowGizmo = false; public bool showConeGizmo = false; public Vector3 arrowRotation = Vector3.zero; public Vector3 arrowSize = Vector3.one; public Vector3 coneRotation = Vector3.zero; public Vector3 coneSize = Vector3.one; /// /// Shows a large circle representing max range. /// /// Maximum radius for the skill. public void ShowCircleRangeIndicator(float radius) { if (circleRangeIndicator != null) { circleRangeIndicator.gameObject.SetActive(true); circleRangeIndicator.transform.localScale = new Vector3(radius, radius, 1f); } } /// /// Shows the smaller circle representing the aimed AoE position, used for RadialAoE in real-time. /// /// Radius for the AoE at the aimed position. /// Where to place the circle in the world. public void ShowPositionAoECircle(float radius, Vector3 worldPosition) { if (damageIndicator != null) { damageIndicator.gameObject.SetActive(true); damageIndicator.transform.position = worldPosition; damageIndicator.transform.localScale = new Vector3(radius, radius, 1f); } } /// /// Hides the smaller position AoE circle. /// public void HidePositionAoECircle() { if (damageIndicator != null) { damageIndicator.gameObject.SetActive(false); } } /// /// Starts the cast circle animation from 0 to radius, then closes indicators. /// public void StartCastCircleIndicator(float radius, float castDuration) { if (castCircleIndicator != null) { castCircleIndicator.gameObject.SetActive(true); castCircleIndicator.transform.localScale = Vector3.zero; StartCoroutine(AnimateCastCircleIndicator(castCircleIndicator, radius, castDuration)); } } /// /// Starts the cast damage AoE animation from 0 to radius, then closes indicators. /// public void StartCastDamageIndicator(float radius, float castDuration) { if (castDamageIndicator != null) { castDamageIndicator.gameObject.SetActive(true); castDamageIndicator.transform.localScale = Vector3.zero; StartCoroutine(AnimateCastCircleIndicator(castDamageIndicator, radius, castDuration)); } } private IEnumerator AnimateCastCircleIndicator(SpriteRenderer target, float targetRadius, float duration) { float elapsed = 0f; Vector3 initialScale = Vector3.zero; Vector3 finalScale = new Vector3(targetRadius, targetRadius, 1f); while (elapsed < duration) { elapsed += Time.deltaTime; float t = Mathf.Clamp01(elapsed / duration); target.transform.localScale = Vector3.Lerp(initialScale, finalScale, t); yield return null; } target.transform.localScale = finalScale; yield return new WaitForSeconds(0.1f); CloseIndicators(); } /// /// Shows an arrow indicator. Rotation/scale handled externally. /// public void ShowArrowIndicator() { if (arrowIndicator != null) { arrowIndicator.gameObject.SetActive(true); } } /// /// Sets arrow position and rotation in top-down (y-axis rotation). /// public void UpdateArrowIndicator(Vector3 position, float distance, float maxRange, Quaternion rotation) { if (arrowIndicator == null) return; arrowIndicator.transform.position = position; // scale factor: distance / maxRange float factor = Mathf.Clamp01(distance / maxRange); // assume arrowIndicator default scale is the "max" size arrowIndicator.transform.localScale = factor * Vector3.one; // only rotate on Y axis arrowIndicator.transform.rotation = rotation; } /// /// Starts the cast arrow animation from 0 to full scale. /// public void StartCastArrowIndicator(Vector3 finalSize, float castDuration) { if (castArrowIndicator != null) { castArrowIndicator.gameObject.SetActive(true); castArrowIndicator.transform.localScale = Vector3.zero; StartCoroutine(AnimateCastArrowConeIndicator(castArrowIndicator, finalSize, castDuration)); } } private IEnumerator AnimateCastArrowConeIndicator(SpriteRenderer rend, Vector3 targetSize, float duration) { float elapsed = 0f; Vector3 initialScale = Vector3.zero; while (elapsed < duration) { elapsed += Time.deltaTime; float t = Mathf.Clamp01(elapsed / duration); rend.transform.localScale = Vector3.Lerp(initialScale, targetSize, t); yield return null; } rend.transform.localScale = targetSize; yield return new WaitForSeconds(0.1f); CloseIndicators(); } /// /// Shows the cone indicator. Rotation/scale handled externally. /// public void ShowConeIndicator() { if (coneIndicator != null) { coneIndicator.gameObject.SetActive(true); } } /// /// Updates cone position/rotation in top-down. /// public void UpdateConeIndicator(Vector3 position, float distance, float maxRange, Quaternion rotation) { if (coneIndicator == null) return; coneIndicator.transform.position = position; float factor = Mathf.Clamp01(distance / maxRange); coneIndicator.transform.localScale = factor * Vector3.one; coneIndicator.transform.rotation = rotation; } public void StartCastConeIndicator(Vector3 finalSize, float castDuration) { if (castConeIndicator != null) { castConeIndicator.gameObject.SetActive(true); castConeIndicator.transform.localScale = Vector3.zero; StartCoroutine(AnimateCastArrowConeIndicator(castConeIndicator, finalSize, castDuration)); } } /// /// Shows a line from the caster to AoE center. /// public void ShowAoECurveLine(Vector3 startPos, Vector3 endPos) { if (aoeCurveLine == null) return; aoeCurveLine.gameObject.SetActive(true); Vector3 midPoint = Vector3.Lerp(startPos, endPos, 0.5f); aoeCurveLine.transform.position = midPoint; Vector3 dir = endPos - startPos; float dist = dir.magnitude; if (dist > 0.0001f) { // rotate around Y only Vector3 dirFlat = new Vector3(dir.x, 0f, dir.z); if (dirFlat.sqrMagnitude > 0.0001f) { aoeCurveLine.transform.rotation = Quaternion.LookRotation(dirFlat, Vector3.up); } aoeCurveLine.transform.localScale = new Vector3(dist, dist, 1f); } } /// /// Hides the AoE curve line after a delay. /// public IEnumerator HideAoECurveLineAfterDelay(float delay) { yield return new WaitForSeconds(delay); if (aoeCurveLine != null) { aoeCurveLine.gameObject.SetActive(false); } } /// /// Closes all indicators immediately. /// public void CloseIndicators() { if (circleRangeIndicator != null) circleRangeIndicator.gameObject.SetActive(false); if (castCircleIndicator != null) castCircleIndicator.gameObject.SetActive(false); if (damageIndicator != null) damageIndicator.gameObject.SetActive(false); if (castDamageIndicator != null) castDamageIndicator.gameObject.SetActive(false); if (arrowIndicator != null) arrowIndicator.gameObject.SetActive(false); if (castArrowIndicator != null) castArrowIndicator.gameObject.SetActive(false); if (coneIndicator != null) coneIndicator.gameObject.SetActive(false); if (castConeIndicator != null) castConeIndicator.gameObject.SetActive(false); if (aoeCurveLine != null) aoeCurveLine.gameObject.SetActive(false); } } }