Files
AppleHillsProduction/docs/state_machine_save_load_integration.md
2025-11-03 01:34:34 +01:00

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:

  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)

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() 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