Add subtitle UI for voice playback
This commit is contained in:
@@ -255,6 +255,7 @@ namespace BriarQueen.UI.Codex
|
||||
}
|
||||
|
||||
public WindowType WindowType => WindowType.CodexWindow;
|
||||
public UIPauseBehavior PauseBehavior => UIPauseBehavior.TreatAsBackRequest;
|
||||
|
||||
// ── IUIWindow ─────────────────────────────────────────────────
|
||||
|
||||
@@ -263,7 +264,7 @@ namespace BriarQueen.UI.Codex
|
||||
ResetOperationCts();
|
||||
|
||||
gameObject.SetActive(true);
|
||||
TryRegisterRaycaster();
|
||||
EnsureExclusiveRaycaster();
|
||||
|
||||
if (_canvasGroup != null)
|
||||
{
|
||||
@@ -380,7 +381,7 @@ namespace BriarQueen.UI.Codex
|
||||
|
||||
// ── Raycaster ─────────────────────────────────────────────────
|
||||
|
||||
private void TryRegisterRaycaster()
|
||||
private void EnsureExclusiveRaycaster()
|
||||
{
|
||||
Debug.Log($"[CodexWindow] TryRegisterRaycaster " +
|
||||
|
||||
@@ -392,10 +393,15 @@ namespace BriarQueen.UI.Codex
|
||||
|
||||
Debug.Log("[CodexWindow] Try register raycaster.");
|
||||
|
||||
if (_raycasterRegistered || _interactManager == null || _graphicRaycaster == null) return;
|
||||
_interactManager.AddUIRaycaster(_graphicRaycaster);
|
||||
if (_interactManager == null || _graphicRaycaster == null) return;
|
||||
|
||||
if (!_raycasterRegistered)
|
||||
{
|
||||
_interactManager.AddUIRaycaster(_graphicRaycaster);
|
||||
_raycasterRegistered = true;
|
||||
}
|
||||
|
||||
_interactManager.SetExclusiveRaycaster(_graphicRaycaster);
|
||||
_raycasterRegistered = true;
|
||||
|
||||
Debug.Log("[CodexWindow] Registered raycaster.");
|
||||
}
|
||||
@@ -406,7 +412,7 @@ namespace BriarQueen.UI.Codex
|
||||
|
||||
if (!_raycasterRegistered || _interactManager == null || _graphicRaycaster == null) return;
|
||||
_interactManager.RemoveUIRaycaster(_graphicRaycaster);
|
||||
_interactManager.ClearExclusiveRaycaster();
|
||||
_interactManager.ReleaseExclusiveRaycaster(_graphicRaycaster);
|
||||
_raycasterRegistered = false;
|
||||
|
||||
Debug.Log("[CodexWindow] Raycaster unregistered.");
|
||||
|
||||
212
Assets/Scripts/UI/HUD/SubtitleUI.cs
Normal file
212
Assets/Scripts/UI/HUD/SubtitleUI.cs
Normal file
@@ -0,0 +1,212 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using BriarQueen.Framework.Coordinators.Events;
|
||||
using BriarQueen.Framework.Events.UI;
|
||||
using BriarQueen.Framework.Services.Subtitles;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using PrimeTween;
|
||||
using UnityEngine;
|
||||
using VContainer;
|
||||
|
||||
namespace BriarQueen.UI.HUD
|
||||
{
|
||||
public class SubtitleUI : MonoBehaviour
|
||||
{
|
||||
[Header("UI")]
|
||||
[SerializeField]
|
||||
private InteractTextUI _interactTextUI;
|
||||
|
||||
[SerializeField]
|
||||
private CanvasGroup _canvasGroup;
|
||||
|
||||
[Header("Tweens")]
|
||||
[SerializeField]
|
||||
private TweenSettings _tweenSettings = new()
|
||||
{
|
||||
duration = 0.2f,
|
||||
ease = Ease.InOutSine,
|
||||
useUnscaledTime = true
|
||||
};
|
||||
|
||||
private EventCoordinator _eventCoordinator;
|
||||
private SubtitleService _subtitleService;
|
||||
private CancellationTokenSource _cancellationTokenSource;
|
||||
private Sequence _sequence;
|
||||
|
||||
[Inject]
|
||||
public void Construct(EventCoordinator eventCoordinator, SubtitleService subtitleService)
|
||||
{
|
||||
_eventCoordinator = eventCoordinator;
|
||||
_subtitleService = subtitleService;
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
ApplyImmediate(string.Empty, false);
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
_eventCoordinator?.Subscribe<SubtitleDisplayChangedEvent>(OnSubtitleDisplayChanged);
|
||||
SyncFromService();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
_eventCoordinator?.Unsubscribe<SubtitleDisplayChangedEvent>(OnSubtitleDisplayChanged);
|
||||
StopTween();
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
StopTween();
|
||||
}
|
||||
|
||||
private void SyncFromService()
|
||||
{
|
||||
if (_subtitleService != null && _subtitleService.IsVisible)
|
||||
{
|
||||
ApplyImmediate(_subtitleService.CurrentText, true);
|
||||
return;
|
||||
}
|
||||
|
||||
ApplyImmediate(string.Empty, false);
|
||||
}
|
||||
|
||||
private void OnSubtitleDisplayChanged(SubtitleDisplayChangedEvent eventData)
|
||||
{
|
||||
if (!eventData.Visible || string.IsNullOrWhiteSpace(eventData.Text))
|
||||
{
|
||||
HideSubtitle().Forget();
|
||||
return;
|
||||
}
|
||||
|
||||
ShowSubtitle(eventData.Text).Forget();
|
||||
}
|
||||
|
||||
private async UniTaskVoid ShowSubtitle(string text)
|
||||
{
|
||||
if (_canvasGroup == null || _interactTextUI == null)
|
||||
return;
|
||||
|
||||
StopTween();
|
||||
|
||||
_interactTextUI.SetText(text);
|
||||
_canvasGroup.blocksRaycasts = false;
|
||||
_canvasGroup.interactable = false;
|
||||
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
var tween = new TweenSettings<float>
|
||||
{
|
||||
startValue = _canvasGroup.alpha,
|
||||
endValue = 1f,
|
||||
settings = _tweenSettings
|
||||
};
|
||||
|
||||
_sequence = Sequence.Create(useUnscaledTime: true)
|
||||
.Group(Tween.Alpha(_canvasGroup, tween));
|
||||
|
||||
try
|
||||
{
|
||||
await _sequence.ToUniTask(cancellationToken: _cancellationTokenSource.Token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
finally
|
||||
{
|
||||
DisposeTweenState();
|
||||
}
|
||||
|
||||
_canvasGroup.alpha = 1f;
|
||||
}
|
||||
|
||||
private async UniTaskVoid HideSubtitle()
|
||||
{
|
||||
if (_canvasGroup == null || _interactTextUI == null)
|
||||
return;
|
||||
|
||||
StopTween();
|
||||
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
var tween = new TweenSettings<float>
|
||||
{
|
||||
startValue = _canvasGroup.alpha,
|
||||
endValue = 0f,
|
||||
settings = _tweenSettings
|
||||
};
|
||||
|
||||
_sequence = Sequence.Create(useUnscaledTime: true)
|
||||
.Group(Tween.Alpha(_canvasGroup, tween));
|
||||
|
||||
try
|
||||
{
|
||||
await _sequence.ToUniTask(cancellationToken: _cancellationTokenSource.Token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
finally
|
||||
{
|
||||
DisposeTweenState();
|
||||
}
|
||||
|
||||
ApplyImmediate(string.Empty, false);
|
||||
}
|
||||
|
||||
private void ApplyImmediate(string text, bool visible)
|
||||
{
|
||||
if (_interactTextUI != null)
|
||||
{
|
||||
if (visible)
|
||||
_interactTextUI.SetText(text);
|
||||
else
|
||||
_interactTextUI.ClearText();
|
||||
}
|
||||
|
||||
if (_canvasGroup == null)
|
||||
return;
|
||||
|
||||
_canvasGroup.alpha = visible ? 1f : 0f;
|
||||
_canvasGroup.blocksRaycasts = false;
|
||||
_canvasGroup.interactable = false;
|
||||
}
|
||||
|
||||
private void StopTween()
|
||||
{
|
||||
if (_sequence.isAlive)
|
||||
_sequence.Stop();
|
||||
|
||||
_sequence = default;
|
||||
|
||||
if (_cancellationTokenSource == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
_cancellationTokenSource.Cancel();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
_cancellationTokenSource.Dispose();
|
||||
_cancellationTokenSource = null;
|
||||
}
|
||||
|
||||
private void DisposeTweenState()
|
||||
{
|
||||
_sequence = default;
|
||||
|
||||
if (_cancellationTokenSource == null)
|
||||
return;
|
||||
|
||||
_cancellationTokenSource.Dispose();
|
||||
_cancellationTokenSource = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UI/HUD/SubtitleUI.cs.meta
Normal file
2
Assets/Scripts/UI/HUD/SubtitleUI.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a7225bc6a3f524fb8b820162cd937c1e
|
||||
@@ -635,6 +635,11 @@ namespace BriarQueen.UI.Menus
|
||||
group.blocksRaycasts = inputEnabled;
|
||||
}
|
||||
|
||||
public bool CanSuspendFor(WindowType incomingWindowType)
|
||||
{
|
||||
return incomingWindowType == WindowType.SettingsWindow;
|
||||
}
|
||||
|
||||
public async UniTask SuspendForOverlay()
|
||||
{
|
||||
if (_mainMenuGroup == null)
|
||||
|
||||
@@ -79,18 +79,25 @@ namespace BriarQueen.UI.Menus
|
||||
|
||||
public bool IsModal => true;
|
||||
public WindowType WindowType => WindowType.PauseMenuWindow;
|
||||
|
||||
private void TryRegisterRaycaster()
|
||||
{
|
||||
if (_raycasterRegistered)
|
||||
return;
|
||||
public UIPauseBehavior PauseBehavior => UIPauseBehavior.TreatAsBackRequest;
|
||||
|
||||
public bool CanSuspendFor(WindowType incomingWindowType)
|
||||
{
|
||||
return incomingWindowType == WindowType.SettingsWindow;
|
||||
}
|
||||
|
||||
private void EnsureExclusiveRaycaster()
|
||||
{
|
||||
if (_interactManager == null || _graphicRaycaster == null)
|
||||
return;
|
||||
|
||||
_interactManager.AddUIRaycaster(_graphicRaycaster);
|
||||
if (!_raycasterRegistered)
|
||||
{
|
||||
_interactManager.AddUIRaycaster(_graphicRaycaster);
|
||||
_raycasterRegistered = true;
|
||||
}
|
||||
|
||||
_interactManager.SetExclusiveRaycaster(_graphicRaycaster);
|
||||
_raycasterRegistered = true;
|
||||
}
|
||||
|
||||
private void TryUnregisterRaycaster()
|
||||
@@ -102,7 +109,7 @@ namespace BriarQueen.UI.Menus
|
||||
return;
|
||||
|
||||
_interactManager.RemoveUIRaycaster(_graphicRaycaster);
|
||||
_interactManager.ClearExclusiveRaycaster();
|
||||
_interactManager.ReleaseExclusiveRaycaster(_graphicRaycaster);
|
||||
_raycasterRegistered = false;
|
||||
}
|
||||
|
||||
@@ -149,7 +156,7 @@ namespace BriarQueen.UI.Menus
|
||||
SetLevelName();
|
||||
|
||||
gameObject.SetActive(true);
|
||||
TryRegisterRaycaster();
|
||||
EnsureExclusiveRaycaster();
|
||||
|
||||
_canvasGroup.blocksRaycasts = false;
|
||||
_canvasGroup.interactable = false;
|
||||
@@ -233,6 +240,7 @@ namespace BriarQueen.UI.Menus
|
||||
public async UniTask ResumeFromOverlay()
|
||||
{
|
||||
StopAndResetCancellation();
|
||||
EnsureExclusiveRaycaster();
|
||||
|
||||
_buttonsGroup.blocksRaycasts = true;
|
||||
_buttonsGroup.interactable = true;
|
||||
|
||||
@@ -147,6 +147,7 @@ namespace BriarQueen.UI.Menus
|
||||
// ── IUIWindow ─────────────────────────────────────────────────
|
||||
public bool IsModal => true;
|
||||
public WindowType WindowType => WindowType.SettingsWindow;
|
||||
public UIPauseBehavior PauseBehavior => UIPauseBehavior.TreatAsBackRequest;
|
||||
|
||||
// ── Unity lifecycle ───────────────────────────────────────────
|
||||
private void Awake()
|
||||
@@ -155,9 +156,9 @@ namespace BriarQueen.UI.Menus
|
||||
if (_backButton != null) _backButton.onClick.AddListener(OnBackClicked);
|
||||
|
||||
// Individual buttons drive panel switching via SelectionRequested
|
||||
if (_gameCategoryButton != null) _gameCategoryButton.SelectionRequested += _ => SwitchCategory(Category.Game);
|
||||
if (_visualCategoryButton != null) _visualCategoryButton.SelectionRequested += _ => SwitchCategory(Category.Visual);
|
||||
if (_audioCategoryButton != null) _audioCategoryButton.SelectionRequested += _ => SwitchCategory(Category.Audio);
|
||||
if (_gameCategoryButton != null) _gameCategoryButton.SelectionRequested += OnGameCategorySelected;
|
||||
if (_visualCategoryButton != null) _visualCategoryButton.SelectionRequested += OnVisualCategorySelected;
|
||||
if (_audioCategoryButton != null) _audioCategoryButton.SelectionRequested += OnAudioCategorySelected;
|
||||
|
||||
HookSlider(_masterVolumeSlider, _masterVolumeText, v => _draftAudio.MasterVolume = v);
|
||||
HookSlider(_musicVolumeSlider, _musicVolumeText, v => _draftAudio.MusicVolume = v);
|
||||
@@ -210,9 +211,9 @@ namespace BriarQueen.UI.Menus
|
||||
if (_applyButton != null) _applyButton.onClick.RemoveListener(OnApplyClicked);
|
||||
if (_backButton != null) _backButton.onClick.RemoveListener(OnBackClicked);
|
||||
|
||||
if (_gameCategoryButton != null) _gameCategoryButton.SelectionRequested -= _ => SwitchCategory(Category.Game);
|
||||
if (_visualCategoryButton != null) _visualCategoryButton.SelectionRequested -= _ => SwitchCategory(Category.Visual);
|
||||
if (_audioCategoryButton != null) _audioCategoryButton.SelectionRequested -= _ => SwitchCategory(Category.Audio);
|
||||
if (_gameCategoryButton != null) _gameCategoryButton.SelectionRequested -= OnGameCategorySelected;
|
||||
if (_visualCategoryButton != null) _visualCategoryButton.SelectionRequested -= OnVisualCategorySelected;
|
||||
if (_audioCategoryButton != null) _audioCategoryButton.SelectionRequested -= OnAudioCategorySelected;
|
||||
|
||||
if (_popupDisplayDurationSlider != null)
|
||||
_popupDisplayDurationSlider.onValueChanged.RemoveListener(OnPopupDisplayDurationChanged);
|
||||
@@ -246,7 +247,7 @@ namespace BriarQueen.UI.Menus
|
||||
StopAndResetCancellation();
|
||||
|
||||
gameObject.SetActive(true);
|
||||
TryRegisterRaycaster();
|
||||
EnsureExclusiveRaycaster();
|
||||
|
||||
_canvasGroup.alpha = 1f;
|
||||
_canvasGroup.blocksRaycasts = false;
|
||||
@@ -368,6 +369,21 @@ namespace BriarQueen.UI.Menus
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnGameCategorySelected(AnimatedSelectionButton _)
|
||||
{
|
||||
SwitchCategory(Category.Game);
|
||||
}
|
||||
|
||||
private void OnVisualCategorySelected(AnimatedSelectionButton _)
|
||||
{
|
||||
SwitchCategory(Category.Visual);
|
||||
}
|
||||
|
||||
private void OnAudioCategorySelected(AnimatedSelectionButton _)
|
||||
{
|
||||
SwitchCategory(Category.Audio);
|
||||
}
|
||||
|
||||
// ── Category switching ────────────────────────────────────────
|
||||
|
||||
private void SwitchCategory(Category category)
|
||||
@@ -909,17 +925,18 @@ namespace BriarQueen.UI.Menus
|
||||
};
|
||||
}
|
||||
|
||||
private void TryRegisterRaycaster()
|
||||
private void EnsureExclusiveRaycaster()
|
||||
{
|
||||
if (_raycasterRegistered)
|
||||
return;
|
||||
|
||||
if (_interactManager == null || _graphicRaycaster == null)
|
||||
return;
|
||||
|
||||
_interactManager.AddUIRaycaster(_graphicRaycaster);
|
||||
if (!_raycasterRegistered)
|
||||
{
|
||||
_interactManager.AddUIRaycaster(_graphicRaycaster);
|
||||
_raycasterRegistered = true;
|
||||
}
|
||||
|
||||
_interactManager.SetExclusiveRaycaster(_graphicRaycaster);
|
||||
_raycasterRegistered = true;
|
||||
}
|
||||
|
||||
private void TryUnregisterRaycaster()
|
||||
@@ -931,7 +948,7 @@ namespace BriarQueen.UI.Menus
|
||||
return;
|
||||
|
||||
_interactManager.RemoveUIRaycaster(_graphicRaycaster);
|
||||
_interactManager.ClearExclusiveRaycaster();
|
||||
_interactManager.ReleaseExclusiveRaycaster(_graphicRaycaster);
|
||||
_raycasterRegistered = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,9 @@ namespace BriarQueen.UI.Scopes
|
||||
|
||||
[SerializeField]
|
||||
private InventoryBar _inventoryBar;
|
||||
|
||||
[SerializeField]
|
||||
private SubtitleUI _subtitleUI;
|
||||
|
||||
|
||||
protected override void Configure(IContainerBuilder builder)
|
||||
@@ -70,6 +73,9 @@ namespace BriarQueen.UI.Scopes
|
||||
|
||||
if (_inventoryBar != null)
|
||||
builder.RegisterComponent(_inventoryBar);
|
||||
|
||||
if (_subtitleUI != null)
|
||||
builder.RegisterComponent(_subtitleUI);
|
||||
|
||||
|
||||
builder.RegisterBuildCallback(container =>
|
||||
@@ -101,4 +107,4 @@ namespace BriarQueen.UI.Scopes
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user