首页/游戏开发/msw-combat-system
M

msw-combat-system

by @msw-gitv
4.4(120)

此技能提供 MSW 2D 游戏战斗系统的完整集成指南,涵盖攻击判定、伤害模型、无敌帧、击退、受击停顿、镜头震动、特效、死亡复活、伤害皮肤、命中效果、角色战斗动作及 AI FSM 等核心机制。它基于 MSW 原生 API,帮助开发者高效构建多类型 2D 游戏的战斗体验,提升游戏打击感与沉浸感。

game-developmentcombat-systemgame-ai2d-gameshit-detectionGitHub
安装方式
npx skills add https://github.com/msw-git/msw-ai-coding-plugins-official --skill msw-combat-system
compare_arrows

Before / After 效果对比

1
使用前

在没有这个技能之前,开发者需要手动从零开始设计和实现复杂的 2D 游戏战斗系统,包括攻击判定、伤害计算、受击反馈、特效联动和 AI 行为等,这通常需要投入大量时间和精力进行架构设计、编码和调试,导致开发周期长且容易出现不一致的体验。

使用后

此技能提供了一套基于 MSW 原生 API 的完整战斗系统集成指南,开发者可以直接利用其提供的框架和示例,快速实现各种战斗机制,大幅缩短开发时间,并确保战斗体验的稳定性和专业性。

SKILL.md

msw-combat-system

The full MSW combat pipeline. Covers only items in the common 2D combat layer that have MSW native API support, regardless of genre. Excludes formulas/theory. API signatures are based on Environment/NativeScripts/**/*.d.mlua.


0. Coverage matrix

#LayerNativeCustom required
1Attack ResolutionAttackComponent + HitComponent (Box/Circle/Polygon)Capsule/Cone/Ray, pierce count
2Damage ModelCalcDamage/CalcCritical/GetCriticalDamageRate/GetDisplayHitCount hooks + HitEvent.Extra:anyElement affinity, composite formulas
3Hit ReactionPer-Body knockback API, IsHitTarget-based i-frameStagger level, status effects
4Game FeelAll 6 native (Hit Stop, Shake, Zoom, Flash, VFX, SFX)
5Combat StateStateComponent + DeadEvent/ReviveEvent, PlayerComponent HP/reviveMP/Stamina/Rage, aggro
6Event BusHitEvent/AttackEvent/StateChangeEvent/PlayerActionEvent + custom @EventOnKill/OnBlocked
7AIStateComponent (FSM) + AIComponent (BT, 4 Composite types native) + AIChaseComponent/AIWanderComponent, _UserService.UserEntitiesDecorator/Memory(Blackboard), Threat Table
+Damage Skin3 DamageSkin* components + DamageSkinService
+Hit EffectHitEffectSpawnerComponent (auto)
+Avatar MotionAvatarStateAnimationComponent (State→MapleAvatarBodyActionState)

0.5 References — where to go

This SKILL.md covers only the system flow and native API surface. Actual model JSON, full script code, and variation patterns are in the references/* files below — Read them directly.

FileScopeWhen to read
../msw-general/references/monster.mdMonster .model component assembly + ActionSheet + AI choice + canonical Pattern A scripts (Soldier-style) + HP/Respawn + spawn + verificationWhen building a combat-capable monster
references/hp-gauge.mdFull implementation of an overhead HP bar based on PixelRendererComponentWhen attaching an overhead HP bar
references/projectile.mdProjectile (Body-less entity + OnUpdate Translate) + homing/pierce/splash variantsWhen building ranged attacks like arrows, bullets, magic bolts
references/ai-bt.mdBehaviourTree — AIComponent + 4 Composite types + @BTNode + custom Decorator/Memory/ThreatWhen you need BT-based monster/boss AI and multi-layer decision making

Priority: *this SKILL.md (concepts + API tables) → the relevant references/ (full implementation)**.


1. Attack Resolution

1-1. Shape & Attack trigger

HitComponent.ColliderType supports only Box / Circle / Polygon. Other shapes must be approximated by composition.

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)
  • Shapes: CircleShape(position, radius) / BoxShape(position, size, angle) / PolygonShape(position, points, angle). For an axis-aligned rectangle, use BoxShape(center, size, 0) — there is no RectangleShape type. Pass angle = 0 to BoxShape / PolygonShape when no rotation is needed.
  • Polygon hit surface: HitComponent.PolygonPoints: SyncList<Vector2>
  • AttackFast does not build a hit table → better performance for bullet hell / mass resolution

1-2. Target filter

SideOverridePurpose
AttackerAttackComponent:IsAttackTarget(defender, attackInfo) → booleanFaction / distance / state
DefenderHitComponent:IsHitTarget(attackInfo) → booleanInvincibility / immunity

If either returns false → the hit is excluded. The super call is __base:IsAttackTarget(...) (mlua-specific).

Do not add @ExecSpace when overriding — both IsAttackTarget and IsHitTarget have an unspecified ExecSpace (=All) on the parent. Adding an annotation like @ExecSpace("ServerOnly") in the child triggers LEA-3014 SignatureMismatch at runtime. Even without the annotation, the call path runs through the server-side hit pipeline, so actual execution happens on the server. Details: msw-scripting/SKILL.md §9 "Method override".

  • HitComponent.CollisionGroup defaults to CollisionGroups.HitBox. The last argument of Attack(..., cg) specifies the target group.
  • Duplicate-hit prevention / pierce / max hits: not native. Manage in script via the table returned by Attack + a table<Entity, boolean> cache.

1-3. attackInfo tagging

A string extension point that propagates into CalcDamage/IsHitTarget/GetDisplayHitCount. Value conventions are up to the project. A namespace style such as "melee.light", "dot.poison" is recommended.

1-4. ⚠️ IsLegacy

ColliderType/ColliderOffset/PolygonPoints are only valid when HitComponent.IsLegacy = false. BoxOffset/ColliderName are deprecated.

1-5. Shape mapping per attack form

FormShape construction
Frontal melee BoxBoxShape(pos + LookDirectionX*offset, size, 0) — see DefaultPlayer PlayerAttack
Circular AoECircleShape(self.WorldPos, radius)
ProjectileSpawn a Body-less model (Sprite+Transform only) + in OnUpdate(delta) call TransformComponent:Translate(speed*delta, 0) + distance-based hit check + _EntityService:Destroy. Movement rules in §1-6, full implementation → references/projectile.md

There is no MSW-specific projectile system — implemented as an entity + AttackComponent combo.

1-6. Continuous movement — common rules for projectiles, monsters, and AI

Continuous movement (chase, flight, auto-move) is per-frame OnUpdate(delta)-based. Moving via a timer (SetTimerRepeat(0.1~0.15s)) produces 6~10Hz teleportation that looks choppy.

Recommended API per target

TargetBody?Movement APIRationale
Monster / NPC / AIYes (map-type Body + MovementComponent)MovementComponent:MoveToDirection(dir, 0) + MovementComponent.InputSpeedMovementComponent.d.mlua:1 — controls all three of Rigid/Kinematic/Sideview. InputSpeed belongs to MovementComponent (.d.mlua:7), so it is not Player-only. The second arg 0 — deltaTime is applied only on ladders (.d.mlua:32). Official BT examples ActionFollow/ActionMoveRandom also use 0.
Projectile / gem / drop item / effectNo (Sprite+Transform+Trigger)self.Entity.TransformComponent:Translate(speed*delta, 0) every frameDirect Transform manipulation is safe without a Body. The pattern from the official "Create a Long-Range Projectile" tutorial.
Direct Rigidbody control (advanced)Yesbody:AddForce(...) — sustained acceleration / impulseRigidbodyComponent.d.mlua:71MoveVelocity is "mainly controlled by MovementComponent", so prefer routing through MovementComponent instead of writing it directly.

For the actual velocity conversion of MovementComponent.InputSpeed per map type, see msw-general/references/platform.md §10 (MapleTile=×1, RectTile=÷1.2, SideView=×1.5).

Forbidden patterns

Reason
_TimerService:SetTimerRepeat(move, 0.1~0.15) for movement6~10Hz teleport, no frame interpolation → jerky
body:SetPosition(...) / MovementComponent:SetPosition(...) inside OnUpdateBoth are teleport methods (MovementComponent.d.mlua:37, SetPosition on each Body's .d.mlua). Using them for continuous movement is choppy. Use only for one-shot spawn/respawn/snap.
self.Entity.TransformComponent.Position = newPos (entity with active Body)The physics engine overwrites it next frame and network sync is blocked.
Constant-step move without delta, e.g. Translate(0.009, 0)Frame-rate dependent. Speed differs between 60FPS and 30FPS.

⚠ Be cautious with the official msw-search antipattern

mlua_Document_Retriever returns a high-scoring "Entity Movement Control Using MovementComponent" document, but the body is an antipattern that moves every frame inside OnUpdate with MovementComponent:SetPosition(...)ignore that document and use MoveToDirection / Translate from the table above. FlappyFish Remake, Stopping the Taxi, and Making a Moving Foothold also show missing-delta or direct-Position assignment, so be careful when referring to them.

MovementComponent attachment — monsters / NPCs

Not included in the monster model by default. The .model must contain all of the following components.

ComponentNotes
MOD.Core.TransformComponentDefault
MOD.Core.SpriteRendererComponentRenderer
Body (map type)RigidbodyComponent(MapleTile) / KinematicbodyComponent(RectTile) / SideviewbodyComponent(SideViewRectTile)
MOD.Core.MovementComponentE.g. InputSpeed = 2.0 — required for movement APIs

Cross references


2. Damage Model

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)

⚠ All hooks above have an unspecified ExecSpace (=All) on the parent. Adding @ExecSpace("ServerOnly") etc. when overriding triggers LEA-3014 SignatureMismatch. Drop the annotation and declare just method .... Details: msw-scripting/SKILL.md §9 "Method override".

2-1. HitEvent payload

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

Carry auxiliary info (knockback vector, stagger time, element) on the Extra table.

2-2. AttackEvent payload

A single field, DefenderEntity: Entity. The attacker is the handler's self.

2-3. ⚠️ Antipattern: direct HP subtraction — do not bypass HitEvent

Subtracting the defender's HP directly, such as monster.Hp -= damage / target.MonsterAI.HP -= damage, does not emit HitEvent — damage skin, hit effect, IsHitTarget immunity, and OnHit overrides all silently skip.

For player-side custom damage (channel / aura / DoT), the bypass also breaks avatar animation: AvatarStateAnimationComponent only reacts to StateChangeEvent, so the player avatar stays in idle even though the HP bar drops. If you must apply damage without HitEvent, also manually call StateComponent:ChangeState("HIT") (UPPERCASE key — see §10) and, for death/revive, PlayerComponent:ProcessDead() / ProcessRevive(). Otherwise hit/dead motions silently miss with no error.


3. Hit Reaction

3-1. Knockback — API per Body

Body (map type)Implementation
Rigidbody (MapleTile)body:AddForce(Vector2(dir*5, 3)) ★recommended · SetForce · JustJump(Vector2(0, 4)) (vertical)
Kinematicbody (RectTile / top-down)body.MoveVelocity = Vector2(dir*5, 0) — no AddForce
Sideviewbody (SideViewRectTile)body.MoveVelocity + body.JumpSpeed
  • Rigidbody is auto-damped by the engine. Kinematic/Sideview must be damped manually inside OnUpdate (MoveVelocity *= 0.9).
  • Wall bounce: subscribe to FootholdCollisionEvent and flip the velocity.
  • Knockback is a 1-shot impulse — do not confuse it with continuous movement (chase, flight). For continuous movement see §1-6.
  • ⚠ Forbidden: assigning TransformComponent.Position directly on an entity with an active Body → network sync is blocked. body:SetPosition(...) is a teleport method, so do not call it inside an OnUpdate loop (§1-6).

3-2. i-frame

Standard pattern: deadline check based on _UtilLogic.ElapsedSeconds + returning false from HitComponent:IsHitTarget. The DefaultPlayer default PlayerHit.mlua provides this pattern as-is (§9-4).

Alternative: while invincible, swap HitComponent.CollisionGroup to a separate group → the resolution itself is excluded. This is better for frame-accurate precision.

3-3. Status effects (Buff/Debuff)

No native support. Implement a @Component BuffComponent directly + tick with _TimerService:SetTimerRepeat + broadcast custom StatusAppliedEvent/StatusExpiredEvent.

For a single simple stun, StateComponent:ChangeState("STUN") + input/AI block flags is enough.


4. Game Feel — all native

ElementAPIExecSpace
Hit Stop (global)_UtilLogic:SetClientTimeScale(float) — 0~100ClientOnly
Hit Stop (individual)renderer.PlayRate = 0 (Sprite/Skeleton/Avatar)@Sync
Slow Motion_UtilLogic:SetClientTimeScale(0.3) + timer to restoreClientOnly
Camera ShakecameraComp:ShakeCamera(intensity, duration, targetUserId?)Client
Camera ZoomcameraComp:SetZoomTo(percent, duration, targetUserId?) · requires IsAllowZoomInOut=true firstClient
Hit FlashspriteRenderer.Color = Color(r,g,b,a) → timer to restore@Sync
Color HDR overbrightColor.HSVToRGB(h, s, v, hdr=true) — values > 1.0 allowed
VFX fixed_EffectService:PlayEffect(clipRUID, instigator, pos, zRot, scale, isLoop?, options?) → serial
VFX attached_EffectService:PlayEffectAttached(clipRUID, parent, localPos, localZRot, localScale, isLoop?, options?)
VFX remove_EffectService:RemoveEffect(serial)
SFX 2D`_SoundService:PlaySound(id, volume, targetUserId?

...

用户评价 (0)

发表评价

效果
易用性
文档
兼容性

暂无评价

统计数据

安装量2.7K
评分4.4 / 5.0
版本
更新日期2026年7月10日
对比案例1 组

用户评分

4.4(120)
5
37%
4
43%
3
13%
2
5%
1
2%

为此 Skill 评分

0.0

兼容平台

🤖claude-code

时间线

创建2026年6月21日
最后更新2026年7月10日
🎁 Agent 知识卡片
调研问卷