为什么我的游戏要连载这个 class?

Why is my game serializing this class?

所以我正在制作一个游戏,它将用户在计算机上的进度保存在一个二进制文件中。 User class 存储一些东西:

  1. 统计值的整数(可序列化)
  2. 用户名和皮肤资产的字符串
  3. 我自己创建的 Achievement class 和 InventoryItem class 的列表。

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

Achievementclass存储intboolstring,都是可序列化的。 InventoryItem class 存储其名称(字符串)和 InventoryAction,它是在使用项目时调用的委托。

这些是 Achievement class 的字段:

    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 class 的字段:

    InventoryAction actionWhenUsed;
    public string Name;

    public string AssetName;

InventoryAction 变量的来源在我的 XNAGame class 中。我的意思是 XNAGame class 有一个名为 "UseSword()" 或其他名称的方法,它传递给 InventoryItem class。之前方法是存放在Game1class,但是Game1继承的Gameclass是不可序列化的,没办法我来控制它。这就是为什么我有一个 XNAGame class.

我在尝试序列化时遇到错误:"The 'SpriteFont' class is not marked as serializable",或类似的错误。好吧,我的 XNAGame class 中有一个 SpriteFont 对象,一些快速测试表明这是问题的根源。好吧,我无法控制 SpriteFont class 是否可序列化。

为什么游戏会这样?为什么 XNAGame class 中的所有字段都必须是可序列化的,而我只需要几个方法?

回答时请记住,我才 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);

你不能序列化一个 SpriteFont 设计 ,实际上这是可能的(.XNB 文件),但它还没有被制作 public。

解法:

将其从序列化 class 中删除。

备选方案:

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

  2. 使用 XNA Content app(不记得它的确切名称);然后将此用户首选项存储为两个字符串。使用 string.Format(...) 你应该能够很容易地加载正确的字体。

备选方案 2 无疑是最简单的,实施起来不会超过几分钟。

编辑

基本上,我没有保存委托,而是执行以下操作:

  • 库存物品有自己的类型
  • 每个类型名称相应地是de/serialized
  • 他们的逻辑不再出现在主游戏中 class
  • 您不必手动匹配项目类型/操作方法

因此,虽然您最终会得到更多 classes,但您可以将关注点分开,并且可以保持主循环干净且相对 generic.

代码:

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

好的,我明白了。

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