diff --git a/.idea/.idea.HALLUCINATE/.idea/workspace.xml b/.idea/.idea.HALLUCINATE/.idea/workspace.xml index b26ed11f..09c57bba 100644 --- a/.idea/.idea.HALLUCINATE/.idea/workspace.xml +++ b/.idea/.idea.HALLUCINATE/.idea/workspace.xml @@ -5,6 +5,7 @@ + diff --git a/Assets/Scripts/UI/UIManager.cs b/Assets/Scripts/UI/UIManager.cs index 9a012a2f..1d29f870 100644 --- a/Assets/Scripts/UI/UIManager.cs +++ b/Assets/Scripts/UI/UIManager.cs @@ -18,6 +18,7 @@ namespace Hallucinate.UI private UIDocument _uiDocument; private VisualElement _rootElement; private VisualElement _cursorLayer; + private VisualElement _mainCursor; private readonly Dictionary _controllers = new Dictionary(); private readonly Stack _history = new Stack(); @@ -26,11 +27,13 @@ namespace Hallucinate.UI [SerializeField] private Texture2D gameIcon; [Header("Cursor & Effects Settings")] + [SerializeField] private Sprite cursorSprite; [SerializeField] private Sprite cursorTrailSprite; - [SerializeField, Range(10f, 100f)] private float cursorSize = 30f; - [SerializeField, Range(5, 30)] private int trailLength = 15; + [SerializeField, Range(10f, 150f)] private float cursorSize = 40f; + [SerializeField, Range(5, 50)] private int trailLength = 20; + [SerializeField, Range(1, 10)] private int trailSpacing = 2; [SerializeField] private bool enableRipples = true; - [SerializeField] private Color rippleColor = new Color(1, 1, 1, 0.5f); + [SerializeField] private Color rippleColor = new Color(1, 1, 1, 0.4f); [Header("UI Templates")] [SerializeField] private VisualTreeAsset mainMenuTemplate; @@ -41,76 +44,81 @@ namespace Hallucinate.UI private MainMenuController _mainMenuController; private List _trailSegments = new List(); - private List _trailPositions = new List(); + private List _posHistory = 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}"); - } + Instance = this; + _uiDocument = GetComponent(); + // Chỉ định rõ UnityEngine.Cursor để tránh lỗi Ambiguous + UnityEngine.Cursor.visible = false; } - private void SetupCursorTrail() + private void Start() { - if (cursorTrailSprite == null) return; + if (_uiDocument == null) return; + _rootElement = _uiDocument.rootVisualElement; - for (int i = 0; i < trailLength; i++) + _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); + + SetupVirtualCursor(); + + _rootElement.RegisterCallback(OnGlobalClick, TrickleDown.TrickleDown); + +#if UNITY_EDITOR + if (gameIcon == null) { - 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); + var icons = PlayerSettings.GetIconsForTargetGroup(BuildTargetGroup.Unknown); + if (icons != null && icons.Length > 0) gameIcon = icons[0]; } +#endif + InitializeControllers(); + } + + private void SetupVirtualCursor() + { + _cursorLayer.Clear(); + _trailSegments.Clear(); + _posHistory.Clear(); + + if (cursorTrailSprite != null) + { + 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 * 0.5f; + segment.style.scale = new StyleScale(new Scale(new Vector3(ratio, ratio, 1f))); + segment.style.translate = new StyleTranslate(new Translate(Length.Percent(-50), Length.Percent(-50))); + segment.pickingMode = PickingMode.Ignore; + + _cursorLayer.Add(segment); + _trailSegments.Add(segment); + } + } + + _mainCursor = new VisualElement(); + _mainCursor.style.position = Position.Absolute; + _mainCursor.style.width = cursorSize; + _mainCursor.style.height = cursorSize; + _mainCursor.style.backgroundImage = new StyleBackground(Background.FromSprite(cursorSprite != null ? cursorSprite : cursorTrailSprite)); + _mainCursor.style.translate = new StyleTranslate(new Translate(Length.Percent(-50), Length.Percent(-50))); + _mainCursor.pickingMode = PickingMode.Ignore; + _cursorLayer.Add(_mainCursor); + + Vector2 startPos = new Vector2(Input.mousePosition.x, Screen.height - Input.mousePosition.y); + for (int i = 0; i < trailLength * trailSpacing + 1; i++) _posHistory.Add(startPos); } private void OnGlobalClick(PointerDownEvent evt) @@ -121,6 +129,7 @@ namespace Hallucinate.UI ripple.style.position = Position.Absolute; ripple.style.width = cursorSize; ripple.style.height = cursorSize; + ripple.style.translate = new StyleTranslate(new Translate(Length.Percent(-50), Length.Percent(-50))); var radius = new StyleLength(new Length(50, LengthUnit.Percent)); ripple.style.borderTopLeftRadius = radius; @@ -137,54 +146,68 @@ namespace Hallucinate.UI 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.style.left = evt.localPosition.x; + ripple.style.top = evt.localPosition.y; 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) + Tween.Scale(ripple.transform, Vector3.one * 2.5f, duration: 0.4f, ease: Ease.OutQuad); + Tween.Custom(1f, 0f, duration: 0.4f, onValueChange: val => ripple.style.opacity = val) .OnComplete(() => ripple.RemoveFromHierarchy()); } private void Update() { - _mainMenuController?.Update(); - UpdateTrail(); + if (_mainMenuController != null) _mainMenuController.Update(); + UpdateCursorAndTrail(); } - private void UpdateTrail() + private void UpdateCursorAndTrail() { - 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++) + if (!Application.isFocused) { - _trailPositions[i] = Vector2.Lerp(_trailPositions[i], _trailPositions[i - 1], Time.deltaTime * 25f); + if (_cursorLayer != null) _cursorLayer.style.display = DisplayStyle.None; + return; + } + + Vector3 mousePos = Input.mousePosition; + bool isMouseInWindow = mousePos.x >= 0 && mousePos.x <= Screen.width && mousePos.y >= 0 && mousePos.y <= Screen.height; + + if (!isMouseInWindow) + { + if (_cursorLayer != null) _cursorLayer.style.display = DisplayStyle.None; + return; + } + + if (_cursorLayer != null) _cursorLayer.style.display = DisplayStyle.Flex; + Vector2 uiPos = new Vector2(mousePos.x, Screen.height - mousePos.y); + + _posHistory.Insert(0, uiPos); + if (_posHistory.Count > trailLength * trailSpacing + 1) + _posHistory.RemoveAt(_posHistory.Count - 1); + + if (_mainCursor != null) + { + _mainCursor.style.left = uiPos.x; + _mainCursor.style.top = uiPos.y; } - // Á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); + int historyIndex = (i + 1) * trailSpacing; + if (historyIndex < _posHistory.Count) + { + _trailSegments[i].style.left = _posHistory[historyIndex].x; + _trailSegments[i].style.top = _posHistory[historyIndex].y; + } } } private void InitializeControllers() { _mainMenuController = RegisterController(mainMenuTemplate); - if (_mainMenuController != null && gameIcon != null) - { - _mainMenuController.SetGameIcon(gameIcon); - } + if (_mainMenuController != null && gameIcon != null) _mainMenuController.SetGameIcon(gameIcon); RegisterController(lobbyTemplate); RegisterController(profileTemplate); @@ -197,7 +220,6 @@ namespace Hallucinate.UI 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; @@ -205,13 +227,11 @@ namespace Hallucinate.UI instance.style.height = Length.Percent(100); instance.style.display = DisplayStyle.None; _rootElement.Add(instance); - - _cursorLayer.BringToFront(); + if (_cursorLayer != null) _cursorLayer.BringToFront(); var controller = new T(); controller.Initialize(instance, this); _controllers[typeof(T)] = controller; - return controller; } @@ -223,7 +243,7 @@ namespace Hallucinate.UI _history.Push(newScreen); await newScreen.PlayTransitionIn(); - _cursorLayer.BringToFront(); + if (_cursorLayer != null) _cursorLayer.BringToFront(); } public async Task Pop() @@ -231,7 +251,7 @@ namespace Hallucinate.UI if (_history.Count <= 1) return; await _history.Pop().PlayTransitionOut(); if (_history.Count > 0) await _history.Peek().PlayTransitionIn(); - _cursorLayer.BringToFront(); + if (_cursorLayer != null) _cursorLayer.BringToFront(); } } } diff --git a/Assets/Textures/Cursor/cursor.png.meta b/Assets/Textures/Cursor/cursor.png.meta index 9c416bb5..b80dde01 100644 --- a/Assets/Textures/Cursor/cursor.png.meta +++ b/Assets/Textures/Cursor/cursor.png.meta @@ -54,7 +54,7 @@ TextureImporter: alphaUsage: 1 alphaIsTransparency: 1 spriteTessellationDetail: -1 - textureType: 7 + textureType: 8 textureShape: 1 singleChannelComponent: 0 flipbookRows: 1