I’ve read a couple of similar questions, but nothing that seems to cover what I’m trying to do. I’m building a small open world single player rpg with the option to create up to 3 different characters. At the start of the game, the player can either choose to load an already saved character and continue playing from where they logged out OR create a new one (assuming there is an available slot).
If they load a saved character, it should load the game from the relevant slot, cast to the SaveGame file for that slot and set the ‘ActiveSlot’ variable value to the matching slot number. Then during the game, whenever any variable needs to be read/saved, it should reference the ActiveSlot number and read/update the data for whatever variable it needs on the matching SaveGame file. This is the best solution I could find that would do what I wanted (although I’m open to other suggestions if there’s an easier way).
When creating a new character, the process is similar to the above. It looks to see if a Game slot exists called ‘Slot 1’, and if not it will create it. If it already exists, it checks for ‘Slot2’ and if not creates that one. It then creates a SaveGame object with all of the values for its variables set to the defaults and the variable ‘ActiveSlot’ set to 2.
HOWEVER, I’m having issues reading/writing to the SavedGame data file during the game. Are there any example blueprints anyone can show me for how they:
a) pull a value from the SaveGame data file after checking the active slot (i.e. display current number of coins in inventory when clicking on UI button)
and b) update a value on the SaveGame data file after checking the active slot (i.e. update character profession variable after they click ‘Ok’ on an NPC dialogue box).
I’m using SaveGame data files / g instance to store my data. I have several different ones storing fixed values for things like items, enemy type, and anything else that isn’t character-specific that I need to refer to during gameplay. I know it’s a bit of a ‘dirty’ method, but so far it’s the best solution I’ve found for what I’m trying to do.
Generally save game data is not used in that way, so the API’s aren’t really designed around working that way.
Usually when saving the game, you would create your save game object, fill it out with all the data that you want to save, write it to the disk and then throw it away.
And when loading, you read it from disk, apply the data from the save to your game data and then throw it away.
It seems wasteful, but has a lot of benefits like not tying all your systems to the save. You can also write your gameplay systems so they have the data that makes sense for the runtime but only put the data you need into the save.
It also makes it more difficult for everything to function if you can no longer relay on member variables but have to find your data inside the save game every time.
Instead of that, just use a SaveGame slot called “Characters” that stores an array of all created characters. That way, you don’t have to check for each slot individually, and you can have as many characters as you want. Once the player selects a character, load the slot of that character. If the player makes a new character, add it to the character array and make a new SaveGame slot for the character.
If you mean you’re splitting your save game up into multiple SaveGame classes/slots, that’s good. A SaveGame is not a literal “save game,” it’s just a class with variables that can be saved & loaded from disk. You don’t have to put your entire game in one class/slot, you can use it however you like.
Here’s documentation for it. You just get (read) or set (write) variables in it like any other class. When you want to save it, you just save it to a slot. If you need a character-specific example, I can post a simple one.
That wasn’t really my point. There’s a lot of things you can do. You can make a game using only global variables. You can make a game that wipes the user’s hard drive. That doesn’t make them a good reason to do the thing.
My 1st best argument against it is data representation. The simplest implementation of save data requires all the members being UPROPERTIES. Which is fine, except that there are a lot of constructs which can’t be represented as a UPROPERTY. And if your runtime representation isn’t compatible with UPROPERTY, you have to convert back and forth anyway. Now that’s primarily a C++ problem and this is a Blueprint forum so that’s not directly a problem, but it’s about habit, patterns and best practices.
The 2nd best argument is data locality. If I’ve got 12 guns in my inventory, what is easier? Using the member variable of my gun blueprint for ammo? or accessing the savegame, finding the element that corresponds with the instance data I care about, and reading from there? This might be easier with structure pointers, but you can’t do that in Blueprint (it’s not really that ideal for UE C++ either).
Lastly, it’s about robust code. The fewer dependencies code has, the better and outside of serialization specific functionality, there’s no reason for most code to be dependent on save games. Take the gun example again, if you’re not dependent on save games you can more likely do some minor multiplayer setup and it just works. But if you’re trying to read all sorts of variables out of a save all the time you will never be able to share that code between your single player and multiplayer projects. You’ll also make it that much harder to re-use across projects in general as you now must setup a save system before you can do anything else.
Ok, the reason of keeping it in memory & using it directly is so you don’t waste time copying all the data out of the SaveGame, as well as waste time creating and refilling the SaveGame object each time you want to save. Even the docs mention keeping the SaveGame object:
Depending on what kind of data your SaveGame type contains, you may want to keep a copy of it, or simply use the data and discard the object.
It’s just an object that can be saved; don’t get confused by the name, as the docs also mention:
The meaning of “saving the game” can vary considerably from one game to the next.
Lol, I don’t mean run your entire game off a SaveGame, obviously; put your variables in the place that makes the most sense. My point was that certain variables (like a score) can be put in the SaveGame object directly; knowing that you’re going to be saving them, it makes sense to put them in a place ready to be saved. This makes it so when you need to save, you literally just save; you don’t do any copying because the variables are already where they need to be. This results in less code and faster save & load times.
Or, considering your inventory is likely going to be a class (to store other variables & functions), and knowing you’re going to be saving your inventory, you can make the inventory it’s own SaveGame class and get everything from that:
Saving becomes quick & easy: literally just save. Plus, since your inventory is a separate save, you only need to save the inventory object, not anything else, making the save faster.
I’m not saying any of this is the best way, I’m just saying this is one way to approach saving. I’m coming from the point of view of performance, not necessarily “ease-of-use.” If this method is actually less performant, I’d like to know.
So now you’ve got some data in your resident save game and some not. That sounds like a nightmare.
Yeah, and I’m not saying that approach won’t work. For a while. But based on my experience it’s not a viable long term strategy.
From the point of view of performance, local member accesses will likely always be faster than accessing some other object that may or may not be in the cache. This particular argument isn’t well really well suited to the blueprint forum, but still. Frankly I’ve never seen any save game code that I’ve worked on be the bottle neck in the way that you are trying to optimize. Usually it’s the write-to-disk that is the problem and not the actually construction of the save data itself. And the write can always be given over to an async task.
It also doesn’t help (in some cases) that save objects are UObjects and not AActors and so you now have to make sure to clear all that sort of stuff up if you exit to the main menu, load a different campaign, etc. It does help some with level transitions within the same game, but there are reasonable other solutions there.
All’s I can say is that in my years of professional game development save games have never been approached this way, even on UE projects. Neither has the games that one of my colleague has worked on over the course of his professional career.
@super-squirrel We’ve sort of hijacked this thread a bit. Listen, as midgunner66 rightly points out the savegame object is just a UObject like any other and you should be able to access all the data from it just like from any other object once you have a reference to it. As long as you’re not trying to actually load the save game from a slot each time you want to access a variable in it you should be okay (so load all the saves you need and cache them on your game instance or something).
My argument is that while this approach is more immediate, it will eventually cause you more harm than good. And that it will ultimately be easier for you to build your game the way I describe. Frankly (depending on how far into your project you are) you should be able to build out your project without save games at all and add them in later. I’ve been working on my hobby project for nearly 2 years before I decided that I actually needed save games!
I posted this right before the site went down for maintenance. So only just remembered it was here!
Thanks for the insights (both of you). It was a useful debate to read! I do plan to expand this to multiplayer later down the line, so will try to switch the savegame files to another method at this point to save myself future work. I’ve been storing some regularly used variables (active game slot, for example) on the GameInstance anyway, so will probably just use this entirely and have it save a copy of all the variables to the gameslot on shutdown/logout.
The variables in the ‘fixed’ data savegame slots should probably go in datatables anyway, as the values will never get updated between different characters or games (i.e. item/gear base stat details). They were just copies from another project.