Fix issues with puzzle loading order and add saving state when using Menu

This commit is contained in:
Michal Pikulski
2025-11-05 20:17:32 +01:00
committed by Michal Pikulski
parent c527ba334d
commit 717deee0cd
8 changed files with 201 additions and 19 deletions

View File

@@ -122,6 +122,32 @@ TextureImporter:
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: WindowsStoreApps
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites:

View File

@@ -122,6 +122,32 @@ TextureImporter:
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: WindowsStoreApps
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites:

View File

@@ -180,6 +180,67 @@ namespace Core.SaveLoad
return participant;
}
#endregion
#region Unlocked Minigames Management
/// <summary>
/// Marks a minigame as unlocked in the global save data.
/// This is separate from scene-specific participant states and persists across all saves.
/// </summary>
/// <param name="minigameName">The name/identifier of the minigame (typically scene name)</param>
public void UnlockMinigame(string minigameName)
{
if (string.IsNullOrEmpty(minigameName))
{
Logging.Warning("[SaveLoadManager] Attempted to unlock minigame with null or empty name");
return;
}
if (currentSaveData == null)
{
Logging.Warning("[SaveLoadManager] Cannot unlock minigame - no save data loaded");
return;
}
if (currentSaveData.unlockedMinigames == null)
{
currentSaveData.unlockedMinigames = new System.Collections.Generic.List<string>();
}
if (!currentSaveData.unlockedMinigames.Contains(minigameName))
{
currentSaveData.unlockedMinigames.Add(minigameName);
Logging.Debug($"[SaveLoadManager] Unlocked minigame: {minigameName}");
}
}
/// <summary>
/// Checks if a minigame has been unlocked.
/// </summary>
/// <param name="minigameName">The name/identifier of the minigame</param>
/// <returns>True if the minigame is unlocked, false otherwise</returns>
public bool IsMinigameUnlocked(string minigameName)
{
if (string.IsNullOrEmpty(minigameName))
return false;
if (currentSaveData == null || currentSaveData.unlockedMinigames == null)
return false;
return currentSaveData.unlockedMinigames.Contains(minigameName);
}
/// <summary>
/// Gets a read-only list of all unlocked minigames.
/// </summary>
public System.Collections.Generic.IReadOnlyList<string> GetUnlockedMinigames()
{
if (currentSaveData == null || currentSaveData.unlockedMinigames == null)
return new System.Collections.Generic.List<string>();
return currentSaveData.unlockedMinigames.AsReadOnly();
}
#endregion
@@ -343,29 +404,68 @@ namespace Core.SaveLoad
Logging.Debug("[SaveLoadManager] Saving scene-specific data...");
// Collect scene data from LifecycleManager
// Build a dictionary of all data to save
var allSceneData = new Dictionary<string, string>();
// Collect scene data from ManagedBehaviours via LifecycleManager
if (Lifecycle.LifecycleManager.Instance != null)
{
var sceneData = Lifecycle.LifecycleManager.Instance.BroadcastSceneSaveRequested();
// Remove old scene data and add new
if (currentSaveData.participantStates != null)
foreach (var kvp in sceneData)
{
// Remove existing entries for these SaveIds (to avoid duplicates)
currentSaveData.participantStates.RemoveAll(entry => sceneData.ContainsKey(entry.saveId));
// Add new scene data
foreach (var kvp in sceneData)
allSceneData[kvp.Key] = kvp.Value;
}
Logging.Debug($"[SaveLoadManager] Collected {sceneData.Count} ManagedBehaviour scene states");
}
// Collect data from ISaveParticipants (all currently registered, identified by SaveId)
foreach (var kvp in participants.ToList())
{
string saveId = kvp.Key;
ISaveParticipant participant = kvp.Value;
try
{
string serializedState = participant.SerializeState();
allSceneData[saveId] = serializedState;
Logging.Debug($"[SaveLoadManager] Captured state for ISaveParticipant: {saveId}");
}
catch (Exception ex)
{
Logging.Warning($"[SaveLoadManager] Exception while serializing ISaveParticipant '{saveId}': {ex}");
}
}
// Update existing entries or add new ones (matches SaveAsync() pattern)
if (currentSaveData.participantStates != null)
{
int updatedCount = 0;
foreach (var kvp in allSceneData)
{
var existingEntry = currentSaveData.participantStates.Find(e => e.saveId == kvp.Key);
if (existingEntry != null)
{
// Update existing entry in place
existingEntry.serializedState = kvp.Value;
}
else
{
// Add new entry
currentSaveData.participantStates.Add(new ParticipantStateEntry
{
saveId = kvp.Key,
serializedState = kvp.Value
});
}
updatedCount++;
}
Logging.Debug($"[SaveLoadManager] Updated {sceneData.Count} scene data entries in memory");
Logging.Debug($"[SaveLoadManager] Updated {updatedCount} scene data entries in memory");
}
else
{
Logging.Warning("[SaveLoadManager] participantStates list is null, cannot save scene data");
}
}

View File

@@ -255,9 +255,9 @@ namespace Levels
if (_switchData == null)
return;
var data = SaveLoadManager.Instance?.currentSaveData;
// Use the new public API to check unlock status
string minigameName = _switchData.targetMinigameSceneName;
bool unlocked = data?.unlockedMinigames != null && !string.IsNullOrEmpty(minigameName) && data.unlockedMinigames.Contains(minigameName);
bool unlocked = SaveLoadManager.Instance != null && SaveLoadManager.Instance.IsMinigameUnlocked(minigameName);
// Show/hide padlock
if (padlockImage) padlockImage.gameObject.SetActive(!unlocked);

View File

@@ -102,7 +102,13 @@ namespace Levels
isUnlocked = true;
gameObject.SetActive(true);
// Save will happen automatically on next save cycle via ISaveParticipant
// Add to global unlocked minigames list
if (switchData != null && !string.IsNullOrEmpty(switchData.targetLevelSceneName))
{
Core.SaveLoad.SaveLoadManager.Instance?.UnlockMinigame(switchData.targetLevelSceneName);
}
// Save will happen automatically on next save cycle via SaveableInteractable
}
#if UNITY_EDITOR
@@ -231,6 +237,12 @@ namespace Levels
isUnlocked = data.isUnlocked;
// Sync with global unlocked minigames list
if (isUnlocked && switchData != null && !string.IsNullOrEmpty(switchData.targetLevelSceneName))
{
Core.SaveLoad.SaveLoadManager.Instance?.UnlockMinigame(switchData.targetLevelSceneName);
}
// Show/hide based on unlock state
gameObject.SetActive(isUnlocked);
}

View File

@@ -34,8 +34,6 @@ namespace PuzzleS
protected override void Awake()
{
base.Awake();
_interactable = GetComponent<InteractableBase>();
// Initialize the indicator if it exists, but ensure it's hidden initially
@@ -58,6 +56,8 @@ namespace PuzzleS
Logging.Warning($"[Puzzles] Indicator prefab for {stepData?.stepId} does not implement IPuzzlePrompt");
}
}
base.Awake();
}
protected override void OnManagedAwake()

View File

@@ -50,7 +50,24 @@ namespace PuzzleS
// Save system configuration
public override bool AutoRegisterForSave => true;
public override string SaveId => $"{SceneManager.GetActiveScene().name}/PuzzleManager";
/// <summary>
/// SaveId uses CurrentGameplayScene instead of GetActiveScene() because PuzzleManager
/// lives in DontDestroyOnLoad and needs to save/load data per-scene.
/// </summary>
public override string SaveId
{
get
{
string sceneName = SceneManagerService.Instance?.CurrentGameplayScene;
if (string.IsNullOrEmpty(sceneName))
{
// Fallback during early initialization
sceneName = SceneManager.GetActiveScene().name;
}
return $"{sceneName}/PuzzleManager";
}
}
/// <summary>
/// Singleton instance of the PuzzleManager.
@@ -602,7 +619,8 @@ namespace PuzzleS
// Update any behaviors that registered before RestoreState was called
foreach (var behaviour in _pendingRegistrations)
{
UpdateStepState(behaviour);
if(behaviour != null)
UpdateStepState(behaviour);
}
_pendingRegistrations.Clear();
}

View File

@@ -16,8 +16,8 @@ MonoBehaviour:
pauseTimeOnPauseGame: 0
useSaveLoadSystem: 1
bootstrapLogVerbosity: 0
settingsLogVerbosity: 1
gameManagerLogVerbosity: 1
settingsLogVerbosity: 0
gameManagerLogVerbosity: 0
sceneLogVerbosity: 0
saveLoadLogVerbosity: 0
inputLogVerbosity: 0