Savegame design

I don’t want to pollute the minecraft thread anymore, doing a manual breakout thread. :slight_smile:

Cas:

[quote]XML = yuk! Custom binary format FTW!
[/quote]
Me:

[quote]A little of topic but… why binary? A simple text format can be pretty compact, not compared to binary but is that an issue?
Text is more human readable but also editable. I couldn’t care less if some guy cheated to beat my game.
Am I missing something?
[/quote]
Cas:

[quote]Easier for a machine to read and write. And therefore easier for you to code. Why deal with Integer.parseInt() when you can just read 4 bytes as an int and know it will work and not throw a NumberFormatException? and so on.
[/quote]
Riven:

[quote]Sure, but if it breaks, and you have to do debugging, binary looks like a bunch of random bits, whereas you can probably figure out what went wrong when browsing through human readable data.
[/quote]

I assume “easier for the computer to read” == “easier to code” if you know exactly how to do it…
For me it is easy to slap together a few strings and write them to disk. Then I can read them back, split on various stuff and easily pick out the data. But something in me thinks it would be nice with a binary format.

Also, I plan on using the persistence API (to be able to run sandboxed). So it can’t be a Reader for me. Anyone have any experience with this?

It depends on the purpose of the file.

If it’s a configuration file, human readable (and editable), XML, INI or whatever, is the way to go.

Savegames? I don’t think you’ll ever want your players to edit those. I think Cas is right here, machine-readability/writeability should be the primary focus here. Having it in a human readable format implies that it can be modified.

Why not? Let people cheat if they want to cheat. I’ve always appreciated games that have cheats or character editors or whatever built in, even though I rarely use them, and when I do it’s usually for a second save after I’ve already beat the game so I can mess around with “god-like” characters and saves. Usually I don’t end up playing for very long that way, but I still enjoy it.

Plus I always feel cool if I am smart enough to change a save file without outside help. I’ve done it a few times via a hex editor and value searching in binary save files, but it’s much more a pain and rarely worth my time.

Why get rid of something that might make your players play longer?

For my latest game I’ll be doing all its settings in a basic text format. Saves me from making an editor for now.

[quote]Savegames? I don’t think you’ll ever want your players to edit those.
[/quote]
Not want is one thing. Not care another. Besides, if I really want to, I can save the file as zip (or invert all bytes) and block out 99% of the cheaters :slight_smile:

Or course, if it is a game with online highscores, protecting your save games is vital. Possibly should they be saved on the server.

My main concern here (I may not be very good a getting it out) is that I don’t particularly like writing the code that does save/load. Discussing how to do it optimally is much more fun! I have been thinking on how to write a binary format easily, maybe it can be made more simple than my text format.

But it is so easy to like this (super simplified, but it took less than a minute to write):


String wholeSaveGame = ...
String[] lines = wholeSaveGame.split("\n");
for (String line : lines) {
    String[] keyValuePairs = line.split("$");
    for (String pair : keyValuePairs) {
        String key = pair.split("=")[0];
        String value = pair.split("=")[1];
        //use BeanUtils from apache to set this keyValue to the object at hand
    }
}

If you don’t like BeanUtils, writing it yourself takes less than 5 minutes.

I would remove the possibly and say absolutely. If you actually care about data getting hacked, no matter how you mess with your save file people are still going to find a way to break it, so you absolutely must put it on a server - if you care. If you don’t why waste time?

Check out my sig. In less time than it took you to write those for loops, you could write out AND read in objects in either binary format using Kryo or human readable text format using YamlBeans. :slight_smile:

Thanks, will have a look.

I keep my settings in INI, easy to pull out. For static game data (weapons, classes, etc) I haven’t decided yet, but think sqlite might be a good way to store that info

For dynamic game data (players, levels, etc) you can serialize a java object and write it to disk. Super easy and quick to save/load.
You can even run it though gzip or encrypt it before saving it to disk if worried about cheats. Not fool proof, but will slow them down.
Also serialized objects can be sent over net.

The only downside is if your classes change (ie. game update) then reading old data files (saved games) might get kind funky.

I could have written a fancy pants XML writer (and reader let’s not forget), or even gone super-verbose and used javabeans XML output, or done something with JSON, or whatever. But because my time is rather valuable I did this:


FileOutputStream fos = new FileOutputStream(file);
BufferedOutputStream bos = new BufferedOutputStream(fos);
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(gameState);
oos.flush();
fos.close();

and thus in 5 lines of code solved all my actual real problems forever and got back on with the more urgent requirement to figure out how to put “medals” into Revenge of the Titans. This has been working nicely for me and 1,000,000 players for the last 7 years or so.

Yes, gameState is the entire state of the game. References to game data are stored with a bit of fancy pants serialization trickery but nothing that clever (which basically involves replacing references to stuff like textures with strings that I can use to look up that texture again on loading).

YMMV.

Cas :slight_smile:

Aha, so it isn’t as simple as you made it look. :slight_smile:

Java’s built-in serialization is workable for many tasks, but has a typical ugly Sun API. Also, it uses an extralinguistic mechanism to create objects that bypasses constructors. This is hacky and can have unpredictable results.

That’s why, like I said before, you just make a SavedObject that has the information it needs to construct the actual objects you want. Solves all the problems - what Cas is describing is more or less the same thing, from what I understand.

So you duplicate the storage of all the data you want to serialize? This is fine for shallow graphs, but could easily be tedious to implement and maintain for larger graphs. FWIW, Google Protobuf generates the data storage classes and says these classes “don’t make good first class citizens in an object model”. They recommend wrapping the data storage in an application object. This is a PITA, but if you want to exchange data with other languages, it may be worth it to use Protobuf. Otherwise, IMO it generally isn’t worth the trouble to use this approach. Better to use a mechanism that can serialize your application objects directly. Using the built-in serialization for this has the issue I mentioned. Most serialization libraries can serialize third party classes. Typically you implement an interface or register some serialization code for a class to customize serialization.

It isn’t really a hard problem to solve, so it really comes down to how invasive is the serialization API and any specific features you need (speed, size, customization, versioning, forward/backward compatibility, binary/text, etc).

For my libs, I tried to make a clean API that gets in the way as little as possible. Simple POJOs can be written out with less code than Cas posted for built-in serialization. I make the assumption that Java + my lib is what is going to consume the serialized format, which allows for some optimization (eg, the class definition is the schema).

You could indeed make a rather simpler serialization mechanism. Except by the time you were finished you’d end up with exactly what Sun have already done for you.

Cas :slight_smile:

I did make a rather simpler serialization mechanism and it is not exactly what Sun has done for me. :slight_smile:

What strategies did you use to handle upgrading the save game from a previous version to the current?

easier to code FTW.

I keep the game in “alpha” state and break savegames frequently :wink:

Cas :slight_smile:

Thats the main problem. With serialization you break the data encapsulation so that refactoring is hard if you need backward compatibility.