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ダメージフックとヒットイベントAPIの整理
  3. 03ノックバック、無敵時間、演出の解説
  4. 04状態、プレイヤーHP、復活フローの説明
  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 + カスタム @EventOnKill/OnBlocked
7AIStateComponent(FSM)+ AIComponent(BT、ネイティブ Composite 4 種 + カスタム extends CompositeNode)+ AIChaseComponent/AIWanderComponent、_UserService.UserEntitiesDecorator/Memory(Blackboard)、脅威テーブル
+ダメージスキン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.mdPixelRendererComponent に基づく頭上 HP バーの完全な実装頭上 HP バーを取り付けるとき
references/projectile.md飛び道具(Body のないエンティティ + OnUpdate Translate)+ 誘導/貫通/範囲攻撃のバリエーション矢、弾丸、魔法弾などの遠距離攻撃を作成するとき
references/ai-bt.mdBehaviourTree — AIComponent + Composite(ネイティブ 4 種 + カスタム)+ @BTNode + カスタム Decorator/Memory/脅威BT ベースのモンスター/ボス 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 を返すと、そのヒットは除外されます。super の呼び出しは __base:IsAttackTarget(...) です(mlua 固有)。

⚠ オーバーライド時に @ExecSpace を追加しないでください — IsAttackTarget と IsHitTarget はどちらも親クラスで ExecSpace が指定されていません(=All)。子クラスに @ExecSpace("ServerOnly") のようなアノテーションを追加すると、実行時に LEA-3014 SignatureMismatch が発生します。アノテーションがなくても、呼び出し経路はサーバー側のヒット処理を通るため、実際の処理はサーバー上で実行されます。詳細は msw-scripting/SKILL.md §9「メソッドのオーバーライド」 を参照してください。

  • Attack*(..., cg) の cg 引数は、防御側の HitComponent.CollisionGroup との完全一致フィルターです(デフォルトは CollisionGroups.HitBox)。衝突マトリックスは参照せず、攻撃側自身のグループも関係ありません。nil(省略時)は、すべての HitComponent が候補になります。有効なグループでも防御側の実際のグループと異なる場合、エラーなしで対象に一度もヒットしません(サイレント)。このワールドの衝突グループセットに存在しないグループ参照は、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

ColliderType/ColliderOffset/PolygonPoints は、HitComponent.IsLegacy = false の場合にのみ有効です。BoxOffset/ColliderName は非推奨です。

1-5. 攻撃形態ごとの形状対応
形態形状の構築方法
正面近接ボックスBoxShape(pos + LookDirectionX*offset, size, 0) — DefaultPlayer の PlayerAttack を参照
円形範囲攻撃CircleShape(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~10Hz のテレポートになり、動きがぎこちなく見えます。

対象ごとの推奨 API
対象Body は必要?移動 API理由
モンスター/NPC/AIはい(マップタイプに対応した Body + MovementComponent)MovementComponent:MoveToDirection(dir, 0) + MovementComponent.InputSpeedMovementComponent.d.mlua:1 — Rigid/Kinematic/Sideview の 3 種すべてを制御します。InputSpeed は MovementComponent に属するため(.d.mlua:7)、Player 専用ではありません。第 2 引数の 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 に必要
関連項目
  • **ノックバック(単発インパルス)**は連続移動ではないため、§3-1 を直接使用してください。
  • マップタイプごとの Body の選択/InputSpeed の換算:msw-general/references/platform.md §4·§10

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)あらかじめ対象エンティティが1つだけ判明している場合に限り、妥当です
  • ❌ プレイヤー↔モンスターまたは複数ターゲットのコリジョンに、フレームごとの math.sqrt(dx*dx+dy*dy) < r を使わないでください。トランスフォームを中心とした1つの円で暗黙に近似するため(各コライダーの形状/サイズ/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「メソッドのオーバーライド」 を参照してください。

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 のペイロード

フィールドは1つだけです。DefenderEntity: Entity です。攻撃側はハンドラーの self です。

2-3. ⚠️ アンチパターン:HPを直接減らす — HitEvent をバイパスしない

monster.Hp -= damage / target.MonsterAI.HP -= damage のように防御側のHPを直接減らしても、HitEvent は発行されません。ダメージスキン、ヒットエフェクト、IsHitTarget の無敵状態、OnHit のオーバーライドは、すべて何も通知されずにスキップされます。

プレイヤー側のカスタムダメージ(継続ダメージ/オーラ/DoT)でバイパスすると、アバターのアニメーションも動作しなくなります。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 回限りのインパルスです。継続的な移動(追跡、飛行)と混同しないでください。継続的な移動については §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. 状態異常(バフ/デバフ)

ネイティブサポートはありません。 @Component BuffComponent を直接実装し、_TimerService:SetTimerRepeat で定期処理を行い、カスタムの StatusAppliedEvent/StatusExpiredEvent をブロードキャストしてください。

単純なスタンが 1 種類だけなら、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=true が必要ですClient
ヒットフラッシュ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) でクリーンアップする必要があります。後から削除できるように、シリアルを self._T に保存してください。

BasicParticleType の全リスト
ファミリー名前説明
爆発/衝撃SparkExplosion火花(単発)— 汎用的なヒットエフェクト
SparkLoop継続的な火花
SparkRadialExplosion放射状に散る火花
SmallExplosion小さな爆発+煙
BigExplosion大きな爆発+煙
TinyExplosion非常に小さな爆発(Color オプションは無視されます)
DustExplosion円形の衝撃波+煙(Color オプションは無視されます)
EnergyExplosion円形の衝撃波の後、中心に収束
CircleBurst円形の光のバースト
PillarBurst円形の光のバースト+指向性の光
火/炎FireFieldカートゥーン調の炎
FireFieldIntense激しいカートゥーン調の炎
FireBall1点に発生する炎
FlameThrower火炎放射器の炎
LargeFlames床から立ち上る大きな炎
MediumFlames床から立ち上る中くらいの炎
TinyFlames床から立ち上る小さな炎
WildFire巨大な炎の柱(Color オプションは無視されます)
雷/電気LightningOrbSharp球状の電気粒子
LightningStrikeSharp稲妻
LightningStrikeSharpTall背の高い稲妻
LightningOrbSoft電気の波の放出
LightningBlast周期的な電気の波
LightningStrike周期的な稲妻
LightningStrikeTall周期的な背の高い稲妻
バフ/魔法Aura床から立ち上るオーロラの光
Buff床から立ち上る強い光
Charge1点に収束する大きな粒子
ChargeOrb1点に収束する粒子
Enchant周囲に光や粒子を伴う大きな光
SpinField回転する円の周囲の粒子
StarVortex中心に収束する星明かり
Nova幅広い円形の波
UpperCylinder床から立ち上る柱
その他Firework花火
FireworkCluster複数の花火が同時に打ち上がる
FireFliesホタル
GoopSpray横方向への液体の噴射
GoopSprayEffect下方向への液体の噴射
DustStorm広範囲の砂嵐
RisingSteam床から立ち上る白い霧
BigSplash大きな水しぶき
Shower1か所に水を注ぐ
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
OnCriticalHitHitEvent.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、ボスのフェーズ、アニメーション同期(AvatarStateAnimationComponent による自動マッピング §10)。StateAnimationComponent でクリップを自動切り替えするには、StateComponent.IsLegacy=false が必要です。../msw-general/references/animation-state.md(ステートマシンとアニメーションパイプラインを統合)
BT(AIComponent + Composites(ネイティブ 4 種+カスタム)+ @BTNode)巡回+追跡+攻撃のコンボ、多様なボスパターン、Composite/Decorator の再利用、確率に基づく行動。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「標準パターン A のスクリプト(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 に自動登録されるわけではありません。この2つは別個のものです。

実装の詳細 → ../msw-general/references/animation-state.md(FSM の作成、ChangeState の失敗パターン一覧、標準的な PATROL/CHASE/ATTACK/HIT/DEAD モンスターのパターン、状態からアニメーションへのパイプラインをまとめて解説しています。これらは同じ基盤システムを異なる角度から見たものです)

7-2. BT — AIComponent(概要)

AIComponent + SequenceNode/SelectorNode/RandomSelectorNode/ParallelNode + @BTNode アクションノード + ネイティブの AIChaseComponent/AIWanderComponent。4種類すべてのコンポジットがネイティブで用意されています。カスタムの子ノード実行ポリシーは @BTNode ... extends CompositeNode(ChildCount/ChildBehave)を使ってスクリプトで実装できます。デコレーター/メモリー(ブラックボード)/脅威テーブルは手動で実装する必要があります。

⚐ カスタム 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 バー(頭上に表示):ネイティブ対応はありません。実装方法は2つあります。

方法アプローチ適合する用途
軽量子エンティティの SpriteRendererComponent の LocalScale.x = hp/maxHp を調整するか、SpriteDrawMode.Tiled と併せて TiledSize.x を使います簡易プロトタイプ、シンプルなゲージ
完全版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

デフォルトのパターンは、AttackComponent を拡張した PlayerAttack で、@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) 複数ヒット — 1回の呼び出しでN個に分割

_DamageSkinService:Play の damages 引数に List を渡すと、攻撃側コンポーネントの 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連続斬撃/コンボ(数値を重ねる)
*MiniDoT(毒/火傷)などの小さなダメージ — 画面のごちゃつきを軽減
(g) 陣営別のスキン

陣営ごとに異なるスキン RUID(プレイヤー対敵、PvP の陣営など)を使用するには、実行時に 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 と AvatarStateAnimationComponent.StateToAvatarBodyActionSheet に ATTACK/HIT/DEAD を登録します
  • HP の処理:プレイヤーは PlayerComponent.Hp を使用し、モンスターはカスタムの @Sync Hp を使用します
  • 向きのチェック:LookDirectionX を使用します(Scale.x は使用しません)。自動攻撃や AI による攻撃では、攻撃前にターゲットの方向へ(±1)設定します — §9-1
  • 時間の参照:_UtilLogic.ElapsedSeconds を使用します(os.clock は使用しません)
  • イベントのクリーンアップ:OnEndPlay で明示的に DisconnectEvent を呼び出します
  • Body のルール:有効な Body を持つエンティティの TransformComponent.Position を直接設定しないでください

14. カスタム実装が必要です

バフ/デバフ · BT デコレーター/メモリー(Blackboard) · ヘイト/脅威テーブル · 飛び道具のプーリング · 貫通/最大ヒット数 · ひるみレベルシステム · リソース(MP/スタミナ/レイジ) · コンボ/キャンセル受付時間 · ガード/パリィ · ワールド座標から画面座標への変換 · ワールド空間の HP バー


対象範囲外

  • 一般的なプレイヤー関連のトピック(HP/移動/カメラ/コスチュームは除く):msw-defaultplayer
  • mlua の文法/ライフサイクル全般:msw-scripting
  • .model の作成ルール/テンプレート:msw-general

コメント