Progress Update #26
Saving and Loading a Solar System
As per the roadmap, I did a week long sprint implementing a saving system for the game.
Attempt #1:
I started out the same way I've done for every Unreal project in the past; making a SaveGame class and shoving all the variables I wanted to save into it. I got trough the player variables, positions and velocities, and orbit visualization settings, but when I looked at the mountain of variables I would need to save for the data assets I began to rethink my approach. Manually writing all those variables would be much too time consuming, so with three days left in the sprint I scrapped the system and started again.
Attempt #2 (the real one):
(I will be skipping out on recounting the entire research phase and jump to the solution I used in the interest of brevity)
Because I didn't want to store individual variables in the SaveGame, I looked at how I could save full actors. This rabbit hole lead me to a post on the answerhub written by a developer working on Fortnite back in 2014 which became my number 1 resource through the rest of the sprint. The method described in the post is to essentially serialize the actor to a bytestream and add that to the SaveGame. Then when you want to load the actor back in, you just do the opposite. If I'm being 100% honest I don't fully understand all of this system (like when the post says that it's using a "proxy wrapper"), but I think I know just enough to explain how I used it.
Saving Actors
First, we must define a struct that will hold the necessary data for each actor that we save. ActorData is the bytestream I mentioned earlier:
Then we add variables of this struct type to the SaveGame class for actors we want to save. Optionally arrays of structs can be used to save multiple actors of the same class for example:
Note: these struct variables must also be UPROPERTY() |
Next is the part I don't understand too well. We create a "proxy wrapper based on the Objects-as-strings proxy wrapper" (Note: official documentation for the Objects-as-strings proxy wrapper seems to not exist anymore so you need to #include "Serialization\ObjectAndNameAsStringProxyArchive.h"). From what I understand, this is what allows properties marked with the SaveGame specifier to be serialized (line 6 in the image?):
Line 18 is the one that serializes the celestial body to the archive object |
Loading it back in then looks like this:
Final thing to check is to make sure all properties you want to be saved are marked as UPROPERTY(SaveGame). Then once all that is done, any property will be automatically saved and loaded.
Saving "DataAssets"
On this journey I figured out that data assets do not like to be saved in this way. Luckily for me though, I figured out that I don't need something as complex as a data asset to save these variables and I could use simple structs instead.
I was able to put all the variables I wanted to save from the data asset into a struct, and convert the old data asset to a simpler UObject to handle delegates and functions. However, I am still yet to test how to save these objects when they are created dynamically at runtime.
Saving Components of Actors
The final feature I worked on for this sprint was saving components of celestial actors. For example gas giants can have ring systems and terrestrial planets can have an atmosphere and ring systems. This went through several iterations that I'll go through quickly noting why they didn't work:
- TMap of key FName and value FActorRecord. The name would be the name of the parent and the record would be the component's data.
- Worked fine only that keys of maps are unique meaning that each body would only load back in with one component maximum.
- TArray of TTuple with key and value the same as the map. Theoretically a map that allows duplicate keys
- TTuple is not allowed to be a UPROPERTY()
- TMultiMap with keys and values the same as attempt 1. This is the correct way to get a map that allows duplicate keys.
- TMultiMap is also not allowed to be a UPROPERTY(), wtf
- A TArray of a custom struct called FComponentData which would hold an FName (name of the parent actor) and an FActorRecord (the data of the actual component)
- Works a treat when playing in editor but causes horrifically hard to debug fatal errors in a packaged game.
After taking a break for lunch and seeing the problem with a fresher mind I realized that I could just make a struct called FComponentRecord that would hold the exact same stuff as FActorRecord with the addition of an FName to hold the name of the attached parent actor. This worked perfectly first try and I really don't know why it took me so long to figure it out.
Comments
Post a Comment