using UnityEngine;
namespace RR.Common.UI
{
///
/// Указатель-стрелка, который плавно перемещается к произвольному UI-элементу.
/// Управляется извне через или .
///
public class UIRowPointer : MonoBehaviour
{
[Header("Указатель")]
[Tooltip("RectTransform указателя (стрелка, иконка и т.д.).")]
[SerializeField] private RectTransform pointer;
[Header("Цели (опционально)")]
[Tooltip("Фиксированные цели для SetTarget(int). Можно не заполнять, если используется SetTarget(RectTransform).")]
[SerializeField] private RectTransform[] targets;
[Header("Движение")]
[Tooltip("Скорость плавного перемещения. 0 — мгновенно.")]
[SerializeField] private float smoothSpeed = 12f;
[Tooltip("Смещение указателя относительно центра цели (в локальных координатах родителя указателя).")]
[SerializeField] private Vector2 offset;
[Header("Покачивание")]
[Tooltip("Включить покачивание указателя по горизонтали.")]
[SerializeField] private bool wobble = true;
[Tooltip("Амплитуда покачивания (пикселей).")]
[SerializeField] private float wobbleAmplitude = 4f;
[Tooltip("Скорость покачивания (циклов в секунду).")]
[SerializeField] private float wobbleSpeed = 2f;
// ── Состояние ──
private RectTransform _currentTarget;
private Vector2 _targetPos;
private Vector2 _baseX;
private bool _ready;
// ────────────── Публичный API ──────────────
///
/// Перемещает указатель к произвольному .
///
public void SetTarget(RectTransform target)
{
if (target == null) return;
_currentTarget = target;
_targetPos = CalculatePosition(target);
if (!_ready)
{
if (pointer) pointer.anchoredPosition = _targetPos;
_ready = true;
}
ShowPointer();
}
///
/// Перемещает указатель к цели по индексу из массива .
///
public void SetTarget(int index)
{
if (targets == null || index < 0 || index >= targets.Length) return;
SetTarget(targets[index]);
}
///
/// Скрывает указатель.
///
public void Hide()
{
if (pointer) pointer.gameObject.SetActive(false);
_currentTarget = null;
}
///
/// Задаёт массив целей из кода (если не задан через инспектор).
///
public void SetTargets(RectTransform[] newTargets)
{
targets = newTargets;
}
// ────────────── Lifecycle ──────────────
private void Awake()
{
if (pointer) _baseX = new Vector2(pointer.anchoredPosition.x, 0f);
}
private void LateUpdate()
{
if (!pointer || _currentTarget == null) return;
// Пересчитываем позицию (цель может двигаться из-за layout)
_targetPos = CalculatePosition(_currentTarget);
// Плавное перемещение
Vector2 pos = smoothSpeed > 0
? Vector2.Lerp(pointer.anchoredPosition, _targetPos, Time.unscaledDeltaTime * smoothSpeed)
: _targetPos;
// Покачивание
if (wobble)
pos.x += Mathf.Sin(Time.unscaledTime * wobbleSpeed * Mathf.PI * 2f) * wobbleAmplitude;
pointer.anchoredPosition = pos;
}
// ────────────── Внутреннее ──────────────
private Vector2 CalculatePosition(RectTransform target)
{
RectTransform parent = pointer.parent as RectTransform;
if (parent == null) return pointer.anchoredPosition;
Vector3[] corners = new Vector3[4];
target.GetWorldCorners(corners);
Vector3 worldCenter = (corners[0] + corners[2]) * 0.5f;
RectTransformUtility.ScreenPointToLocalPointInRectangle(
parent,
RectTransformUtility.WorldToScreenPoint(null, worldCenter),
null,
out Vector2 localPos
);
return new Vector2(
_baseX.x + offset.x,
localPos.y + offset.y
);
}
private void ShowPointer()
{
if (pointer && !pointer.gameObject.activeSelf)
pointer.gameObject.SetActive(true);
}
}
}