Skip to content

Commit f5d15d4

Browse files
author
hobobo
committed
更新示例代码
1 parent fd181c2 commit f5d15d4

122 files changed

Lines changed: 4010 additions & 1562 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# MOBA 技能开发流程设计文档
2+
3+
## 1. 目标
4+
5+
建立一套可重复、可追踪、可分层收敛的技能开发流程,覆盖单元测试、冒烟测试、DSL 环境测试与严格模式验收,避免技能只“能跑”但缺少配置正式化、跨模块契约和回归保障。
6+
7+
## 2. 流程分层
8+
9+
### 2.1 单元测试
10+
11+
单元测试面向最小逻辑单元,验证技能参数解析、配置引用、时序计算、伤害/BUFF/区域触发等核心规则。
12+
13+
作用:
14+
- 快速定位代码级回归。
15+
- 保障技能逻辑在不依赖完整战斗环境下仍可验证。
16+
- 适合补齐边界条件,例如 `DurationMs``IntervalMs``position_mode`、触发链路、配置缺失等。
17+
18+
典型覆盖:
19+
- `SpawnAreaSchema` 参数解析。
20+
- `SpawnAreaPlanActionModule` 生命周期计算。
21+
- `MobaBattleConfigReferenceValidator` 配置引用合法性。
22+
- 技能 trace 断言与 HP 变化断言。
23+
24+
### 2.2 冒烟测试
25+
26+
冒烟测试面向最短闭环,验证技能从输入到结果的主链路是否仍可运行。
27+
28+
作用:
29+
- 检查基础战斗入口、UI、运行时服务、输入流是否可用。
30+
- 用最少样本发现集成层破坏。
31+
- 适合作为继续开发前的 P0 门禁。
32+
33+
典型覆盖:
34+
- 启动战斗环境。
35+
- 释放一个技能。
36+
- 观察最基本的 trace、伤害、BUFF、区域生成是否出现。
37+
38+
### 2.3 DSL 环境测试
39+
40+
DSL 测试面向场景化、可读性更强的验收样本,使用 scenario JSON 描述 actor、timeline、setup、stateExpectations 和 contextExpectations。
41+
42+
作用:
43+
- 让设计、策划、开发共享同一份场景描述。
44+
- 便于复现复杂技能过程,例如延迟区域、周期伤害、复合触发链。
45+
- 适合作为技能“正式需求”的验收载体。
46+
47+
典型覆盖:
48+
- Xiao Qiao 技能 2 的延迟区域命中。
49+
- Xiao Qiao 技能 3 的持续 BUFF + 间隔伤害。
50+
- 复杂位置模式、目标模式、持续时间语义。
51+
52+
## 3. 这套流程带来的好处
53+
54+
### 3.1 开发效率
55+
56+
- 单测能最快反馈参数和逻辑错误。
57+
- 冒烟测试能快速确认主链路没有断。
58+
- DSL 测试能让复杂技能一次性验证完整行为。
59+
60+
### 3.2 需求正式化
61+
62+
- area 的 `DelayMs``DurationMs``VfxId`、触发数组不再只停留在约定层。
63+
- `position_mode` 的语义可以被明确写进 scenario 与解析层。
64+
- 配置缺字段、字段混用、隐式默认值问题更容易暴露。
65+
66+
### 3.3 回归控制
67+
68+
- 每个技能都有可回放样本。
69+
- 重构 runtime、view、config、triggering 时不会只靠人工验证。
70+
- 适合阶段性收口和批量回归。
71+
72+
### 3.4 协作成本下降
73+
74+
- 策划看 DSL 就能理解技能预期。
75+
- 开发看单测就能定位实现缺陷。
76+
- QA 看冒烟和 scenario 结果就能判断回归范围。
77+
78+
## 4. 推荐门禁链路
79+
80+
### 4.1 开发前
81+
82+
必须通过:
83+
- 单元测试
84+
- MOBA console smoke
85+
86+
目的:确保继续写功能之前,基础 runtime 没坏。
87+
88+
### 4.2 技能实现阶段
89+
90+
必须补齐:
91+
- 参数解析单测
92+
- 关键 trace 断言
93+
- 场景级 stateExpectation
94+
95+
目的:把“能跑”提升为“行为正式”。
96+
97+
### 4.3 技能收口阶段
98+
99+
必须通过:
100+
- DSL 场景验收
101+
- 关键分支回归
102+
- 配置引用校验
103+
104+
目的:确认技能在真实输入、真实时序、真实配置下稳定。
105+
106+
## 5. 适用对象
107+
108+
这套流程适用于:
109+
- 子弹技能
110+
- 区域技能
111+
- 持续效果技能
112+
- 召唤物技能
113+
- 表现/特效驱动技能
114+
- 需要 `position_mode`、目标集合、偏移、持续时间等参数的复合技能
115+
116+
## 6. 当前实践建议
117+
118+
- 新技能先写最小单测,再补 DSL scenario。
119+
- area 必须区分 `DelayMs``DurationMs`,不能混用。
120+
- `VfxId`、触发效果数组、位置参数必须进入配置校验。
121+
- 复杂技能优先用 scenario JSON 固化期望,再补充 trace 和状态断言。
122+
- 回归时优先跑 P0 门禁,再跑技能相关 acceptance,最后做批量回归。
123+
124+
## 7. 结论
125+
126+
单元测试负责“逻辑正确”,冒烟测试负责“链路可用”,DSL 环境测试负责“需求正式化与可读验收”。三者组合后,技能开发从“修到能跑”升级为“按需求稳定交付、可持续回归”的工程化流程。

Unity/Assets/Resources/moba/aoes.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,19 @@
1818
"Id": 40020201,
1919
"Name": "小乔-甜蜜恋风-目标点击飞区域-配置先行",
2020
"ModelId": 0,
21-
"VfxId": 0,
21+
"VfxId": 10020201,
2222
"AttachMode": 0,
2323
"OffsetX": 0,
2424
"OffsetY": 0,
2525
"OffsetZ": 0,
2626
"Radius": 4.0,
2727
"DelayMs": 300,
28+
"DurationMs": 1200,
2829
"CollisionLayerMask": 1,
2930
"MaxTargets": 10,
30-
"OnDelayTriggerIds": [10020211]
31+
"OnDelayTriggerIds": [10020211],
32+
"OnEnterTriggerIds": [10020211],
33+
"OnExitTriggerIds": [],
34+
"OnIntervalTriggerIds": []
3135
}
3236
]

Unity/Packages/com.abilitykit.demo.moba.runtime/Runtime/Application/Services/Triggering/MobaStageTriggerService.cs

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
using AbilityKit.Ability.World.DI;
1010
using AbilityKit.Ability.World.Services;
1111
using AbilityKit.Ability.World.Services.Attributes;
12+
using AbilityKit.Demo.Moba.Config.BattleDemo.MO;
1213
using AbilityKit.Demo.Moba.Config.Core;
14+
using AbilityKit.Demo.Moba.Services;
1315
using AbilityKit.Demo.Moba.Services.Area;
1416
using AbilityKit.Demo.Moba.Services.Projectile;
1517
using AbilityKit.Demo.Moba.Services.Triggering;
@@ -34,7 +36,40 @@ public sealed class MobaStageTriggerService : IService, IMobaStageTriggerService
3436

3537
public void ExecuteAreaStage(string eventId, int areaId, int templateId, object raw, in MobaAreaRuntimeInfo info, int ownerActorId, int targetActorId, int frame, in Vec3 center, float radius, ColliderId collider, int collisionLayerMask, int maxTargets)
3638
{
37-
// omitted
39+
if (_triggers == null || _configs == null) return;
40+
if (templateId <= 0 || string.IsNullOrEmpty(eventId)) return;
41+
if (!_configs.TryGetAoe(templateId, out var aoe) || aoe == null) return;
42+
43+
var triggerIds = ResolveAreaTriggerIds(eventId, aoe);
44+
if (triggerIds == null || triggerIds.Length == 0) return;
45+
46+
var payload = new AreaEventArgs
47+
{
48+
EventId = eventId,
49+
AreaId = areaId,
50+
TemplateId = templateId,
51+
OwnerActorId = ownerActorId,
52+
TargetActorId = targetActorId,
53+
Frame = frame,
54+
SourceContextId = info.SourceContextId,
55+
RootContextId = info.RootContextId,
56+
OwnerContextId = info.OwnerContextId,
57+
TraceKind = ResolveAreaTraceKind(eventId),
58+
Center = center,
59+
Radius = radius,
60+
Collider = collider,
61+
CollisionLayerMask = collisionLayerMask,
62+
MaxTargets = maxTargets,
63+
Raw = raw,
64+
};
65+
66+
for (var i = 0; i < triggerIds.Length; i++)
67+
{
68+
var triggerId = triggerIds[i];
69+
if (triggerId <= 0) continue;
70+
var request = MobaTriggerExecutionRequest<AreaEventArgs>.Create(triggerId, payload, eventId);
71+
_triggers.ExecuteDirectTrigger(in request);
72+
}
3873
}
3974

4075
public void ExecuteProjectileSpawn(in ProjectileSpawnEvent evt)
@@ -60,5 +95,45 @@ public void ExecuteProjectileHit(in ProjectileHitEvent evt, int hitActorId)
6095
public void Dispose()
6196
{
6297
}
98+
99+
private static int[] ResolveAreaTriggerIds(string eventId, AoeMO aoe)
100+
{
101+
if (aoe == null || string.IsNullOrEmpty(eventId)) return Array.Empty<int>();
102+
103+
switch (eventId)
104+
{
105+
case "area.spawn":
106+
return aoe.OnDelayTriggerIds;
107+
case "area.enter":
108+
return aoe.OnEnterTriggerIds;
109+
case "area.exit":
110+
return aoe.OnExitTriggerIds;
111+
case "area.tick":
112+
case "area.stay":
113+
return aoe.OnIntervalTriggerIds;
114+
default:
115+
return Array.Empty<int>();
116+
}
117+
}
118+
119+
private static MobaTraceKind ResolveAreaTraceKind(string eventId)
120+
{
121+
switch (eventId)
122+
{
123+
case "area.spawn":
124+
return MobaTraceKind.AreaSpawn;
125+
case "area.enter":
126+
return MobaTraceKind.AreaEnter;
127+
case "area.exit":
128+
return MobaTraceKind.AreaExit;
129+
case "area.tick":
130+
case "area.stay":
131+
return MobaTraceKind.AreaStay;
132+
case "area.expire":
133+
return MobaTraceKind.AreaExpire;
134+
default:
135+
return MobaTraceKind.None;
136+
}
137+
}
63138
}
64139
}

Unity/Packages/com.abilitykit.demo.moba.runtime/Runtime/Application/Services/Triggering/PlanActions/Core/MobaActionTargetRequest.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,11 +184,15 @@ private static SearchQueryTemplateMO BuildInlineTemplate(in MobaActionTargetRequ
184184
var rules = BuildRules(in request);
185185
var scorer = BuildScorer(in request);
186186
var selector = new SearchTargetSelectorConfig(0, (int)request.SelectCode);
187+
var explicitTargetPolicy = request.TargetActorId > 0 || request.SourceCode == MobaActionTargetSourceCode.ExplicitActor
188+
? SearchQueryExplicitTargetPolicy.PreferExplicitTarget
189+
: SearchQueryExplicitTargetPolicy.IgnoreExplicitTarget;
190+
187191
return new SearchQueryTemplateMO(
188192
id: 0,
189193
name: "inline_action_target_query",
190194
maxCount: request.MaxCount > 0 ? request.MaxCount : 1,
191-
explicitTargetPolicy: (int)SearchQueryExplicitTargetPolicy.IgnoreExplicitTarget,
195+
explicitTargetPolicy: (int)explicitTargetPolicy,
192196
provider: provider,
193197
rules: rules,
194198
scorer: scorer,

Unity/Packages/com.abilitykit.demo.moba.runtime/Runtime/Application/Services/Triggering/PlanActions/Skill/SpawnAreaPlanActionModule.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ protected override void Execute(object triggerArgs, SpawnAreaArgs args, ExecCtx<
4646

4747
center = center + new Vec3(aoe.OffsetX + args.OffsetX, aoe.OffsetY + args.OffsetY, aoe.OffsetZ + args.OffsetZ);
4848
var radius = args.RadiusOverride > 0f ? args.RadiusOverride : aoe.Radius;
49-
var lifetimeFrames = ResolveLifetimeFrames(args, aoe.DelayMs, ctx.Context);
49+
var lifetimeFrames = ResolveLifetimeFrames(args, aoe.DurationMs, ctx.Context);
5050
var collisionLayerMask = args.CollisionLayerMaskOverride != 0 ? args.CollisionLayerMaskOverride : aoe.CollisionLayerMask;
5151
var stayIntervalFrames = ResolveStayIntervalFrames(args, aoe.IntervalMs, ctx.Context);
5252
var frame = ResolveFrame(ctx.Context);

Unity/Packages/com.abilitykit.demo.moba.runtime/Runtime/Application/Services/Validation/MobaBattleConfigReferenceValidator.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,16 @@ private static void ValidateAreas(MobaConfigDatabase config, TriggerPlanJsonData
318318
var path = $"aoe.{area.Id}";
319319

320320
OptionalRef(Ref<ModelMO>(config.TryGetModel), area.ModelId, report, path + ".modelId", "model", area.Id);
321+
if (area.VfxId < 0)
322+
{
323+
report.Warning(Source, path + ".vfxId", "area vfx id is negative.", area.Id.ToString());
324+
}
325+
326+
if (area.AttachMode < 0)
327+
{
328+
report.Warning(Source, path + ".attachMode", "area attach mode is negative.", area.Id.ToString());
329+
}
330+
321331
ValidateTriggerRefs(triggers, area.OnDelayTriggerIds, report, path + ".onDelayTriggerIds", area.Id);
322332
ValidateTriggerRefs(triggers, area.OnEnterTriggerIds, report, path + ".onEnterTriggerIds", area.Id);
323333
ValidateTriggerRefs(triggers, area.OnExitTriggerIds, report, path + ".onExitTriggerIds", area.Id);
@@ -333,6 +343,11 @@ private static void ValidateAreas(MobaConfigDatabase config, TriggerPlanJsonData
333343
report.Warning(Source, path + ".delayMs", "area delay is negative.", area.Id.ToString());
334344
}
335345

346+
if (area.DurationMs <= 0)
347+
{
348+
report.Error(Source, path + ".durationMs", "area duration must be positive.", area.Id.ToString());
349+
}
350+
336351
if (area.MaxTargets < 0)
337352
{
338353
report.Warning(Source, path + ".maxTargets", "area max targets is negative.", area.Id.ToString());

Unity/Packages/com.abilitykit.demo.moba.runtime/Runtime/Infrastructure/Config/BattleDemo/MO/AoeMO.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public sealed class AoeMO
1717

1818
public float Radius { get; }
1919
public int DelayMs { get; }
20+
public int DurationMs { get; }
2021
public int CollisionLayerMask { get; }
2122
public int MaxTargets { get; }
2223

@@ -41,6 +42,7 @@ public AoeMO(AoeDTO dto)
4142

4243
Radius = dto.Radius;
4344
DelayMs = dto.DelayMs;
45+
DurationMs = dto.DurationMs;
4446
CollisionLayerMask = dto.CollisionLayerMask;
4547
MaxTargets = dto.MaxTargets;
4648

Unity/Packages/com.abilitykit.demo.moba.runtime/Runtime/Infrastructure/Config/Core/LubanConfigGroupDeserializer.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ private static ProjectileDTO DeserializeProjectile(JObject obj)
404404

405405
private static AoeDTO DeserializeAoe(JObject obj)
406406
{
407-
// 妗嗘灦 DTO: Id, Name, ModelId, VfxId, AttachMode, OffsetX, OffsetY, OffsetZ, Radius, DelayMs, CollisionLayerMask, MaxTargets, OnDelayTriggerIds, OnEnterTriggerIds, OnExitTriggerIds, OnIntervalTriggerIds, IntervalMs
407+
// 妗嗘灦 DTO: Id, Name, ModelId, VfxId, AttachMode, OffsetX, OffsetY, OffsetZ, Radius, DelayMs, DurationMs, CollisionLayerMask, MaxTargets, OnDelayTriggerIds, OnEnterTriggerIds, OnExitTriggerIds, OnIntervalTriggerIds, IntervalMs
408408
var dto = new AoeDTO
409409
{
410410
Id = obj["Id"]?.Value<int>() ?? obj["Code"]?.Value<int>() ?? 0,
@@ -417,6 +417,7 @@ private static AoeDTO DeserializeAoe(JObject obj)
417417
OffsetZ = obj["OffsetZ"]?.Value<float>() ?? 0,
418418
Radius = obj["Radius"]?.Value<float>() ?? 0,
419419
DelayMs = obj["DelayMs"]?.Value<int>() ?? 0,
420+
DurationMs = obj["DurationMs"]?.Value<int>() ?? 0,
420421
CollisionLayerMask = obj["CollisionLayerMask"]?.Value<int>() ?? 0,
421422
MaxTargets = obj["MaxTargets"]?.Value<int>() ?? 0,
422423
OnDelayTriggerIds = obj["OnDelayTriggerIds"]?.ToObject<int[]>() ?? Array.Empty<int>(),

Unity/Packages/com.abilitykit.demo.moba.share/Runtime/Game/Config/Dto/ProjectileAreaSummonDtos.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ public sealed class AoeDTO
6262

6363
public float Radius;
6464
public int DelayMs;
65+
public int DurationMs;
6566
public int CollisionLayerMask;
6667
public int MaxTargets;
6768

Unity/Packages/com.abilitykit.demo.moba.view.abstractions/Runtime.meta

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)