为什么我的游戏序列化这个类?

时间:2016-10-01 14:18:03

标签: c# serialization xna monogame

所以我正在制作游戏,它可以在二进制文件中保存用户在计算机上的进度。 User类存储了一些内容:

  1. 统计值的整数(可序列化)
  2. 用户名和皮肤资产的字符串
  3. 我自己创建的Achievement班级和InventoryItem班级的列表。
  4. 以下是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,boolstring 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'类没有标记为可序列化”,或类似的东西。好吧,我的XNAGame类中有一个SpriteFont对象,一些快速测试表明这是问题的根源。好吧,我无法控制SpriteFont类是否可以序列化。

    为什么游戏会这样做?为什么XNAGame类中的所有字段都必须是可序列化的,当我需要的只是一些方法时?

    在回答我13岁时,请记住,并且可能无法理解您正在使用的所有术语。如果您需要任何代码示例,我很乐意为您提供。提前谢谢!

    编辑:我想到的一个解决方案是将InventoryAction代理存储在Dictionary中,除了这将是一个痛苦并且不是很好的编程实践。如果这是唯一的方法,我会接受它(尽管此时我认为这是最好的解决方案)。

    编辑2:这是User.Serialize方法的代码(我知道我在做什么效率低下,我应该使用数据库,等等,等等,等等。我现在正在做的事情很好,请耐心等待。):

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

2 个答案:

答案 0 :(得分:0)

您无法按设计序列化SpriteFont ,实际上这是可能的(.XNB文件),但尚未公开。

<强>解决方案:

将其从序列化类中删除。

<强>备选方案:

  1. 如果由于某些原因你必须序列化一些字体,我想到的第一件事就是推出你自己的字体系统,例如BMFont,但这是一项艰巨的任务,因为你将会必须在你可能已经做过的其他任何地方使用它......

  2. 使用 XNA内容应用生成预定义数量的字体(即Arial / Times / Courier,大小为10/11/12等...)(无法回想起它确切名称);然后将此用户首选项存储为两个字符串。使用string.Format(...),您应该能够轻松地加载正确的字体。

  3. 备选方案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 :(得分:0)

好的,所以我明白了。

最好的解决方案是在XNAGame类中使用Dictionary,它存储两件事:ItemType(枚举)和InventoryAction。基本上,当我使用一个项目时,我检查它的类型,然后查找它的方法。感谢所有尝试的人,如果这个问题令人困惑,我很抱歉。