Add subtitle UI for voice playback

This commit is contained in:
2026-05-16 21:33:00 +01:00
parent 58050abded
commit 3174079e37
81 changed files with 8657 additions and 1231 deletions

View File

@@ -45,7 +45,8 @@ namespace BriarQueen.Framework.Managers.Audio
private AudioSource _musicSourceB;
private AudioSource _voiceSource;
private string _activeVoiceSubtitleId;
private VoiceKey _activeVoiceKey = VoiceKey.None;
private SubtitleKey _activeSubtitleKey = SubtitleKey.None;
private AudioFileSo _currentMusicTrack;
private CancellationTokenSource _musicDuckCts;
@@ -140,7 +141,8 @@ namespace BriarQueen.Framework.Managers.Audio
_musicSourceB = null;
_voiceSource = null;
_currentMusicTrack = null;
_activeVoiceSubtitleId = null;
_activeVoiceKey = VoiceKey.None;
_activeSubtitleKey = SubtitleKey.None;
_voiceFinishedPublished = false;
Initialized = false;
}
@@ -205,8 +207,7 @@ namespace BriarQueen.Framework.Managers.Audio
if (_audioMixer == null || string.IsNullOrWhiteSpace(parameter))
return;
if (!_baseDb.TryGetValue(parameter, out var baseDb))
baseDb = 0f;
var baseDb = _baseDb.GetValueOrDefault(parameter, 0f);
var effective = baseDb;
@@ -340,10 +341,14 @@ namespace BriarQueen.Framework.Managers.Audio
_voiceCts = new CancellationTokenSource();
var token = _voiceCts.Token;
_activeVoiceSubtitleId = SubtitleIdentifiers.Get(audioData.MatchingSubtitleID);
_activeVoiceKey = audioData.VoiceKey;
_activeSubtitleKey = audioData.MatchingSubtitleID;
_voiceFinishedPublished = false;
_eventCoordinator.Publish(new VoiceLineStartedEvent(_activeVoiceSubtitleId));
_eventCoordinator.Publish(new VoicePlaybackStartedEvent(
_activeVoiceKey,
_activeSubtitleKey,
audioData.Clip.length));
_voiceSource.clip = audioData.Clip;
_voiceSource.pitch = audioData.Pitch;
@@ -369,11 +374,16 @@ namespace BriarQueen.Framework.Managers.Audio
{
if (_voiceFinishedPublished) return;
if (!string.IsNullOrEmpty(_activeVoiceSubtitleId))
_eventCoordinator.Publish(new VoiceLineFinishedEvent(_activeVoiceSubtitleId));
if (_activeVoiceKey != VoiceKey.None || _activeSubtitleKey != SubtitleKey.None)
{
_eventCoordinator.Publish(new VoicePlaybackFinishedEvent(
_activeVoiceKey,
_activeSubtitleKey));
}
_voiceFinishedPublished = true;
_activeVoiceSubtitleId = null;
_activeVoiceKey = VoiceKey.None;
_activeSubtitleKey = SubtitleKey.None;
}
public void StopVoice()
@@ -666,4 +676,4 @@ namespace BriarQueen.Framework.Managers.Audio
public float StartedAtUnscaled;
}
}
}
}

View File

@@ -449,13 +449,12 @@ namespace BriarQueen.Framework.Managers.Input
private void OnPause(InputAction.CallbackContext ctx)
{
var isMainMenu = _gameService != null && _gameService.IsMainMenuSceneLoaded;
if (isMainMenu || _isAnyUIOpen)
if (isMainMenu)
{
_eventCoordinator?.PublishImmediate(new UIBackRequestedEvent());
return;
}
_isPaused = true;
_eventCoordinator?.Publish(new PauseButtonClickedEvent());
}

View File

@@ -163,6 +163,22 @@ namespace BriarQueen.Framework.Managers.Interaction
Debug.Log($"[InteractManager] SetExclusiveRaycaster set to {raycaster.gameObject.name}.");
}
public void ReleaseExclusiveRaycaster(GraphicRaycaster raycaster)
{
if (raycaster == null)
return;
if (_exclusiveRaycaster != raycaster)
return;
_exclusiveRaycaster = null;
if (_currentHovered != null)
ClearHover().Forget();
Debug.Log($"[InteractManager] Released exclusive raycaster {raycaster.gameObject.name}.");
}
/// <summary>
/// Clear exclusive mode and return to using all registered raycasters.
/// </summary>
@@ -453,4 +469,4 @@ namespace BriarQueen.Framework.Managers.Interaction
_selectedItem = evt.Item;
}
}
}
}

View File

@@ -15,10 +15,15 @@ namespace BriarQueen.Framework.Managers.Player.Data.Codex
[Header("Codex")]
[SerializeField]
private CodexEntrySo _codexEntry;
[SerializeField]
private bool _removeTrigger;
[Header("Events")]
[SerializeField]
private SFXKey _soundEffect;
[SerializeField]
private VoiceKey _voiceLine;
public override UICursorService.CursorStyle ApplicableCursorStyle => UICursorService.CursorStyle.Inspect;
public override string InteractableName =>
@@ -40,12 +45,23 @@ namespace BriarQueen.Framework.Managers.Player.Data.Codex
return;
}
PlayerManager.UnlockCodexEntry(_codexEntry);
if (_removeTrigger)
{
await Remove();
}
if (_soundEffect != SFXKey.None)
{
AudioManager.Play(AudioNameIdentifiers.Get(_soundEffect));
}
if (_voiceLine != VoiceKey.None)
{
AudioManager.Play(AudioNameIdentifiers.Get(_voiceLine));
}
}
protected override void UpdateSaveGameOnRemoval()

View File

@@ -4,6 +4,7 @@ namespace BriarQueen.Framework.Managers.UI.Base
{
public interface IUIOverlayHost
{
bool CanSuspendFor(WindowType incomingWindowType);
UniTask SuspendForOverlay();
UniTask ResumeFromOverlay();
}

View File

@@ -1,16 +1,19 @@
using System.Threading;
using Cysharp.Threading.Tasks;
using PrimeTween;
using UnityEngine;
namespace BriarQueen.Framework.Managers.UI.Base
{
public enum UIPauseBehavior
{
TreatAsBackRequest,
OpenPauseOverlay
}
public interface IUIWindow
{
UniTask Show();
UniTask Hide();
WindowType WindowType { get; }
UIPauseBehavior PauseBehavior { get; }
}
}
}

View File

@@ -4,6 +4,7 @@ namespace BriarQueen.Framework.Managers.UI.Base
{
PauseMenuWindow,
SettingsWindow,
CodexWindow
CodexWindow,
AshwickGateKeypadWindow
}
}
}

View File

@@ -41,12 +41,14 @@ namespace BriarQueen.Framework.Managers.UI
public bool Initialized { get; private set; }
private sealed record OverlayResumeContext(WindowType OverlayWindowType, IUIOverlayHost Host);
private IHud _hudContainer;
private IPopup _infoPopup;
private IPopup _tutorialPopup;
private IScreenFader _screenFader;
private IUIOverlayHost _mainMenuOverlayHost;
private IUIOverlayHost _activeSettingsOverlayHost;
private readonly Stack<OverlayResumeContext> _overlayResumeStack = new();
[Inject]
public UIManager(
@@ -127,6 +129,18 @@ namespace BriarQueen.Framework.Managers.UI
window.Hide().Forget();
}
public void UnregisterWindow(IUIWindow window)
{
if (window == null)
return;
if (_windows.TryGetValue(window.WindowType, out var registered) && ReferenceEquals(registered, window))
_windows.Remove(window.WindowType);
if (window is IUIOverlayHost overlayHost)
RemoveOverlayResumeContextsForHost(overlayHost);
}
public void RegisterHUD(IHud hudContainer)
{
_hudContainer = hudContainer;
@@ -169,9 +183,7 @@ namespace BriarQueen.Framework.Managers.UI
if (!ReferenceEquals(_mainMenuOverlayHost, host))
return;
if (ReferenceEquals(_activeSettingsOverlayHost, host))
_activeSettingsOverlayHost = null;
RemoveOverlayResumeContextsForHost(host);
_mainMenuOverlayHost = null;
}
@@ -186,6 +198,51 @@ namespace BriarQueen.Framework.Managers.UI
return target != null && _windowStack.Contains(target);
}
private void RemoveOverlayResumeContextsForHost(IUIOverlayHost host)
{
if (host == null || _overlayResumeStack.Count == 0)
return;
var contextsToKeep = new List<OverlayResumeContext>();
foreach (var context in _overlayResumeStack)
{
if (!ReferenceEquals(context.Host, host))
contextsToKeep.Add(context);
}
_overlayResumeStack.Clear();
for (var i = contextsToKeep.Count - 1; i >= 0; i--)
_overlayResumeStack.Push(contextsToKeep[i]);
}
private async UniTask<bool> TrySuspendActiveWindowFor(WindowType incomingWindowType)
{
if (ActiveWindow is IUIOverlayHost overlayHost &&
overlayHost.CanSuspendFor(incomingWindowType))
{
await overlayHost.SuspendForOverlay();
_overlayResumeStack.Push(new OverlayResumeContext(incomingWindowType, overlayHost));
return true;
}
return false;
}
private async UniTask RestoreUnderlyingUi(WindowType closedWindowType)
{
if (_overlayResumeStack.Count > 0 &&
_overlayResumeStack.Peek().OverlayWindowType == closedWindowType)
{
var resumeContext = _overlayResumeStack.Pop();
await resumeContext.Host.ResumeFromOverlay();
return;
}
if (ActiveWindow != null)
await ActiveWindow.Show();
}
private async UniTask ApplyHudVisibility(bool visible)
{
if (_disposed || _hudContainer == null)
@@ -206,13 +263,25 @@ namespace BriarQueen.Framework.Managers.UI
private void OnPauseClickReceived(PauseButtonClickedEvent _)
{
if (_windowStack.Count > 0)
if (ActiveWindow == null)
{
OpenWindow(WindowType.PauseMenuWindow);
return;
}
if (ActiveWindow.WindowType == WindowType.PauseMenuWindow)
{
TryHandleBackRequest();
return;
}
OpenWindow(WindowType.PauseMenuWindow);
if (ActiveWindow.PauseBehavior == UIPauseBehavior.OpenPauseOverlay)
{
OpenWindow(WindowType.PauseMenuWindow);
return;
}
TryHandleBackRequest();
}
private void OnBackRequested(UIBackRequestedEvent _)
@@ -350,28 +419,22 @@ namespace BriarQueen.Framework.Managers.UI
if (_windowStack.Contains(window))
return;
_activeSettingsOverlayHost = null;
var suspended = false;
var openingSettingsOverPause =
source == SettingsOpenSource.PauseMenu &&
ActiveWindow?.WindowType == WindowType.PauseMenuWindow &&
ActiveWindow is IUIOverlayHost;
var openingSettingsOverMainMenu =
source == SettingsOpenSource.MainMenu &&
_mainMenuOverlayHost != null;
if (openingSettingsOverPause)
if (source == SettingsOpenSource.MainMenu &&
_mainMenuOverlayHost != null &&
_mainMenuOverlayHost.CanSuspendFor(WindowType.SettingsWindow))
{
_activeSettingsOverlayHost = (IUIOverlayHost)ActiveWindow;
await _activeSettingsOverlayHost.SuspendForOverlay();
await _mainMenuOverlayHost.SuspendForOverlay();
_overlayResumeStack.Push(new OverlayResumeContext(WindowType.SettingsWindow, _mainMenuOverlayHost));
suspended = true;
}
else if (openingSettingsOverMainMenu)
else
{
_activeSettingsOverlayHost = _mainMenuOverlayHost;
await _activeSettingsOverlayHost.SuspendForOverlay();
suspended = await TrySuspendActiveWindowFor(WindowType.SettingsWindow);
}
else if (ActiveWindow != null)
if (!suspended && ActiveWindow != null)
{
await ActiveWindow.Hide();
}
@@ -407,7 +470,9 @@ namespace BriarQueen.Framework.Managers.UI
if (_windowStack.Contains(window))
return;
if (ActiveWindow != null)
var suspended = await TrySuspendActiveWindowFor(windowType);
if (!suspended && ActiveWindow != null)
await ActiveWindow.Hide();
_windowStack.Push(window);
@@ -452,15 +517,7 @@ namespace BriarQueen.Framework.Managers.UI
break;
}
if (target.WindowType == WindowType.SettingsWindow && _activeSettingsOverlayHost != null)
{
await _activeSettingsOverlayHost.ResumeFromOverlay();
_activeSettingsOverlayHost = null;
}
else if (ActiveWindow != null)
{
await ActiveWindow.Show();
}
await RestoreUnderlyingUi(target.WindowType);
NotifyUIStackChanged();
}
@@ -506,17 +563,8 @@ namespace BriarQueen.Framework.Managers.UI
NotifyWindowStateChanged(top.WindowType, false);
}
if (top != null &&
top.WindowType == WindowType.SettingsWindow &&
_activeSettingsOverlayHost != null)
{
await _activeSettingsOverlayHost.ResumeFromOverlay();
_activeSettingsOverlayHost = null;
}
else if (ActiveWindow != null)
{
await ActiveWindow.Show();
}
if (top != null)
await RestoreUnderlyingUi(top.WindowType);
NotifyUIStackChanged();
}
@@ -536,17 +584,12 @@ namespace BriarQueen.Framework.Managers.UI
await _windowTransitionGate.WaitAsync();
try
{
var shouldResumeSettingsHost = false;
while (_windowStack.Count > 0)
{
var window = _windowStack.Pop();
if (window == null)
continue;
if (window.WindowType == WindowType.SettingsWindow && _activeSettingsOverlayHost != null)
shouldResumeSettingsHost = true;
try
{
await window.Hide();
@@ -557,18 +600,7 @@ namespace BriarQueen.Framework.Managers.UI
}
}
if (shouldResumeSettingsHost)
{
try
{
await _activeSettingsOverlayHost.ResumeFromOverlay();
}
catch
{
}
}
_activeSettingsOverlayHost = null;
_overlayResumeStack.Clear();
if (_tutorialPopup != null)
{
@@ -621,7 +653,7 @@ namespace BriarQueen.Framework.Managers.UI
faderComponent.gameObject.SetActive(false);
_windowStack.Clear();
_activeSettingsOverlayHost = null;
_overlayResumeStack.Clear();
_mainMenuOverlayHost = null;
}