2026-04-25 18:20:16 +07:00
|
|
|
using UnityEngine;
|
|
|
|
|
using UnityEngine.UIElements;
|
2026-04-29 01:04:28 +07:00
|
|
|
using UnityEngine.InputSystem;
|
2026-04-28 00:07:42 +07:00
|
|
|
using PrimeTween;
|
2026-04-29 01:04:28 +07:00
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
2026-04-28 00:07:42 +07:00
|
|
|
using System.Threading.Tasks;
|
2026-04-29 01:04:28 +07:00
|
|
|
using UnityEngine.SceneManagement;
|
|
|
|
|
using OnlyScove.Scripts; // Namespace của InputReader
|
2026-04-25 18:20:16 +07:00
|
|
|
|
2026-04-28 00:07:42 +07:00
|
|
|
namespace Hallucinate.UI
|
2026-04-25 18:20:16 +07:00
|
|
|
{
|
2026-04-28 00:07:42 +07:00
|
|
|
public class SettingsController : BaseUIController
|
2026-04-25 18:20:16 +07:00
|
|
|
{
|
2026-04-28 00:07:42 +07:00
|
|
|
private VisualElement _sidebar;
|
|
|
|
|
private Label _tabTitle;
|
|
|
|
|
private ScrollView _content;
|
2026-04-29 01:04:28 +07:00
|
|
|
private InputActionAsset _inputActions;
|
|
|
|
|
|
|
|
|
|
private Dictionary<string, Button> _tabButtons = new Dictionary<string, Button>();
|
|
|
|
|
private string _activeTab = "GENERAL";
|
2026-04-25 18:20:16 +07:00
|
|
|
|
2026-04-28 00:07:42 +07:00
|
|
|
public override void Initialize(VisualElement uxmlRoot, UIManager manager)
|
2026-04-25 18:20:16 +07:00
|
|
|
{
|
2026-04-28 00:07:42 +07:00
|
|
|
base.Initialize(uxmlRoot, manager);
|
2026-04-25 18:20:16 +07:00
|
|
|
|
2026-04-28 00:07:42 +07:00
|
|
|
_sidebar = root.Q<VisualElement>("Sidebar");
|
|
|
|
|
_tabTitle = root.Q<Label>("TabTitle");
|
|
|
|
|
_content = root.Q<ScrollView>("SettingsContent");
|
2026-04-25 18:20:16 +07:00
|
|
|
|
2026-04-29 01:04:28 +07:00
|
|
|
// Ưu tiên 1: Lấy từ InputReader đã gán trong UIManager
|
|
|
|
|
_inputActions = uiManager.InputReader?.InputActions;
|
|
|
|
|
|
|
|
|
|
// Ưu tiên 2: Nếu null, thử tìm PlayerInput trong scene
|
|
|
|
|
if (_inputActions == null)
|
|
|
|
|
{
|
|
|
|
|
_inputActions = GameObject.FindAnyObjectByType<PlayerInput>()?.actions;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Click outside to close
|
2026-04-28 18:49:05 +07:00
|
|
|
root.RegisterCallback<PointerDownEvent>(evt => {
|
2026-04-28 11:35:49 +07:00
|
|
|
if (evt.target == root)
|
|
|
|
|
{
|
|
|
|
|
uiManager.ToggleSettings();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-04-29 01:04:28 +07:00
|
|
|
SetupTab("GeneralTab", "GENERAL");
|
|
|
|
|
SetupTab("VideoTab", "VIDEO");
|
|
|
|
|
SetupTab("SoundTab", "SOUND");
|
|
|
|
|
SetupTab("ControlTab", "CONTROL");
|
|
|
|
|
|
|
|
|
|
var closeBtn = root.Q<Button>("CloseSettingsBtn");
|
|
|
|
|
if (closeBtn != null) closeBtn.clicked += () => uiManager.ToggleSettings();
|
|
|
|
|
|
|
|
|
|
// Default tab
|
|
|
|
|
SwitchTab("GENERAL");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void SetupTab(string btnName, string tabId)
|
|
|
|
|
{
|
|
|
|
|
var btn = root.Q<Button>(btnName);
|
|
|
|
|
if (btn != null)
|
|
|
|
|
{
|
|
|
|
|
_tabButtons[tabId] = btn;
|
|
|
|
|
btn.clicked += () => SwitchTab(tabId);
|
|
|
|
|
}
|
2026-04-25 18:20:16 +07:00
|
|
|
}
|
|
|
|
|
|
2026-04-29 01:04:28 +07:00
|
|
|
private void SwitchTab(string tabId)
|
2026-04-25 18:20:16 +07:00
|
|
|
{
|
2026-04-29 01:04:28 +07:00
|
|
|
_activeTab = tabId;
|
|
|
|
|
_tabTitle.text = tabId;
|
|
|
|
|
|
|
|
|
|
// Update tab styles
|
|
|
|
|
foreach (var kvp in _tabButtons)
|
|
|
|
|
{
|
|
|
|
|
if (kvp.Key == tabId) kvp.Value.AddToClassList("active-tab");
|
|
|
|
|
else kvp.Value.RemoveFromClassList("active-tab");
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-28 00:07:42 +07:00
|
|
|
_content.Clear();
|
2026-04-29 01:04:28 +07:00
|
|
|
|
|
|
|
|
switch (tabId)
|
|
|
|
|
{
|
|
|
|
|
case "GENERAL":
|
|
|
|
|
RenderGeneralSettings();
|
|
|
|
|
break;
|
|
|
|
|
case "CONTROL":
|
|
|
|
|
RenderControlSettings();
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
var comingSoon = new Label($"Settings for {tabId} coming soon...");
|
|
|
|
|
comingSoon.AddToClassList("text-body");
|
|
|
|
|
_content.Add(comingSoon);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void RenderGeneralSettings()
|
|
|
|
|
{
|
|
|
|
|
var section = CreateSection("ACCOUNT & DATA");
|
|
|
|
|
|
|
|
|
|
var wipeBtn = new Button { text = "WIPE ALL USER DATA (TEST ONLY)" };
|
|
|
|
|
wipeBtn.AddToClassList("button-spring");
|
|
|
|
|
wipeBtn.AddToClassList("btn-exit");
|
|
|
|
|
wipeBtn.style.marginTop = 20;
|
|
|
|
|
wipeBtn.style.backgroundColor = new Color(0.8f, 0.2f, 0.2f, 0.8f);
|
|
|
|
|
|
|
|
|
|
wipeBtn.clicked += () => {
|
|
|
|
|
bool confirm = true;
|
|
|
|
|
#if UNITY_EDITOR
|
|
|
|
|
confirm = UnityEditor.EditorUtility.DisplayDialog("Wipe Data", "This will delete your local username and restart the game. Proceed?", "Yes", "Cancel");
|
|
|
|
|
#endif
|
|
|
|
|
if (confirm)
|
|
|
|
|
{
|
|
|
|
|
PlayerPrefs.DeleteAll();
|
|
|
|
|
PlayerPrefs.Save();
|
|
|
|
|
Debug.Log("[Settings] Data wiped. Restarting...");
|
|
|
|
|
SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
_content.Add(section);
|
|
|
|
|
_content.Add(wipeBtn);
|
|
|
|
|
|
|
|
|
|
_content.Add(new Label("\nNote: This will force the game to ask for your name again on next launch.") {
|
|
|
|
|
style = { fontSize = 12, color = new Color(0.6f, 0.6f, 0.6f), whiteSpace = WhiteSpace.Normal, marginTop = 10 }
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void RenderControlSettings()
|
|
|
|
|
{
|
|
|
|
|
if (_inputActions == null)
|
|
|
|
|
{
|
|
|
|
|
var errorLabel = new Label("Input Actions not found. Cannot rebind.");
|
|
|
|
|
errorLabel.AddToClassList("text-body");
|
|
|
|
|
_content.Add(errorLabel);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var playerMap = _inputActions.FindActionMap("Player");
|
|
|
|
|
if (playerMap == null) return;
|
|
|
|
|
|
|
|
|
|
// Categories
|
|
|
|
|
RenderSection("MOVEMENT", playerMap, new[] { "Move", "Jump", "Sprint", "Crouch" });
|
|
|
|
|
RenderSection("COMBAT", playerMap, new[] { "Attack" });
|
|
|
|
|
RenderSection("INTERACTION", playerMap, new[] { "Interact", "Next", "Previous" });
|
|
|
|
|
RenderSection("VIEW", playerMap, new[] { "Look", "ToggleView", "Scroll" });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void RenderSection(string header, InputActionMap map, string[] actionNames)
|
|
|
|
|
{
|
|
|
|
|
_content.Add(CreateSection(header));
|
|
|
|
|
foreach (var name in actionNames)
|
|
|
|
|
{
|
|
|
|
|
var action = map.FindAction(name);
|
|
|
|
|
if (action != null)
|
|
|
|
|
{
|
|
|
|
|
_content.Add(CreateRebindRow(action));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private VisualElement CreateSection(string title)
|
|
|
|
|
{
|
|
|
|
|
var header = new Label(title);
|
|
|
|
|
header.AddToClassList("setting-section-header");
|
|
|
|
|
return header;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private VisualElement CreateRebindRow(InputAction action)
|
|
|
|
|
{
|
|
|
|
|
var row = new VisualElement();
|
|
|
|
|
row.AddToClassList("rebind-row");
|
|
|
|
|
|
|
|
|
|
var label = new Label(action.name.ToUpper());
|
|
|
|
|
label.AddToClassList("rebind-label");
|
|
|
|
|
|
|
|
|
|
var rebindBtn = new Button();
|
|
|
|
|
rebindBtn.AddToClassList("rebind-button");
|
|
|
|
|
|
|
|
|
|
// Get current binding text
|
|
|
|
|
UpdateBindingText(action, rebindBtn);
|
|
|
|
|
|
|
|
|
|
rebindBtn.clicked += () => StartRebind(action, rebindBtn);
|
|
|
|
|
|
|
|
|
|
row.Add(label);
|
|
|
|
|
row.Add(rebindBtn);
|
|
|
|
|
return row;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void UpdateBindingText(InputAction action, Button btn)
|
|
|
|
|
{
|
|
|
|
|
int bindingIndex = action.GetBindingIndexForControl(action.controls[0]);
|
|
|
|
|
btn.text = action.GetBindingDisplayString(bindingIndex);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void StartRebind(InputAction action, Button btn)
|
|
|
|
|
{
|
|
|
|
|
string oldText = btn.text;
|
|
|
|
|
btn.text = "...";
|
|
|
|
|
btn.style.color = Color.yellow;
|
|
|
|
|
|
|
|
|
|
action.Disable();
|
|
|
|
|
|
|
|
|
|
var rebindOperation = action.PerformInteractiveRebinding()
|
|
|
|
|
.WithControlsExcluding("<Mouse>/delta") // Don't bind to mouse movement
|
|
|
|
|
.WithControlsExcluding("<Mouse>/scroll")
|
|
|
|
|
.OnMatchWaitForAnother(0.1f)
|
|
|
|
|
.OnComplete(operation => {
|
|
|
|
|
btn.style.color = new Color(0f, 1f, 0.8f); // Reset color
|
|
|
|
|
UpdateBindingText(action, btn);
|
|
|
|
|
action.Enable();
|
|
|
|
|
operation.Dispose();
|
|
|
|
|
// Save bindings here if you have a save system
|
|
|
|
|
})
|
|
|
|
|
.OnCancel(operation => {
|
|
|
|
|
btn.style.color = new Color(0f, 1f, 0.8f);
|
|
|
|
|
btn.text = oldText;
|
|
|
|
|
action.Enable();
|
|
|
|
|
operation.Dispose();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
rebindOperation.Start();
|
2026-04-28 00:07:42 +07:00
|
|
|
}
|
2026-04-26 05:02:49 +07:00
|
|
|
|
2026-04-28 00:07:42 +07:00
|
|
|
public override async Task PlayTransitionIn()
|
|
|
|
|
{
|
2026-04-28 11:35:49 +07:00
|
|
|
if (root != null)
|
|
|
|
|
{
|
|
|
|
|
root.style.translate = new StyleTranslate(new Translate(0, 0));
|
|
|
|
|
root.style.display = DisplayStyle.Flex;
|
2026-04-29 01:04:28 +07:00
|
|
|
root.style.opacity = 1;
|
2026-04-28 11:35:49 +07:00
|
|
|
}
|
|
|
|
|
|
2026-04-28 00:07:42 +07:00
|
|
|
_sidebar.style.translate = new StyleTranslate(new Translate(Length.Percent(-100), 0));
|
|
|
|
|
await Tween.Custom(-100f, 0f, duration: 0.4f, ease: Ease.OutQuad,
|
|
|
|
|
onValueChange: val => _sidebar.style.translate = new StyleTranslate(new Translate(Length.Percent(val), 0)));
|
|
|
|
|
}
|
2026-04-26 05:02:49 +07:00
|
|
|
|
2026-04-28 00:07:42 +07:00
|
|
|
public override async Task PlayTransitionOut()
|
|
|
|
|
{
|
|
|
|
|
await Tween.Custom(0f, -100f, duration: 0.4f, ease: Ease.InQuad,
|
|
|
|
|
onValueChange: val => _sidebar.style.translate = new StyleTranslate(new Translate(Length.Percent(val), 0)));
|
2026-04-28 11:35:49 +07:00
|
|
|
|
2026-04-28 00:07:42 +07:00
|
|
|
Hide();
|
2026-04-25 18:20:16 +07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|