M

msw-scripting

by @msw-gitv
4.4(120)

この Skill は、MSW スクリプト (.mlua) の作成、テスト、デバッグを支援します。mlua 構文、MSW 固有のアノテーション、ライフサイクル、実行空間、プロパティ同期、イベントシステム、ファイルワークフローを網羅。スクリプトの正確性と効率的な動作を保証し、ゲームロジックの開発とイテレーションを加速させます。

scriptinggame-developmentdebuggingluaworkflowGitHub
インストール方法
npx skills add https://github.com/msw-git/msw-ai-coding-plugins-official --skill msw-scripting
compare_arrows

Before / After 効果比較

1
使用前

この Skill がない場合、開発者は MSW スクリプトの独自のメカニズムと API の使用法を理解するために、大量のドキュメントを手動で参照し、試行錯誤を繰り返す必要がありました。これにより、スクリプト開発期間が長くなり、実行時エラーが頻発し、デバッグプロセスが時間と効率を要していました。

使用後

この Skill は、mlua 構文、MSW 固有のアノテーション、ファイルワークフローに関する包括的なガイダンスを提供し、開発者がスクリプト作成のベストプラクティスを迅速に習得できるよう支援します。統合されたテストとデバッグのループにより、この Skill は実行時エラーとデバッグ時間を大幅に削減し、スクリプト開発の効率と品質を著しく向上させます。

SKILL.md

MSW Scripting (.mlua) — Framework + File Workflow + Playtest & Debugging

mlua is Lua-based, but it has MSW-specific annotations, a lifecycle, and an execution-space model. General Lua knowledge alone will not produce working code. All work is done by editing files in the workspace directly, and code is validated in the order build logs → runtime logs.


1. Core Principles (must follow)

1.1 Existing Script First

Before creating a new .mlua, glob/keyword-search under ./RootDesk/MyDesk/ for an existing script with the same purpose — extending an existing file is always the first choice. Duplicate implementations raise maintenance cost and conflict risk.

1.2 Folder Structure for New Scripts — Never Dump Files Flat

When a new .mlua is unavoidable, place it under a feature/category subfolder. Required path shape: ./RootDesk/MyDesk/<FeatureFolder>/<ScriptName>.mlua.

  • Reuse an existing subfolder if it fits (Player/, UI/, Combat/, Inventory/, …); glob ./RootDesk/MyDesk/ first.
  • Otherwise create one named for the feature (PascalCase). All related scripts of one feature (Component/Logic/Event/Struct) stay together. Even a single-file feature gets its own folder.
  • Forbidden: catch-all folders like Scripts/, Misc/, Common/, New/, temp/. A flat root makes rule §1.1 (search before creating) impossible.

Examples: Inventory/InventoryManager.mlua, Combat/MeleeAttackComponent.mlua, UI/Popup/RewardPopupLogic.mlua.

1.3 Never Guess APIs — Verify Before Writing

Guessing an MSW API name/param/return type silently fails at runtime. Required order: .d.mlua for signaturemsw-search for semantics/examples if needed → write → LSP diagnose (auto-run).

The engine API lives under ./Environment/NativeScripts/:

FolderContentsCount
Component/Engine components104
Service/System services46
Event/Event types202
Logic/Built-in logic9
Enum/Enumerations118
Misc/Utility types (Vector2, …)140

Known name → Read ./Environment/NativeScripts/{folder}/{name}.d.mlua. Unknown name → Grep keywords there.

1.4 Lint (LSP diagnostics)

mlua-diagnose hook runs LSP diagnose automatically after every .mlua create/modify. Iterate fix → re-edit until error-severity diagnostics reach zero.

1.5 .codeblock & Refresh

  • .codeblock files are generated by Maker Refresh — never create/edit/delete manually.
  • After any .mlua create/modify/rename/delete, call Maker MCP refresh. Refresh requires edit mode — stop first if playing.

1.6 MSW ≠ Unity — Do Not Reason From Intuition

Applying Unity/generic patterns directly compiles fine but silently fails at runtime. Common misconceptions:

Unity intuitionMSW reality / Where it's covered
gameObject / transform from a global manager@Logic has no self.Entity — see §3.2 (use property injection / _EntityService)
OnMouseDown / BoxCollider2D for clicksPhysics colliders never emit TouchEvent — World uses TouchReceiveComponent (§10); UI uses ButtonComponent/UITouchReceiveComponent
OnCollisionEnter + RigidbodyEntity↔entity collisions need TriggerComponent + TriggerEnter/Leave/Stay event
UI field names (interactable/text/color)MSW-specific names — check msw-ui-system/references/component-api.md. Common mappings: disable→Enable, text→Text, text color→FontColor, tint→Color. ButtonComponent.Interactable doesn't exist.
Attach multiple Rigidbody/Collider freelyOne Body per map type — see msw-general/references/platform.md §4
Touch UI from server codeUI is client-only — server→UI goes via @ExecSpace("Client") RPC. Hosting Server/ServerOnly/Multicast/@Sync on a UI-attached Component silently no-ops with runtime warning. See msw-ui-system/references/runtime-patterns.md
Instantiate(prefab) callable anywhere_SpawnService:SpawnByModelId(id, name, pos, parent)parent required, server-only — see §11
static classes / hand-rolled singletons@Logic is itself the singleton — call as _ScriptName:Method(), never instantiate — see §3.2

Rule: when tempted to apply a Unity pattern, stop and verify against Environment/NativeScripts/*.d.mlua first.

1.7 Builder Protocol Preflight — MUST

If this turn touches .map / .model / .ui (directly, or via spawn/entity-placement/UI-binding code in .mlua), Read ../msw-general/references/builder-protocol.md first (full file, every turn — do not skip on prior-turn memory). It consolidates the write-side contracts (componentNames sync, typeKey metadata, auto-lint, child-entity invariants, placeModel mirroring) for all three builders; knowing one builder doesn't cover another.

Triggers (broad on purpose): _SpawnService / SpawnByModelId / SpawnByEntity; any .map/.model/.ui change; calling msw_map_builder.cjs / msw_model_builder.cjs / msw_ui_builder.cjs; any "new monster/NPC/popup/map object" request; §11 or §16 work.

1.8 Method Documentation Comments — Inside the Body

Every method (lifecycle, RPC, event handler, user-defined) must have a description comment as the first line inside the body, never above the declaration. mlua's parser binds leading comments to the previous declaration, so an "above" comment is unreliable.

-- ✅ Correct
method void ApplyDamage(Entity target, number amount)
    -- Applies damage and triggers hit VFX.
    target:TakeDamage(amount)
end

-- ❌ Wrong — comment above the method
-- Applies damage...
method void ApplyDamage(Entity target, number amount)
    target:TakeDamage(amount)
end

2. Paths and File Roles

TargetPathAgent action
User scripts./RootDesk/MyDesk/**/*.mluaCreate / read / modify / delete directly
Auto-generated artifacts*.codeblockDo not touch (Refresh manages them)
Engine API definitions./Environment/NativeScripts/**Read-only (do not modify)
Models (component lists)./RootDesk/MyDesk/*.model, ./Global/*.model, etc.Edit Components when attaching scripts
Map instances./map/*.mapEdit when attaching scripts to entities that exist only inside a map

3. Script Types and Declarations

3.1 Component scripts (@Component)

Scripts attached to an Entity. Use self.Entity to access the owning entity.

@Component
script MyScript extends Component
    property number Speed = 5.0

    @ExecSpace("ServerOnly")
    method void OnBeginPlay()
        -- initialization (also: OnUpdate(delta), OnEndPlay)
    end
end

Allowed parents:

  • Component — generic component
  • AttackComponent — attack system (Shape, AttackFast, OnAttack)
  • HitComponent — hit system (OnHit, HandleHitEvent)

3.2 Logic scripts (@Logic)

Global singletons. Run independently without an Entity. Use for game managers, UI managers, utilities, etc.

@Logic
script GameManager extends Logic
    @Sync property integer Score = 0

    @ExecSpace("ServerOnly")
    method void OnBeginPlay()
        -- global initialization (also: OnUpdate, OnEndPlay)
    end
end
  • One per world (singleton)
  • Accessed as _<ExactScriptName>no suffix stripping. TDHUDLogic.mlua_TDHUDLogic (not _TDHUD); TowerDefenseConfig.mlua_TowerDefenseConfig. Heuristic stripping silently returns nil.
  • Supports @Sync properties (server→client)
  • Logic's OnUpdate runs before Components'.

⚠️ @Logic has no self.Entity — Logic parent only exposes ConnectEvent/DisconnectEvent/IsClient/IsServer/SendEvent. self.Entity.xxx compiles but is a runtime nil-access. To bind a world entity, inject via property (property Entity x = "uuid" / property EntityRef x = "") or look it up with _EntityService:GetEntityByPath(...) / :FindEntityByName(...). Property injection (UUID literal) is preferred. See §7.

⚠️ OnMapEnter / OnMapLeave never fire on @Logic — they're Component-only (see §5). Declaring them on a Logic is silent dead code.

Decision: @Component vs @Logic — by lifetime, not "is it global?"

ScopePickWhy
World-wide, survives every map transition (account state, world event bus, global UI manager)@LogicEngine singleton; lives for whole world session.
Map-scoped — only meaningful inside one map (quest controller, wave spawner, puzzle)@Component on the map entityCleaned on map unload. Putting this in @Logic leaks state/timers across maps.
One actor (monster AI, item pickup, player skill)@Component on that entity

Ask: "Still running after the player walks to another map?" — Yes ⇒ @Logic; No (this map) ⇒ @Component on map entity; No (this actor) ⇒ @Component on actor.

3.3 Extend scripts

@Component
script PlayerAttack extends AttackComponent
    -- Override parent methods; call parent via __base:MethodName()
end

3.4 Other script types

@Event (custom event) · @Item (inventory) · @BTNode (behaviour tree) · @State (state machine) · @Struct (composite data type).


4. mlua Language Extensions (vs. plain Lua)

Based on Lua 5.3 with these differences:

Added syntax:

  • continue — skip to next loop iteration.
  • Compound assignment: +=, -=, *=, /=, //=, %=, ^=, ..= (and bitwise &=, |=, <<=, >>=). Multi-assign (a, b += 1, 2) and use as a function arg (print(a += 1)) are invalid.
  • Bitwise operators: &, |, <<, >>.

Restrictions:

  • No globals (global keyword forbidden) — share values via Properties.
  • No coroutines (coroutine.*).
  • Parent call is __base:MethodName(), not super.

Built-in utility functions:

FunctionPurpose
log() / log_warning() / log_error()Logging at each severity
wait(seconds)Pause script execution
isvalid(obj) → booleanValidity (handles deletion/nil)
enum(t) → tableSwap keys and values
beginscope(name) / endscope()Profiling scopes

5. Lifecycle

OnInitialize → OnBeginPlay → OnUpdate(delta) → OnEndPlay → OnDestroy
                                ↑
                  OnMapEnter / OnMapLeave (Component only, per transition)
MethodWhenWherePurpose
OnInitializeAfter creationComponent + LogicInit internal vars (rarely used)
OnBeginPlayGame startComponent + LogicWire events, start timers, initial setup
OnUpdate(delta)Every frameComponent + Logic (Logic first)Movement, animation, input
OnMapEnter / OnMapLeaveMap transitionComponent only (silent no-op on Logic)Per-map init/cleanup
OnEndPlayGame endComponent + LogicDisconnect events, clear timers (mandatory!)
OnDestroyRemovalComponent + LogicFinal cleanup (rarely used)

Required pattern: everything connected in OnBeginPlay must be released in OnEndPlay (events, timers).

property any eventHandler = nil   -- EventHandlerBase (must be 'any'; not integer)
property integer timerId = 0

method void OnBeginPlay()
    self.eventHandler = self.Entity:ConnectEvent(SomeEvent, self.OnSomeEvent)
    self.timerId = _TimerService:SetTimerRepeat(self.Tick, 1/60)
end
method void OnEndPlay()
    if self.eventHandler then self.Entity:DisconnectEvent(SomeEvent, self.eventHandler) end
    if self.timerId then _TimerService:ClearTimer(self.timerId) end
end

6. Execution Space (ExecSpace)

MSW is a server-client architecture. Every method must declare where it runs.

ExecSpaceRuns onDirectionUse case
ServerOnlyServerServer-internal onlyDamage calc, state changes, spawning
ClientOnlyClientClient-internal onlyUI updates, effects, sounds
ServerServerClient→Server RPCClient requesting the server (attack, item use)
ClientClientServer→Client RPCServer notifying a client (result UI, effects)
MulticastAll clientsServer→all clientsGlobal events (announcements, boss spawn)
(unspecified)Caller sideServer→Server, Client→ClientShared functions executed locally on either side

ExecSpace constraints on lifecycle methods

MethodAllowed ExecSpace
OnSyncPropertyClientOnly only
OnInitialize, OnBeginPlay, OnUpdate, OnEndPlay, OnDestroy, OnMapEnter, OnMapLeaveServerOnly, ClientOnly, or unspecified
All event handlersServerOnly, ClientOnly, or unspecified
Custom user methodsAny of Server, Client, ServerOnly, ClientOnly, Multicast

Typical server-client pattern

[Client]  input (ClientOnly) ──Request()──→ [Server] validate (ServerOnly)
                                                ├─ state auto-syncs via @Sync
[Client]  UI update (ClientOnly) ←──Show()──────┘ (Client RPC)
  • ServerOnly: client call is silently ignored (no error).
  • Server: client→server RPC (network latency).
  • Client: server→client RPC; add UserId as the last call-site arg to target one client (do NOT add it to the declaration).

senderUserId — verifying the requester

Inside an @ExecSpace("Server") body, the local senderUserId holds the caller client's UserId (server-assigned, not client-modifiable). Use it for security checks.

@ExecSpace("Server")
method void RequestBuyItem(integer itemId)
    if senderUserId ~= self.Entity.PlayerComponent.UserId then return end
    self:ProcessPurchase(itemId)
end

Reserved parameter names — name is unavailable

Four parameter names are reserved for the RPC marshaller and cannot be used as your own parameter names on any @ExecSpace(...) method. The LSP blocks the script with '<name>' name is unavailable.:

ReservedWhat the engine uses it for
selfMethod receiver
senderUserIdCalling client's UserId on @ExecSpace("Server") bodies
targetUserIdRecipient client's UserId (last call-site arg on @ExecSpace("Client") bodies — do NOT declare it; the engine appends it)
messageOwnerEntityOriginating entity for some service callbacks

Rename your own parameters when they collide (targetUserIdforUserId, senderUserIdfromUserId). self is the receiver and cannot be aliased — pick any other name for an unrelated parameter.

Manual branching — IsServer() / IsClient() are methods, not properties

When a method has no `@E

...

ユーザーレビュー (0)

レビューを書く

効果
使いやすさ
ドキュメント
互換性

レビューなし

統計データ

インストール数2.8K
評価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 知識カード
アンケート