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.
- Add ObjectStateHolder to the object.
- Set Target Source to the object itself (use a "Self" GameObject value).
- Add a Transform Full state to the states list.
- 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.
- Set up an
ObjectStateHolderas above. - 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".
- Create a command sequence that runs on "Load Game":
- Load State From File command with
restoreImmediatelyenabled.
- Load State From File command with
Inventory Slot Persistence¶
Save which items are in which slots.
- Each slot implements
BindableSlotwith a uniqueDatabaseKey. - Add a BindingRecorder to the inventory root with Discovery set to Children.
- On save: call
recorder.Capture(), serialize withSceneBindingSerializer.ToJson(), and write to disk. - On load: read JSON, deserialize with
SceneBindingSerializer.FromJson(), and callrecorder.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