Danro/Assets/Scripts/UI/UIPointer.cs
Danil Iarosh 4d05d1adbb
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
FEAT: Added new settings system
- Добавил новую систему настроек, возможности:
    - Динамически создает список настроек
    - Сохраняет настройки в JSON-файл
    - Возможность группировать настройки
    - Широкие возможности по кастомизации
    - Применение настроек в реальном времени (частично)
- Добавил префабы для новых элементов настроек
- Изменил поведение игры исходя из нового формата настроек
2026-02-04 14:32:15 +03:00

200 lines
8.0 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)
{
// Получаем позицию целевого элемента относительно content
Vector2 targetAnchoredPos = target.anchoredPosition;
// Если элементы не прямые дети content, нужно пересчитать позицию
if (target.parent == content)
return new Vector2(
pointerRect.anchoredPosition.x + offset.x, // X оставляем как есть (или применяем offset)
targetAnchoredPos.y + offset.y // Y берем от целевого элемента
);
// Конвертируем мировую позицию в локальную позицию content
Vector3[] corners = new Vector3[4];
target.GetWorldCorners(corners);
Vector3 center = (corners[0] + corners[2]) * 0.5f;
RectTransformUtility.ScreenPointToLocalPointInRectangle(
content,
RectTransformUtility.WorldToScreenPoint(null, center),
null,
out Vector2 localPos
);
targetAnchoredPos = localPos;
// Применяем смещение
return new Vector2(
pointerRect.anchoredPosition.x + offset.x, // X оставляем как есть (или применяем offset)
targetAnchoredPos.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);
}
}
}