msw-avatar
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMSW 아바타 (코스튬 · 애니메이션)
MSW 角色(Avatar)(服装 · 动画)
아바타는 두 축으로 관리한다.
- 코스튬(외형): — 어떤 장비를 착용하는가(17슬롯).
MOD.Core.CostumeManagerComponent - 애니메이션(동작): +
AvatarStateAnimationComponent— 어떤 상태 클립을 재생하는가(기본 14상태 + 커스텀 액션).AvatarRendererComponent
예전 Maker RPC( / )는 사용하지 않는다. 워크스페이스 파일을 직접 편집하고, 변경이 에디터에 반영되도록 의 도구를 호출한다.
get_costumeset_costumemsw-maker-mcprefresh아래 문서는 먼저 코스튬(편집 기반)을, 마지막에 애니메이션(스크립트 기반)을 다룬다.
워크스페이스 경로 규칙: 맵, UI./map/, 스크립트·기타 에셋./ui/, DefaultPlayer·Player 등 글로벌 모델./RootDesk/MyDesk/./Global/
角色通过两大维度进行管理:
- 服装(外观):—— 穿戴的装备(17个插槽)。
MOD.Core.CostumeManagerComponent - 动画(动作):+
AvatarStateAnimationComponent—— 播放的状态片段(默认14种状态 + 自定义动作)。AvatarRendererComponent
不再使用旧版Maker RPC( / )。需直接编辑工作区文件,并调用**的工具**使变更同步到编辑器。
get_costumeset_costumemsw-maker-mcprefresh本文档先介绍基于编辑操作的服装系统,最后介绍基于脚本的动画系统。
工作区路径规则:地图文件、UI文件./map/、脚本及其他资源./ui/、DefaultPlayer·Player等全局模型./RootDesk/MyDesk/./Global/
작업 대상별 편집 위치
按操作对象划分的编辑位置
| 대상 | 편집 파일 | 비고 |
|---|---|---|
| DefaultPlayer | | |
| Player (베이스) | | 보통 코스튬 기본값은 여기보다 DefaultPlayer.model에서 오버라이드 |
| 맵에 배치된 엔티티 (NPC, 몬스터 등) | | 해당 엔티티의 |
| 커스텀 모델만 참조하는 엔티티 | 해당 | 맵에 인라인 컴포넌트가 없고 |
조회(get에 해당): 위 파일을 읽어 관련 필드· 항목을 확인한다. Maker MCP가 연결되어 있다면 로 런타임/에디터 스냅샷을 보조 조회할 수 있다( 스킬 참고).
CostumeManagerComponentValuesget_componentmsw-maker-mcp적용(set에 해당): 파일에 값을 쓴 뒤 호출.
refresh| 对象 | 编辑文件 | 备注 |
|---|---|---|
| DefaultPlayer | | 在 |
| Player(基础模型) | | 通常服装默认值会在DefaultPlayer.model中覆盖,而非此处 |
| 地图中放置的实体(NPC、怪物等) | | 对应实体的 |
| 仅引用自定义模型的实体 | 对应 | 若地图中无内联组件,仅通过 |
查询(对应get操作):读取上述文件,查看相关字段及项。若已连接Maker MCP,可通过辅助查询运行时/编辑器快照(参考技能)。
CostumeManagerComponentValuesget_componentmsw-maker-mcp应用(对应set操作):在文件中写入值后,调用。
refresh변경 반영: MCP refresh
refresh变更同步:MCP refresh
refresh파일 저장 후 반드시 서버의 도구를 호출하여 Maker와 비주얼 상태를 동기화한다. ( 스킬의 도구 목록 참고)
msw-maker-mcprefreshmsw-maker-mcp文件保存后必须调用服务器的****工具,同步Maker与可视化状态。(参考技能的工具列表)
msw-maker-mcprefreshmsw-maker-mcpRUID(리소스 고유 ID)
RUID(资源唯一ID)
코스튬에 넣는 문자열은 아바타 아이템의 RUID(일반적으로 32자 hex)이다.
- RUID를 추측·임의 생성하면 안 된다. 스킬 및
msw-search(기본 body/head, 아이템 상세, 렌더 조합),references/avatar.md,references/search.md등으로 검색·조회하여 확보한다.references/detail.md - 스크립트 API 와 동일하게, 에디터/모델에 저장하는 것도 결국 같은 RUID 문자열이다.
SetEquip(MapleAvatarItemCategory, itemRUID)
服装中填入的字符串是角色道具的RUID(通常为32位十六进制字符串)。
- 不可猜测或随意生成RUID。需通过技能及**
msw-search**(默认身体/头部、道具详情、渲染组合)、references/avatar.md、references/search.md等进行搜索查询获取。references/detail.md - 与脚本API 相同,编辑器/模型中保存的也是相同的RUID字符串。
SetEquip(MapleAvatarItemCategory, itemRUID
CostumeManagerComponent 개요
CostumeManagerComponent 概述
플레이어·NPC 등 아바타를 쓰는 엔티티에 부착된다. 장비 슬롯은 커스텀 장비용 문자열 프로퍼티 17개()로 표현되며, 스크립트에서는 / 과 로 접근한다.
Custom*EquipGetEquipSetEquipMapleAvatarItemCategory附加在玩家·NPC等使用角色的实体上。装备插槽通过17个自定义装备字符串属性()表示,脚本中可通过 / 与进行访问。
Custom*EquipGetEquipSetEquipMapleAvatarItemCategory기타 동기화 프로퍼티
其他同步属性
| 프로퍼티 | 타입 | 설명 |
|---|---|---|
| UseCustomEquipOnly | | |
| DefaultEquipUserId | | 지정한 유저의 장비를 복제한 뒤, 그 위에 커스텀 장비를 얹는 방식. 접속하지 않은 유저도 지정 가능. 대상 유저가 이후 장비를 바꾸면 반영이 달라질 수 있다. |
| EquippedItems | 읽기 전용 | 런타임에서 실제 장착 정보. 스크립트에서 수정 불가. |
| 属性 | 类型 | 说明 |
|---|---|---|
| UseCustomEquipOnly | | 设为 |
| DefaultEquipUserId | | 复制指定用户的装备后,在其基础上叠加自定义装备。未登录用户也可指定。若目标用户后续修改装备,可能会影响当前设置。 |
| EquippedItems | 只读 | 运行时的实际装备信息。脚本无法修改。 |
17 슬롯 ↔ 프로퍼티 ↔ MapleAvatarItemCategory
17插槽 ↔ 属性 ↔ MapleAvatarItemCategory
CostumeManagerComponentMapleAvatarItemCategoryEnvironment/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) | 귀(신체 파츠) |
CostumeManagerComponentMapleAvatarItemCategoryEnvironment/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) | 耳朵(身体部位) |
enum에만 있고 위 17필드에 직접 대응하지 않는 항목
仅存在于枚举中、无对应17字段的项
| MapleAvatarItemCategory | 설명 |
|---|---|
| Head (2) | “장비로 쓰지 않음”에 가깝고 바디 색에 맞춰 자동 처리되는 분류. |
| Invalid (0) | 오류/미정의 검출용. |
| Shield (11) | enum 주석상 보조 무기 슬롯(SubWeapon) 을 사용. 실질 저장은 CustomSubWeaponEquip 쪽과 배타적으로 정리하는 것이 안전하다. |
| MapleAvatarItemCategory | 说明 |
|---|---|
| Head (2) | 近乎“不用于装备”,会根据身体颜色自动处理。无 |
| Invalid (0) | 用于检测错误/未定义情况。 |
| Shield (11) | 枚举注释中说明使用副武器插槽(SubWeapon)。实际存储时,建议与CustomSubWeaponEquip互斥设置以避免冲突。 |
상호 배타·슬롯 점유 규칙 (필수 이해)
互斥·插槽占用规则(必须理解)
-
롱코트 ↔ 코트 + 바지
Longcoat는 설계상 코트(Coat)와 바지(Pants) 슬롯을 함께 사용한다. 롱코트를 착용시키면 롱코트 RUID는에 두고, 상·하의를 따로 보이게 하려면 코트/바지와의 조합을 논리적으로 정리한다(통상 롱코트 사용 시 코트·바지 개별 장비는 비우거나 충돌을 피한다).CustomLongcoatEquip -
두손 무기 ↔ 한손 무기 + 보조
TwoHandedWeapon은 한손 무기 슬롯과 보조 무기 슬롯을 함께 쓴다. 두손 무기를 쓰면 CustomTwoHandedWeaponEquip를 중심으로 맞추고, 한손·보조와의 이중 장착이 되지 않게 값을 정리한다. -
방패(Shield) ↔ 보조 무기
Shield는 보조 무기 슬롯을 쓴다. CustomSubWeaponEquip와 동시에 다른 보조 장비를 기대하지 않도록 한다. -
빈 문자열로 해제
스크립트와 같이, 파일에서도SetEquip(category, "")로 두면 해당 슬롯 해제로 이해하면 된다.""
-
长款外套 ↔ 外套 + 裤子
Longcoat在设计上同时占用外套(Coat)和裤子(Pants)插槽。穿戴长款外套时,需将长款外套RUID放在中,若要单独显示上衣/下装,需逻辑上整理外套/裤子的组合(通常穿戴长款外套时,需清空或避免外套·裤子的单独装备冲突)。CustomLongcoatEquip -
双手武器 ↔ 单手武器 + 副武器
TwoHandedWeapon会同时占用单手武器插槽和副武器插槽。使用双手武器时,需以为核心设置,确保不会与单手·副武器重复穿戴。CustomTwoHandedWeaponEquip -
盾牌(Shield) ↔ 副武器
Shield占用副武器插槽。请勿同时设置与其他副装备。CustomSubWeaponEquip -
空字符串解除装备
如同脚本,在文件中设置为**SetEquip(category, "")时,视为解除对应插槽的装备**。""
DefaultPlayer.model — Values
에 코스튬 넣기
ValuesDefaultPlayer.model —— 在Values
中设置服装
Values./Global/DefaultPlayer.modelContentProto.Json.Values- TargetType:
"MOD.Core.CostumeManagerComponent" - Name: 위 표의 프로퍼티 이름 (예: ,
CustomCapEquip)UseCustomEquipOnly - ValueType: 기존 의 다른
DefaultPlayer.model항목과 동일한 패턴을 따른다. 문자열은Values, 불리언은System.String, mscorlib, ...System.Boolean, mscorlib, ... - Value: RUID 문자열 또는 /
truefalse
동일 가 이미 있으면 그 항목만 갱신하고, 없으면 배열에 객체 하나 추가한다.
(TargetType, Name)在的****数组中添加或修改项。
./Global/DefaultPlayer.modelContentProto.Json.Values- TargetType:
"MOD.Core.CostumeManagerComponent" - Name: 上述表格中的属性名(例:,
CustomCapEquip)UseCustomEquipOnly - ValueType: 遵循中其他
DefaultPlayer.model项的相同模式。字符串类型为Values,布尔类型为System.String, mscorlib, ...System.Boolean, mscorlib, ... - Value: RUID字符串或/
truefalse
若相同已存在,仅更新该条目;若不存在,向数组中添加一个对象。
(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
文件中修改
.map./map/- 배열에서 대상 엔티티(이름·path·id로 식별)를 찾는다.
ContentProto.Entities - 배열에서
jsonString["@components"]인 객체를 찾는다."@type": "MOD.Core.CostumeManagerComponent" - 그 객체의 ,
Custom*Equip,UseCustomEquipOnly등을 직접 수정한다.DefaultEquipUserId - 문자열 목록에
componentNames가 포함되어 있는지 확인하고, 컴포넌트 배열과 불일치하면 안 된다.MOD.Core.CostumeManagerComponent
맵이 바이너리 포맷만 쓰는 경우 등은 워크스페이스 정책에 따라 편집 도구가 다를 수 있다. JSON 텍스트로 열리는 경우 위 구조를 따른다.
打开下对应地图的实体记录。
./map/- 在数组中找到目标实体(通过名称·路径·ID识别)。
ContentProto.Entities - 在数组中找到**
jsonString["@components"]**的对象。"@type": "MOD.Core.CostumeManagerComponent" - 直接修改该对象的****,
Custom*Equip, **UseCustomEquipOnly**等属性。DefaultEquipUserId - 确认字符串列表中包含
componentNames,需与组件数组保持一致。MOD.Core.CostumeManagerComponent
若地图仅使用二进制格式等,编辑工具可能因工作区政策不同而有所差异。若以JSON文本打开,则遵循上述结构。
GET /v3/avatars 검색 결과 → 슬롯 매핑
GET /v3/avatars搜索结果 → 插槽映射
GET /v3/avatarscategoryCustom*Equipmsw-searchreferences/resource/avatar.mdAPI | | |
|---|---|---|
| | Body (1) |
| | Hair (3) |
| | Face (4) |
| | FaceAccessory (16) |
| | EyeAccessory (17) |
| | EarAccessory (18) |
| | Cap (5) |
| | Cape (6) |
| | Longcoat (9) |
| | Coat (7) |
| | Pants (10) |
| | Glove (8) |
| | Shoes (12) |
| | OneHandedWeapon (13) |
| | TwoHandedWeapon (14) |
| | SubWeapon (15) |
| | Shield (11) — SubWeapon 슬롯 공유 |
将获取的道具字段映射到属性。搜索方法参考技能 → 。
GET /v3/avatarscategoryCustom*Equipmsw-searchreferences/resource/avatar.mdAPI | | |
|---|---|---|
| | Body (1) |
| | Hair (3) |
| | Face (4) |
| | FaceAccessory (16) |
| | EyeAccessory (17) |
| | EarAccessory (18) |
| | Cap (5) |
| | Cape (6) |
| | Longcoat (9) |
| | Coat (7) |
| | Pants (10) |
| | Glove (8) |
| | Shoes (12) |
| | OneHandedWeapon (13) |
| | TwoHandedWeapon (14) |
| | SubWeapon (15) |
| | Shield (11) —— 共享SubWeapon插槽 |
아바타 리소스 검색 참고
角色资源搜索参考
- 스킬 →
msw-search:references/resource/avatar.md(코스튬 검색), 기본 body/head,GET /v3/avatars, 렌더 조합 등 아바타 파이프라인 상세.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 키 | 대문자 영문 | |
| AvatarBodyActionStateName (Value 쪽) | 소문자 영문 | |
| MapleAvatarBodyActionState (enum) | Pascal case | |
| CoreActionName/PartsActionName (실제 스프라이트 액션 ID) | 소문자+숫자 | |
흔한 혼동: "attack"은 State 가 아니다. State는 대문자, 매핑 Value는 소문자ATTACK(= MapleAvatarBodyActionState.Attack), 그리고 그 Value가 다시 무기에 따라attack/swingO1같은 스프라이트 액션 ID로 풀린다.shoot1
角色动画通过三层流水线流转。若仅关注单个层级,可能因其他层级覆盖导致不符合预期的动作。
[1] 输入 / 游戏逻辑
│ PlayerControllerComponent · 脚本
▼
[2] StateComponent ──── StateChangeEvent ────▶ AvatarStateAnimationComponent
(示例: "ATTACK") (CurrentStateName) (StateToAvatarBodyActionSheet
或ActionSheet查找)
│
▼
[3] AvatarRendererComponent ◀── BodyActionStateChange / ActionStateChanged ── 身体实体
(实际精灵播放)核心区分:
| 术语 | 格式 | 示例 |
|---|---|---|
| State键 | 大写英文 | |
| AvatarBodyActionStateName(值端) | 小写英文 | |
| MapleAvatarBodyActionState(枚举) | 帕斯卡命名法 | |
| CoreActionName/PartsActionName(实际精灵动作ID) | 小写+数字 | |
常见混淆:"attack"不是State。State是大写的,映射值是小写的ATTACK(对应MapleAvatarBodyActionState.Attack),该值会根据武器进一步解析为attack/swingO1等精灵动作ID。shoot1
AvatarStateAnimationComponent — 상태↔동작 매핑
AvatarStateAnimationComponent —— 状态↔动作映射
MOD.Core.AvatarStateAnimationComponent| 프로퍼티 | 사용 조건 | 형식 | 비고 |
|---|---|---|---|
| 두 시스템 선택 스위치 | | |
| | | |
| | | |
MOD.Core.AvatarStateAnimationComponent| 属性 | 使用条件 | 格式 | 备注 |
|---|---|---|---|
| 系统切换开关 | | 设为 |
| | | 如 |
| | | 如 |
StateToAvatarBodyActionSheet
기본 매핑 (IsLegacy=false 기본 11개 키)
StateToAvatarBodyActionSheetStateToAvatarBodyActionSheet
默认映射(IsLegacy=false默认11个键)
StateToAvatarBodyActionSheet| Key (State) | AvatarBodyActionStateName | PlayRate | 트리거 조건(PlayerControllerComponent 동반 시) |
|---|---|---|---|
| | 1.0 | 입력 없음 |
| | 1.68 | 좌/우 이동 |
| | 1.33 | Left Ctrl (Attack 액션) |
| | 1.0 | HitComponent 피격 처리 |
| | 1.0 | 아래 방향키 |
| | 1.0 | 공중에서 낙하 |
| | 1.0 | Space (Jump 액션) |
| | 1.0 | 로프 진입 |
| | 1.0 | 사다리 진입 |
| | 1.0 | 사망 |
| | 1.0 | C (Sit 액션) |
State 키는 대문자, AvatarBodyActionStateName 값은 소문자라는 점에 주의.
| Key(State) | AvatarBodyActionStateName | PlayRate | 触发条件(附带PlayerControllerComponent时) |
|---|---|---|---|
| | 1.0 | 无输入 |
| | 1.68 | 左/右移动 |
| | 1.33 | Left Ctrl(Attack动作) |
| | 1.0 | HitComponent受击处理 |
| | 1.0 | 下方向键 |
| | 1.0 | 空中下落 |
| | 1.0 | Space(Jump动作) |
| | 1.0 | 进入绳索 |
| | 1.0 | 进入梯子 |
| | 1.0 | 死亡 |
| | 1.0 | C(Sit动作) |
注意:State键为大写,AvatarBodyActionStateName值为小写。
MapleAvatarBodyActionState
→ 실제 액션 ID 디폴트 변환표
MapleAvatarBodyActionStateMapleAvatarBodyActionState
→ 实际动作ID默认转换表
MapleAvatarBodyActionStateAvatarBodyActionStateName"attack""stand"MapleAvatarBodyActionStateActionStateChangedEvent| MapleAvatarBodyActionState | CoreActionName | PartsActionName | PlayRate | PlayType |
|---|---|---|---|---|
| Stand | | 동일 | 1 | ZigzagLoop |
| Walk | | 동일 | 1 | Loop |
| Attack | | | 1 | Loop |
| Crouch | | | 1 | Loop |
| Fall | | | 1 | Loop |
| Sit | | | 1 | Loop |
| Rope | | | 1 | Loop |
| Ladder | | | 1 | Loop |
| Dead | | | 1 | Loop |
| Blink | | | 1 | Loop |
| Fly | | | 1 | Loop |
| Hit | | | 1 | ZigzagLoop |
| Alert | | | 1 | ZigzagLoop |
| Heal | | | 1 | Loop |
무기를 장착하면은 무기 종류에 맞는 스프라이트 액션 ID로 자동 치환된다 (다음 절 표 참조). 즉 한손검을 쥐면 칼 휘두르기, 활을 쥐면 활 쏘기 모션이 나온다.Attack
AvatarBodyActionStateName"attack""stand"MapleAvatarBodyActionStateActionStateChangedEvent| MapleAvatarBodyActionState | CoreActionName | PartsActionName | PlayRate | PlayType |
|---|---|---|---|---|
| Stand | | 相同 | 1 | ZigzagLoop |
| Walk | | 相同 | 1 | Loop |
| Attack | | | 1 | Loop |
| Crouch | | | 1 | Loop |
| Fall | | | 1 | Loop |
| Sit | | | 1 | Loop |
| Rope | | | 1 | Loop |
| Ladder | | | 1 | Loop |
| Dead | | | 1 | Loop |
| Blink | | | 1 | Loop |
| Fly | | | 1 | Loop |
| Hit | | | 1 | ZigzagLoop |
| Alert | | | 1 | ZigzagLoop |
| Heal | | | 1 | Loop |
装备武器后,会自动替换为对应武器类型的精灵动作ID(参考下一节表格)。例如手持单手剑时会播放挥剑动作,手持弓时会播放射箭动作。Attack
무기별 attack
풀이 — 스프라이트 액션 ID 후보군
attack按武器分类的attack
解析 —— 精灵动作ID候选集
attackATTACK| 무기 분류 | 사용되는 CoreActionName/PartsActionName 후보 |
|---|---|
한손검/단검 ( | |
두손검/해머 ( | |
활 ( | |
| 스태프/완드 | |
| 무기 없음 (기본 body) | 별도 attack 클립 없음 → |
같은 분류라도 아이템 메타에 따라 사용되는 액션 ID 집합이 다를 수 있다. 위 표는 SDK 가이드()가 사용하는 대표 후보군._ActionNameLogic
触发状态时,引擎会根据装备的武器(MapleAvatarItemCategory)选择以下ID之一播放动作。
ATTACK| 武器分类 | 使用的CoreActionName/PartsActionName候选 |
|---|---|
单手剑/短剑 ( | |
双手剑/锤子 ( | |
弓( | |
| 法杖/魔杖 | |
| 无武器(默认身体) | 无单独attack片段 → 显示 |
即使属于同一分类,根据道具元数据的不同,使用的动作ID集合也可能不同。上述表格为SDK指南()使用的典型候选集。_ActionNameLogic
PlayerControllerComponent와 자동 상태 추가
PlayerControllerComponent与自动状态添加
플레이어 엔티티에 가 붙어 있으면 에 다음 State가 자동으로 추가되고 키 입력에 따라 자동 전이된다.
MOD.Core.PlayerControllerComponentStateComponentMOVECLIMBLADDERCROUCHJUMPFALLATTACKATTACK_WAITSIT따라서 DefaultPlayer가 Ctrl을 누르면 자동으로 ATTACK 상태가 되고, 위 매핑에 따라 attack 바디 동작(=무기별 칼/활/지팡이 휘두르기)이 자동 재생된다. 스크립트로 별도 처리를 안 해도 칼은 휘둘러진다.
若玩家实体附加了,则会自动添加以下State,并根据按键输入自动切换:
MOD.Core.PlayerControllerComponentStateComponentMOVECLIMBLADDERCROUCHJUMPFALLATTACKATTACK_WAITSIT因此,DefaultPlayer按下Ctrl时会自动进入ATTACK状态,并根据上述映射自动播放attack身体动作(即对应武器的挥剑/射箭/挥杖动作)。无需额外脚本处理,即可实现挥剑动作。
자동 재생 ↔ 수동 ActionStateChangedEvent 충돌 (★ 자주 만나는 함정)
自动播放 ↔ 手动ActionStateChangedEvent冲突(★ 常见陷阱)
증상: 스크립트에서 로 같은 커스텀 액션을 보내도, 여전히 칼 휘두르기(또는 무기 기본 attack) 가 나오거나 한 프레임만 스치고 곧 덮인다.
ActionStateChangedEventshoot1원인: 상태가 활성인 동안 가 매핑된 바디 동작을 지속적으로 보낸다. 우리가 보낸 1회성 이벤트는 즉시 덮인다.
ATTACKAvatarStateAnimationComponentattack症状:脚本通过发送等自定义动作,但仍显示挥剑(或武器默认attack)动作,或仅闪一下就被覆盖。
ActionStateChangedEventshoot1原因:状态激活期间,会持续发送映射的身体动作。我们发送的一次性事件会立即被覆盖。
ATTACKAvatarStateAnimationComponentattack해결 전략
解决策略
| 전략 | 방법 | 언제 쓰나 |
|---|---|---|
| A. 매핑 제거 | | 공격 모션을 완전히 커스텀으로 대체할 때 (활 쏘기, 마법 시전 등) |
| B. 매핑 변경 | | 다른 내장 상태 애니메이션으로 바꾸고 싶을 때 (예: ATTACK→heal) |
| C. 강제 리셋 | | 같은 상태를 재시작하고 싶을 때 |
| D. 무기 교체 | | 단순히 attack 모션의 무기 종류만 바꾸고 싶을 때 (가장 직관적) |
| 策略 | 方法 | 使用场景 |
|---|---|---|
| A. 移除映射 | 使用 | 需完全自定义攻击动作时(如射箭、施法等) |
| B. 修改映射 | 使用 | 需切换为其他内置状态动画时(例:ATTACK→heal) |
| C. 强制重置 | 发送 | 需重新启动同一状态时 |
| D. 更换武器 | 将 | 仅需修改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.modelValuesshoot1json
{
"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
}在的中添加弓的RUID,即使保持映射不变,引擎也会在ATTACK状态下自动选择动作。
./Global/DefaultPlayer.modelValuesshoot1json
{
"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 自定义动作 —— 哪些需要手动触发
AvatarStateAnimationComponentMapleAvatarBodyActionStateswingO2shoot1dancecast1AvatarStateAnimationComponentMapleAvatarBodyActionStateswingO2shoot1dancecast1엔진이 자동 처리하는 14개 바디 동작 (= MapleAvatarBodyActionState 멤버)
引擎自动处理的14种身体动作(=MapleAvatarBodyActionState成员)
StateToAvatarBodyActionSheetActionSheet| 바디 동작 이름 | enum | 의미 |
|---|---|---|
| Stand | 대기 |
| Walk | 이동 |
| Attack | 공격(무기에 따라 sprite ID 자동 결정) |
| Hit | 피격 |
| Crouch | 웅크리기 |
| Fall | 낙하 |
| Rope | 로프 잡기 |
| Ladder | 사다리 |
| Dead | 사망 |
| Sit | 앉기 |
| Heal | 회복 |
| Alert | 경계 |
| Fly | 비행 |
| Blink | 눈 깜박임 |
可作为/的值。只需完成状态映射即可自动播放。
StateToAvatarBodyActionSheetActionSheet| 身体动作名称 | 枚举 | 含义 |
|---|---|---|
| Stand | 待机 |
| Walk | 移动 |
| Attack | 攻击(根据武器自动确定sprite ID) |
| Hit | 受击 |
| Crouch | 下蹲 |
| Fall | 下落 |
| Rope | 抓绳索 |
| Ladder | 爬梯子 |
| Dead | 死亡 |
| Sit | 坐下 |
| Heal | 恢复 |
| Alert | 警戒 |
| Fly | 飞行 |
| Blink | 眨眼 |
그 외 커스텀 동작 — ActionStateChangedEvent
로 직접 재생
ActionStateChangedEvent其他自定义动作 —— 通过ActionStateChangedEvent
手动播放
ActionStateChangedEvent14개 enum 외의 임의 스프라이트 액션 ID(예: , , , , , 등)는 다음 절차로 재생한다.
shoot1swingO2cast1throw1dancecheer재생 파이프라인
- 엔티티의 에서
AvatarRendererComponent로 아바타 바디 엔티티를 얻는다. 애니메이션 이벤트는 아바타 본체가 아니라 바디 엔티티에 보낸다.GetBodyEntity() - 를 생성하고 필드를 채운다.
ActionStateChangedEvent - 로 바디 엔티티에 전송한다.
body:SendEvent(event) - 애니메이션 재생은 각 클라이언트에서 보이기만 하면 되므로 보통 로 한정한다. 게임 로직(데미지, 투사체 스폰 등)은
@ExecSpace("Client")쪽에서 처리한다.ServerOnly
ActionStateChangedEventActionStateChangedEvent(coreActionName, partsActionName, playRate=1, playType=Loop, startFrameIndex=0, endFrameIndex=2147483647)| 필드 | 타입 | 기본값 | 설명 |
|---|---|---|---|
| string | | 코어 파츠(바디)에서 재생할 애니메이션 ID. 필수 (예: |
| string | | 부위 파츠에서 재생할 애니메이션 ID. 필수 — 보통 |
| float | | 재생 속도 배율 ( |
| | | |
| int32 | | 시작 프레임 (음수면 0으로 보정) |
| int32 | | 끝 프레임 (총 프레임 초과 시 자동 보정) |
SpriteAnimClipPlayType| 값 | 의미 |
|---|---|
| 1회 재생 후 정지 |
| 0→끝 반복 |
| 0→끝→0 왕복 반복 |
14种枚举之外的任意精灵动作ID(例:, , , , , 等)需按以下步骤播放:
shoot1swingO2cast1throw1dancecheer播放流水线
- 通过实体的调用**
AvatarRendererComponent获取角色身体实体**。动画事件需发送给角色身体实体,而非角色根实体。GetBodyEntity() - 创建****并填充字段。
ActionStateChangedEvent - 通过发送给身体实体。
body:SendEvent(event) - 动画播放仅需在各客户端显示,因此通常限定为****。游戏逻辑(伤害、投射物生成等)需在
@ExecSpace("Client")中处理。ServerOnly
ActionStateChangedEventActionStateChangedEvent(coreActionName, partsActionName, playRate=1, playType=Loop, startFrameIndex=0, endFrameIndex=2147483647)| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| string | | 核心部件(身体)播放的动画ID。必填(例: |
| string | | 部位部件播放的动画ID。必填 —— 通常与 |
| float | | 播放速度倍率( |
| | | |
| int32 | | 起始帧(负数会修正为0) |
| int32 | | 结束帧(超过总帧数会自动修正) |
SpriteAnimClipPlayType| 值 | 含义 |
|---|---|
| 播放一次后停止 |
| 从0到结束循环播放 |
| 从0到结束再回到0往复循环播放 |
BodyActionStateChangeEvent
— 14개 내장 상태 고수준 이벤트
BodyActionStateChangeEventBodyActionStateChangeEvent
—— 14种内置状态的高级事件
BodyActionStateChangeEventMapleAvatarBodyActionStateneedResetAction=trueSendEventActionStateChangedEventself.Entitylua
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)로 변환되어 전달| 필드 | 설명 |
|---|---|
| |
| |
| |
선택 기준
- 임의 스프라이트 액션 ID(,
shoot1,swingO2등) →dance(바디 엔티티에 전송)ActionStateChangedEvent - enum에 있는 14개 상태(Stand/Walk/Attack/...) → (루트 엔티티에 전송)
BodyActionStateChangeEvent
可直接指定枚举进行变更。无需记忆各武器对应的动作ID,且当需要重新播放同一状态时,可通过强制重置。
MapleAvatarBodyActionStateneedResetAction=trueSendEventActionStateChangedEventself.Entitylua
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)发送| 字段 | 说明 |
|---|---|
| |
| 设为 |
| 与 |
选择标准
- 任意精灵动作ID(,
shoot1,swingO2等) → 使用dance(发送给身体实体)ActionStateChangedEvent - 枚举内置的14种状态(Stand/Walk/Attack/...) → 使用(发送给根实体)
BodyActionStateChangeEvent
예시 — 화살 발사(shoot
) 애니메이션 재생
shoot示例 —— 播放射箭(shoot
)动画
shoot서버에서 공격 입력을 받으면 투사체를 스폰하고, 클라이언트에서 액션을 재생하는 전형적인 패턴이다.
shoot1lua
@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服务器接收攻击输入后生成投射物,客户端播放动作的典型模式。
shoot1lua
@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- YES → 의 해당 슬롯에 클립을 지정만 하면 된다. 스크립트 불필요.
AvatarStateAnimationComponent - NO → 아래로.
- YES →
- 커스텀 액션(예: ,
shoot1,cast1)은dance를 만들어ActionStateChangedEvent가 반환한 바디 엔티티에AvatarRendererComponent:GetBodyEntity()로 보낸다.SendEvent - 입력 처리·데미지 판정은 서버()에서, 애니메이션 재생은 클라이언트(
ServerOnly) 에서 나누어 실행한다.Client
- 要播放的动作是否属于默认14种状态(,
stand,walk,attack,hit,crouch,fall,rope,ladder,dead,sit,heal,alert,fly)之一?blink- 是 → 只需在的对应插槽指定片段即可,无需脚本。
AvatarStateAnimationComponent - 否 → 继续下一步。
- 是 → 只需在
- 自定义动作(例:,
shoot1,cast1)需创建**dance,并发送给ActionStateChangedEvent返回的身体实体**。AvatarRendererComponent:GetBodyEntity() - 输入处理·伤害判定在服务器()执行,**动画播放在客户端(
ServerOnly)**执行,需分开处理。Client
자주 발생하는 실수
常见错误
- State 키와 AvatarBodyActionStateName(=enum)을 혼동. State 키는 대문자(), 매핑 Value는 소문자(
ATTACK).attack의 Key/Value를 뒤집어 넣으면 매핑이 작동하지 않는다.StateToAvatarBodyActionSheet - 자동 재생을 끄지 않고 ActionStateChangedEvent만 보내는 경우. Ctrl 입력 시 상태가 자동 활성화되어 매핑된 attack 바디 동작이 우리 이벤트를 즉시 덮는다. 커스텀 공격 모션을 쓰려면 반드시
ATTACK또는RemoveActionSheet("ATTACK")으로 매핑을 정리해야 한다.SetActionSheet("ATTACK", "<원하는 동작>") - 의 SendEvent 대상: 반드시
ActionStateChangedEvent가 반환한 바디 엔티티.AvatarRendererComponent:GetBodyEntity()(아바타 루트)나 컴포넌트에 보내면 재생되지 않는다. (반대로self.Entity는 루트 엔티티에 보낸다.)BodyActionStateChangeEvent - 에 임의 상태명을 넣는 시도. 14개 enum(
AvatarStateAnimationComponent) 외 이름(MapleAvatarBodyActionState,shoot,cast)을 Value로 넣으면 무시된다. 임의 ID는dance경로로만.ActionStateChangedEvent - 빠뜨리기.
PartsActionName만 채우면 부위(무기·모자·망토 등) 애니메이션이 풀리지 않아 상체만 동작하고 무기는 멈춘 채 보일 수 있다. 보통CoreActionName과 같은 값을 사용.CoreActionName - 미지정. 기본값이
PlayType이라서 1회성 액션이 무한 반복된다. 1회성은 명시적으로Loop.SpriteAnimClipPlayType.Onetime - /
RemoveActionSheet를 클라이언트에서 호출.SetActionSheet는StateToAvatarBodyActionSheet동기화 프로퍼티이므로 서버에서 호출해야 모든 클라이언트에 반영된다.@Sync - 서버/클라이언트 실행 공간 분리 누락. 게임 로직(데미지·투사체) = , 애니메이션 재생 =
ServerOnly. 두 경로를 한 곳에 몰아넣으면 클라마다 중복 재생되거나 시각 누락이 생긴다.Client - 무기를 안 끼우고 활 모션만 기대. 액션을 쏘면 바디는 활 자세가 되지만, 활 RUID가
shoot1에 없으면 손에 활이 그려지지 않는다. 시각적으로 자연스러우려면 모션과 무기를 함께 세팅한다.CustomTwoHandedWeaponEquip
- 混淆State键与AvatarBodyActionStateName(=枚举)。State键为大写(),映射值为小写(
ATTACK)。若颠倒attack的Key/Value,映射将无法生效。StateToAvatarBodyActionSheet - 未关闭自动播放仅发送ActionStateChangedEvent。按下Ctrl时状态会自动激活,映射的attack身体动作会立即覆盖我们的事件。使用自定义攻击动作时,必须通过
ATTACK或RemoveActionSheet("ATTACK")整理映射。SetActionSheet("ATTACK", "<目标动作>") - 的SendEvent目标错误:必须发送给
ActionStateChangedEvent返回的身体实体。发送给AvatarRendererComponent:GetBodyEntity()(角色根)或组件将无法播放。(相反,self.Entity需发送给根实体)BodyActionStateChangeEvent - 尝试在中添加任意状态名。14种枚举(
AvatarStateAnimationComponent)之外的名称(MapleAvatarBodyActionState,shoot,cast)作为值会被忽略。任意ID需通过dance路径处理。ActionStateChangedEvent - 遗漏:仅填充
PartsActionName会导致部位(武器·帽子·披风等)动画无法解析,可能出现上半身动作但武器静止的情况。通常使用与CoreActionName相同的值。CoreActionName - 未指定:默认值为
PlayType,会导致一次性动作无限循环。一次性动作需显式设置为Loop。SpriteAnimClipPlayType.Onetime - 在客户端调用/
RemoveActionSheet。SetActionSheet是StateToAvatarBodyActionSheet同步属性,需在服务器调用才能同步到所有客户端。@Sync - 未分离服务器/客户端执行空间。游戏逻辑(伤害·投射物)= ,动画播放 =
ServerOnly。若混在一起,会导致各客户端重复播放或视觉缺失。Client - 未装备武器却期望弓动作:播放动作时身体会摆出弓的姿势,但若
shoot1中无弓的RUID,手中不会显示弓。要实现自然的视觉效果,需同时设置动作与武器。CustomTwoHandedWeaponEquip
관련 스킬
相关技能
| 스킬 | 용도 |
|---|---|
| msw-defaultplayer | |
| msw-search | RUID 검색, |
| msw-maker-mcp | |
| 技能 | 用途 |
|---|---|
| msw-defaultplayer | |
| msw-search | RUID搜索、 |
| msw-maker-mcp | |
요약 체크리스트
总结检查清单
코스튬
服装
- RUID는 resource 검색·avatar 참고 문서로 확보한다.
- DefaultPlayer/Player → 의
./Global/*.model또는 베이스 모델 정의.Values - 맵 엔티티 → 의 해당 엔티티
./map/*.map.@components - 롱코트 / 두손 무기 / 방패·보조 배타 규칙을 지킨다.
- ****로 "계정 기본 코스튬 무시" 여부를 명시한다.
UseCustomEquipOnly - 저장 후 →
msw-maker-mcp호출.refresh
- RUID需通过资源搜索·角色参考文档获取。
- DefaultPlayer/Player → 的**
./Global/*.model**或基础模型定义。Values - 地图实体 → 中对应实体的**
./map/*.map**。@components - 遵守长款外套 / 双手武器 / 盾牌·副武器的互斥规则。
- 通过****明确“是否忽略账号默认服装”。
UseCustomEquipOnly - 保存后调用**→
msw-maker-mcp**。refresh
애니메이션
动画
- State 키(대문자)와 바디 동작 이름(소문자) 구분. 형태.
StateToAvatarBodyActionSheet["ATTACK"] = AvatarBodyActionElement("attack", 1.33) - 원하는 동작이 enum 14개 바디 동작(·
stand·walk·attack·hit·crouch·fall·rope·ladder·dead·sit·heal·alert·fly) 에 포함되면 매핑만 정의하고 끝.blink - 그 외 액션 ID(,
shoot1,swingT3등)는dance생성 →ActionStateChangedEvent에AvatarRendererComponent:GetBodyEntity(). enum 안의 상태 재시작은SendEvent+ 루트 엔티티.BodyActionStateChangeEvent - /
CoreActionName/PartsActionName/PlayRate네 필드를 채우고, 1회성 액션은PlayType.SpriteAnimClipPlayType.Onetime - 자동 상태 전이와 충돌하는지 확인. PlayerControllerComponent가 붙은 엔티티는 입력 시 가 자동 발화되므로, 커스텀 공격을 쓰려면 충돌 키를
MOVE/ATTACK/JUMP/...또는RemoveActionSheet로 정리한다(서버에서 호출).SetActionSheet - 게임 로직은 , 애니메이션 재생은
@ExecSpace("ServerOnly")로 분리한다.@ExecSpace("Client") - 공격 모션의 무기 종류만 바꾸는 것이 목적이라면, 가장 단순한 방법은 의 무기 슬롯 RUID 교체 (전략 D).
CostumeManagerComponent
- 区分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()** + 根实体。BodyActionStateChangeEvent - 填充/
CoreActionName/PartsActionName/PlayRate四个字段,一次性动作需设置为**PlayType**。SpriteAnimClipPlayType.Onetime - 检查是否与自动状态切换冲突。附加PlayerControllerComponent的实体在输入时会自动触发,使用自定义攻击动作时,需通过**
MOVE/ATTACK/JUMP/...或RemoveActionSheet**整理冲突键(需在服务器调用)。SetActionSheet - 游戏逻辑在执行,动画播放在**
@ExecSpace("ServerOnly")**执行,需分离处理。@ExecSpace("Client") - 若仅需修改攻击动作的武器类型,最简单的方法是替换武器插槽的RUID(策略D)。
CostumeManagerComponent