Unity C#物品属性系统设计:如何识别基类对象类型?
1. 新增类型标识字段
在ItemAttributeBase基类中添加一个枚举类型字段,用来标记当前属性的具体类型:
public enum ItemAttributeType { Condition, Heal, Damage // 后续新增属性直接扩展枚举即可 } public abstract class ItemAttributeBase { public ItemAttributeType AttrType; // 其他基类字段/方法 }
每个属性子类在初始化时,将AttrType设为对应枚举值(比如ItemATR_Condition的AttrType = ItemAttributeType.Condition)。后续在非SO的Item类中,只需判断AttrType就能快速识别属性类型,再强转为对应子类处理。
这种方式实现简单,JSON序列化兼容枚举,性能开销可以忽略。
2. 直接使用C#类型判断
利用C#的is关键字或GetType()方法直接判断属性类型:
foreach (var attr in itemAttributes) { if (attr is ItemATR_Condition conditionAttr) { // 处理耐久属性逻辑 var currentCondition = conditionAttr.CurrentValue; } else if (attr is ItemATR_Heal healAttr) { // 处理回血属性逻辑 var healAmount = healAttr.HealAmount; } }
这种方式不需要额外字段,代码简洁,但如果属性类型较多,会产生大量if-else分支,适合属性类型较少的场景。
3. 访问者模式(适合属性类型频繁扩展的场景)
如果后续会频繁新增属性类型,不想维护大量条件分支,可以用访问者模式解耦类型判断与逻辑处理:
首先定义访问者接口:
public interface IItemAttributeVisitor { void Visit(ItemATR_Condition condition); void Visit(ItemATR_Heal heal); void Visit(ItemATR_Damage damage); }
在基类中添加抽象的接受方法:
public abstract class ItemAttributeBase { public abstract void Accept(IItemAttributeVisitor visitor); }
每个属性子类实现Accept方法:
public class ItemATR_Condition : ItemAttributeBase { public override void Accept(IItemAttributeVisitor visitor) { visitor.Visit(this); } }
最后在处理属性时,实现访问者接口并编写对应逻辑:
public class AttributeHandler : IItemAttributeVisitor { public void Visit(ItemATR_Condition condition) { // 耐久属性处理逻辑 } public void Visit(ItemATR_Heal heal) { // 回血属性处理逻辑 } } // 使用示例 var handler = new AttributeHandler(); foreach (var attr in itemAttributes) { attr.Accept(handler); }
这种方式符合开闭原则,新增属性只需扩展访问者接口和子类实现,无需修改原有逻辑,但实现复杂度稍高。
如果上述类型识别方案仍无法满足需求,或现有SO+动态Item的组合存在序列化/性能隐患,可以考虑以下替代设计:
1. 扁平化属性结构
放弃继承式属性体系,在Item类中直接定义常用属性字段(用可空类型标记非必备属性):
public class Item { public string ItemTemplateId; // 关联对应的ItemBaseSO public int? Condition; // 无该属性则为null public int? HealAmount; public int? Damage; // 其他常用属性... }
静态SO中也采用相同结构存储初始属性值。这种方式JSON序列化简单直接,无需处理多态类型,访问属性时直接调用字段即可,适合属性类型不多的背包系统。缺点是属性过多时类会臃肿。
2. 带类型标记的多态JSON序列化
如果坚持使用继承式属性,可通过JSON序列化工具保留类型信息,反序列化后直接得到具体子类对象。以Newtonsoft.Json为例:
// 序列化配置 var settings = new JsonSerializerSettings { TypeNameHandling = TypeNameHandling.Auto }; string json = JsonConvert.SerializeObject(itemAttributes, settings); // 反序列化 var attributes = JsonConvert.DeserializeObject<List<ItemAttributeBase>>(json, settings);
序列化后的JSON会包含C#类型的全限定名,反序列化后可直接使用具体子类。但要注意,类名或命名空间修改会导致反序列化失败,存在耦合风险。
3. 属性与数据分离设计
将静态模板数据与动态背包数据彻底分离:
- SO仅存储物品的基础属性模板,每个模板对应唯一ID;
- 动态
Item类仅存储模板ID和动态变化的属性键值对:
public class Item { public string TemplateId; public Dictionary<string, int> DynamicAttributes = new Dictionary<string, int>(); }
比如斧头的耐久变化,可存储为DynamicAttributes["Condition"] = currentValue。需要获取完整属性时,通过TemplateId找到对应SO,结合动态属性计算当前值。这种方式灵活性极高,新增属性无需修改代码,仅需在SO中配置属性键即可,但需要额外处理属性默认值和键名校验。
内容的提问来源于stack exchange,提问作者user20494201

