Best way to write a save system?

Mar 8, 2021 at 2:36am
So I want to write a save system for my game but im unsure how to approach it design wise. Should I create a save and load class? should each class like player, enemy etc have their own save and restore functions? How would you approach this?
Mar 8, 2021 at 2:54am
What data needs to be saved?

If you can't concretely answer that question, you're getting ahead of yourself.
Mar 8, 2021 at 3:03am
In my game what I want to save is player data like inventory, kill count, money, health, name. Enemies dont need one at this point but I may implement some things for enemies that would need a save system. Nothing too crazy right now, but I would like to make the save and load system easily expandable in case I want to save more than just player data.
Mar 8, 2021 at 4:48am
Enemies dont need one at this point but I may implement some things for enemies that would need a save system.
So you're getting ahead of yourself!

I would like to make the save and load system easily expandable in case I want to save more than just player data.

You might want to, but it's not a good idea. Trying to do this requires that you solve a problem you don't have. This is needlessly difficult task that produces over-engineered solutions and wastes time. There is a significant risk that you will never use the features you added, or encounter every problem that you attempt to solve.

Right now all you have to do is read and write stuff from a text file. This is essentially trivial, so if and when you need something more sophisticated you can throw away what you have and write a more sophisticated system.

Bottom line: do not write code that you don't need.
Last edited on Mar 8, 2021 at 4:50am
Mar 8, 2021 at 4:52am
Ok, well i'll just stick to writing and reading player data for now then, since thats something I know for a fact I need to do. So with that said, should I write a save and load class? or just add saving and loading to the player class?

To me it seems more sensible to put it in the player class since that's the only thing that will use it, but im unsure.
Last edited on Mar 8, 2021 at 4:52am
Mar 8, 2021 at 5:40am
You should stick to non-member non-friend functions if possible
1
2
3
4
// save p to the stream
std::ostream& save(std::ostream& os, player p);
// load p from the stream
std::istream& load(std::istream& is, player& p);

I would avoid creating a new class. Classes with invariants are fairly hard to write, and how many "loaders" or "savers" do you reasonably expect to need? Probably just one -- therefore a new class would have limited benefit.
Last edited on Mar 8, 2021 at 5:45am
Mar 8, 2021 at 7:17am
Should I create a save and load class?
No.

should each class like player, enemy etc have their own save and restore functions?
Yes. At least for loading it needs as mbozzi wrote an independent function.

The container class of your objects needs to do some work since you want different types. Saving is relative trivial but restoring requires that objects of the right type must be created.

When saving/loading the best approach would be symmetric. Each function for saving has its loading counterpart. For the type it would be something like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class object
{
  ...
  virtual std::string get_type() const = 0;
  ...
};
class player : public object
{
  ...
  virtual std::string get_type() const override;
  ...
};

bool is_player(const std::string& type);


mbozzi wrote:
You should stick to non-member non-friend functions if possible
But that would mean only public data members could be saved/loaded?

Mar 8, 2021 at 6:09pm
Alright thanks, I'll just do some simple saving for player, since its the only one that needs it.
Mar 9, 2021 at 1:15am
> You should stick to non-member non-friend functions if possible
But that would mean only public data members could be saved/loaded?

Non-member non-friends are ideal, but you're right, sometimes there's no choice.

For a well-designed class object, if the object has invariants, they should hold unconditionally throughout the object's lifetime. It shouldn't be possible to break those invariants by properly using its public interface. The only correct code which can potentially break invariants are friends and members.

Non-member non-friends have don't (can't) access class internals. Therefore, as long as they use the interface properly, they can't ever load an object with broken invariants, no matter what input is provided - even if it's corrupted, damaged, or malicious.

Further, the values gotten from public interfaces is more frequently applicable on different platforms or after the class is modified. A non-member non-friend does not need to be reviewed unless the class interface changes.
Last edited on Mar 9, 2021 at 1:16am
Topic archived. No new replies allowed.