#c# #serialization #xna #monogame
#c# #сериализация #xna #моногейм
Вопрос:
Итак, я создаю игру, и она сохраняет прогресс пользователей на компьютере в двоичном файле. User
Класс хранит несколько вещей:
- Целые числа для значений статистики (сериализуемые)
- Строки для имени пользователя и ресурсов скина
- Списки как
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), но он не был обнародован.
Решение:
Удалите его из своего сериализованного класса.
Альтернативы:
-
Если по каким-то причинам вам необходимо сериализовать какой-то шрифт, первое, что приходит мне в голову, это развернуть собственную систему шрифтов, такую как BMFont, но это непростая задача, поскольку вам придется использовать ее везде, где вы, возможно, уже делаете …
-
Создайте заранее определенное количество шрифтов (например, 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 . В принципе, когда я использую элемент, я проверяю его тип, а затем ищу его метод. Спасибо всем, кто пытался, и прошу прощения, если вопрос был запутанным.