Skip to content

效果系统(Effect System) ​

效果系统是技能、Buff 等游戏机制的底层执行引擎。通过数据驱动的树状结构,将伤害、搜索、创建单位、添加 Buff 等原子操作组合成复杂的 Gameplay 效果链。

核心概念 ​

效果树 ​

效果以树状结构组织。技能触发时创建根效果节点,根节点在执行过程中可以 ExecuteChild 创建子效果节点。整棵树共享一个 ExecutionParamShared 上下文,保存施法者、主目标、技能等级等快照信息。

响应的附加效果由响应 Owner 创建,其 Caster 与默认目标需要另行判断;引发响应的原效果保存在 ResponseContext.IncomingContext。受击自疗、反伤、死亡复活的双方与目标规则见响应系统,不要把两条效果链的上下文混用。

Effect (根: EffectSet "火球术")
├── Effect (子: EffectSearch "范围搜索")
│   ├── Effect (孙: EffectDamage "对目标A造成伤害")
│   └── Effect (孙: EffectDamage "对目标B造成伤害")
└── Effect (子: EffectBuffAdd "施加燃烧Buff")

目标传递规则 ​

效果树的目标按以下规则从父节点传递到子节点:

  1. 根效果的目标由 FactoryCreateTree 的 defaultTarget 参数指定(通常来自技能的 AbilityTargetType)
  2. 子效果默认继承父效果的目标
  3. ExecuteChild 可以显式指定 target 参数覆盖继承的目标
  4. GameDataEffectSearch 搜索到的每个单位会作为子效果的目标(覆盖继承)
  5. 每个效果节点的 TargetType 属性会进一步约束目标类型(见下方 TargetType 说明)

TargetType 属性 ​

GameDataEffect.TargetType 决定效果节点如何解析接收到的目标:

值行为视觉表现
Any(默认)接受目标的原始类型跟随目标类型决定
Unit强制要求目标为单位,否则效果执行失败Actor 跟随目标单位移动
Point从单位目标提取当前位置,转换为坐标点Actor 固定在坐标点播放

继承自 GameDataEffectUnit 的效果类型(如 GameDataEffectDamage、GameDataEffectBuffAdd)默认 TargetType = Unit,因为它们只能作用于单位。

类型层次 ​

GameDataEffect (abstract)
├── GameDataEffectUnit (abstract, 要求目标为单位)
│   └── GameDataEffectDamage
├── GameDataEffectSearch
├── GameDataEffectSet
├── GameDataEffectPersist (abstract)
│   └── GameDataEffectPersistLoop
├── GameDataEffectCreateUnit
├── GameDataEffectCustomAction
├── GameDataEffectBuffAdd
├── GameDataEffectBuffRemove
└── GameDataEffectLaunchMissile

GameDataEffect 基类 ​

所有效果节点共享的属性:

csharp
public abstract partial class GameDataEffect
{
    public FuncProbabilityEffect? Chance { get; set; }           // 触发概率(null = 必定触发)
    public ValidatorEffect? Validators { get; set; }             // 执行前验证条件
    public TargetLocationExpression TargetLocation { get; set; }  // 目标位置表达式
    public EffectAngleExpression? Facing { get; set; }           // 朝向角度
    public FuncFloatEffect? Scale { get; set; }                  // 缩放
    public TargetType TargetType { get; set; }                   // 目标类型约束
    public bool LogExecutionFailure { get; set; }                // 失败时记录日志
    public List<IGameLink<GameDataActor>?>? ActorArray { get; set; } // 视觉表现
}

效果类型详解 ​

GameDataEffectDamage — 伤害 ​

继承 GameDataEffectUnit,对目标单位造成伤害。

csharp
public partial class GameDataEffectDamage
{
    public FuncNumberEffect Amount { get; set; } = (_) => 0;    // 基础伤害值
    public Fixed Random { get; set; }                           // 随机浮动(最终 = Amount + Random(0, Random))
    public IGameLink<GameDataDamageType> Type { get; set; }     // 伤害类型
    public DeathType DeathType { get; set; }                    // 致死时的死亡类型
}

示例:

csharp
var damageLink = new GameLink<GameDataEffect, GameDataEffectDamage>("fireball_damage");
_ = new GameDataEffectDamage(damageLink)
{
    Amount = (effect) =>
    {
        var power = effect.GetUnitProperty(MyProperty.SpellPower) ?? 0;
        return 100 + power * (Fixed)0.5;
    },
    Random = 20,
    Type = ScopeData.DamageType.Physical,
};

GameDataEffectSearch — 区域搜索 ​

在指定区域搜索单位,对每个找到的目标执行子效果。

csharp
public partial class GameDataEffectSearch
{
    public SearchMethod Method { get; set; }                    // 搜索形状
    public FuncNumberEffect? Radius { get; set; }               // 半径(Circle/Cone)
    public FuncAngleEffect? CentralAngle { get; set; }          // 扇形中心角(Cone)
    public FuncNumberEffect? Height { get; set; }               // 高度/长度(Rectangle/Line)
    public FuncNumberEffect? Width { get; set; }                // 宽度(Rectangle/Line)
    public FuncUIntEffect? MinCount { get; set; }               // 最小目标数
    public FuncUIntEffect? MaxCount { get; set; }               // 最大目标数
    public CmdResult MinCountError { get; set; }                // 不足时的错误类型
    public IGameLink<GameDataEffect>? MinCountEffect { get; set; } // 不足时的替代效果
    public IGameLink<GameDataEffect>? Effect { get; set; }      // 对每个目标执行的效果
    public TargetFilterComplex? SearchFilters { get; set; }     // 目标筛选条件
    public ValidatorEffect? SearchValidator { get; set; }       // 自定义验证
    public ContextualPriorityComparer<Effect, Entity>? TargetSorts { get; set; } // 目标排序
    public EffectOffsetExpression? SearchOffset { get; set; }   // 搜索中心偏移
    public LocVarType? ImpactUnitLocVar { get; set; }           // 将命中单位存入局部变量
    public SearchFlags SearchFlags { get; set; }                // 搜索行为控制
}

SearchMethod 枚举 ​

csharp
public enum SearchMethod
{
    Circle,     // 圆形(需要 Radius)
    Cone,       // 扇形(需要 Radius + CentralAngle + Facing)
    Rectangle,  // 矩形(需要 Width + Height + Facing)
    Line,       // 线段(需要 Width + Height(=Length) + Facing)
}

SearchFlags ​

csharp
public class SearchFlags
{
    public bool FailIfNotEnoughTarget { get; set; }   // 目标不足 MinCount 时是否使效果失败
    public bool ExtendByUnitRadius { get; set; }      // 搜索范围是否扩展单位碰撞半径
    public bool ValidateChildren { get; set; }        // 是否预验证子效果
    public bool OffsetByUnitRadiusAndAngle { get; set; }
}

TargetFilterComplex 和 TargetFilter ​

TargetFilterComplex 继承 List<TargetFilter>,多个 TargetFilter 之间为 OR 逻辑。单个 TargetFilter 内部 Required 和 Excluded 为 AND 逻辑。

csharp
public class TargetFilter
{
    public HashSet<UnitFilterTag> Required { get; set; } = [];  // 目标必须拥有的标签
    public HashSet<UnitFilterTag> Excluded { get; set; } = [];  // 目标不能拥有的标签
}

public class TargetFilterComplex : List<TargetFilter> { }

初始化示例(E06 高频问题 — 这是正确的初始化语法):

csharp
// 筛选「存活的敌方单位」
SearchFilters = new TargetFilterComplex
{
    new TargetFilter
    {
        Required = { UnitFilterTag.Enemy, UnitFilterTag.Alive },
        Excluded = { UnitFilterTag.Structure }
    }
};

// OR 逻辑:筛选「敌方单位」或「中立单位」
SearchFilters = new TargetFilterComplex
{
    new TargetFilter { Required = { UnitFilterTag.Enemy } },
    new TargetFilter { Required = { UnitFilterTag.Neutral } }
};

搜索效果完整示例 ​

csharp
var searchLink = new GameLink<GameDataEffect, GameDataEffectSearch>("aoe_search");
_ = new GameDataEffectSearch(searchLink)
{
    Method = SearchMethod.Circle,
    Radius = (_) => 300,
    MaxCount = (_) => 5u,
    SearchFilters = new TargetFilterComplex
    {
        new TargetFilter { Required = { UnitFilterTag.Enemy } }
    },
    SearchFlags = new SearchFlags
    {
        FailIfNotEnoughTarget = false,
    },
    Effect = damageEffectLink,
};

GameDataEffectSet — 效果集合 ​

将多个效果组合为一组,支持顺序执行或加权随机执行。

csharp
public partial class GameDataEffectSet
{
    public required WeightedLinkComplex<IGameLink<GameDataEffect>?> Effects { get; set; }
    public SetFlags SetFlags { get; set; }
    public FuncUIntEffect? MinCount { get; set; }   // 最小执行数
    public FuncUIntEffect? MaxCount { get; set; }   // 最大执行数
}

SetFlags ​

csharp
public class SetFlags
{
    public bool WeightedRandom { get; set; }    // true=加权随机选择, false=顺序执行
    public bool PreferUnique { get; set; }      // 随机时尽量不重复选择
    public bool Unique { get; set; }            // 随机时严格不重复
    public bool OverrideSource { get; set; }    // 将目标覆盖为源(用于反弹等)
    public bool ValidateChildren { get; set; }  // 执行前预验证子效果
}

示例:

csharp
var setLink = new GameLink<GameDataEffect, GameDataEffectSet>("multi_attack");
_ = new GameDataEffectSet(setLink)
{
    Effects = new WeightedLinkComplex<IGameLink<GameDataEffect>?>
    {
        Links =
        [
            new() { Link = slashEffectLink, Weight = 60 },
            new() { Link = thrustEffectLink, Weight = 30 },
            new() { Link = kickEffectLink, Weight = 10 },
        ]
    },
    SetFlags = new SetFlags { WeightedRandom = true, PreferUnique = true },
    MinCount = (_) => 2u,
    MaxCount = (_) => 3u,
};

GameDataEffectPersistLoop — 周期循环效果 ​

按时间间隔或次数周期性执行子效果。继承自 GameDataEffectPersist。

csharp
// 基类 GameDataEffectPersist 的属性
public abstract partial class GameDataEffectPersist
{
    public IGameLink<GameDataEffect>? StartEffect { get; set; }      // 开始时执行
    public IGameLink<GameDataEffect>? CompleteEffect { get; set; }   // 正常结束时执行
    public IGameLink<GameDataEffect>? FinalEffect { get; set; }      // 结束时始终执行(含中断)
    public FuncNumberEffect? Radius { get; set; }                    // 影响半径
    public IGameLink<GameDataUnitProperty>? TimeScaleProperty { get; set; } // 时间缩放属性
    public ValidatorEffect? PersistValidator { get; set; }           // 持续验证,失败时中断
    public PersistChannelFlags ChannelFlags { get; set; }            // 引导行为标志
}

// GameDataEffectPersistLoop 自身属性
public partial class GameDataEffectPersistLoop
{
    public List<IGameLink<GameDataEffect>?> PeriodicEffects { get; set; } // 周期执行的子效果
    public FuncTimeEffect? Period { get; set; }                          // 执行间隔
    public FuncUIntEffect? Count { get; set; }                           // 最大执行次数
    public FuncTimeEffect? Duration { get; set; }                        // 持续时长
    public PersistLoopFlags LoopFlags { get; set; }                      // 循环标志
}

示例 — DOT 伤害:

csharp
var dotLink = new GameLink<GameDataEffect, GameDataEffectPersistLoop>("poison_dot");
_ = new GameDataEffectPersistLoop(dotLink)
{
    Period = (_) => TimeSpan.FromSeconds(2),
    Count = (_) => 5u,
    Duration = (_) => TimeSpan.FromSeconds(10),
    PeriodicEffects = [poisonDamageLink],
    StartEffect = snapshotEffectLink,
};

GameDataEffectCreateUnit — 创建单位 ​

在指定位置创建游戏单位(仅服务端执行)。

csharp
public partial class GameDataEffectCreateUnit
{
    public UnitPicker UnitPicker { get; set; }                              // 单位选择方式
    public LeveledData<IGameLink<GameDataUnit>> SpawnUnitTypePerLevel { get; set; } // 按等级选择单位
    public TargetLocationExpression SpawnTypeUnit { get; set; }             // 从目标位置获取单位
    public EffectOffsetExpression? SpawnOffset { get; set; }                // 生成位置偏移
    public FuncUIntEffect SpawnCount { get; set; } = static (_) => 1;      // 生成数量
    public EffectOwnerExpression SpawnOwner { get; set; }                   // 单位所有者
    public IGameLink<GameDataEffect>? SpawnEffect { get; set; }            // 生成后执行的效果
    public LocVarType? ImpactUnitLocVar { get; set; }                      // 存储新单位到局部变量
    public CreateUnitFlags Flags { get; set; }                             // 创建标志(UseDefaultAI 等)
}

GameDataEffectCustomAction — 自定义逻辑 ​

通过委托函数执行自定义逻辑。

csharp
public partial class GameDataEffectCustomAction
{
    public Func<Effect, ValueTask<bool>> Func { get; set; }   // 异步自定义函数(返回 true 继续执行子效果)
    public IGameLink<GameDataEffect>? Effect { get; set; }    // 函数返回 true 后执行的子效果
}

> 注意:Func 的签名是 Func<Effect, ValueTask<bool>>,支持异步操作。

GameDataEffectBuffAdd — 添加 Buff ​

csharp
public partial class GameDataEffectBuffAdd
{
    public FuncUInt? Stack { get; set; }                     // 添加层数(null = 默认初始层数)
    public IGameLink<GameDataBuff> Buff { get; set; }        // Buff 类型
    public FuncTimeEffect? Duration { get; set; }            // 持续时间(null = 永久)
}

GameDataEffectBuffRemove — 移除 Buff ​

csharp
public partial class GameDataEffectBuffRemove
{
    public FuncUInt? Stack { get; set; }                     // 移除层数
    public IGameLink<GameDataBuff>? Buff { get; set; }       // 指定 Buff(null = 按分类移除)
    public HashSetFilter<BuffCategory>? CategoryFilter { get; set; } // 按分类筛选
    public Func<Buff, CmdResult>? BuffValidator { get; set; } // 自定义验证
    public List<Comparison<Buff>>? Sorts { get; set; }       // 移除优先级排序
}

Effect 运行时类 ​

Effect 是效果节点的运行时实例,由 GameDataEffect.CreateEffect() 创建。

csharp
public partial class Effect
{
    // 数据
    public IGameLink<GameDataEffect> Link { get; }
    public GameDataEffect Cache { get; }

    // 上下文
    public Entity Caster => Shared.Caster;
    public ITarget Source { get; }              // 效果源(默认为施法者)
    public ITarget DefaultTarget { get; }       // 默认目标(从父节点继承)
    public ITarget? Target { get; }             // 解析后的实际目标
    public ExecutionParamShared Shared { get; }  // 共享上下文
    public IExecutableObject? Parent { get; }   // 父效果
    public uint Level => Shared.Level;
    public bool IsTransient { get; }            // true=瞬态, false=持续

    // 局部变量
    public void SetLocVar(LocVarType type, Unit target);
    public void SetLocVar(LocVarType type, ScenePoint target);

    // 属性获取
    public Fixed? GetUnitProperty(IGameLink<GameDataUnitProperty> property,
        PropertyFetchMethod method = PropertyFetchMethod.SnapshotBeforeProperty);

    // 执行
    public CmdResult Execute();
    public CmdResult Validate();
    public bool ResolveTarget();
    public CmdResult PreValidate(ITarget defaultTarget);
    public CmdResult<Effect> ExecuteChild(IGameLink<GameDataEffect>? link,
        ITarget? target = null, Action<Effect>? initialAction = null);

    // 静态工厂
    public static Effect? FactoryCreateTree(IGameLink<GameDataEffect> link,
        ITarget defaultTarget, Entity caster, bool preValidateOnly = false);
    public Effect? FactoryCreateChild(IGameLink<GameDataEffect> link,
        ITarget defaultTarget, bool preValidateOnly = false);
}

ExecutionParamShared — 共享上下文 ​

同一效果树的所有节点共享此对象,保存创建时的快照信息:

csharp
public class ExecutionParamShared
{
    // 不可变上下文
    public IExecutionContext CreationContext { get; }
    public Entity Caster { get; }
    public Ability? Ability { get; }
    public Item? Item { get; }
    public Order? Order { get; }

    // 快照
    public Player CreatorPlayer { get; internal set; }
    public uint Level { get; internal set; }
    public ITarget MainTarget { get; init; }
    public int? AbilityPhase { get; internal set; }

    // 属性快照
    public Fixed? GetUnitProperty(IGameLink<GameDataUnitProperty> property,
        PropertyFetchMethod method = PropertyFetchMethod.SnapshotBeforeProperty);
    public void UpdateUnitPropertySnapshot(IEnumerable<IGameLink<GameDataUnitProperty>> properties);

    // 引导管理
    public bool IsChanneling { get; }
    public void StopAllChannel();
}

属性快照机制 ​

效果树的 GetUnitProperty 支持三种获取策略:

csharp
public enum PropertyFetchMethod
{
    SnapshotBeforeProperty,  // 优先快照值,无则取当前值(默认)
    PropertyBeforeSnapshot,  // 优先当前值,无则取快照值
    PropertyAndUpdate,       // 取当前值并更新快照
}

快照用于解决长时间效果(如 DOT)的一致性问题:施法时快照属性值,后续每 tick 伤害基于快照计算,不受属性变化影响。

函数委托类型 ​

效果系统使用这些委托实现数据驱动的动态计算:

委托类型签名用途
FuncNumberEffectEffect → Fixed数值(伤害、半径等)
FuncFloatEffectEffect → float?浮点数值(缩放等)
FuncUIntEffectEffect → uint无符号整数(数量等)
FuncTimeEffectEffect → TimeSpan?时间(持续时长、周期等)
FuncAngleEffectEffect → Angle?角度(扇形中心角等)
FuncProbabilityEffectEffect → probability概率(触发几率)
ValidatorEffectEffect → CmdResult验证器

视觉表现(ActorArray) ​

GameDataEffect.ActorArray 配置效果执行时播放的视觉表现。表现行为由 TargetType 和 IsTransient 决定:

  • IsTransient = true(瞬态效果):所有 Actor 强制一次性播放
  • IsTransient = false(持续效果):Actor 持续播放直到效果结束

表现位置由 TargetType 决定:

  • Unit 目标:Actor 附着到目标单位,跟随移动
  • Point 目标:Actor 固定在世界坐标播放

常见模式 ​

技能效果链 ​

典型的技能效果链结构:

Ability
└── EffectSet (根效果)
    ├── EffectSearch (搜索区域内敌人)
    │   └── EffectDamage (对每个目标造成伤害)
    └── EffectBuffAdd (给施法者添加增益)

在 OnGameDataInitialization 中注册 ​

csharp
public class MyEffects : IGameClass
{
    public static void OnRegisterGameClass()
    {
        Game.OnGameDataInitialization += OnGameDataInitialization;
    }

    private static void OnGameDataInitialization()
    {
        if (Game.GameModeLink != ScopeData.GameDataGameMode.MapGameMode) return;

        // 创建伤害效果
        var dmgLink = new GameLink<GameDataEffect, GameDataEffectDamage>("my_damage");
        _ = new GameDataEffectDamage(dmgLink)
        {
            Amount = (_) => 100,
            Type = ScopeData.DamageType.Physical,
        };

        // 创建搜索 + 伤害的 AOE 效果
        var searchLink = new GameLink<GameDataEffect, GameDataEffectSearch>("my_aoe");
        _ = new GameDataEffectSearch(searchLink)
        {
            Method = SearchMethod.Circle,
            Radius = (_) => 400,
            MaxCount = (_) => 8u,
            SearchFilters = new TargetFilterComplex
            {
                new TargetFilter { Required = { UnitFilterTag.Enemy } }
            },
            Effect = dmgLink,
        };
    }
}

投射物效果(GameDataEffectLaunchMissile) ​

发射弹道投射物。支持三种常见模式,详细文档见 投射物模式指南。

模式目标类型DoImpactEntity命中效果典型用途
追踪型单位falseCompleteEffect普攻弹道、追踪导弹
Skillshot 穿透型点/FixedRangetrueImpactEffect火球、穿透箭矢
落点 AOE 型点falseCompleteEffect迫击炮、手雷

FixedRange 快捷模式 ​

FixedRange 属性将任何目标(单位或点)自动转换为从发射点出发的固定距离点目标,省去手动组合 GameDataEffectPolarOffset 的步骤:

csharp
_ = new GameDataEffectLaunchMissile(myMissileLink)
{
    Missile = Unit.FireballMissile,
    FixedRange = static (_) => 800f,       // 固定射程 800
    DoImpactEntity = true,                 // 沿途碰撞
    ImpactEffect = Effect.FireballDamage,  // 碰撞时造成伤害
    Speed = static (_) => 800,
};

相关文档 ​