1
\$\begingroup\$

I realize this is a bit of a broad question, but I was wondering if there is a "standard" in the industry when it comes to storing save data for games (and is it different across platforms - Xbox/PS/PC/Mac/Android/iOS?)

For example for a game like Assassin's Creed or The Walking Dead: They are on multiple platforms and they usually have to save enough information about the player and their actions. Do they use something like XML files, databases, or just straight binary dumps? How much does it differ from platform to platform?

I would appreciate it if someone with experience in the game industry would answer this.

\$\endgroup\$
  • 1
    \$\begingroup\$ possible duplicate of How to save a game state? \$\endgroup\$ – user15805 Nov 8 '13 at 1:22
  • \$\begingroup\$ I already checked that, but it's not really the same thing. It just gives ideas on how to store information. I know there are hundreds of ways of doing it, but I wanted to know what is common in the industry and what is considered "standard" (if there is such a thing) \$\endgroup\$ – PixelPerfect3 Nov 8 '13 at 2:20
  • 6
    \$\begingroup\$ There is no standard. \$\endgroup\$ – user15805 Nov 8 '13 at 2:21
  • 2
    \$\begingroup\$ xkcd.com/927 \$\endgroup\$ – Vaughan Hilts Nov 8 '13 at 3:17
1
\$\begingroup\$

I wrote the save game system for Disney's Guilty Party. I can give you a high level overview of how it was done. Although it was shipped as a Wii game, for debugging purposes we needed a save game system that worked both on the Wii and on the PC. Disclaimer: this is the only time I've written such a system, and by no means can I claim that it is The Standard Way(tm) to write a save game system. It is more of a case study.

From a purely design standpoint, binary files are always a hassle. For one thing, they are extremely hard to debug since you have to open the save game files in a hex editor. Second, you cannot download a save game file generated on the console and open it on the PC without making sure that your endianness is correct, which requires you either to A) have duplicate serialization code for big and little endian machines or B) wrap your serialization code in swapBytes() calls that switch things based on the endianness of your machine. Both are clunky.

Hence, we were looking for a save game system that had the ability to

  • Be able to be read and written from both the Wii and the PC with the same format.
  • Be easy to debug the serialization of our objects
  • Have a versioning system that allowed us to read save game files from previous versions of the game in order to track down crash reports during playtesting of old builds.
  • Ignore loading time because loading a game from a deserialized save file was on the same order of magnitude as loading a fresh level from scratch.

With these ideas in mind, we used (roughly) the following system. Each save game file was saved as plaintext. The beginning of each save game file always started with a line that looked like:

Version: 21

Then, we used something akin to the following for serializing and deserializing:

const unsigned int kCurrentVersion = 24;
void Serialize(GameState &state, const char *filename) {
  File saveFile = File::Open(filename);
  saveFile.Write("%d\n", kCurrentVersion);
  saveFile.Write("%f %f %f\n", state.playerPosition);
  saveFile.Write("%f %f %f\n", state.playerPantsColor);
  saveFile.Write("%d\n", state.kNumTacosIHadForLunch);
}

// Any save game file before this version is invalid...
const unsigned int kMinimumSupportedVersion = 18;
GameState Deserialize(const char *filename) {
  File saveFile = File::Open(filename);
  unsigned int version = 0;

  GameState savedState;

  if(saveFile.Read("%d\n", &version) != 1 || !version) {
    fprintf(stderr, "No version... no idea what's going on.");
    return kInvalidState;
  }

  if(version < kMinimumSupportedVersion || version > kCurrentVersion) {
    fprintf(stderr, "Unsupported version: %d\n", version);
    return kInvalidState;
  }

  float pos[3];
  if(saveFile.Read("%f %f %f\n", &pos[0], &pos[0], &pos[0]) != 3) {
    fprintf(stderr, "Error reading position.\n");
    return kInvalidState;
  } else {
    savedState.setPlayerPosition(pos);
  }

  if(version > 10) {
    float c[3];
    if(saveFile.Read("%f %f %f\n", &c[0], &c[1], &c[2]) != 3) {
      fprintf(stderr, "Error reading pants color.\n");
      return kInvalidState;
    } else {
      savedState.setPantsColor(c);
    }
  }

  if(version > 17 && version < 22) {
    int nsiwaat;
    if(saveFile.Read("%d\n", &nsiwaat) != 1) {
      fprintf(stderr, "Error reading the number of socks I wear at a time.\n");
      return kInvalidState;
    } else {
      savedState.setNSIWAAT(nsiwaat);
    }
  }

  // ...etc

  return savedState;
}

The key takeaways from this are: Your serialization code should always write the most up to date version of your game state, regardless of what that is. However, it must also write what the most up to date version is so that future (and current) versions of the game are able to know how to read the file.

The deserialization code gets a little bit messy with this technique. It takes a very defensive approach to reading save game files. There are security reasons (i.e. buffer overflow) for why this is a good idea. It also reduces the risk of reading a corrupt save game file and loading bad data and crashing (or worse, not crashing). For debugging purposes, however, you should make sure to be as verbose as possible for why a saved game could not be read. This will save you many headaches in future versions where you thought a thing should be a certain way but it isn't. This system proved to not only be very effective, but I had a very easy time maintaining it.

\$\endgroup\$
  • \$\begingroup\$ Since pretty much all development is done on the PC I guess it would be a good idea to use text-based save files rather than binaries? Especially considering memory is not really an issue nowadays. Thanks for the answer \$\endgroup\$ – PixelPerfect3 Nov 8 '13 at 6:34
  • \$\begingroup\$ @PixelPerfect3 I think that depends on the size of your savegames. Consider not only disk space but also read and write times. I'd use a binary format where the first n bytes are a header that doesn't change between versions. In the simplest form, that would just be the first byte saving the version. \$\endgroup\$ – danijar Oct 24 '14 at 17:05
0
\$\begingroup\$

I'd advice on using SQL databases whenever you can. They are robust, well known, easy to interface, scalable and all. The only downside might be performance. But as you are not asking about how to store your live game objects on every update performance of the database itself will very likely not be a problem.

Make sure to learn about serializing you savegame-relavant part of your game state. Pushing those into a database should be fairly easy then.

\$\endgroup\$
  • \$\begingroup\$ SQL databases generally aren't run on game consoles due to the overhead created by the need for a server, user privlidges, etc. \$\endgroup\$ – Mokosha Nov 8 '13 at 15:40
  • \$\begingroup\$ Actually games like Assassins Creed do saves frequently, so I assume a lot of writes to SQL databases would be at least some perf hit \$\endgroup\$ – PixelPerfect3 Nov 8 '13 at 21:36
  • \$\begingroup\$ @PixelPerfect3 frequently is a very vague term. I'd classify everything happening less frequent than once every 10 seconds as rare in a game. It is reasonable to process those rare events in a background thread while continuing to run the actual game. \$\endgroup\$ – Jonas Bötel Nov 10 '13 at 9:11
0
\$\begingroup\$

You could use Google's protocol buffers (https://developers.google.com/protocol-buffers).

\$\endgroup\$
  • \$\begingroup\$ Seems quite useful. Would be interesting to know if Android games use something like this. \$\endgroup\$ – PixelPerfect3 Nov 8 '13 at 21:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.