Skip to content

State & Save

Capture and restore object state - positions, rotations, physics, UI - with one component. Save to disk for persistent progress.

Overview

Need to reset an object to its starting position after a player dies? Want to save game progress to a file and load it back later? The State package handles both. You add a single component to a GameObject, pick what to capture (transform, rigidbody, UI state, etc.) from a dropdown, and trigger save/restore through commands or automated triggers. For persistent saves, built-in file commands serialize everything to JSON.

The package handles two distinct concerns:

  • Object state - snapshot individual component properties (position, velocity, UI values) and restore them later.
  • Scene bindings - record which objects occupy which slots (e.g., items on attachment points) and restore that topology after a load.

Both systems are data-driven: you configure what to save in the Inspector using class selection, and trigger save/restore through commands or automated triggers.


Object State

Each state type knows how to capture and restore one aspect of a GameObject or component. You pick the states you need from the Inspector, and the holder manages the capture/restore lifecycle for you.

ISaveState

The core interface behind every state type. Each implementation captures and restores one property:

public interface ISaveState
{
    DatabaseKey Key { get; }
    bool Capture(GameObject target);
    bool Restore(GameObject target);
    bool HasCapturedData { get; }
    void Clear();
}
Member Description
Key Optional DatabaseKey for stable lookup. Null means index-only access.
Capture() Snapshots the current state from the target GameObject.
Restore() Applies the previously captured state back to the target.
HasCapturedData Whether Capture() has been called and data is available.
Clear() Discards captured data.

The generic variant ISaveState<T> adds a CapturedValue property for reading back the stored value programmatically.

SaveState<T>

Abstract base class for states targeting a specific Component type. Handles component resolution and the capture/restore lifecycle - subclasses only implement OnCapture(T) and OnRestore(T).

[Serializable]
public abstract class SaveState<T> : ISaveState where T : Component
{
    protected abstract void OnCapture(T target);
    protected abstract void OnRestore(T target);
}

Each SaveState<T> supports an optional target override (IGameObjectValue). If set, the state targets a different GameObject than the holder's default. This lets a single holder manage states across multiple objects.

Built-in State Types

The package ships with ready-made states for common Unity components:

Category State Type What It Captures
Transform TransformFullState Position, rotation, and scale
TransformLocalState Local position, rotation, and scale
TransformPositionState World position only
TransformRotationState World rotation only
TransformScaleState Local scale only
TransformPositionRotationState World position and rotation
Rigidbody RigidbodyFullState Velocity, angular velocity, kinematic, gravity, mass, drag
RigidbodyVelocityState Linear and angular velocity
RigidbodyKinematicState Kinematic flag only
Rigidbody2D Rigidbody2DFullState Full 2D physics state
Rigidbody2DVelocityState 2D velocity
Rigidbody2DBodyTypeState Body type enum
UI CanvasGroupState Alpha, interactable, blocks raycasts
ImageFillState Image fill amount
SliderValueState Slider value
Rendering LightFullState Light properties
VFXEnabledState Visual Effect enabled flag
VideoPlayerState Video player state
Physics ColliderEnabledState Enabled state of all child colliders
Navigation NavMeshAgentState NavMeshAgent properties
GameObject GameObjectLayerState Layer assignment

Writing a Custom State

Extend SaveState<T> to capture any component property:

[Serializable]
[JungleClassInfo("Renderer Color", "Saves and restores renderer material color.", null, "Rendering")]
[DefaultStateKey("Renderer Color")]
public class RendererColorState : SaveState<Renderer>
{
    private Color color;

    protected override void OnCapture(Renderer target)
    {
        color = target.material.color;
    }

    protected override void OnRestore(Renderer target)
    {
        target.material.color = color;
    }
}

The [DefaultStateKey] attribute auto-assigns a DatabaseKey asset when the state is added to a holder, so users do not need to create keys manually.


ObjectStateHolder

The main component for managing state. Add it to any GameObject via Add Component > Jungle > Save > Object State Holder.

Configuration

Field Description
Target Source IGameObjectValue that provides the default target for all states.
States List of ISaveState implementations to manage. Select types via class selection.
Capture On Begin If enabled, automatically captures all states when the begin trigger fires.
End Action Action when the end trigger fires: None, Clear, Restore, or Restore And Clear.

ObjectStateHolder extends TriggeredComponent, so its begin/end triggers control the lifecycle automatically. For manual control, call the public API directly.

API

// Capture and restore everything
holder.CaptureAll();
holder.RestoreAll();

// Target a specific state by key
holder.Capture(myKey);
holder.Restore(myKey);

// Clear captured data
holder.ClearAll();
holder.Clear(myKey);

// Restore then clear in one call
holder.RestoreAndClearAll();

// Read back a captured value
float speed = holder.GetValue<float>(speedKey);
Vector3 pos = holder.GetValue<Vector3>(positionKey);

Commands

Commands integrate state operations into the Jungle action system. Use them with ProcessLauncher or any command runner to trigger save/restore from events.

Command Description
Capture State Calls CaptureAll() or Capture(key) on a holder.
Restore State Calls RestoreAll() or Restore(key). Optionally clears data after restoring.
Clear State Calls ClearAll() or Clear(key).
Save State To File Serializes all captured states to a JSON file on disk.
Load State From File Deserializes states from a JSON file back into a holder. Optionally restores immediately.

File Persistence

SaveStateToFileCommand and LoadStateFromFileCommand handle full disk serialization. They use Unity's JsonUtility to serialize each state entry with its type name and key, producing a portable JSON snapshot.

// The file path is provided by an IStringValue - 
// use a constant, a PlayerPrefs lookup, or Application.persistentDataPath.

The file path is configured as an IStringValue, so it can come from a constant string, a runtime path builder, or any other value provider.


Value Providers

The package provides value providers for reading captured data from a holder without direct API calls. These plug into any IValue<T> field across the framework.

Value Provider Output Type Description
FloatFromStateHolder float Reads a float from a holder by key.
Vector3FromStateHolder Vector3 Reads a Vector3 from a holder by key.
QuaternionFromStateHolder Quaternion Reads a Quaternion from a holder by key.
// Example: feed a captured position into a DragTarget or pipeline
[SerializeReference] [JungleClassSelection]
private IValue<Vector3> targetPosition;
// Select "Vector3 From State Holder" in the Inspector,
// then assign the holder and the position key.

Scene Bindings

Scene bindings capture which objects occupy which slots - a higher-level topology snapshot. This is useful for systems like inventory grids or attachment point layouts where you need to know not just an object's transform, but where it belongs logically.

IBindableSlot

Implemented by components that represent a slot capable of holding an identified object.

public interface IBindableSlot
{
    DatabaseKey BindingKey { get; }
    ScriptableObject HeldIdentifier { get; }
    bool IsEmpty { get; }
    void RestoreBinding(ScriptableObject item);
    void ClearBinding();
}

Extend the abstract BindableSlot base class (a MonoBehaviour) for concrete implementations. This gives BindingRecorder a serializable type to reference.

SceneBinding

A serializable snapshot mapping slot keys to held item identifiers.

var binding = new SceneBinding();
binding.Record(slotKey, itemIdentifier);  // Record a slot's contents

if (binding.TryGet(slotKey, out var item))
    Debug.Log($"Slot holds: {item.name}");

SceneBindingAsset

A ScriptableObject that stores a SceneBinding as a reusable asset. Create via Assets > Create > Jungle > Save > Scene Binding. Use as a design-time template for default slot configurations.

BindingRecorder

Component that captures and restores bindings for a set of slots. Add via Add Component > Jungle > Save > Binding Recorder.

Field Description
Discovery Children scans GetComponentsInChildren<IBindableSlot>(). Explicit uses a manually assigned list.
Explicit Slots List of BindableSlot references (only used with Explicit discovery).
// Capture current slot topology
SceneBinding snapshot = recorder.Capture();

// Restore from a snapshot
recorder.Restore(snapshot);

// Clear all slots
recorder.ClearAll();

SceneBindingSerializer

Utility for JSON serialization of SceneBinding objects at runtime.

// Save to string
string json = SceneBindingSerializer.ToJson(binding, prettyPrint: true);

// Load from string
SceneBinding loaded = SceneBindingSerializer.FromJson(json);

Practical Examples

Resettable Object

Save an object's transform when a level starts and restore it on death or reset.

  1. Add ObjectStateHolder to the object.
  2. Set Target Source to the object itself (use a "Self" GameObject value).
  3. Add a Transform Full state to the states list.
  4. Enable Capture On Begin and set End Action to Restore And Clear.

The holder's begin trigger fires on enable - the transform is captured automatically. When the end trigger fires (e.g., on disable or a game event), the object snaps back to its original position, rotation, and scale.

Save to Disk

Persist state across sessions using file commands.

  1. Set up an ObjectStateHolder as above.
  2. Create a command sequence that runs on "Save Game":
    • Capture State command targeting the holder.
    • Save State To File command with a file path like Application.persistentDataPath + "/save.json".
  3. Create a command sequence that runs on "Load Game":
    • Load State From File command with restoreImmediately enabled.

Inventory Slot Persistence

Save which items are in which slots.

  1. Each slot implements BindableSlot with a unique DatabaseKey.
  2. Add a BindingRecorder to the inventory root with Discovery set to Children.
  3. On save: call recorder.Capture(), serialize with SceneBindingSerializer.ToJson(), and write to disk.
  4. On load: read JSON, deserialize with SceneBindingSerializer.FromJson(), and call recorder.Restore(binding).

Architecture

flowchart TD
    subgraph ObjectStateHolder
        A[ISaveState list]
    end

    subgraph "Built-in States"
        B[TransformFullState]
        C[RigidbodyFullState]
        D[CanvasGroupState]
        E[...]
    end

    subgraph Commands
        F[CaptureStateCommand]
        G[RestoreStateCommand]
        H[SaveStateToFileCommand]
        I[LoadStateFromFileCommand]
    end

    subgraph "Value Providers"
        J[FloatFromStateHolder]
        K[Vector3FromStateHolder]
    end

    B --> A
    C --> A
    D --> A
    E --> A
    F -->|CaptureAll| ObjectStateHolder
    G -->|RestoreAll| ObjectStateHolder
    H -->|JSON serialize| ObjectStateHolder
    I -->|JSON deserialize| ObjectStateHolder
    J -->|GetValue| ObjectStateHolder
    K -->|GetValue| ObjectStateHolder
flowchart LR
    subgraph "Scene Binding System"
        L[BindingRecorder]
        M[IBindableSlot implementations]
        N[SceneBinding snapshot]
        O[SceneBindingSerializer]
    end

    L -->|discovers| M
    L -->|Capture| N
    N -->|Restore| L
    O -->|ToJson / FromJson| N