Concept: World State & Identity
World-actor state is stored in slabs - one UCrimsonSaveWorldData per level - held at UCrimsonSaveGameManagerSubsystem (GameInstance) scope, so they outlive any single level or UWorldSubsystem and survive streaming and seamless travel. Each slab is a TMap<FGuid, FCrimsonSaveWorldActorData>. An actor opts in by implementing ICrimsonSaveableActor; on every save the world subsystem scans loaded levels and captures each one automatically.
OnActorStateChanged / OnActorDestroyed are optional incremental hooks, not a requirement. bSaveWorldStateAfterEveryChange only changes when the disk flush happens, not whether your actor is captured.What is captured automatically
| Captured | Notes |
|---|---|
| Transform | Movable actors only (root mobility != Static). |
Actor UPROPERTY(SaveGame) fields | The actor's own tagged properties, via the engine proxy archive. |
Component UPROPERTY(SaveGame) fields | Cascades into every component - state on health / inventory / movement components persists with no extra code. |
| Non-property state | Hidden flag, physics & movement velocities, and pawn controller rotation - no SaveGame flag needed. |
| Cross-object references | SaveGame AActor* / TWeakObjectPtr<AActor> pointers are stored as stable GUIDs and re-resolved to live instances after load (same level or persistent level). |
| Custom payload | Optional FInstancedStruct from GatherActorSaveData for anything beyond the above. |
Identity
Every saveable actor is keyed by a stable GUID so it can be matched across sessions:
| Actor kind | Identity key |
|---|---|
| Level-placed | The engine-assigned ActorGuid (set in the editor, stable across runs). |
| Runtime-spawned | A session FGuid assigned by the subsystem; the actor's class (TSoftClassPtr) is recorded so it can be re-spawned on load. |
Capture and restore flow
- Save - the subsystem scans loaded levels for
ICrimsonSaveableActoractors and writes each into its level slab: transform, the actor'sSaveGameblob, per-componentSaveGameblobs, non-property state, and cross-object reference GUIDs. - Flush - slabs are written to
WorldSlab_<Level>.savin the active slot (as part of the main save, or viaSaveWorldState). - Load - slabs are read back into memory when the slot loads; each level's slab is applied when the level is present (
LoadWorldState, or automatically as sublevels stream in). - Resolve - after every actor in a level is restored / re-spawned, a deferred pass rewrites
SaveGameactor-pointer properties from their stored GUIDs to the live instances.
Deletion
Destruction is recorded from the engine's actor-destroyed event on the server, so only a real Destroy() removes an actor from the save - an actor simply being absent does not.
| Actor kind | On destroy | Save-file cost |
|---|---|---|
| Runtime-spawned | Its slab entry is removed entirely - it simply stops being saved. | None - a removed entry means nothing to re-spawn. |
| Placed (level) | A minimal GUID-only tombstone is written; the copy reloaded from the level is destroyed on load. | A few bytes, bounded by how many map actors you actually delete. |
ShouldRespawnAfterDestroyed() (default false) and return true to skip the tombstone for a placed actor, so it respawns from the level on the next load. Runtime actors ignore this flag. See How-To: Persist a Level Actor.ICrimsonSaveableActor overrides
All interface methods are optional BlueprintNativeEvents. Implement the interface and you are done; override these only for custom state or lifecycle handling.
| Override | When used |
|---|---|
GatherActorSaveData() | Return an FInstancedStruct for data that cannot be expressed as UPROPERTY(SaveGame). Empty (default) means only auto-captured state is saved. |
RestoreFromSaveData(ActorData) | Receives the FInstancedStruct. Transform, SaveGame fields, and component state are already restored before this runs. |
OnPreRestoreState() | Before any restoration. Disable physics, clear containers. |
OnPostRestoreState() | After all restoration. Re-enable physics, broadcast events. |
ShouldRespawnAfterDestroyed() | Return true so a destroyed placed actor respawns from the level instead of staying deleted. Default false. Runtime actors ignore it. |
Streaming & World Partition
A sublevel's slab is applied when it streams in and its actors are re-captured just before it streams out, so revisiting a region restores it. World Partition runtime cells stream as levels through the same path, gated by bSupportWorldPartition (default on).
SaveGame-only property never reaches clients unless it is also Replicated - see Concept: Multiplayer & Player Scoping.Driving world state manually (optional)
With bIncludeWorldStateInMainSave (default), a normal save persists world actors too. To drive world state on its own: