Refactor interactions, introduce template-method lifecycle management, work on save-load system (#51)

# Lifecycle Management & Save System Revamp

## Overview
Complete overhaul of game lifecycle management, interactable system, and save/load architecture. Introduces centralized `ManagedBehaviour` base class for consistent initialization ordering and lifecycle hooks across all systems.

## Core Architecture

### New Lifecycle System
- **`LifecycleManager`**: Centralized coordinator for all managed objects
- **`ManagedBehaviour`**: Base class replacing ad-hoc initialization patterns
  - `OnManagedAwake()`: Priority-based initialization (0-100, lower = earlier)
  - `OnSceneReady()`: Scene-specific setup after managers ready
  - Replaces `BootCompletionService` (deleted)
- **Priority groups**: Infrastructure (0-20) → Game Systems (30-50) → Data (60-80) → UI/Gameplay (90-100)
- **Editor support**: `EditorLifecycleBootstrap` ensures lifecycle works in editor mode

### Unified SaveID System
- Consistent format: `{ParentName}_{ComponentType}`
- Auto-registration via `AutoRegisterForSave = true`
- New `DebugSaveIds` editor tool for inspection

## Save/Load Improvements

### Enhanced State Management
- **Extended SaveLoadData**: Unlocked minigames, card collection states, combination items, slot occupancy
- **Async loading**: `ApplyCardCollectionState()` waits for card definitions before restoring
- **New `SaveablePlayableDirector`**: Timeline sequences save/restore playback state
- **Fixed race conditions**: Proper initialization ordering prevents data corruption

## Interactable & Pickup System

- Migrated to `OnManagedAwake()` for consistent initialization
- Template method pattern for state restoration (`RestoreInteractionState()`)
- Fixed combination item save/load bugs (items in slots vs. follower hand)
- Dynamic spawning support for combined items on load
- **Breaking**: `Interactable.Awake()` now sealed, use `OnManagedAwake()` instead

##  UI System Changes

- **AlbumViewPage** and **BoosterNotificationDot**: Migrated to `ManagedBehaviour`
- **Fixed menu persistence bug**: Menus no longer reappear after scene transitions
- **Pause Menu**: Now reacts to all scene loads (not just first scene)
- **Orientation Enforcer**: Enforces per-scene via `SceneManagementService`
- **Loading Screen**: Integrated with new lifecycle

## ⚠️ Breaking Changes

1. **`BootCompletionService` removed** → Use `ManagedBehaviour.OnManagedAwake()` with priority
2. **`Interactable.Awake()` sealed** → Override `OnManagedAwake()` instead
3. **SaveID format changed** → Now `{ParentName}_{ComponentType}` consistently
4. **MonoBehaviours needing init ordering** → Must inherit from `ManagedBehaviour`

Co-authored-by: Michal Pikulski <michal.a.pikulski@gmail.com>
Co-authored-by: Michal Pikulski <michal@foolhardyhorizons.com>
Reviewed-on: #51
This commit is contained in:
2025-11-07 15:38:31 +00:00
parent dfa42b2296
commit e27bb7bfb6
93 changed files with 7900 additions and 4347 deletions

View File

@@ -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;

View File

@@ -1,5 +1,6 @@
using UnityEngine;
// TODO: Remove this
public class LureSpot : MonoBehaviour
{
[SerializeField] public GameObject luredBird;

View File

@@ -2,6 +2,7 @@ using UnityEngine;
using Pixelplacement;
using System.Collections;
using Core;
using Core.SaveLoad;
using UnityEngine.Audio;
public class PicnicBehaviour : MonoBehaviour
@@ -12,7 +13,7 @@ public class PicnicBehaviour : MonoBehaviour
public float getFlirtyMin = 1f;
public float getFlirtyMax = 3f;
private StateMachine stateMachine;
private AppleMachine stateMachine;
private Animator animator;
[Header("The FakeChocolate to destroy!")]
@@ -32,7 +33,7 @@ public class PicnicBehaviour : MonoBehaviour
void Awake()
{
stateMachine = GetComponent<StateMachine>();
stateMachine = GetComponent<AppleMachine>();
animator = GetComponent<Animator>();
_audioSource = GetComponent<AppleAudioSource>();
}

View File

@@ -97,90 +97,7 @@ AnimationClip:
- time: 1.5833334
value: {fileID: -1414182512, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
attribute: m_Sprite
path: SoundBird
classID: 212
script: {fileID: 0}
flags: 2
- serializedVersion: 2
curve:
- time: 0
value: {fileID: -1035714051, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.033333335
value: {fileID: -740831527, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.05
value: {fileID: -648204482, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.11666667
value: {fileID: -960280295, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.13333334
value: {fileID: -1144832505, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.2
value: {fileID: -1860215682, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.25
value: {fileID: 519773293, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.26666668
value: {fileID: -1067281986, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.33333334
value: {fileID: -36811272, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.38333333
value: {fileID: -1592089404, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.41666666
value: {fileID: -1729322987, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.45
value: {fileID: -91858778, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.5
value: {fileID: -26124593, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.53333336
value: {fileID: 259088195, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.6
value: {fileID: 1746085375, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.6166667
value: {fileID: -182272111, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.68333334
value: {fileID: 1436667360, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.73333335
value: {fileID: 545467259, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.75
value: {fileID: 121392657, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.8
value: {fileID: 938631806, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.8333333
value: {fileID: 1943282875, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.8833333
value: {fileID: -1918772169, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.93333334
value: {fileID: -1252794517, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 0.96666664
value: {fileID: -927331073, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 1.0166667
value: {fileID: -1038168376, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 1.0833334
value: {fileID: 1855149249, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 1.1
value: {fileID: -2116798272, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 1.1666666
value: {fileID: 2078607702, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 1.1833333
value: {fileID: -633261939, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 1.2333333
value: {fileID: -86103801, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 1.2833333
value: {fileID: 1380056380, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 1.3166667
value: {fileID: 1797284751, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 1.3666667
value: {fileID: 2004539437, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 1.4166666
value: {fileID: 1984933759, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 1.45
value: {fileID: -89013944, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 1.5
value: {fileID: 1990407029, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 1.5166667
value: {fileID: 1094948637, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- time: 1.5833334
value: {fileID: -1414182512, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
attribute: m_Sprite
path: SoundBirdTakeoff/SoundBirdTakeOffAnim
path:
classID: 212
script: {fileID: 0}
flags: 2
@@ -192,16 +109,7 @@ AnimationClip:
m_ClipBindingConstant:
genericBindings:
- serializedVersion: 2
path: 1707885837
attribute: 0
script: {fileID: 0}
typeID: 212
customType: 23
isPPtrCurve: 1
isIntCurve: 0
isSerializeReferenceCurve: 0
- serializedVersion: 2
path: 631576921
path: 0
attribute: 0
script: {fileID: 0}
typeID: 212
@@ -248,44 +156,6 @@ AnimationClip:
- {fileID: 1990407029, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: 1094948637, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -1414182512, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -1035714051, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -740831527, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -648204482, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -960280295, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -1144832505, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -1860215682, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: 519773293, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -1067281986, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -36811272, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -1592089404, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -1729322987, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -91858778, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -26124593, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: 259088195, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: 1746085375, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -182272111, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: 1436667360, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: 545467259, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: 121392657, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: 938631806, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: 1943282875, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -1918772169, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -1252794517, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -927331073, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -1038168376, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: 1855149249, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -2116798272, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: 2078607702, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -633261939, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -86103801, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: 1380056380, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: 1797284751, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: 2004539437, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: 1984933759, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -89013944, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: 1990407029, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: 1094948637, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
- {fileID: -1414182512, guid: 9d1670b18fc5fa8459596f1ddd4a4bd7, type: 3}
m_AnimationClipSettings:
serializedVersion: 2
m_AdditiveReferencePoseClip: {fileID: 0}

View File

@@ -1,4 +1,5 @@
using Core;
using Core.SaveLoad;
using Pixelplacement;
using UnityEngine;
@@ -8,7 +9,7 @@ public class SoundGenerator : MonoBehaviour
[SerializeField] private Sprite exitSprite;
[SerializeField] private AudioClip enterSound;
[SerializeField] private AppleAudioSource audioSource;
[SerializeField] private StateMachine soundBirdSMRef;
[SerializeField] private AppleMachine soundBirdSMRef;
[SerializeField] private soundBird_CanFly soundbirdHearingCheck;
private bool playerInside = false;
@@ -37,7 +38,7 @@ public class SoundGenerator : MonoBehaviour
{
audioSource.audioSource.PlayOneShot(enterSound);
}
if (soundBirdSMRef != null && soundBirdSMRef.currentState.name == "SoundBird" && soundbirdHearingCheck.canFly == true)
if (soundBirdSMRef != null && soundBirdSMRef.currentState.name.ToLower().Contains("soundbird_slot") && soundbirdHearingCheck.canFly == true)
{
soundBirdSMRef.ChangeState("SoundBirdTakeoff");

View File

@@ -1,6 +1,7 @@
using UnityEngine;
using Unity.Cinemachine;
using System.Collections;
using Core.SaveLoad;
using Pixelplacement;
public class cameraSwitcher : MonoBehaviour
@@ -12,7 +13,7 @@ public class cameraSwitcher : MonoBehaviour
[SerializeField] private float transitionDuration = 0.5f; // Duration of the transition
[SerializeField] private soundBird_FlyingBehaviour flyingBehaviour;
[SerializeField] private soundBird_TakeOffBehaviour takeOffBehaviour; // New reference
[SerializeField] private StateMachine birdStateMachine;
[SerializeField] private AppleMachine birdStateMachine;
private int playerInsideCount = 0;
private Coroutine zoomCoroutine;
@@ -32,6 +33,9 @@ public class cameraSwitcher : MonoBehaviour
private void OnTriggerExit2D(Collider2D other)
{
if (!gameObject.activeInHierarchy)
return;
if (other.CompareTag("Player"))
{
playerInsideCount--;

View File

@@ -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
}

View File

@@ -1,3 +1,4 @@
using Core.SaveLoad;
using Pixelplacement;
using Pixelplacement.TweenSystem;
using UnityEngine;
@@ -10,7 +11,7 @@ public class soundBird_FlyingBehaviour : MonoBehaviour
public float flightDelay;
public float cooldownTime;
private StateMachine stateMachine;
private AppleMachine stateMachine;
private Animator animator;
private TweenBase objectTween;
//private Coroutine cooldownCoroutine;
@@ -21,7 +22,7 @@ public class soundBird_FlyingBehaviour : MonoBehaviour
void Awake()
{
stateMachine = GetComponentInParent<StateMachine>();
stateMachine = GetComponentInParent<AppleMachine>();
animator = GetComponentInParent<Animator>();
}

View File

@@ -1,3 +1,4 @@
using Core.SaveLoad;
using Pixelplacement;
using Pixelplacement.TweenSystem;
using UnityEngine;
@@ -9,7 +10,7 @@ public class soundBird_LandingBehaviour1 : MonoBehaviour
public float flightDuration;
public float flightDelay;
public soundBird_FlyingBehaviour flyingBehaviour;
private StateMachine stateMachine;
private AppleMachine stateMachine;
private Animator animator;
private TweenBase objectTween;
@@ -18,7 +19,7 @@ public class soundBird_LandingBehaviour1 : MonoBehaviour
void Awake()
{
stateMachine = GetComponentInParent<StateMachine>();
stateMachine = GetComponentInParent<AppleMachine>();
animator = GetComponentInParent<Animator>();
}
@@ -52,7 +53,7 @@ public class soundBird_LandingBehaviour1 : MonoBehaviour
if (stateMachine != null)
{
animator.SetBool("isScared", false);
stateMachine.ChangeState("SoundBird"); // Change to the desired state name
stateMachine.ChangeState(0); // Change to the desired state name
}
}

View File

@@ -1,3 +1,4 @@
using Core.SaveLoad;
using Pixelplacement;
using Pixelplacement.TweenSystem;
using UnityEngine;
@@ -9,7 +10,7 @@ public class soundBird_TakeOffBehaviour : MonoBehaviour
public Transform SoundBirdObject;
public float flightDuration;
public float flightDelay;
private StateMachine stateMachine;
private AppleMachine stateMachine;
private Animator animator;
private TweenBase objectTween;
public soundBird_FlyingBehaviour flyingBehaviour;
@@ -18,7 +19,7 @@ public class soundBird_TakeOffBehaviour : MonoBehaviour
void Awake()
{
stateMachine = GetComponentInParent<StateMachine>();
stateMachine = GetComponentInParent<AppleMachine>();
animator = GetComponentInParent<Animator>();
}
// Start is called once before the first execution of Update after the MonoBehaviour is created