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; // Lớp trên cùng chứa trail và ripples private readonly Dictionary _controllers = new Dictionary(); private readonly Stack _history = new Stack(); [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 _trailSegments = new List(); private void Awake() { if (Instance == null) { Instance = this; DontDestroyOnLoad(gameObject); } else { Destroy(gameObject); return; } _uiDocument = GetComponent(); _rootElement = _uiDocument.rootVisualElement; // Tạo lớp Cursor trên cùng _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(); // Đăng ký ripple toàn cục _rootElement.RegisterCallback(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(); } 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(cursorTrailSprite); segment.style.opacity = 1f - ((float)i / trailLength); segment.style.scale = new StyleScale(new Vector2(1f - ((float)i / trailLength), 1f - ((float)i / trailLength))); segment.pickingMode = PickingMode.Ignore; _cursorLayer.Add(segment); _trailSegments.Add(segment); } } 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; ripple.style.borderTopLeftRadius = cursorSize; ripple.style.borderTopRightRadius = cursorSize; ripple.style.borderBottomLeftRadius = cursorSize; ripple.style.borderBottomRightRadius = cursorSize; 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); // Hiệu ứng Ripple: To ra và nhạt dần (Sửa lỗi dùng Vector3 thay vì float) 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); // Segment đầu tiên đi theo chuột _trailSegments[0].style.left = uiPos.x - (cursorSize / 2); _trailSegments[0].style.top = uiPos.y - (cursorSize / 2); // Các segment sau đuổi theo segment trước for (int i = 1; i < _trailSegments.Count; i++) { float targetX = _trailSegments[i - 1].resolvedStyle.left; float targetY = _trailSegments[i - 1].resolvedStyle.top; float currX = _trailSegments[i].resolvedStyle.left; float currY = _trailSegments[i].resolvedStyle.top; // Nội suy để mượt mà (Lerp) _trailSegments[i].style.left = Mathf.Lerp(currX, targetX, Time.deltaTime * 20f); _trailSegments[i].style.top = Mathf.Lerp(currY, targetY, Time.deltaTime * 20f); } } private void InitializeControllers() { _mainMenuController = RegisterController(mainMenuTemplate); if (_mainMenuController != null && gameIcon != null) { _mainMenuController.SetGameIcon(gameIcon); } RegisterController(lobbyTemplate); RegisterController(profileTemplate); RegisterController(settingsTemplate); RegisterController(hudTemplate); _ = Push(); } private T RegisterController(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); // Luôn đảm bảo CursorLayer nằm trên cùng sau khi add màn hình mới _cursorLayer.BringToFront(); var controller = new T(); controller.Initialize(instance, this); _controllers[typeof(T)] = controller; return controller; } public async Task Push() 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(); // Giữ trail luôn trên cùng } public async Task Pop() { if (_history.Count <= 1) return; await _history.Pop().PlayTransitionOut(); if (_history.Count > 0) await _history.Peek().PlayTransitionIn(); _cursorLayer.BringToFront(); } } }