using UnityEngine; /// /// Анимированный курсор: центральный кружок + пульсирующие кольца. /// Free — зелёный, синхронная пульсация всех объектов. /// Blocked — красный, сжатие. /// Shot — жёлтая вспышка, дальнее кольцо расширяется сильнее. /// public class CursorAnimator : MonoBehaviour { [Header("Дочерние объекты")] public Renderer dot; public Renderer[] rings; [Header("Цвета")] public Color freeColor = new Color(0.1f, 1f, 0.1f, 0.85f); public Color blockedColor = new Color(1f, 0.1f, 0.1f, 0.85f); public Color shotColor = new Color(1f, 0.9f, 0.0f, 1.00f); [Header("Пульсация (Free)")] [Tooltip("Синхронная пульсация всех объектов / со сдвигом фазы между кольцами")] public bool syncPulse = false; [Tooltip("Сдвиг фазы между кольцами (сек) при syncPulse = false")] public float phaseOffset = 0.18f; public float dotMinScale = 0.7f; public float dotMaxScale = 1.0f; public float ringMinScale = 0.5f; public float ringMaxScale = 1.1f; public float pulseSpeed = 1.4f; [Header("Blocked: сжатие")] public float blockedDotScale = 0.35f; public float blockedRingScale = 0.2f; [Header("Shot: расширение")] [Tooltip("Во сколько раз кольцо 0 расширяется при выстреле")] public float shotRingBaseExpand = 1.5f; [Tooltip("Доп. множитель на каждое следующее кольцо")] public float shotRingExpandStep = 0.5f; [Tooltip("Длительность анимации выстрела (сек)")] public float shotDuration = 0.4f; [Header("Переход между состояниями")] public float transitionSpeed = 8f; // ── внутреннее ───────────────────────────────────────────────────────── private enum CursorState { Free, Blocked } private CursorState curState = CursorState.Free; private float blendT; private float shotT; private bool shotActive; private Vector3 dotBaseScale; private Vector3[] ringBaseScales; private static readonly int ColorProp = Shader.PropertyToID("_Color"); // ──────────────────────────────────────────────────────────────────────── void Awake() { if (dot == null) { var t = transform.Find("Dot"); if (t != null) dot = t.GetComponent(); } if (rings == null || rings.Length == 0) { var found = new System.Collections.Generic.List(); for (int i = 0; i < transform.childCount; i++) { var ch = transform.GetChild(i); if (ch.name.StartsWith("Ring")) { var r = ch.GetComponent(); if (r != null) found.Add(r); } } rings = found.ToArray(); } dotBaseScale = dot != null ? dot.transform.localScale : Vector3.one; ringBaseScales = new Vector3[rings != null ? rings.Length : 0]; if (rings != null) for (int i = 0; i < rings.Length; i++) ringBaseScales[i] = rings[i] != null ? rings[i].transform.localScale : Vector3.one; blendT = 1f; } void Update() { // прогресс анимации выстрела if (shotActive) { shotT += Time.deltaTime / shotDuration; if (shotT >= 1f) { shotT = 1f; shotActive = false; } } // blendT: 1 = Free, 0 = Blocked float blendTarget = (curState == CursorState.Free) ? 1f : 0f; blendT = Mathf.MoveTowards(blendT, blendTarget, transitionSpeed * Time.deltaTime); // фаза пульсации float basePhase = Time.time * pulseSpeed * Mathf.PI * 2f; float shotArch = Mathf.Sin(shotT * Mathf.PI); Color baseColor = Color.Lerp(blockedColor, freeColor, blendT); Color curColor = Color.Lerp(baseColor, shotColor, shotArch); // dot if (dot != null) { float sinVal = (Mathf.Sin(basePhase) + 1f) * 0.5f; float freeS = Mathf.Lerp(dotMinScale, dotMaxScale, sinVal); float finalS = Mathf.Lerp(blockedDotScale, freeS, blendT); dot.transform.localScale = dotBaseScale * finalS; SetRendererColor(dot, curColor); } // rings if (rings != null) { for (int i = 0; i < rings.Length; i++) { if (rings[i] == null) continue; float phase = syncPulse ? basePhase : basePhase + i * phaseOffset * Mathf.PI * 2f; float sinVal = (Mathf.Sin(phase) + 1f) * 0.5f; float freeS = Mathf.Lerp(ringMinScale, ringMaxScale, sinVal); float baseS = Mathf.Lerp(blockedRingScale, freeS, blendT); float expandFactor = 1f + (shotRingBaseExpand - 1f + i * shotRingExpandStep) * shotArch; rings[i].transform.localScale = ringBaseScales[i] * (baseS * expandFactor); Color rc = curColor; rc.a *= Mathf.Lerp(0.25f, 0.75f, blendT); SetRendererColor(rings[i], rc); } } } // ──────────────────────────────────────────────────────────────────────── /// Мгновенно выставить состояние (без плавного перехода). Вызывать при появлении курсора. public void RefreshState(bool cellBlocked) { curState = cellBlocked ? CursorState.Blocked : CursorState.Free; blendT = cellBlocked ? 0f : 1f; shotT = 0f; shotActive = false; } public void SetStateFree() { curState = CursorState.Free; } public void SetStateBlocked() { curState = CursorState.Blocked; } /// Жёлтая вспышка + расширение колец. willBeBlocked — состояние после анимации. public void PlayShot(bool willBeBlocked) { curState = willBeBlocked ? CursorState.Blocked : CursorState.Free; shotT = 0f; shotActive = true; } public void SetColors(Color free, Color blocked) { freeColor = free; blockedColor = blocked; } // ──────────────────────────────────────────────────────────────────────── private static void SetRendererColor(Renderer r, Color c) { if (r is SpriteRenderer sr) { sr.color = c; } else { var block = new MaterialPropertyBlock(); r.GetPropertyBlock(block); block.SetColor(ColorProp, c); r.SetPropertyBlock(block); } } }