Having trouble creating a functional SaveManager.

Hey guys, I’m working on a pretty substantial project with my brother in Monogame, and I’ve gotten all of the basic things covered to start building the game itself (gamestate management, audio management, building tiles, maps, transitioning between them, etc), but the one thing that I cannot seem to find useful information on is how to actually deal with saving and loading the game.

All of the information that I’ve found has either been outdated, designed for xna(and mostly uses things like Framework.Storage that doesn’t seem to exist anymore). I know how to use the IntermediateSerializer for creating xml files, but it also states that you shouldn’t use it for production titles, which seems like I’d only be using a temporary solution. My knowledge of xml is pretty basic, and I seem to be missing something on all of the other solutions in order to get it to work.

In short, I want to be able to save my game files somewhere (I’m planning on limiting this to 3 files for now, maybe expanding it out later), and having them ideally in %appdata%\Gamename\saves"save1.dat", or something similar. I’ve watched a ton of videos on xml serialization for various projects and read a ton of documentation, I just can’t seem to get it to work for some reason. I’m either not getting it, or really badly overthinking it. Any help would be appreciated.

tl;dr: I need a frame of a SaveManager class that will allow me to save/load data to and from appdata, or some other folder.

I have no idea how other people do it but I use a tree structure inside binary files.

Something like this. Untested.

public enum Save_Type { String = 0, Integer = 1, Byte = 2, Double = 3, Vector2 = 4, Vector3 = 5 };

public class Save_Entry
{
    public string Key;
    public Save_Type Type;

    public virtual void Write(ref BinaryWriter e)
    {
        e.Write((byte)Type);
    }
}

public class Save_Entry_Integer : Save_Entry
{
    public Save_Entry_Integer() { this.Type = Save_Type.Integer; }
    public Save_Entry_Integer(int Value) { this.Type = Save_Type.Integer; this.Value = Value; }
    public int Value;
    public override void Write(ref BinaryWriter e)
    {
        base.Write(e);
        e.Write(Value);
    }
}

public Save_Entry[] Entries;
public void Save(string Filename)
{
    BinaryWriter bw;

    //Record how many entries to expect
    bw.Write(Entries.Length);
    for (int i = 0; i < Entries.Length; i++)
    {
        Entries[i].Write(ref bw);
    }
}

public void Load(string Filename)
{
    BinaryReader br;
    //read how many to load
    int count = br.readint32();
    Entries = new Save_Entry[count];
    for (int i = 0; i < Entries.Length; i++)
    {
        Save_Type type = (Save_Type)br.readbyte();
        if (type == Save_Type.Integer)
        {
            Entries[i] = new Save_Type_Integer(br.ReadInt32());
        }
    }
}

Use example

var e = new Save_Entry_Vector2();
e.Key = “PLAYER_POS”;
e.Value = new Vector2(10,10);

and from there you can load the needed player’s position

var e = new Save_Entry_Integer();
e.Key = “PLAYER_AMMO_RIFLE”;
e.Value = 500;

or how much ammo they have.

var e = new Save_Entry_String();
e.Key = “PLAYER_NAME”;
e.Value = Mr. Butts;

or their character’s name

1 Like

The built-in .net serializers are just for throwaway purposes. Even the DataContract based serializers are pretty nasty if a project is expected to live and change over time.

Getting paths to files is pretty straightforward:

    /// <summary>
    ///  Gets the path of something in the User application directory, creates the directories if necessary
    ///  TODO: actually check directory creation, unused path
    /// </summary>
    public static string DataPath(string aPath)
    {
        string asmName = Assembly.GetExecutingAssembly().GetName().Name;
        string fileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), string.Format("{0}\\{1}", asmName, aPath));
        if (!File.Exists(fileName))
            Directory.CreateDirectory(Path.GetDirectoryName(fileName));
        return fileName;
    }

    /// <summary>
    /// Gets the path to something contained relative to the Program
    /// </summary>
    public static string ProgramPath(string aPath)
    {
        string basePath = System.IO.Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
        return Path.Combine(basePath, aPath);
    }

For serialization I use code-generation, writing it manually is too miserable and error-prone. I generally write a code generator for each of the different broad-strokes.

Here’s a horrifying gist of a recent generator I use for generating XML and binary serialization for my texture graph nodes. A whopping 7000 lines of robo-code just for one part of the program (highly redundant output though).

Even with the barbaric implementation of “WritePropertyMethod” (redundant out the wazoo) it’s only 530 lines of work to spit out ~30k total lines of robo-code in the whole program is pretty snazzy and I’m very glad to not have to write or keep that stuff up to date. Though supporting text output for XML for some my types was … interesting.

I make saves in json format, then crypted when zipped so no one would be able to cheat or at least it is harder

I’m with Alkher. My saves all go to JSON with compression. Using JSON has resulted in much smaller save files (~50kb) then using XML (~100kb).

I was strongly against datacontracts until I tried them… now I can’t imagine going back to anything else. I use DataContracts and use the datacontractjsonserializer. All Monogame structs support it and all C# data structures support it. It is very simple to use. If you use DataContracts, make sure to read up on [OnSerializing] and [OnSerialized] (there are deserializing equivalents too). The beauty of the datacontracts is you can pick and choose what data gets saved very easily. It does ALL the work for you so you don’t have to reinvent the wheel. I haven’t had any issues versioning datacontracts which someone mentioned up above.

The plus about using datacontracts is it should be compatible regardless of the system. If an xbox saved your game to xml or json using data contacts, that save file would work on your PC too if you moved it over. That is something raw binary saves could have issues with though I have not tried.

Also you’ll run into KnownTypes growth problem and there is a simple way around it with reflection. Knowntypes have to be used if you extend the class. Here is a simple example of datacontract class and getting the derived types via reflection: https://pastebin.com/DE8SVrXw

Best of luck!
-David

Thanks for the replies, guys. I managed to get simple xml serializer working (That saves to %appdata%/MyGame/saves/data.xml) for reading and writing, and it’s not too bad right now. What do you guys prefer to do to handle loading into a gamestate? I definitely can’t serialize my current game state, that’d be like serializing half of the working program so far. I’m thinking something like a string variable that would tell me which gamestate to load, but I’m not 100% sure what the best practice would be.

Also as for JSON, is it as easy to get working as xml? I know the syntax is more similar to C# with the curly braces and stuff.

Pretty close. It can be a bit of a headache when you’re working with JSON laid out by a jerkwad when it comes to “this field could be an array, or it could be an object, or it could be a value” nonsense, but when it’s your own stuff you can avoid doing silly things.

At the end of the day it’s no worse than working with XML that has values encoded as strings or base64.

Changing from xml to json asks a little twist of your mind if you are used to use attributes with xml. Nonetheless it is simple and the file produced is faster to read in general and lightweight.
But where in xml you could select a tag from an xml file with an xpath for ex, in json you’ll have to load it entirely before.
It depends on your tastes afterall

Messing with serializers directly is so messy. I prefer to use a database and just have that serialize my save objects for me. I’m personally a fan of MarcelloDB since it’s so easy and straight-forward, but there’s a ton of other in-process databases on github. Only thing about using a database is that you can’t easily edit the database file (which is probably considered a good thing). You’d probably want to set up a GUI frontend for your database in your game or game editor (if they’re separate).