Unity中Newtonsoft JSON加载玩家数据时IEnumerable集合丢失问题
问题分析与解决方案
你的问题核心出在**PlayerState中InventoryContent的IEnumerable<ItemSavableState>类型**以及反序列化时的异常处理逻辑上,具体原因和解决步骤如下:
1. IEnumerable接口的序列化/反序列化局限性
Newtonsoft.Json对IEnumerable<T>这种抽象集合类型的处理存在隐患:
- 序列化时,
IEnumerable会被当作枚举器处理,但反序列化时,Newtonsoft需要选择一个具体的集合类型实例化。如果原集合是只读枚举(比如Enumerable.Empty)或LINQ查询结果,反序列化后的实例可能是一次性枚举器,后续访问时已被耗尽,导致InventoryContent看起来为空,最终重新保存成空数组。 - 抽象集合类型无法保证反序列化后的实例具备可重复遍历、可再次序列化的特性。
解决方法:改用具体集合类型
把PlayerState中的InventoryContent类型从IEnumerable<ItemSavableState>改成List<ItemSavableState>:
public sealed class PlayerState { public static PlayerState Default => new(100f, 0f, PlayerShipType.TempestMk1, new List<ItemSavableState>()); public float Credits { get; } public float Experience { get; } public PlayerShipType SelectedShip { get; } public List<ItemSavableState> InventoryContent { get; } public PlayerState(float credits, float experience, PlayerShipType selectedShip, List<ItemSavableState> inventoryContent) { Credits = Mathf.Clamp(credits, 0f, float.MaxValue); Experience = Mathf.Clamp(experience, 0f, float.MaxValue); SelectedShip = selectedShip; InventoryContent = inventoryContent ?? throw new ArgumentNullException(nameof(inventoryContent)); } }
同时在GetState()中把枚举转换为List:
public string GetState() { var inventoryStates = Inventory.GetItemsSavableStates().ToList(); PlayerState state = new(Wallet.Balance, Experience.Value, SelectedShipType, inventoryStates); return JsonConvert.SerializeObject(state, Formatting.Indented, s_serializationSettings); }
2. 异常吞导致的问题排查障碍
你的SetState()方法直接吞掉所有异常,无法得知反序列化失败的具体原因——比如子类类型绑定错误、集合实例化失败、ItemSavableState子类构造函数参数不匹配等。这会导致你误以为是集合类型问题,但实际可能是**DefaultSerializationBinder无法正确解析子类类型**。
解决方法:添加异常日志
修改catch块,记录异常详情,方便定位问题:
catch (Exception ex) { Debug.LogError($"加载玩家存档失败: {ex.Message}\n{ex.StackTrace}"); PlayerState defaultState = PlayerState.Default; Wallet = new(defaultState.Credits); SelectedShipType = defaultState.SelectedShip; }
3. 序列化绑定器与子类类型验证
检查DefaultSerializationBinder是否能正确解析RegularAmmoSetSavableState类型:
- Unity中默认程序集名称通常是
Assembly-CSharp而非Scripts,可以通过typeof(RegularAmmoSetSavableState).Assembly.FullName获取正确的程序集名称。 - 优化绑定器,增加容错性:
public sealed class DefaultSerializationBinder : ISerializationBinder { public Type BindToType(string assemblyName, string typeName) { var type = Type.GetType($"{typeName}, {assemblyName}"); if (type == null) { // 尝试Unity默认程序集 type = Type.GetType($"{typeName}, Assembly-CSharp"); } return type ?? throw new InvalidOperationException($"无法找到类型: {typeName}, {assemblyName}"); } public void BindToName(Type serializedType, out string assemblyName, out string typeName) { assemblyName = serializedType.Assembly.FullName; typeName = serializedType.FullName; } }
4. 确保子类的反序列化兼容性
RegularAmmoSetSavableState作为ItemSavableState的子类,需要确保:
- 所有需要反序列化的属性都标注
[JsonProperty]。 - 构造函数添加
[JsonConstructor]标注,确保Newtonsoft能正确匹配参数:
public sealed class RegularAmmoSetSavableState : ItemSavableState { [JsonProperty] public int Amount { get; } [JsonProperty] public float HeatGeneration { get; } [JsonProperty] public float Speed { get; } [JsonProperty] public float Damage { get; } [JsonConstructor] public RegularAmmoSetSavableState(int amount, float heatGeneration, float speed, float damage, Size size, Quality quality, float price) : base(size, quality, price) { Amount = amount; HeatGeneration = heatGeneration; Speed = speed; Damage = damage; } public override IItem Recreate(SavedItemsServices services) { // 实现逻辑 } }
总结
优先修改InventoryContent为List<ItemSavableState>,同时添加异常日志排查具体错误,再验证类型绑定和子类的反序列化逻辑,就能解决存档丢失和报错的问题。
内容的提问来源于stack exchange,提问作者Heavy Violence
相关产品推荐
相关产品推荐

