Files
BABA_YAGA/Assets/Scripts/UI/UIManager.cs
2026-04-28 10:48:04 +07:00

238 lines
8.7 KiB
C#

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.UIElements;
using PrimeTween;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Hallucinate.UI
{
[RequireComponent(typeof(UIDocument))]
public class UIManager : MonoBehaviour
{
public static UIManager Instance { get; private set; }
private UIDocument _uiDocument;
private VisualElement _rootElement;
private VisualElement _cursorLayer;
private readonly Dictionary<Type, BaseUIController> _controllers = new Dictionary<Type, BaseUIController>();
private readonly Stack<BaseUIController> _history = new Stack<BaseUIController>();
[Header("Game Metadata")]
[SerializeField] private Texture2D gameIcon;
[Header("Cursor & Effects Settings")]
[SerializeField] private Sprite cursorTrailSprite;
[SerializeField, Range(10f, 100f)] private float cursorSize = 30f;
[SerializeField, Range(5, 30)] private int trailLength = 15;
[SerializeField] private bool enableRipples = true;
[SerializeField] private Color rippleColor = new Color(1, 1, 1, 0.5f);
[Header("UI Templates")]
[SerializeField] private VisualTreeAsset mainMenuTemplate;
[SerializeField] private VisualTreeAsset lobbyTemplate;
[SerializeField] private VisualTreeAsset profileTemplate;
[SerializeField] private VisualTreeAsset settingsTemplate;
[SerializeField] private VisualTreeAsset hudTemplate;
private MainMenuController _mainMenuController;
private List<VisualElement> _trailSegments = new List<VisualElement>();
private List<Vector2> _trailPositions = new List<Vector2>();
private void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
return;
}
try
{
_uiDocument = GetComponent<UIDocument>();
_rootElement = _uiDocument.rootVisualElement;
// Tạo lớp Cursor
_cursorLayer = new VisualElement();
_cursorLayer.name = "CursorLayer";
_cursorLayer.style.position = Position.Absolute;
_cursorLayer.style.width = Length.Percent(100);
_cursorLayer.style.height = Length.Percent(100);
_cursorLayer.pickingMode = PickingMode.Ignore;
_rootElement.Add(_cursorLayer);
SetupCursorTrail();
// Ripple
_rootElement.RegisterCallback<PointerDownEvent>(OnGlobalClick, TrickleDown.TrickleDown);
#if UNITY_EDITOR
if (gameIcon == null)
{
var icons = PlayerSettings.GetIconsForTargetGroup(BuildTargetGroup.Unknown);
if (icons != null && icons.Length > 0) gameIcon = icons[0];
}
#endif
InitializeControllers();
}
catch (Exception e)
{
Debug.LogError($"[UIManager] Error during Awake: {e.Message}\n{e.StackTrace}");
}
}
private void SetupCursorTrail()
{
if (cursorTrailSprite == null) return;
for (int i = 0; i < trailLength; i++)
{
var segment = new VisualElement();
segment.style.position = Position.Absolute;
segment.style.width = cursorSize;
segment.style.height = cursorSize;
segment.style.backgroundImage = new StyleBackground(Background.FromSprite(cursorTrailSprite));
float ratio = 1f - ((float)i / trailLength);
segment.style.opacity = ratio;
segment.style.scale = new StyleScale(new Scale(new Vector3(ratio, ratio, 1f)));
segment.pickingMode = PickingMode.Ignore;
_cursorLayer.Add(segment);
_trailSegments.Add(segment);
_trailPositions.Add(Vector2.zero);
}
}
private void OnGlobalClick(PointerDownEvent evt)
{
if (!enableRipples) return;
var ripple = new VisualElement();
ripple.style.position = Position.Absolute;
ripple.style.width = cursorSize;
ripple.style.height = cursorSize;
var radius = new StyleLength(new Length(50, LengthUnit.Percent));
ripple.style.borderTopLeftRadius = radius;
ripple.style.borderTopRightRadius = radius;
ripple.style.borderBottomLeftRadius = radius;
ripple.style.borderBottomRightRadius = radius;
ripple.style.borderTopColor = rippleColor;
ripple.style.borderBottomColor = rippleColor;
ripple.style.borderLeftColor = rippleColor;
ripple.style.borderRightColor = rippleColor;
ripple.style.borderTopWidth = 2;
ripple.style.borderBottomWidth = 2;
ripple.style.borderLeftWidth = 2;
ripple.style.borderRightWidth = 2;
ripple.style.left = evt.localPosition.x - (cursorSize / 2);
ripple.style.top = evt.localPosition.y - (cursorSize / 2);
ripple.pickingMode = PickingMode.Ignore;
_cursorLayer.Add(ripple);
Tween.Scale(ripple.transform, Vector3.one * 3f, duration: 0.5f, ease: Ease.OutQuad);
Tween.Custom(1f, 0f, duration: 0.5f, onValueChange: val => ripple.style.opacity = val)
.OnComplete(() => ripple.RemoveFromHierarchy());
}
private void Update()
{
_mainMenuController?.Update();
UpdateTrail();
}
private void UpdateTrail()
{
if (_trailSegments.Count == 0) return;
Vector2 mousePos = Input.mousePosition;
Vector2 uiPos = new Vector2(mousePos.x, Screen.height - mousePos.y);
// Cập nhật vị trí đầu tiên
_trailPositions[0] = uiPos;
// Đuổi theo mượt mà
for (int i = 1; i < _trailPositions.Count; i++)
{
_trailPositions[i] = Vector2.Lerp(_trailPositions[i], _trailPositions[i - 1], Time.deltaTime * 25f);
}
// Áp dụng vào UI
for (int i = 0; i < _trailSegments.Count; i++)
{
_trailSegments[i].style.left = _trailPositions[i].x - (cursorSize / 2);
_trailSegments[i].style.top = _trailPositions[i].y - (cursorSize / 2);
}
}
private void InitializeControllers()
{
_mainMenuController = RegisterController<MainMenuController>(mainMenuTemplate);
if (_mainMenuController != null && gameIcon != null)
{
_mainMenuController.SetGameIcon(gameIcon);
}
RegisterController<LobbyController>(lobbyTemplate);
RegisterController<ProfileController>(profileTemplate);
RegisterController<SettingsController>(settingsTemplate);
RegisterController<HUDController>(hudTemplate);
_ = Push<MainMenuController>();
}
private T RegisterController<T>(VisualTreeAsset template) where T : BaseUIController, new()
{
if (template == null) return null;
var instance = template.Instantiate();
instance.style.flexGrow = 1;
instance.style.position = Position.Absolute;
instance.style.width = Length.Percent(100);
instance.style.height = Length.Percent(100);
instance.style.display = DisplayStyle.None;
_rootElement.Add(instance);
_cursorLayer.BringToFront();
var controller = new T();
controller.Initialize(instance, this);
_controllers[typeof(T)] = controller;
return controller;
}
public async Task Push<T>() where T : BaseUIController
{
if (!_controllers.TryGetValue(typeof(T), out var newScreen)) return;
if (_history.Count > 0 && _history.Peek() == newScreen) return;
if (_history.Count > 0) await _history.Peek().PlayTransitionOut();
_history.Push(newScreen);
await newScreen.PlayTransitionIn();
_cursorLayer.BringToFront();
}
public async Task Pop()
{
if (_history.Count <= 1) return;
await _history.Pop().PlayTransitionOut();
if (_history.Count > 0) await _history.Peek().PlayTransitionIn();
_cursorLayer.BringToFront();
}
}
}