First commit for private source control. Older commits available on Github.
This commit is contained in:
3
Assets/Scripts/Framework/Services/Destruction.meta
Normal file
3
Assets/Scripts/Framework/Services/Destruction.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2113896494844424af1d7aa9c3c01ae4
|
||||
timeCreated: 1769705715
|
||||
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using BriarQueen.Framework.Assets;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using VContainer;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace BriarQueen.Framework.Services.Destruction
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles safe destruction of GameObjects and ensures proper cleanup from world state.
|
||||
/// </summary>
|
||||
public class DestructionService
|
||||
{
|
||||
private readonly AddressableManager _addressables;
|
||||
|
||||
[Inject]
|
||||
public DestructionService(AddressableManager addressables)
|
||||
{
|
||||
_addressables = addressables;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Asynchronously destroys a GameObject, invoking destructible callbacks and unregistering state.
|
||||
/// </summary>
|
||||
public async UniTask Destroy(GameObject go)
|
||||
{
|
||||
if (go == null) return;
|
||||
|
||||
Debug.Log($"[DestructionManager] Trying to destroy object of {go}");
|
||||
|
||||
var destructibles = go.GetComponentsInChildren<IDestructible>(true);
|
||||
if (destructibles is { Length: > 0 })
|
||||
{
|
||||
foreach (var d in destructibles)
|
||||
try
|
||||
{
|
||||
if (d != null) await d.OnPreDestroy();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning($"[DestructionManager] Exception in OnPreDestroy of {d}: {ex}");
|
||||
}
|
||||
|
||||
foreach (var d in destructibles)
|
||||
try
|
||||
{
|
||||
if (d != null) await d.OnDestroyed();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning($"[DestructionManager] Exception in OnDestroyed of {d}: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
// Prefer Addressables release if this is an Addressables-managed instance.
|
||||
if (_addressables != null &&
|
||||
_addressables.TryReleaseInstance(go)) return; // Addressables.ReleaseInstance will destroy it.
|
||||
|
||||
Object.Destroy(go);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0fcef8bcf5944f639d5d3daab61778c3
|
||||
timeCreated: 1769705715
|
||||
@@ -0,0 +1,10 @@
|
||||
using Cysharp.Threading.Tasks;
|
||||
|
||||
namespace BriarQueen.Framework.Services.Destruction
|
||||
{
|
||||
public interface IDestructible
|
||||
{
|
||||
UniTask OnPreDestroy(); // Optional: prepare for destruction
|
||||
UniTask OnDestroyed(); // Final cleanup
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ab93d57d135a4580afcfd67996a0348b
|
||||
timeCreated: 1769705764
|
||||
3
Assets/Scripts/Framework/Services/Game.meta
Normal file
3
Assets/Scripts/Framework/Services/Game.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 41b2559a70e741e1961c7a685eaa788e
|
||||
timeCreated: 1769705047
|
||||
BIN
Assets/Scripts/Framework/Services/Game/.DS_Store
vendored
Normal file
BIN
Assets/Scripts/Framework/Services/Game/.DS_Store
vendored
Normal file
Binary file not shown.
226
Assets/Scripts/Framework/Services/Game/GameService.cs
Normal file
226
Assets/Scripts/Framework/Services/Game/GameService.cs
Normal file
@@ -0,0 +1,226 @@
|
||||
using System;
|
||||
using BriarQueen.Data.Identifiers;
|
||||
using BriarQueen.Framework.Assets;
|
||||
using BriarQueen.Framework.Coordinators.Events;
|
||||
using BriarQueen.Framework.Events.UI;
|
||||
using BriarQueen.Framework.Managers.IO;
|
||||
using BriarQueen.Framework.Managers.Levels;
|
||||
using BriarQueen.Framework.Registries;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.ResourceManagement.AsyncOperations;
|
||||
using UnityEngine.ResourceManagement.ResourceProviders;
|
||||
using UnityEngine.SceneManagement;
|
||||
using VContainer;
|
||||
|
||||
namespace BriarQueen.Framework.Services.Game
|
||||
{
|
||||
public class GameService
|
||||
{
|
||||
private readonly AddressableManager _addressableManager;
|
||||
private readonly AssetRegistry _assetRegistry;
|
||||
private readonly EventCoordinator _eventCoordinator;
|
||||
private readonly LevelManager _levelManager;
|
||||
private readonly SaveManager _saveManager;
|
||||
|
||||
private AsyncOperationHandle<SceneInstance> _gameSceneHandle;
|
||||
private AsyncOperationHandle<SceneInstance> _mainMenuSceneHandle;
|
||||
private AsyncOperationHandle<SceneInstance>? _uiSceneHandle;
|
||||
|
||||
public bool IsMainMenuSceneLoaded {get; private set;}
|
||||
|
||||
[Inject]
|
||||
public GameService(
|
||||
SaveManager saveManager,
|
||||
EventCoordinator eventCoordinator,
|
||||
AddressableManager addressableManager,
|
||||
LevelManager levelManager,
|
||||
AssetRegistry assetRegistry)
|
||||
{
|
||||
_saveManager = saveManager;
|
||||
_eventCoordinator = eventCoordinator;
|
||||
_addressableManager = addressableManager;
|
||||
_levelManager = levelManager;
|
||||
_assetRegistry = assetRegistry;
|
||||
}
|
||||
|
||||
public async UniTask LoadUIAndMainMenuScene()
|
||||
{
|
||||
if (_assetRegistry == null ||
|
||||
!_assetRegistry.TryGetReference(AssetKeyIdentifiers.Get(SceneKey.UIScene), out var uiSceneRef))
|
||||
{
|
||||
Debug.LogError("[GameService] UI Scene reference not found in registry.");
|
||||
return;
|
||||
}
|
||||
|
||||
_uiSceneHandle = await _addressableManager.LoadSceneAsync(uiSceneRef);
|
||||
if (!_uiSceneHandle.Value.IsValid())
|
||||
{
|
||||
Debug.LogError("[GameService] Failed to load UI Scene.");
|
||||
return;
|
||||
}
|
||||
|
||||
// After UI is loaded, we fade to black to prevent pops
|
||||
_eventCoordinator.PublishImmediate(new FadeEvent(false, 0.2f));
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(0.5f));
|
||||
await UniTask.NextFrame();
|
||||
await LoadMainMenu();
|
||||
}
|
||||
|
||||
public async UniTask LoadMainMenu()
|
||||
{
|
||||
const float fadeDuration = 1f;
|
||||
|
||||
_eventCoordinator.PublishImmediate(new FadeEvent(false, fadeDuration));
|
||||
await UniTask.Delay(TimeSpan.FromSeconds(fadeDuration));
|
||||
|
||||
await UnloadGameSceneIfLoaded();
|
||||
|
||||
if (_assetRegistry == null ||
|
||||
!_assetRegistry.TryGetReference(
|
||||
AssetKeyIdentifiers.Get(SceneKey.MainMenuScene),
|
||||
out var mainMenuSceneRef))
|
||||
{
|
||||
Debug.LogError("[GameService] Main menu scene reference missing.");
|
||||
return;
|
||||
}
|
||||
|
||||
_mainMenuSceneHandle = await _addressableManager.LoadSceneAsync(mainMenuSceneRef);
|
||||
|
||||
if (!_mainMenuSceneHandle.IsValid())
|
||||
{
|
||||
Debug.LogError("[GameService] Failed to load Main Menu scene.");
|
||||
return;
|
||||
}
|
||||
|
||||
SceneManager.SetActiveScene(_mainMenuSceneHandle.Result.Scene);
|
||||
IsMainMenuSceneLoaded = true;
|
||||
|
||||
await UniTask.NextFrame();
|
||||
|
||||
_eventCoordinator.PublishImmediate(new FadeEvent(true, fadeDuration));
|
||||
}
|
||||
|
||||
public async UniTask StartGame()
|
||||
{
|
||||
await EnterGameplayLoop();
|
||||
}
|
||||
|
||||
private async UniTask EnterGameplayLoop()
|
||||
{
|
||||
_eventCoordinator.PublishImmediate(new RequestUIOverrideEvent(false));
|
||||
_eventCoordinator.PublishImmediate(new FadeEvent(false, 0.35f));
|
||||
|
||||
if (_mainMenuSceneHandle.IsValid())
|
||||
{
|
||||
await _addressableManager.UnloadSceneAsync(_mainMenuSceneHandle);
|
||||
IsMainMenuSceneLoaded = false;
|
||||
}
|
||||
|
||||
var currentSave = _saveManager.CurrentSave;
|
||||
if (currentSave == null)
|
||||
{
|
||||
Debug.LogError("[GameService] Cannot start game because CurrentSave is null.");
|
||||
await LoadMainMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Log($"[GameService] Loading scene '{currentSave.CurrentSceneID}'...");
|
||||
|
||||
if (!currentSave.OpeningCinematicPlayed)
|
||||
{
|
||||
if (_assetRegistry == null ||
|
||||
!_assetRegistry.TryGetReference(
|
||||
AssetKeyIdentifiers.Get(SceneKey.OpeningCinematicScene),
|
||||
out var cinematicScene))
|
||||
{
|
||||
Debug.LogError("[GameService] Opening cinematic scene reference missing.");
|
||||
await LoadMainMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
var loadedSceneHandle = await _addressableManager.LoadSceneAsync(cinematicScene);
|
||||
if (!loadedSceneHandle.IsValid())
|
||||
{
|
||||
Debug.LogError("[GameService] Failed to load opening cinematic scene.");
|
||||
await LoadMainMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
await SwapGameSceneHandle(loadedSceneHandle);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_assetRegistry == null ||
|
||||
!_assetRegistry.TryGetReference(currentSave.CurrentSceneID, out var chapterSceneRef))
|
||||
{
|
||||
Debug.LogError($"[GameService] Scene reference missing for '{currentSave.CurrentSceneID}'.");
|
||||
await LoadMainMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Log($"[GameService] Loading chapter scene '{currentSave.CurrentSceneID}'...");
|
||||
var loadedSceneHandle = await _addressableManager.LoadSceneAsync(chapterSceneRef);
|
||||
if (!loadedSceneHandle.IsValid())
|
||||
{
|
||||
Debug.LogError($"[GameService] Failed to load chapter scene '{currentSave.CurrentSceneID}'.");
|
||||
await LoadMainMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
await SwapGameSceneHandle(loadedSceneHandle);
|
||||
|
||||
var levelLoaded = await _levelManager.LoadLevel(currentSave.CurrentLevelID);
|
||||
if (!levelLoaded)
|
||||
{
|
||||
Debug.LogError(
|
||||
$"[GameService] Failed to load level '{currentSave.CurrentLevelID}'. Returning to main menu.");
|
||||
await LoadMainMenu();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_eventCoordinator.PublishImmediate(new FadeEvent(true, 0.35f));
|
||||
}
|
||||
|
||||
public async UniTask UnloadGameSceneIfLoaded()
|
||||
{
|
||||
if (_gameSceneHandle.IsValid())
|
||||
{
|
||||
await _addressableManager.UnloadSceneAsync(_gameSceneHandle);
|
||||
_gameSceneHandle = default;
|
||||
}
|
||||
}
|
||||
|
||||
public async UniTask SwapGameSceneHandle(AsyncOperationHandle<SceneInstance> nextSceneHandle)
|
||||
{
|
||||
if (!nextSceneHandle.IsValid())
|
||||
{
|
||||
Debug.LogError("[GameService] SwapGameSceneHandle called with invalid nextSceneHandle.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_gameSceneHandle.IsValid() && _gameSceneHandle.Equals(nextSceneHandle))
|
||||
{
|
||||
_gameSceneHandle = nextSceneHandle;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_gameSceneHandle.IsValid())
|
||||
await _addressableManager.UnloadSceneAsync(_gameSceneHandle);
|
||||
|
||||
_gameSceneHandle = nextSceneHandle;
|
||||
SceneManager.SetActiveScene(nextSceneHandle.Result.Scene);
|
||||
}
|
||||
|
||||
public void QuitGame()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
EditorApplication.isPlaying = false;
|
||||
#else
|
||||
Application.Quit();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ef88df0f91964cd1a879ddaf98aca27b
|
||||
timeCreated: 1769705047
|
||||
3
Assets/Scripts/Framework/Services/Puzzles.meta
Normal file
3
Assets/Scripts/Framework/Services/Puzzles.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e1aa05d44324413fb6fff7d6dd1c8c55
|
||||
timeCreated: 1769720489
|
||||
BIN
Assets/Scripts/Framework/Services/Puzzles/.DS_Store
vendored
Normal file
BIN
Assets/Scripts/Framework/Services/Puzzles/.DS_Store
vendored
Normal file
Binary file not shown.
3
Assets/Scripts/Framework/Services/Puzzles/Base.meta
Normal file
3
Assets/Scripts/Framework/Services/Puzzles/Base.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 833a26bf9c2c446e8c738a8b377956a0
|
||||
timeCreated: 1773830388
|
||||
50
Assets/Scripts/Framework/Services/Puzzles/Base/BasePuzzle.cs
Normal file
50
Assets/Scripts/Framework/Services/Puzzles/Base/BasePuzzle.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
// ==============================
|
||||
// PuzzleBase.cs (updated)
|
||||
// ==============================
|
||||
|
||||
using System.Collections.Generic;
|
||||
using BriarQueen.Framework.Assets;
|
||||
using BriarQueen.Framework.Coordinators.Events;
|
||||
using BriarQueen.Framework.Managers.Audio;
|
||||
using BriarQueen.Framework.Managers.Hints.Data;
|
||||
using BriarQueen.Framework.Managers.IO;
|
||||
using BriarQueen.Framework.Managers.Levels.Data;
|
||||
using BriarQueen.Framework.Registries;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using VContainer;
|
||||
|
||||
namespace BriarQueen.Framework.Services.Puzzles.Base
|
||||
{
|
||||
public abstract class BasePuzzle : BaseLevel
|
||||
{
|
||||
protected AddressableManager AddressableManager;
|
||||
protected AssetRegistry AssetRegistry;
|
||||
protected AudioManager AudioManager;
|
||||
protected ItemRegistry ItemRegistry;
|
||||
protected PuzzleService PuzzleService;
|
||||
|
||||
public abstract string PuzzleID { get; }
|
||||
|
||||
public override bool IsPuzzleLevel => true;
|
||||
|
||||
// BaseLevel still requires these.
|
||||
public abstract override string LevelName { get; }
|
||||
public abstract override Dictionary<int, BaseHint> Hints { get; }
|
||||
|
||||
public abstract UniTask CompletePuzzle();
|
||||
|
||||
[Inject]
|
||||
public void Construct(EventCoordinator eventCoordinator, AudioManager audioManager,
|
||||
SaveManager saveManager, ItemRegistry itemRegistry, AddressableManager addressableManager,
|
||||
AssetRegistry assetRegistry, PuzzleService puzzleService)
|
||||
{
|
||||
EventCoordinator = eventCoordinator;
|
||||
AudioManager = audioManager;
|
||||
SaveManager = saveManager;
|
||||
ItemRegistry = itemRegistry;
|
||||
AddressableManager = addressableManager;
|
||||
AssetRegistry = assetRegistry;
|
||||
PuzzleService = puzzleService;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2f7dc618c5084940a094037aab5f4e8b
|
||||
timeCreated: 1769876583
|
||||
@@ -0,0 +1,15 @@
|
||||
using Cysharp.Threading.Tasks;
|
||||
|
||||
namespace BriarQueen.Framework.Services.Puzzles.Base
|
||||
{
|
||||
public interface IPuzzleStateful
|
||||
{
|
||||
/// <summary>If true, manager can mark the puzzle completed (and optionally clear state).</summary>
|
||||
bool IsCompleted { get; }
|
||||
|
||||
UniTask<byte[]> CaptureState();
|
||||
|
||||
/// <summary>Restore the puzzle from a previously captured serialized blob.</summary>
|
||||
UniTask RestoreState(byte[] state);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e05bb678cdfe40f6acffd77fceb3faba
|
||||
timeCreated: 1769878647
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace BriarQueen.Framework.Services.Puzzles.Base
|
||||
{
|
||||
public interface IPuzzleWorldStateSync
|
||||
{
|
||||
void SyncWorldStateToSave();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 120766eb18204d1593aad20a292f305c
|
||||
timeCreated: 1774453311
|
||||
134
Assets/Scripts/Framework/Services/Puzzles/PuzzleService.cs
Normal file
134
Assets/Scripts/Framework/Services/Puzzles/PuzzleService.cs
Normal file
@@ -0,0 +1,134 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BriarQueen.Data.IO.Saves;
|
||||
using BriarQueen.Framework.Managers.IO;
|
||||
using BriarQueen.Framework.Services.Puzzles.Base;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using VContainer;
|
||||
|
||||
namespace BriarQueen.Framework.Services.Puzzles
|
||||
{
|
||||
public class PuzzleService : IDisposable
|
||||
{
|
||||
private readonly SaveManager _saveManager;
|
||||
|
||||
private BasePuzzle _currentBasePuzzle;
|
||||
private bool _isWritingState;
|
||||
|
||||
[Inject]
|
||||
public PuzzleService(SaveManager saveManager)
|
||||
{
|
||||
_saveManager = saveManager;
|
||||
_saveManager.OnBeforeSaveRequestedAsync += OnBeforeSaveRequestedAsync;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_saveManager.OnBeforeSaveRequestedAsync -= OnBeforeSaveRequestedAsync;
|
||||
}
|
||||
|
||||
public async UniTask LoadPuzzle(BasePuzzle basePuzzle)
|
||||
{
|
||||
_currentBasePuzzle = basePuzzle;
|
||||
if (_currentBasePuzzle == null)
|
||||
return;
|
||||
|
||||
await TryRestorePuzzleState(_currentBasePuzzle);
|
||||
}
|
||||
|
||||
public async UniTask SavePuzzle(BasePuzzle basePuzzle)
|
||||
{
|
||||
if (basePuzzle == null)
|
||||
return;
|
||||
|
||||
if (_currentBasePuzzle != null && basePuzzle != _currentBasePuzzle)
|
||||
return;
|
||||
|
||||
await SavePuzzleState(basePuzzle, flushToDisk: true);
|
||||
_currentBasePuzzle = null;
|
||||
}
|
||||
|
||||
private async UniTask OnBeforeSaveRequestedAsync()
|
||||
{
|
||||
if (_currentBasePuzzle == null)
|
||||
return;
|
||||
|
||||
await SavePuzzleState(_currentBasePuzzle, flushToDisk: false);
|
||||
}
|
||||
|
||||
private async UniTask TryRestorePuzzleState(BasePuzzle basePuzzle)
|
||||
{
|
||||
if (basePuzzle == null || _saveManager.CurrentSave == null)
|
||||
return;
|
||||
|
||||
if (basePuzzle is not IPuzzleStateful stateful)
|
||||
return;
|
||||
|
||||
var save = _saveManager.CurrentSave;
|
||||
var entry = save.PuzzleStates?.FirstOrDefault(x => x != null && x.PuzzleID == basePuzzle.PuzzleID);
|
||||
var stateBlob = entry?.State;
|
||||
|
||||
try
|
||||
{
|
||||
await stateful.RestoreState(stateBlob);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning($"[PuzzleService] Failed to restore state for '{basePuzzle.PuzzleID}': {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private async UniTask SavePuzzleState(BasePuzzle basePuzzle, bool flushToDisk)
|
||||
{
|
||||
if (basePuzzle == null || _saveManager.CurrentSave == null)
|
||||
return;
|
||||
|
||||
if (basePuzzle is not IPuzzleStateful stateful)
|
||||
return;
|
||||
|
||||
if (_isWritingState)
|
||||
return;
|
||||
|
||||
_isWritingState = true;
|
||||
|
||||
try
|
||||
{
|
||||
if (basePuzzle is IPuzzleWorldStateSync worldStateSync)
|
||||
worldStateSync.SyncWorldStateToSave();
|
||||
|
||||
var save = _saveManager.CurrentSave;
|
||||
save.PuzzleStates ??= new List<PuzzleStateSaveData>();
|
||||
|
||||
byte[] blob;
|
||||
try
|
||||
{
|
||||
blob = await stateful.CaptureState() ?? Array.Empty<byte>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogWarning($"[PuzzleService] Failed to capture state for '{basePuzzle.PuzzleID}': {ex}");
|
||||
return;
|
||||
}
|
||||
|
||||
var existing = save.PuzzleStates.FirstOrDefault(x => x != null && x.PuzzleID == basePuzzle.PuzzleID);
|
||||
if (existing == null)
|
||||
{
|
||||
existing = new PuzzleStateSaveData { PuzzleID = basePuzzle.PuzzleID };
|
||||
save.PuzzleStates.Add(existing);
|
||||
}
|
||||
|
||||
existing.State = blob;
|
||||
existing.Completed = stateful.IsCompleted;
|
||||
|
||||
if (flushToDisk)
|
||||
await _saveManager.SaveGameDataLatest();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isWritingState = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6475266033724d2db5366ba6e56cf8a1
|
||||
timeCreated: 1769720489
|
||||
3
Assets/Scripts/Framework/Services/Settings.meta
Normal file
3
Assets/Scripts/Framework/Services/Settings.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 793fe8757aa34b23a1249ffdafc4dba3
|
||||
timeCreated: 1769800626
|
||||
BIN
Assets/Scripts/Framework/Services/Settings/.DS_Store
vendored
Normal file
BIN
Assets/Scripts/Framework/Services/Settings/.DS_Store
vendored
Normal file
Binary file not shown.
8
Assets/Scripts/Framework/Services/Settings/Data.meta
Normal file
8
Assets/Scripts/Framework/Services/Settings/Data.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b0c389726255940069bd3d699d642044
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/Scripts/Framework/Services/Settings/Data/.DS_Store
vendored
Normal file
BIN
Assets/Scripts/Framework/Services/Settings/Data/.DS_Store
vendored
Normal file
Binary file not shown.
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
|
||||
namespace BriarQueen.Framework.Services.Settings.Data
|
||||
{
|
||||
[Serializable]
|
||||
public class AudioSettings
|
||||
{
|
||||
public float MasterVolume;
|
||||
public float MusicVolume;
|
||||
public float SfxVolume;
|
||||
public float VoiceVolume;
|
||||
public float AmbienceVolume;
|
||||
public float UIVolume;
|
||||
public bool MuteWhenUnfocused;
|
||||
|
||||
public AudioSettings()
|
||||
{
|
||||
MasterVolume = 1.0f; // 100%
|
||||
MusicVolume = 0.75f; // 75%
|
||||
SfxVolume = 0.75f; // 75%
|
||||
VoiceVolume = 1.0f; // 100%
|
||||
AmbienceVolume = 0.75f; // 75%
|
||||
UIVolume = 0.5f; // 50%
|
||||
MuteWhenUnfocused = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f10f93ec9d8c48d18d41fcf1b8e2a160
|
||||
timeCreated: 1769800676
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace BriarQueen.Framework.Services.Settings.Data
|
||||
{
|
||||
[Serializable]
|
||||
public class GameSettings
|
||||
{
|
||||
public float PopupDisplayDuration = 3f;
|
||||
public bool TutorialsEnabled = true;
|
||||
public bool TooltipsEnabled = true;
|
||||
public bool AutoUseTools = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c89e0d84df0742999d88e7a33e8360c2
|
||||
timeCreated: 1772819897
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BriarQueen.Framework.Services.Settings.Data
|
||||
{
|
||||
[Serializable]
|
||||
public class VisualSettings
|
||||
{
|
||||
public int VSyncCount; // 0 = Off, 1 = Every V-Blank, 2 = Every second V-Blank
|
||||
|
||||
public FullScreenMode FullScreenMode;
|
||||
|
||||
/// <summary>
|
||||
/// Requested application framerate cap.
|
||||
/// Ignored or effectively overridden on many platforms when VSync is enabled.
|
||||
/// Use -1 for platform default / uncapped behavior.
|
||||
/// </summary>
|
||||
public int MaxFramerate;
|
||||
|
||||
public VisualSettings()
|
||||
{
|
||||
VSyncCount = 0;
|
||||
FullScreenMode = FullScreenMode.FullScreenWindow;
|
||||
MaxFramerate = 120;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6e288ada3e7b4b06a23bf0824f393543
|
||||
timeCreated: 1769800769
|
||||
228
Assets/Scripts/Framework/Services/Settings/SettingsService.cs
Normal file
228
Assets/Scripts/Framework/Services/Settings/SettingsService.cs
Normal file
@@ -0,0 +1,228 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using BriarQueen.Data.Identifiers;
|
||||
using BriarQueen.Data.IO;
|
||||
using BriarQueen.Framework.Coordinators.Events;
|
||||
using BriarQueen.Framework.Events.Save;
|
||||
using BriarQueen.Framework.Managers.Audio;
|
||||
using BriarQueen.Framework.Services.Settings.Data;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
using UnityEngine;
|
||||
using VContainer;
|
||||
using AudioSettings = BriarQueen.Framework.Services.Settings.Data.AudioSettings;
|
||||
|
||||
namespace BriarQueen.Framework.Services.Settings
|
||||
{
|
||||
public class SettingsService
|
||||
{
|
||||
private const string AUDIO_FILE = "Audio.json";
|
||||
private const string VISUAL_FILE = "Visual.json";
|
||||
private const string GAME_FILE = "Game.json";
|
||||
|
||||
private readonly AudioManager _audioManager;
|
||||
private readonly EventCoordinator _eventCoordinator;
|
||||
|
||||
|
||||
[Inject]
|
||||
public SettingsService(EventCoordinator eventCoordinator, AudioManager audioManager)
|
||||
{
|
||||
_eventCoordinator = eventCoordinator;
|
||||
_audioManager = audioManager;
|
||||
}
|
||||
|
||||
public AudioSettings Audio { get; private set; }
|
||||
public VisualSettings Visual { get; private set; }
|
||||
public GameSettings Game { get; private set; }
|
||||
|
||||
public async UniTask InitializeAsync()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
Debug.Log($"[{nameof(SettingsService)}] Initializing...");
|
||||
#endif
|
||||
EnsureDirectories();
|
||||
await LoadAllSettings();
|
||||
Apply(Audio, Visual, Game, false, false);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
Debug.Log($"[{nameof(SettingsService)}] Initialized.");
|
||||
#endif
|
||||
}
|
||||
|
||||
private void EnsureDirectories()
|
||||
{
|
||||
Directory.CreateDirectory(FilePaths.ConfigFolder);
|
||||
Directory.CreateDirectory(FilePaths.SaveDataFolder);
|
||||
Directory.CreateDirectory(FilePaths.SaveBackupDataFolder);
|
||||
}
|
||||
|
||||
public void Apply(
|
||||
AudioSettings audio,
|
||||
VisualSettings visual,
|
||||
GameSettings game,
|
||||
bool saveToDisk = true,
|
||||
bool fireEvent = true)
|
||||
{
|
||||
Audio = audio ?? new AudioSettings();
|
||||
Visual = visual ?? new VisualSettings();
|
||||
Game = game ?? new GameSettings();
|
||||
|
||||
ApplyAudio(Audio);
|
||||
ApplyVisual(Visual);
|
||||
ApplyGame(Game);
|
||||
|
||||
if (saveToDisk)
|
||||
{
|
||||
SaveSettingsFile(Audio, AUDIO_FILE).Forget();
|
||||
SaveSettingsFile(Visual, VISUAL_FILE).Forget();
|
||||
SaveSettingsFile(Game, GAME_FILE).Forget();
|
||||
}
|
||||
|
||||
if (fireEvent)
|
||||
_eventCoordinator.Publish(new SettingsChangedEvent());
|
||||
}
|
||||
|
||||
public float GetVolume(string mixerGroup)
|
||||
{
|
||||
return mixerGroup switch
|
||||
{
|
||||
AudioMixerGroups.MASTER_GROUP => Audio.MasterVolume,
|
||||
AudioMixerGroups.MUSIC_GROUP => Audio.MusicVolume,
|
||||
AudioMixerGroups.SFX_GROUP => Audio.SfxVolume,
|
||||
AudioMixerGroups.UI_GROUP => Audio.UIVolume,
|
||||
AudioMixerGroups.VOICE_GROUP => Audio.VoiceVolume,
|
||||
_ => Audio.MasterVolume
|
||||
};
|
||||
}
|
||||
|
||||
public bool AreTutorialsEnabled()
|
||||
{
|
||||
return Game != null && Game.TutorialsEnabled;
|
||||
}
|
||||
|
||||
public bool AreTooltipsEnabled()
|
||||
{
|
||||
return Game != null && Game.TooltipsEnabled;
|
||||
}
|
||||
|
||||
private void ApplyAudio(AudioSettings a)
|
||||
{
|
||||
_audioManager.SetVolume(AudioMixerParameters.MASTER_VOLUME, a.MasterVolume);
|
||||
_audioManager.SetVolume(AudioMixerParameters.MUSIC_VOLUME, a.MusicVolume);
|
||||
_audioManager.SetVolume(AudioMixerParameters.SFX_VOLUME, a.SfxVolume);
|
||||
_audioManager.SetVolume(AudioMixerParameters.VOICE_VOLUME, a.VoiceVolume);
|
||||
_audioManager.SetVolume(AudioMixerParameters.AMBIENCE_VOLUME, a.AmbienceVolume);
|
||||
_audioManager.SetVolume(AudioMixerParameters.UI_VOLUME, a.UIVolume);
|
||||
|
||||
Application.runInBackground = !a.MuteWhenUnfocused;
|
||||
}
|
||||
|
||||
private void ApplyVisual(VisualSettings v)
|
||||
{
|
||||
SanitizeVisualSettings(v);
|
||||
|
||||
var currentRes = Screen.currentResolution;
|
||||
var rr = new RefreshRate
|
||||
{
|
||||
numerator = currentRes.refreshRateRatio.numerator,
|
||||
denominator = currentRes.refreshRateRatio.denominator
|
||||
};
|
||||
|
||||
Screen.SetResolution(currentRes.width, currentRes.height, v.FullScreenMode, rr);
|
||||
QualitySettings.vSyncCount = v.VSyncCount;
|
||||
|
||||
// On many desktop platforms, VSync will take precedence over targetFrameRate.
|
||||
Application.targetFrameRate = v.MaxFramerate;
|
||||
}
|
||||
|
||||
private void ApplyGame(GameSettings g)
|
||||
{
|
||||
if (g.PopupDisplayDuration <= 0f)
|
||||
g.PopupDisplayDuration = 3f;
|
||||
|
||||
// TutorialsEnabled and TooltipsEnabled are gameplay/UI preferences.
|
||||
// They do not need an immediate engine-level apply step here.
|
||||
// Systems that show tutorials/tooltips should query SettingsManager.Game
|
||||
// or use the helper methods above.
|
||||
}
|
||||
|
||||
private async UniTask LoadAllSettings()
|
||||
{
|
||||
Audio = await LoadOrCreateSettingsFiles<AudioSettings>(AUDIO_FILE);
|
||||
Visual = await LoadOrCreateSettingsFiles<VisualSettings>(VISUAL_FILE);
|
||||
Game = await LoadOrCreateSettingsFiles<GameSettings>(GAME_FILE);
|
||||
|
||||
Audio ??= new AudioSettings();
|
||||
Visual ??= new VisualSettings();
|
||||
Game ??= new GameSettings();
|
||||
|
||||
SanitizeVisualSettings(Visual);
|
||||
|
||||
if (Game.PopupDisplayDuration <= 0f)
|
||||
Game.PopupDisplayDuration = 3f;
|
||||
}
|
||||
|
||||
private void SanitizeVisualSettings(VisualSettings v)
|
||||
{
|
||||
if (v == null)
|
||||
return;
|
||||
|
||||
v.VSyncCount = Mathf.Clamp(v.VSyncCount, 0, 2);
|
||||
|
||||
// Treat 0 or any negative value other than -1 as invalid and fall back to 120.
|
||||
// If you want to support -1 as "platform default", keep it. Otherwise, normalize it too.
|
||||
if (v.MaxFramerate == 0 || v.MaxFramerate < -1)
|
||||
v.MaxFramerate = 120;
|
||||
|
||||
if (v.MaxFramerate > 0)
|
||||
v.MaxFramerate = Mathf.Clamp(v.MaxFramerate, 30, 240);
|
||||
}
|
||||
|
||||
private async UniTask<T> LoadOrCreateSettingsFiles<T>(string fileName) where T : class, new()
|
||||
{
|
||||
var filePath = Path.Combine(FilePaths.ConfigFolder, fileName);
|
||||
T settings;
|
||||
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
try
|
||||
{
|
||||
var jsonString = await File.ReadAllTextAsync(filePath);
|
||||
settings = JsonConvert.DeserializeObject<T>(jsonString);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError($"[SettingsManager] Failed to load {fileName}. Creating new. Error: {e.Message}");
|
||||
settings = new T();
|
||||
await SaveSettingsFile(settings, fileName);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
settings = new T();
|
||||
await SaveSettingsFile(settings, fileName);
|
||||
}
|
||||
|
||||
return settings ?? new T();
|
||||
}
|
||||
|
||||
private async UniTask SaveSettingsFile<T>(T settings, string fileName) where T : class
|
||||
{
|
||||
if (settings == null)
|
||||
return;
|
||||
|
||||
var filePath = Path.Combine(FilePaths.ConfigFolder, fileName);
|
||||
Directory.CreateDirectory(FilePaths.ConfigFolder);
|
||||
|
||||
try
|
||||
{
|
||||
var jsonString = JsonConvert.SerializeObject(settings, Formatting.Indented);
|
||||
await File.WriteAllTextAsync(filePath, jsonString);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError($"[SettingsManager] Failed to save {fileName}. Error: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6a9b8ee2440445a692de5a8e3851712c
|
||||
timeCreated: 1769800626
|
||||
3
Assets/Scripts/Framework/Services/Tutorials.meta
Normal file
3
Assets/Scripts/Framework/Services/Tutorials.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f74655d5e387472f83a5ada14d07c739
|
||||
timeCreated: 1773945075
|
||||
@@ -0,0 +1,47 @@
|
||||
using BriarQueen.Data.Identifiers;
|
||||
using BriarQueen.Framework.Coordinators.Events;
|
||||
using BriarQueen.Framework.Events.Save;
|
||||
using BriarQueen.Framework.Events.UI;
|
||||
using BriarQueen.Framework.Managers.IO;
|
||||
using BriarQueen.Framework.Services.Settings;
|
||||
using VContainer;
|
||||
|
||||
namespace BriarQueen.Framework.Services.Tutorials
|
||||
{
|
||||
public class TutorialService
|
||||
{
|
||||
private readonly EventCoordinator _eventCoordinator;
|
||||
private readonly SettingsService _settingsService;
|
||||
private readonly SaveManager _saveManager;
|
||||
|
||||
[Inject]
|
||||
public TutorialService(
|
||||
EventCoordinator eventCoordinator,
|
||||
SettingsService settingsService,
|
||||
SaveManager saveManager)
|
||||
{
|
||||
_eventCoordinator = eventCoordinator;
|
||||
_settingsService = settingsService;
|
||||
_saveManager = saveManager;
|
||||
}
|
||||
|
||||
public void DisplayTutorial(TutorialPopupID tutorialPopupID)
|
||||
{
|
||||
var save = _saveManager.CurrentSave;
|
||||
var tutorialVars = save?.PersistentVariables?.TutorialPopupVariables;
|
||||
|
||||
if (tutorialVars == null)
|
||||
return;
|
||||
|
||||
if (tutorialVars.HasBeenDisplayed(tutorialPopupID))
|
||||
return;
|
||||
|
||||
tutorialVars.MarkDisplayed(tutorialPopupID);
|
||||
|
||||
if (_settingsService.AreTutorialsEnabled())
|
||||
_eventCoordinator.Publish(new DisplayTutorialPopupEvent(tutorialPopupID));
|
||||
|
||||
_eventCoordinator.PublishImmediate(new RequestGameSaveEvent());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4fe74e4cef404c51a2b151b539c64295
|
||||
timeCreated: 1773945075
|
||||
Reference in New Issue
Block a user