Update audio manager
This commit is contained in:
@@ -30,6 +30,11 @@ namespace Hallucinate.Audio
|
||||
if (database != null) database.Initialize();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
ApplyAllVolumes();
|
||||
}
|
||||
|
||||
private void InitializePool()
|
||||
{
|
||||
_pool = new List<AudioSource>();
|
||||
@@ -43,6 +48,27 @@ namespace Hallucinate.Audio
|
||||
}
|
||||
}
|
||||
|
||||
public void ApplyAllVolumes()
|
||||
{
|
||||
SetVolume("MasterVolume", PlayerPrefs.GetFloat("MasterVolume", 80f));
|
||||
SetVolume("MusicVolume", PlayerPrefs.GetFloat("MusicVolume", 80f));
|
||||
SetVolume("VFXVolume", PlayerPrefs.GetFloat("VFXVolume", 80f));
|
||||
SetVolume("PlayerVolume", PlayerPrefs.GetFloat("PlayerVolume", 80f));
|
||||
SetVolume("UIVolume", PlayerPrefs.GetFloat("UIVolume", 80f));
|
||||
}
|
||||
|
||||
public void SetVolume(string key, float volume)
|
||||
{
|
||||
if (defaultGroup == null || defaultGroup.audioMixer == null) return;
|
||||
|
||||
// Chuyển đổi từ 0-100 sang dB (-80f đến 0f hoặc 20f tùy mixer)
|
||||
// Công thức: dB = 20 * log10(volume / 100)
|
||||
float db = volume <= 0.001f ? -80f : Mathf.Log10(volume / 100f) * 20f;
|
||||
|
||||
// Đảm bảo Parameter đã được EXPOSE trong AudioMixer với tên tương ứng (MasterVolume, MusicVolume, etc.)
|
||||
defaultGroup.audioMixer.SetFloat(key, db);
|
||||
}
|
||||
|
||||
public void Play(string sampleName, float volumeMult = 1f, float pitchMult = 1f, Vector3? position = null)
|
||||
{
|
||||
if (database == null) return;
|
||||
|
||||
@@ -31,9 +31,13 @@ namespace Hallucinate.UI
|
||||
private float _sliderMin, _sliderMax;
|
||||
|
||||
// Osu-style Volume Overlay
|
||||
private VisualElement _volumeOverlay;
|
||||
private VisualElement _volumeContainer;
|
||||
private VisualElement _masterRing;
|
||||
private Label _masterVolLabel;
|
||||
private Dictionary<string, (VisualElement ring, Label label)> _subRings = new Dictionary<string, (VisualElement, Label)>();
|
||||
private string _hoveredSubVolume = null;
|
||||
private float _masterVol = 80f;
|
||||
private int _overlayActiveCount = 0;
|
||||
|
||||
public override void Initialize(VisualElement uxmlRoot, UIManager manager)
|
||||
{
|
||||
@@ -43,9 +47,9 @@ namespace Hallucinate.UI
|
||||
_tabTitle = root.Q<Label>("TabTitle");
|
||||
_content = root.Q<ScrollView>("SettingsContent");
|
||||
|
||||
// Osu Volume Logic - Registering on Root for Global Wheel Catch
|
||||
root.RegisterCallback<WheelEvent>(OnMouseWheel);
|
||||
SetupVolumeOverlay();
|
||||
// Global Volume Catch - Use TrickleDown to catch events before they are consumed by children (like ScrollViews)
|
||||
uiManager.Root.RegisterCallback<WheelEvent>(OnMouseWheel, TrickleDown.TrickleDown);
|
||||
SetupHierarchicalVolumeOverlay();
|
||||
|
||||
root.RegisterCallback<PointerDownEvent>(evt => {
|
||||
if (evt.target == root) uiManager.ToggleSettings();
|
||||
@@ -67,88 +71,191 @@ namespace Hallucinate.UI
|
||||
SwitchTab("GENERAL");
|
||||
}
|
||||
|
||||
private void SetupVolumeOverlay()
|
||||
private void SetupHierarchicalVolumeOverlay()
|
||||
{
|
||||
_volumeOverlay = new VisualElement();
|
||||
_volumeOverlay.style.position = Position.Absolute;
|
||||
_volumeOverlay.style.right = 40;
|
||||
_volumeOverlay.style.top = Length.Percent(40);
|
||||
_volumeOverlay.style.width = 120;
|
||||
_volumeOverlay.style.height = 120;
|
||||
_volumeOverlay.style.backgroundColor = new Color(0, 0, 0, 0.8f);
|
||||
_volumeOverlay.style.borderTopLeftRadius = 60;
|
||||
_volumeOverlay.style.borderTopRightRadius = 60;
|
||||
_volumeOverlay.style.borderBottomLeftRadius = 60;
|
||||
_volumeOverlay.style.borderBottomRightRadius = 60;
|
||||
_volumeOverlay.style.borderTopWidth = 4;
|
||||
_volumeOverlay.style.borderBottomWidth = 4;
|
||||
_volumeOverlay.style.borderLeftWidth = 4;
|
||||
_volumeOverlay.style.borderRightWidth = 4;
|
||||
_volumeOverlay.style.borderTopColor = Color.cyan;
|
||||
_volumeOverlay.style.borderBottomColor = Color.cyan;
|
||||
_volumeOverlay.style.borderLeftColor = Color.cyan;
|
||||
_volumeOverlay.style.borderRightColor = Color.cyan;
|
||||
_volumeOverlay.style.justifyContent = Justify.Center;
|
||||
_volumeOverlay.style.alignItems = Align.Center;
|
||||
_volumeOverlay.style.display = DisplayStyle.None;
|
||||
_volumeOverlay.pickingMode = PickingMode.Ignore;
|
||||
_volumeContainer = new VisualElement();
|
||||
_volumeContainer.name = "GlobalVolumeOverlay";
|
||||
_volumeContainer.style.position = Position.Absolute;
|
||||
_volumeContainer.style.right = 50;
|
||||
_volumeContainer.style.bottom = 50;
|
||||
_volumeContainer.style.width = 300;
|
||||
_volumeContainer.style.height = 300;
|
||||
_volumeContainer.style.display = DisplayStyle.None;
|
||||
_volumeContainer.pickingMode = PickingMode.Ignore;
|
||||
|
||||
// Add to UIManager root so it stays even when Settings is hidden
|
||||
uiManager.Root.Add(_volumeContainer);
|
||||
|
||||
_masterVolLabel = new Label("80%");
|
||||
_masterVolLabel.style.color = Color.white;
|
||||
_masterVolLabel.style.fontSize = 24;
|
||||
_masterVolLabel.style.unityFontStyleAndWeight = FontStyle.Bold;
|
||||
_volumeOverlay.Add(_masterVolLabel);
|
||||
// Master Ring (Bottom Right)
|
||||
_masterRing = CreateRing("Master", 120, cyan: true);
|
||||
_masterRing.style.right = 0;
|
||||
_masterRing.style.bottom = 0;
|
||||
_masterVolLabel = _masterRing.Q<Label>();
|
||||
_volumeContainer.Add(_masterRing);
|
||||
|
||||
root.Add(_volumeOverlay);
|
||||
// Sub Rings (Music, VFX, Player, UI)
|
||||
string[] subs = { "MusicVolume", "VFXVolume", "PlayerVolume", "UIVolume" };
|
||||
string[] shortNames = { "MUS", "VFX", "PLY", "UI" };
|
||||
|
||||
// Layout sub-rings in an arc around Master
|
||||
for (int i = 0; i < subs.Length; i++)
|
||||
{
|
||||
var ring = CreateRing(shortNames[i], 70, false);
|
||||
|
||||
// Angle 0 = Top, Angle 90 = Left
|
||||
float angle = (i * 30f) * Mathf.Deg2Rad;
|
||||
float radius = 140f;
|
||||
|
||||
// right moves element to the LEFT, bottom moves element UP
|
||||
// Starting from top (right=25, bottom=140+25) to left (right=140+25, bottom=25)
|
||||
ring.style.right = 25 + Mathf.Sin(angle) * radius;
|
||||
ring.style.bottom = 25 + Mathf.Cos(angle) * radius;
|
||||
|
||||
string key = subs[i];
|
||||
ring.RegisterCallback<PointerEnterEvent>(evt => _hoveredSubVolume = key);
|
||||
ring.RegisterCallback<PointerLeaveEvent>(evt => { if (_hoveredSubVolume == key) _hoveredSubVolume = null; });
|
||||
ring.pickingMode = PickingMode.Position; // Allow hover detection
|
||||
|
||||
_subRings[key] = (ring, ring.Q<Label>());
|
||||
_volumeContainer.Add(ring);
|
||||
}
|
||||
}
|
||||
|
||||
private VisualElement CreateRing(string text, float size, bool cyan)
|
||||
{
|
||||
var ring = new VisualElement();
|
||||
ring.style.width = size;
|
||||
ring.style.height = size;
|
||||
ring.style.backgroundColor = new Color(0, 0, 0, 0.85f);
|
||||
ring.style.borderTopLeftRadius = size / 2;
|
||||
ring.style.borderTopRightRadius = size / 2;
|
||||
ring.style.borderBottomLeftRadius = size / 2;
|
||||
ring.style.borderBottomRightRadius = size / 2;
|
||||
ring.style.borderTopWidth = 3;
|
||||
ring.style.borderBottomWidth = 3;
|
||||
ring.style.borderLeftWidth = 3;
|
||||
ring.style.borderRightWidth = 3;
|
||||
ring.style.borderTopColor = ring.style.borderBottomColor = ring.style.borderLeftColor = ring.style.borderRightColor = cyan ? Color.cyan : new Color(0.7f, 0.7f, 0.7f);
|
||||
ring.style.justifyContent = Justify.Center;
|
||||
ring.style.alignItems = Align.Center;
|
||||
ring.style.position = Position.Absolute;
|
||||
|
||||
var label = new Label("80%");
|
||||
label.style.color = Color.white;
|
||||
label.style.fontSize = size * 0.25f;
|
||||
label.style.unityFontStyleAndWeight = FontStyle.Bold;
|
||||
ring.Add(label);
|
||||
|
||||
var title = new Label(text);
|
||||
title.style.color = Color.gray;
|
||||
title.style.fontSize = size * 0.15f;
|
||||
title.style.position = Position.Absolute;
|
||||
title.style.bottom = size * 0.15f;
|
||||
ring.Add(title);
|
||||
|
||||
return ring;
|
||||
}
|
||||
|
||||
private void OnMouseWheel(WheelEvent evt)
|
||||
{
|
||||
// Osu style: Volume control with scroll wheel
|
||||
// Only apply if in the SOUND tab
|
||||
if (_activeTab != "SOUND") return;
|
||||
// Debug Log to see if event is even reaching here
|
||||
Debug.Log($"[SettingsController] Mouse Wheel Detected. Settings Open: {uiManager.IsSettingsOpen}");
|
||||
|
||||
if (_hoveredSlider != null)
|
||||
// Do not control volume if we are in MainMenu (unless Settings is explicitly open)
|
||||
// Fix: Check if MainMenu is actually visible (Flex), not just exists in hierarchy
|
||||
var mainMenuRoot = uiManager.Root.Q<VisualElement>("MainMenuRoot");
|
||||
bool isMainMenuVisible = mainMenuRoot != null && mainMenuRoot.style.display == DisplayStyle.Flex;
|
||||
|
||||
if (!uiManager.IsSettingsOpen && isMainMenuVisible)
|
||||
{
|
||||
// Adjust the hovered slider's value
|
||||
Debug.Log("[SettingsController] Volume control suppressed: Currently at Main Menu.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Osu style volume control
|
||||
_overlayActiveCount++;
|
||||
ShowVolumeOverlay();
|
||||
// ... rest of method unchanged
|
||||
|
||||
if (_hoveredSubVolume != null)
|
||||
{
|
||||
Debug.Log($"[SettingsController] Adjusting Sub Volume: {_hoveredSubVolume}");
|
||||
UpdateSubVolume(_hoveredSubVolume, -evt.delta.y * 2f);
|
||||
}
|
||||
else if (_hoveredSlider != null && _activeTab == "SOUND")
|
||||
{
|
||||
// If hovering a slider in the Sound tab, adjust that
|
||||
float currentVal = _hoveredSlider.value;
|
||||
// Determine step size: default to 1% of range, adjusted for 0-100 range.
|
||||
float step = (_sliderMax - _sliderMin) / 100f;
|
||||
float newVal = Mathf.Clamp(currentVal - (evt.delta.y * step * 5f), _sliderMin, _sliderMax); // Multiply by a factor to make scroll smoother
|
||||
|
||||
float newVal = Mathf.Clamp(currentVal - (evt.delta.y * step * 5f), _sliderMin, _sliderMax);
|
||||
_hoveredSlider.value = newVal;
|
||||
|
||||
// Trigger the associated OnValueChanged callback to save PlayerPrefs etc.
|
||||
_hoveredOnChanged?.Invoke(newVal);
|
||||
|
||||
evt.StopPropagation(); // Consume the event so it doesn't affect other elements
|
||||
}
|
||||
else
|
||||
{
|
||||
// If not hovering a specific slider, control Master Volume
|
||||
Debug.Log("[SettingsController] Adjusting Master Volume.");
|
||||
UpdateMasterVolume(-evt.delta.y * 2f);
|
||||
evt.StopPropagation(); // Consume the event
|
||||
}
|
||||
|
||||
evt.StopPropagation();
|
||||
}
|
||||
|
||||
private void UpdateMasterVolume(float delta)
|
||||
{
|
||||
_masterVol = Mathf.Clamp(_masterVol + delta, 0f, 100f);
|
||||
PlayerPrefs.SetFloat("MasterVolume", _masterVol);
|
||||
|
||||
AudioManager.Instance?.SetVolume("MasterVolume", _masterVol);
|
||||
_masterVolLabel.text = $"{Mathf.RoundToInt(_masterVol)}%";
|
||||
ShowVolumeOverlay();
|
||||
|
||||
// Refresh Sound Tab UI if visible
|
||||
if (_activeTab == "SOUND") SwitchTab("SOUND");
|
||||
}
|
||||
|
||||
private void UpdateSubVolume(string key, float delta)
|
||||
{
|
||||
float current = PlayerPrefs.GetFloat(key, 80f);
|
||||
float newVal = Mathf.Clamp(current + delta, 0f, 100f);
|
||||
PlayerPrefs.SetFloat(key, newVal);
|
||||
AudioManager.Instance?.SetVolume(key, newVal);
|
||||
|
||||
if (_subRings.TryGetValue(key, out var data))
|
||||
data.label.text = $"{Mathf.RoundToInt(newVal)}%";
|
||||
|
||||
if (_activeTab == "SOUND") SwitchTab("SOUND");
|
||||
}
|
||||
|
||||
private async void ShowVolumeOverlay()
|
||||
{
|
||||
_volumeOverlay.style.display = DisplayStyle.Flex;
|
||||
_volumeOverlay.style.opacity = 1f;
|
||||
await Task.Delay(1500);
|
||||
if (_volumeOverlay.style.opacity == 1f)
|
||||
Debug.Log("[SettingsController] Showing Volume Overlay.");
|
||||
// Ensure overlay is on top of other UI screens
|
||||
_volumeContainer.BringToFront();
|
||||
|
||||
// CRITICAL: Ensure Virtual Cursor is ALWAYS on top of the volume rings
|
||||
uiManager.Root.Q<VisualElement>("CursorLayer")?.BringToFront();
|
||||
|
||||
_volumeContainer.style.display = DisplayStyle.Flex;
|
||||
_volumeContainer.style.opacity = 1f;
|
||||
|
||||
// Refresh all sub labels
|
||||
foreach (var kvp in _subRings)
|
||||
{
|
||||
Tween.Custom(1f, 0f, duration: 0.5f, onValueChange: val => _volumeOverlay.style.opacity = val)
|
||||
.OnComplete(() => _volumeOverlay.style.display = DisplayStyle.None);
|
||||
float val = PlayerPrefs.GetFloat(kvp.Key, 80f);
|
||||
kvp.Value.label.text = $"{Mathf.RoundToInt(val)}%";
|
||||
}
|
||||
_masterVolLabel.text = $"{Mathf.RoundToInt(_masterVol)}%";
|
||||
|
||||
int currentId = _overlayActiveCount;
|
||||
await Task.Delay(3000); // Wait 3s as requested
|
||||
|
||||
// Only fade out if no new scroll activity happened
|
||||
if (currentId == _overlayActiveCount && _hoveredSubVolume == null)
|
||||
{
|
||||
Debug.Log("[SettingsController] Fading out Volume Overlay.");
|
||||
Tween.Custom(1f, 0f, duration: 0.5f, onValueChange: val => _volumeContainer.style.opacity = val)
|
||||
.OnComplete(() => {
|
||||
if (_volumeContainer.style.opacity == 0f)
|
||||
_volumeContainer.style.display = DisplayStyle.None;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,13 +399,17 @@ namespace Hallucinate.UI
|
||||
|
||||
private VisualElement CreateAudioSlider(string label, string prefKey)
|
||||
{
|
||||
var sliderRow = CreateSliderWithInput(label, 0, 100, PlayerPrefs.GetFloat(prefKey, 80), val => PlayerPrefs.SetFloat(prefKey, val));
|
||||
var sliderRow = CreateSliderWithInput(label, 0, 100, PlayerPrefs.GetFloat(prefKey, 80), val => {
|
||||
PlayerPrefs.SetFloat(prefKey, val);
|
||||
AudioManager.Instance?.SetVolume(prefKey, val);
|
||||
});
|
||||
|
||||
// Register wheel specifically on this row
|
||||
sliderRow.RegisterCallback<WheelEvent>(evt => {
|
||||
float current = PlayerPrefs.GetFloat(prefKey, 80f);
|
||||
float newVal = Mathf.Clamp(current - (evt.delta.y * 2f), 0f, 100f);
|
||||
PlayerPrefs.SetFloat(prefKey, newVal);
|
||||
AudioManager.Instance?.SetVolume(prefKey, newVal);
|
||||
|
||||
// Visual update only (to avoid heavy re-render of whole list)
|
||||
var slider = sliderRow.Q<Slider>();
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace Hallucinate.UI
|
||||
|
||||
private UIDocument _uiDocument;
|
||||
private VisualElement _rootElement;
|
||||
public VisualElement Root => _rootElement;
|
||||
private VisualElement _cursorLayer;
|
||||
private VisualElement _mainCursor;
|
||||
|
||||
@@ -60,6 +61,7 @@ namespace Hallucinate.UI
|
||||
private Vector2 _lastMousePos;
|
||||
private float _trailOpacity = 1f;
|
||||
private bool _isSettingsOpen = false;
|
||||
public bool IsSettingsOpen => _isSettingsOpen;
|
||||
|
||||
private const string UI_SCALE_KEY = "UIScale";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user