SaveLoad using managed lifecycle

This commit is contained in:
Michal Pikulski
2025-11-04 20:01:27 +01:00
parent 379a033d6b
commit bb68d1fd31
23 changed files with 1083 additions and 648 deletions

View File

@@ -1,12 +1,10 @@
using Interactions;
using UnityEngine;
using Pathfinding;
using UnityEngine.SceneManagement;
using Utils;
using AppleHills.Core.Settings;
using Core;
using Core.Lifecycle;
using Core.SaveLoad;
using UnityEngine.Events;
/// <summary>
@@ -18,13 +16,13 @@ public class FollowerSaveData
public Vector3 worldPosition;
public Quaternion worldRotation;
public string heldItemSaveId; // Save ID of held pickup (if any)
public string heldItemDataAssetPath; // Asset path to PickupItemData
public string heldItemDataAssetPath; // ItemId of the PickupItemData (for fallback restoration)
}
/// <summary>
/// Controls the follower character, including following the player, handling pickups, and managing held items.
/// </summary>
public class FollowerController : ManagedBehaviour, ISaveParticipant
public class FollowerController : ManagedBehaviour
{
private static readonly int CombineTrigger = Animator.StringToHash("Combine");
@@ -54,6 +52,12 @@ public class FollowerController : ManagedBehaviour, ISaveParticipant
// Direction variables for 2D blend tree animation
private float _lastDirX = 0f; // -1 (left) to 1 (right)
private float _lastDirY = -1f; // -1 (down) to 1 (up)
// Save system configuration
public override bool AutoRegisterForSave => true;
// Scene-specific SaveId - each level has its own follower state
public override string SaveId => $"{gameObject.scene.name}/FollowerController";
private float _currentSpeed = 0f;
private Animator _animator;
private Transform _artTransform;
@@ -98,8 +102,7 @@ public class FollowerController : ManagedBehaviour, ISaveParticipant
private Input.PlayerTouchController _playerTouchController;
// Save system tracking
private bool hasBeenRestored;
// Save system tracking for bilateral restoration
private bool _hasRestoredHeldItem; // Track if held item restoration completed
private string _expectedHeldItemSaveId; // Expected saveId during restoration
@@ -124,38 +127,11 @@ public class FollowerController : ManagedBehaviour, ISaveParticipant
// Initialize settings references
_settings = GameManager.GetSettingsObject<IPlayerFollowerSettings>();
_interactionSettings = GameManager.GetSettingsObject<IInteractionSettings>();
// Register with save system
if (SaveLoadManager.Instance != null)
{
SaveLoadManager.Instance.RegisterParticipant(this);
Logging.Debug("[FollowerController] Registered with SaveLoadManager");
}
else
{
Logging.Warning("[FollowerController] SaveLoadManager not available for registration");
}
}
void OnEnable()
{
SceneManager.sceneLoaded += OnSceneLoaded;
FindPlayerReference();
}
void OnDisable()
{
SceneManager.sceneLoaded -= OnSceneLoaded;
// Unregister from save system
if (SaveLoadManager.Instance != null)
{
SaveLoadManager.Instance.UnregisterParticipant(GetSaveId());
}
}
void OnSceneLoaded(Scene scene, LoadSceneMode mode)
protected override void OnSceneReady()
{
// Find player reference when scene is ready (called for every scene load)
FindPlayerReference();
}
@@ -163,9 +139,7 @@ public class FollowerController : ManagedBehaviour, ISaveParticipant
{
if (_playerTransform == null)
{
FindPlayerReference();
if (_playerTransform == null)
return;
return;
}
// Skip all movement logic when playing a stationary animation
@@ -749,16 +723,9 @@ public class FollowerController : ManagedBehaviour, ISaveParticipant
#endregion ItemInteractions
#region ISaveParticipant Implementation
#region Save/Load Lifecycle Hooks
public bool HasBeenRestored => hasBeenRestored;
public string GetSaveId()
{
return "FollowerController";
}
public string SerializeState()
protected override string OnSceneSaveRequested()
{
var saveData = new FollowerSaveData
{
@@ -772,26 +739,24 @@ public class FollowerController : ManagedBehaviour, ISaveParticipant
var pickup = _cachedPickupObject.GetComponent<Pickup>();
if (pickup is SaveableInteractable saveable)
{
saveData.heldItemSaveId = saveable.GetSaveId();
saveData.heldItemSaveId = saveable.SaveId;
}
// Save the itemId for build-compatible restoration
if (_currentlyHeldItemData != null)
{
#if UNITY_EDITOR
saveData.heldItemDataAssetPath = UnityEditor.AssetDatabase.GetAssetPath(_currentlyHeldItemData);
#endif
saveData.heldItemDataAssetPath = _currentlyHeldItemData.itemId;
}
}
return JsonUtility.ToJson(saveData);
}
public void RestoreState(string serializedData)
protected override void OnSceneRestoreRequested(string serializedData)
{
if (string.IsNullOrEmpty(serializedData))
{
Logging.Debug("[FollowerController] No saved state to restore");
hasBeenRestored = true;
return;
}
@@ -811,7 +776,6 @@ public class FollowerController : ManagedBehaviour, ISaveParticipant
TryRestoreHeldItem(saveData.heldItemSaveId, saveData.heldItemDataAssetPath);
}
hasBeenRestored = true;
Logging.Debug($"[FollowerController] Restored position: {saveData.worldPosition}");
}
}
@@ -825,7 +789,7 @@ public class FollowerController : ManagedBehaviour, ISaveParticipant
/// Bilateral restoration: Follower tries to find and claim the held item.
/// If pickup doesn't exist yet, it will try to claim us when it restores.
/// </summary>
private void TryRestoreHeldItem(string heldItemSaveId, string heldItemDataAssetPath)
private void TryRestoreHeldItem(string heldItemSaveId, string itemDataId)
{
if (_hasRestoredHeldItem)
{
@@ -850,7 +814,7 @@ public class FollowerController : ManagedBehaviour, ISaveParticipant
}
// Claim the pickup
TakeOwnership(pickup, heldItemDataAssetPath);
TakeOwnership(pickup, itemDataId);
}
/// <summary>
@@ -871,9 +835,9 @@ public class FollowerController : ManagedBehaviour, ISaveParticipant
// Verify this is the expected pickup
if (pickup is SaveableInteractable saveable)
{
if (saveable.GetSaveId() != _expectedHeldItemSaveId)
if (saveable.SaveId != _expectedHeldItemSaveId)
{
Logging.Warning($"[FollowerController] Pickup tried to claim but saveId mismatch: {saveable.GetSaveId()} != {_expectedHeldItemSaveId}");
Logging.Warning($"[FollowerController] Pickup tried to claim but saveId mismatch: {saveable.SaveId} != {_expectedHeldItemSaveId}");
return false;
}
}
@@ -886,28 +850,29 @@ public class FollowerController : ManagedBehaviour, ISaveParticipant
/// <summary>
/// Takes ownership of a pickup during restoration. Called by both restoration paths.
/// </summary>
private void TakeOwnership(Pickup pickup, string itemDataAssetPath)
private void TakeOwnership(Pickup pickup, string itemDataIdOrPath)
{
if (_hasRestoredHeldItem)
return; // Already claimed
// Get the item data
// Get the item data from the pickup
PickupItemData heldData = pickup.itemData;
#if UNITY_EDITOR
// Try loading from asset path if available and pickup doesn't have data
if (heldData == null && !string.IsNullOrEmpty(itemDataAssetPath))
{
heldData = UnityEditor.AssetDatabase.LoadAssetAtPath<PickupItemData>(itemDataAssetPath);
}
#endif
// Fallback: If pickup doesn't have itemData, log detailed error
if (heldData == null)
{
Logging.Warning($"[FollowerController] Could not get item data for pickup: {pickup.gameObject.name}");
Logging.Warning($"[FollowerController] Pickup {pickup.gameObject.name} has null itemData!");
Logging.Warning($"[FollowerController] Expected itemId: {itemDataIdOrPath}");
Logging.Warning($"[FollowerController] This pickup prefab may be missing its PickupItemData reference.");
return;
}
// Verify itemId matches if we have it (additional safety check)
if (!string.IsNullOrEmpty(itemDataIdOrPath) && heldData.itemId != itemDataIdOrPath)
{
Logging.Warning($"[FollowerController] ItemId mismatch! Pickup has '{heldData.itemId}' but expected '{itemDataIdOrPath}'");
}
// Setup the held item
_cachedPickupObject = pickup.gameObject;
_cachedPickupObject.SetActive(false); // Held items should be hidden
@@ -915,7 +880,7 @@ public class FollowerController : ManagedBehaviour, ISaveParticipant
_animator.SetBool("IsCarrying", true);
_hasRestoredHeldItem = true;
Logging.Debug($"[FollowerController] Successfully restored held item: {heldData.itemName}");
Logging.Debug($"[FollowerController] Successfully restored held item: {heldData.itemName} (itemId: {heldData.itemId})");
}
/// <summary>
@@ -927,7 +892,7 @@ public class FollowerController : ManagedBehaviour, ISaveParticipant
return FindObjectOfType<FollowerController>();
}
#endregion ISaveParticipant Implementation
#endregion Save/Load Lifecycle Hooks
#if UNITY_EDITOR
void OnDrawGizmos()