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 @@
+
@@ -142,7 +143,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