You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

父类能否引用子类字段?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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 15:07:15