Make a system save¶
Designer · 30 min · No code
Result: your own state is written into XenoCore's save file and comes back on Continue or Load, with old save files still loading. The worked example is a lever that stays pulled; the same steps save a bestiary, a crafting bench or anything else, without editing XenoCore.
Before you start¶
- Save and front end: slots, the autosave, what XenoCore itself saves.
- Add an interactable object builds the lever used below. Any actor works.
How it works¶
XenoCore saves its own systems (party, levels, bag, quests, chests) itself. Anything else takes part as a save participant: an actor, component or object that stores its own record in the file under its own key. XenoCore never needs to know what the record holds.
| Piece | What it is |
|---|---|
| SaveGame flag on a variable | Marks what to save. Only flagged variables are written |
XenoCore Save Participant component (UXenoCoreSaveParticipantComponent) |
Add it to an actor and that actor's flagged variables are saved. No graph needed |
XenoCore Save Participant interface (IXenoCoreSaveParticipant) |
Optional. Gives an object a key, a data version and two hooks |
| Register Save Participant / Unregister Save Participant | On the save subsystem, for anything the component does not cover |
What is found without any registration: the player controller, the player state, the game state, and every component on those three, if it implements the interface. So a party-wide system on a controller component only needs the interface.
Order. On load, participants are restored after all of XenoCore's own systems, so a participant that reads the party or the bag while restoring sees the loaded state.
Records that nobody claims are kept. If a participant is missing this session (a streamed-out actor, a system removed for one build), its record is carried into the next save instead of being lost.
Steps¶
1. Flag what to save¶
- Open your Blueprint (here
BP_MyGame_Lever). Select the variable that holds the state, such as a booleanbPulled. - In its Details, open the advanced section under Variable and tick SaveGame.
- Repeat for every variable that must survive a load.
Fails silently: a struct saves empty
Only flagged properties are written, and that includes the fields inside a struct variable. Flag a struct variable but not the struct's own fields, and it saves as all defaults. For a Blueprint struct, open the struct asset, expand each field with its arrow and tick Save Game there too.
2. Add the component¶
- In Components, add XenoCore Save Participant.
- Leave Save Key empty for an actor placed in a level. Its key is then made from the map and the actor's name, which is unique.
- For an actor spawned at runtime, set Save Key to a unique name, such as
MyGame.Forge.
That is all the lever needs. The component registers the actor at Begin Play and unregisters it at End Play. Unregistering records the actor's current state first, so an actor that streams out and back in, or a level left and re-entered, gets its state back.
Fails silently: two actors with one key
A spawned actor with an empty Save Key is keyed by its class, so a second copy has the
same key. Only the first one registered is saved; the other logs an error
(XenoCoreSave: ... registered with save key '...', already used by ... and, at save time,
duplicate save participant key). Give every copy of a spawned actor its own key.
A key is forever
Records are found by key. Renaming a key, or renaming a placed actor whose key is derived from its name, leaves every existing save holding a record nobody claims, so the actor loads with its defaults. Settle keys and placed-actor names before release.
3. React to a load (optional)¶
The component restores the variables and nothing else. If the lever must also look pulled, use the interface's hooks:
- Class Settings › Interfaces › Add:
XenoCore Save Participant. - Implement On Save Data Restored. It runs after this actor's record has been applied, and is
given Saved Version. Here, snap the lever mesh to the pulled pose if
bPulledis true, and call Set Interact Enabled with the opposite. - Implement On Pre Save Capture only if some state lives outside flagged variables: copy it into them there. It runs just before this actor is written.
On Save Data Restored runs only when there was a record
An actor the save has never heard of keeps its defaults and hears nothing. To react to every load, bind the save subsystem's On Save Loaded instead; it fires once the whole load is applied, participants included, and passes the slot index.
4. Version your data (optional)¶
Implement Get Save Data Version and return 1. When you later change what you save (rename a
variable, change what a number means), return 2, and in On Save Data Restored convert data
that arrived with Saved Version 1.
Adding a new flagged variable does not need a new version: an older record simply has no value for it, and the variable keeps its default.
Your version is your own. XenoCore's save file has its own version, which you never change. A save made with a XenoCore release older than save participants loads as "no participant has saved anything yet".
5. Other kinds of participant¶
| You have | Do this |
|---|---|
| A component on the player controller, player state or game state (a party-wide system) | Implement the interface on the component. It is found automatically; no save participant component, no registration |
| An actor that is not placed and not spawned by you, or a plain object | Call Register Save Participant on the save subsystem with it (and a Save Key if it can exist twice), and Unregister Save Participant from its End Play |
| A record you want gone (a quest line reset) | Forget Save Record with its key; Has Save Record checks for one |
A registered object does not have to implement the interface; its flagged variables are saved either way. The interface only adds the key, the version and the hooks.
If a record already exists when something registers (it was loaded, or it streamed out earlier), it is applied at once, unless a load is still being applied, in which case it waits and is applied with everything else.
For programmers: C++ participants
A C++ class implementing IXenoCoreSaveParticipant may override CaptureSaveData(FArchive&)
and RestoreSaveData(FArchive&, int32 SavedVersion) to write whatever it likes instead of its
UPROPERTY(SaveGame) fields. The archive stores object pointers as paths. The defaults
(GetSaveParticipantKey, GetSaveDataVersion, OnPreSaveCapture, OnSaveDataRestored) are
BlueprintNativeEvents: override the _Implementation versions. All of it is exported, so this
works from your own module; see Add a mechanic by subclassing.
XenoCore's own file version is EXenoCoreSaveVersion (currently Gems = 5; participant
records arrived in ParticipantData = 3).
Rules that keep old saves loading¶
These are the rules XenoCore's own saved data follows. Follow them in your records too.
- Keep state on something that outlives the world for anything party-wide: a controller component, not a pawn. A benched party member has no pawn.
- Store asset references as soft references or paths, never as object pointers to live actors. An asset that has since been deleted is then skipped instead of breaking the load.
- Store enums and table rows by name, never by number, if the list may grow.
- Recompute what you can. Save only what nothing else remembers.
- Guard starting grants. If your system gives something at the start of a game (a first entry, a free item), check Was World Loaded From Save on the save subsystem first, or every load gives it again.
Test it¶
- Place the lever in
Maps/L_XenoCore_Gym, press Play, and pull it. - Open the console and enter
XenoSave 1. The Output Log reportsXenoCoreSave: captured 1 save participant(s). XenoSaveDumpprints each slot; slot 1 now lists1 participant records.- Stop, press Play again, and enter
XenoLoad 1. After the map reloads the lever is still pulled and offers no prompt. The log linerestored ... 1/1 participant record(s)confirms it. - Load a save made before you added the component: it loads, and the lever starts unpulled.
If it doesn't work¶
Loaded, but the value is back to its default
The variable is not flagged SaveGame, or it is a struct whose fields are not flagged. Or the actor was renamed since the save was written, which changed its key.
Only one of several spawned copies keeps its state
They share a key. Set Save Key per copy. The Output Log names the clash.
The value loads, but the actor does not look right
The component restores variables only. Apply them in On Save Data Restored.