2026-04-28 00:07:42 +07:00
|
|
|
using System;
|
2026-04-23 23:09:54 +07:00
|
|
|
using System.Collections.Generic;
|
2026-04-28 00:07:42 +07:00
|
|
|
using System.Threading.Tasks;
|
2026-04-23 23:09:54 +07:00
|
|
|
using UnityEngine;
|
|
|
|
|
using UnityEngine.UIElements;
|
2026-04-28 10:44:22 +07:00
|
|
|
using PrimeTween;
|
2026-04-28 18:49:05 +07:00
|
|
|
using OnlyScove.Scripts;
|
2026-04-28 10:11:28 +07:00
|
|
|
#if UNITY_EDITOR
|
|
|
|
|
using UnityEditor;
|
|
|
|
|
#endif
|
2026-04-23 23:09:54 +07:00
|
|
|
|
2026-04-28 00:07:42 +07:00
|
|
|
namespace Hallucinate.UI
|
2026-04-23 23:09:54 +07:00
|
|
|
{
|
2026-04-28 00:07:42 +07:00
|
|
|
[RequireComponent(typeof(UIDocument))]
|
2026-04-23 23:09:54 +07:00
|
|
|
public class UIManager : MonoBehaviour
|
|
|
|
|
{
|
2026-04-25 18:20:16 +07:00
|
|
|
public static UIManager Instance { get; private set; }
|
|
|
|
|
|
2026-04-28 00:07:42 +07:00
|
|
|
private UIDocument _uiDocument;
|
|
|
|
|
private VisualElement _rootElement;
|
2026-05-01 01:25:02 +07:00
|
|
|
public VisualElement Root => _rootElement;
|
2026-05-01 15:08:59 +07:00
|
|
|
|
2026-04-28 10:48:04 +07:00
|
|
|
private VisualElement _cursorLayer;
|
2026-04-28 11:03:57 +07:00
|
|
|
private VisualElement _mainCursor;
|
2026-04-23 23:09:54 +07:00
|
|
|
|
2026-04-28 00:07:42 +07:00
|
|
|
private readonly Dictionary<Type, BaseUIController> _controllers = new Dictionary<Type, BaseUIController>();
|
|
|
|
|
private readonly Stack<BaseUIController> _history = new Stack<BaseUIController>();
|
2026-04-25 18:20:16 +07:00
|
|
|
|
2026-04-28 11:35:49 +07:00
|
|
|
[Header("References")]
|
|
|
|
|
[SerializeField] private InputReader inputReader;
|
2026-04-29 01:04:28 +07:00
|
|
|
public InputReader InputReader => inputReader;
|
2026-04-28 11:35:49 +07:00
|
|
|
|
2026-04-28 10:11:28 +07:00
|
|
|
[Header("Game Metadata")]
|
|
|
|
|
[SerializeField] private Texture2D gameIcon;
|
|
|
|
|
|
2026-04-28 10:44:22 +07:00
|
|
|
[Header("Cursor & Effects Settings")]
|
2026-04-28 11:03:57 +07:00
|
|
|
[SerializeField] private Sprite cursorSprite;
|
2026-04-28 10:44:22 +07:00
|
|
|
[SerializeField] private Sprite cursorTrailSprite;
|
2026-04-28 11:03:57 +07:00
|
|
|
[SerializeField, Range(10f, 150f)] private float cursorSize = 40f;
|
2026-05-01 16:17:12 +07:00
|
|
|
[SerializeField, Range(2, 50)] private float trailDistanceThreshold = 10f;
|
2026-04-28 10:44:22 +07:00
|
|
|
[SerializeField] private bool enableRipples = true;
|
2026-05-01 16:17:12 +07:00
|
|
|
[SerializeField] private Color rippleColor = new Color(0, 1, 0.8f, 0.4f);
|
2026-04-28 10:44:22 +07:00
|
|
|
|
2026-05-01 16:17:12 +07:00
|
|
|
[Header("UI Templates & Global Styles")]
|
2026-04-28 19:04:09 +07:00
|
|
|
[SerializeField] private VisualTreeAsset loginTemplate;
|
2026-04-28 00:07:42 +07:00
|
|
|
[SerializeField] private VisualTreeAsset mainMenuTemplate;
|
|
|
|
|
[SerializeField] private VisualTreeAsset lobbyTemplate;
|
2026-04-29 01:04:28 +07:00
|
|
|
[SerializeField] private VisualTreeAsset roomItemTemplate;
|
2026-04-28 00:07:42 +07:00
|
|
|
[SerializeField] private VisualTreeAsset profileTemplate;
|
|
|
|
|
[SerializeField] private VisualTreeAsset settingsTemplate;
|
|
|
|
|
[SerializeField] private VisualTreeAsset hudTemplate;
|
2026-05-01 21:58:20 +07:00
|
|
|
[SerializeField] private VisualTreeAsset pauseMenuTemplate;
|
2026-05-01 16:17:12 +07:00
|
|
|
[SerializeField] private StyleSheet globalStyleSheet;
|
2026-04-28 11:35:49 +07:00
|
|
|
|
2026-04-28 18:49:05 +07:00
|
|
|
private LoginController _loginController;
|
2026-04-28 00:07:42 +07:00
|
|
|
private MainMenuController _mainMenuController;
|
2026-04-28 19:04:09 +07:00
|
|
|
private LobbyController _lobbyController;
|
2026-04-28 11:35:49 +07:00
|
|
|
private SettingsController _settingsController;
|
2026-05-01 21:58:20 +07:00
|
|
|
private PauseMenuController _pauseMenuController;
|
2026-04-25 18:20:16 +07:00
|
|
|
|
2026-05-01 16:17:12 +07:00
|
|
|
// Osu Trail Pooling
|
|
|
|
|
private const int MAX_TRAIL_PARTICLES = 60;
|
|
|
|
|
private readonly List<VisualElement> _trailPool = new List<VisualElement>();
|
|
|
|
|
private int _currentTrailIndex = 0;
|
|
|
|
|
|
|
|
|
|
private Vector2 _lastTrailSpawnPos;
|
2026-04-28 11:35:49 +07:00
|
|
|
private bool _isSettingsOpen = false;
|
2026-05-01 01:25:02 +07:00
|
|
|
public bool IsSettingsOpen => _isSettingsOpen;
|
2026-04-28 11:35:49 +07:00
|
|
|
|
2026-05-01 21:58:20 +07:00
|
|
|
private bool _isPauseMenuOpen = false;
|
|
|
|
|
public bool IsPauseMenuOpen => _isPauseMenuOpen;
|
|
|
|
|
|
2026-04-29 02:31:15 +07:00
|
|
|
private const string UI_SCALE_KEY = "UIScale";
|
|
|
|
|
|
2026-05-01 15:08:59 +07:00
|
|
|
#if UNITY_EDITOR
|
|
|
|
|
private void OnValidate()
|
|
|
|
|
{
|
|
|
|
|
if (gameIcon == null)
|
|
|
|
|
{
|
|
|
|
|
var icons = PlayerSettings.GetIcons(UnityEditor.Build.NamedBuildTarget.Unknown, IconKind.Any);
|
|
|
|
|
if (icons != null && icons.Length > 0)
|
|
|
|
|
{
|
|
|
|
|
gameIcon = icons[0];
|
|
|
|
|
UnityEditor.EditorUtility.SetDirty(this);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
2026-04-28 00:07:42 +07:00
|
|
|
private void Awake()
|
2026-04-23 23:09:54 +07:00
|
|
|
{
|
2026-05-01 16:17:12 +07:00
|
|
|
if (Instance != null && Instance != this) { Destroy(gameObject); return; }
|
2026-04-28 11:03:57 +07:00
|
|
|
Instance = this;
|
2026-04-29 13:10:00 +07:00
|
|
|
DontDestroyOnLoad(gameObject);
|
|
|
|
|
|
2026-04-28 11:03:57 +07:00
|
|
|
_uiDocument = GetComponent<UIDocument>();
|
|
|
|
|
UnityEngine.Cursor.visible = false;
|
2026-04-29 02:31:15 +07:00
|
|
|
|
2026-05-01 15:08:59 +07:00
|
|
|
LoadGeneralSettings();
|
2026-04-29 02:31:15 +07:00
|
|
|
ApplySavedUIScale();
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-01 15:08:59 +07:00
|
|
|
private void LoadGeneralSettings()
|
|
|
|
|
{
|
|
|
|
|
cursorSize = PlayerPrefs.GetFloat("CursorSize", 40f);
|
|
|
|
|
enableRipples = PlayerPrefs.GetInt("CursorRipples", 1) == 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void SetCursorSize(float size)
|
|
|
|
|
{
|
|
|
|
|
cursorSize = size;
|
|
|
|
|
PlayerPrefs.SetFloat("CursorSize", size);
|
2026-05-01 16:17:12 +07:00
|
|
|
if (_mainCursor != null)
|
|
|
|
|
{
|
|
|
|
|
_mainCursor.style.width = cursorSize;
|
|
|
|
|
_mainCursor.style.height = cursorSize;
|
|
|
|
|
}
|
|
|
|
|
foreach(var p in _trailPool) { p.style.width = cursorSize; p.style.height = cursorSize; }
|
2026-05-01 15:08:59 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void SetCursorTrail(bool enabled)
|
|
|
|
|
{
|
|
|
|
|
PlayerPrefs.SetInt("CursorTrail", enabled ? 1 : 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void SetCursorRipples(bool enabled)
|
|
|
|
|
{
|
|
|
|
|
enableRipples = enabled;
|
|
|
|
|
PlayerPrefs.SetInt("CursorRipples", enabled ? 1 : 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void SetMouseSensitivity(float sensitivity)
|
|
|
|
|
{
|
|
|
|
|
PlayerPrefs.SetFloat("MouseSensitivity", sensitivity);
|
2026-05-01 17:57:07 +07:00
|
|
|
if (OnlyScove.Scripts.SettingsManager.Instance != null)
|
|
|
|
|
{
|
|
|
|
|
OnlyScove.Scripts.SettingsManager.Instance.SetSensitivity(sensitivity);
|
|
|
|
|
}
|
2026-05-01 15:08:59 +07:00
|
|
|
}
|
|
|
|
|
|
2026-04-29 13:10:00 +07:00
|
|
|
public void OnGameStarted()
|
|
|
|
|
{
|
|
|
|
|
_ = Push<HUDController>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void OnBackToMenu()
|
|
|
|
|
{
|
|
|
|
|
_ = Push<MainMenuController>();
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-29 02:31:15 +07:00
|
|
|
public void SetUIScale(float scale)
|
|
|
|
|
{
|
|
|
|
|
if (_uiDocument == null || _uiDocument.panelSettings == null) return;
|
2026-04-30 16:52:19 +07:00
|
|
|
_uiDocument.panelSettings.scale = scale * 1.3f;
|
2026-04-29 02:31:15 +07:00
|
|
|
PlayerPrefs.SetFloat(UI_SCALE_KEY, scale);
|
|
|
|
|
PlayerPrefs.Save();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void ApplySavedUIScale()
|
|
|
|
|
{
|
|
|
|
|
float savedScale = PlayerPrefs.GetFloat(UI_SCALE_KEY, 1.0f);
|
|
|
|
|
SetUIScale(savedScale);
|
2026-04-28 11:03:57 +07:00
|
|
|
}
|
2026-05-01 16:17:12 +07:00
|
|
|
|
2026-04-28 11:03:57 +07:00
|
|
|
private void Start()
|
|
|
|
|
{
|
2026-04-29 01:04:28 +07:00
|
|
|
if (_uiDocument == null) _uiDocument = GetComponent<UIDocument>();
|
2026-04-28 11:03:57 +07:00
|
|
|
_rootElement = _uiDocument.rootVisualElement;
|
2026-04-28 10:11:28 +07:00
|
|
|
|
2026-05-01 16:17:12 +07:00
|
|
|
if (globalStyleSheet != null)
|
|
|
|
|
_rootElement.panel.visualTree.styleSheets.Add(globalStyleSheet);
|
|
|
|
|
|
2026-05-01 17:57:07 +07:00
|
|
|
var dimOverlay = new VisualElement { name = "BackgroundDimOverlay" };
|
|
|
|
|
dimOverlay.style.position = Position.Absolute;
|
|
|
|
|
dimOverlay.style.width = Length.Percent(100);
|
|
|
|
|
dimOverlay.style.height = Length.Percent(100);
|
|
|
|
|
dimOverlay.pickingMode = PickingMode.Ignore;
|
|
|
|
|
float savedDim = PlayerPrefs.GetFloat("BackgroundDim", 50f);
|
|
|
|
|
dimOverlay.style.backgroundColor = new Color(0, 0, 0, savedDim / 100f);
|
|
|
|
|
_rootElement.Add(dimOverlay);
|
|
|
|
|
|
2026-05-01 16:17:12 +07:00
|
|
|
_cursorLayer = new VisualElement { name = "CursorLayer" };
|
2026-04-28 11:03:57 +07:00
|
|
|
_cursorLayer.style.position = Position.Absolute;
|
|
|
|
|
_cursorLayer.style.width = Length.Percent(100);
|
|
|
|
|
_cursorLayer.style.height = Length.Percent(100);
|
|
|
|
|
_cursorLayer.pickingMode = PickingMode.Ignore;
|
|
|
|
|
_rootElement.Add(_cursorLayer);
|
2026-04-28 10:44:22 +07:00
|
|
|
|
2026-05-01 16:17:12 +07:00
|
|
|
InitializeTrailPool();
|
|
|
|
|
|
|
|
|
|
_mainCursor = new VisualElement { name = "MainCursor" };
|
|
|
|
|
_mainCursor.style.position = Position.Absolute;
|
|
|
|
|
_mainCursor.style.width = cursorSize;
|
|
|
|
|
_mainCursor.style.height = cursorSize;
|
|
|
|
|
_mainCursor.style.backgroundImage = new StyleBackground(Background.FromSprite(cursorSprite));
|
|
|
|
|
|
|
|
|
|
// Căn giữa sprite hình tròn bằng translate
|
|
|
|
|
_mainCursor.style.translate = new StyleTranslate(new Translate(Length.Percent(-50), Length.Percent(-50)));
|
|
|
|
|
_mainCursor.pickingMode = PickingMode.Ignore;
|
|
|
|
|
_cursorLayer.Add(_mainCursor);
|
2026-04-28 10:44:22 +07:00
|
|
|
|
2026-04-28 11:03:57 +07:00
|
|
|
_rootElement.RegisterCallback<PointerDownEvent>(OnGlobalClick, TrickleDown.TrickleDown);
|
2026-04-28 10:44:22 +07:00
|
|
|
|
2026-04-28 11:35:49 +07:00
|
|
|
if (inputReader != null)
|
|
|
|
|
{
|
|
|
|
|
inputReader.OnToggleSettingsEvent += ToggleSettings;
|
|
|
|
|
inputReader.OnCancelEvent += HandleCancel;
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-28 11:03:57 +07:00
|
|
|
InitializeControllers();
|
2026-04-28 18:49:05 +07:00
|
|
|
CheckLoginStatus();
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-01 16:17:12 +07:00
|
|
|
private void InitializeTrailPool()
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < MAX_TRAIL_PARTICLES; i++)
|
|
|
|
|
{
|
|
|
|
|
var particle = new VisualElement();
|
|
|
|
|
particle.style.position = Position.Absolute;
|
|
|
|
|
particle.style.width = cursorSize;
|
|
|
|
|
particle.style.height = cursorSize;
|
|
|
|
|
particle.style.backgroundImage = new StyleBackground(Background.FromSprite(cursorTrailSprite));
|
|
|
|
|
particle.style.translate = new StyleTranslate(new Translate(Length.Percent(-50), Length.Percent(-50)));
|
|
|
|
|
particle.style.opacity = 0;
|
|
|
|
|
particle.style.display = DisplayStyle.None;
|
|
|
|
|
particle.pickingMode = PickingMode.Ignore;
|
|
|
|
|
_cursorLayer.Add(particle);
|
|
|
|
|
_trailPool.Add(particle);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-28 18:49:05 +07:00
|
|
|
private void CheckLoginStatus()
|
|
|
|
|
{
|
|
|
|
|
string savedName = PlayerPrefs.GetString("Username", "");
|
2026-05-01 15:08:59 +07:00
|
|
|
if (string.IsNullOrEmpty(savedName)) _ = Push<LoginController>();
|
|
|
|
|
else _ = Push<MainMenuController>();
|
2026-04-28 18:49:05 +07:00
|
|
|
}
|
|
|
|
|
|
2026-05-01 15:08:59 +07:00
|
|
|
public void OnLoginSuccess() => _ = Push<MainMenuController>();
|
2026-04-26 04:39:59 +07:00
|
|
|
|
2026-04-28 11:35:49 +07:00
|
|
|
private void OnDestroy()
|
|
|
|
|
{
|
|
|
|
|
if (inputReader != null)
|
|
|
|
|
{
|
|
|
|
|
inputReader.OnToggleSettingsEvent -= ToggleSettings;
|
|
|
|
|
inputReader.OnCancelEvent -= HandleCancel;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-01 21:58:20 +07:00
|
|
|
private void HandleCancel()
|
|
|
|
|
{
|
|
|
|
|
if (_isSettingsOpen) ToggleSettings();
|
|
|
|
|
else if (UnityEngine.SceneManagement.SceneManager.GetActiveScene().name == "Main Scene")
|
|
|
|
|
{
|
|
|
|
|
TogglePauseMenu();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async void TogglePauseMenu()
|
|
|
|
|
{
|
|
|
|
|
if (_pauseMenuController == null) return;
|
|
|
|
|
if (!_isPauseMenuOpen)
|
|
|
|
|
{
|
|
|
|
|
_isPauseMenuOpen = true;
|
|
|
|
|
_pauseMenuController.Root.BringToFront();
|
|
|
|
|
if (_cursorLayer != null) _cursorLayer.BringToFront();
|
|
|
|
|
|
|
|
|
|
// Unlock cursor when menu is open
|
|
|
|
|
UnityEngine.Cursor.lockState = CursorLockMode.None;
|
|
|
|
|
UnityEngine.Cursor.visible = false;
|
|
|
|
|
|
|
|
|
|
await _pauseMenuController.PlayTransitionIn();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
_isPauseMenuOpen = false;
|
|
|
|
|
|
|
|
|
|
// Re-lock cursor when menu is closed
|
|
|
|
|
if (!_isSettingsOpen)
|
|
|
|
|
{
|
|
|
|
|
UnityEngine.Cursor.lockState = CursorLockMode.Locked;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await _pauseMenuController.PlayTransitionOut();
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-28 11:35:49 +07:00
|
|
|
|
|
|
|
|
public async void ToggleSettings()
|
|
|
|
|
{
|
|
|
|
|
if (_settingsController == null) return;
|
|
|
|
|
if (!_isSettingsOpen)
|
|
|
|
|
{
|
|
|
|
|
_isSettingsOpen = true;
|
2026-04-28 18:49:05 +07:00
|
|
|
_settingsController.Root.BringToFront();
|
2026-05-01 16:17:12 +07:00
|
|
|
if (_cursorLayer != null) _cursorLayer.BringToFront();
|
2026-04-28 11:35:49 +07:00
|
|
|
await _settingsController.PlayTransitionIn();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
_isSettingsOpen = false;
|
|
|
|
|
await _settingsController.PlayTransitionOut();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void Update()
|
|
|
|
|
{
|
2026-04-29 13:10:00 +07:00
|
|
|
if (_history.Count > 0) _history.Peek().Update();
|
2026-05-01 16:17:12 +07:00
|
|
|
UpdateCursorInput();
|
2026-04-28 11:35:49 +07:00
|
|
|
}
|
|
|
|
|
|
2026-05-01 16:17:12 +07:00
|
|
|
private void UpdateCursorInput()
|
2026-04-28 10:44:22 +07:00
|
|
|
{
|
2026-05-01 16:17:12 +07:00
|
|
|
if (!Application.isFocused || _cursorLayer == null) return;
|
|
|
|
|
|
|
|
|
|
// Dùng cách tính tọa độ thủ công để tránh offset khi pivot ở giữa
|
|
|
|
|
Vector2 mousePos = Input.mousePosition;
|
|
|
|
|
float scale = GetCurrentScale();
|
|
|
|
|
Vector2 uiPos = new Vector2(mousePos.x / scale, (Screen.height - mousePos.y) / scale);
|
|
|
|
|
|
|
|
|
|
if (_mainCursor != null)
|
|
|
|
|
{
|
|
|
|
|
_mainCursor.style.left = uiPos.x;
|
|
|
|
|
_mainCursor.style.top = uiPos.y;
|
|
|
|
|
}
|
2026-04-28 10:44:22 +07:00
|
|
|
|
2026-05-01 16:17:12 +07:00
|
|
|
bool trailEnabled = PlayerPrefs.GetInt("CursorTrail", 1) == 1;
|
|
|
|
|
if (trailEnabled && cursorTrailSprite != null)
|
2026-04-28 10:44:22 +07:00
|
|
|
{
|
2026-05-01 16:17:12 +07:00
|
|
|
float dist = Vector2.Distance(uiPos, _lastTrailSpawnPos);
|
|
|
|
|
if (dist > trailDistanceThreshold)
|
2026-04-28 11:03:57 +07:00
|
|
|
{
|
2026-05-01 16:17:12 +07:00
|
|
|
SpawnPooledTrail(uiPos);
|
|
|
|
|
_lastTrailSpawnPos = uiPos;
|
2026-04-28 11:03:57 +07:00
|
|
|
}
|
2026-04-28 10:44:22 +07:00
|
|
|
}
|
2026-05-01 16:17:12 +07:00
|
|
|
}
|
2026-04-28 11:03:57 +07:00
|
|
|
|
2026-05-01 16:17:12 +07:00
|
|
|
private void SpawnPooledTrail(Vector2 pos)
|
|
|
|
|
{
|
|
|
|
|
var particle = _trailPool[_currentTrailIndex];
|
|
|
|
|
_currentTrailIndex = (_currentTrailIndex + 1) % MAX_TRAIL_PARTICLES;
|
|
|
|
|
|
|
|
|
|
Tween.StopAll(particle);
|
|
|
|
|
particle.style.display = DisplayStyle.Flex;
|
|
|
|
|
particle.style.left = pos.x;
|
|
|
|
|
particle.style.top = pos.y;
|
|
|
|
|
particle.style.opacity = 0f;
|
|
|
|
|
particle.style.scale = Vector3.one;
|
|
|
|
|
|
2026-05-30 17:41:31 +07:00
|
|
|
|
2026-04-28 10:44:22 +07:00
|
|
|
}
|
|
|
|
|
|
2026-05-01 15:08:59 +07:00
|
|
|
private float GetCurrentScale() => (_uiDocument != null && _uiDocument.panelSettings != null) ? _uiDocument.panelSettings.scale : 1.0f;
|
2026-04-29 02:31:15 +07:00
|
|
|
|
2026-04-28 10:44:22 +07:00
|
|
|
private void OnGlobalClick(PointerDownEvent evt)
|
|
|
|
|
{
|
2026-04-29 01:04:28 +07:00
|
|
|
if (!enableRipples || _cursorLayer == null) return;
|
2026-05-01 16:17:12 +07:00
|
|
|
|
2026-04-28 10:44:22 +07:00
|
|
|
var ripple = new VisualElement();
|
|
|
|
|
ripple.style.position = Position.Absolute;
|
|
|
|
|
ripple.style.width = cursorSize;
|
|
|
|
|
ripple.style.height = cursorSize;
|
2026-04-28 11:03:57 +07:00
|
|
|
ripple.style.translate = new StyleTranslate(new Translate(Length.Percent(-50), Length.Percent(-50)));
|
2026-05-01 16:17:12 +07:00
|
|
|
ripple.style.left = evt.localPosition.x;
|
|
|
|
|
ripple.style.top = evt.localPosition.y;
|
|
|
|
|
|
2026-04-28 10:48:04 +07:00
|
|
|
var radius = new StyleLength(new Length(50, LengthUnit.Percent));
|
2026-05-01 15:08:59 +07:00
|
|
|
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;
|
2026-04-28 10:44:22 +07:00
|
|
|
ripple.pickingMode = PickingMode.Ignore;
|
2026-05-01 16:17:12 +07:00
|
|
|
|
2026-04-28 10:44:22 +07:00
|
|
|
_cursorLayer.Add(ripple);
|
2026-05-01 16:17:12 +07:00
|
|
|
|
2026-05-01 15:08:59 +07:00
|
|
|
Tween.Custom(Vector3.one, Vector3.one * 2.5f, duration: 0.4f, onValueChange: val => ripple.style.scale = new StyleScale(new Scale(val)), ease: Ease.OutQuad);
|
|
|
|
|
Tween.Custom(1f, 0f, duration: 0.4f, onValueChange: val => ripple.style.opacity = val).OnComplete(() => ripple.RemoveFromHierarchy());
|
2026-04-28 10:44:22 +07:00
|
|
|
}
|
|
|
|
|
|
2026-04-28 00:07:42 +07:00
|
|
|
private void InitializeControllers()
|
|
|
|
|
{
|
2026-04-29 01:04:28 +07:00
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
_mainMenuController = RegisterController<MainMenuController>(mainMenuTemplate);
|
|
|
|
|
if (_mainMenuController != null && gameIcon != null) _mainMenuController.SetGameIcon(gameIcon);
|
|
|
|
|
_lobbyController = RegisterController<LobbyController>(lobbyTemplate);
|
|
|
|
|
if (_lobbyController != null) _lobbyController.SetRoomTemplate(roomItemTemplate);
|
|
|
|
|
RegisterController<ProfileController>(profileTemplate);
|
|
|
|
|
_settingsController = RegisterController<SettingsController>(settingsTemplate);
|
|
|
|
|
RegisterController<HUDController>(hudTemplate);
|
2026-05-01 21:58:20 +07:00
|
|
|
_pauseMenuController = RegisterController<PauseMenuController>(pauseMenuTemplate);
|
2026-04-29 01:04:28 +07:00
|
|
|
_loginController = RegisterController<LoginController>(loginTemplate);
|
|
|
|
|
}
|
2026-05-01 15:08:59 +07:00
|
|
|
catch (Exception e) { Debug.LogError($"[UIManager] Failed to initialize controllers: {e}"); }
|
2026-04-26 04:39:59 +07:00
|
|
|
}
|
2026-04-25 18:20:16 +07:00
|
|
|
|
2026-04-29 01:04:28 +07:00
|
|
|
private T RegisterController<T>(VisualTreeAsset template) where T : BaseUIController
|
2026-04-26 04:39:59 +07:00
|
|
|
{
|
2026-05-01 15:08:59 +07:00
|
|
|
if (template == null || _rootElement == null) return null;
|
|
|
|
|
VisualElement instance = template.Instantiate();
|
2026-04-29 01:04:28 +07:00
|
|
|
if (instance == null) return null;
|
2026-05-01 15:08:59 +07:00
|
|
|
instance.style.flexGrow = 1; instance.style.position = Position.Absolute;
|
|
|
|
|
instance.style.width = Length.Percent(100); instance.style.height = Length.Percent(100);
|
2026-04-28 10:44:22 +07:00
|
|
|
instance.style.display = DisplayStyle.None;
|
2026-04-28 00:07:42 +07:00
|
|
|
_rootElement.Add(instance);
|
2026-05-01 16:17:12 +07:00
|
|
|
if (_cursorLayer != null) _cursorLayer.BringToFront();
|
2026-04-29 01:04:28 +07:00
|
|
|
var controller = ScriptableObject.CreateInstance<T>();
|
2026-04-28 00:07:42 +07:00
|
|
|
controller.Initialize(instance, this);
|
|
|
|
|
_controllers[typeof(T)] = controller;
|
|
|
|
|
return controller;
|
2026-04-23 23:09:54 +07:00
|
|
|
}
|
|
|
|
|
|
2026-04-28 00:07:42 +07:00
|
|
|
public async Task Push<T>() where T : BaseUIController
|
2026-04-26 05:02:49 +07:00
|
|
|
{
|
2026-04-28 10:21:28 +07:00
|
|
|
if (!_controllers.TryGetValue(typeof(T), out var newScreen)) return;
|
|
|
|
|
if (_history.Count > 0 && _history.Peek() == newScreen) return;
|
2026-04-28 10:44:22 +07:00
|
|
|
if (_history.Count > 0) await _history.Peek().PlayTransitionOut();
|
2026-04-28 00:07:42 +07:00
|
|
|
_history.Push(newScreen);
|
|
|
|
|
await newScreen.PlayTransitionIn();
|
2026-04-28 11:03:57 +07:00
|
|
|
if (_cursorLayer != null) _cursorLayer.BringToFront();
|
2026-04-26 04:39:59 +07:00
|
|
|
}
|
|
|
|
|
|
2026-04-28 00:07:42 +07:00
|
|
|
public async Task Pop()
|
2026-04-26 04:39:59 +07:00
|
|
|
{
|
2026-04-28 00:07:42 +07:00
|
|
|
if (_history.Count <= 1) return;
|
2026-04-28 10:44:22 +07:00
|
|
|
await _history.Pop().PlayTransitionOut();
|
|
|
|
|
if (_history.Count > 0) await _history.Peek().PlayTransitionIn();
|
2026-04-28 11:03:57 +07:00
|
|
|
if (_cursorLayer != null) _cursorLayer.BringToFront();
|
2026-04-23 23:09:54 +07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|