Files
AppleHillsProduction/docs/state_machine_save_load_integration.md

202 lines
6.1 KiB
Markdown
Raw Normal View History

# 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<ChaseSaveData>(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