Last improvements on the save front
This commit is contained in:
@@ -1,44 +1,82 @@
|
||||
using Core.Lifecycle;
|
||||
using Core.SaveLoad;
|
||||
using UnityEngine;
|
||||
using Pixelplacement;
|
||||
|
||||
public class BirdEyesBehavior : MonoBehaviour
|
||||
public class BirdEyesBehavior : ManagedBehaviour
|
||||
{
|
||||
private AppleMachine statemachine;
|
||||
private Animator animator;
|
||||
// Animator Hashes
|
||||
private static readonly int RightGuess = Animator.StringToHash("RightGuess");
|
||||
private static readonly int WrongGuess = Animator.StringToHash("WrongGuess");
|
||||
private static readonly int NoGuess = Animator.StringToHash("NoGuess");
|
||||
private static readonly int Wolterisout = Animator.StringToHash("wolterisout");
|
||||
|
||||
private AppleMachine _statemachine;
|
||||
private Animator _animator;
|
||||
public bool correctItemIsIn;
|
||||
[SerializeField] private Animator bushAnimator; // Assign in Inspector
|
||||
|
||||
// Save state
|
||||
private bool _wolterisoutTriggered;
|
||||
|
||||
// Enable save/load participation
|
||||
public override bool AutoRegisterForSave => true;
|
||||
|
||||
// Start is called once before the first execution of Update after the MonoBehaviour is created
|
||||
void Start()
|
||||
{
|
||||
statemachine = GetComponent<AppleMachine>();
|
||||
animator = GetComponentInChildren<Animator>();
|
||||
_statemachine = GetComponent<AppleMachine>();
|
||||
_animator = GetComponentInChildren<Animator>();
|
||||
}
|
||||
|
||||
public void CorrectItem()
|
||||
{
|
||||
correctItemIsIn = true;
|
||||
animator.SetTrigger("RightGuess");
|
||||
_animator.SetTrigger(RightGuess);
|
||||
BirdReveal();
|
||||
}
|
||||
|
||||
public void IncorrectItem()
|
||||
{
|
||||
correctItemIsIn = false;
|
||||
animator.SetTrigger("WrongGuess");
|
||||
_animator.SetTrigger(WrongGuess);
|
||||
}
|
||||
|
||||
public void NoItem()
|
||||
{
|
||||
animator.SetTrigger("NoGuess");
|
||||
_animator.SetTrigger(NoGuess);
|
||||
}
|
||||
|
||||
public void BirdReveal()
|
||||
{
|
||||
if (bushAnimator != null)
|
||||
{
|
||||
bushAnimator.SetTrigger("wolterisout");
|
||||
statemachine.ChangeState("BirdSpawned");
|
||||
return;
|
||||
bushAnimator.SetTrigger(Wolterisout);
|
||||
_wolterisoutTriggered = true;
|
||||
}
|
||||
statemachine.ChangeState ("BirdSpawned");
|
||||
_statemachine.ChangeState("BirdSpawned");
|
||||
}
|
||||
|
||||
protected override void OnSceneRestoreRequested(string serializedData)
|
||||
{
|
||||
base.OnSceneRestoreRequested(serializedData);
|
||||
|
||||
if (!string.IsNullOrEmpty(serializedData))
|
||||
{
|
||||
if (bool.TryParse(serializedData, out bool wasTriggered))
|
||||
{
|
||||
_wolterisoutTriggered = wasTriggered;
|
||||
|
||||
// If it was triggered before, set it again on restore
|
||||
if (_wolterisoutTriggered && bushAnimator != null)
|
||||
{
|
||||
bushAnimator.SetTrigger(Wolterisout);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override string OnSceneSaveRequested()
|
||||
{
|
||||
return _wolterisoutTriggered.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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}";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
148
Assets/Scripts/Core/SaveablePlayableDirector.cs
Normal file
148
Assets/Scripts/Core/SaveablePlayableDirector.cs
Normal 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
|
||||
}
|
||||
}
|
||||
|
||||
3
Assets/Scripts/Core/SaveablePlayableDirector.cs.meta
Normal file
3
Assets/Scripts/Core/SaveablePlayableDirector.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a5c5614fc04140cb81e5bda7451f7b14
|
||||
timeCreated: 1762360145
|
||||
@@ -1,9 +1,9 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using Core;
|
||||
using Pathfinding;
|
||||
|
||||
// TODO: Remove movement based logic
|
||||
public class AnneLiseBehaviour : MonoBehaviour
|
||||
{
|
||||
[SerializeField] public float moveSpeed;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using UnityEngine;
|
||||
|
||||
// TODO: Remove this
|
||||
public class LureSpot : MonoBehaviour
|
||||
{
|
||||
[SerializeField] public GameObject luredBird;
|
||||
|
||||
@@ -1,23 +1,51 @@
|
||||
using UnityEngine;
|
||||
using Core.Lifecycle;
|
||||
|
||||
public class soundBird_CanFly : MonoBehaviour
|
||||
[System.Serializable]
|
||||
public class SoundBirdSaveData
|
||||
{
|
||||
public bool canFly;
|
||||
}
|
||||
|
||||
public class soundBird_CanFly : ManagedBehaviour
|
||||
{
|
||||
public bool canFly = true;
|
||||
// Start is called once before the first execution of Update after the MonoBehaviour is created
|
||||
void Start()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Enable save/load participation
|
||||
public override bool AutoRegisterForSave => true;
|
||||
|
||||
public void birdCanHear(bool canhear)
|
||||
{
|
||||
if (canhear)
|
||||
canFly = canhear;
|
||||
}
|
||||
|
||||
#region Save/Load Implementation
|
||||
|
||||
protected override string OnSceneSaveRequested()
|
||||
{
|
||||
var saveData = new SoundBirdSaveData
|
||||
{
|
||||
canFly = true;
|
||||
canFly = this.canFly
|
||||
};
|
||||
|
||||
return JsonUtility.ToJson(saveData);
|
||||
}
|
||||
|
||||
protected override void OnSceneRestoreRequested(string serializedData)
|
||||
{
|
||||
if (string.IsNullOrEmpty(serializedData))
|
||||
{
|
||||
Debug.LogWarning($"[soundBird_CanFly] No save data to restore for {gameObject.name}");
|
||||
return;
|
||||
}
|
||||
else
|
||||
|
||||
var saveData = JsonUtility.FromJson<SoundBirdSaveData>(serializedData);
|
||||
if (saveData != null)
|
||||
{
|
||||
canFly = false;
|
||||
canFly = saveData.canFly;
|
||||
Debug.Log($"[soundBird_CanFly] Restored canFly state: {canFly}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using System; // for Action<T>
|
||||
@@ -357,7 +357,27 @@ namespace Interactions
|
||||
// Try to find the item in the scene by its save ID via ItemManager
|
||||
GameObject slottedObject = ItemManager.Instance?.FindPickupBySaveId(slottedItemSaveId);
|
||||
|
||||
if (slottedObject == null)
|
||||
if (slottedObject == null && !string.IsNullOrEmpty(expectedItemDataId))
|
||||
{
|
||||
// Item not found in scene - it might be a dynamically spawned combined item
|
||||
// Try to spawn it from the itemDataId
|
||||
Debug.Log($"[ItemSlot] Slotted item not found in scene: {slottedItemSaveId}, attempting to spawn from itemId: {expectedItemDataId}");
|
||||
|
||||
GameObject prefab = interactionSettings?.FindPickupPrefabByItemId(expectedItemDataId);
|
||||
if (prefab != null)
|
||||
{
|
||||
// Spawn the item (inactive, since it will be slotted)
|
||||
slottedObject = Instantiate(prefab, transform.position, Quaternion.identity);
|
||||
slottedObject.SetActive(false);
|
||||
Debug.Log($"[ItemSlot] Successfully spawned combined item for slot: {expectedItemDataId}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"[ItemSlot] Could not find prefab for itemId: {expectedItemDataId}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (slottedObject == null)
|
||||
{
|
||||
Debug.LogWarning($"[ItemSlot] Could not find slotted item with save ID: {slottedItemSaveId}");
|
||||
return;
|
||||
@@ -382,12 +402,16 @@ namespace Interactions
|
||||
if (slottedData == null)
|
||||
{
|
||||
Debug.LogWarning($"[ItemSlot] Pickup {pickup.gameObject.name} has null itemData! Expected itemId: {expectedItemDataId}");
|
||||
if (slottedObject != null)
|
||||
Destroy(slottedObject);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"[ItemSlot] Slotted object has no Pickup component: {slottedObject.name}");
|
||||
if (slottedObject != null)
|
||||
Destroy(slottedObject);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Interactions
|
||||
public abstract class SaveableInteractable : InteractableBase
|
||||
{
|
||||
[Header("Save System")]
|
||||
[SerializeField]
|
||||
[SerializeField]
|
||||
|
||||
// Save system configuration
|
||||
public override bool AutoRegisterForSave => true;
|
||||
|
||||
Reference in New Issue
Block a user