Posted September 09, 2017 by benmakesgames
#save game #json #C#
finally finished the nuts and bolts of saving and loading.
there's not much to say about a saving and loading system, except very technical things, so let's talk about technical things :P (that'll be fun! ...right? >_>)
when making this save system, there were a few things that were important considerations for me:
the first thing I did, was to get the Newtonsoft.Json library. it's one of the most-downloaded NuGet packages, not only because end-user programmers like myself like JSON, but because many other libraries (REST servers & clients, for example) also like JSON. Newtonsoft.Json is super-good at handling JSON, INCLUDING serializing/deserializing objects, and provides options for handling common problems.
I use Newtonsoft is serialize LOOT × LORE × LOVE's World object, like so:
public string Serialize(World world)
{
StringBuilder sb = new StringBuilder();
using (StringWriter s = new StringWriter(sb))
using (JsonWriter w = new JsonTextWriter(s))
{
_serializer.Serialize(w, world);
}
return sb.ToString();
}
(the _serializer object here is a JsonSerializer. importantly, it has been given PreserveReferencesHandling = PreserveReferencesHandling.Objects setting.)
this is some trickery to get the data as a plain string. there ARE ways to write straight to a file, but I don't want to do that. why?
my third requirement: "I should be able to provide a summary of the game on the load screen (current health, date of save, list of items, etc), without loading up the entire saved game."
in order to accomplish this, what I really need is one save file with two pieces of data: the first, an object which summarizes the save (the date of the save, the player's appearance, and anything else I want displayed on the load screen), the second, the serialized world, produced by my Serialize function above.
there are a couple ways I can think of to do this, and probably some more ways I'm not thinking of; the way I chose was to make a SavedGame class which stores this metadata, AND the already-serialized world data.
pros to this approach all stem from the fact that we get everything down to a single object:
public bool Save(World world)
{
if (!Directory.Exists(SavePath))
Directory.CreateDirectory(SavePath);
using (FileStream stream = File.Create(SavePath + "\\" + world.Player.Name + ".save.new"))
using (GZipOutputStream gzStream = new GZipOutputStream(stream))
{
_formatter.Serialize(gzStream, new SavedGame(world));
}
File.Delete(SavePath + "\\" + world.Player.Name + ".save");
File.Move(SavePath + "\\" + world.Player.Name + ".save.new", SavePath + "\\" + world.Player.Name + ".save");
return true;
}
where _formatter = new BinaryFormatter(), and SavePath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\LOOTxLORExLOVE".
(also: there's a little bit of extra logic in there which is attempting to protect your current save game in case saving the new one fails, HOWEVER: I haven't tested it, or added logic to properly handle failure, so I can't guarantee that the above code is actually the best way of doing what it wants to do! use with caution!)
cons to this approach:
so the nuts and bolts are in-place, but I'm definitely not done working on this save system. besides addressing that second con, the UI still doesn't show the items collected (even though that information IS saved), and there's no way to delete saves! I'd also like to put some of this logic into my BMGFramework, so that others can take advantage of the work I've done, if they like; doing that will definitely take some additional work & refactoring.
ANYWAY:
programming's a funny thing. there are a lot of details to any system, when you get into it, even a save/load game system.
and I'm not on any kind of schedule or deadline here, but I'm still reminded of this rule of thumb: if anyone asks you how long you think it'll take for you to do something (including when you ask yourself!), always double your initial guess!
P.S. now I'm curious about the file sizes of other games' saves; let's see here... wow... there's save data from old games I almost forgot I even played... hm... I should find recent games...
I feel like Civ 6 must be doing some very special to keep their saves so small, but I'm not too embarrassed about 4KB saves, even if they probably do contain less information. Cities Skylines saves are rightly large - those cities can get HUGE... but, jesus, Senua's Sacrifice, what are YOU doing?! whatever it is, it's absolutely wrong. but even so: 642KB? do I ACTUALLY honestly care? no. my Windows Temp directory is over 200MB. my user's temp directory is over 600MB. I have 11 GIGABYTES of songs, mostly MP3s, averaging between 5 and 6MB per song. Senua's 642KB saves are peanuts.
(wait: my user temp directory is over 600MB!?!? wtf, Windows! I should do something about that...)