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 _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 List _trailPositions = new List(); private void Awake() { if (Instance == null) { Instance = this; DontDestroyOnLoad(gameObject); } else { Destroy(gameObject); return; } try { _uiDocument = GetComponent(); _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(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(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); _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(); } public async Task Pop() { if (_history.Count <= 1) return; await _history.Pop().PlayTransitionOut(); if (_history.Count > 0) await _history.Peek().PlayTransitionIn(); _cursorLayer.BringToFront(); } } }