Last improvements on the save front

This commit is contained in:
Michal Pikulski
2025-11-05 17:34:26 +01:00
committed by Michal Pikulski
parent b3e0f90e09
commit 64253c1048
14 changed files with 610 additions and 176 deletions

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using UnityEngine;
@@ -382,8 +382,9 @@ namespace Core.Lifecycle
string serializedData = component.InvokeSceneSaveRequested();
if (!string.IsNullOrEmpty(serializedData))
{
saveData[component.SaveId] = serializedData;
LogDebug($"Collected scene save data from: {component.SaveId}");
string saveId = component.SaveId;
saveData[saveId] = serializedData;
LogDebug($"Collected scene save data from: {saveId} (Type: {component.GetType().Name})");
}
}
catch (Exception ex)

View File

@@ -66,7 +66,7 @@ namespace Core.Lifecycle
/// <summary>
/// Unique identifier for this component in the save system.
/// Default: "SceneName/GameObjectName"
/// Default: "SceneName/GameObjectName/ComponentType"
/// Override ONLY for special cases (e.g., singletons like "PlayerController", or custom IDs).
/// </summary>
public virtual string SaveId
@@ -74,7 +74,8 @@ namespace Core.Lifecycle
get
{
string sceneName = gameObject.scene.IsValid() ? gameObject.scene.name : "UnknownScene";
return $"{sceneName}/{gameObject.name}";
string componentType = GetType().Name;
return $"{sceneName}/{gameObject.name}/{componentType}";
}
}

View File

@@ -123,9 +123,8 @@ namespace Core.SaveLoad
return $"{sceneName}/{customSaveId}";
}
// Auto-generate from hierarchy path
string hierarchyPath = GetHierarchyPath();
return $"{sceneName}/StateMachine_{hierarchyPath}";
// Match ManagedBehaviour convention: SceneName/GameObjectName/ComponentType
return $"{sceneName}/{gameObject.name}/AppleMachine";
}
private string GetSceneName()
@@ -133,19 +132,6 @@ namespace Core.SaveLoad
return gameObject.scene.name;
}
private string GetHierarchyPath()
{
string path = gameObject.name;
Transform parent = transform.parent;
while (parent != null)
{
path = parent.name + "/" + path;
parent = parent.parent;
}
return path;
}
public string SerializeState()
{

View File

@@ -0,0 +1,148 @@
using UnityEngine;
using UnityEngine.Playables;
using Core.Lifecycle;
namespace Core
{
/// <summary>
/// Saveable data for PlayableDirector state
/// </summary>
[System.Serializable]
public class PlayableDirectorSaveData
{
public bool wasPlayed; // Has the timeline been played?
public bool wasCompleted; // Did it complete playback?
public double playbackTime; // Current playback position
}
/// <summary>
/// Makes a PlayableDirector (Timeline) saveable.
/// On load, if the timeline was completed, it seeks to the end to ensure
/// all timeline-activated objects are in their final state.
/// </summary>
[RequireComponent(typeof(PlayableDirector))]
public class SaveablePlayableDirector : ManagedBehaviour
{
private PlayableDirector _director;
private bool _hasPlayed = false;
private bool _hasCompleted = false;
// Enable save/load participation
public override bool AutoRegisterForSave => true;
protected override void Awake()
{
base.Awake();
_director = GetComponent<PlayableDirector>();
if (_director != null)
{
_director.stopped += OnDirectorStopped;
_director.played += OnDirectorPlayed;
}
}
protected override void OnDestroy()
{
base.OnDestroy();
if (_director != null)
{
_director.stopped -= OnDirectorStopped;
_director.played -= OnDirectorPlayed;
}
}
private void OnDirectorPlayed(PlayableDirector director)
{
_hasPlayed = true;
}
private void OnDirectorStopped(PlayableDirector director)
{
_hasCompleted = true;
}
#region Save/Load Implementation
protected override string OnSceneSaveRequested()
{
var saveData = new PlayableDirectorSaveData
{
wasPlayed = _hasPlayed,
wasCompleted = _hasCompleted,
playbackTime = _director != null ? _director.time : 0
};
return JsonUtility.ToJson(saveData);
}
protected override void OnSceneRestoreRequested(string serializedData)
{
if (string.IsNullOrEmpty(serializedData))
{
Debug.LogWarning($"[SaveablePlayableDirector] No save data to restore for {gameObject.name}");
return;
}
var saveData = JsonUtility.FromJson<PlayableDirectorSaveData>(serializedData);
if (saveData == null || _director == null)
return;
_hasPlayed = saveData.wasPlayed;
_hasCompleted = saveData.wasCompleted;
if (_hasCompleted)
{
// Seek to the end of the timeline to apply all final states
// This ensures objects activated/deactivated by the timeline are in correct state
_director.time = _director.duration;
_director.Evaluate(); // Force evaluation to apply the state
Debug.Log($"[SaveablePlayableDirector] Restored completed timeline '{gameObject.name}' - seeked to end");
}
else if (_hasPlayed && saveData.playbackTime > 0)
{
// If it was playing but not completed, restore the playback position
_director.time = saveData.playbackTime;
_director.Evaluate();
Debug.Log($"[SaveablePlayableDirector] Restored timeline '{gameObject.name}' at time {saveData.playbackTime}");
}
else
{
// Timeline hasn't been played yet, ensure it's at the start
_director.time = 0;
_director.Evaluate();
Debug.Log($"[SaveablePlayableDirector] Timeline '{gameObject.name}' not yet played - at start");
}
}
#endregion
#region Public API
/// <summary>
/// Check if this timeline has been played
/// </summary>
public bool HasPlayed => _hasPlayed;
/// <summary>
/// Check if this timeline completed playback
/// </summary>
public bool HasCompleted => _hasCompleted;
/// <summary>
/// Manually mark the timeline as completed (useful for triggering completion via code)
/// </summary>
public void MarkAsCompleted()
{
_hasCompleted = true;
_hasPlayed = true;
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a5c5614fc04140cb81e5bda7451f7b14
timeCreated: 1762360145