Update
This commit is contained in:
@@ -1,192 +1,146 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using PrimeTween;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace UI
|
||||
namespace Hallucinate.UI
|
||||
{
|
||||
public class MainMenuController : MonoBehaviour
|
||||
public class MainMenuController : BaseUIController
|
||||
{
|
||||
private VisualElement _root;
|
||||
private VisualElement _logoContainer;
|
||||
public enum MenuState { Idle, Ribbon }
|
||||
private MenuState _currentState = MenuState.Idle;
|
||||
|
||||
private VisualElement _logo;
|
||||
private VisualElement _ribbon;
|
||||
private VisualElement _logoPlaceholder;
|
||||
private bool _isActive = false;
|
||||
|
||||
[Header("Animation Settings")]
|
||||
public float transitionDuration = 0.5f;
|
||||
public float idleTimeout = 5f;
|
||||
public float pulseSpeed = 2f;
|
||||
public float pulseAmount = 0.05f;
|
||||
private VisualElement _virtualCursor;
|
||||
|
||||
private float _lastInteractionTime;
|
||||
private int _lastClickFrame = -1;
|
||||
private Coroutine _currentTransition;
|
||||
private const float IDLE_TIMEOUT = 5.0f;
|
||||
|
||||
private void OnEnable()
|
||||
private Tween _pulseTween;
|
||||
|
||||
public override void Initialize(VisualElement uxmlRoot, UIManager manager)
|
||||
{
|
||||
_root = GetComponent<UIDocument>().rootVisualElement;
|
||||
base.Initialize(uxmlRoot, manager);
|
||||
|
||||
_logoContainer = _root.Q<VisualElement>("beat-logo-container");
|
||||
_logo = _root.Q<VisualElement>("beat-logo");
|
||||
_ribbon = _root.Q<VisualElement>("menu-ribbon");
|
||||
_logoPlaceholder = _root.Q<VisualElement>("logo-placeholder");
|
||||
_logo = root.Q<VisualElement>("Logo");
|
||||
_ribbon = root.Q<VisualElement>("Ribbon");
|
||||
_virtualCursor = root.Q<VisualElement>("VirtualCursor");
|
||||
|
||||
// Đảm bảo Logo luôn có thể nhấn được
|
||||
_logoContainer.pickingMode = PickingMode.Position;
|
||||
_logo.pickingMode = PickingMode.Position;
|
||||
_logoContainer.RegisterCallback<ClickEvent>(OnLogoClicked);
|
||||
if (_logo == null)
|
||||
{
|
||||
Debug.LogError($"[MainMenuController] Element 'Logo' not found in UXML! Root children: {root.childCount}");
|
||||
return;
|
||||
}
|
||||
|
||||
_root.RegisterCallback<MouseMoveEvent>(evt => ResetIdleTimer());
|
||||
_logo.RegisterCallback<PointerDownEvent>(OnLogoClicked);
|
||||
|
||||
// Bind Buttons with null checks
|
||||
var settingsBtn = root.Q<Button>("SettingsBtn");
|
||||
if (settingsBtn != null) settingsBtn.clicked += () => uiManager.Push<SettingsController>();
|
||||
|
||||
var buttons = _root.Query<Button>().ToList();
|
||||
foreach (var btn in buttons)
|
||||
{
|
||||
btn.RegisterCallback<PointerDownEvent>(evt => ApplyDrumHit(btn, true));
|
||||
btn.RegisterCallback<PointerUpEvent>(evt => ApplyDrumHit(btn, false));
|
||||
btn.RegisterCallback<ClickEvent>(evt => ResetIdleTimer());
|
||||
}
|
||||
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();
|
||||
|
||||
_logoContainer.RegisterCallback<PointerDownEvent>(evt => ApplyDrumHit(_logoContainer, true));
|
||||
_logoContainer.RegisterCallback<PointerUpEvent>(evt => ApplyDrumHit(_logoContainer, false));
|
||||
|
||||
// Routing
|
||||
_root.Q<Button>("btn-create")?.RegisterCallback<ClickEvent>(ev => NavigateToLobby(true));
|
||||
_root.Q<Button>("btn-join")?.RegisterCallback<ClickEvent>(ev => NavigateToLobby(false));
|
||||
_root.Q<Button>("btn-settings")?.RegisterCallback<ClickEvent>(ev => UIManager.Instance.ToggleSettings());
|
||||
_root.Q<Button>("btn-profile")?.RegisterCallback<ClickEvent>(ev => UIManager.Instance.ShowScreen("Profile"));
|
||||
_root.Q<Button>("btn-exit")?.RegisterCallback<ClickEvent>(ev => Application.Quit());
|
||||
|
||||
ResetToIdleState();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
float baseScale = _isActive ? 0.35f : 1.0f;
|
||||
float pulse = Mathf.Sin(Time.time * pulseSpeed) * pulseAmount;
|
||||
_logo.style.scale = new Scale(new Vector3(baseScale + pulse, baseScale + pulse, 1f));
|
||||
|
||||
if (_isActive && _currentTransition == null)
|
||||
{
|
||||
if (Time.time - _lastInteractionTime > idleTimeout)
|
||||
_currentTransition = StartCoroutine(TransitionToIdle());
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLogoClicked(ClickEvent evt)
|
||||
{
|
||||
if (Time.frameCount == _lastClickFrame) return;
|
||||
_lastClickFrame = Time.frameCount;
|
||||
|
||||
ResetIdleTimer();
|
||||
|
||||
// QUAN TRỌNG: Chỉ vào Lobby nếu ĐÃ Active và KHÔNG đang chuyển cảnh
|
||||
if (!_isActive) {
|
||||
if (_currentTransition != null) StopCoroutine(_currentTransition);
|
||||
_currentTransition = StartCoroutine(TransitionToActive());
|
||||
} else if (_currentTransition == null) {
|
||||
NavigateToLobby(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyDrumHit(VisualElement element, bool isDown)
|
||||
{
|
||||
if (isDown)
|
||||
{
|
||||
element.style.scale = new Scale(new Vector3(0.85f, 0.85f, 1f));
|
||||
element.style.transitionDuration = new List<TimeValue> { new TimeValue(0.05f, TimeUnit.Second) };
|
||||
}
|
||||
else
|
||||
{
|
||||
element.style.scale = new Scale(Vector3.one);
|
||||
element.style.transitionDuration = new List<TimeValue> { new TimeValue(0.15f, TimeUnit.Second) };
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetIdleTimer()
|
||||
{
|
||||
StartPulse();
|
||||
_lastInteractionTime = Time.time;
|
||||
}
|
||||
|
||||
private void NavigateToLobby(bool isCreate)
|
||||
public override async Task PlayTransitionIn()
|
||||
{
|
||||
var lobby = Object.FindFirstObjectByType<LobbyController>();
|
||||
lobby?.SetMode(isCreate);
|
||||
UIManager.Instance.ShowScreen("Lobby");
|
||||
await base.PlayTransitionIn();
|
||||
UnityEngine.Cursor.visible = false;
|
||||
}
|
||||
|
||||
private void ResetToIdleState()
|
||||
public override async Task PlayTransitionOut()
|
||||
{
|
||||
_isActive = false;
|
||||
UIManager.Instance.isMainMenuActive = false;
|
||||
_ribbon.style.display = DisplayStyle.None;
|
||||
UnityEngine.Cursor.visible = true;
|
||||
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)
|
||||
{
|
||||
_lastInteractionTime = Time.time;
|
||||
|
||||
if (_currentState == MenuState.Idle)
|
||||
{
|
||||
TransitionToRibbon();
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = uiManager.Push<LobbyController>();
|
||||
}
|
||||
}
|
||||
|
||||
private void TransitionToRibbon()
|
||||
{
|
||||
_currentState = MenuState.Ribbon;
|
||||
|
||||
_root.Add(_logoContainer);
|
||||
_logoContainer.style.position = Position.Absolute;
|
||||
_logoContainer.style.width = 300; _logoContainer.style.height = 300;
|
||||
_logoContainer.style.left = Length.Percent(50);
|
||||
_logoContainer.style.top = Length.Percent(50);
|
||||
_logoContainer.style.translate = new Translate(Length.Percent(-50), Length.Percent(-50));
|
||||
_currentTransition = null;
|
||||
}
|
||||
|
||||
private IEnumerator TransitionToActive()
|
||||
{
|
||||
_isActive = true;
|
||||
UIManager.Instance.isMainMenuActive = true;
|
||||
ResetIdleTimer();
|
||||
// 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;
|
||||
|
||||
yield return null;
|
||||
|
||||
_logoContainer.style.transitionProperty = new List<StylePropertyName> { "translate", "opacity" };
|
||||
_logoContainer.style.transitionDuration = new List<TimeValue> { new TimeValue(transitionDuration, TimeUnit.Second) };
|
||||
_ribbon.style.transitionProperty = new List<StylePropertyName> { "opacity" };
|
||||
_ribbon.style.transitionDuration = new List<TimeValue> { new TimeValue(transitionDuration, TimeUnit.Second) };
|
||||
|
||||
// Trượt Logo từ tâm sang vị trí ribbon (#2)
|
||||
_logoContainer.style.translate = new Translate(Length.Percent(-75f), Length.Percent(-50f));
|
||||
_ribbon.style.opacity = 1;
|
||||
|
||||
yield return new WaitForSeconds(transitionDuration);
|
||||
|
||||
// Gán chặt vào placeholder và khóa kích thước để chống giãn
|
||||
_logoPlaceholder.Add(_logoContainer);
|
||||
_logoContainer.style.position = Position.Relative;
|
||||
_logoContainer.style.left = StyleKeyword.Auto;
|
||||
_logoContainer.style.top = StyleKeyword.Auto;
|
||||
_logoContainer.style.translate = new Translate(0, 0);
|
||||
|
||||
// Khóa kích thước nhỏ để fit vào Ribbon
|
||||
_logoContainer.style.width = 100;
|
||||
_logoContainer.style.height = 100;
|
||||
|
||||
_currentTransition = null;
|
||||
// Fade in Ribbon
|
||||
Tween.Custom(0f, 1f, duration: 0.5f, onValueChange: val => _ribbon.style.opacity = val);
|
||||
}
|
||||
|
||||
private IEnumerator TransitionToIdle()
|
||||
private void TransitionToIdle()
|
||||
{
|
||||
_isActive = false;
|
||||
UIManager.Instance.isMainMenuActive = false;
|
||||
_currentState = MenuState.Idle;
|
||||
|
||||
Tween.Custom(-300f, 0f, duration: 0.5f, ease: Ease.OutQuad,
|
||||
onValueChange: val => _logo.style.left = val);
|
||||
|
||||
_root.Add(_logoContainer);
|
||||
_logoContainer.style.position = Position.Absolute;
|
||||
_logoContainer.style.width = 300; _logoContainer.style.height = 300;
|
||||
_logoContainer.style.left = Length.Percent(50);
|
||||
_logoContainer.style.top = Length.Percent(50);
|
||||
_logoContainer.style.translate = new Translate(Length.Percent(-75f), Length.Percent(-50f));
|
||||
Tween.Custom(1f, 0f, duration: 0.5f, onValueChange: val => _ribbon.style.opacity = val)
|
||||
.OnComplete(() => _ribbon.style.display = DisplayStyle.None);
|
||||
}
|
||||
|
||||
yield return null;
|
||||
public void Update()
|
||||
{
|
||||
UpdateVirtualCursor();
|
||||
|
||||
_logoContainer.style.translate = new Translate(Length.Percent(-50f), Length.Percent(-50f));
|
||||
_ribbon.style.opacity = 0;
|
||||
if (_currentState == MenuState.Ribbon)
|
||||
{
|
||||
if (Time.time - _lastInteractionTime > IDLE_TIMEOUT)
|
||||
{
|
||||
TransitionToIdle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
yield return new WaitForSeconds(transitionDuration);
|
||||
_ribbon.style.display = DisplayStyle.None;
|
||||
_currentTransition = null;
|
||||
private void UpdateVirtualCursor()
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user