Files
AIGame/Doc/Design/Combat/SkillSystem.md
T

25 KiB
Raw Blame History

关卡战斗技能系统设计

2026-10-20。首版服务于现有十伙伴、五类敌人、二十关的自动战斗,统一普攻、主动技能、动作、特效、音效和战斗数据处理。技能由配置组合,服务端执行逻辑并产生有序战斗事件,客户端按战斗时钟播放表现。

本次交付设计、禁用运行时的配置样例、TangoFlux音效制作清单及校验工具。现行 greenfield.json、battleRules=2、在途挑战及奖励协议保持原状;运行时接入按下文实施顺序进行。

现有基础与版本边界

PrototypeBattle.cs 与 AIServer/src/game.cpp::battle_lanes 使用0.05秒固定步长,伙伴技能为shield/heal/slow和伤害分支,命中即时处理;同一单位可以在同tick释放技能和普攻。PrototypeFlow.RefreshBattle 读取Pulse/SkillPulse,驱动原型弹体及缩放表现。

服务端在battle.begin时计算并保存结果,以readyAt限制最早结算;battle.finish不接受客户端自报胜负。现阶段没有战中手动决策。新系统沿用该授权方式,自动战斗的技能选择也由服务端执行。

带前摇、投射物到达伤害和动作互斥的新规则指定为 battleRules=3。这些会影响战斗节奏和结果,不能直接替换旧分支并声称数值行为完全等价。旧挑战继续读取已冻结结果/快照;新规则发布前须完成平衡、协议和旧版本恢复测试。

系统结构

flowchart TD
    Config[冻结玩法配置与技能定义] --> Sim[服务端 BattleSimulation]
    AI[自动选技能与目标] --> Sim
    Sim --> Runtime[SkillRuntime 状态与时间调度]
    Runtime --> Actions[逻辑行为执行器]
    Actions --> Data[伤害 治疗 护盾 减速 投射物]
    Data --> Events[有序 BattleEvent 与权威结果]
    Events --> Clock[客户端 BattlePlaybackClock]
    Clock --> Present[SkillPresentationDirector]
    Present --> Motion[动作播放器]
    Present --> VFX[特效池]
    Present --> SFX[音效池]
    Events --> UI[血量 状态 飘字 战斗统计]
模块 职责 不承担的职责
SkillDefinition 目标、前摇、行为、冷却、资源消耗、打断及效果参数引用 保存每个单位当前血量或冷却
SkillRuntime 一次施法的castId、阶段、目标、释放tick和实例状态 直接调用Animator/ParticleSystem/AudioSource
SkillActionExecutor 执行Damage、Heal、ApplyStatus、LaunchProjectile等行为 读取贴图、骨骼或Unity碰撞结果决定伤害
StatusSystem 状态实例、过期、合并、护盾吸收、移速修正 播放循环特效
BattleEvent 记录已发生的逻辑事实与结果数据 接受客户端改写结果
SkillPresentation 将事件映射为动作、特效、音效、飘字等组合行为 计冷却、再次扣血或发放奖励

一次技能可以包含多个逻辑行为,例如命中后先造成伤害再施加减速;一个表现事件也可同时播放动作、特效和音效。首版提供有限的类型化行为和处理器注册表,配置只引用白名单参数与公式,不执行任意脚本字符串。复杂节点编辑器、技能连招树和战中手动输入后续扩展。

模板使用 actions[] 顺序组合逻辑行为,每条有独立actionId和trigger。命中时依次执行并输出结果;后续增加“伤害+减速”的技能,只需增加ApplyStatus行为及对应正式数值来源。表现使用事件下的 bindings[event][] 组合animation/vfx/sfx,同一事件可触发多种表现。两侧按castId和actionId关联,资源数据不会进入服务端计算。

配置和运行实例

配置分为玩法和表现两部分,数值只由 Config/Gameplay/greenfield.json 的冻结副本解析。技能样例通过actorId、sourcePointer、template引用现有角色参数,不重复维护一份攻击力、射程或T1/T2数值。

数据 主要字段
SkillDefinition skillId、actorKind、slot、template、parameterSources、selector、timing、channel、priority、cost、cooldown、interruptPolicy
LogicalAction actionId、phase或触发条件、type、targetSelector、effect参数、delivery策略
SkillPresentation presentationId、事件名、cue数组;每条cue为animation/vfx/sfx,含assetId、anchor、生命周期token
CastInstance battleId、castId、skillId、sourceEntityId、targetIds、startTick、releaseTick、endTick、casterStatsSnapshot、阶段
ProjectileInstance projectileId、castId、targetId、releaseTick、arrivalTick、释放位置、目标位置、payload、失效策略
StatusInstance statusId、ownerId、sourceId、value、expiresTick、mergeGroup、来源记录

样例文件:

  • Config/Combat/skill_system_v1.proposal.json:8种模板、25个技能绑定,覆盖每只伙伴的普攻/主动技能和每类敌人的普攻。
  • Config/Combat/skill_presentations_v1.proposal.json:12个表现配置、2个现有动画引用、18项待制作的特效/音效引用。
  • Doc/Design/Sound/BattleSkills/manifest.json:9项TangoFlux音效制作配方,状态planned_not_generated。

proposal文件显式 runtimeEnabled=false,现有CMake和客户端加载器不引用它们。接入时将技能逻辑定义加入正式配置的skillSystem字段,由CMake参与同一内容哈希并冻结入battleSnapshot;表现配置作为YooAsset资源按版本加载。禁止运行时把“当前最新技能表”混入旧挑战。

参数解析器至少支持以下白名单。配置字段的秒数转为整数tick时向上取整;UI仍可显示秒。

参数 来源与语义
basic_cooldown / basic_range 伙伴或敌人的cooldown;伙伴range;敌人近战接触距离首版为1世界单位
actor_attack 本场战斗初始化后的攻击力,已含共享等级/进化成长;敌人含stage.attackScale
ability_cooldown / ability_range 对应伙伴ability.cooldown/range
ability_value / duration / radius / target_count 按该单位冻结tier读取t1/t2、duration/t2Duration、radius/t2Radius、targets/t2Targets
boss_control_scale ability.bossControlScale,控制首领减速强度

施法阶段与竞争规则

状态流为 Ready → Windup → Released → Recovery → Ready,另有Cancelled。冷却以绝对 nextReadyTick 保存,与施法阶段独立:从施法开始计冷却,后摇结束且冷却到期才可再次施放。

同一单位首版只有一个main_action通道。每tick先收集符合条件的主动技能候选,再普攻;主动技能优先级100,普攻10,同优先级按skillId稳定排序。未进入射程、治疗没有受伤目标、单位死亡或受硬控制时不开始,也不扣冷却/资源。首版没有额外蓝量,cost为0;将来扩展消耗须在CastStarted时原子扣除。

开始后预留动作通道直到endTick;新技能不叠播在普攻上,也不任意打断后摇。死亡/硬控制在释放前取消,清理蓄力,首版不退款、不退冷却;释放后的投射物独立存在,施法者死亡不撤销已经发出的攻击。缺失动画、特效或音效不会取消逻辑施法。

零前摇技能在开始的同tick释放,调度器必须处理新加入的到期任务。结束和取消只执行一次。恢复正常动作时依据当前移动状态回到Idle或Walk,不能把Attack布尔值长期置true。

固定时间和数据规则

逻辑tick为50ms,独立于Unity帧率、AnimationEvent、声音长度、Time.timeScale和网络到达时间。客户端用deltaTime累积播放tick并插值位置,所有逻辑事件按顺序消费;帧率降低可以合并表现更新,不能丢弃血量、状态、死亡或胜负事件。

建议新版内核使用int64中间计算:血量/攻击以千分之一单位保存,位置以毫米保存,倍率以万分比保存,时间以整数tick保存。配置编译时明确每次取整位置。此为新版规则,仍须以基准样例检查量化后的结果,不能把“使用整数”当成已经通过一致性验证。

伤害先按施法开始时攻击快照乘技能倍率;命中时读取目标防御和当前护盾。元素开启时按现行元素表计算、减防、向下取整到整点伤害且至少1;元素关闭时沿用raw伤害语义。护盾先吸收,再扣血;实际血量变化计入伤害/治疗统计,过量治疗和溢出伤害单独记录。死亡只允许Alive→Dead一次,不能重复累计击败数。漏怪为Exited状态,不计击败。

同tick采用固定阶段:

  1. 生成本tick单位、处理到期状态和此前已发出投射物的到达;按稳定键应用结果并处理死亡。
  2. 更新移动/阻挡关系,收集仍存活单位的自动施法请求。
  3. 推进已有施法与本tick新施法,产生到期释放;先收集本阶段的有效释放,再结算其即时效果。
  4. 以 (dueTick, sourceEntityId, castSequence, actionIndex, hitIndex, targetEntityId) 排序执行;目标已经死亡则跳过并记录失效结果。本阶段已经有效释放的行为不因稍后同tick施法者死亡而撤回。
  5. 处理死亡、漏怪、动作结束和关卡胜负,输出最终事件序号。耐久归零/达到关卡超时优先判负,其后才判断全部敌人清除;同tick最后一敌死亡与超时相遇时也按此顺序。新触发的反应行为首版不支持递归执行,后续必须入队并限制连锁深度。

首版目标选择和技能结果不使用随机数;seed保留给确定的表现变化和未来扩展。将来加入暴击、闪避等概率效果时,须同时固定随机算法、种子和抽样顺序,并由服务端记录结果。

首版普通近战按释放tick结算。远程投射物从逻辑位置计算 travelTicks=max(1,ceil(distanceMm/speedMmPerTick)),固定arrivalTick;样例速度每tick800mm,即16世界单位/秒。飞行路径由客户端插值显示,到达是否命中由逻辑决定。范围在释放时检查,命中时检查目标仍存活且未离场;目标丢失时expire,不自动改打其它目标。目标移动期间表现追踪同一目标,到达tick保持不变。以后增加非追踪弹体、范围碰撞或地面落点需显式增加delivery类型。

选敌 治疗 护盾和减速

首版选择器覆盖当前玩法:同路最前敌人、自身、范围内血量比例最低的友方、同路最前阻挡伙伴。选敌按逻辑Z距离再entityId排序;最低血量比例用整数交叉相乘比较,再按slot排序。候选目标在开始时用于判断能否施法,释放时重新选择并冻结本次目标集;投射物发出后不重选。

治疗范围沿用路线的实际左右关系:lane0在中、lane1在左、lane2在右;相邻路线不是简单的laneId相差1。T2治疗radius=1可覆盖中路相邻的一路,具体候选集合按lane_position映射计算。burst和chain按targets/t2Targets选取同路前N个目标;当前chain是多目标伤害,首版不自动加入有延时的跳跃衰减或额外弹射目标。

效果 首版规则
Damage 普攻攻击倍率1;burst/chain为攻击×ability_value,按目标分别执行并记录结果
Heal ability_value为固定治疗量,不额外乘攻击;封顶MaxHp,满血友方不作为起手目标
Shield shield_main共享合并组:取当前剩余盾和新盾较大值,过期时间改为本次命中tick+duration;到期/耗尽移除
Slow slow_main共享合并组:取较小剩余移速倍率,过期取较晚值;首领倍率为1-(1-value)×bossControlScale,到期还原

上述合并方式对应现有规则的主要语义。以后需要各来源独立过期、叠层、驱散或免疫时,以新状态定义明确合并策略,不能默默改写旧技能效果。每次应用状态保留来源,用于状态特效归属和统计。

战斗事件与表现行为

所有事件携带battleId、eventSeq、tick、type、castId、skillId、sourceEntityId,以及按类型需要的targetId/projectileId/statusId。HitResolved包含validHit和本次命中的效果结果数组:原始值、吸收值、实际伤害/治疗、hpAfter、shieldAfter、是否死亡及失效原因。客户端直接应用这些结果,不再次调用伤害公式。命中cue要求validHit=true;被护盾完全吸收仍为有效命中,已经死亡/离场的目标则不播放命中cue,投射物通过ProjectileExpired回收。

事件 表现行为
CastStarted 播放技能动作、朝向目标、开启手部蓄力特效和蓄力声音
CastReleased 停止蓄力token,播放发射音;瞬时技能在此tick产生命中结果
ProjectileSpawned 从出手挂点创建/租用弹体表现,绑定projectileId及预计到达时间
HitResolved 停止对应弹体,更新血量,播放命中特效/命中音/飘字;一次命中可有多项逻辑结果
StatusApplied / StatusRemoved 按statusId添加或停止护盾/减速等持续表现,刷新现有实例而不重复堆节点
CastEnded / CastCancelled 回收施法局部token并恢复移动/待机;已经发射的弹体不归施法结束清理
UnitDied / BattleEnded 停止该单位或本场所属表现、回收对象池和音源,结束战斗视图

逻辑效果去重键为 (battleId,castId,actionId,targetEntityId,hitIndex);表现去重键为 (battleId,eventSeq,cueIndex)。对象池索引不能作entityId,重连/换场必须隔离battleId。资源异步返回时检查所属battleId和token仍有效,过期回调立即释放handle,避免在新一场播放旧效果。

动作、特效、音效和UI共用BattlePlaybackClock。暂停时冻结播放时钟、暂停本场动作/粒子及本场持有的AudioSource,UI音效仍可使用;恢复不重复触发开始事件。倍速改变事件间隔、动画速度和弹体插值;首版短音效保持原音高,超过有效播放窗口的旧one-shot可以跳过。逻辑结果始终完整应用。初始音源上限建议12路,按技能重要性、距离和同类冷却控制抢占,实机再定预算。

重连默认重播同一已授权挑战;以后增加seek时从状态快照+事件游标重建血量、状态及在飞弹体,历史一次性声音/爆闪不补播。资源未就绪使用已制作原型表现或跳过装饰,不暂停权威结算。正式关卡需在播放前预加载本阵容和敌人所需的表现资源。

火喵施法样例

火喵已有Attack长1.1秒、释放标记0.55秒、非循环。普攻冷却0.9秒,因此普攻动作按0.9秒窗口播放,速度为1.1/0.9约1.222;释放发生在第9tick即0.45秒,结束第18tick。主动技能冷却6秒,动作按原速播放,释放第11tick即0.55秒,结束第22tick。

阶段 逻辑行为 动作 特效 音效
开始 验证条件、占main_action、记录攻击快照、开始冷却 Attack+双爪蓄力粒子+FireCharge
释放 选取同路前方目标,创建逻辑投射物 停蓄力、FireLaunch、FireProjectile从Cast挂点飞出
到达 目标有效则按普攻×1或主动技能T1×1.3/T2×1.8造成伤害 FireHit+命中音+伤害飘字
后摇结束 释放动作通道,等待下一次可施法 根据移动状态恢复Idle/Walk;在飞火球继续存在

例:以本次施法开始为相对tick0,T1主动技能在tick11释放;释放时目标距离4世界单位,以16单位/秒飞行5tick,在tick16结算伤害;tick22动作结束。多个火球可以在飞行中并存,每个有独立projectileId。当前该技能targets=1、radius=0,命中特效不得让玩家误认为有范围溅射。

现有 OnCompanionAttackImpact AnimationEvent保留为动作对齐诊断标记,不再独立发射或扣血。技能时钟事件是唯一表现调度源,避免事件回调和时间轴各发一颗。骨骼动画由已有.anim驱动;首版通过Animator动作适配器/局部速度参数接入,精确seek扩展可使用Playables手动采样。Unity Playables支持组织和混合动画、音频等数据源,详见Unity 2022.3文档。

角色挂点和资源归属

逻辑侧只认识entityId和逻辑位置。角色Prefab提供语义挂点Cast、Body、Hit、Head;表现配置映射到各自骨架,业务代码不硬编码bone_7等名称。已有火喵/岩团团尚未制作这些技能挂点,设计样例采用bounds fallback;正式接入须通过Pipeline制作挂点并在动作全过程检查位置。

角色局部-Z为正面。世界朝向由目标方向决定,不能总朝世界-Z或直接把Unity的transform.forward当角色正面。粒子挂点随动作,投射物释放后进入世界空间;护盾随目标,命中爆闪按HitResolved位置播放。根位移由逻辑驱动,动作Root Motion关闭。

资源加载走YooAsset,动画/特效/音效引用按地址和类型校验,资源handle由战斗资源集持有;动作实例持有租用对象和生命周期token。BattleEnded、切页和销毁都必须停止本场循环声音、清空粒子/Trail并归还池,禁止把旧轨迹带到下一次播放。

TangoFlux音效制作

严格使用 D:/UD/AI/XModel/TangoFlux_AI调用指南.md 的TangoFluxService方法。现有 Tools/generate_ui_sfx.py 接受通用manifest,可复用其HTTP提交/轮询/下载流程制作首批战斗音效;长期应抽取共享客户端,以请求内容哈希校验缓存,避免只按文件存在判断有效。

  1. GET /api/status,确认ready和内存;未加载时POST /api/warmup后复查。不为生成音效擅自停止其它3D/音乐服务。
  2. 英文声学描述,固定seed;POST /api/generate,duration在1~30秒,steps=50、guidance_scale=4.5、sync=false。首批并发上限2,服务queued+running总上限4。
  3. 每0.8秒GET /api/jobs/{id};429退避,失败记录error,不能无限重试。成功后从返回url或/api/jobs/{id}/audio下载。
  4. 保留44.1kHz立体声WAV原稿、jobId、提示词、seed、参数和SHA256;检测静音、爆音和削波,并实际试听。服务最短生成1秒,实际几十到几百毫秒的技能声音需后期裁切、淡入淡出和增益调整,不能请求duration=0.2。
  5. FireCharge、FireLaunch、FireHit分开生成;蓄力有独立token可在释放时停止。火喵之外补RockHit、Heal、Shield、Slow、Lightning、EnemyHit。原稿/后期输出分开,剪切起止点写制作记录。
  6. 原始文件在Tools/output与Doc/Design/Sound,最终游戏文件在 Assets/Bundles/Sound/SFX/BattleSkills/。AudioClip导入、音源Prefab和混音设置经Unity Pipeline制作,检查音效开关、暂停、倍速、多实例及切场清理。
  7. 生成并验收完成后登记 Doc/ResDesc_Sound.md;目前manifest只列制作需求,没有生成音频。指南载明模型权重为非商业研究用途,资源发布前按上游许可核查并保留结论,不能把此清单视为商用授权。

可复用命令:python Tools/generate_ui_sfx.py --manifest Doc/Design/Sound/BattleSkills/manifest.json --max-inflight 2。这是后续制作入口,本轮未执行。该旧脚本会复制原始WAV到Bundles,正式交付仍须完成后期、Pipeline导入与试听验收。

服务端协议和关卡过程

首版新增SkillRuntime在C++服务端执行一次,产生权威结果与事件轨迹;客户端新版消费轨迹,避免继续长期维护两套独立技能逻辑。C#纯数据参考实现可用于交叉测试,但不得覆盖服务端结果。战斗数据不依赖MonoBehaviour、Unity Physics或资源加载。

拟扩展battle.begin响应/冻结数据:rulesVersion、logicVersion、gameplayHash、compiledSkillsHash、seed、stage/formation快照、traceId、traceHash、eventCount、tickMs及事件首块。长轨迹用拟新增battle.events(cursor,limit)按本玩家已授权battleId获取;返回范围、nextCursor和校验值。预计算结果及轨迹在同一授权记录中提交,重试返回同一战斗,不重复开局或发奖。轨迹容量、分页上限和压缩格式需按20关最坏情况实测后设定。

单位移动用带起止tick/位置的分段记录,减速、到达阻挡点或目标变化时分段;客户端插值,不必把每个单位每tick的位置重复传一遍。首次实现可先保留完整逐tick开发轨迹,确认正确性后再压缩,压缩前后必须逐事件等价。

关卡过程:加载冻结配置/资源→建立单位和波次→按tick生成敌人→移动/阻挡→自动选技能和目标→施法/投射物/效果→状态与死亡/漏怪→后续波次→胜负→调用battle.finish获取服务器奖励回执。暂停/倍速只影响客户端播放;readyAt时间门槛和幂等奖励继续由服务端控制。

手动施法暂不启用。加入战中输入后,服务端须接收并验证commandId、battleId、目标、申请tick/可接受窗口、冷却和资源,并记录确认输入;当前“开局预计算结果”协议需要改为命令驱动模拟或经验证的输入日志重算,不能直接相信客户端的施法、伤害或won字段。

实施顺序和验收

阶段 改动 完成标准
设计契约 当前 本设计、25技能/12表现样例、9音效需求、校验器 覆盖当前10伙伴/5敌人/20关,阶段/冷却/动画标记和资源状态检查通过
逻辑内核 AIServer增加独立combat模块、typed action、状态/投射物、事件轨迹;接battleRules=3 无Unity依赖的逻辑测试、确定顺序、去重/死亡/失效测试;结果与轨迹一致
客户端播放器 HotUpdate/Combat增加BattlePlaybackClock、事件读模型、动作/VFX/SFX适配器;PrototypeFlow接读模型 1×/2×、暂停恢复、低帧率、多投射物、重连/重播一致,奖励仍取服务端
火喵和岩团团样例 Pipeline制作挂点/特效/音源,按TangoFlux指南生成并后期/试听 火喵出手/到达/扣血对齐;岩团团近战/护盾完整;池与音效开关正确
全关迁移 补10伙伴表现、20关回归、配置哈希与协议滚动升级 旧在途战斗原结果、退款/放弃/finish幂等不变,新规则平衡及资源预算达标

逻辑测试至少覆盖:同tick技能与普攻竞争、零前摇、目标死亡/离场、施法者释放前后死亡、护盾吸收/刷新/耗尽、减速刷新/首领折扣、T2相邻路线治疗与多目标上限、重复命中去重、冷却边界、漏怪不计击杀、超时和最后一敌人的胜负顺序。

表现测试至少覆盖:动画丢标记仍只发一次、动画播放速度、粒子挂点与世界空间、音频延迟/静音/抢占、异步资源过期返回、对象池Trail清空、暂停恢复、后台恢复时跳过过期声音、BattleEnded彻底清理。最终在竖屏实际战斗相机与微信目标设备测资源加载、活跃音源、粒子透明叠加及帧耗;开发机截图通过不等同实机性能通过。

配置校验命令:python Tools/validate_skill_design.py。测试命令:python -m unittest discover -s Tools/tests -p test_skill_design.py。报告位于 Tools/output/skill_system_design/validation.json,该proposal报告的design_contract_validated仅表示历史设计契约通过;其中runtimeIntegrated/generatedAudio=false不代表正式运行时未接通。正式状态使用 Tools/validate_skill_runtime.py(runtimeIntegrated=true、generatedAudio=true、9/9声音)。

已落地的敌人表现批次(2026-10-21)

五类敌人的攻击与普通/硬甲/护盾受击表现已作为现有battleRules=1/2的适配层接入。实际完成8套特效、8条TangoFlux短音效、32实例/8音源播放池;真实PrototypeFlow隔离战斗及暂停/倍速/静音通过。完整制作记录见 EnemyCombatFx/Production.md。当批BattleSkills另有9条计划声音,不与本批8条敌人声音混算;后续已完成battleRules=3权威技能运行时、十伙伴表现和9/9技能声音,见本页末正式接入记录。

五敌人战场替换与技能闭环样例(2026-10-21)

火喵与岩团团已完成独立 C++ 事件轨迹→Unity 时钟/动作/5类特效/5条TangoFlux音效的可运行样例,详见 SkillSlice/Production.md。当批独立样例尚未接主线;后续已完成battle.begin/events协议、25技能全关迁移和9/9音效,下节记录正式状态。

权威技能协议与正式战场接入(2026-10-21)

本阶段已完成正式 skill_runtime_v1.json、25技能权威内核、分页协议和客户端正式战场接入;8080开发服已启用。原proposal仍为设计样例,正式验证器为 Tools/validate_skill_runtime.py。旧挑战不重算,新客户端使用快照rulesVersion3;新旧规则数值节奏不等价。详细边界、测试、部署和待补美术见 Runtime/Production.md。

正式伙伴技能表现补齐(2026-10-21)

十伙伴的专属效果与九条计划技能声音已接入正式权威战斗表现池,配置 Config/Combat/partner_presentations_v1.json,制作用 BuildPartnerSkillFx、BuildBattleSkills。每次cast同种声音去重、雷电连接源目标、治疗/减速按已结算事件播放;不改逻辑内核。见 PartnerSkills/Production.md 的资源、流程和验证回执。