UPdate
This commit is contained in:
@@ -18,6 +18,7 @@ namespace Hallucinate.UI
|
||||
private UIDocument _uiDocument;
|
||||
private VisualElement _rootElement;
|
||||
private VisualElement _cursorLayer;
|
||||
private VisualElement _mainCursor;
|
||||
|
||||
private readonly Dictionary<Type, BaseUIController> _controllers = new Dictionary<Type, BaseUIController>();
|
||||
private readonly Stack<BaseUIController> _history = new Stack<BaseUIController>();
|
||||
@@ -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<VisualElement> _trailSegments = new List<VisualElement>();
|
||||
private List<Vector2> _trailPositions = new List<Vector2>();
|
||||
private List<Vector2> _posHistory = 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}");
|
||||
}
|
||||
Instance = this;
|
||||
_uiDocument = GetComponent<UIDocument>();
|
||||
// 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<PointerDownEvent>(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<MainMenuController>(mainMenuTemplate);
|
||||
if (_mainMenuController != null && gameIcon != null)
|
||||
{
|
||||
_mainMenuController.SetGameIcon(gameIcon);
|
||||
}
|
||||
if (_mainMenuController != null && gameIcon != null) _mainMenuController.SetGameIcon(gameIcon);
|
||||
|
||||
RegisterController<LobbyController>(lobbyTemplate);
|
||||
RegisterController<ProfileController>(profileTemplate);
|
||||
@@ -197,7 +220,6 @@ namespace Hallucinate.UI
|
||||
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;
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user