mcpField
M
msw-combat-system

msw-combat-system

github.com/msw-git开发3810.0k 次安装3 周前更新
代码仓库

关于 msw-combat-system

msw-combat-system 汇总 MapleStory Worlds 常见 2D 战斗层的原生 API 流程,面向实现战斗系统的开发者。内容涵盖攻击判定、伤害、受击反应、战斗状态、事件、AI 与伤害显示,并链接到具体实现参考。

主要功能

  1. 01说明攻击形状与目标筛选
  2. 02整理伤害计算及命中事件接口
  3. 03涵盖击退、无敌帧与战斗反馈
  4. 04介绍状态、玩家生命值和复活流程
  5. 05梳理事件总线与原生 AI 能力

适用场景

  1. 01为近战或范围攻击配置命中判定
  2. 02实现怪物受击、击退与无敌帧
  3. 03构建带状态机或行为树的战斗 AI

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/msw-git/msw-ai-coding-plugins-official --skill msw-combat-system

安装后在 Claude Code 中输入/msw-combat-system确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
ai-bt.md
hp-gauge.md
projectile.md

详细介绍

msw-combat-system

完整的 MSW 战斗流程。仅涵盖通用 2D 战斗层中具有 MSW 原生 API 支持的项目,与游戏类型无关。不包括公式和理论。API 签名基于 Environment/NativeScripts/**/*.d.mlua。


0. 覆盖范围矩阵

#层原生支持需要自定义
1攻击判定AttackComponent + HitComponent(Box/Circle/Polygon)Capsule/Cone/Ray、穿透次数
2伤害模型CalcDamage/CalcCritical/GetCriticalDamageRate/GetDisplayHitCount 钩子 + HitEvent.Extra:any元素相性、复合公式
3受击反应各 Body 击退 API、基于 IsHitTarget 的无敌帧硬直等级、状态效果
4游戏手感全部 6 项均为原生支持(受击停顿、震动、缩放、闪光、VFX、SFX)—
5战斗状态StateComponent + DeadEvent/ReviveEvent、PlayerComponent HP/复活MP/耐力/怒气、仇恨
6事件总线HitEvent/AttackEvent/StateChangeEvent/PlayerActionEvent + 自定义 @Event击杀/格挡事件
7AIStateComponent(FSM)+ AIComponent(BT、4 个原生组合节点 + 自定义 extends CompositeNode)+ AIChaseComponent/AIWanderComponent、_UserService.UserEntities装饰器/记忆(黑板)、威胁表
+伤害外观3 个 DamageSkin* 组件 + DamageSkinService—
+受击特效HitEffectSpawnerComponent(自动)—
+化身动作AvatarStateAnimationComponent(State→MapleAvatarBodyActionState)—

0.5 参考资料 — 查阅指南

本 SKILL.md 仅介绍系统流程和原生 API 范围。实际模型 JSON、完整脚本代码和变化模式,请参阅以下 references/* 文件——直接阅读即可。

文件范围查阅时机
../msw-general/references/monster.md怪物 .model 组件组装 + ActionSheet + AI 选择 + 规范 Pattern A 脚本(Soldier 风格)+ HP/重生 + 生成 + 验证构建可战斗的怪物时
references/hp-gauge.md基于 PixelRendererComponent 的头顶 HP 条完整实现添加头顶 HP 条时
references/projectile.md投射物(无 Body 实体 + OnUpdate Translate)+ 追踪/穿透/溅射变体构建箭矢、子弹、魔法弹等远程攻击时
references/ai-bt.md行为树——AIComponent + 组合节点(4 个原生 + 自定义)+ @BTNode + 自定义装饰器/记忆/威胁需要基于 BT 的怪物/Boss AI 和多层决策时

优先顺序:本 SKILL.md(概念 + API 表格)→ 相关的 references/*(完整实现)。


1. 攻击判定

1-1. 形状与攻击触发

HitComponent.ColliderType 仅支持 Box / Circle / Polygon。其他形状必须通过组合近似实现。

AttackComponent:
  Attack(Vector2 size, Vector2 offset, string attackInfo, CollisionGroup? cg)    → table<Component>
  Attack(Shape shape, string attackInfo, CollisionGroup? cg)                     → table<Component>
  AttackFast(Shape shape, string attackInfo, CollisionGroup? cg)                 → void   (for mass resolution, bullet hell)
  AttackFrom(Vector2 size, Vector2 position, string attackInfo, CollisionGroup? cg) → table<Component>
  emitter EmitAttackEvent(AttackEvent)
  • 形状:CircleShape(position, radius) / BoxShape(position, size, angle) / PolygonShape(position, points, angle)。对于轴对齐矩形,使用 BoxShape(center, size, 0)——没有 RectangleShape 类型。无需旋转时,向 BoxShape / PolygonShape 传入 angle = 0。
  • 多边形受击区域:HitComponent.PolygonPoints: SyncList<Vector2>
  • AttackFast 不会构建受击表 → 子弹地狱 / 大规模判定时性能更好
1-2. 目标筛选
一侧重写方法用途
攻击者AttackComponent:IsAttackTarget(defender, attackInfo) → boolean阵营 / 距离 / 状态
防御者HitComponent:IsHitTarget(attackInfo) → boolean无敌 / 免疫

任一方法返回 false → 排除该次命中。父类调用方式为 __base:IsAttackTarget(...)(mlua 专用)。

⚠ 重写时不要添加 @ExecSpace — 父类中的 IsAttackTarget 和 IsHitTarget 均未指定 ExecSpace(=All)。在子类中添加 @ExecSpace("ServerOnly") 等注解,会在运行时触发 LEA-3014 SignatureMismatch。即使没有注解,调用路径也会经过服务端命中流程,因此实际执行发生在服务端。详情:msw-scripting/SKILL.md §9 "Method override"。

  • Attack*(..., cg) 中的 cg 参数是对防御者 HitComponent.CollisionGroup 的精确匹配筛选条件(默认为 CollisionGroups.HitBox)。它不会查询碰撞矩阵,攻击者自身的组也无关紧要。nil(省略)= 每个 HitComponent 都是候选对象。若传入的组有效,但与防御者实际所属组不同,则会命中 0 个目标且不报错(静默失败);若引用的组不存在于当前世界的碰撞组集合中,则其行为等同于 nil(所有组)。传入组(例如 CollisionGroups.Monster)时,先将目标怪物的 HitComponent.CollisionGroup 设为同一组 — 示例工作区中的怪物会将其重写为 Monster,因此示例攻击脚本可以正常工作。从其他世界导入的模型若其组 ID 不在当前世界的组集合中,则会静默注册到 Default 组 — 使用 nil 或 CollisionGroups.Default 进行检测。
  • 重复命中防止 / 穿透 / 最大命中数:原生不支持。通过 Attack 返回的表格和 table<Entity, boolean> 缓存,在脚本中自行管理。
1-3. attackInfo 标记

用于扩展的字符串,会传递到 CalcDamage/IsHitTarget/GetDisplayHitCount。取值约定由项目自行决定。建议使用 "melee.light"、"dot.poison" 这类命名空间格式。

1-4. ⚠️ IsLegacy

只有当 HitComponent.IsLegacy = false 时,ColliderType/ColliderOffset/PolygonPoints 才有效。BoxOffset/ColliderName 已弃用。

1-5. 各攻击形式的形状映射
形式形状构造
正面近战 BoxBoxShape(pos + LookDirectionX*offset, size, 0) — 参见 DefaultPlayer PlayerAttack
圆形 AoECircleShape(self.WorldPos, radius)
投射物生成一个无 Body 的模型(仅 Sprite+Transform),并在 OnUpdate(delta) 中调用 TransformComponent:Translate(speed*delta, 0) + 基于距离的命中检测 + _EntityService:Destroy。移动规则见 §1-6,完整实现 → references/projectile.md

MSW 没有专用的投射物系统 — 通过实体 + AttackComponent 组合实现。

1-6. 连续移动 — 投射物、怪物和 AI 的通用规则

连续移动(追逐、飞行、自动移动)基于每帧调用 OnUpdate(delta)。通过定时器(SetTimerRepeat(0.1~0.15s))移动会产生 6~10 Hz 的瞬移效果,看起来很卡顿。

每种目标建议使用的 API
目标是否需要 Body?移动 API原因
怪物 / NPC / AI需要(地图类型对应的 Body + MovementComponent)MovementComponent:MoveToDirection(dir, 0) + MovementComponent.InputSpeedMovementComponent.d.mlua:1——可控制 Rigid/Kinematic/Sideview 这三种类型。InputSpeed 属于 MovementComponent(.d.mlua:7),因此并非仅限 Player。第二个参数 0——deltaTime 仅在梯子上生效(.d.mlua:32)。官方 BT 示例 ActionFollow/ActionMoveRandom 也使用 0。
投射物 / 宝石 / 掉落物品 / 特效不需要(Sprite+Transform+Trigger)每帧执行 self.Entity.TransformComponent:Translate(speed*delta, 0)没有 Body 时,直接操作 Transform 是安全的。这种模式源自官方教程“Create a Long-Range Projectile”。
直接控制 Rigidbody(进阶)需要body:AddForce(...)——持续加速 / 冲量RigidbodyComponent.d.mlua:71——MoveVelocity“主要由 MovementComponent 控制”,因此应优先通过 MovementComponent 控制,而不是直接写入。

关于 MovementComponent.InputSpeed 在不同地图类型下的实际速度换算,请参阅 msw-general/references/platform.md §10(MapleTile=×1,RectTile=÷1.2,SideView=×1.5)。

禁止使用的模式
❌原因
使用 _TimerService:SetTimerRepeat(move, 0.1~0.15) 移动以 6~10Hz 传送,没有逐帧插值 → 移动生硬
在 OnUpdate 中调用 body:SetPosition(...) / MovementComponent:SetPosition(...)两者都是传送方法(MovementComponent.d.mlua:37,以及各 Body 的 .d.mlua 中的 SetPosition)。用它们连续移动会很卡顿。仅用于一次性生成 / 重生 / 瞬移。
对有活动 Body 的实体使用 self.Entity.TransformComponent.Position = newPos物理引擎会在下一帧覆盖该值,并阻止网络同步。
不带 delta 的固定步长移动,例如 Translate(0.009, 0)受帧率影响。60FPS 和 30FPS 下的速度不同。
⚠ 谨慎对待官方 msw-search 中的反模式

mlua_Document_Retriever 会返回一篇得分很高的“使用 MovementComponent 控制实体移动”文档,但其正文展示的是一种反模式:在 OnUpdate 中每帧调用 MovementComponent:SetPosition(...) 移动——请忽略该文档,改用上表中的 MoveToDirection / Translate。FlappyFish Remake、Stopping the Taxi 和 Making a Moving Foothold 也展示了缺少 delta 或直接赋值 Position 的做法,引用时请谨慎。

MovementComponent 附加方式——怪物 / NPC

怪物模型默认不包含此组件。.model 必须包含以下所有组件。

组件说明
MOD.Core.TransformComponent默认
MOD.Core.SpriteRendererComponent渲染器
Body(地图类型对应)RigidbodyComponent(MapleTile)/ KinematicbodyComponent(RectTile)/ SideviewbodyComponent(SideViewRectTile)
MOD.Core.MovementComponent例如 InputSpeed = 2.0——移动 API 必需
交叉引用

1-7. 基于碰撞的命中——选择检测方式(不要手动计算距离)

当需求是“基于碰撞/接触的命中”(玩家↔怪物接触伤害、危险区域、陷阱、重叠)时,应根据意图选择。逐帧计算距离并非默认方式——设计要求基于碰撞体接触时却使用距离计算,是需求方与实现方最常见的不匹配。

意图检测方式原因
主动攻击挥击/命中框/弹幕AttackComponent:Attack/AttackFast/AttackFrom → 对防御方的 HitComponent 进行判定,并触发 HitEvent完整流程:伤害皮肤、命中效果、IsHitTarget 无敌帧、OnHit
身体接触/区域/陷阱重叠,目标任意或多个TriggerComponent + OnEnterTriggerBody/OnStayTriggerBody/OnLeaveTriggerBody(或 TriggerEnter/Stay/LeaveEvent)+ CollisionGroup 筛选,然后通过 HitEvent 流程处理伤害引擎端重叠检测:遵循实际碰撞体形状(Box/Circle/Polygon)、ColliderOffset 和分组筛选
追踪/单个已知目标逐帧执行 OnUpdate 距离检测(references/projectile.md)仅当事先确切知道只有一个目标实体时才合理
  • ❌ 对玩家↔怪物或多目标碰撞逐帧执行 math.sqrt(dx*dx+dy*dy) < r:这种方式会悄悄地近似为一个以变换为中心的圆(忽略每个碰撞体的形状/大小/ColliderOffset),跳过 CollisionGroup 筛选,并且每帧的开销为 O(攻击者 × 目标)。所谓“基于碰撞的命中”指的是原生碰撞体,而不是轮询距离。
  • CollisionGroup 默认值为 CollisionGroups.TriggerBox。重叠期间,OnStayTriggerBody 会每帧触发——请通过冷却时间限制伤害,不要每帧都造成伤害。
  • 检测 ≠ 伤害应用:原生重叠事件触发后,仍须通过 HitEvent / AttackComponent 造成伤害——绝不可直接扣除 HP。

2. 伤害模型

AttackComponent:
  method integer CalcDamage(attacker, defender, attackInfo)        -- default 1     (ExecSpace=All)
  method boolean CalcCritical(attacker, defender, attackInfo)      -- default false (ExecSpace=All)
  method float   GetCriticalDamageRate()                           -- default 2.0   (ExecSpace=All)
  method int32   GetDisplayHitCount(attackInfo)                    -- default 1     (ExecSpace=All)
  method void    OnAttack(defender)                                                 -- (ExecSpace=All)

HitComponent:
  method void OnHit(Entity attacker, integer damage, boolean isCritical, string attackInfo, int32 hitCount)
  emitter EmitHitEvent(HitEvent)

⚠ 上述所有钩子在父类中都具有未指定的 ExecSpace(=All)。重写时添加 @ExecSpace("ServerOnly") 等标注会触发 LEA-3014 SignatureMismatch。删除标注,仅声明 method ...。详情:msw-scripting/SKILL.md §9 "Method override"。

2-1. HitEvent 负载
AttackCenter:   Vector2
AttackerEntity: Entity (nilable)
Damages:        List<integer>    -- multi-hit split
Extra:          any              -- ★ extension slot (knockback/stun/element/tags)
IsCritical:     boolean
TotalDamage:    integer
FeedbackAction: HitFeedbackAction  -- ⚠ entire enum deprecated

将辅助信息(击退向量、硬直时间、元素)放在 Extra 表中。

2-2. AttackEvent 负载

仅有一个字段:DefenderEntity: Entity。攻击者是处理器的 self。

2-3. ⚠️ 反模式:直接扣除 HP——不要绕过 HitEvent

直接扣除防御方的 HP,例如 monster.Hp -= damage / target.MonsterAI.HP -= damage,不会触发 HitEvent——伤害皮肤、命中效果、IsHitTarget 免疫和 OnHit 重写都会被悄悄跳过。

对于玩家侧自定义伤害(持续/光环/持续伤害),绕过流程还会导致角色动画异常:AvatarStateAnimationComponent 只响应 StateChangeEvent,因此即使 HP 条下降,玩家角色仍会保持待机状态。如果必须在不触发 HitEvent 的情况下造成伤害,还要手动调用 StateComponent:ChangeState("HIT")(大写键——参见 §10),并在死亡/复活时调用 PlayerComponent:ProcessDead() / ProcessRevive()。否则,受击/死亡动作会悄悄失效,且不会报错。


3. 受击反应

3-1. 击退 — 各 Body 的 API
Body(地图类型)实现方式
Rigidbody(MapleTile)body:AddForce(Vector2(dir*5, 3)) ★推荐 · SetForce · JustJump(Vector2(0, 4))(垂直方向)
Kinematicbody(RectTile / 俯视视角)body.MoveVelocity = Vector2(dir*5, 0) — 不要使用 AddForce
Sideviewbody(SideViewRectTile)body.MoveVelocity + body.JumpSpeed
  • Rigidbody 会由引擎自动阻尼。Kinematic/Sideview 必须在 OnUpdate 中手动阻尼(MoveVelocity *= 0.9)。
  • 墙壁反弹:订阅 FootholdCollisionEvent 并反转速度。
  • 击退是一次性冲量,不要将其与持续移动(追逐、飞行)混淆。持续移动请参见 §1-6。
  • ⚠ 禁止:在具有活动 Body 的实体上直接设置 TransformComponent.Position → 网络同步会被阻止。body:SetPosition(...) 是传送方法,因此不要在 OnUpdate 循环中调用(§1-6)。
3-2. 无敌帧

标准模式:基于 _UtilLogic.ElapsedSeconds 检查截止时间 + 从 HitComponent:IsHitTarget 返回 false。DefaultPlayer 默认的 PlayerHit.mlua 已直接提供此模式(§9-4)。

另一种方式:无敌期间,将 HitComponent.CollisionGroup 切换到单独的组 → 直接排除碰撞判定。此方式更适合需要精确到帧的场景。

3-3. 状态效果(Buff/Debuff)

不原生支持。 直接实现 @Component BuffComponent + 使用 _TimerService:SetTimerRepeat 进行计时 + 广播自定义的 StatusAppliedEvent/StatusExpiredEvent。

对于单次简单的眩晕,使用 StateComponent:ChangeState("STUN") + 输入/AI 阻止标志即可。


4. 游戏手感 — 全部原生支持

元素APIExecSpace
命中停顿(全局)_UtilLogic:SetClientTimeScale(float) — 0~100ClientOnly
命中停顿(单个对象)renderer.PlayRate = 0(Sprite/Skeleton/Avatar)@Sync
慢动作_UtilLogic:SetClientTimeScale(0.3) + 使用计时器恢复ClientOnly
镜头震动cameraComp:ShakeCamera(intensity, duration, targetUserId?)Client
镜头缩放cameraComp:SetZoomTo(percent, duration, targetUserId?) · 需先设置 IsAllowZoomInOut=trueClient
命中闪白spriteRenderer.Color = Color(r,g,b,a) → 使用计时器恢复@Sync
HDR 颜色过亮Color.HSVToRGB(h, s, v, hdr=true) — 允许使用大于 1.0 的值—
固定位置 VFX_EffectService:PlayEffect(clipRUID, instigator, pos, zRot, scale, isLoop?, options?) → 返回 serial—
附着式 VFX_EffectService:PlayEffectAttached(clipRUID, parent, localPos, localZRot, localScale, isLoop?, options?)—
移除 VFX_EffectService:RemoveEffect(serial)—
2D SFX_SoundService:PlaySound(id, volume, targetUserId?)Client
3D SFX_SoundService:PlaySoundAtPos(id, pos, listener, volume)Client
循环 SFXPlayLoopSound / PlayLoopSoundAtPosClient
附着式 SFXSoundComponent:Play() · 通过 Pitch 0~3 实现音高随机化Client
BGM_SoundService:PlayBGM(id, volume) / StopBGM(immediately)Client
预加载_SoundService:LoadSound(id)ClientOnly

PlayEffect 的选项键:FlipX, FlipY, SortingLayer, OrderInLayer, Alpha, StartFrameIndex, EndFrameIndex, PlayRate, SyncFlip, Color, MaterialID, IgnoreMapLayerCheck, LitMode

获取当前镜头:_CameraService:GetCurrentCameraComponent()。

ParticleService — 内置粒子

仅由枚举值驱动的通用粒子效果,无需 RUID。分为 3 类:

-- BasicParticle: general-purpose presets (no RUID needed)
integer _ParticleService:PlayBasicParticle(BasicParticleType, Entity instigator, Vector3 pos, number zRot, Vector3 scale, boolean isLoop, Dictionary options)
integer _ParticleService:PlayBasicParticleAttached(BasicParticleType, Entity parent, Vector3 localPos, number localZRot, Vector3 localScale, boolean isLoop, Dictionary options)

-- SpriteParticle: custom sprite as a particle (spriteRUID required)
integer _ParticleService:PlaySpriteParticle(SpriteParticleType, string spriteRUID, Entity instigator, Vector3 pos, number zRot, Vector3 scale, boolean isLoop, Dictionary options)
integer _ParticleService:PlaySpriteParticleAttached(SpriteParticleType, string spriteRUID, Entity parent, Vector3 localPos, number localZRot, Vector3 localScale, boolean isLoop, Dictionary options)

-- AreaParticle: environmental particles over a wide area (areaSize added)
integer _ParticleService:PlayAreaParticle(AreaParticleType, Vector2 areaSize, Entity instigator, Vector3 pos, number zRot, Vector3 scale, boolean isLoop, Dictionary options)

void _ParticleService:RemoveParticle(integer serial)

选项键:Color, SortingLayer, OrderInLayer, ParticleSize, ParticleCount

循环粒子(isLoop=true)必须通过 RemoveParticle(serial) 清理。将 serial 存储在 self._T 中,以便之后移除。

完整的 BasicParticleType 列表
系列名称描述
爆炸/冲击SparkExplosion火花(一次性)——通用撞击效果
SparkLoop连续火花
SparkRadialExplosion向四周散射的火花
SmallExplosion小型爆炸 + 烟雾
BigExplosion大型爆炸 + 烟雾
TinyExplosion极小爆炸(忽略 Color 选项)
DustExplosion环形冲击波 + 烟雾(忽略 Color 选项)
EnergyExplosion环形冲击波,随后向中心汇聚
CircleBurst环形光爆
PillarBurst环形光爆 + 定向光
火焰/烈焰FireField卡通火焰
FireFieldIntense强烈的卡通火焰
FireBall单点火焰
FlameThrower喷火流
LargeFlames从地面升起的大型火焰
MediumFlames从地面升起的中型火焰
TinyFlames从地面升起的微小火焰
WildFire巨大的火焰柱(忽略 Color 选项)
闪电/电流LightningOrbSharp球形电粒子
LightningStrikeSharp闪电
LightningStrikeSharpTall高耸的闪电
LightningOrbSoft电波发射
LightningBlast周期性电波
LightningStrike周期性闪电
LightningStrikeTall周期性高耸闪电
增益/魔法Aura从地面升起的极光
Buff从地面升起的强光
Charge大型粒子向一点汇聚
ChargeOrb粒子向一点汇聚
Enchant强光,周围伴有微光/粒子
SpinField环绕旋转圆圈的粒子
StarVortex星光向中心汇聚
Nova宽阔的环形波
UpperCylinder从地面升起的光柱
其他Firework烟花
FireworkCluster多个烟花同时绽放
FireFlies萤火虫
GoopSpray向侧面喷射液体
GoopSprayEffect向下喷射液体
DustStorm范围宽广的沙尘暴
RisingSteam从地面升起的白色雾气
BigSplash大型水花
Shower向一处浇水
完整的 SpriteParticleType 列表(8 种)
名称描述
BurstBig精灵以放射状出现
SpawnField粒子 + 精灵在圆形区域内出现
BurstNova粒子 + 精灵以环形爆发
SimpleSpawn简单的粒子 + 精灵显现效果
Burst粒子 + 精灵四散
Stream移动时沿特定方向生成
StreamSharp沿特定方向移动的细线
AdditiveColor应用于精灵的颜色效果
完整的 AreaParticleType 列表(12 种)
名称描述
Rain雨
Snow雪
FogCalm雾
FogHeavy向下沉降的浓雾
FogLively上升的雾
CalmStarField升起的星群
StarFieldSimple闪烁的星群
StarFog星星 + 星云粒子(静止)
StarFogFlow星星 + 星云粒子(上升)
Windlines细线
WindlinesBig细线 + 粗线
WindlinesSpeedy快速直线
选择 EffectService 还是 ParticleService
情况推荐方案
MapleStory 技能 / 命中动画(特定画面)EffectService(指定 RUID)
常规命中 / 爆炸(快速实现)ParticleService.BasicParticle
将自定义图像散布为粒子ParticleService.SpriteParticle
雨 / 雪 / 雾等环境氛围ParticleService.AreaParticle
增益光环等持续效果两者均可,设置 isLoop=true
丰富的分层效果组合使用 EffectService 和 ParticleService

服务端事件 → 客户端效果的标准模式:更改 @Sync 属性 → 在 OnSyncProperty(ClientOnly) 中检测 → 调用 EffectService/ParticleService。


5. 死亡 / 复活

事件触发条件载荷
DeadEventStateComponent:ChangeState("DEAD") 时自动触发无
ReviveEventPlayerComponent:Respawn() 时自动触发(仅玩家)无
StateChangeEvent每次状态转换时自动触发CurrentStateName、PrevStateName

追踪击杀者:DeadEvent 不含载荷 → 在 HandleHitEvent 中缓存 self.LastAttacker = event.AttackerEntity,并在 HandleDeadEvent 中使用。

对于玩家专属的死亡 / 复活,优先使用 §9-1 PlayerComponent.Respawn/ProcessDead/ProcessRevive。


6. 事件总线

逻辑事件MSW 实现
OnAttackStartOnAttack 钩子或自定义 AttackStartEvent
OnAttackHit / OnDamageTaken原生 HitEvent
OnAttackMiss自定义——当 IsAttackTarget 返回 false 时调用 SendEvent
OnCriticalHit由 HitEvent.IsCritical 标志覆盖
OnDeath / OnRevive原生 DeadEvent/ReviveEvent
OnStateChange原生 StateChangeEvent
OnKill / OnBlocked / OnParry / OnStatusApplied自定义 @Event
6-1. 自定义事件规则
  • 定义:@Event script XxxEvent extends EventType + property 声明
  • 接收:handler 关键字(非方法)、@EventSender("Self" | "Service","XxxService" | "Logic","XxxLogic")
  • 连接 / 断开连接:entity:ConnectEvent(XxxEvent, self.Handler) / 在 OnEndPlay 中调用 DisconnectEvent(引擎不会自动断开连接)
  • 全局:@Logic CombatEventBusLogic 单例 + @EventSender("Logic","CombatEventBusLogic")

7. AI — FSM(StateComponent)+ BT(AIComponent)+ 自定义脚本(模式 A),均兼容原生功能

模式适用场景参考
FSM(StateComponent + @State)简单敌人(3~5 个状态)、玩家 IDLE/HIT/DEAD、Boss 阶段、动画同步(AvatarStateAnimationComponent 自动映射 §10)。如果需要让 StateAnimationComponent 自动切换动画片段,必须设置 StateComponent.IsLegacy=false。../msw-general/references/animation-state.md(统一状态机与动画管线)
BT(AIComponent + 组合节点(4 个原生 + 自定义)+ @BTNode)巡逻 + 追击 + 攻击连招、多样化的 Boss 模式、组合节点 / 装饰器复用、按概率加权的动作。必须设置 StateComponent.IsLegacy=false。references/ai-bt.md
使用自身状态的自定义脚本(持有 CurrentAIState 的 @Component,并直接赋值 SpriteRUID——Soldier 风格模式)不适用于 AIChase/AIWander 的行为(游荡 ↔ 站立 ↔ 说话 ↔ 攻击、受距离限制的攻击、对话待机)。无需 AIChaseComponent/AIWanderComponent,也无需设置 IsLegacy=false——脚本绕过 ActionSheet 管线。StateComponent 仅用于 IDLE ↔ DEAD。../msw-general/references/monster.md §7 "Canonical Pattern A Scripts (Soldier)"
7-1. FSM — StateComponent(摘要)

StateComponent + @State script XxxStateType extends StateType(生命周期:OnEnter/OnUpdate/OnExit/OnConditionCheck)。仅以下状态会自动注册:IDLE/DEAD(如果存在 HitComponent,还会注册 HIT;如果存在 AIChase/AIWander,还会注册 MOVE)——ATTACK/PATROL/STUN/PHASE2 等状态都必须在 OnBeginPlay 中通过 AddState("name", XxxStateType) 预先注册。自动转换使用 AddCondition(from, to) + 每帧调用的 OnConditionCheck()。

⚠ 状态名称必须使用大写;未注册的名称会立即产生 [LEA-3005] InvalidArgument : 'stateName'。在 AvatarStateAnimationComponent.StateToAvatarBodyActionSheet 中注册键名不会自动在 StateComponent 中注册该状态——两者相互独立。

完整实现 → ../msw-general/references/animation-state.md(FSM 编写、ChangeState 失败情况对照表、标准的 PATROL/CHASE/ATTACK/HIT/DEAD 怪物模式,以及状态→动画管线都在此处一并介绍——这是同一底层系统的两个视角)

7-2. BT — AIComponent(摘要)

AIComponent + SequenceNode/SelectorNode/RandomSelectorNode/ParallelNode + @BTNode Action Nodes + 原生 AIChaseComponent/AIWanderComponent。全部 4 种 Composite 类型均为原生支持,也可通过 @BTNode ... extends CompositeNode 编写自定义子节点流转策略(ChildCount/ChildBehave);Decorator/Memory(Blackboard)/Threat Table 必须手动实现。

⚠ 使用自定义 BT 时,请从 .model 中移除 AIChaseComponent/AIWanderComponent。

完整实现 → references/ai-bt.md

完整的怪物实体构成 → ../msw-general/references/monster.md

此 SKILL.md 仅介绍战斗相关内容(ATTACK/HIT/DEAD + DeadEvent/ReviveEvent + BT 入口)。有关通用 mlua 状态机/脚本编写模式,请参阅 msw-scripting。


8. UI 原生支持

UIAPI
HP 条(固定在屏幕上)使用 HP 量表切片精灵图(image_ruid = "f0911af597259044aa624a11332c0595")通过 SpriteGUIRendererComponent 填充,并通过左侧为轴心的 UITransformComponent 调整宽度。用户可拖动的控件使用 SliderComponent,只读 HP 量表则不适用。⚠ 仅限 UI 实体
伤害数字3 个 DamageSkin* 组件 + DamageSkinService — §11
准星.ui 中的 SpriteGUIRendererComponent
连击计数器 / 增益图标TextGUIRendererComponent + SpriteGUIRendererComponent

世界空间 HP 条(头顶显示):原生不支持。有两种实现方案:

方案方法适用场景
轻量级在子实体的 SpriteRendererComponent 上调整 LocalScale.x = hp/maxHp,或使用 TiledSize.x(搭配 SpriteDrawMode.Tiled)快速原型、简单量表
完整方案基于 PixelRendererComponent — 完整实现 references/hp-gauge.md生产级,可同时显示大量怪物

9. DefaultPlayer 战斗原生功能

玩家实体原生具备 HP、复活和输入功能。不要创建自定义的 Hp/MaxHp 属性——请使用 PlayerComponent。

PlayerComponent / PlayerControllerComponent 的完整属性/方法表请参阅 msw-defaultplayer/SKILL.md。此处仅介绍战斗必需内容。

9-1. 核心战斗 API
项目用法
HP 扣减self.Entity.PlayerComponent.Hp -= event.TotalDamage
死亡检查PlayerComponent:IsDead()
复活PlayerComponent:Respawn() — RespawnPosition → SpawnLocation → map entry point。自动发出 DeadEvent/ReviveEvent
仅客户端死亡处理@ExecSpace("Client") ProcessDead(targetUserId?) / ProcessRevive(targetUserId?)
方向检查 ★PlayerControllerComponent.LookDirectionX(+1 表示向右,-1 表示向左)。不要使用 TransformComponent.Scale.x
动作钩子覆写ActionAttack / ActionJump / ActionInteraction(key, isKeyDown) 等
接收动作事件EmitPlayerActionEvent(PlayerActionEvent) → §9-3

⚠ 自动攻击 / AI 触发的攻击必须先设置朝向。 LookDirectionX 是可写的 @Sync 属性 — 赋值 1/-1 可使玩家头像朝向相应方向。默认输入流程只会根据移动输入更新该属性,因此在没有移动时触发的攻击(自动战斗循环、AI 计时、技能按钮)会沿用上一次的朝向(默认向左)— 头像会朝错方向,基于 LookDirectionX 的攻击框(§1-5)也会计算到错误的一侧。触发此类攻击前,应将朝向设为目标所在方向:controller.LookDirectionX = target.TransformComponent.WorldPosition.x >= self.Entity.TransformComponent.WorldPosition.x and 1 or -1。只赋值符号(±1),不要使用原始位置差值 — 攻击偏移会乘以 LookDirectionX,因此非单位值会缩放 / 错位命中框。

9-3. PlayerActionEvent
property string ActionName       -- "Attack" / "Jump" / "Crouch" / ...
property Entity PlayerEntity

默认模式是使用 PlayerAttack extends AttackComponent,接收 @EventSender("Self") handler HandlePlayerActionEvent(...),并根据 event.ActionName == "Attack" 进行分支处理。

9-4. 默认模板(RootDesk/MyDesk/)

可直接复制粘贴,无需修改。按需覆写:

文件作用要点
PlayerAttack.mlua正面盒状攻击使用 LookDirectionX 确定方向,AttackFast + CollisionGroups.Monster,CalcDamage=50,30% 暴击
PlayerHit.mlua无敌帧ImmuneCooldown 属性,_UtilLogic.ElapsedSeconds 截止时间,覆写 IsHitTarget
Monster.mlua怪物 HP自定义 @Sync Hp(不使用 PlayerComponent),HandleHitEvent → Dead/Respawn
MonsterAttack.mlua基于精灵图尺寸的近战攻击在 IsAttackTarget 中使用 isvalid(defender.PlayerComponent) + __base:IsAttackTarget(...) 父类调用
9-5. 时间参考

推荐使用 _UtilLogic.ElapsedSeconds(世界时钟,暂停 / 恢复时保持一致)。不要使用 os.clock()。

9-6. 标准 CollisionGroups
常量用途
CollisionGroups.Player怪物 → 玩家攻击
CollisionGroups.Monster玩家 → 怪物攻击
CollisionGroups.HitBoxHitComponent.CollisionGroup 的默认值

10. 头像动作 — AvatarStateAnimationComponent

自动将 StateComponent 状态转换关联到头像动画。

@Sync property SyncDictionary<string, AvatarBodyActionElement> StateToAvatarBodyActionSheet  -- IsLegacy=false
@Sync property SyncDictionary<string, string>                  ActionSheet                    -- IsLegacy=true (deprecated)

method void   SetActionSheet(string key, string animationClipRuid)
method void   RemoveActionSheet(string key)
method string StateStringToAnimationKey(string stateName)
emitter EmitBodyActionStateChangeEvent(BodyActionStateChangeEvent)
  • ChangeState("HIT") → 自动播放映射的 MapleAvatarBodyActionState.Hit
  • 与战斗相关的状态值:Attack=3、Hit=14、Dead=10、Alert=4、Heal=13
  • 固定为 IsLegacy=false;仅使用 StateToAvatarBodyActionSheet

完整的头像组件说明(AvatarRendererComponent 等)请参阅 msw-defaultplayer。本节仅介绍战斗动作映射。


11. 伤害皮肤(数字显示)

默认 RUID
用途RUID用于
命中3271c3e79bf04ecba9a107d55495970d攻击者 DamageSkinSettingComponent.DamageSkinId 的默认值
受到命中02c22d93421b4038b3c413b3e40b57ec防守方显示 — 手动调用 _DamageSkinService:Play
治疗d58b67cf0f3a4eaf9fe1ad87c0ffac8a治疗 / 药水 — 手动调用 _DamageSkinService:Play
11-1. 自动模式(基于组件)

在 Attack/AttackFast 上,如果存在以下全部 3 个组件,就会自动显示伤害数字:

一侧组件作用
攻击方DamageSkinSettingComponent要显示的皮肤/样式
防御方DamageSkinSpawnerComponent显示位置偏移
防御方DamageSkinComponent伤害数字主体(显示在实体上方)

将这 3 个组件都添加到 .model 中,无需编写任何脚本代码即可显示伤害数字。

DamageSkinSettingComponent(攻击方)
属性类型默认值描述
DamageSkinIdDataRef命中 RUID(见上表)伤害数字皮肤 RUID
DamageSkinScaleVector2(1, 1)数字大小
Alphafloat1不透明度
PlayRatefloat1播放速度
DelayPerAttackfloat0.05多段命中之间的间隔(秒)
TweenTypeDamageSkinTweenTypeDefault动画样式
LitModeLitModeDefault光照影响

DamageSkinTweenType:Default(弹出)/Volcano(扇形散开)/Blade(重叠)/各类 *Mini(75% 缩放)

DamageSkinSpawnerComponent(防御方)
属性类型默认值
DamageSkinOffsetVector2(0,0)
11-2. 手动模式 — DamageSkinService

自动模式未覆盖的情况(治疗、Miss/Guard、非标准伤害来源)可直接调用 _DamageSkinService。

_DamageSkinService:Play(targetEntity, skinRuid, delay, damages:List<int>, tweenType, isCritical, offset, scale, playRate, alpha, litMode)
_DamageSkinService:PlayTextDamage(targetEntity, skinRuid, textType, tweenType)
_DamageSkinService:PreloadAsync(skinRuid, callback(success))    -- ClientOnly

DamageSkinTextType:Miss/Guard/Resist/Shot/Counter

⚠ _DamageSkinService:Play 位于 Client 空间——要从服务器逻辑(扣除 HP 等)调用它,请将调用封装在 @ExecSpace("Client") 方法中,或修改 @Sync 属性并从 OnSyncProperty 触发调用。

⚠ Play() 有 6 个必需参数。只传入 5 个可选参数中的部分参数会触发 LEA-3005 InvalidArgument。

11-3. 用法示例
(a) 强调暴击 — 自动模式 + 动态缩放

当 IsCritical=true 时,自动模式会自动显示红色字体。要进一步强调,可暂时增大攻击方的缩放比例:

-- ⚠ AttackComponent hooks (CalcDamage/CalcCritical/GetCriticalDamageRate/GetDisplayHitCount/
--   IsAttackTarget/IsHitTarget/OnAttack) have an unspecified ExecSpace (=All) on the parent.
--   Adding @ExecSpace when overriding triggers LEA-3014 SignatureMismatch.
--   Details: msw-scripting/SKILL.md §9 "Method override → LEA-3014"
method integer CalcDamage(Entity attacker, Entity defender, string attackInfo)
    return 100
end

method boolean CalcCritical(Entity attacker, Entity defender, string attackInfo)
    return math.random() < 0.3
end

method float GetCriticalDamageRate()
    return 2.5     -- 100 → 250
end

还可以通过设置 DamageSkinSettingComponent.TweenType = Volcano(扇形散开)或 Blade(重叠)来突出显示暴击。

(b) 治疗/恢复 — 手动调用
local HEAL_RUID = "d58b67cf0f3a4eaf9fe1ad87c0ffac8a"

@ExecSpace("Client")
method void ShowHeal(Entity target, integer amount)
    _DamageSkinService:Play(
        target, HEAL_RUID, 0,
        { amount },                            -- damages
        DamageSkinTweenType.Default,
        false,                                 -- isCritical
        Vector2(0, 0.5),                       -- offset (above head)
        Vector2(1, 1), 1.0, 1.0, LitMode.Default
    )
end
(c) Miss/Guard/Resist 文本
local HIT_RUID = "02c22d93421b4038b3c413b3e40b57ec"

@ExecSpace("Client")
method void ShowMiss(Entity target)
    _DamageSkinService:PlayTextDamage(
        target, HIT_RUID, DamageSkinTextType.Miss, DamageSkinTweenType.Default
    )
end

当 AttackComponent:IsAttackTarget 返回 false 时调用此方法 →“未命中动画 + 伤害 0”。

(d) 多段命中 — 单次调用拆分为 N 个数字

如果将 List 作为 _DamageSkinService:Play 的 damages 参数传入,数字会按 DelayPerAttack(攻击方组件的值)指定的间隔依次显示:

_DamageSkinService:Play(target, ATTACK_RUID, 0, { 12, 8, 14, 11, 9 },
    DamageSkinTweenType.Default, false, Vector2(0,0), Vector2(1,1), 1, 1, LitMode.Default)

自动模式对 HitEvent.Damages(List)的处理方式相同——重写 GetDisplayHitCount(attackInfo) 以控制拆分数量。

(e) 预加载 — 避免首次显示卡顿

首次使用某个皮肤 RUID 时,纹理加载可能会有延迟。请在进入地图时预加载:

@ExecSpace("ClientOnly")
method void OnBeginPlay()
    _DamageSkinService:PreloadAsync("3271c3e79bf04ecba9a107d55495970d", function(ok) end)
    _DamageSkinService:PreloadAsync("02c22d93421b4038b3c413b3e40b57ec", function(ok) end)
    _DamageSkinService:PreloadAsync("d58b67cf0f3a4eaf9fe1ad87c0ffac8a", function(ok) end)
end
(f) TweenType 的使用场景
TweenType推荐场景
Default普通命中
Volcano暴击/范围命中(向上散开)
Blade连续斩击/连击(数字重叠)
*Mini中毒/燃烧等持续伤害造成的小额伤害——减少画面杂乱
(g) 阵营专属皮肤

若要为不同阵营(玩家与敌人、PvP 阵营等)使用不同的皮肤 RUID,可在运行时替换 DamageSkinSettingComponent.DamageSkinId:

self.Entity.DamageSkinSettingComponent.DamageSkinId = MY_TEAM_SKIN_RUID

12. 命中效果 — HitEffectSpawnerComponent

将其附加到防御方后,命中时会在 HitEvent 上自动播放命中效果。没有任何属性——只需将组件添加到 .model 中。


13. 完整战斗检查清单

  • 攻击者模型:派生自 AttackComponent 的脚本(+ 可选:DamageSkinSettingComponent)
  • 防御者模型:HitComponent + HitEffectSpawnerComponent +(可选:DamageSkinSpawnerComponent + DamageSkinComponent)
  • HitComponent:IsLegacy=false,设置 ColliderType/BoxSize/CircleRadius,设置 CollisionGroup
  • 状态动作:在 StateComponent 中注册 ATTACK/HIT/DEAD + AvatarStateAnimationComponent.StateToAvatarBodyActionSheet
  • HP 处理:玩家使用 PlayerComponent.Hp;怪物使用自定义 @Sync Hp
  • 方向检查:使用 LookDirectionX(不要使用 Scale.x);对于自动触发或 AI 触发的攻击,在攻击前将其设置为朝向目标的方向(±1)——§9-1
  • 时间参考:_UtilLogic.ElapsedSeconds(不要使用 os.clock)
  • 事件清理:在 OnEndPlay 中显式调用 DisconnectEvent
  • 实体规则:不要直接为带有活动 Body 的实体赋值 TransformComponent.Position

14. 需要自定义实现的功能

Buff/Debuff · BT 装饰器/记忆(Blackboard)· 仇恨/威胁表 · 投射物池化 · 穿透/最大命中次数 · 硬直等级系统 · 资源(MP/体力/怒气)· 连击/取消窗口 · 格挡/招架 · 世界坐标→屏幕坐标转换 · 世界空间 HP 条


不在范围内

评论

支持 Markdown