The common belief: UPROPERTY(SaveGame) controls what UGameplayStatics::SaveGameToSlot writes. It doesn’t. (Checked in GameplayStatics.cpp + Property.cpp.)
What actually happens:
SaveGameToSlot→SaveGameToMemoryserializes the wholeUSaveGamethrough a proxy archive, and never turns on the SaveGame flag on that archive (ArIsSaveGamestays false).FProperty::ShouldSerializeValueonly skips non-SaveGameproperties when the archive is in SaveGame mode. It isn’t here, so the flag is ignored → every non-Transientproperty gets saved, markedSaveGameor not.- The writer is persistent, so the properties it does skip are the
Transientones.
So:
- The
SaveGameflag only matters when you serialize with an archive whereArIsSaveGame = true. It does nothing on the plainSaveGameToSlotpath. - On a
USaveGame, leavingSaveGameoff does not mean “won’t be saved.”
Making the SaveGame flag actually work
Serialize the object yourself and flip the flag on the archive. Now only UPROPERTY(SaveGame) fields are written:
TArray<uint8> Bytes;
FMemoryWriter Writer(Bytes, /*bIsPersistent*/ true);
FObjectAndNameAsStringProxyArchive Ar(Writer, /*bLoadIfFindFails*/ false);
Ar.ArIsSaveGame = true; // THIS is what makes the SaveGame flag filter
Object->Serialize(Ar); // only SaveGame-marked properties end up in Bytes
Loading mirrors it: FMemoryReader + FObjectAndNameAsStringProxyArchive(Reader, true) + Ar.ArIsSaveGame = true, then Object->Serialize(Ar). This is the pattern used to save/restore actor & component state into a byte blob.
⚠️ Gotcha: on the plain SaveGameToSlot path, delegates, runtime UObject pointers, and caches on your save object get serialized too. On load they come back — so a delegate you bound before is restored with its old binding, and binding again reads as a double-add (“delegate already bound”). Dangling object refs are the same story.
✅ Rule: mark anything runtime-only on a save object as Transient. Don’t trust “no SaveGame = not saved” — that’s not how SaveGameToSlot works.
TL;DR
SaveGameToSlotsaves everything exceptTransient, and ignores theSaveGameflag.- The
SaveGameflag only works with your own archive whereArIsSaveGame = true. - Put runtime state (delegates, live refs, caches) behind
Transient.