Danro/Assets/Scripts/UI/UIPointer.cs
Danil Iarosh 24aaf18d4b
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
Added new settings in settings system
- Добавил больше настроек в систему настроек
- Пока у большинства настроек нет функционала
- Немного изменил внешний вид настроек
2026-02-06 08:43:51 +03:00

198 lines
7.8 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 System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
namespace RR.Common.UI
{
public class UIPointer : MonoBehaviour
{
[Header("Настройки")]
[SerializeField] private GameObject pointer;
[SerializeField] private RectTransform content; // Content ScrollView, где находятся элементы
[SerializeField] private float smoothSpeed = 10f; // Скорость перемещения указателя
[SerializeField] private Vector2 offset = Vector2.zero; // Смещение указателя относительно элемента
[Header("Поведение")]
[Tooltip("Не скрывать указатель при отсутствии выбора (оставаться на последней позиции)")]
[SerializeField] private bool keepVisibleWhenNoSelection = true;
[Tooltip("Задержка перед скрытием указателя (в секундах)")]
[SerializeField] private float hideDelay = 0.5f;
[Header("Исключения")]
[Tooltip("Элементы, при выборе которых курсор скрывается")]
[SerializeField] private List<GameObject> hideOnSelectElements = new();
private RectTransform pointerRect;
private float hideTimer;
private bool hasEverSelected; // Был ли когда-либо выбран элемент
private void Start()
{
if (pointer != null)
{
pointerRect = pointer.GetComponent<RectTransform>();
if (pointerRect == null)
{
Debug.LogError("UIPointer: pointer не имеет RectTransform!");
}
}
else
{
Debug.LogError("UIPointer: pointer не назначен!");
}
// Если content не задан, пытаемся найти его автоматически
if (content == null)
{
content = GetComponent<RectTransform>();
}
hideTimer = hideDelay;
}
private void LateUpdate()
{
if (!pointerRect || !content) return;
// Получаем текущий выбранный элемент из EventSystem
GameObject selected = EventSystem.current?.currentSelectedGameObject;
// Если ничего не выбрано
if (selected == null)
{
HandleNoSelection();
return;
}
// Проверяем, находится ли выбранный элемент в списке исключений
if (IsInHideList(selected))
{
HidePointer();
return;
}
// Проверяем, что выбранный элемент находится внутри нашего content
RectTransform selectedRect = selected.GetComponent<RectTransform>();
if (!selectedRect || !selectedRect.IsChildOf(content))
{
HandleNoSelection();
return;
}
// Сбрасываем таймер и отмечаем что был выбор
hideTimer = hideDelay;
hasEverSelected = true;
// Показываем указатель, если он был скрыт
if (!pointer.activeSelf)
pointer.SetActive(true);
// Вычисляем целевую позицию
Vector2 targetPos = GetTargetPosition(selectedRect);
// Плавно перемещаем указатель
if (smoothSpeed > 0)
{
pointerRect.anchoredPosition = Vector2.Lerp(
pointerRect.anchoredPosition,
targetPos,
Time.unscaledDeltaTime * smoothSpeed
);
}
else
{
pointerRect.anchoredPosition = targetPos;
}
}
private void HandleNoSelection()
{
// Если включено сохранение видимости или ещё не было выбора - не скрываем
if (keepVisibleWhenNoSelection || !hasEverSelected)
return;
// Уменьшаем таймер
hideTimer -= Time.unscaledDeltaTime;
if (hideTimer <= 0 && pointer.activeSelf)
{
pointer.SetActive(false);
}
}
/// <summary>
/// Проверяет, находится ли элемент в списке исключений или является его дочерним элементом
/// </summary>
private bool IsInHideList(GameObject obj)
{
if (hideOnSelectElements == null || hideOnSelectElements.Count == 0)
return false;
foreach (var hideElement in hideOnSelectElements)
{
if (hideElement == null) continue;
// Проверяем сам элемент или его родителей
if (obj == hideElement || obj.transform.IsChildOf(hideElement.transform))
return true;
}
return false;
}
/// <summary>
/// Немедленно скрывает указатель
/// </summary>
private void HidePointer()
{
if (pointer != null && pointer.activeSelf)
{
pointer.SetActive(false);
}
}
private Vector2 GetTargetPosition(RectTransform target)
{
// Получаем мировые координаты центра целевого элемента
Vector3[] corners = new Vector3[4];
target.GetWorldCorners(corners);
Vector3 worldCenter = (corners[0] + corners[2]) * 0.5f;
// Конвертируем мировую позицию в локальную позицию родителя указателя
RectTransform pointerParent = pointerRect.parent as RectTransform;
if (pointerParent == null)
{
Debug.LogWarning("UIPointer: pointer не имеет RectTransform родителя!");
return pointerRect.anchoredPosition;
}
RectTransformUtility.ScreenPointToLocalPointInRectangle(
pointerParent,
RectTransformUtility.WorldToScreenPoint(null, worldCenter),
null,
out Vector2 localPos
);
// X фиксирована, меняется только Y
return new Vector2(
pointerRect.anchoredPosition.x, // X остается как есть
localPos.y + offset.y // Y от целевого элемента + смещение
);
}
// Метод для ручного обновления позиции указателя (если нужно)
public void UpdatePointerPosition()
{
if (pointerRect == null || content == null) return;
GameObject selected = EventSystem.current?.currentSelectedGameObject;
if (selected == null) return;
RectTransform selectedRect = selected.GetComponent<RectTransform>();
if (selectedRect == null || !selectedRect.IsChildOf(content)) return;
pointerRect.anchoredPosition = GetTargetPosition(selectedRect);
}
}
}