# State Machine Save/Load Integration **Date:** November 2, 2025 **Status:** ✅ Complete ## Overview Integrated the Pixelplacement StateMachine framework with the AppleHills save/load system by directly modifying the library source files and providing a clean API for state persistence. ## Architecture ### Two-Method Pattern States use a clean, explicit lifecycle pattern: 1. **`OnEnterState()`** - Called when entering state during normal gameplay 2. **`OnRestoreState(string data)`** - Called when restoring state from save file 3. **`SerializeState()`** - Returns state data as JSON string for saving ### How It Works **Normal Gameplay:** ``` Player triggers transition → ChangeState("Chase") ├─ StateMachine.Enter() activates GameObject ├─ IsRestoring = false └─ Calls state.OnEnterState() └─ Full initialization: animations, events, movement ``` **Save/Load:** ``` StateMachine.SerializeState() ├─ Returns current state name └─ Calls currentState.SerializeState() └─ State returns its internal data as JSON StateMachine.RestoreState(data) ├─ Sets IsRestoring = true ├─ ChangeState(stateName) - activates GameObject │ └─ Does NOT call OnEnterState() (IsRestoring=true) ├─ Calls state.OnRestoreState(stateData) │ └─ State restores without animations/effects └─ Sets IsRestoring = false ``` ## Files Modified ### 1. State.cs **Location:** `Assets/External/Pixelplacement/Surge/StateMachine/State.cs` **Added:** - `OnEnterState()` - virtual method for normal state entry - `OnRestoreState(string data)` - virtual method for restoration - `SerializeState()` - virtual method for serialization ### 2. StateMachine.cs **Location:** `Assets/External/Pixelplacement/Surge/StateMachine/StateMachine.cs` **Added:** - Implements `ISaveParticipant` interface - `saveId` field (serialized, set in inspector) - `IsRestoring` property (public, readable by states) - `HasBeenRestored` property - Modified `Enter()` to call `OnEnterState()` when not restoring - `SerializeState()` implementation - collects state name + state data - `RestoreState()` implementation - restores to saved state - Registration with SaveLoadManager via BootCompletionService - Unregistration on destroy ### 3. GardenerChaseBehavior.cs (Example Migration) **Location:** `Assets/Scripts/Animation/GardenerChaseBehavior.cs` **Migrated from:** - `Start()` method with initialization **To:** - `OnEnterState()` - starts chase tween - `OnRestoreState(string)` - positions gardener without animation, resumes tween from saved progress - `SerializeState()` - saves tween progress and completion state ## Usage Guide ### For Simple States (No Data to Save) ```csharp public class IdleState : State { public override void OnEnterState() { // Normal initialization PlayIdleAnimation(); SubscribeToEvents(); } public override void OnRestoreState(string data) { // Minimal restoration - just set visual state SetAnimatorToIdle(); } // SerializeState() not overridden - returns empty string by default } ``` ### For Complex States (With Data to Save) ```csharp public class ChaseState : State { private float progress; public override void OnEnterState() { StartChaseAnimation(); progress = 0f; } public override void OnRestoreState(string data) { if (string.IsNullOrEmpty(data)) { OnEnterState(); // No saved data, initialize normally return; } var saved = JsonUtility.FromJson(data); progress = saved.progress; // Position objects without playing animations SetPosition(saved.progress); } public override string SerializeState() { return JsonUtility.ToJson(new ChaseSaveData { progress = progress }); } [System.Serializable] private class ChaseSaveData { public float progress; } } ``` ### For States That Don't Need Save/Load States that don't override the new methods continue to work normally: - Existing states using `Start()` and `OnEnable()` are unaffected - Only states that need save/load functionality need to be migrated ## Setup in Unity 1. **Add Save ID to StateMachine:** - Select GameObject with StateMachine component - In inspector, set "Save Id" field to unique identifier (e.g., "GardenerStateMachine") - Leave empty to disable saving for that state machine 2. **Migrate States:** - For each state that needs saving: - Move initialization logic from `Start()`/`OnEnable()` to `OnEnterState()` - Implement `OnRestoreState()` for restoration logic - Implement `SerializeState()` if state has data to save ## Benefits ✅ **Clean separation** - Normal vs restore logic is explicit ✅ **No timing issues** - Explicit method calls, no flag-based checks ✅ **Opt-in** - States choose to participate in save/load ✅ **Backward compatible** - Existing states work without changes ✅ **Centralized** - StateMachine manages registration automatically ✅ **State-level data** - Each state manages its own persistence ## Migration Checklist For each state machine that needs saving: - [ ] Set Save ID in StateMachine inspector - [ ] Identify states that need save/load - [ ] For each state: - [ ] Move `Start()` logic to `OnEnterState()` - [ ] Implement `OnRestoreState()` (handle empty data case) - [ ] Implement `SerializeState()` if state has data - [ ] Test normal gameplay flow - [ ] Test save/load flow ## Completed Migrations ### ✅ GardenerChaseBehavior - Saves tween progress and completion state - Restores gardener position without animation - Resumes tween from saved progress if not completed ## Notes - All changes to Pixelplacement code are marked with `// === APPLE HILLS SAVE/LOAD INTEGRATION ===` comments - If Pixelplacement framework is updated from GitHub, reapply these changes - SaveLoadManager.IsRestoringState global flag is NOT used - each StateMachine has its own IsRestoring flag - States can check `StateMachine.IsRestoring` if needed, but typically don't need to