msw-avatar について
msw-avatarは、MapleStory Worldsのコスチュームとアニメーション設定を扱うガイドです。モデル、マップ、スクリプトを編集する開発者向けに、装備スロットの対応、エディターの更新、AvatarRendererComponentによる色や透明度の調整を説明します。
主な機能
- 01モデルやマップ内のコスチューム設定編集
- 0217個の装備スロットとカテゴリの対応
- 03ロングコートや武器のスロット占有規則
- 04RUIDによるアバターアイテム指定
- 05レンダラーによる色と透明度の設定
活用シーン
- 01DefaultPlayerの標準コスチューム設定
- 02マップ内NPCの装備設定
- 03ゲーム内のアバター点滅やフェード実装
MCP 対応
ワークスペースファイルを編集した後、msw-maker-mcp MCPサーバーのrefreshツールを呼び出してエディターに反映します。
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ msw-git/ msw-ai-coding-plugins-official --skill msw-avatarインストール後、Claude Code で/msw-avatarを入力して読み込まれたか確認します。
詳細
MSW アバター(コスチューム・アニメーション)
アバターは、次の 2 つの軸で管理されます。
- コスチューム(外見):
MOD.Core.CostumeManagerComponent— 装備するアイテム(17 スロット)を指定します。 - アニメーション(動き):
AvatarStateAnimationComponent+AvatarRendererComponent— 再生する状態クリップ(デフォルトの 14 状態 + カスタムアクション)を指定します。
ワークスペース内のファイルを直接編集し、続いて msw-maker-mcp の refresh ツールを呼び出して、エディターに変更を反映させてください。
このドキュメントでは、まずコスチューム(ファイル編集ベース)を説明し、最後にアニメーション(スクリプトベース)を説明します。
ワークスペースのパスのルール:マップは
./map/、UI は./ui/、スクリプトなどのアセットは./RootDesk/MyDesk/、DefaultPlayer/Player などのグローバルモデルは./Global/です。
対象ごとの編集場所
| 対象 | 編集するファイル | 備考 |
|---|---|---|
| DefaultPlayer | ./Global/DefaultPlayer.model | Values 配列を通じて CostumeManagerComponent のプロパティを上書きします |
| Player(ベース) | ./Global/Player.model | 通常、コスチュームのデフォルト値はここではなく、DefaultPlayer.model で上書きします |
| マップに配置されたエンティティ(NPC、モンスターなど) | ./map/{mapName}.map | そのエンティティの jsonString.@components 内にある CostumeManagerComponent ブロック |
| カスタムモデルのみを参照するエンティティ | 対応する .model(例:./RootDesk/MyDesk/ 以下) | マップ内にインラインコンポーネントがなく、エンティティが modelId のみでバインドされている場合は、モデル側を編集します |
読み取り(get と同等):上記のファイルを読み、CostumeManagerComponent 関連のフィールド/Values エントリーを確認します。Maker MCP に接続されている場合は、実行時/エディターのスナップショットを確認する補助として get_component を使用できます(msw-maker-mcp スキルを参照)。
適用(set と同等):値をファイルに書き込み、その後 refresh を呼び出します。
変更の適用:MCP refresh
ファイルを保存した後、Maker とその表示状態を同期するため、必ず msw-maker-mcp サーバーの refresh ツールを呼び出してください(msw-maker-mcp スキルのツール一覧を参照)。
RUID(リソース固有 ID)
コスチュームに書き込む文字列は、アバターアイテムの RUID(通常は 32 文字の 16 進文字列)です。
- RUID を推測または捏造しないでください。
msw-searchスキルで検索してください。アバター RUID のワークフロー(デフォルトのボディ/ヘッド、アイテムの詳細、レンダー構成)については../msw-search/references/resource/avatar.mdを、一般的な検索については../msw-search/references/resource/search.mdを、単一アイテムの詳細については../msw-search/references/resource/detail.mdを参照してください。 - スクリプト API の
SetEquip(MapleAvatarItemCategory, itemRUID)と、エディター/モデルに保存される値は、同じ RUID 文字列です。 Custom*Equipスロットが受け付けるのは、プレーンな Guid のみです。thumbnail://<ruid>を含む、プレフィックス付きの形式はすべて無視され、スロットは未装備のままになります(エラーも警告も発生しません)。msw-searchが返す RUID はすでにプレーンな Guid です。スキームを付加しないでください。サムネイル/アイコンに関する一般的なルールについては、msw-sprite-ruidスキルを参照してください。
CostumeManagerComponent の概要
アバターを使用するエンティティ(プレイヤー、NPC など)にアタッチされます。装備スロットは 17 個の文字列プロパティ Custom*Equip として公開され、スクリプトからは MapleAvatarItemCategory 列挙型を使用して GetEquip/SetEquip でアクセスします。
その他の同期プロパティ
| プロパティ | 型 | 説明 |
|---|---|---|
| UseCustomEquipOnly | boolean(デフォルト false) | true の場合、ユーザーアカウントのデフォルトコスチュームは無視され、スクリプト/モデルで割り当てられたコスチュームのみが使用されます。ワールド内で外見を固定したい場合に重要です。 |
| DefaultEquipUserId | string | 指定したユーザーの装備を複製し、その上にカスタム装備を適用します。現在オンラインでないユーザーも指定できます。そのユーザーが後で装備を変更すると、反映される外見が変わる場合があります。 |
| EquippedItems | 読み取り専用 | 実行時の実際の装備情報です。スクリプトから変更できません。 |
17 スロット ↔ プロパティ ↔ MapleAvatarItemCategory
CostumeManagerComponent の 17 個の装備文字列フィールドは、以下のようにエンジンの列挙型 MapleAvatarItemCategory に対応しています。(列挙型の定義については、Environment/NativeScripts/Enum/MapleAvatarItemCategory.d.mlua を参照してください。)
| # | コンポーネントプロパティ(文字列 RUID) | MapleAvatarItemCategory | 備考 |
|---|---|---|---|
| 1 | CustomBodyEquip | Body (1) | 肌/体 |
| 2 | CustomHairEquip | Hair (3) | 髪 |
| 3 | CustomFaceEquip | Face (4) | 顔/顔の形 |
| 4 | CustomCapEquip | Cap (5) | 帽子 |
| 5 | CustomCapeEquip | Cape (6) | マント |
| 6 | CustomCoatEquip | Coat (7) | コート(上半身) |
| 7 | CustomLongcoatEquip | Longcoat (9) | ロングコート — 上半身と下半身の両方のスロットを占有するアイテムクラス |
| 8 | CustomPantsEquip | Pants (10) | 下半身 |
| 9 | CustomGloveEquip | Glove (8) | 手袋 |
| 10 | CustomShoesEquip | Shoes (12) | 靴 |
| 11 | CustomOneHandedWeaponEquip | OneHandedWeapon (13) | 片手武器 |
| 12 | CustomTwoHandedWeaponEquip | TwoHandedWeapon (14) | 両手武器 — 片手武器スロットとサブ武器スロットの両方を占有する |
| 13 | CustomSubWeaponEquip | SubWeapon (15) | サブ武器 |
| 14 | CustomFaceAccessoryEquip | FaceAccessory (16) | 顔アクセサリー |
| 15 | CustomEyeAccessoryEquip | EyeAccessory (17) | 目のアクセサリー |
| 16 | CustomEarAccessoryEquip | EarAccessory (18) | 耳のアクセサリー |
| 17 | CustomEarEquip | Ear (19) | 耳(体の部位) |
17 個のフィールドに直接対応しない列挙型の値
| MapleAvatarItemCategory | 説明 |
|---|---|
| Head (2) | 「装備としては使用しない」ものに近く、体の色に合わせて自動的に処理されます。CustomHeadEquip フィールドはありません。 |
| Invalid (0) | エラー値/未定義値の検出に使用されます。 |
| Shield (11) | 列挙型のコメントによると、SubWeapon スロットを使用します。保存時には、CustomSubWeaponEquip と同時に装備できないものとして扱うのが安全です。 |
排他/スロット占有ルール(必ず理解してください)
-
Longcoat ↔ Coat + Pants
Longcoat は、Coat スロットと Pants スロットの両方を占有するよう設計されています。ロングコートを装備する場合は、ロングコートの RUID をCustomLongcoatEquipに設定し、coat/pants との組み合わせを論理的に解決してください。通常、ロングコートを使用する場合は coat/pants を空にするか、見た目が競合しないようにします。 -
両手武器 ↔ 片手武器 + サブ武器
TwoHandedWeapon は、片手武器スロットとサブ武器スロットの両方を使用します。両手武器を使用する場合は、CustomTwoHandedWeaponEquipを中心に設定し、片手武器/サブ武器にも値が設定されていないことを確認してください。二重装備を避けてください。 -
Shield ↔ Sub-weapon
Shield はサブ武器スロットを使用します。別のサブ武器がCustomSubWeaponEquipと共存することを想定しないでください。 -
空文字列 = 装備解除
スクリプトでのSetEquip(category, "")と同様に、ファイル内でフィールドを""にすると、そのスロットの装備が解除されます。
DefaultPlayer.model — 衣装を Values に設定する
./Global/DefaultPlayer.model の ContentProto.Json.Values 配列にエントリーを追加または変更します。
- TargetType:
"MOD.Core.CostumeManagerComponent" - Name: 上記の表にあるプロパティ名(例:
CustomCapEquip、UseCustomEquipOnly) - ValueType:
DefaultPlayer.modelの既存のValuesエントリーと同じ形式にします。文字列にはSystem.String, mscorlib, ...、ブール値にはSystem.Boolean, mscorlib, ...を使用します。 - Value: RUID 文字列または
true/false
同じ (TargetType, Name) がすでに存在する場合は、そのエントリーのみを更新します。存在しない場合は、配列に新しいオブジェクトを追加します。
文字列スロットの例(構造のみ。検索で RUID を置き換えてください)
{
"TargetType": "MOD.Core.CostumeManagerComponent",
"Name": "CustomCapEquip",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "PUT_32_HEX_RUID_HERE"
}
UseCustomEquipOnly の例
{
"TargetType": "MOD.Core.CostumeManagerComponent",
"Name": "UseCustomEquipOnly",
"ValueType": {
"$type": "MODNativeType",
"type": "System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": true
}
マップエンティティ — .map ファイルを編集する
./map/ 内の対象マップを開き、エンティティレコードを編集します。
ContentProto.Entities配列で、名前/パス/ID を使って対象エンティティを見つけます。jsonString["@components"]で、"@type": "MOD.Core.CostumeManagerComponent"を持つオブジェクトを見つけます。- そのオブジェクトの
Custom*Equip、UseCustomEquipOnly、DefaultEquipUserIdなどを直接編集します。 MOD.Core.CostumeManagerComponentがcomponentNames文字列リストにも記載されていること、およびこのリストがコンポーネント配列と一致していることを確認します。
マップがバイナリのみの形式を使用している場合、編集ツールはワークスペースのポリシーによって異なることがあります。ファイルを JSON テキストとして開ける場合は、上記の構造に従ってください。
GET /v3/avatars の結果をスロットに対応付ける
GET /v3/avatars が返すアイテムの category フィールドを Custom*Equip プロパティに対応付けます。検索方法については、msw-search スキル → references/resource/avatar.md を参照してください。
API category | Custom*Equip プロパティ | MapleAvatarItemCategory |
|---|---|---|
body | CustomBodyEquip | Body (1) |
hair | CustomHairEquip | Hair (3) |
face | CustomFaceEquip | Face (4) |
faceaccessory | CustomFaceAccessoryEquip | FaceAccessory (16) |
eyeaccessory | CustomEyeAccessoryEquip | EyeAccessory (17) |
earaccessory | CustomEarAccessoryEquip | EarAccessory (18) |
cap | CustomCapEquip | Cap (5) |
cape | CustomCapeEquip | Cape (6) |
longcoat | CustomLongcoatEquip | Longcoat (9) |
coat | CustomCoatEquip | Coat (7) |
pants | CustomPantsEquip | Pants (10) |
glove | CustomGloveEquip | Glove (8) |
shoes | CustomShoesEquip | Shoes (12) |
weapon | CustomOneHandedWeaponEquip | OneHandedWeapon (13) |
twohandweapon | CustomTwoHandedWeaponEquip | TwoHandedWeapon (14) |
subweapon | CustomSubWeaponEquip | SubWeapon (15) |
shield | CustomSubWeaponEquip | Shield (11) — SubWeapon スロットと共有 |
アバターリソース検索リファレンス
msw-searchスキル →references/resource/avatar.md:GET /v3/avatars(衣装検索)、デフォルトの体/頭部、GET /v3/avatars/{ruid}、レンダリング構成などの詳細。- カテゴリ検索と詳細 API を組み合わせて、装備の RUID を収集します。
アバターの色合い/アルファ値(視覚的な色変更)
AvatarRendererComponent がアタッチされているすべてのエンティティ(DefaultPlayer、アバターを持つ NPC、モンスター)に色や透明度の効果(被弾時のフラッシュ、ゴーストのフェード、パレットの切り替えなど)を適用する場合は、レンダラー独自のメソッドを使用してください。アバターエンティティでは、SpriteRendererComponent.Color と FlipX は何もせずに終了します(isvalid(spriteRenderer) が true を返す場合でも、アバターレンダラーがスプライトレンダラーの出力を上書きします)。
| メソッド | シグネチャ | 備考 |
|---|---|---|
SetColor | (r, g, b, a [, targetUserId]) | r/g/b/a は 0~1 の浮動小数点数です。アバター全体に色を付けます。クライアント ExecSpace。 |
SetAlpha | (a [, targetUserId]) | 0~1 の浮動小数点数です。透明度を個別に設定します。クライアント ExecSpace。 |
SetAvatarPartColor | (category, r, g, b, a [, targetUserId]) | MapleAvatarItemCategory のスロットを1つだけ着色します。 |
@ExecSpace("Client")
method void FlashRed()
local renderer = self.Entity.AvatarRendererComponent
if isvalid(renderer) == false then return end
renderer:SetColor(1.0, 0.25, 0.25, 1.0) -- red flash
wait(0.1)
renderer:SetColor(1.0, 1.0, 1.0, 1.0) -- restore
end
アバターの向き/反転には、スプライトレベルの反転を書き換えるのではなく、MovementComponent の向き API(例:MoveDirection)を使用してください。理由は同じく、書き換えても何も起こらないためです。
アニメーション — 全体構造
アバターのアニメーションは3層のパイプラインを通じて処理されます。1つの層だけを変更すると、ほかの層が変更を上書きし、意図しない動きが発生します。
[1] Input / game logic
│ PlayerControllerComponent · scripts
▼
[2] StateComponent ──── StateChangeEvent ────▶ AvatarStateAnimationComponent
(e.g. "ATTACK") (CurrentStateName) (StateToAvatarBodyActionSheet
or ActionSheet lookup)
│
▼
[3] AvatarRendererComponent ◀── BodyActionStateChange / ActionStateChanged ── body entity
(actual sprite playback)
主な違いは次のとおりです。
| 用語 | 形式 | 例 |
|---|---|---|
| State key | 大文字 | IDLE、MOVE、ATTACK、HIT、CROUCH、FALL、JUMP、CLIMB、LADDER、DEAD、SIT、ATTACK_WAIT |
| AvatarBodyActionStateName(Value側) | 小文字 | stand、walk、attack、hit、crouch、fall、rope、ladder、dead、sit、alert、fly、blink、heal |
| MapleAvatarBodyActionState(enum) | パスカルケース | Stand、Walk、Attack、Hit、Crouch、Fall、Sit、Rope、Ladder、Dead、Blink、Fly、Heal、Alert、Invalid |
| CoreActionName / PartsActionName(実際のスプライトアクション ID) | 小文字+数字 | stand1、walk1、swingO1、shoot1、prone、jump、alert など |
よくある混同:「attack」はStateではありません。Stateは大文字の
ATTACK、マッピング Value は小文字のattack(=MapleAvatarBodyActionState.Attack)で、その Value は武器に応じてswingO1/shoot1などのスプライトアクション ID に解決されます。スクリプトから State を切り替える呼び出しはStateComponent:ChangeState("ATTACK")(大文字の文字列)です。「Attack」や「attack」では何も起こらず(エラーも発生せず、State が変わらないだけです)、一致しません。
AvatarStateAnimationComponent — State とモーションのマッピング
MOD.Core.AvatarStateAnimationComponent は両方のシステムを保持します。
| プロパティ | 使用条件 | 型 | 備考 |
|---|---|---|---|
IsLegacy | 2つのシステムを切り替えるとき | boolean(デフォルト false) | true = ActionSheet を使用、false = StateToAvatarBodyActionSheet を使用 |
ActionSheet | IsLegacy = true(旧方式) | SyncDictionary<string, string> | State→AnimationKey。例:"ATTACK" → "attack" |
StateToAvatarBodyActionSheet | IsLegacy = false(新方式、デフォルト) | SyncDictionary<string, AvatarBodyActionElement> | 例:"ATTACK" → {AvatarBodyActionStateName="attack", PlayRate=1.33} |
StateToAvatarBodyActionSheet のデフォルトマッピング(IsLegacy=false のときのデフォルトキー11個)
| Key(State) | AvatarBodyActionStateName | PlayRate | トリガー条件(PlayerControllerComponent が存在する場合) |
|---|---|---|---|
IDLE | stand | 1.0 | 入力なし |
MOVE | walk | 1.68 | 左右への移動 |
ATTACK | attack | 1.33 | Left Ctrl(攻撃アクション) |
HIT | hit | 1.0 | HitComponent による被弾処理 |
CROUCH | crouch | 1.0 | 下矢印キー |
FALL | fall | 1.0 | 空中で落下中 |
JUMP | fall | 1.0 | Space(ジャンプアクション) |
CLIMB | rope | 1.0 | ロープに入る |
LADDER | ladder | 1.0 | はしごに入る |
DEAD | dead | 1.0 | 死亡 |
SIT | sit | 1.0 | C(座るアクション) |
State のキーは大文字で、
AvatarBodyActionStateNameの値は小文字です。
デフォルト解決テーブル:MapleAvatarBodyActionState → 実際のアクションID
AvatarBodyActionStateName 文字列("attack"、"stand" など)は enum の MapleAvatarBodyActionState にキャストされ、エンジンによって以下のデフォルトに解決され、ActionStateChangedEvent が合成されます。
| MapleAvatarBodyActionState | CoreActionName | PartsActionName | PlayRate | PlayType |
|---|---|---|---|---|
| Stand | stand1 / stand2 | 同じ | 1 | ZigzagLoop |
| Walk | walk1 / walk2 | 同じ | 1 | Loop |
| Attack | alert(武器がない場合のデフォルト) | alert | 1 | Loop |
| Crouch | prone | prone | 1 | Loop |
| Fall | jump | jump | 1 | Loop |
| Sit | sit | sit | 1 | Loop |
| Rope | rope | rope | 1 | Loop |
| Ladder | ladder | ladder | 1 | Loop |
| Dead | dead | stand1 | 1 | Loop |
| Blink | blink | blink | 1 | Loop |
| Fly | fly | fly | 1 | Loop |
| Hit | alert | alert | 1 | ZigzagLoop |
| Alert | alert | alert | 1 | ZigzagLoop |
| Heal | heal | heal | 1 | Loop |
武器を装備すると、
Attackは武器タイプに一致するスプライトアクションIDに自動的に置き換わります(次の表を参照)。片手剣を持つと剣を振り、弓を持つと弓を射ます。
武器ごとの attack 解決 — 候補となるスプライトアクションID
ATTACK がトリガーされると、エンジンは装備中の武器(MapleAvatarItemCategory)を確認し、以下のアクションIDのいずれかを再生します。
| 武器クラス | 候補となる CoreActionName / PartsActionName |
|---|---|
片手剣/短剣(OneHandedWeapon) | swingO1、swingO2、swingO3、stabO1、stabO2 |
両手剣/ハンマー(TwoHandedWeapon) | swingT1、swingT2、swingT3、stabT1、stabT2 |
弓(TwoHandedWeapon、弓系統) | swingT1、swingT3、shoot1 |
| 杖/ワンド | swingO1、swingO2、swingO3 |
| 武器なし(デフォルトのボディ) | 専用の攻撃クリップはなく、alert などで表示 |
同じクラスでも、使用されるアクションIDのセットはアイテムのメタデータによって異なる場合があります。上の表には、SDK ガイド(
_ActionNameLogic)で使用される代表的な候補を示しています。
PlayerControllerComponent と State の自動追加
MOD.Core.PlayerControllerComponent がプレイヤーエンティティにアタッチされると、以下の State が StateComponent に自動的に追加され、キー入力に応じて自動的に遷移します。
MOVE、CLIMB、LADDER、CROUCH、JUMP、FALL、ATTACK、ATTACK_WAIT、SIT
そのため、DefaultPlayer が Ctrl を押すと ATTACK state が自動的に有効になり、マッピングされた攻撃時のボディモーション(武器ごとの剣/弓/杖の振り)が自動的に再生されます。追加のスクリプトがなくても、剣を振ります。
自動再生と手動 ActionStateChangedEvent の競合(★ よくある落とし穴)
症状:スクリプトから ActionStateChangedEvent を使って shoot1 などのカスタムアクションを送信しても、**剣の振り(または武器のデフォルト攻撃)**が再生され続ける、あるいはカスタムアクションが1フレームだけ表示され、すぐに上書きされます。
原因:ATTACK が有効な間、AvatarStateAnimationComponent はマッピングされた attack のボディモーションを継続的に再送信します。1回限りのイベントはすぐに上書きされます。
解決方法
| 方法 | 手順 | 使用する状況 |
|---|---|---|
| A. マッピングを削除する | asac:RemoveActionSheet("ATTACK") を呼び出して、キーを削除します。その後、ActionStateChangedEvent でアクションを直接再生します。 | カスタムアクション(弓の射撃、呪文の詠唱など)で攻撃モーションを完全に置き換えたい場合 |
| B. マッピングを変更する | asac:SetActionSheet("ATTACK", "<Body Action name>") を呼び出すか、StateToAvatarBodyActionSheet["ATTACK"] を別の MapleAvatarBodyActionState に変更します。 | 別の組み込み状態アニメーションに切り替えたい場合(例:ATTACK→回復) |
| C. 強制的にリセットする | needResetAction=true を指定して BodyActionStateChangeEvent を送信します。 | 同じ状態を再開したい場合 |
| D. 武器を交換する | CostumeManagerComponent の武器スロットを弓の RUIDに置き換えます。 | 攻撃モーションの武器バリエーションだけを変更したい場合(最も直感的な方法) |
方法 A の例 — 剣の振りを止めて、弓の射撃に置き換える
@Component
script PlayerAttack extends AttackComponent
@HideFromInspector
property any Shape = nil
@ExecSpace("ServerOnly")
method void OnBeginPlay()
self.Shape = BoxShape(Vector2.zero, Vector2.one, 0)
-- Remove the attack(=sword swing) mapping that the engine auto-plays during ATTACK
local asac = self.Entity.AvatarStateAnimationComponent
if isvalid(asac) then
asac:RemoveActionSheet("ATTACK")
end
end
@ExecSpace("ServerOnly")
method void AttackNormal()
-- ... damage resolution ...
self:PlayShootAnimation()
end
@ExecSpace("Client")
method void PlayShootAnimation()
local body = self.Entity.AvatarRendererComponent:GetBodyEntity()
if isvalid(body) == false then return end
local event = ActionStateChangedEvent()
event.CoreActionName = "shoot1"
event.PartsActionName = "shoot1"
event.PlayType = SpriteAnimClipPlayType.Onetime
body:SendEvent(event)
end
@ExecSpace("ServerOnly")
@EventSender("Self")
handler HandlePlayerActionEvent(PlayerActionEvent event)
if event.ActionName == "Attack" then
self:AttackNormal()
end
end
end
変更を同期するには、
RemoveActionSheet/SetActionSheetをサーバー上で呼び出す必要があります。これはStateToAvatarBodyActionSheetが@Syncプロパティであるためです。
方法 D の例 — CostumeManagerComponent で弓を装備する
./Global/DefaultPlayer.model の Values に弓の RUID を追加すると、マッピングを変更せずに、エンジンが ATTACK 中に自動的に shoot1 モーションを選択します。
{
"TargetType": "MOD.Core.CostumeManagerComponent",
"Name": "CustomTwoHandedWeaponEquip",
"ValueType": {
"$type": "MODNativeType",
"type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": "<bow RUID — obtain via msw-search>"
},
{
"TargetType": "MOD.Core.CostumeManagerComponent",
"Name": "UseCustomEquipOnly",
"ValueType": {
"$type": "MODNativeType",
"type": "System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
},
"Value": true
}
14種類の状態とカスタムアクション — 自分でトリガーするのはどちら?
AvatarStateAnimationComponent / MapleAvatarBodyActionState がすでに認識している14種類のボディモーションと、それ以外の任意のスプライトアクション ID(例:swingO2、shoot1、dance、cast1)は、異なる経路で処理されます。
エンジンが自動的に処理する14種類のボディモーション(= MapleAvatarBodyActionState のメンバー)
これらは StateToAvatarBodyActionSheet / ActionSheet の値として使用できる名前です。状態をこれらのいずれかにマッピングすると、自動的に再生されます。
| ボディモーション名 | Enum | 意味 |
|---|---|---|
stand | Stand | 待機 |
walk | Walk | 移動 |
attack | Attack | 攻撃(スプライト ID は武器によって自動的に選択されます) |
hit | Hit | 被弾 |
crouch | Crouch | しゃがみ |
fall | Fall | 落下 |
rope | Rope | ロープにつかまる |
ladder | Ladder | はしご |
dead | Dead | 死亡 |
sit | Sit | 座る |
heal | Heal | 回復 |
alert | Alert | 警戒 |
fly | Fly | 飛行 |
blink | Blink | まばたき |
その他 — ActionStateChangedEvent で直接再生する
14 個の enum メンバー以外の任意のスプライトアクション ID(例:shoot1、swingO2、cast1、throw1、dance、cheer)には、以下の手順を使用します。
再生手順
- エンティティの
AvatarRendererComponentからGetBodyEntity()を呼び出し、アバターのボディエンティティを取得します。アニメーションイベントの送信先はアバターのルートではなく、ボディエンティティです。 ActionStateChangedEventを作成し、そのフィールドに値を設定します。body:SendEvent(event)でボディエンティティに送信します。- アニメーションは各クライアントで表示されればよいため、通常は
@ExecSpace("Client")のスコープで実行します。ゲームロジック(ダメージ、飛翔体の生成など)はServerOnly側に置きます。
ActionStateChangedEvent の主なフィールド(コンストラクター:ActionStateChangedEvent(coreActionName, partsActionName, playRate=1, playType=Loop, startFrameIndex=0, endFrameIndex=2147483647))
| フィールド | 型 | デフォルト値 | 説明 |
|---|---|---|---|
CoreActionName | string | "" | コアパーツ(ボディ)で再生するアニメーション ID です。必須(例:"shoot1"、"swingO1") |
PartsActionName | string | "" | サブパーツで再生するアニメーション ID です。必須 — 通常は CoreActionName と同じ値です |
PlayRate | float | 1 | 再生速度の倍率(1.0 = 通常速度、1.5 = 1.5 倍) |
PlayType | SpriteAnimClipPlayType | Loop | Onetime / Loop / ZigzagLoop。1 回限りのアクションには Onetime を使用します |
StartFrameIndex | int32 | 0 | 開始フレーム(負の値は 0 に補正されます) |
EndFrameIndex | int32 | 2147483647 | 終了フレーム(全フレーム数を超える場合は補正されます) |
SpriteAnimClipPlayType:
| 値 | 意味 |
|---|---|
Onetime | 1 回再生して停止します |
Loop | 0 から終端までを繰り返します |
ZigzagLoop | 0 から終端まで再生した後、0 に戻る動作を繰り返します |
BodyActionStateChangeEvent — 組み込みの 14 種類の状態に対応する高レベルイベント
MapleAvatarBodyActionState の enum 値を直接指定します。武器ごとのアクション ID を覚える必要がなく、needResetAction=true を指定すると、同じ状態を強制的に再スタートできます。
ActionStateChangedEvent と異なり、SendEvent の送信先はボディエンティティではなく、**アバターのルートエンティティ(self.Entity)**です。
local event = BodyActionStateChangeEvent()
event.ActionState = MapleAvatarBodyActionState.Fly
event.needResetAction = true
event.startFrameIndex = 1
event.endFrameIndex = 2
self.Entity:SendEvent(event)
-- Internally converted to ActionStateChangedEvent("fly", "fly", 1, Loop, 1, 2) and dispatched
| フィールド | 説明 |
|---|---|
ActionState | MapleAvatarBodyActionState の enum(Stand/Walk/Attack/Hit/...) |
needResetAction | true の場合、すでに再生中の状態であっても最初から強制的に再スタートします |
playRate / startFrameIndex / endFrameIndex | ActionStateChangedEvent と同じです |
使い分け
- 任意のスプライトアクション ID(
shoot1、swingO2、danceなど)→ActionStateChangedEvent(ボディエンティティに送信) - 14 種類の enum 状態のいずれか(Stand/Walk/Attack/...)→
BodyActionStateChangeEvent(ルートエンティティに送信)
例 — 矢を放つ(shoot)アニメーションの再生
一般的なパターンは、サーバーで攻撃入力を受けて飛翔体を生成し、クライアントで shoot1 アクションを再生する方法です。
@Component
script PlayerAttack extends Component
property string ArrowModelId = "model://bc9f9d0e-2b5d-4b3b-a115-d857f85e9145"
@HideFromInspector
property integer ArrowCount = 0
@ExecSpace("ServerOnly")
method void FireArrow()
if self.ArrowModelId == nil or self.ArrowModelId == "" then
log_warning("PlayerAttack: ArrowModelId is not set")
return
end
local playerController = self.Entity.PlayerControllerComponent
local transform = self.Entity.TransformComponent
if isvalid(playerController) == false or isvalid(transform) == false then
return
end
local dirX = playerController.LookDirectionX
if dirX == 0 then dirX = 1 end
local worldPos = transform.WorldPosition
local spawnPos = Vector3(worldPos.x + 0.35 * dirX, worldPos.y + 0.35, worldPos.z)
self.ArrowCount += 1
local arrowName = "PlayerArrow_" .. tostring(self.ArrowCount)
local parent = self.Entity.CurrentMap
if isvalid(parent) == false then
parent = self.Entity.Parent
end
local arrow = _SpawnService:SpawnByModelId(self.ArrowModelId, arrowName, spawnPos, parent)
if isvalid(arrow) == false then
log_warning("PlayerAttack: failed to spawn arrow")
return
end
local arrowProj = arrow.ArrowProjectile
if isvalid(arrowProj) then
arrowProj:Fire(Vector2(dirX, 0))
end
self:PlayShootAnimation()
end
@ExecSpace("Client")
method void PlayShootAnimation()
local avatarRenderer = self.Entity.AvatarRendererComponent
if isvalid(avatarRenderer) == false then
return
end
local body = avatarRenderer:GetBodyEntity()
if isvalid(body) == false then
return
end
local event = ActionStateChangedEvent()
event.CoreActionName = "shoot1"
event.PartsActionName = "shoot1"
event.PlayRate = 1.5
event.PlayType = SpriteAnimClipPlayType.Onetime
body:SendEvent(event)
end
@ExecSpace("ServerOnly")
@EventSender("Self")
handler HandlePlayerActionEvent(PlayerActionEvent event)
local ActionName = event.ActionName
if ActionName == "Attack" then
self:FireArrow()
end
end
end
判断の流れ
- 再生したいモーションは、組み込みの 14 種類の状態(
stand、walk、attack、hit、crouch、fall、rope、ladder、dead、sit、heal、alert、fly、blink)のいずれかですか?- はい →
AvatarStateAnimationComponentの対応するスロットにクリップを割り当てるだけです。スクリプトは不要です。 - いいえ → 以下に進みます。
- はい →
- カスタムアクション(例:
shoot1、cast1、dance)では、ActionStateChangedEventを作成し、AvatarRendererComponent:GetBodyEntity()が返すボディエンティティにSendEventで送信します。 - 実行空間を分けます。入力処理とダメージ判定はサーバー側(
ServerOnly)、**アニメーション再生はクライアント側(Client)**で行います。
よくある間違い
- State キーと AvatarBodyActionStateName(=列挙型)を混同する。 State キーは大文字(
ATTACK)、マッピングの値は小文字(attack)です。StateToAvatarBodyActionSheetのキーと値を入れ替えると、マッピングはエラーなく機能しなくなります。 - 自動再生を無効にせず、ActionStateChangedEvent だけを送信する。 Ctrl キーを押すと
ATTACK状態が自動的に有効になり、マッピングされた攻撃モーションがイベントのモーションを即座に上書きします。カスタムの攻撃モーションを使うには、RemoveActionSheet("ATTACK")またはSetActionSheet("ATTACK", "<desired motion>")でマッピングを必ず解除してください。 ActionStateChangedEventの SendEvent 先を間違える。AvatarRendererComponent:GetBodyEntity()が返すボディエンティティに送信する必要があります。self.Entity(アバターのルート)やコンポーネントに送信しても再生されません。(一方、BodyActionStateChangeEventの送信先はルートエンティティです。)- DefaultPlayer 型のエンティティ(
PlayerControllerComponent+StateComponent+AvatarStateAnimationComponentが実行中)のAvatarBodyActionSelectorComponent.ActionStateを直接書き換える。 コントローラーは毎ティック、接地状態・移動・入力を再評価し、遷移時にChangeStateを呼び出します。その結果発生するStateChangeEvent → BodyActionStateChangeEventがセレクターを再描画し、書き込みは気づかないうちに破棄されます。代わりにStateComponent:ChangeState("UPPERCASE_KEY")を使ってください。セレクターへの直接書き込みが維持されるのは、このコントローラースタックがない NPC/モンスターだけです。 AvatarStateAnimationComponentに任意の状態名を設定しようとする。 14 個の列挙型メンバー(MapleAvatarBodyActionState)以外の値(例:shoot、cast、dance)は無視されます。カスタム ID はActionStateChangedEvent経由で渡す必要があります。PartsActionNameを設定し忘れる。CoreActionNameだけを設定すると、サブパーツ(武器、帽子、マントなど)が解決されず、上半身は動いているのに武器が固まったままになることがあります。CoreActionNameと同じ値を使ってください。PlayTypeを未設定のままにする。 デフォルトはLoopなので、ワンショットアクションが永遠に繰り返されます。ワンショットアクションでは、SpriteAnimClipPlayType.Onetimeを明示的に設定してください。- クライアントで
RemoveActionSheet/SetActionSheetを呼び出す。StateToAvatarBodyActionSheetは@Syncプロパティなので、すべてのクライアントに反映するにはサーバーで呼び出す必要があります。 - サーバーとクライアントの実行環境を分け忘れる。 ゲームロジック(ダメージ、投射物)=
ServerOnly、アニメーション再生=Clientです。これらを同じ場所で混在させると、クライアントごとに再生が重複したり、表示されなくなったりします。 - 弓を装備せずに弓のモーションを期待する。
shoot1を再生するとボディは弓を構えるポーズになりますが、CustomTwoHandedWeaponEquipに弓の RUID が設定されていない場合、手には弓が表示されません。自然な見た目にするには、モーションと武器を一緒に設定してください。
関連スキル
| スキル | 目的 |
|---|---|
| msw-defaultplayer | ./Global/DefaultPlayer.model / Player.model の構造と Values のルール |
| msw-search | RUID の検索、references/resource/avatar.md |
| msw-maker-mcp | refresh、必要に応じて get_component / set_property(実行時の調整と組み合わせる場合) |
まとめチェックリスト
コスチューム
- リソース検索/アバターリファレンスのドキュメントで RUID を取得します。
- DefaultPlayer / Player →
./Global/*.model(またはベースモデルの定義)のValues。 - マップエンティティ →
./map/*.map内の対象エンティティの@components。 - ロングコート/両手武器/盾 ↔ サブ武器の併用制限を守ります。
UseCustomEquipOnlyでユーザーのアカウント既定コスチュームを無視するかどうかを決めます。- 保存後、
msw-maker-mcp→refreshを呼び出します。
アニメーション
- State キー(大文字)とボディモーション名(小文字)を区別します。 形式:
StateToAvatarBodyActionSheet["ATTACK"] = AvatarBodyActionElement("attack", 1.33). - 希望するモーションが14 種類の列挙型ボディモーション(
stand·walk·attack·hit·crouch·fall·rope·ladder·dead·sit·heal·alert·fly·blink)に含まれている場合は、マッピングを定義すれば完了です。 - その他のアクション ID(
shoot1、swingT3、danceなど)の場合は、ActionStateChangedEventを作成し、AvatarRendererComponent:GetBodyEntity()の戻り値に対してSendEventします。列挙型内の State を再生し直すには、BodyActionStateChangeEventとルートエンティティを使用します。 - 4 つすべてのフィールド(
CoreActionName/PartsActionName/PlayRate/PlayType)に値を設定し、ワンショットアクションにはSpriteAnimClipPlayType.Onetimeを使用します。 - 自動 State 遷移との競合を確認します。 PlayerControllerComponent を持つエンティティでは、入力に応じて
MOVE/ATTACK/JUMP/...が自動的に発火します。カスタム攻撃を使用するには、競合するキーをRemoveActionSheetまたはSetActionSheetで削除します(サーバーで呼び出します)。 - ゲームロジックは
@ExecSpace("ServerOnly")に、アニメーション再生は@ExecSpace("Client")に分けます。 - 攻撃モーションの武器バリエーションだけを変更する場合は、
CostumeManagerComponentの武器スロット RUID を入れ替える方法が最も簡単です(Strategy D)。