主题
效果系统(Effect System)
效果系统是技能、Buff 等游戏机制的底层执行引擎。通过数据驱动的树状结构,将伤害、搜索、创建单位、添加 Buff 等原子操作组合成复杂的 Gameplay 效果链。
核心概念
效果树
效果以树状结构组织。技能触发时创建根效果节点,根节点在执行过程中可以 ExecuteChild 创建子效果节点。整棵树共享一个 ExecutionParamShared 上下文,保存施法者、主目标、技能等级等快照信息。
响应的附加效果由响应 Owner 创建,其 Caster 与默认目标需要另行判断;引发响应的原效果保存在 ResponseContext.IncomingContext。受击自疗、反伤、死亡复活的双方与目标规则见响应系统,不要把两条效果链的上下文混用。
Effect (根: EffectSet "火球术")
├── Effect (子: EffectSearch "范围搜索")
│ ├── Effect (孙: EffectDamage "对目标A造成伤害")
│ └── Effect (孙: EffectDamage "对目标B造成伤害")
└── Effect (子: EffectBuffAdd "施加燃烧Buff")目标传递规则
效果树的目标按以下规则从父节点传递到子节点:
- 根效果的目标由
FactoryCreateTree的defaultTarget参数指定(通常来自技能的AbilityTargetType) - 子效果默认继承父效果的目标
ExecuteChild可以显式指定target参数覆盖继承的目标GameDataEffectSearch搜索到的每个单位会作为子效果的目标(覆盖继承)- 每个效果节点的
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
└── GameDataEffectLaunchMissileGameDataEffect 基类
所有效果节点共享的属性:
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 伤害基于快照计算,不受属性变化影响。
函数委托类型
效果系统使用这些委托实现数据驱动的动态计算:
| 委托类型 | 签名 | 用途 |
|---|---|---|
FuncNumberEffect | Effect → Fixed | 数值(伤害、半径等) |
FuncFloatEffect | Effect → float? | 浮点数值(缩放等) |
FuncUIntEffect | Effect → uint | 无符号整数(数量等) |
FuncTimeEffect | Effect → TimeSpan? | 时间(持续时长、周期等) |
FuncAngleEffect | Effect → Angle? | 角度(扇形中心角等) |
FuncProbabilityEffect | Effect → probability | 概率(触发几率) |
ValidatorEffect | Effect → 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 | 命中效果 | 典型用途 |
|---|---|---|---|---|
| 追踪型 | 单位 | false | CompleteEffect | 普攻弹道、追踪导弹 |
| Skillshot 穿透型 | 点/FixedRange | true | ImpactEffect | 火球、穿透箭矢 |
| 落点 AOE 型 | 点 | false | CompleteEffect | 迫击炮、手雷 |
FixedRange 快捷模式
FixedRange 属性将任何目标(单位或点)自动转换为从发射点出发的固定距离点目标,省去手动组合 GameDataEffectPolarOffset 的步骤:
csharp
_ = new GameDataEffectLaunchMissile(myMissileLink)
{
Missile = Unit.FireballMissile,
FixedRange = static (_) => 800f, // 固定射程 800
DoImpactEntity = true, // 沿途碰撞
ImpactEffect = Effect.FireballDamage, // 碰撞时造成伤害
Speed = static (_) => 800,
};相关文档
- 技能系统 — 技能通过效果链产生 Gameplay 效果
- Buff 系统 —
GameDataEffectBuffAdd/Remove的目标类型 - TargetType 指南 — TargetType 对效果行为和视觉表现的深入解析
- 投射物模式指南 — 三种投射物模式的详细配置和最佳实践