0

So I'm making a game, and it saves users' progress on the computer in a binary file. The User class stores a few things:

  1. Integers for stat values (Serializable)
  2. Strings for the Username and the skin assets
  3. Lists of both the Achievement class and the InventoryItem class, which I have created myself.

Here are the User fields:

    public string Username = "";
    // ID is used for local identification, as usernames can be changed.
    public int ID;

    public int Coins = 0;
    public List<Achievement> AchievementsCompleted = new List<Achievement>();
    public List<InventoryItem> Inventory = new List<InventoryItem>();
    public List<string> Skins = new List<string>();

    public string CurrentSkinAsset { get; set; }

The Achievement class stores ints, bools, and strings, which are all serializable. The InventoryItem class stores its name (a string) and an InventoryAction, which is a delegate that is called when the item is used.

These are the Achievement class's fields:

    public int ID = 0;
    public string Name = "";
    public bool Earned = false;
    public string Description = "";
    public string Image;
    public AchievmentDifficulty Difficulty;
    public int CoinsOnCompletion = 0;

    public AchievementMethod OnCompletion;
    public AchievementCriteria CompletionCriteria;

    public bool Completed = false;

And here are the fields for the InventoryItem class:

    InventoryAction actionWhenUsed;
    public string Name;

    public string AssetName;

The source of the InventoryAction variables are in my XNAGame class. What I mean by this is that the XNAGame class has a method called "UseSword()" or whatever, which it passes into the InventoryItem class. Previously, the methods were stored in the Game1 class, but the Game class, which Game1 inherits from, is not serializable, and there's no way for me to control that. This is why I have an XNAGame class.

I get an error when trying to serialize: "The 'SpriteFont' class is not marked as serializable", or something like that. Well, there is a SpriteFont object in my XNAGame class, and some quick tests showed that this is the source of the issue. Well, I have no control over whether or not the SpriteFont class is Serializable.

Why is the game doing this? Why must all the fields in the XNAGame class be serializable, when all I need is a few methods?

Keep in mind when answering that I'm 13, and may not understand all the terms you're using. If you need any code samples, I'll be glad to provide them for you. Thanks in advance!

EDIT: One solution I have thought of is to store the InventoryAction delegates in a Dictionary, except that this will be a pain and isn't very good programming practice. If this is the only way, I'll accept it, though (Honestly at this point I think this is the best solution).

EDIT 2: Here's the code for the User.Serialize method (I know what I'm doing in inefficient, and I should use a database, blah, blah, blah. I'm fine with what I'm doing now, so bear with me.):

        FileStream fileStream = null;
        List<User> users;
        BinaryFormatter binaryFormatter = new BinaryFormatter();
        try
        {
            if (File.Exists(FILE_PATH) && !IsFileLocked(FILE_PATH))
            {
                fileStream = File.Open(FILE_PATH, FileMode.Open);
                users = (List<User>)binaryFormatter.Deserialize(fileStream);
            }
            else
            {
                fileStream = File.Create(FILE_PATH);
                users = new List<User>();
            }

            for (int i = 0; i < users.Count; i++)
            {
                if (users[i].ID == this.ID)
                {
                    users.Remove(users[i]);
                }
            }

            foreach (Achievement a in AchievementsCompleted)
            {
                if (a.CompletionCriteria != null)
                {
                    a.CompletionCriteria = null;
                }
                if (a.OnCompletion != null)
                {
                    a.OnCompletion = null;
                }
            }

            users.Add(this);
            fileStream.Position = 0;
            binaryFormatter.Serialize(fileStream, users);
Daniel G
  • 245
  • 4
  • 15
  • You should post some code instead of trying to *explain* it with words. – aybe Oct 01 '16 at 14:20
  • Please post some code samples. We need to see the code for the class you are serializing - somewhere it has a reference to a non-serializable class. – PhillipH Oct 01 '16 at 15:39
  • @Aybe I added the code for the User class, InventoryInterface class, and the Achievement class. – Daniel G Oct 01 '16 at 18:28
  • See my answer if it helps you, btw you have http://gamedev.stackexchange.com/ which is a better place for such questions. – aybe Oct 01 '16 at 21:02

2 Answers2

1

You cannot serialize a SpriteFont by design, actually this is possible (.XNB file) but it hasn't been made public.

Solution:

Strip it off your serialized class.

Alternatives:

  1. If for some reasons you must serialize some font, the first thing that comes to my mind would be to roll-out your own font system such as BMFont but that's a daunting task since you'll have to use it everywhere else where you might already do ...

  2. Generate a pre-defined amount of fonts (i.e. Arial/Times/Courier at size 10/11/12 etc ...) using XNA Content app (can't recall its exact name); then store this user preference as two strings. With a string.Format(...) you should be able to load the right font back quite easily.

Alternative 2 is certainly the easiest and won't take more than a few minutes to roll-out.

EDIT

Basically, instead of saving a delegate I do the following:

  • inventory items have their own type
  • each type name is de/serialized accordingly
  • their logic does not happen in the main game class anymore
  • you don't have to manually match item type / action method

So while you'll end up with more classes, you have concerns separated and you can keep your main loop clean and relatively generic.

Code:

public static class Demo
{
    public static void DemoCode()
    {
        // create new profile
        var profile = new UserProfile
        {
            Name = "Bill",
            Gold = 1000000,
            Achievements = new List<Achievement>(new[]
            {
                Achievement.Warrior
            }),
            Inventory = new Inventory(new[]
            {
                new FireSpell()
            })
        };

        // save it
        using (var stream = File.Create("profile.bin"))
        {
            var formatter = new BinaryFormatter();
            formatter.Serialize(stream, profile);
        }

        // load it
        using (var stream = File.OpenRead("profile.bin"))
        {
            var formatter = new BinaryFormatter();
            var deserialize = formatter.Deserialize(stream);
            var userProfile = (UserProfile) deserialize;

            // set everything on fire :)
            var fireSpell = userProfile.Inventory.Items.OfType<FireSpell>().FirstOrDefault();
            if (fireSpell != null) fireSpell.Execute("whatever");
        }
    }
}

[Serializable]
public sealed class UserProfile
{
    public string Name { get; set; }
    public int Gold { get; set; }
    public List<Achievement> Achievements { get; set; }
    public Inventory Inventory { get; set; }
}

public enum Achievement
{
    Warrior
}

[Serializable]
public sealed class Inventory : ISerializable
{
    public Inventory() // for serialization
    {
    }

    public Inventory(SerializationInfo info, StreamingContext context) // for serialization
    {
        var value = (string) info.GetValue("Items", typeof(string));
        var strings = value.Split(';');
        var items = strings.Select(s =>
        {
            var type = Type.GetType(s);
            if (type == null) throw new ArgumentNullException(nameof(type));
            var instance = Activator.CreateInstance(type);
            var item = instance as InventoryItem;
            return item;
        }).ToArray();
        Items = new List<InventoryItem>(items);
    }

    public Inventory(IEnumerable<InventoryItem> items)
    {
        if (items == null) throw new ArgumentNullException(nameof(items));
        Items = new List<InventoryItem>(items);
    }

    public List<InventoryItem> Items { get; }

    #region ISerializable Members

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        var strings = Items.Select(s => s.GetType().AssemblyQualifiedName).ToArray();
        var value = string.Join(";", strings);
        info.AddValue("Items", value);
    }

    #endregion
}

public abstract class InventoryItem
{
    public abstract void Execute(params object[] objects);
}

public abstract class Spell : InventoryItem
{
}

public sealed class FireSpell : Spell
{
    public override void Execute(params object[] objects)
    {
        // using 'params object[]' a simple and generic way to pass things if any, i.e.
        // var world = objects[0];
        // var strength = objects[1];
        // now do something with these !
    }
}
aybe
  • 15,516
  • 9
  • 57
  • 105
  • Well, the problem isn't just the SpriteFont object. _All_ the fields in the `XNAGame` class are required to be serialized, but I don't want to have to go through this. – Daniel G Oct 01 '16 at 21:04
  • How are you serializing your thing ?? XML ? JSON ? – aybe Oct 01 '16 at 21:05
  • Sorry, I forgot to explain that.I'm saving it to a .bin file (a binary file). I'll edit my question to show this. – Daniel G Oct 01 '16 at 21:08
  • @DanielG See my edit, it's a bit more involved but it'll probably be easier to deal with in the long term. (and it's generic :) – aybe Oct 01 '16 at 22:04
0

Okay, so I figured it out.

The best solution was to use a Dictionary in the XNAGame class, which stores two things: an ItemType (an enumeration), and an InventoryAction. Basically, when I use an item, I check it's type and then look up it's method. Thanks to everyone who tried, and I'm sorry if the question was confusing.

Daniel G
  • 245
  • 4
  • 15