sea-battle/Assets/Scripts/CursorAnimator.cs

188 lines
7.5 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using UnityEngine;
/// <summary>
/// Анимированный курсор: центральный кружок + пульсирующие кольца.
/// Free — зелёный, синхронная пульсация всех объектов.
/// Blocked — красный, сжатие.
/// Shot — жёлтая вспышка, дальнее кольцо расширяется сильнее.
/// </summary>
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<Renderer>();
}
if (rings == null || rings.Length == 0)
{
var found = new System.Collections.Generic.List<Renderer>();
for (int i = 0; i < transform.childCount; i++)
{
var ch = transform.GetChild(i);
if (ch.name.StartsWith("Ring"))
{
var r = ch.GetComponent<Renderer>();
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);
}
}
}
// ────────────────────────────────────────────────────────────────────────
/// <summary>Мгновенно выставить состояние (без плавного перехода). Вызывать при появлении курсора.</summary>
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; }
/// <summary>Жёлтая вспышка + расширение колец. willBeBlocked — состояние после анимации.</summary>
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);
}
}
}