Почему моя игра сериализует этот класс?

#c# #serialization #xna #monogame

#c# #сериализация #xna #моногейм

Вопрос:

Итак, я создаю игру, и она сохраняет прогресс пользователей на компьютере в двоичном файле. User Класс хранит несколько вещей:

  1. Целые числа для значений статистики (сериализуемые)
  2. Строки для имени пользователя и ресурсов скина
  3. Списки как Achievement класса, так и InventoryItem класса, которые я создал сам.

Вот поля User :

     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; }
  

Achievement Класс хранит int s, bool s и string s, которые все сериализуемы. InventoryItem Класс хранит свое имя (строку) и InventoryAction, который является делегатом, вызываемым при использовании элемента.

Это поля Achievement класса:

     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;
  

А вот поля для InventoryItem класса:

     InventoryAction actionWhenUsed;
    public string Name;

    public string AssetName;
  

Источник переменных InventoryAction находится в моем XNAGame классе. Я имею в виду, что у XNAGame класса есть метод с именем «UseSword ()» или что-то еще, который он передает в класс InventoryItem. Ранее методы хранились в Game1 классе, но Game класс, который Game1 наследуется от, не является сериализуемым, и я не могу это контролировать. Вот почему у меня есть XNAGame класс.

При попытке сериализации я получаю сообщение об ошибке: «Класс ‘SpriteFont’ не помечен как сериализуемый» или что-то в этом роде. Ну, в моем классе есть объект SpriteFont XNAGame , и несколько быстрых тестов показали, что это источник проблемы. Ну, я не могу контролировать, является ли класс SpriteFont сериализуемым.

Почему игра делает это? Почему все поля в XNAGame классе должны быть сериализуемыми, когда все, что мне нужно, это несколько методов?

При ответе имейте в виду, что мне 13 лет, и я могу не понимать все термины, которые вы используете. Если вам нужны какие-либо примеры кода, я буду рад предоставить их вам. Заранее спасибо!

РЕДАКТИРОВАТЬ: одно из решений, о котором я подумал, — хранить делегаты InventoryAction в a Dictionary , за исключением того, что это будет болезненно и не очень хорошая практика программирования. Если это единственный способ, я приму его (честно говоря, на данный момент я думаю, что это лучшее решение).

РЕДАКТИРОВАНИЕ 2: вот код для метода User.Serialize (я знаю, что я делаю неэффективно, и я должен использовать базу данных, бла, бла, бла. Я в порядке с тем, что я делаю сейчас, так что потерпите меня.):

         FileStream fileStream = null;
        List<User> users;
        BinaryFormatter binaryFormatter = new BinaryFormatter();
        try
        {
            if (File.Exists(FILE_PATH) amp;amp; !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);
  

Комментарии:

1. Вы должны опубликовать некоторый код, а не пытаться объяснить его словами.

2. Пожалуйста, опубликуйте несколько примеров кода. Нам нужно увидеть код для класса, который вы сериализуете — где-то в нем есть ссылка на несериализуемый класс.

3. @Aybe Я добавил код для класса User, класса InventoryInterface и класса достижений.

4. Посмотрите мой ответ, если он вам поможет, кстати, у вас есть gamedev.stackexchange.com что является лучшим местом для таких вопросов.

Ответ №1:

Вы не можете сериализовать a SpriteFont по дизайну, на самом деле это возможно (файл .XNB), но он не был обнародован.

Решение:

Удалите его из своего сериализованного класса.

Альтернативы:

  1. Если по каким-то причинам вам необходимо сериализовать какой-то шрифт, первое, что приходит мне в голову, это развернуть собственную систему шрифтов, такую как BMFont, но это непростая задача, поскольку вам придется использовать ее везде, где вы, возможно, уже делаете …

  2. Создайте заранее определенное количество шрифтов (например, Arial / Times / Courier размером 10/11/12 и т. Д.), Используя приложение XNA Content (не могу вспомнить его точное название); затем сохраните это пользовательское предпочтение в виде двух строк. С помощью a string.Format(...) вы сможете довольно легко загрузить нужный шрифт обратно.

Вариант 2, безусловно, самый простой, и его развертывание не займет больше нескольких минут.

Редактировать

По сути, вместо сохранения делегата я делаю следующее:

  • предметы инвентаря имеют свой собственный тип
  • каждое имя типа де / сериализуется соответствующим образом
  • их логика больше не выполняется в основном игровом классе
  • вам не нужно вручную сопоставлять тип элемента / метод действия

Таким образом, хотя в итоге у вас будет больше классов, у вас будут разделены проблемы, и вы сможете сохранить свой основной цикл чистым и относительно общим.

Код:

 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 !
    }
}
  

Комментарии:

1. Ну, проблема не только в объекте SpriteFont. Все поля в XNAGame классе должны быть сериализованы, но я не хочу проходить через это.

2. Как вы сериализуете свою вещь?? XML? JSON ?

3. Извините, я забыл это объяснить. Я сохраняю его в файл .bin (двоичный файл). Я отредактирую свой вопрос, чтобы показать это.

4. @DanielG Посмотрите мою правку, это немного сложнее, но, вероятно, с этим будет легче справиться в долгосрочной перспективе. (и это общее 🙂

Ответ №2:

Хорошо, я понял это.

Лучшим решением было использовать словарь в классе XNAGame, в котором хранятся две вещи: ItemType (перечисление) и InventoryAction . В принципе, когда я использую элемент, я проверяю его тип, а затем ищу его метод. Спасибо всем, кто пытался, и прошу прощения, если вопрос был запутанным.