This commit is contained in:
2026-04-28 10:11:28 +07:00
parent 252489f48a
commit 6d5a9a4e5b
8 changed files with 137 additions and 742 deletions

View File

@@ -12,12 +12,13 @@ namespace Hallucinate.UI
private VisualElement _logo;
private VisualElement _ribbon;
private VisualElement _virtualCursor;
private VisualElement _logoSpace;
private float _lastInteractionTime;
private const float IDLE_TIMEOUT = 5.0f;
private Tween _pulseTween;
private Tween _rotationTween;
public override void Initialize(VisualElement uxmlRoot, UIManager manager)
{
@@ -25,89 +26,117 @@ namespace Hallucinate.UI
_logo = root.Q<VisualElement>("Logo");
_ribbon = root.Q<VisualElement>("Ribbon");
_virtualCursor = root.Q<VisualElement>("VirtualCursor");
_logoSpace = root.Q<VisualElement>("LogoSpace");
if (_logo == null)
{
Debug.LogError($"[MainMenuController] Element 'Logo' not found in UXML! Root children: {root.childCount}");
Debug.LogError($"[MainMenuController] Element 'Logo' not found in UXML!");
return;
}
_logo.RegisterCallback<PointerDownEvent>(OnLogoClicked);
ResetLogoPosition();
_logo.RegisterCallback<ClickEvent>(OnLogoClicked);
// Bind Buttons with null checks
var settingsBtn = root.Q<Button>("SettingsBtn");
if (settingsBtn != null) settingsBtn.clicked += () => uiManager.Push<SettingsController>();
var joinBtn = root.Q<Button>("JoinBtn");
if (joinBtn != null) joinBtn.clicked += () => uiManager.Push<LobbyController>();
var createBtn = root.Q<Button>("CreateBtn");
if (createBtn != null) createBtn.clicked += () => uiManager.Push<LobbyController>();
var profileBtn = root.Q<Button>("ProfileBtn");
if (profileBtn != null) profileBtn.clicked += () => uiManager.Push<ProfileController>();
var exitBtn = root.Q<Button>("ExitBtn");
if (exitBtn != null) exitBtn.clicked += () => Application.Quit();
// Bind Buttons
root.Q<Button>("SettingsBtn").clicked += () => uiManager.Push<SettingsController>();
root.Q<Button>("JoinBtn").clicked += () => uiManager.Push<LobbyController>();
root.Q<Button>("CreateBtn").clicked += () => uiManager.Push<LobbyController>();
root.Q<Button>("ProfileBtn").clicked += () => uiManager.Push<ProfileController>();
root.Q<Button>("ExitBtn").clicked += () => Application.Quit();
StartPulse();
_lastInteractionTime = Time.time;
}
private void ResetLogoPosition()
{
_logo.style.left = (Screen.width / 2f) - 100;
_logo.style.top = (Screen.height / 2f) - 100;
_logo.style.width = 200;
_logo.style.height = 200;
}
public void SetGameIcon(Texture2D icon)
{
if (icon == null || _logo == null) return;
_logo.style.backgroundImage = icon;
var radius = new StyleLength(new Length(50, LengthUnit.Percent));
_logo.style.borderTopLeftRadius = radius;
_logo.style.borderTopRightRadius = radius;
_logo.style.borderBottomLeftRadius = radius;
_logo.style.borderBottomRightRadius = radius;
_logo.style.overflow = Overflow.Hidden;
var label = _logo.Q<Label>();
if (label != null) label.style.display = DisplayStyle.None;
if (_rotationTween.isAlive) _rotationTween.Stop();
_rotationTween = Tween.Custom(0f, 360f, duration: 4f, cycles: -1, ease: Ease.Linear,
onValueChange: val => _logo.style.rotate = new StyleRotate(new Rotate(Angle.Degrees(val))));
}
public override async Task PlayTransitionIn()
{
await base.PlayTransitionIn();
UnityEngine.Cursor.visible = false;
Show();
// Đảm bảo chuột hệ thống luôn hiện
UnityEngine.Cursor.visible = true;
await Task.CompletedTask;
}
public override async Task PlayTransitionOut()
{
UnityEngine.Cursor.visible = true;
if (_rotationTween.isAlive) _rotationTween.Stop();
await base.PlayTransitionOut();
}
private void StartPulse()
{
// Use Vector3.one * 1.1f for target scale
_pulseTween = Tween.Scale(_logo.transform, Vector3.one * 1.1f, duration: 0.8f, cycles: -1, cycleMode: CycleMode.Yoyo, ease: Ease.InOutSine);
}
private void OnLogoClicked(PointerDownEvent evt)
private void OnLogoClicked(ClickEvent evt)
{
_lastInteractionTime = Time.time;
if (_currentState == MenuState.Idle)
{
TransitionToRibbon();
}
else
{
_ = uiManager.Push<LobbyController>();
}
if (_currentState == MenuState.Idle) TransitionToRibbon();
else _ = uiManager.Push<LobbyController>();
}
private void TransitionToRibbon()
private async void TransitionToRibbon()
{
_currentState = MenuState.Ribbon;
// Transition Logo using Custom tween for offset
Tween.Custom(0f, -300f, duration: 0.5f, ease: Ease.OutQuad,
onValueChange: val => _logo.style.left = val);
_ribbon.style.display = DisplayStyle.Flex;
_ribbon.style.opacity = 0;
Tween.Custom(0f, 1f, duration: 0.3f, onValueChange: val => _ribbon.style.opacity = val);
await Task.Yield();
Rect targetBounds = _logoSpace.worldBound;
// Fade in Ribbon
Tween.Custom(0f, 1f, duration: 0.5f, onValueChange: val => _ribbon.style.opacity = val);
Tween.Custom(_logo.style.left.value.value, targetBounds.x, duration: 0.5f, ease: Ease.OutQuad,
onValueChange: val => _logo.style.left = val);
Tween.Custom(_logo.style.top.value.value, targetBounds.y - 35, duration: 0.5f, ease: Ease.OutQuad,
onValueChange: val => _logo.style.top = val);
Tween.Custom(_logo.style.width.value.value, 120f, duration: 0.5f, ease: Ease.OutQuad,
onValueChange: val => _logo.style.width = val);
Tween.Custom(_logo.style.height.value.value, 120f, duration: 0.5f, ease: Ease.OutQuad,
onValueChange: val => _logo.style.height = val);
}
private void TransitionToIdle()
{
_currentState = MenuState.Idle;
Tween.Custom(-300f, 0f, duration: 0.5f, ease: Ease.OutQuad,
float targetX = (Screen.width / 2f) - 100;
float targetY = (Screen.height / 2f) - 100;
Tween.Custom(_logo.style.left.value.value, targetX, duration: 0.5f, ease: Ease.OutQuad,
onValueChange: val => _logo.style.left = val);
Tween.Custom(_logo.style.top.value.value, targetY, duration: 0.5f, ease: Ease.OutQuad,
onValueChange: val => _logo.style.top = val);
Tween.Custom(_logo.style.width.value.value, 200f, duration: 0.5f, ease: Ease.OutQuad,
onValueChange: val => _logo.style.width = val);
Tween.Custom(_logo.style.height.value.value, 200f, duration: 0.5f, ease: Ease.OutQuad,
onValueChange: val => _logo.style.height = val);
Tween.Custom(1f, 0f, duration: 0.5f, onValueChange: val => _ribbon.style.opacity = val)
.OnComplete(() => _ribbon.style.display = DisplayStyle.None);
@@ -115,32 +144,15 @@ namespace Hallucinate.UI
public void Update()
{
UpdateVirtualCursor();
if (_currentState == MenuState.Ribbon)
if (_currentState == MenuState.Ribbon && Time.time - _lastInteractionTime > IDLE_TIMEOUT)
{
if (Time.time - _lastInteractionTime > IDLE_TIMEOUT)
{
TransitionToIdle();
}
TransitionToIdle();
}
}
private void UpdateVirtualCursor()
private void StartPulse()
{
if (_virtualCursor == null) return;
Vector2 mousePos = Input.mousePosition;
float x = mousePos.x;
float y = Screen.height - mousePos.y;
if (_currentState == MenuState.Ribbon)
{
y = Screen.height / 2f + 50;
}
_virtualCursor.style.left = x - _virtualCursor.layout.width / 2;
_virtualCursor.style.top = y - _virtualCursor.layout.height / 2;
_pulseTween = Tween.Scale(_logo.transform, Vector3.one * 1.1f, duration: 0.8f, cycles: -1, cycleMode: CycleMode.Yoyo, ease: Ease.InOutSine);
}
}
}

View File

@@ -3,6 +3,9 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.UIElements;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace Hallucinate.UI
{
@@ -17,6 +20,9 @@ namespace Hallucinate.UI
private readonly Dictionary<Type, BaseUIController> _controllers = new Dictionary<Type, BaseUIController>();
private readonly Stack<BaseUIController> _history = new Stack<BaseUIController>();
[Header("Game Metadata")]
[SerializeField] private Texture2D gameIcon;
[Header("UI Templates")]
[SerializeField] private VisualTreeAsset mainMenuTemplate;
[SerializeField] private VisualTreeAsset lobbyTemplate;
@@ -48,6 +54,14 @@ namespace Hallucinate.UI
_uiDocument = GetComponent<UIDocument>();
_rootElement = _uiDocument.rootVisualElement;
#if UNITY_EDITOR
if (gameIcon == null)
{
var icons = PlayerSettings.GetIconsForTargetGroup(BuildTargetGroup.Unknown);
if (icons != null && icons.Length > 0) gameIcon = icons[0];
}
#endif
InitializeControllers();
}
@@ -55,6 +69,11 @@ namespace Hallucinate.UI
private void InitializeControllers()
{
_mainMenuController = RegisterController<MainMenuController>(mainMenuTemplate);
if (_mainMenuController != null && gameIcon != null)
{
_mainMenuController.SetGameIcon(gameIcon);
}
_lobbyController = RegisterController<LobbyController>(lobbyTemplate);
_profileController = RegisterController<ProfileController>(profileTemplate);
_settingsController = RegisterController<SettingsController>(settingsTemplate);
@@ -120,7 +139,5 @@ namespace Hallucinate.UI
var previousScreen = _history.Peek();
await previousScreen.PlayTransitionIn();
}
// Custom Inspector features can be added here with [ContextMenu] or CustomEditor
}
}