msw-avatar

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

MSW 아바타 (코스튬 · 애니메이션)

MSW 角色(Avatar)(服装 · 动画)

아바타는 두 축으로 관리한다.
  • 코스튬(외형):
    MOD.Core.CostumeManagerComponent
    — 어떤 장비를 착용하는가(17슬롯).
  • 애니메이션(동작):
    AvatarStateAnimationComponent
    +
    AvatarRendererComponent
    — 어떤 상태 클립을 재생하는가(기본 14상태 + 커스텀 액션).
예전 Maker RPC(
get_costume
/
set_costume
)는 사용하지 않는다. 워크스페이스 파일을 직접 편집하고, 변경이 에디터에 반영되도록
msw-maker-mcp
refresh
도구
를 호출한다.
아래 문서는 먼저 코스튬(편집 기반)을, 마지막에 애니메이션(스크립트 기반)을 다룬다.
워크스페이스 경로 규칙: 맵
./map/
, UI
./ui/
, 스크립트·기타 에셋
./RootDesk/MyDesk/
, DefaultPlayer·Player 등 글로벌 모델
./Global/

角色通过两大维度进行管理:
  • 服装(外观)
    MOD.Core.CostumeManagerComponent
    —— 穿戴的装备(17个插槽)。
  • 动画(动作)
    AvatarStateAnimationComponent
    +
    AvatarRendererComponent
    —— 播放的状态片段(默认14种状态 + 自定义动作)。
不再使用旧版Maker RPC(
get_costume
/
set_costume
)。需直接编辑工作区文件,并调用**
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/{맵명}.map
해당 엔티티의
jsonString.@components
CostumeManagerComponent
블록
커스텀 모델만 참조하는 엔티티해당
.model
(예:
./RootDesk/MyDesk/
등)
맵에 인라인 컴포넌트가 없고
modelId
로만 묶인 경우 모델 쪽을 수정
조회(get에 해당): 위 파일을 읽어
CostumeManagerComponent
관련 필드·
Values
항목을 확인한다. Maker MCP가 연결되어 있다면
get_component
로 런타임/에디터 스냅샷을 보조 조회할 수 있다(
msw-maker-mcp
스킬 참고).
적용(set에 해당): 파일에 값을 쓴 뒤
refresh
호출.

对象编辑文件备注
DefaultPlayer
./Global/DefaultPlayer.model
Values
数组中覆盖
CostumeManagerComponent
属性
Player(基础模型)
./Global/Player.model
通常服装默认值会在DefaultPlayer.model中覆盖,而非此处
地图中放置的实体(NPC、怪物等)
./map/{地图名}.map
对应实体的
jsonString.@components
内的
CostumeManagerComponent
仅引用自定义模型的实体对应
.model
文件(例:
./RootDesk/MyDesk/
等)
若地图中无内联组件,仅通过
modelId
关联,则修改模型文件
查询(对应get操作):读取上述文件,查看
CostumeManagerComponent
相关字段及
Values
项。若已连接Maker MCP,可通过
get_component
辅助查询运行时/编辑器快照(参考
msw-maker-mcp
技能)。
应用(对应set操作):在文件中写入值后,调用
refresh

변경 반영: MCP
refresh

变更同步:MCP
refresh

파일 저장 후 반드시
msw-maker-mcp
서버의
refresh
도구를 호출하여 Maker와 비주얼 상태를 동기화한다. (
msw-maker-mcp
스킬의 도구 목록 참고)

文件保存后必须调用
msw-maker-mcp
服务器的**
refresh
**工具,同步Maker与可视化状态。(参考
msw-maker-mcp
技能的工具列表)

RUID(리소스 고유 ID)

RUID(资源唯一ID)

코스튬에 넣는 문자열은 아바타 아이템의 RUID(일반적으로 32자 hex)이다.
  • RUID를 추측·임의 생성하면 안 된다.
    msw-search
    스킬 및
    references/avatar.md
    (기본 body/head, 아이템 상세, 렌더 조합),
    references/search.md
    ,
    references/detail.md
    등으로 검색·조회하여 확보한다.
  • 스크립트 API
    SetEquip(MapleAvatarItemCategory, itemRUID)
    와 동일하게, 에디터/모델에 저장하는 것도 결국 같은 RUID 문자열이다.

服装中填入的字符串是角色道具的RUID(通常为32位十六进制字符串)。
  • 不可猜测或随意生成RUID。需通过
    msw-search
    技能及**
    references/avatar.md
    **(默认身体/头部、道具详情、渲染组合)、
    references/search.md
    references/detail.md
    等进行搜索查询获取。
  • 与脚本API
    SetEquip(MapleAvatarItemCategory, itemRUID
    相同,编辑器/模型中保存的也是相同的RUID字符串

CostumeManagerComponent 개요

CostumeManagerComponent 概述

플레이어·NPC 등 아바타를 쓰는 엔티티에 부착된다. 장비 슬롯은 커스텀 장비용 문자열 프로퍼티 17개(
Custom*Equip
)로 표현되며, 스크립트에서는
GetEquip
/
SetEquip
MapleAvatarItemCategory
로 접근한다.
附加在玩家·NPC等使用角色的实体上。装备插槽通过17个自定义装备字符串属性
Custom*Equip
)表示,脚本中可通过
GetEquip
/
SetEquip
MapleAvatarItemCategory
进行访问。

기타 동기화 프로퍼티

其他同步属性

프로퍼티타입설명
UseCustomEquipOnly
boolean
(기본
false
)
true
이면 유저 계정 기본 코스튬을 쓰지 않고, 스크립트·모델에 지정한 코스튬만 사용한다. 월드에서 외형을 고정할 때 중요하다.
DefaultEquipUserId
string
지정한 유저의 장비를 복제한 뒤, 그 위에 커스텀 장비를 얹는 방식. 접속하지 않은 유저도 지정 가능. 대상 유저가 이후 장비를 바꾸면 반영이 달라질 수 있다.
EquippedItems읽기 전용런타임에서 실제 장착 정보. 스크립트에서 수정 불가.

属性类型说明
UseCustomEquipOnly
boolean
(默认
false
设为
true
时,不使用用户账号默认服装,仅使用脚本·模型中指定的服装。在世界中固定外观时非常重要。
DefaultEquipUserId
string
复制指定用户的装备后,在其基础上叠加自定义装备。未登录用户也可指定。若目标用户后续修改装备,可能会影响当前设置。
EquippedItems只读运行时的实际装备信息。脚本无法修改

17 슬롯 ↔ 프로퍼티 ↔ MapleAvatarItemCategory

17插槽 ↔ 属性 ↔ MapleAvatarItemCategory

CostumeManagerComponent
장비 문자열 필드 17개와 엔진 enum
MapleAvatarItemCategory
대응이다. (enum 정의:
Environment/NativeScripts/Enum/MapleAvatarItemCategory.d.mlua
등 참고)
#컴포넌트 프로퍼티 (문자열 RUID)MapleAvatarItemCategory비고
1CustomBodyEquipBody (1)스킨/바디
2CustomHairEquipHair (3)헤어
3CustomFaceEquipFace (4)성형/페이스
4CustomCapEquipCap (5)모자
5CustomCapeEquipCape (6)망토
6CustomCoatEquipCoat (7)상의(코트)
7CustomLongcoatEquipLongcoat (9)롱코트 — 상의+하의 슬롯을 함께 쓰는 아이템 분류
8CustomPantsEquipPants (10)하의
9CustomGloveEquipGlove (8)장갑
10CustomShoesEquipShoes (12)신발
11CustomOneHandedWeaponEquipOneHandedWeapon (13)한손 무기
12CustomTwoHandedWeaponEquipTwoHandedWeapon (14)두손 무기 — 한손 무기 + 보조 무기 슬롯을 함께 쓰는 분류
13CustomSubWeaponEquipSubWeapon (15)보조 무기
14CustomFaceAccessoryEquipFaceAccessory (16)페이스 악세
15CustomEyeAccessoryEquipEyeAccessory (17)눈 악세
16CustomEarAccessoryEquipEarAccessory (18)귀 악세
17CustomEarEquipEar (19)귀(신체 파츠)
CostumeManagerComponent
17个装备字符串字段与引擎枚举**
MapleAvatarItemCategory
**的对应关系。(枚举定义参考:
Environment/NativeScripts/Enum/MapleAvatarItemCategory.d.mlua
等)
#组件属性(字符串RUID)MapleAvatarItemCategory备注
1CustomBodyEquipBody (1)皮肤/身体
2CustomHairEquipHair (3)发型
3CustomFaceEquipFace (4)脸型/面部
4CustomCapEquipCap (5)帽子
5CustomCapeEquipCape (6)披风
6CustomCoatEquipCoat (7)上衣(外套)
7CustomLongcoatEquipLongcoat (9)长款外套 —— 占用上衣+下装插槽的道具分类
8CustomPantsEquipPants (10)下装
9CustomGloveEquipGlove (8)手套
10CustomShoesEquipShoes (12)鞋子
11CustomOneHandedWeaponEquipOneHandedWeapon (13)单手武器
12CustomTwoHandedWeaponEquipTwoHandedWeapon (14)双手武器 —— 占用单手武器+副武器插槽的分类
13CustomSubWeaponEquipSubWeapon (15)副武器
14CustomFaceAccessoryEquipFaceAccessory (16)面部饰品
15CustomEyeAccessoryEquipEyeAccessory (17)眼部饰品
16CustomEarAccessoryEquipEarAccessory (18)耳部饰品
17CustomEarEquipEar (19)耳朵(身体部位)

enum에만 있고 위 17필드에 직접 대응하지 않는 항목

仅存在于枚举中、无对应17字段的项

MapleAvatarItemCategory설명
Head (2)“장비로 쓰지 않음”에 가깝고 바디 색에 맞춰 자동 처리되는 분류.
CustomHeadEquip
같은 필드는 없다.
Invalid (0)오류/미정의 검출용.
Shield (11)enum 주석상 보조 무기 슬롯(SubWeapon) 을 사용. 실질 저장은 CustomSubWeaponEquip 쪽과 배타적으로 정리하는 것이 안전하다.

MapleAvatarItemCategory说明
Head (2)近乎“不用于装备”,会根据身体颜色自动处理。无
CustomHeadEquip
这类字段。
Invalid (0)用于检测错误/未定义情况。
Shield (11)枚举注释中说明使用副武器插槽(SubWeapon)。实际存储时,建议与CustomSubWeaponEquip互斥设置以避免冲突。

상호 배타·슬롯 점유 규칙 (필수 이해)

互斥·插槽占用规则(必须理解)

  1. 롱코트 ↔ 코트 + 바지
    Longcoat는 설계상 코트(Coat)와 바지(Pants) 슬롯을 함께 사용한다. 롱코트를 착용시키면 롱코트 RUID는
    CustomLongcoatEquip
    에 두고
    , 상·하의를 따로 보이게 하려면 코트/바지와의 조합을 논리적으로 정리한다(통상 롱코트 사용 시 코트·바지 개별 장비는 비우거나 충돌을 피한다).
  2. 두손 무기 ↔ 한손 무기 + 보조
    TwoHandedWeapon한손 무기 슬롯과 보조 무기 슬롯을 함께 쓴다. 두손 무기를 쓰면 CustomTwoHandedWeaponEquip를 중심으로 맞추고, 한손·보조와의 이중 장착이 되지 않게 값을 정리한다.
  3. 방패(Shield) ↔ 보조 무기
    Shield보조 무기 슬롯을 쓴다. CustomSubWeaponEquip와 동시에 다른 보조 장비를 기대하지 않도록 한다.
  4. 빈 문자열로 해제
    스크립트
    SetEquip(category, "")
    와 같이, 파일에서도
    ""
    로 두면 해당 슬롯 해제
    로 이해하면 된다.

  1. 长款外套 ↔ 外套 + 裤子
    Longcoat在设计上同时占用外套(Coat)和裤子(Pants)插槽。穿戴长款外套时,需将长款外套RUID放在
    CustomLongcoatEquip
    中,若要单独显示上衣/下装,需逻辑上整理外套/裤子的组合(通常穿戴长款外套时,需清空或避免外套·裤子的单独装备冲突)。
  2. 双手武器 ↔ 单手武器 + 副武器
    TwoHandedWeapon同时占用单手武器插槽和副武器插槽。使用双手武器时,需以
    CustomTwoHandedWeaponEquip
    为核心设置,确保不会与单手·副武器重复穿戴
  3. 盾牌(Shield) ↔ 副武器
    Shield占用副武器插槽。请勿同时设置
    CustomSubWeaponEquip
    与其他副装备。
  4. 空字符串解除装备
    如同脚本
    SetEquip(category, "")
    ,在文件中设置为**
    ""
    时,视为解除对应插槽的装备**。

DefaultPlayer.model —
Values
에 코스튬 넣기

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)
가 이미 있으면 그 항목만 갱신하고, 없으면 배열에 객체 하나 추가한다.
./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는 검색으로 치환)

字符串插槽示例(仅参考结构;RUID需替换为搜索到的值)

json
{
  "TargetType": "MOD.Core.CostumeManagerComponent",
  "Name": "CustomCapEquip",
  "ValueType": {
    "$type": "MODNativeType",
    "type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
  },
  "Value": "여기에_32자hex_RUID"
}
json
{
  "TargetType": "MOD.Core.CostumeManagerComponent",
  "Name": "CustomCapEquip",
  "ValueType": {
    "$type": "MODNativeType",
    "type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
  },
  "Value": "此处填入32位十六进制RUID"
}

UseCustomEquipOnly 예시

UseCustomEquipOnly示例

json
{
  "TargetType": "MOD.Core.CostumeManagerComponent",
  "Name": "UseCustomEquipOnly",
  "ValueType": {
    "$type": "MODNativeType",
    "type": "System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
  },
  "Value": true
}

json
{
  "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
文件中修改

./map/
아래 해당 맵의 엔티티 레코드를 연다.
  1. ContentProto.Entities
    배열에서 대상 엔티티(이름·path·id로 식별)를 찾는다.
  2. jsonString["@components"]
    배열에서
    "@type": "MOD.Core.CostumeManagerComponent"
    인 객체를 찾는다.
  3. 그 객체의
    Custom*Equip
    ,
    UseCustomEquipOnly
    ,
    DefaultEquipUserId
    등을 직접 수정한다.
  4. componentNames
    문자열 목록에
    MOD.Core.CostumeManagerComponent
    가 포함되어 있는지 확인하고, 컴포넌트 배열과 불일치하면 안 된다.
맵이 바이너리 포맷만 쓰는 경우 등은 워크스페이스 정책에 따라 편집 도구가 다를 수 있다. JSON 텍스트로 열리는 경우 위 구조를 따른다.

打开
./map/
下对应地图的实体记录
  1. ContentProto.Entities
    数组中找到目标实体(通过名称·路径·ID识别)。
  2. jsonString["@components"]
    数组中找到**
    "@type": "MOD.Core.CostumeManagerComponent"
    **的对象。
  3. 直接修改该对象的**
    Custom*Equip
    **,
    UseCustomEquipOnly
    , **
    DefaultEquipUserId
    **等属性。
  4. 确认
    componentNames
    字符串列表中包含
    MOD.Core.CostumeManagerComponent
    ,需与组件数组保持一致。
若地图仅使用二进制格式等,编辑工具可能因工作区政策不同而有所差异。若以JSON文本打开,则遵循上述结构。

GET /v3/avatars 검색 결과 → 슬롯 매핑

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 슬롯 공유

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
    (코스튬 검색), 기본 body/head,
    GET /v3/avatars/{ruid}
    , 렌더 조합 등 아바타 파이프라인 상세.
  • 장비 RUID는 카테고리 검색·상세 API를 병행해 확보한다.

  • **
    msw-search
    **技能 →
    references/resource/avatar.md
    :
    GET /v3/avatars
    (服装搜索)、默认身体/头部、
    GET /v3/avatars/{ruid}
    、渲染组合等角色流水线详情。
  • 装备RUID需结合分类搜索·详情API获取。

아바타 애니메이션 — 전체 구조

角色动画 —— 整体结构

아바타 애니메이션은 3계층 파이프라인으로 흐른다. 한 계층만 보고 작업하면 다른 계층이 덮어써서 의도와 다른 동작이 나온다.
[1] 입력 / 게임 로직
       │  PlayerControllerComponent · 스크립트
[2] StateComponent       ──── StateChangeEvent ────▶ AvatarStateAnimationComponent
       (ex. "ATTACK")            (CurrentStateName)        (StateToAvatarBodyActionSheet
                                                            또는 ActionSheet 룩업)
[3] AvatarRendererComponent ◀── BodyActionStateChange / ActionStateChanged ── 바디 엔티티
       (실제 스프라이트 재생)
핵심 구분:
용어형식예시
State 키대문자 영문
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)Pascal case
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로 풀린다.

角色动画通过三层流水线流转。若仅关注单个层级,可能因其他层级覆盖导致不符合预期的动作。
[1] 输入 / 游戏逻辑
       │  PlayerControllerComponent · 脚本
[2] StateComponent       ──── StateChangeEvent ────▶ AvatarStateAnimationComponent
       (示例: "ATTACK")            (CurrentStateName)        (StateToAvatarBodyActionSheet
                                                            或ActionSheet查找)
[3] AvatarRendererComponent ◀── BodyActionStateChange / ActionStateChanged ── 身体实体
       (实际精灵播放)
核心区分:
术语格式示例
State键大写英文
IDLE
,
MOVE
,
ATTACK
,
HIT
,
CROUCH
,
FALL
,
JUMP
,
CLIMB
,
LADDER
,
DEAD
,
SIT
,
ATTACK_WAIT
AvatarBodyActionStateName(值端)小写英文
stand
,
walk
,
attack
,
hit
,
crouch
,
fall
,
rope
,
ladder
,
dead
,
sit
,
alert
,
fly
,
blink
,
heal
MapleAvatarBodyActionState(枚举)帕斯卡命名法
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
,映射值是小写的
attack
(对应MapleAvatarBodyActionState.Attack),该值会根据武器进一步解析为
swingO1
/
shoot1
等精灵动作ID。

AvatarStateAnimationComponent — 상태↔동작 매핑

AvatarStateAnimationComponent —— 状态↔动作映射

MOD.Core.AvatarStateAnimationComponent
는 두 시스템을 모두 갖고 있다.
프로퍼티사용 조건형식비고
IsLegacy
두 시스템 선택 스위치
boolean
(기본
false
)
true
이면 ActionSheet,
false
이면 StateToAvatarBodyActionSheet 사용
ActionSheet
IsLegacy = true
(구)
SyncDictionary<string, string>
"ATTACK"
"attack"
처럼 State→AnimationKey
StateToAvatarBodyActionSheet
IsLegacy = false
(신, 기본)
SyncDictionary<string, AvatarBodyActionElement>
"ATTACK"
{AvatarBodyActionStateName="attack", PlayRate=1.33}
MOD.Core.AvatarStateAnimationComponent
同时支持两套系统。
属性使用条件格式备注
IsLegacy
系统切换开关
boolean
(默认
false
设为
true
时使用ActionSheet,
false
时使用StateToAvatarBodyActionSheet
ActionSheet
IsLegacy = true
(旧版)
SyncDictionary<string, string>
"ATTACK"
"attack"
的State→AnimationKey映射
StateToAvatarBodyActionSheet
IsLegacy = false
(新版,默认)
SyncDictionary<string, AvatarBodyActionElement>
"ATTACK"
{AvatarBodyActionStateName="attack", PlayRate=1.33}
的映射

StateToAvatarBodyActionSheet
기본 매핑 (IsLegacy=false 기본 11개 키)

StateToAvatarBodyActionSheet
默认映射(IsLegacy=false默认11个键)

Key (State)AvatarBodyActionStateNamePlayRate트리거 조건(PlayerControllerComponent 동반 시)
IDLE
stand
1.0입력 없음
MOVE
walk
1.68좌/우 이동
ATTACK
attack
1.33Left Ctrl (Attack 액션)
HIT
hit
1.0HitComponent 피격 처리
CROUCH
crouch
1.0아래 방향키
FALL
fall
1.0공중에서 낙하
JUMP
fall
1.0Space (Jump 액션)
CLIMB
rope
1.0로프 진입
LADDER
ladder
1.0사다리 진입
DEAD
dead
1.0사망
SIT
sit
1.0C (Sit 액션)
State 키는 대문자, AvatarBodyActionStateName 값은 소문자라는 점에 주의.
Key(State)AvatarBodyActionStateNamePlayRate触发条件(附带PlayerControllerComponent时)
IDLE
stand
1.0无输入
MOVE
walk
1.68左/右移动
ATTACK
attack
1.33Left Ctrl(Attack动作)
HIT
hit
1.0HitComponent受击处理
CROUCH
crouch
1.0下方向键
FALL
fall
1.0空中下落
JUMP
fall
1.0Space(Jump动作)
CLIMB
rope
1.0进入绳索
LADDER
ladder
1.0进入梯子
DEAD
dead
1.0死亡
SIT
sit
1.0C(Sit动作)
注意:State键为大写,AvatarBodyActionStateName值为小写。

MapleAvatarBodyActionState
→ 실제 액션 ID 디폴트 변환표

MapleAvatarBodyActionState
→ 实际动作ID默认转换表

AvatarBodyActionStateName
문자열 (
"attack"
,
"stand"
등)은 enum
MapleAvatarBodyActionState
로 캐스팅되고, 엔진은 이를 다음 기본값으로 풀어
ActionStateChangedEvent
를 합성한다.
MapleAvatarBodyActionStateCoreActionNamePartsActionNamePlayRatePlayType
Stand
stand1
/
stand2
동일1ZigzagLoop
Walk
walk1
/
walk2
동일1Loop
Attack
alert
(무기 미장착 기본)
alert
1Loop
Crouch
prone
prone
1Loop
Fall
jump
jump
1Loop
Sit
sit
sit
1Loop
Rope
rope
rope
1Loop
Ladder
ladder
ladder
1Loop
Dead
dead
stand1
1Loop
Blink
blink
blink
1Loop
Fly
fly
fly
1Loop
Hit
alert
alert
1ZigzagLoop
Alert
alert
alert
1ZigzagLoop
Heal
heal
heal
1Loop
무기를 장착하면
Attack
은 무기 종류에 맞는 스프라이트 액션 ID로 자동 치환
된다 (다음 절 표 참조). 즉 한손검을 쥐면 칼 휘두르기, 활을 쥐면 활 쏘기 모션이 나온다.
AvatarBodyActionStateName
字符串(
"attack"
,
"stand"
等)会被转换为
MapleAvatarBodyActionState
枚举,引擎将其解析为以下默认值并生成
ActionStateChangedEvent
MapleAvatarBodyActionStateCoreActionNamePartsActionNamePlayRatePlayType
Stand
stand1
/
stand2
相同1ZigzagLoop
Walk
walk1
/
walk2
相同1Loop
Attack
alert
(未装备武器默认)
alert
1Loop
Crouch
prone
prone
1Loop
Fall
jump
jump
1Loop
Sit
sit
sit
1Loop
Rope
rope
rope
1Loop
Ladder
ladder
ladder
1Loop
Dead
dead
stand1
1Loop
Blink
blink
blink
1Loop
Fly
fly
fly
1Loop
Hit
alert
alert
1ZigzagLoop
Alert
alert
alert
1ZigzagLoop
Heal
heal
heal
1Loop
装备武器后,
Attack
会自动替换为对应武器类型的精灵动作ID
(参考下一节表格)。例如手持单手剑时会播放挥剑动作,手持弓时会播放射箭动作。

무기별
attack
풀이 — 스프라이트 액션 ID 후보군

按武器分类的
attack
解析 —— 精灵动作ID候选集

ATTACK
상태가 트리거되면 엔진이 장착 무기(MapleAvatarItemCategory)를 보고 다음 ID 중 하나로 액션을 재생한다.
무기 분류사용되는 CoreActionName/PartsActionName 후보
한손검/단검 (
OneHandedWeapon
)
swingO1
,
swingO2
,
swingO3
,
stabO1
,
stabO2
두손검/해머 (
TwoHandedWeapon
)
swingT1
,
swingT2
,
swingT3
,
stabT1
,
stabT2
활 (
TwoHandedWeapon
Bow 계열)
swingT1
,
swingT3
,
shoot1
스태프/완드
swingO1
,
swingO2
,
swingO3
무기 없음 (기본 body)별도 attack 클립 없음 →
alert
등으로 표시됨
같은 분류라도 아이템 메타에 따라 사용되는 액션 ID 집합이 다를 수 있다. 위 표는 SDK 가이드(
_ActionNameLogic
)가 사용하는 대표 후보군.
触发
ATTACK
状态时,引擎会根据装备的武器(MapleAvatarItemCategory)选择以下ID之一播放动作。
武器分类使用的CoreActionName/PartsActionName候选
单手剑/短剑 (
OneHandedWeapon
)
swingO1
,
swingO2
,
swingO3
,
stabO1
,
stabO2
双手剑/锤子 (
TwoHandedWeapon
)
swingT1
,
swingT2
,
swingT3
,
stabT1
,
stabT2
弓(
TwoHandedWeapon
弓类)
swingT1
,
swingT3
,
shoot1
法杖/魔杖
swingO1
,
swingO2
,
swingO3
无武器(默认身体)无单独attack片段 → 显示
alert
即使属于同一分类,根据道具元数据的不同,使用的动作ID集合也可能不同。上述表格为SDK指南(
_ActionNameLogic
)使用的典型候选集。

PlayerControllerComponent와 자동 상태 추가

PlayerControllerComponent与自动状态添加

플레이어 엔티티에
MOD.Core.PlayerControllerComponent
가 붙어 있으면
StateComponent
에 다음 State가 자동으로 추가되고 키 입력에 따라 자동 전이된다.
MOVE
,
CLIMB
,
LADDER
,
CROUCH
,
JUMP
,
FALL
,
ATTACK
,
ATTACK_WAIT
,
SIT
따라서 DefaultPlayer가 Ctrl을 누르면 자동으로 ATTACK 상태가 되고, 위 매핑에 따라 attack 바디 동작(=무기별 칼/활/지팡이 휘두르기)이 자동 재생된다. 스크립트로 별도 처리를 안 해도 칼은 휘둘러진다.

若玩家实体附加了
MOD.Core.PlayerControllerComponent
,则
StateComponent
自动添加以下State,并根据按键输入自动切换:
MOVE
,
CLIMB
,
LADDER
,
CROUCH
,
JUMP
,
FALL
,
ATTACK
,
ATTACK_WAIT
,
SIT
因此,DefaultPlayer按下Ctrl时会自动进入ATTACK状态,并根据上述映射自动播放attack身体动作(即对应武器的挥剑/射箭/挥杖动作)。无需额外脚本处理,即可实现挥剑动作。

자동 재생 ↔ 수동 ActionStateChangedEvent 충돌 (★ 자주 만나는 함정)

自动播放 ↔ 手动ActionStateChangedEvent冲突(★ 常见陷阱)

증상: 스크립트에서
ActionStateChangedEvent
shoot1
같은 커스텀 액션을 보내도, 여전히 칼 휘두르기(또는 무기 기본 attack) 가 나오거나 한 프레임만 스치고 곧 덮인다.
원인:
ATTACK
상태가 활성인 동안
AvatarStateAnimationComponent
가 매핑된
attack
바디 동작을 지속적으로 보낸다. 우리가 보낸 1회성 이벤트는 즉시 덮인다.
症状:脚本通过
ActionStateChangedEvent
发送
shoot1
等自定义动作,但仍显示挥剑(或武器默认attack)动作,或仅闪一下就被覆盖。
原因
ATTACK
状态激活期间,
AvatarStateAnimationComponent
持续发送映射的
attack
身体动作。我们发送的一次性事件会立即被覆盖。

해결 전략

解决策略

전략방법언제 쓰나
A. 매핑 제거
asac:RemoveActionSheet("ATTACK")
로 해당 키를 지운다. 이후
ActionStateChangedEvent
로 직접 액션 재생.
공격 모션을 완전히 커스텀으로 대체할 때 (활 쏘기, 마법 시전 등)
B. 매핑 변경
asac:SetActionSheet("ATTACK", "<Body Action 이름>")
또는
StateToAvatarBodyActionSheet["ATTACK"]
을 다른
MapleAvatarBodyActionState
로 변경.
다른 내장 상태 애니메이션으로 바꾸고 싶을 때 (예: ATTACK→heal)
C. 강제 리셋
BodyActionStateChangeEvent
needResetAction=true
로 보낸다.
같은 상태를 재시작하고 싶을 때
D. 무기 교체
CostumeManagerComponent
의 무기 슬롯을 활 RUID로 바꾼다.
단순히 attack 모션의 무기 종류만 바꾸고 싶을 때 (가장 직관적)
策略方法使用场景
A. 移除映射使用
asac:RemoveActionSheet("ATTACK")
移除对应键。之后通过
ActionStateChangedEvent
直接播放动作。
完全自定义攻击动作时(如射箭、施法等)
B. 修改映射使用
asac:SetActionSheet("ATTACK", "<身体动作名称>")
或将
StateToAvatarBodyActionSheet["ATTACK"]
修改为其他
MapleAvatarBodyActionState
需切换为其他内置状态动画时(例:ATTACK→heal)
C. 强制重置发送
needResetAction=true
BodyActionStateChangeEvent
重新启动同一状态时
D. 更换武器
CostumeManagerComponent
的武器插槽设置为弓的RUID
仅需修改attack动作的武器类型时(最直观)

전략 A 예시 — 칼 휘두르기 끄고 활 쏘기로 대체

策略A示例 —— 关闭挥剑动作,替换为射箭动作

lua
@Component
script PlayerAttack extends AttackComponent

	@HideFromInspector
	property any Shape = nil

	@ExecSpace("ServerOnly")
	method void OnBeginPlay()
		self.Shape = BoxShape(Vector2.zero, Vector2.one, 0)

		-- 엔진이 ATTACK 상태에서 자동 재생하던 attack(=칼 휘두르기) 매핑을 제거
		local asac = self.Entity.AvatarStateAnimationComponent
		if isvalid(asac) then
			asac:RemoveActionSheet("ATTACK")
		end
	end

	@ExecSpace("ServerOnly")
	method void AttackNormal()
		-- ... 데미지 판정 ...
		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
프로퍼티이기 때문이다.
lua
@Component
script PlayerAttack extends AttackComponent

	@HideFromInspector
	property any Shape = nil

	@ExecSpace("ServerOnly")
	method void OnBeginPlay()
		self.Shape = BoxShape(Vector2.zero, Vector2.one, 0)

		-- 移除引擎在ATTACK状态下自动播放的attack(即挥剑动作)映射
		local asac = self.Entity.AvatarStateAnimationComponent
		if isvalid(asac) then
			asac:RemoveActionSheet("ATTACK")
		end
	end

	@ExecSpace("ServerOnly")
	method void AttackNormal()
		-- ... 伤害判定 ...
		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로 활 장착

策略D示例 —— 通过CostumeManagerComponent装备弓

./Global/DefaultPlayer.model
Values
에 활 RUID를 추가하면, 매핑은 그대로 두어도 엔진이 ATTACK 상태에서
shoot1
모션을 자동으로 고른다.
json
{
  "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 — 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
}

./Global/DefaultPlayer.model
Values
中添加弓的RUID,即使保持映射不变,引擎也会在ATTACK状态下自动选择
shoot1
动作。
json
{
  "TargetType": "MOD.Core.CostumeManagerComponent",
  "Name": "CustomTwoHandedWeaponEquip",
  "ValueType": {
    "$type": "MODNativeType",
    "type": "System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
  },
  "Value": "<弓的RUID —— 通过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상태 vs 커스텀 액션 — 무엇을 직접 발사해야 하나

14种状态 vs 自定义动作 —— 哪些需要手动触发

AvatarStateAnimationComponent
이/
MapleAvatarBodyActionState
이미 알고 있는 14개 바디 동작과, 그 외의 임의 스프라이트 액션 ID(예:
swingO2
,
shoot1
,
dance
,
cast1
)는 처리 경로가 다르다.
AvatarStateAnimationComponent
/
MapleAvatarBodyActionState
已内置的14种身体动作,与其他任意精灵动作ID(例:
swingO2
,
shoot1
,
dance
,
cast1
)的处理路径不同。

엔진이 자동 처리하는 14개 바디 동작 (= MapleAvatarBodyActionState 멤버)

引擎自动处理的14种身体动作(=MapleAvatarBodyActionState成员)

StateToAvatarBodyActionSheet
/
ActionSheet
의 Value로 쓸 수 있는 이름. State에 매핑만 해두면 자동 재생된다.
바디 동작 이름enum의미
stand
Stand대기
walk
Walk이동
attack
Attack공격(무기에 따라 sprite ID 자동 결정)
hit
Hit피격
crouch
Crouch웅크리기
fall
Fall낙하
rope
Rope로프 잡기
ladder
Ladder사다리
dead
Dead사망
sit
Sit앉기
heal
Heal회복
alert
Alert경계
fly
Fly비행
blink
Blink눈 깜박임
可作为
StateToAvatarBodyActionSheet
/
ActionSheet
的值。只需完成状态映射即可自动播放。
身体动作名称枚举含义
stand
Stand待机
walk
Walk移动
attack
Attack攻击(根据武器自动确定sprite ID)
hit
Hit受击
crouch
Crouch下蹲
fall
Fall下落
rope
Rope抓绳索
ladder
Ladder爬梯子
dead
Dead死亡
sit
Sit坐下
heal
Heal恢复
alert
Alert警戒
fly
Fly飞行
blink
Blink眨眼

그 외 커스텀 동작 —
ActionStateChangedEvent
로 직접 재생

其他自定义动作 —— 通过
ActionStateChangedEvent
手动播放

14개 enum 외의 임의 스프라이트 액션 ID(예:
shoot1
,
swingO2
,
cast1
,
throw1
,
dance
,
cheer
등)는 다음 절차로 재생한다.
재생 파이프라인
  1. 엔티티의
    AvatarRendererComponent
    에서
    GetBodyEntity()
    로 아바타 바디 엔티티를 얻는다. 애니메이션 이벤트는 아바타 본체가 아니라 바디 엔티티에 보낸다.
  2. ActionStateChangedEvent
    를 생성하고 필드를 채운다.
  3. body:SendEvent(event)
    로 바디 엔티티에 전송한다.
  4. 애니메이션 재생은 각 클라이언트에서 보이기만 하면 되므로 보통
    @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 왕복 반복
14种枚举之外的任意精灵动作ID(例:
shoot1
,
swingO2
,
cast1
,
throw1
,
dance
,
cheer
等)需按以下步骤播放:
播放流水线
  1. 通过实体的
    AvatarRendererComponent
    调用**
    GetBodyEntity()
    获取角色身体实体**。动画事件需发送给角色身体实体,而非角色根实体。
  2. 创建**
    ActionStateChangedEvent
    **并填充字段。
  3. 通过
    body:SendEvent(event)
    发送给身体实体。
  4. 动画播放仅需在各客户端显示,因此通常限定为**
    @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
。一次性动作需设置为**
Onetime
**
StartFrameIndex
int32
0
起始帧(负数会修正为0)
EndFrameIndex
int32
2147483647
结束帧(超过总帧数会自动修正)
SpriteAnimClipPlayType
:
含义
Onetime
播放一次后停止
Loop
从0到结束循环播放
ZigzagLoop
从0到结束再回到0往复循环播放

BodyActionStateChangeEvent
— 14개 내장 상태 고수준 이벤트

BodyActionStateChangeEvent
—— 14种内置状态的高级事件

MapleAvatarBodyActionState
enum을 직접 지정해 변경한다. 무기별 액션 ID를 일일이 외울 필요가 없고, 같은 상태를 다시 재생하고 싶을 때
needResetAction=true
로 강제 리셋이 가능하다는 장점이 있다.
SendEvent
대상은
ActionStateChangedEvent
처럼 바디 엔티티가 아니라 아바타 루트 엔티티(
self.Entity
)
이다.
lua
local event = BodyActionStateChangeEvent()
event.ActionState = MapleAvatarBodyActionState.Fly
event.needResetAction = true
event.startFrameIndex = 1
event.endFrameIndex = 2
self.Entity:SendEvent(event)
-- 내부적으로 ActionStateChangedEvent("fly", "fly", 1, Loop, 1, 2)로 변환되어 전달
필드설명
ActionState
MapleAvatarBodyActionState
enum (Stand/Walk/Attack/Hit/...)
needResetAction
true
이면 같은 상태가 이미 재생 중이어도 처음부터 강제 재생
playRate
/
startFrameIndex
/
endFrameIndex
ActionStateChangedEvent
와 동일
선택 기준
  • 임의 스프라이트 액션 ID(
    shoot1
    ,
    swingO2
    ,
    dance
    등) →
    ActionStateChangedEvent
    (바디 엔티티에 전송)
  • enum에 있는 14개 상태(Stand/Walk/Attack/...) →
    BodyActionStateChangeEvent
    (루트 엔티티에 전송)
可直接指定
MapleAvatarBodyActionState
枚举进行变更。无需记忆各武器对应的动作ID,且当需要重新播放同一状态时,可通过
needResetAction=true
强制重置。
SendEvent
的目标不是
ActionStateChangedEvent
中的身体实体,而是角色根实体(
self.Entity
)
lua
local event = BodyActionStateChangeEvent()
event.ActionState = MapleAvatarBodyActionState.Fly
event.needResetAction = true
event.startFrameIndex = 1
event.endFrameIndex = 2
self.Entity:SendEvent(event)
-- 内部会转换为ActionStateChangedEvent("fly", "fly", 1, Loop, 1, 2)发送
字段说明
ActionState
MapleAvatarBodyActionState
枚举(Stand/Walk/Attack/Hit/...)
needResetAction
设为
true
时,即使同一状态正在播放,也会强制从头开始播放
playRate
/
startFrameIndex
/
endFrameIndex
ActionStateChangedEvent
相同
选择标准
  • 任意精灵动作ID(
    shoot1
    ,
    swingO2
    ,
    dance
    等) → 使用
    ActionStateChangedEvent
    (发送给身体实体)
  • 枚举内置的14种状态(Stand/Walk/Attack/...) → 使用
    BodyActionStateChangeEvent
    (发送给根实体)

예시 — 화살 발사(
shoot
) 애니메이션 재생

示例 —— 播放射箭(
shoot
)动画

서버에서 공격 입력을 받으면 투사체를 스폰하고, 클라이언트에서
shoot1
액션을 재생하는 전형적인 패턴이다.
lua
@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
服务器接收攻击输入后生成投射物,客户端播放
shoot1
动作的典型模式。
lua
@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

의사결정 흐름

决策流程

  1. 재생하려는 동작이 기본 14상태(
    stand
    ,
    walk
    ,
    attack
    ,
    hit
    ,
    crouch
    ,
    fall
    ,
    rope
    ,
    ladder
    ,
    dead
    ,
    sit
    ,
    heal
    ,
    alert
    ,
    fly
    ,
    blink
    ) 중 하나인가?
    • YES
      AvatarStateAnimationComponent
      의 해당 슬롯에 클립을 지정만 하면 된다. 스크립트 불필요.
    • NO → 아래로.
  2. 커스텀 액션(예:
    shoot1
    ,
    cast1
    ,
    dance
    )은
    ActionStateChangedEvent
    를 만들어
    AvatarRendererComponent:GetBodyEntity()
    가 반환한 바디 엔티티
    SendEvent
    로 보낸다.
  3. 입력 처리·데미지 판정은 서버(
    ServerOnly
    )에서, 애니메이션 재생은 클라이언트(
    Client
    )
    에서 나누어 실행한다.
  1. 要播放的动作是否属于默认14种状态(
    stand
    ,
    walk
    ,
    attack
    ,
    hit
    ,
    crouch
    ,
    fall
    ,
    rope
    ,
    ladder
    ,
    dead
    ,
    sit
    ,
    heal
    ,
    alert
    ,
    fly
    ,
    blink
    )之一?
    • → 只需在
      AvatarStateAnimationComponent
      的对应插槽指定片段即可,无需脚本。
    • → 继续下一步。
  2. 自定义动作(例:
    shoot1
    ,
    cast1
    ,
    dance
    )需创建**
    ActionStateChangedEvent
    ,并发送给
    AvatarRendererComponent:GetBodyEntity()
    返回的
    身体实体**。
  3. 输入处理·伤害判定在服务器(
    ServerOnly
    )执行,**动画播放在客户端(
    Client
    )**执行,需分开处理。

자주 발생하는 실수

常见错误

  • State 키와 AvatarBodyActionStateName(=enum)을 혼동. State 키는 대문자(
    ATTACK
    ), 매핑 Value는 소문자(
    attack
    ).
    StateToAvatarBodyActionSheet
    의 Key/Value를 뒤집어 넣으면 매핑이 작동하지 않는다.
  • 자동 재생을 끄지 않고 ActionStateChangedEvent만 보내는 경우. Ctrl 입력 시
    ATTACK
    상태가 자동 활성화되어 매핑된 attack 바디 동작이 우리 이벤트를 즉시 덮는다. 커스텀 공격 모션을 쓰려면 반드시
    RemoveActionSheet("ATTACK")
    또는
    SetActionSheet("ATTACK", "<원하는 동작>")
    으로 매핑을 정리해야 한다.
  • ActionStateChangedEvent
    의 SendEvent 대상
    : 반드시
    AvatarRendererComponent:GetBodyEntity()
    가 반환한 바디 엔티티.
    self.Entity
    (아바타 루트)나 컴포넌트에 보내면 재생되지 않는다. (반대로
    BodyActionStateChangeEvent
    루트 엔티티에 보낸다.)
  • AvatarStateAnimationComponent
    에 임의 상태명을 넣는 시도
    . 14개 enum(
    MapleAvatarBodyActionState
    ) 외 이름(
    shoot
    ,
    cast
    ,
    dance
    )을 Value로 넣으면 무시된다. 임의 ID는
    ActionStateChangedEvent
    경로로만.
  • PartsActionName
    빠뜨리기
    .
    CoreActionName
    만 채우면 부위(무기·모자·망토 등) 애니메이션이 풀리지 않아 상체만 동작하고 무기는 멈춘 채 보일 수 있다. 보통
    CoreActionName
    과 같은 값을 사용.
  • PlayType
    미지정
    . 기본값이
    Loop
    이라서 1회성 액션이 무한 반복된다. 1회성은 명시적으로
    SpriteAnimClipPlayType.Onetime
    .
  • RemoveActionSheet
    /
    SetActionSheet
    를 클라이언트에서 호출
    .
    StateToAvatarBodyActionSheet
    @Sync
    동기화 프로퍼티이므로 서버에서 호출해야 모든 클라이언트에 반영된다.
  • 서버/클라이언트 실행 공간 분리 누락. 게임 로직(데미지·투사체) =
    ServerOnly
    , 애니메이션 재생 =
    Client
    . 두 경로를 한 곳에 몰아넣으면 클라마다 중복 재생되거나 시각 누락이 생긴다.
  • 무기를 안 끼우고 활 모션만 기대.
    shoot1
    액션을 쏘면 바디는 활 자세가 되지만, 활 RUID가
    CustomTwoHandedWeaponEquip
    에 없으면 손에 활이 그려지지 않는다
    . 시각적으로 자연스러우려면 모션과 무기를 함께 세팅한다.

  • 混淆State键与AvatarBodyActionStateName(=枚举)。State键为大写(
    ATTACK
    ),映射值为小写(
    attack
    )。若颠倒
    StateToAvatarBodyActionSheet
    的Key/Value,映射将无法生效。
  • 未关闭自动播放仅发送ActionStateChangedEvent。按下Ctrl时
    ATTACK
    状态会自动激活,映射的attack身体动作会立即覆盖我们的事件。使用自定义攻击动作时,必须通过
    RemoveActionSheet("ATTACK")
    SetActionSheet("ATTACK", "<目标动作>")
    整理映射。
  • ActionStateChangedEvent
    的SendEvent目标错误
    :必须发送给
    AvatarRendererComponent:GetBodyEntity()
    返回的身体实体。发送给
    self.Entity
    (角色根)或组件将无法播放。(相反,
    BodyActionStateChangeEvent
    需发送给根实体
  • 尝试在
    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-searchRUID 검색,
references/resource/avatar.md
msw-maker-mcp
refresh
, 필요 시
get_component
/
set_property
(런타임 조정과 병행 시)

技能用途
msw-defaultplayer
./Global/DefaultPlayer.model
·
Player.model
结构、
Values
规则
msw-searchRUID搜索、
references/resource/avatar.md
msw-maker-mcp
refresh
、必要时使用
get_component
/
set_property
(配合运行时调整)

요약 체크리스트

总结检查清单

코스튬

服装

  1. RUID는 resource 검색·avatar 참고 문서로 확보한다.
  2. DefaultPlayer/Player
    ./Global/*.model
    Values
    또는 베이스 모델 정의.
  3. 맵 엔티티
    ./map/*.map
    의 해당 엔티티
    @components
    .
  4. 롱코트 / 두손 무기 / 방패·보조 배타 규칙을 지킨다.
  5. **
    UseCustomEquipOnly
    **로 "계정 기본 코스튬 무시" 여부를 명시한다.
  6. 저장 후
    msw-maker-mcp
    refresh
    호출.
  1. RUID需通过资源搜索·角色参考文档获取。
  2. DefaultPlayer/Player
    ./Global/*.model
    的**
    Values
    **或基础模型定义。
  3. 地图实体
    ./map/*.map
    中对应实体的**
    @components
    **。
  4. 遵守长款外套 / 双手武器 / 盾牌·副武器的互斥规则。
  5. 通过**
    UseCustomEquipOnly
    **明确“是否忽略账号默认服装”。
  6. 保存后调用**
    msw-maker-mcp
    refresh
    **。

애니메이션

动画

  1. State 키(대문자)와 바디 동작 이름(소문자) 구분.
    StateToAvatarBodyActionSheet["ATTACK"] = AvatarBodyActionElement("attack", 1.33)
    형태.
  2. 원하는 동작이 enum 14개 바디 동작(
    stand
    ·
    walk
    ·
    attack
    ·
    hit
    ·
    crouch
    ·
    fall
    ·
    rope
    ·
    ladder
    ·
    dead
    ·
    sit
    ·
    heal
    ·
    alert
    ·
    fly
    ·
    blink
    ) 에 포함되면 매핑만 정의하고 끝.
  3. 그 외 액션 ID(
    shoot1
    ,
    swingT3
    ,
    dance
    등)는
    ActionStateChangedEvent
    생성 →
    AvatarRendererComponent:GetBodyEntity()
    SendEvent
    . enum 안의 상태 재시작은
    BodyActionStateChangeEvent
    + 루트 엔티티.
  4. CoreActionName
    /
    PartsActionName
    /
    PlayRate
    /
    PlayType
    네 필드를 채우고, 1회성 액션은
    SpriteAnimClipPlayType.Onetime
    .
  5. 자동 상태 전이와 충돌하는지 확인. PlayerControllerComponent가 붙은 엔티티는 입력 시
    MOVE/ATTACK/JUMP/...
    가 자동 발화되므로, 커스텀 공격을 쓰려면 충돌 키를
    RemoveActionSheet
    또는
    SetActionSheet
    로 정리한다(서버에서 호출).
  6. 게임 로직은
    @ExecSpace("ServerOnly")
    , 애니메이션 재생은
    @ExecSpace("Client")
    로 분리한다.
  7. 공격 모션의 무기 종류만 바꾸는 것이 목적이라면, 가장 단순한 방법은
    CostumeManagerComponent
    의 무기 슬롯 RUID 교체
    (전략 D).
  1. 区分State键(大写)与身体动作名称(小写)。格式为
    StateToAvatarBodyActionSheet["ATTACK"] = AvatarBodyActionElement("attack", 1.33)
  2. 若目标动作属于枚举14种身体动作(
    stand
    ·
    walk
    ·
    attack
    ·
    hit
    ·
    crouch
    ·
    fall
    ·
    rope
    ·
    ladder
    ·
    dead
    ·
    sit
    ·
    heal
    ·
    alert
    ·
    fly
    ·
    blink
    ),仅需完成映射即可。
  3. 其他动作ID(
    shoot1
    ,
    swingT3
    ,
    dance
    等)需创建**
    ActionStateChangedEvent
    ** → 发送给
    AvatarRendererComponent:GetBodyEntity()
    返回的身体实体。需重新播放枚举内状态时,使用**
    BodyActionStateChangeEvent
    ** + 根实体。
  4. 填充
    CoreActionName
    /
    PartsActionName
    /
    PlayRate
    /
    PlayType
    四个字段,一次性动作需设置为**
    SpriteAnimClipPlayType.Onetime
    **。
  5. 检查是否与自动状态切换冲突。附加PlayerControllerComponent的实体在输入时会自动触发
    MOVE/ATTACK/JUMP/...
    ,使用自定义攻击动作时,需通过**
    RemoveActionSheet
    SetActionSheet
    **整理冲突键(需在服务器调用)。
  6. 游戏逻辑在
    @ExecSpace("ServerOnly")
    执行,动画播放在**
    @ExecSpace("Client")
    **执行,需分离处理。
  7. 若仅需修改攻击动作的武器类型,最简单的方法是替换
    CostumeManagerComponent
    武器插槽的RUID
    (策略D)。