msw-combat-system について
msw-combat-systemは、MapleStory Worldsの一般的な2D戦闘におけるネイティブAPIの流れをまとめた資料です。戦闘システムを実装する開発者向けに、攻撃判定、ダメージ、被弾反応、状態、イベント、AIを扱い、詳細な実装資料も案内します。
主な機能
- 01攻撃形状と対象フィルターの説明
- 02ダメージフックとヒットイベントAPIの整理
- 03ノックバック、無敵時間、演出の解説
- 04状態、プレイヤーHP、復活フローの説明
- 05イベントバスとネイティブAI機能の整理
活用シーン
- 01近接攻撃や範囲攻撃のヒット判定設定
- 02モンスターの被弾、ノックバック、無敵時間の実装
- 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.mdhp-gauge.mdprojectile.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 | OnKill/OnBlocked |
| 7 | AI | StateComponent(FSM)+ AIComponent(BT、ネイティブ Composite 4 種 + カスタム extends CompositeNode)+ AIChaseComponent/AIWanderComponent、_UserService.UserEntities | Decorator/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.md | PixelRendererComponent に基づく頭上 HP バーの完全な実装 | 頭上 HP バーを取り付けるとき |
references/projectile.md | 飛び道具(Body のないエンティティ + OnUpdate Translate)+ 誘導/貫通/範囲攻撃のバリエーション | 矢、弾丸、魔法弾などの遠距離攻撃を作成するとき |
references/ai-bt.md | BehaviourTree — 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-3014SignatureMismatchが発生します。アノテーションがなくても、呼び出し経路はサーバー側のヒット処理を通るため、実際の処理はサーバー上で実行されます。詳細は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.InputSpeed | MovementComponent.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-3014SignatureMismatchが発生します。アノテーションを削除し、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. ゲームの手触り — すべてネイティブ
| 要素 | API | ExecSpace |
|---|---|---|
| ヒットストップ(全体) | _UtilLogic:SetClientTimeScale(float) — 0~100 | ClientOnly |
| ヒットストップ(個別) | 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 |
| ループ SFX | PlayLoopSound/PlayLoopSoundAtPos | Client |
| アタッチ SFX | SoundComponent: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 | 激しいカートゥーン調の炎 | |
FireBall | 1点に発生する炎 | |
FlameThrower | 火炎放射器の炎 | |
LargeFlames | 床から立ち上る大きな炎 | |
MediumFlames | 床から立ち上る中くらいの炎 | |
TinyFlames | 床から立ち上る小さな炎 | |
WildFire | 巨大な炎の柱(Color オプションは無視されます) | |
| 雷/電気 | LightningOrbSharp | 球状の電気粒子 |
LightningStrikeSharp | 稲妻 | |
LightningStrikeSharpTall | 背の高い稲妻 | |
LightningOrbSoft | 電気の波の放出 | |
LightningBlast | 周期的な電気の波 | |
LightningStrike | 周期的な稲妻 | |
LightningStrikeTall | 周期的な背の高い稲妻 | |
| バフ/魔法 | Aura | 床から立ち上るオーロラの光 |
Buff | 床から立ち上る強い光 | |
Charge | 1点に収束する大きな粒子 | |
ChargeOrb | 1点に収束する粒子 | |
Enchant | 周囲に光や粒子を伴う大きな光 | |
SpinField | 回転する円の周囲の粒子 | |
StarVortex | 中心に収束する星明かり | |
Nova | 幅広い円形の波 | |
UpperCylinder | 床から立ち上る柱 | |
| その他 | Firework | 花火 |
FireworkCluster | 複数の花火が同時に打ち上がる | |
FireFlies | ホタル | |
GoopSpray | 横方向への液体の噴射 | |
GoopSprayEffect | 下方向への液体の噴射 | |
DustStorm | 広範囲の砂嵐 | |
RisingSteam | 床から立ち上る白い霧 | |
BigSplash | 大きな水しぶき | |
Shower | 1か所に水を注ぐ |
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. 死亡/復活
| イベント | 発生条件 | ペイロード |
|---|---|---|
DeadEvent | StateComponent:ChangeState("DEAD") で自動発生 | なし |
ReviveEvent | PlayerComponent:Respawn() で自動発生(プレイヤーのみ) | なし |
StateChangeEvent | 状態が遷移するたびに自動発生 | CurrentStateName、PrevStateName |
キラーの追跡:DeadEvent にはペイロードがありません → HandleHitEvent で self.LastAttacker = event.AttackerEntity をキャッシュし、HandleDeadEvent で使用します。
プレイヤー固有の死亡/復活には、§9-1 の PlayerComponent.Respawn/ProcessDead/ProcessRevive を優先してください。
6. イベントバス
| 論理イベント | MSW の実装 |
|---|---|
| OnAttackStart | OnAttack フック、またはカスタム 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、ボスのフェーズ、アニメーション同期(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 ネイティブ機能
| UI | API |
|---|---|
| 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.HitBox | HitComponent.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(攻撃側)
| プロパティ | 型 | デフォルト | 説明 |
|---|---|---|---|
DamageSkinId | DataRef | ヒット RUID(上記の表) | ダメージ数値のスキン RUID |
DamageSkinScale | Vector2 | (1, 1) | 数値のサイズ |
Alpha | float | 1 | 不透明度 |
PlayRate | float | 1 | 再生速度 |
DelayPerAttack | float | 0.05 | 複数ヒット間の間隔(秒) |
TweenType | DamageSkinTweenType | Default | アニメーションスタイル |
LitMode | LitMode | Default | ライティングの影響 |
DamageSkinTweenType:Default(ポップアップ)/Volcano(扇状)/Blade(重ね合わせ)/各 *Mini(75%のスケール)
DamageSkinSpawnerComponent(防御側)
| プロパティ | 型 | デフォルト |
|---|---|---|
DamageSkinOffset | Vector2 | (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-3005InvalidArgumentが発生します。
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 | 連続斬撃/コンボ(数値を重ねる) |
*Mini | DoT(毒/火傷)などの小さなダメージ — 画面のごちゃつきを軽減 |
(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