父类能否引用子类字段?Unity法术系统设计疑问
Unity C# 法术系统属性修改的规范实现方案
核心问题解答
你提到的“判断对象是否存在某字段再应用修改”可以通过反射实现,但这属于取巧写法:
- 反射会带来明显的性能开销,尤其在Unity这类对帧率敏感的游戏环境中,频繁调用会影响运行效率
- 字段名硬编码,后续修改字段名时容易出现遗漏,代码维护成本高
- 代码可读性差,其他开发者难以快速理解逻辑
因此不推荐这种方式,下面是几种符合面向对象规范的替代方案:
方案一:模板方法模式(推荐)
利用父类定义通用逻辑,子类仅实现自身特有的属性修改,既复用代码又保证扩展性:
public abstract class Spell { public float dmg; // 公共入口,包含通用逻辑 public void UpdateStats(Caster caster) { // 所有法术共享的伤害修改逻辑 dmg *= caster.dmgModifier; // 调用子类的特有属性修改逻辑 ApplySpecificModifiers(caster); } // 抽象方法,子类必须实现自己的特有属性处理 protected abstract void ApplySpecificModifiers(Caster caster); } public class Fireball : Spell { public float projectileSpeed; protected override void ApplySpecificModifiers(Caster caster) { projectileSpeed *= caster.projectileSpeedModifier; } } public class Lightning : Spell { public float stunDuration; protected override void ApplySpecificModifiers(Caster caster) { stunDuration *= caster.stunDurationModifier; } } public class Caster { public float dmgModifier; public float projectileSpeedModifier; public float stunDurationModifier; }
优势
- 完全符合开闭原则:新增法术时只需继承
Spell并实现ApplySpecificModifiers,无需修改父类代码 - 逻辑清晰,通用代码集中在父类,子类只关注自身特有逻辑
- 无性能损耗,运行效率高
方案二:接口组合模式
通过定义不同的属性接口,让法术类按需实现,父类通过类型判断应用对应修改,适合属性组合复杂的场景:
public abstract class Spell { public float dmg; public void UpdateStats(Caster caster) { dmg *= caster.dmgModifier; // 判断当前法术是否具有 projectileSpeed 属性 if (this is IHasProjectileSpeed projectileSpell) { projectileSpell.ProjectileSpeed *= caster.projectileSpeedModifier; } // 判断当前法术是否具有 stunDuration 属性 if (this is IHasStunDuration stunSpell) { stunSpell.StunDuration *= caster.stunDurationModifier; } } } // 定义属性接口 public interface IHasProjectileSpeed { float ProjectileSpeed { get; set; } } public interface IHasStunDuration { float StunDuration { get; set; } } // 法术类按需实现接口 public class Fireball : Spell, IHasProjectileSpeed { public float ProjectileSpeed { get; set; } } public class Lightning : Spell, IHasStunDuration { public float StunDuration { get; set; } } public class Caster { public float dmgModifier; public float projectileSpeedModifier; public float stunDurationModifier; }
优势
- 灵活性高:一个法术可以同时实现多个接口(比如既有飞行速度又有眩晕效果的法术)
- 类型安全,避免反射的弊端
- 新增属性时只需添加新接口,修改父类的
UpdateStats即可
方案三:反射实现(不推荐)
如果一定要实现“存在字段则应用修改”的逻辑,以下是反射的实现方式,但仅作技术参考,不建议在实际项目中使用:
public abstract class Spell { public void UpdateStats(Caster caster) { var spellType = GetType(); var casterType = typeof(Caster); // 处理伤害属性 ApplyModifier(spellType, casterType, this, caster, "dmg", "dmgModifier"); // 处理飞行速度属性 ApplyModifier(spellType, casterType, this, caster, "projectileSpeed", "projectileSpeedModifier"); // 处理眩晕时长属性 ApplyModifier(spellType, casterType, this, caster, "stunDuration", "stunDurationModifier"); } private void ApplyModifier(Type spellType, Type casterType, object spellObj, object casterObj, string spellFieldName, string casterFieldName) { var spellField = spellType.GetField(spellFieldName); var casterField = casterType.GetField(casterFieldName); if (spellField != null && casterField != null) { float currentValue = (float)spellField.GetValue(spellObj); float modifier = (float)casterField.GetValue(casterObj); spellField.SetValue(spellObj, currentValue * modifier); } } }
弊端
- 反射性能开销大,频繁调用会导致帧率下降
- 字段名硬编码,修改字段名时容易出错
- 代码可读性差,排查问题难度高
内容的提问来源于stack exchange,提问作者Hodge
相关产品推荐
相关产品推荐

