6.1 KiB
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:
OnEnterState()- Called when entering state during normal gameplayOnRestoreState(string data)- Called when restoring state from save fileSerializeState()- 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 entryOnRestoreState(string data)- virtual method for restorationSerializeState()- virtual method for serialization
2. StateMachine.cs
Location: Assets/External/Pixelplacement/Surge/StateMachine/StateMachine.cs
Added:
- Implements
ISaveParticipantinterface saveIdfield (serialized, set in inspector)IsRestoringproperty (public, readable by states)HasBeenRestoredproperty- Modified
Enter()to callOnEnterState()when not restoring SerializeState()implementation - collects state name + state dataRestoreState()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 tweenOnRestoreState(string)- positions gardener without animation, resumes tween from saved progressSerializeState()- saves tween progress and completion state
Usage Guide
For Simple States (No Data to Save)
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)
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()andOnEnable()are unaffected - Only states that need save/load functionality need to be migrated
Setup in Unity
-
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
-
Migrate States:
- For each state that needs saving:
- Move initialization logic from
Start()/OnEnable()toOnEnterState() - Implement
OnRestoreState()for restoration logic - Implement
SerializeState()if state has data to save
- Move initialization logic from
- For each state that needs saving:
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 toOnEnterState() - Implement
OnRestoreState()(handle empty data case) - Implement
SerializeState()if state has data - Test normal gameplay flow
- Test save/load flow
- Move
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.IsRestoringif needed, but typically don't need to