Hacker News new | ask | show | jobs
by flohofwoe 732 days ago
If I learned one important lesson from writing savegame systems: don't directly serialize your entire game state on the "game object level" (e.g. don't create a savegame by running a serializer over your game object soup), instead decouple the saved data from your game logic internals, have a clear boundary between your game logic and the savegame-system, keep the saved state as minimal as possible, and reconstruct or default-initialize the rest of the data after loading saved state.

With that approach, a language-assisted serialization system also looses a lot of its appeal IMHO (although it can still be useful of course for describing the separate savegame data format).

Also: resist the architecture astronaut in you, especially savegame systems are a honey trap for overengineering ;)

2 comments

If your game engine is built on a data-first architecture like ECS then it can be pretty trivial to directly serialize your game state. I have had good luck with this using bitECS https://github.com/NateTheGreatt/bitECS/blob/master/docs/INT...
Agreed, when the data is already in a table format (instead of an "object spider web") the idea to automate serialization makes more sense, it essentially becomes a "database problem". I would still very carefully consider what data columns need to be persisted and which should be reconstructed, and I wouldn't try to come up with a too generic solution.

For instance in some games it might not be necessary to save a reference to a targeted object, if the gameplay targeting mechanism picks up a target in the first frame after loading a savegame anyway (etc etc...). Whether that target is exactly the same as at the time of creating the savegame might not be relevant (but very relevant for other games).

I guess the TL;DR is: in many cases it might be much easier to come up with a specialized per-game savegame system instead of coming up with a generic savegame system that works for all types of games.

Depending on how large your save state is, it could be as simple as a function mapping from a list of game objects to the saveable object. That approach works really well with Redux on the web since you really don't want to save most things. Where things really get tricky is when you want to get fancy and support things like saving only the changed portion of the state.
All very good advice that I feel deeply. I think I fell into the honey trap some time ago, but I've made peace with that — the tools I'm making will probably do more good than any game I could finish making, at least for now.

Jokes aside, though, I do try to dog-food my tooling as much as possible. I maintain a Godot/C# 3d platformer game demo with full state preservation/restoration (<https://github.com/chickensoft-games/GameDemo>) to demonstrate this.

By the time I've finished writing tests and docs for a tool, I've usually identified and fixed a bunch of usability pain points and come up with a happy path for myself and other developers — even if it's not 100% perfect.

I also have a bunch of unreleased game projects that spawned these projects, and even gave a talk on how this stuff came about (<https://www.youtube.com/watch?v=fLBkGoOP4RI&t=1705s>) a few months ago if that's of interest to you or anyone else.

The requirements you mentioned in your comment cover selectively serializing state and decoupling saving/loading logic, and I could not agree more. While you can always abuse a serializer, I hope my demonstration in the game demo code shows how I've selectively saved only relevant pieces of game data and how they are decoupled and reconstructed across the scene tree.

Also probably worth mentioning the motivation behind all this — the serialization system here should hopefully enable you to easily refactor type hierarchies without having to maintain manual lists of derived types like System.Text.Json requires you to do when leveraging polymorphic deserialization.

Manually tracking types (presumably in another file, even) is such an error-prone thing to have to do when using hierarchical state machines where each state has its own class (like <https://github.com/chickensoft-games/LogicBlocks>). States as classes is super common when following the state pattern and it is well supported with IDE refactoring tools since they're just classes. Basically this serialization system exists to help save complex, hierarchical state without all the headaches. While I was at it, I also introduced opinionated ways to handle versioning and upgrading because that's also always a headache.