I’m working on a strategy game (campaign/battle style), and I have a bunch of interconnected AActor-based objects like ARegion, AArmy, and a CampaignPawn that holds the world together. Everything works great… until I try to save and reload the campaign.
Here’s the kicker:
When I destroy the CampaignPawn (like when saving or switching maps), every ARegion or AArmy that holds a pointer to it now has an invalid reference. ARegion and AArmytheir internal pointers to other classes also break if I try to duplicate or serialize the objects, to load later.
Now I’m doing this everywhere. Army needs to store the region it’s in? That’s a pointer plus an ID. Region needs to store neighboring regions? Same deal. Every single object that references something else now has to carry a stable FGUID, and use that to reconnect during load.
It’s doubling my data. It’s tedious. And it only exists for saving. Game logic runs on the pointers , which are useless after load unless I re-hook them all manually.
Using only FGUIDs and looking things up via a registry/map: Feels super alien to game logic. Now I’m resolving IDs every time I want a neighbor or location. Gross.
Moving everything to UObjects: Doesn’t help. The internal references still break unless I rebuild them manually, which is a mess and needs and extra variable FGUIDs for everysingle reference you use to another class inside a class so to respawn them correctly.
You need two loops - in the first one you spawn everything that is missing (all the actors you referenced) and call UObject::Rename on them so that the objects have the old path.
In the second loop - you deserialize all the data for them. Then the references will work as if nothing happened.
I dont understand how renaming it would reconnect the references to the classes that are now being spawned from scratch when loading back the campaign?
Could you give me some tips here?
I think even if you rename them the old name, the old data is now invalid, so it wont work…?
Is there a tuts for this, or any examples?
Ah so you are using the old path as the way to identify the references to the objects, is that correct? is the path the only thing reliable for this? I tried something like this before using GetFName() and it didnt even allow me to rename the object. will try this now,a nd get back to you, thanks
Yeah, i was just getting intrigued with it.
So this is what im doing.
Campaign level ends and battle level is about to start.
So I save the Regions in Saved_All_Regions, then save also the Armies (that are contained in each region), and save also the Buildings that are in each region.
Then after the battle is finished i need to restore back the campaign as it was before the battle.
So im not very sure, but from what i understood from your explanation, i need to rename the Newly spawned regions with the Old name (GetPathName()), so I go on the GameInstance get all the Saved_All_Regions and just assign the new respawned regions (All_Regions) with the old names.
Isn’t this renaming what is going to allow the armies to identify the region they were in?
This is super confusing to me.
Edit:
So for example the Regions have this data witht references to Armies inside it:
So how am i supposed to respawn the regions, if when you change levels and respawn the armies and regions you lose the references to the NeighboringRegions and also the CurrentArmiesInRegion…?
UCLASS()
class TOPSYMBOL_API ARegion : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ARegion();
UPROPERTY(BlueprintReadWrite)
TArray<FVector2D> BorderTiles;
UPROPERTY(BlueprintReadWrite)
TArray<URegionObj*> NeighborRegions;
UPROPERTY(BlueprintReadWrite)
TArray<UArmyObj*> CurrentArmiesInRegion;
UPROPERTY(BlueprintReadWrite)
FColor Color;
};
Would this also keep the references of the objects alive? Cant wrap my head around this. Is there some tuts or example for me to follow that explains how this works?
It seems we put different meanings into the words “save/load”.
I mean the object serialization that
Auran13 is talking about.
It is after this that the “magic recovery references” occurs.
It is not enough to simply rename the objects you reference (but it is necessary), you also need to deserialize (load) the objects that contain those references.
to deserialize all objects knowning that their references exist and are properly named.
it is a weird system, in that i dont know why they dont use a FGuid over a FName because where it can fail is loading different levels, if you have level1 with Crate_1 and level2 with Crate_1 it could get confused.
so you have to handle that situation (ie save persistant data and level data seperately)
Thanks sensei.
So far im trying something that might be stupid but its working.
Though its only for transition between Campaign Level and Battle Level.
So before I change level,
I duplicate the UObjects using:
TMap<UArmyObj*, UArmyObj*> Old_New_Army_Map;
for (int32 i = 0; i < CampaignMapManager->All_Armies.Num(); ++i) {
UArmyObj* Original = CampaignMapManager->All_Armies[i];
UArmyObj* SavedArmy = DuplicateObject<UArmyObj>(Original, GameInstance);
Old_New_Army_Map.Add(CampaignMapManager->All_Armies[i], SavedArmy);
This duplicated the UObject perfectly into the GameInstance. The DuplicateObject is very convenient because you dont need to build the whole project with all the variables.
Then I use the TMap Old_New_Army_Map, to keep track of what the new duplicate armies (backup) correspond to the armies in the Campaign (about to become invalid).
Before I launch the Battle level, I fix all the references in the duplicated UObjects. This is easy because im creatin a TMap. So In the duplicated UObjects you have references to Armies and Regions, you fix those references using the TMap.
Then when you load back the Campaign, do something similar (though some exceptions may be necessary), Spawn all the regions, armies, using again TMaps, that relate the GameInstance UObjects, with the newly respawned UObjects. And then fix them by iterating through them.
And voilá it is working. I did this a bit by intuition… so im not sure…
Is this good enough? Is there something im missing? Do you recommend I do it some other way, or maybe even use SPUD ?
Let me know before i do my whole project this way, only to shoot foot later
This will only work while the game is running. It won’t be possible to implement a save system this way.
And I’m not sure (haven’t tested) that the references in the duplicated objects will lead to where you expect…
Is it worth making two similar systems, where one of them only does half the work?
UPD
Serialization does this automatically (if you do Rename before)