First commit for private source control. Older commits available on Github.

This commit is contained in:
2026-03-26 12:52:52 +00:00
parent a04c602626
commit 2d449c4a17
2176 changed files with 408185 additions and 0 deletions

Binary file not shown.

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: cdeb86849b3447b08f904cbe2af60572
timeCreated: 1773830601

View File

@@ -0,0 +1,15 @@
using Cysharp.Threading.Tasks;
using UnityEngine.UI;
namespace BriarQueen.Framework.Managers.UI.Base
{
public interface IHud
{
UniTask Hide();
UniTask Show();
UniTask DisplayInteractText(string text);
GraphicRaycaster Raycaster { get; }
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 69ce1a09c8ba468184443377cb160251
timeCreated: 1773830812

View File

@@ -0,0 +1,15 @@
using Cysharp.Threading.Tasks;
using UnityEngine;
namespace BriarQueen.Framework.Managers.UI.Base
{
public interface IPopup
{
UniTask Show();
UniTask Hide();
UniTask Play(string text, float duration);
GameObject GameObject { get; }
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b6c40bdc6e55460e859a7d0d76422e5b
timeCreated: 1773830634

View File

@@ -0,0 +1,12 @@
using BriarQueen.Framework.Events.UI;
using Cysharp.Threading.Tasks;
using UnityEngine;
namespace BriarQueen.Framework.Managers.UI.Base
{
public interface IScreenFader
{
UniTask FadeFromAsync(float duration);
UniTask FadeToAsync(float duration);
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 486cc0093cdf41db9db9d4a4af124a6f
timeCreated: 1773831975

View File

@@ -0,0 +1,12 @@
using Cysharp.Threading.Tasks;
namespace BriarQueen.Framework.Managers.UI.Base
{
public interface IUIWindow
{
UniTask Show();
UniTask Hide();
WindowType WindowType { get; }
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 27aca68a2b694025b3ed9c26b2d7d5b2
timeCreated: 1769707713

View File

@@ -0,0 +1,9 @@
namespace BriarQueen.Framework.Managers.UI.Base
{
public enum WindowType
{
PauseMenuWindow,
SettingsWindow,
CodexWindow
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 616cfa2de3124c308c9706f50b7ab339
timeCreated: 1773832262

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 75c1e9dba297a499998f45e608b4b71a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

View File

@@ -0,0 +1,6 @@
using BriarQueen.Framework.Events.System;
namespace BriarQueen.Framework.Managers.UI.Events
{
public record UIToggleSettingsWindow(bool Show) : IEvent;
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: cf239878f7aa941c2abbef0ebb2e41bd

View File

@@ -0,0 +1,285 @@
using System;
using System.Collections.Generic;
using BriarQueen.Framework.Coordinators.Events;
using BriarQueen.Framework.Events.UI;
using UnityEngine;
using UnityEngine.UI;
using VContainer;
namespace BriarQueen.Framework.Managers.UI
{
public sealed class UICursorService : MonoBehaviour
{
public enum CursorStyle
{
Default,
Interact,
Talk,
Pickup,
Inspect,
UseItem,
Travel,
Knife,
}
[Header("System Cursor")]
[SerializeField]
private bool _enableCustomCursor = true;
[SerializeField]
private bool _restoreDefaultCursorOnDisable = true;
[Header("Software Cursor")]
[SerializeField]
private RectTransform _virtualCursorTransform;
[SerializeField]
private Image _virtualCursorImage;
[Header("Styles")]
[SerializeField]
private CursorStyle _startingStyle = CursorStyle.Default;
[SerializeField]
private List<CursorStyleEntry> _styles = new();
private readonly Dictionary<CursorStyle, CursorStyleEntry> _styleMap = new();
private EventCoordinator _eventCoordinator;
private CursorStyle _currentStyle;
private CursorStyle _currentStyleOverride = CursorStyle.Default;
private bool _isStyleOverridden;
private Texture2D _currentTexture;
private Vector2 _currentHotspot;
private CursorMode _currentMode;
private bool _useVirtualCursor;
public CursorStyleEntry CurrentStyleEntry => _styleMap[_currentStyle];
[Inject]
private void Construct(EventCoordinator eventCoordinator)
{
_eventCoordinator = eventCoordinator;
}
private void Awake()
{
BuildStyleMap();
_currentStyle = _startingStyle;
_currentStyleOverride = CursorStyle.Default;
_isStyleOverridden = false;
}
private void OnEnable()
{
_eventCoordinator?.Subscribe<CursorStyleChangeEvent>(OnCursorStyleChangeEvent);
_eventCoordinator?.Subscribe<OverrideCursorStyleChangeEvent>(OnOverrideCursorStyleChangeEvent);
ApplyCurrentEffectiveStyle();
ApplyCursorModeVisuals();
}
private void OnDisable()
{
_eventCoordinator?.Unsubscribe<CursorStyleChangeEvent>(OnCursorStyleChangeEvent);
_eventCoordinator?.Unsubscribe<OverrideCursorStyleChangeEvent>(OnOverrideCursorStyleChangeEvent);
if (_restoreDefaultCursorOnDisable)
Cursor.SetCursor(null, Vector2.zero, CursorMode.Auto);
}
private void OnValidate()
{
BuildStyleMap();
}
public void SetUseVirtualCursor(bool useVirtualCursor)
{
if (_useVirtualCursor == useVirtualCursor)
return;
_useVirtualCursor = useVirtualCursor;
ApplyCursorModeVisuals();
ApplyCurrentEffectiveStyle();
}
public void SetVirtualCursorPosition(Vector2 screenPosition)
{
if (!_useVirtualCursor || _virtualCursorTransform == null)
return;
_virtualCursorTransform.position = screenPosition;
}
public void SetStyle(CursorStyle style)
{
_currentStyle = style;
ApplyCurrentEffectiveStyle();
}
public void SetOverrideStyle(CursorStyle style)
{
if (style == CursorStyle.Default)
{
ClearOverrideStyle();
return;
}
_isStyleOverridden = true;
_currentStyleOverride = style;
ApplyCurrentEffectiveStyle();
}
public void ClearOverrideStyle()
{
_isStyleOverridden = false;
_currentStyleOverride = CursorStyle.Default;
ApplyCurrentEffectiveStyle();
}
public void ResetToDefault()
{
_currentStyle = CursorStyle.Default;
ClearOverrideStyle();
}
private void OnCursorStyleChangeEvent(CursorStyleChangeEvent evt)
{
_currentStyle = evt.Style;
ApplyCurrentEffectiveStyle();
}
private void OnOverrideCursorStyleChangeEvent(OverrideCursorStyleChangeEvent evt)
{
if (evt.Style == CursorStyle.Default)
{
ClearOverrideStyle();
return;
}
_isStyleOverridden = true;
_currentStyleOverride = evt.Style;
ApplyCurrentEffectiveStyle();
}
private void ApplyCursorModeVisuals()
{
Cursor.visible = !_useVirtualCursor;
if (_virtualCursorImage != null)
_virtualCursorImage.enabled = _useVirtualCursor;
}
private void ApplyCurrentEffectiveStyle()
{
var style = GetEffectiveStyle();
if (_useVirtualCursor)
{
ApplyVirtualCursorStyle(style);
}
else
{
ApplySystemCursorStyle(style);
}
}
private CursorStyle GetEffectiveStyle()
{
return _isStyleOverridden ? _currentStyleOverride : _currentStyle;
}
private void ApplyVirtualCursorStyle(CursorStyle style)
{
if (_virtualCursorImage == null)
return;
if (!_styleMap.TryGetValue(style, out var entry) || entry.Texture == null)
{
if (!_styleMap.TryGetValue(CursorStyle.Default, out entry) || entry.Texture == null)
return;
}
var rect = new Rect(0, 0, entry.Texture.width, entry.Texture.height);
var pivot = new Vector2(0.5f, 0.5f);
_virtualCursorImage.sprite = Sprite.Create(entry.Texture, rect, pivot);
}
private void ApplySystemCursorStyle(CursorStyle style)
{
if (!_enableCustomCursor)
return;
if (!_styleMap.TryGetValue(style, out var entry) || entry.Texture == null)
{
if (_styleMap.TryGetValue(CursorStyle.Default, out var defaultEntry) && defaultEntry.Texture != null)
{
entry = defaultEntry;
}
else
{
Cursor.SetCursor(null, Vector2.zero, CursorMode.Auto);
CacheApplied(null, Vector2.zero, CursorMode.Auto);
return;
}
}
var hotspot = entry.Hotspot;
if (entry.Texture != null && entry.HotspotIsNormalized)
{
hotspot = new Vector2(
hotspot.x * entry.Texture.width,
hotspot.y * entry.Texture.height);
}
if (IsSameAsCurrent(entry.Texture, hotspot, entry.Mode))
return;
Cursor.SetCursor(entry.Texture, hotspot, entry.Mode);
CacheApplied(entry.Texture, hotspot, entry.Mode);
}
private bool IsSameAsCurrent(Texture2D texture, Vector2 hotspot, CursorMode mode)
{
return _currentTexture == texture &&
_currentHotspot == hotspot &&
_currentMode == mode;
}
private void CacheApplied(Texture2D texture, Vector2 hotspot, CursorMode mode)
{
_currentTexture = texture;
_currentHotspot = hotspot;
_currentMode = mode;
}
private void BuildStyleMap()
{
_styleMap.Clear();
if (_styles == null)
return;
for (var i = 0; i < _styles.Count; i++)
{
var entry = _styles[i];
_styleMap[entry.Style] = entry;
}
}
[Serializable]
public struct CursorStyleEntry
{
public CursorStyle Style;
public Texture2D Texture;
public Vector2 Hotspot;
public CursorMode Mode;
public bool HotspotIsNormalized;
public Vector2 TooltipOffset;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 515a74faf67c4af9912b8b8eed4d8ad7
timeCreated: 1769716152

View File

@@ -0,0 +1,434 @@
using System;
using System.Collections.Generic;
using BriarQueen.Data.Identifiers;
using BriarQueen.Framework.Coordinators.Events;
using BriarQueen.Framework.Events.UI;
using BriarQueen.Framework.Managers.Interaction;
using BriarQueen.Framework.Managers.IO;
using BriarQueen.Framework.Managers.UI.Base;
using BriarQueen.Framework.Managers.UI.Events;
using BriarQueen.Framework.Services.Settings;
using Cysharp.Threading.Tasks;
using UnityEngine;
using VContainer;
namespace BriarQueen.Framework.Managers.UI
{
/// <summary>
/// UIManager:
/// - Modal windows use the window stack
/// - Non-modal UI (popups / fader) does not use the stack
/// - HUD visibility is event-driven
/// - Concrete UI implementations are hidden behind interfaces
/// </summary>
public class UIManager : IDisposable, IManager
{
private readonly EventCoordinator _eventCoordinator;
private readonly InteractManager _interactManager;
private readonly SaveManager _saveManager;
private readonly SettingsService _settingsService;
private readonly Dictionary<WindowType, IUIWindow> _windows = new();
private readonly Stack<IUIWindow> _windowStack = new();
private bool _disposed;
public bool Initialized { get; private set; }
private IHud _hudContainer;
private IPopup _infoPopup;
private IPopup _tutorialPopup;
private IScreenFader _screenFader;
[Inject]
public UIManager(
EventCoordinator eventCoordinator,
InteractManager interactManager,
SettingsService settingsService,
SaveManager saveManager)
{
_eventCoordinator = eventCoordinator;
_interactManager = interactManager;
_settingsService = settingsService;
_saveManager = saveManager;
}
private IUIWindow ActiveWindow => _windowStack.Count > 0 ? _windowStack.Peek() : null;
public bool IsAnyUIOpen => _windowStack.Count > 0;
public void Initialize()
{
if (Initialized)
return;
_disposed = false;
SubscribeToEvents();
Initialized = true;
}
public void Dispose()
{
if (!Initialized || _disposed)
return;
_disposed = true;
UnsubscribeFromEvents();
ResetUIStateHard();
Initialized = false;
}
private void SubscribeToEvents()
{
_eventCoordinator.Subscribe<PauseButtonClickedEvent>(OnPauseClickReceived);
_eventCoordinator.Subscribe<ToggleCodexEvent>(ToggleCodexWindow);
_eventCoordinator.Subscribe<UIToggleSettingsWindow>(ToggleSettingsWindow);
_eventCoordinator.Subscribe<FadeEvent>(OnFadeEvent);
_eventCoordinator.Subscribe<DisplayInteractEvent>(OnDisplayInteractText);
_eventCoordinator.Subscribe<DisplayTutorialPopupEvent>(OnTutorialDisplayPopup);
_eventCoordinator.Subscribe<CodexChangedEvent>(OnCodexChangedEvent);
_eventCoordinator.Subscribe<UIToggleHudEvent>(OnHudToggleEvent);
_eventCoordinator.Subscribe<ToolbeltChangedEvent>(OnToolbeltChangedEvent);
}
private void UnsubscribeFromEvents()
{
_eventCoordinator.Unsubscribe<PauseButtonClickedEvent>(OnPauseClickReceived);
_eventCoordinator.Unsubscribe<ToggleCodexEvent>(ToggleCodexWindow);
_eventCoordinator.Unsubscribe<UIToggleSettingsWindow>(ToggleSettingsWindow);
_eventCoordinator.Unsubscribe<FadeEvent>(OnFadeEvent);
_eventCoordinator.Unsubscribe<DisplayInteractEvent>(OnDisplayInteractText);
_eventCoordinator.Unsubscribe<DisplayTutorialPopupEvent>(OnTutorialDisplayPopup);
_eventCoordinator.Unsubscribe<CodexChangedEvent>(OnCodexChangedEvent);
_eventCoordinator.Unsubscribe<UIToggleHudEvent>(OnHudToggleEvent);
_eventCoordinator.Unsubscribe<ToolbeltChangedEvent>(OnToolbeltChangedEvent);
}
public void RegisterWindow(IUIWindow window)
{
if (window == null)
return;
_windows[window.WindowType] = window;
window.Hide().Forget();
}
public void RegisterHUD(IHud hudContainer)
{
_hudContainer = hudContainer;
if (_hudContainer != null)
{
_hudContainer.Hide().Forget();
_interactManager.AddUIRaycaster(_hudContainer.Raycaster);
}
}
public void RegisterInfoPopup(IPopup infoPopup)
{
_infoPopup = infoPopup;
if (_infoPopup != null)
_infoPopup.Hide().Forget();
}
public void RegisterTutorialPopup(IPopup tutorialPopup)
{
_tutorialPopup = tutorialPopup;
if (_tutorialPopup != null)
_tutorialPopup.Hide().Forget();
}
public void RegisterScreenFader(IScreenFader screenFader)
{
_screenFader = screenFader;
}
private IUIWindow GetWindow(WindowType windowType)
{
return _windows.TryGetValue(windowType, out var window) ? window : null;
}
private async UniTask ApplyHudVisibility(bool visible)
{
if (_disposed || _hudContainer == null)
return;
try
{
if (visible)
await _hudContainer.Show();
else
await _hudContainer.Hide();
}
catch (Exception ex)
{
Debug.LogError($"[UIManager] ApplyHudVisibility error: {ex}");
}
}
private void OnPauseClickReceived(PauseButtonClickedEvent _)
{
if (_windowStack.Count > 0)
{
CloseTopWindow();
return;
}
OpenWindow(WindowType.PauseMenuWindow);
}
private void ToggleSettingsWindow(UIToggleSettingsWindow eventData)
{
if (eventData.Show)
OpenWindow(WindowType.SettingsWindow);
else
CloseWindow(WindowType.SettingsWindow);
}
private void ToggleCodexWindow(ToggleCodexEvent eventData)
{
if (eventData.Shown)
OpenWindow(WindowType.CodexWindow);
else
CloseWindow(WindowType.CodexWindow);
}
private void OnCodexChangedEvent(CodexChangedEvent eventData)
{
if (_infoPopup == null)
return;
var duration = _settingsService?.Game?.PopupDisplayDuration ?? 3f;
var codexText = GetCodexTextForEntry(eventData.EntryType);
_infoPopup.Play(codexText, duration).Forget();
}
private void OnToolbeltChangedEvent(ToolbeltChangedEvent eventData)
{
if (_infoPopup == null)
return;
var duration = _settingsService?.Game?.PopupDisplayDuration ?? 3f;
var toolText = GetToolbeltTextForEntry(eventData.ToolID, eventData.Lost);
_infoPopup.Play(toolText, duration).Forget();
}
private string GetToolbeltTextForEntry(ToolID toolID, bool lost)
{
if (lost)
return $"You lost the {toolID.ToString()}.";
else
return $"You gained the {toolID.ToString()}.";
}
private string GetCodexTextForEntry(CodexType codexType)
{
return codexType switch
{
CodexType.BookEntry => "You've acquired a new book entry.",
CodexType.PuzzleClue => "You've acquired a new puzzle clue.",
CodexType.Photo => "You've acquired a new photo.",
_ => string.Empty
};
}
private void OnTutorialDisplayPopup(DisplayTutorialPopupEvent eventData)
{
if (_tutorialPopup == null)
return;
if (!_settingsService.AreTutorialsEnabled())
return;
var duration = 3f;
var tutorialText = TutorialPopupTexts.AllPopups[eventData.TutorialID];
_tutorialPopup.Play(tutorialText, duration).Forget();
}
private void OnDisplayInteractText(DisplayInteractEvent eventData)
{
if (_hudContainer == null)
return;
_hudContainer.DisplayInteractText(eventData.Message).Forget();
}
private void OnFadeEvent(FadeEvent eventData)
{
if (_screenFader == null)
return;
if (eventData.Hidden)
_screenFader.FadeFromAsync(eventData.Duration).Forget();
else
_screenFader.FadeToAsync(eventData.Duration).Forget();
}
private void OnHudToggleEvent(UIToggleHudEvent eventData)
{
ApplyHudVisibility(eventData.Show).Forget();
}
public void OpenWindow(WindowType windowType)
{
OpenWindowInternal(windowType).Forget();
}
private async UniTask OpenWindowInternal(WindowType windowType)
{
if (_disposed)
return;
var window = GetWindow(windowType);
if (window == null)
{
Debug.LogError($"[UIManager] Window of type {windowType} not registered.");
return;
}
if (ActiveWindow == window)
return;
if (ActiveWindow != null)
await ActiveWindow.Hide();
_windowStack.Push(window);
await window.Show();
NotifyUIStackChanged();
}
public void CloseWindow(WindowType windowType)
{
CloseWindowInternal(windowType).Forget();
}
private async UniTask CloseWindowInternal(WindowType windowType)
{
if (_disposed || _windowStack.Count == 0)
return;
var target = GetWindow(windowType);
if (target == null)
return;
while (_windowStack.Count > 0)
{
var current = _windowStack.Pop();
if (current != null)
await current.Hide();
if (current == target)
break;
}
if (ActiveWindow != null)
await ActiveWindow.Show();
NotifyUIStackChanged();
}
public void CloseTopWindow()
{
CloseTopWindowInternal().Forget();
}
private async UniTask CloseTopWindowInternal()
{
if (_disposed || _windowStack.Count == 0)
return;
var top = _windowStack.Pop();
if (top != null)
await top.Hide();
if (ActiveWindow != null)
await ActiveWindow.Show();
NotifyUIStackChanged();
}
public void ResetUIState()
{
ResetUIStateAsync().Forget();
}
public async UniTask ResetUIStateAsync()
{
while (_windowStack.Count > 0)
{
var window = _windowStack.Pop();
if (window != null)
{
try
{
await window.Hide();
}
catch
{
}
}
}
if (_tutorialPopup != null)
{
try
{
await _tutorialPopup.Hide();
}
catch
{
}
}
if (_infoPopup != null)
{
try
{
await _infoPopup.Hide();
}
catch
{
}
}
NotifyUIStackChanged();
}
private void ResetUIStateHard()
{
foreach (var kv in _windows)
{
if (kv.Value is Component component && component != null)
component.gameObject.SetActive(false);
}
if (_tutorialPopup?.GameObject != null)
_tutorialPopup.GameObject.SetActive(false);
if (_infoPopup?.GameObject != null)
_infoPopup.GameObject.SetActive(false);
if (_hudContainer is Component hudComponent && hudComponent != null)
hudComponent.gameObject.SetActive(false);
if (_screenFader is Component faderComponent && faderComponent != null)
faderComponent.gameObject.SetActive(false);
_windowStack.Clear();
}
private void NotifyUIStackChanged()
{
_eventCoordinator.Publish(new UIStackChangedEvent(_windowStack.Count > 0));
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 877f1720bf604f91a1b277e25a03dcbe
timeCreated: 1769706518