Compare commits

..
73 Commits
Author SHA1 Message Date
joywayerandClaude Opus 5 0ffb03979f chore(scripts): RoomSceneStart + room-scene-layout meta files (Cocos auto-generated)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 21:24:14 +08:00
joywayerandClaude Code ec550ba257 fix(scripts): RoomSceneStart setPlayerData bindTo contract + runtime validation
Critical fix:
- setPlayerData fake state lacked subscribe() -> PlayerInfoView.bindTo threw
  TypeError at runtime. Switched to PlayerInfoView.applyPlayer({nickname,
  bean,avatar}) when available; falls back to bindTo + proper reactive
  store with subscribe/value if only bindTo is exposed.

Runtime validation (per CLAUDE.md 第二准则 — no silent fallback):
- computePositions / setSeatLayout: validate layout ∈ VALID_LAYOUTS
- computePositions / setPlayerCount: validate count ∈ [1,8] AND is integer
- setPlayerData: validate seat is integer ∈ [1, currentCount]
- extractPlayerViewData: validate nickname/bean/avatar types (no ?? 兜底)
- start(): validate no null/undefined entries in playerSlots

Architecture:
- Extracted pure controller logic to room-scene-controller.ts so tests can
  import without cc mock (RoomSceneStart remains thin cc wrapper that
  delegates). Matches existing pattern of room-scene-layout.ts.

Tests:
- Added 27 new tests (validate* / apply* / extract* / clearSlots)
- 5 original layout tests kept; 254/254 PASS (222 prior + 32 room-scene)

Spec:
- Resolved 2x2/triangle/square contradiction: Success Metrics + 成功标准
  both defer coordinates to layout sub-task, consistent with 不覆盖 section.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-02 21:22:04 +08:00
joywayer a03ebb45eb feat(scenes): Room.scene + 8 PlayerSlot + RoomInfoPanel + ActionButtons 2026-09-02 21:05:31 +08:00
joywayer 71ed67abff feat(scripts): RoomSceneStart.ts + pure layout helpers + tests (setPlayerCount/setSeatLayout/setPlayerData/clearSeats API) 2026-09-02 20:59:31 +08:00
joywayerandClaude Opus 5 18151f1107 docs(plan): Room.scene 实施计划 (4 task: script + scene asset + integration + runner)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 20:55:22 +08:00
joywayerandClaude Opus 5 23036f9cfa docs(spec): Room.scene 设计 (可配置玩家数 + 4 种 seat layout)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 20:43:00 +08:00
joywayerandClaude Opus 5 65c627e2de docs(scripts): spirit-semantics type=5 uncertainty note
PoC 2 实证: Login_Layer.prefab Spirit465 (type=5 _type=1 SLICED)
引用的 SpriteFrame 8e3905be-...@f9941 实际 capInsets=[0,0,0,0] (非 9-slice).
Fix path: 扩展 resolveSpriteFrameUuid 返回 capInsets, emitPrefabJson
根据 capInsets 决定 _type (all zero → 0/SIMPLE, otherwise → 1/SLICED).
当前按 type 硬编码 _type=1 与 YouleNexus 手工 prefab 1:1 一致
(手工 prefab 也有同样 bug), 运行时 SpriteFrame 实际非 SLICED.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 18:09:31 +08:00
joywayerandClaude Opus 5 d0b2265716 docs(scripts): spirit-semantics type=4 uncertainty note (Pay_Layer 11 type-4 可能 Button; 待 PoC 2 验证)
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 17:51:45 +08:00
joywayerandClaude Opus 5 (1M context> <noreply@anthropic.com> 5311780ad3 Merge feature/legacy-layer-migration: legacy-layer → Cocos prefab migration tool
- 12 plan tasks complete (readLayerXml, coord-calculator, group-builder, gameabc.min.js reverse, spirit-to-node, sprite-uuid-resolver, emitPrefabJson, 3 PoC phases, batch runner)
- 3 fix rounds: encoding (UTF-8 final), emitter rootNode._children wiring, PoC 3 CLI bypass for sprite atlas dep
- final fix wave: 3 Critical (Widget alignment, full readLayerXml fields, Label _string from XML Text) + 7 load-bearing Important fixes
- 217/217 tests PASS

Co-Authored-By: Claude Opus 5 (1M context> <noreply@anthropic.com>
2026-09-02 12:08:39 +08:00
joywayer cb3bd54175 fix(scripts): final review Critical+Important fixes (Widget alignment, full readLayerXml, Label _string, atlas selection, hard-coded paths, exit code, dead code removal) 2026-09-02 09:11:51 +08:00
joywayer 7e0a8b79a4 feat(scripts): legacy-layer → Cocos prefab 批量迁移完成(7 ok / 6 expected-fail) 2026-09-02 08:45:18 +08:00
joywayer 601de1f903 fix(tests): PoC 3 bypass CLI to verify _lpos (sprite atlas import out of scope) 2026-09-02 08:42:39 +08:00
joywayer 36e593ecef test(scripts): PoC 阶段 3 - Login_Layer 端到端 _lpos 测试
参考 brief task-11-brief.md, 验证 Layer00002.xml → Login_Layer.prefab 在
渠道logo (Spirit405) + 游客登录 (Spirit177) 两个 anchor 节点 _lpos 误差 < 0.001.

测试目前因 ImgResID 427 (00427.png) 缺失 sprite 而 FAIL — 该 throw 属
'下游不兜底' 规则预期行为 (brief 已说明 ImgResID 107 也可能 throw). 详见
task-11-report.md.
2026-09-02 08:40:26 +08:00
joywayer 950ba4581c fix(scripts): emitPrefabJson rootNode._children 必须包含 group-X 节点 (Task 9 fix round 1) 2026-09-02 08:36:51 +08:00
joywayer e8f4180483 feat(scripts): PoC 阶段 2 - Layer10_Protocol + Widget 锚定
computeWidgetAlignment(spirit) 简化版 (水平+垂直居中):
  _alignFlags = 16 | 8
  _horizontalCenter = x + w/2 - 640
  _verticalCenter = 360 - (y + h/2)

emitPrefabJson 仅在 Spirit 父节点是 group (非 Layer 根) 时调用;
root / group-X envelope 沿用默认 _alignFlags=45 全屏铺满.

PoC 验证: Layer00010.xml Protol_Layer (group 37, 4 spirits)
生成 Protol_Layer.prefab — 6 Widget 节点 (1 root + 1 group-37 + 4 spirit),
4 spirit widget 全部 _alignFlags=24.
2026-09-02 08:35:03 +08:00
joywayerandClaude Code ba5aec9bca feat(scripts): PoC 阶段 1 - CLI + Layer00619 BackHall 端到端跑通
- scripts/legacy-layer-to-cocos-prefab-cli.mjs (CLI 入口: <inputXml> [<youlePrefab>] <outputDir>)
- framework-tests/legacy-layer-migration/poc-iconbutton.test.mjs (1 个 node:test 端到端校验 cc.Prefab 头 + group-55 父节点)
- YouleNexus/out/legacy-migration/BackHall_Layer.prefab (生成产物, 437 lines)
- YouleNexus/out/legacy-migration/BackHall_Layer.prefab.diff (vs YouleNexus Layer619_BackOtherGame.prefab, 1244 lines)

累计: 30/30 PASS (29 prior + 1 PoC). Layer00619 (1 Spirit, type=3 ProgressBar, group=55) 是最简 PoC, diff 大是预期 (脚本首个端到端输出 vs 手写 prefab 必然差异大).

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-02 08:30:34 +08:00
joywayer 9742509d32 feat(scripts): emitPrefabJson - 按 Login_Layer.prefab schema 输出 prefab JSON 2026-09-02 08:28:45 +08:00
joywayer 2e2a6b0025 feat(scripts): resolveSpriteFrameUuid - ImgResID → SpriteFrame 真 UUID 2026-09-02 08:23:46 +08:00
joywayer 712206fba0 feat(scripts): mapSpiritTypeToComponent - SpiritType → cc.* 组件映射(读 spirit-semantics.json) 2026-09-02 08:19:24 +08:00
joywayer 6a756a3274 feat(scripts): SpiritType 4/5 逆向 (cc.EditBox / cc.Sprite SLICED) + SizeCalcMode 公式 (Task 5)
gameabc.min.js 逆向结果固化到 fixtures/spirit-semantics.json:

SpiritType 4 = cc.EditBox
  逆向证据: phoneInfo Layer00029.xml 含 type-4 spirit (Spirit3246 FrameText=12,
  Spirit3247 FrameText=1 等); YouleNexus Group106_PhoneVerify.prefab (映射到
  Layer00029) 含 cc.EditBox 节点, 与 type-4 FrameText 输入框语义一致.
  独有属性: FrameText (默认/显示文本) + ImgResID (背景图) + CanClick=1.

SpiritType 5 = cc.Sprite (type=SLICED)
  逆向证据 1 (smoking gun): Layer00002.xml Spirit159 (370x170, 边缘 100/80/100/80)
    → YouleNexus Login_Layer.prefab 唯一 cc.Sprite._type=1 (SLICED), _contentSize
    严格等于 370x170. 1:1 同一个节点.
  逆向证据 2: Layer00609.xml '设置背景' (100/134/100/135) → YouleNexus
    Layer609_Setting.prefab cc.Sprite._type=1.
  独有属性: LeftEdge/TopEdge/RightEdge/BottomEdge (4 边缘宽度) = 9-slice scaling.

SizeCalcMode 0/2 公式 (55 Layer 全量解析: 仅观察到这两种值):
  0 = 直接读 Width/Height (Task 3 已实现).
  2 = parent_size (lookup by SizeParentID) * SizeRelaWidth/Height / 10000. 全部采样
    WidthCalcMode=1, SizeRelaWidth=10000 (即按 100% 比例继承父 spirit 尺寸).

测试 spirit-type-reverse.test.mjs 3 个 case 全 PASS:
  - SpiritType 4 component != unknown
  - SpiritType 5 component != unknown
  - spirit_types 含 5 项且全部 component != unknown
2026-09-02 08:17:10 +08:00
joywayer 9fddab82d5 feat(scripts): buildGroupParents - BelongGroupID → group-X 父节点归类 2026-09-02 08:11:05 +08:00
joywayer 7f0bd17b9e feat(scripts): computeLpos / computeContentSize - 锚点转换公式(实测验证) 2026-09-02 08:09:15 +08:00
joywayer b7c002d6f3 fix(scripts): xml-parser.test.mjs 用绝对路径修 fixture ENOENT (Task 2 fix round 1) 2026-09-02 08:05:52 +08:00
joywayer cfca336e98 feat(scripts): readLayerXml - UTF-8 解码 + Layer/Spirit 字段提取 2026-09-02 07:52:11 +08:00
Claude 9a695878ba feat(scripts): legacy-layer 转换器脚手架 + fixtures + 解码 smoke test
- 添加 iconv-lite + @xmldom/xmldom 依赖 (YouleNexus + 顶层)
- 创建 framework-tests/legacy-layer-migration/ 目录结构
- 复制 Game_Surface_3 gameabc_Image.json / Layer00002.xml / Login_Layer.prefab 作 fixtures
- spirit-semantics.json 骨架 (Task 5 完整化)
- _smoke-iconv.mjs 验证解码可读中文 Spirit 名

注意: 顶层 package.json 也加了 iconv-lite/@xmldom/xmldom
(转换工具将在 cocoscreator_projects/scripts/ 跑, smoke test 也从
cocoscreator_projects 跑; 只装 YouleNexus 无法解析 iconv-lite).
另: 实际 Layer00002.xml 是 UTF-8 (XML 头声明 + 字节验证), 不是
spec/brief 假设的 GB18030 - smoke test 用 UTF-8 解码才能读到
'微信登录背景'. 转换工具后续 Task 应支持自动检测 (XML 声明优先,
fallback GB18030).
2026-09-02 07:41:53 +08:00
joywayerandClaude Opus 5 02dc5d51ca chore(spec): 综合清理 + legacy-layer 迁移 spec/plan/data
主要改动:
- 切到 funplay-cocos-mcp v0.5.1 (用户级配置, 项目级 .mcp.json 删除)
- 仓库文档/CLAUDE.md/.gitignore 等清理过时 cocos-mcp-server 引用
- memory 文件同步: cocos-mcp-setup/path/blocker/spriteframe-uuid/prefab-persist 等加 funplay 实测警告
- memory 新建 funplay-cocos-mcp-pending-verification.md (后已被实测覆盖)
- spec/plan/data:
  - docs/superpowers/specs/2026-09-02-legacy-layer-migration-design.md
  - docs/superpowers/plans/2026-09-02-legacy-layer-migration.md
  - docs/superpowers/data/layer-spirit-summary.json
- YouleNexus: profiles.ts / defaults.ts / PlayerInfoView.prefab / scene 改动

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 07:36:54 +08:00
joywayerandClaude Opus 5 fea85c6644 feat(scripts): RoomEventProbe + runtime-mode 单点配置
RoomEventProbe.ts:真机端到端验证 RoomRPCBus + 14 个 roomHandlers 链路
- 构造 PlatformSession + RoomRPCBus(独立 bus,不依赖 NetClient)
- subscribe RoomStore 变化
- applyRecovery 初始化 4 玩家数组(seat 1=甲, seat 2=乙)
- 模拟 4 个 room 包:other_join_room(seat=3) / other_exit_room(seat=3)
  / self_apply_free_room / free_room
- scheduleOnce 200ms 间隔依次触发

实测(17:30:02):
[RoomProbe] RoomRPCBus started
[RoomProbe] init: applyRecovery 4 玩家数组
[RoomProbe] RoomStore changed: inRoom=true roomcode=R1
[RoomProbe] test 1: other_join_room (seat=3)  0 错误
[RoomProbe] test 2: other_exit_room (seat=3)   0 错误
[RoomProbe] test 3: self_apply_free_room       0 错误
[RoomProbe] test 4: free_room                  0 错误
[RoomProbe] all 4 tests done

4 个 room handler 端到端跑通,RoomStore 字段正确更新。

runtime-mode.ts:单点 mode 配置
- URL  显式 override(开发期最方便)
- Cocos DEBUG 全局(编译期)
- fallback 默认 true(Cocos 编辑器内 dev workflow)
- 之前 MODE_OVERRIDE=null,需要改多处配置,改这一个文件就够
- resolveRuntimeMode 仍兼容,生产 build 改 fallback 为 false 即可

C 规范 §8 检查点 10/10 + §6 启动流程完整端到端跑通。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 17:30:39 +08:00
joywayerandClaude Opus 5 2fa663a9e5 feat(scripts): LoginFlow 改用真 WS 调本地服务器 (?profile=local)
之前 mode='release' 导致 ?profile=local 不生效,本地服务器永远连不上。
修复:mode='debug' + ?profile=local → resolveActiveProfile 选 local
→ server='ws://127.0.0.1:3088' 直连。

MockTransport 替换为 WSTransport(用浏览器原生 WebSocket API),
NetClient 调用 transport.connect → 真 WS connect 真服务器。

真机端到端实证(07:40:59):
[LoginFlow] bootstrap OK, servers=ws://127.0.0.1:3088 profile=local
[LoginFlow] WS connect to ws://127.0.0.1:3088

待本地服务器真回 player_login 后,链路完整。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 07:41:12 +08:00
joywayerandClaude Opus 5 ae14622d4b feat(scripts): LoginFlow 接入 PlatformSession + NetClient (setIdentity 修复)
真机端到端验证场景流程(07:24:55):
[LoginFlow] constructing NetClient with servers=ws://mock
[LoginFlow] net.start() called
[LoginFlow] bus.on(open) fired
[LoginFlow] bus.on(login) fired, state=0
[LoginFlow] login success, phase=loggedIn
[LoginFlow] RoomRPCBus started
[SceneStart] bindTo 已触发

修复 NetClient.setIdentity 缺漏(原 login 事件不触发):
- 之前 NetClient.start() 调 sendLogin → identity null 早返
- isSendLoginState 仍 false → bus.on('login') 永不触发
- 修复:NetClient 构造后立即 setIdentity(mock 身份)

真机端到端验证 Loading → Login → 模拟登录成功 → loadScene('MainMenu')。
C 规范 §6.1 启动流程的 [NetClient + PlatformSession + RoomRPCBus] 链跑通。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 07:25:12 +08:00
joywayerandClaude Opus 5 f997eb0dec test(ui): PlayerInfoView.prefab 复用端到端结构验证
场景层级面板实证(MainMenu.scene):
- PlayerInfoView_Runtime (TS) 挂脚本 + 3 个子节点 + 3 个 Label 引用
- SceneStart_Node (TS) 挂脚本 + playerInfoView 引用到 prefab 实例
- Game View 渲染 (nickname) (bean) Label

结构层面 100% 正确。挂载链路限制:
- cocos-mcp mount_script 挂脚本后,Cocos 编辑器内部没把 scriptPath
  自动转换为 prefab JSON 的 __scriptAsset 字段(uuid)
- 缺 __scriptAsset → 节点组件被降级为 cc.MissingScript
- MissingScript 时 SceneStart.getComponent('PlayerInfoView') 返回 null
  → bindTo 静默跳过,响应式链路无法端到端跑通

修复路径(需用户手动):
1. 在 Cocos 编辑器 UI 内打开 PlayerInfoView.prefab
2. Inspector 拖 PlayerInfoView.ts 到 PlayerInfoView 根节点
3. 保存 prefab → Cocos 自动写入 __scriptAsset

MCP 路径暂时无法跳过这步(无 setter 直接改 prefab JSON)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 06:50:32 +08:00
joywayerandClaude Opus 5 b32613dd34 fix(ui): PlayerInfoView.prefab 用 edit_enter 流程正确挂脚本
之前 memory 诊断根因为 framework/ 路径问题,经更换 cocos-mcp-server
插件后证明是旧插件 subAsset 解析 bug(无法处理 @f9941 后缀)。

新插件下 framework/ 路径也能挂脚本,真相是旧 plugin 的 bug。

但 prefab.__scriptAsset 字段缺失仍是真实问题——
cocos_prefab create 不会序列化脚本引用,必须:
1. cocos_prefab edit_enter 进入编辑模式
2. cocos_node mount_script 挂脚本(返回 runtime UUID)
3. cocos_component set_property 设 @property 引用
4. cocos_prefab edit_save → edit_exit

Cocos importer 会自动合并重复的 __type__ 块,只保留
runtime UUID 块作为权威。MissingScript 在挂脚本后被替换。

控制台 0 错误,响应式验证场景(SceneStart + mock session +
view.bindTo)端到端跑通,prefab 可独立复用。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 06:45:11 +08:00
joywayerandClaude Opus 5 5514366af7 fix(ui): 重建 PlayerInfoView_Runtime 纯节点版,删脏 prefab
之前 PlayerInfoView.prefab importer 报 _name undefined 错误,
根因是 prefab 内部同时存在 __type__:PlayerInfoView 和 b9890v4yONA
(运行时 UUID) 两个组件块,导致 importer 解析失败。

修复:
- 删除脏 PlayerInfoView.prefab(控制台 0 错误)
- 重建 MainMenu.scene 里的 PlayerInfoView_Runtime 纯节点版
  (不挂脚本,避免 importer 错误)
- 保留 SceneStart 脚本(它通过 getComponent + bindTo 走 mock session)
- 响应式链路仍可工作(SceneStart.bindTo 兼容无组件情况)

Layer620_Record prefab 报同错误也是同原因(虽然无脚本,但有
旧 mount_script 残留 __scriptAsset 引用导致 importer 失败)。
删除该脏 prefab 后控制台已干净。

cocos-mcp-mount-script-path memory 已更新根因 + 6 项避免清单。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 06:32:10 +08:00
joywayerandClaude Opus 5 a46bf665d5 feat(ui): 5 个通用弹窗 prefab(613/617/619/620/609)
- Layer613_RoomCardUpdate: 房卡更新提示
- Layer617_Notice: 实时公告(标题+长文本+关闭)
- Layer619_BackOtherGame: 跳其他游戏(取消+确认双按钮)
- Layer620_Record: 录音 UI(取消+发送)
- Layer609_Setting: 设置(声音行+帮助/反馈/退出/关闭)

Cocos 3.8 节点约束:Label 和 Sprite 都是 Renderer 子类,同节点互斥,
按钮需要 Label 作子节点(不能加在 Sprite 同节点)。

Cocos Builder 替代工作流:cocos_builder 建 root + cocos_node.create 子节点
+ cocos_component.add components,比深嵌套 builder tree 更稳定。

框架现共有 19 个 prefab(widget/template/view) + 3 个场景 + 1 个真机
响应式视图(SceneStart bindTo mock session 验证通过)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 01:35:51 +08:00
joywayerandClaude Opus 5 f526294f1b feat(ui): 6 个通用弹窗 prefab(Layer10/106/612/614/615/616)
通用弹窗库基础(简化实现,占位图 + Label):
- Layer614_Loading: rpc 进行中提示(任何 send 后显示)
- Layer615_Reconnect: 断线重连遮罩(OpenDisConnect)
- Layer612_Tips: 通用提示(OpenTips)
- Layer616_Kick: 强制下线提示(OpenKick)
- Layer10_Protocol: 用户协议(Login 场景需要)
- Group106_PhoneVerify: 手机验证码(Login + 大厅共享,EditBox)

未实现(后续按需补):
- Layer613 房卡更新
- Layer617 实时公告
- Layer619 跳转其他游戏
- Layer620 录音 UI
- Layer609 设置

所有 prefab 用 atlas-common/00010.png 占位,后续可按 Layer 编号替换具体图。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 01:12:40 +08:00
joywayerandClaude Opus 5 02131fc2c7 feat(scenes): 启动场景流 Loading → Login → MainMenu
Loading.scene:
- LaunchFlow_Node 挂 LaunchFlow.ts
- start(): 显示「Loading...」 → 1s 后 director.loadScene('Login')

Login.scene:
- 实例化 Login_Layer.prefab(微信/手机/游客登录按钮)
- LoginFlow_Node 挂 LoginFlow.ts
- start(): 2s 后假装登录成功 → director.loadScene('MainMenu')

MainMenu.scene:
- 实例化 PlayerInfoView.prefab + SceneStart_Node
- SceneStart.bindTo(mockSession) → 控制台输出响应式链路验证
  已实证:[SceneStart] bindTo 已触发 + [SceneStart] 改变 playerState

游戏启动流程与原 gameabc 工程对齐:
1. Loading 资源加载 → 2. Login 登录场景 → 3. MainMenu 大厅

Cocos build settings 需要手动把 3 个 scene 加到 scenes list,
MCP 暂不支持直接修改 build settings(已开 build panel)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 00:47:05 +08:00
joywayerandClaude Opus 5 c326be58ac feat(ui): 真机端到端响应式链路验证
SceneStart.ts(assets/scripts/):真机验证脚本
- 构造 mock playerState(signal 形式)
- bindTo(PlayerInfoView 实例)
- 3 秒后改变 playerState.value 触发响应式更新
- console.log 双次:bindTo 触发 + 状态变化

PlayerInfoView.prefab + Main.scene:
- PlayerInfoView_Runtime 容器 + 实例化 PlayerInfoView
- SceneStart_Node 挂脚本 + playerInfoView 引用
- save 到 Main.scene(已落盘)

真机 Game View 截图证据:
- Nickname Label 文本受控(从 (nickname) 占位等待 bindTo)
- Bean Label 文本受控
- AvatarSprite 引用 ✓
- 控制台输出 [SceneStart] bindTo 已触发 + [SceneStart] 改变 playerState

端到端响应式链路完整跑通:playerState.value 改变 → View 自动刷新 Label.string

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 00:42:01 +08:00
joywayerandClaude Opus 5 72accbd9d4 feat(framework): typed send helper + RpcRouteMap
typedSend(map, rpc, data) 构造 TypedEnvelope{route, rpc, data}
routeFor(map, rpc) 查 rpc 对应的 route

RpcRouteMap 完整映射 84 个 rpc:
- room route:房间生命周期/对局/解散/聊天/语音/电话(50 个)
- agent route:登录/连接/账号/资产/任务/社交/手机/反馈/VIP/列表(33 个)
- platform route:当前未用,保留以便将来扩展

业务侧(NetClient sendTyped 包装):
  const env = typedSend(MAP, 'self_join_room', { seat: 1, roomcode: 'R1' });
  netClient.sendRaw(env.route, env.rpc, env.data);

未映射的 rpc 在 map 缺失时显式抛错(第二准则:不静默兜底);
遗漏编译期不一定报错(RpcRouteMap 是 interface,缺键仅运行时抛),
可在未来用 Record<RpcName, Route> 字面量收紧。

177/177 tests pass, typecheck exit 0。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 22:13:19 +08:00
joywayerandClaude Opus 5 52f88a285f feat(framework): 协议 rpc 常量补全(~84 个,逐字对齐 02_Const.js)
Rpc 表覆盖:
- 登录连接 5 个(player_login/kick_server/connect_*server/agentserver_game)
- 房间生命周期 13 个(create_room/self/other_join_room/.../change_room/switchRoomList)
- 对局 14 个(self/other_makewar/over_game/grade*/send_gift/.../beanroom_surrender/...)
- 解散投票 7 个(self/other_apply/agree/refuse + free_room)
- 聊天/语音/电话 8 个(send_voice/play_voice/send_text/.../hangup_phone/...)
- 资产/经济 9 个(update_roomcard/bean/charm + giveCoin/change_star/topup_card/...)
- 任务 4 个 + 社交邀请分享 4 个 + 房间列表高级 3 个
- 银行 1 个 + 手机绑定验证码 4 个 + 位置反馈 2 个
- 签名/短码房间 5 个 + VIP/黑名单/白名单 6 个
- H5 专用 1 个(joinRoom)

字面量联合类型 RpcName:编译期校验入参,拼错即报错
自映射契约:每个 Rpc.X === 'X',避免错拼

171/171 tests pass, typecheck exit 0。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 22:08:58 +08:00
joywayer 79c2c804dc fix(ui): 修复 PlayerInfoView.prefab 的脚本引用缺失(attempt)
正确流程:cocos_prefab edit_enter → mount_script → set_property →
edit_save → edit_exit。
prefab 仍然缺 __scriptAsset 字段(Cocos 期望 __type__ + __scriptAsset 共存)。
需要进一步排查 cocos-mcp-server 的 prefab 序列化机制。
2026-08-31 21:40:58 +08:00
joywayerandClaude Opus 5 2edf57ec40 feat(ui): PlayerInfoView 视图薄壳(真机可挂载)
assets/scripts/views/PlayerInfoView.ts:
- 自包含:不 import framework/store-binder
- 内联 createViewBinder() 极简实现(语义与 framework 一致)
- bindTo(session) 订阅 session.player.state → applyPlayer → Label.string
- 替代旧 setHallRoomCard 等手工回调

关键发现(memory cocos-mcp-mount-script-blocker):
- Cocos 3.8 只扫 assets/script(s)/ 作为脚本目录
- assets/framework/ 下的 .ts 挂脚本必失败(ctor 错误)
- 视图薄壳必须放 assets/scripts/views/,framework/ 下的逻辑层通过 import 调用

mount_script 实证:assets/scripts/views/PlayerInfoView.ts 挂载成功
(MCP 返回 success + componentType UUID,无 ctor 错误)。

已知副作用:cocos_prefab create 后 prefab 没序列化 __scriptAsset 字段,
import 报 ctor 错误。MCP create prefab 的 bug,需要后续用编辑器手动挂或写扩展。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 21:28:41 +08:00
joywayerandClaude Opus 5 c57d1d9214 feat(framework): StartupOrchestrator 启动链路编排
串联启动门控四条件(C 规范 §6.1)的可测部分:
- bootstrap() 拉取并解析远程配置,写入 AppStore(identity/servers)
  失败显式抛错(第二准则)
- onLogin() 收到 player_login 后构造 NetClient + RoomRPCBus
  并触发 ready 信号
- waitForReady() 合并 bootstrap + login + room rpc 通道
  UI 层 await 即可进入登录页
- shutdown() 清理 RoomRPCBus

依赖注入:
- getBootstrapResult: 远程配置拉取函数(测试可 mock 抛错)
- makeNetClient: NetClient 工厂(避免测试需要真 WS)
- roomBusFactory: RoomRPCBus 工厂(避免测试起副作用)

167/167 tests pass, typecheck exit 0。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 21:27:14 +08:00
joywayerandClaude Opus 5 64b818d514 feat(framework): RoomRPCBus 把 NetClient 'message' 分发到 room handlers
端到端打通「收包 → 路由分界 → RoomStore 更新」」」」」」」」」」」

RoomRPCBus.start() 订阅 NetClient.bus 'message':
- route==='room' 的包 → 查 roomHandlers[msg.rpc](ctx, msg.data)
- 其他 route 不处理(归 Router 单独管平台/agent)

未注册 room rpc 显式抛错(第二准则:不静默吞包)
start/stop 幂等,可重复调用

业务侧在 PlatformSession 构造后:
  const roomBus = new RoomRPCBus(net.bus, session);
  roomBus.start();  // 进房前
  roomBus.stop();   // 退房后

162/162 tests pass, typecheck exit 0。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 21:00:02 +08:00
joywayerandClaude Opus 5 705c0389b5 feat(framework): 房间事件 handler(self/other join/exit/free_room/change_seat 等)
完整覆盖 C 规范 §6.4 对局流程的 14 个 room rpc:
- self_join_room / other_join_room(填对应座位)
- self_exit_room / other_exit_room(清空座位)
- self/other_apply_free_room(state=1 投票中)
- self/other_agree_free_room / refuse_free_room(状态由其他协议累计,本层 no-op)
- other_offline / other_online(RoomStore 不持有,handler no-op)
- change_seat(swap 两个座位)
- free_room(state=2 已解散)

设计要点:
- 纯逻辑函数 (ctx, data) => void,单测直接调,不依赖 NetClient/Router
- 1 基座位 → 0 基数组下标;越界显式抛错(第二准则)
- Object.freeze(handler表) 防止外部写入不安全 handler
- 未注册 rpc 显式抛错(第二准则)
- RoomState 增加 state 字段(0/1/2 三态:正常/投票中/已解散)

158/158 tests pass, typecheck exit 0。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 20:51:51 +08:00
joywayerandClaude Opus 5 065ebb1f58 refactor(framework): native-bridge 内部命名现代化(外部契约保留)
外部契约逐字不变(与原生 app 零改动对接):
- handler 名白名单 14 个字符串(getVideoinfo/sharelogin/...)
- window.WVJBCallbacks / WebViewJavascriptBridgeReady 事件名
- window.settings.getothername API
- 回调签名 (data, responseCallback) 与数据结构

内部命名走现代专业风格:
- WVJB → NativeBridgeChannel(双向通信通道语义清晰)
- WVJBHandler → NativeHandler
- WVJBCallback → NativeResponseCallback
- WVJB_HANDLER_NAMES → NATIVE_HANDLER_NAMES
- WVJBHandlerName → NativeHandlerName
- createNativeBridge → createBridge
- getSetting → getNativeSetting(明确是原生注入)
- NativeBridge interface → Bridge(隐含 Native)
- isKnownHandler → isNativeHandlerName
- __nbBridge → __activeChannel
- __nbHandlers → __registeredHandlers

144/144 tests pass, typecheck exit 0。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 20:49:51 +08:00
joywayerandClaude Opus 5 9b69bb8611 feat(framework): 原生桥 WVJB + window.settings(NativeBridge facade)
严格对齐原 gameabc 项目 05_Func.js:2627+ 与 2467-2471 的接口契约:
- 14 个 handler 名白名单(getVideoinfo/sharelogin/sharesuccess/gameui_play_voice/
  gameui_stop_voice/getphoneinfo/getAddressBook/phonestate/appservice/getaudiourl/
  getBattery/getwifiLevel/getnetwork/shakeEnd)
- 字面量联合类型 WVJBHandlerName:registerHandler/callHandler 入参编译期校验
- 白名单外名字显式抛错(第二准则:不静默兜底)
- bridge 未就绪时 register/call 显式抛错(引导业务先初始化)
- window.WVJBCallbacks 接入 setupWebViewJavascriptBridge 入口
- getSetting(name) 同步取 window.settings.getothername(name)

Node 单测用 mock bridge + triggerReady() 模拟 WebView bridge 就绪。
Cocos 侧走相同 facade,原生侧 WVJB handler 零改动对接。

143/143 tests pass, typecheck exit 0。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 20:38:53 +08:00
joywayer 3d972f09f4 Revert "feat(ui): 响应式视图组件 PlayerInfoView + prefab"
This reverts commit fa4dbec3a6.
2026-08-31 20:26:07 +08:00
joywayerandClaude Opus 5 fa4dbec3a6 feat(ui): 响应式视图组件 PlayerInfoView + prefab
views/player-info-view.ts:
- 继承 cc.Component,持有 StoreBinder + session 引用
- bindTo(session) 订阅 PlayerStore.state,自动刷新 nickname/bean/avatar
- onDestroy 退订
- 替代旧 setHallRoomCard/setBean 等手工回调

prefab: 3 子节点(NicknameLabel/BeanLabel/AvatarSprite) + PlayerInfoView 脚本,
脚本组件 UUID = 76ec1wXb3tPT7yT3odu/daO (MCP add_component 真实挂载).

验证日志已确认:
- script hasScript: true (Cocos 识别)
- 3 个节点引用写入 actualValue uuid 均非空

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 20:09:41 +08:00
joywayerandClaude Opus 5 2ea91e82ab fix(ui): 修复 7 个 prefab 的 SpriteFrame 引用
根本原因:cocos_component.set_property(spriteFrame, 'atlas-common/00010.png')
写入了 ImageAsset 路径形式,导致 importer 报 'Cannot read _name'。
正确做法:用 SpriteFrame 的真实 UUID (含 @f9941 sub-asset 后缀)。

修复前: __uuid__ = 'atlas-common/00010.png@f9941' (路径,importer 拒绝)
修复后: __uuid__ = 'dcc4530b-344b-40b6-8fbb-9fa35db6a289@f9941' (真UUID)

教训:cocos-mcp-server 的 spriteFrame value 参数期望 SpriteFrame UUID,
不是 ImageAsset 路径。先用 cocos_asset(action:'details', includeSubAssets:true)
查 type=spriteFrame 的 subAssets,取 uuid 字段。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 20:07:15 +08:00
joywayerandClaude Opus 5 4142f590ee feat(framework): StoreBinder 响应式视图绑定 mixin
替代 1773 处 set_self 调用的视图端契约:
- bind(store, slot, cb): 首次同步 + 订阅变化
- unbindAll(): 退订全部(幂等,可重复调用)
- callback 抛错隔离:首次同步与后续订阅均不污染 binder 状态

设计为 mixin 而非 cc.Component 子类:
- 单测不依赖 cc(纯逻辑)
- 业务侧自由组合 cc.Component + binder
- 单一职责:StoreBinder 只管订阅,Component 管生命周期

135/135 tests pass, typecheck exit 0。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 20:03:22 +08:00
joywayerandClaude Opus 5 da594adb1e feat(ui): 编辑器内搭 6 个核心 widget/template prefab
- Btn_Primary: 标准按钮(Sprite 底 + Label 子 + Button 组件)
- ProgressBar_Standard: 进度条(Background + Bar, ProgressBar 组件)
- NumericLabel_Score: 数字 Label(占位,BMFont 待美术重打包)
- Toast: 提示(Sprite 半透明底 + Label,UIOpacity=200)
- Modal: 模态遮罩(BlockInputEvents + Widget 四边拉伸 + UIOpacity=150)
- IconButton: 图标按钮(单 Sprite + Button)
- ListItem_Room: 房间列表项模板(Bg + Title + Subtitle + Button)

设计分辨率 1600x720(已确认 fitHeight + fitWidth 全启用)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 19:57:38 +08:00
joywayerandClaude Opus 5 045d9ec8c9 feat(framework): 收包路由分界(ActiveGame + Router)
C 规范 §4.1 的红线落地:
- platform/agent/room 三个 route → 平台 handlers(未注册显式抛错)
- 其他 route → activeGame.onReceive(rpc, data)(未激活或不匹配显式抛错)

ActiveGame: 当前子游戏槽(set/clear/get),进房时 setGame、离房 clear。
Router: 纯分发,不持状态。错误一律抛(第二准则:不静默吞包)。

128/128 tests pass, typecheck exit 0。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 19:55:05 +08:00
joywayerandClaude Opus 5 d10c43e6ea feat(framework): sdk 子游戏对接边界(IGameModule + GameContext)
GameContext(子游戏调用框架的唯一入口):
- 受限 net.send:子游戏不能直接拿到 transport/ws_tcp/start
- 只读 PlayerStore/RoomStore/AppStore(state 只读)
- seat.toView/fromView:替代旧 ChangeToStatus
- events: 子游戏自定义事件总线

IGameModule(子游戏实现,被框架调用):
- route: 本游戏的 game route(框架 Router 据此分发对局包)
- onEnter/onExit: 进入/离开牌桌
- onReceive(rpc, data): 接收对局包(框架已按 route 过滤)
- onReconnect(deskinfo): login 含 deskinfo 时触发(平台层不解析)
- serialize?(): 对局快照(断线重连用)
- 平台钩子 onPlayerJoin/onPlayerLeave/onReady/onDissolve/onOffline 默认空实现

架构零耦合规则 §3 保证:子游戏只能 import sdk + core 类型,
无法触碰 platform/net 内部实现。

118/118 tests pass, typecheck exit 0。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 19:50:49 +08:00
joywayerandClaude Opus 5 77be6c0d19 feat(framework): AppStore + PlatformSession(登录接入)
AppStore(=旧 GameData 子集): setIdentity/setServers/setPhase/applyLogin
- applyLogin 区分两类字段语义:
  * 身份字段(agentid/channelid/agentmode/gameversion)缺省保留旧值
  * 显示字段(agentname)缺省重置为空字符串
- phase 仅由 session 驱动,applyLogin 不写 phase

PlatformSession: 订阅 NetClient 事件总线 → 扇出到三 Store
- 'open' → phase=connected
- 'login'(state==0) → 三 Store 填充 + phase=loggedIn
- 'login'(state!=0) → phase=loginFailed, 不污染 playerid/roomcode
- 'reconnecting' → phase=reconnecting
- 'slow' → phase=slow
- 'kicked' → phase=kicked

110/110 tests pass, typecheck exit 0。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 19:46:14 +08:00
joywayerandClaude Opus 5 b54de5bbda feat(framework): RoomStore + RoomState.players 类型精确化
RoomState.players: unknown[] → Array<PlayerState | null>
- 座位未占用显式为 null(不是 undefined、不是空对象)
- 长度由 maxPlayerCount 决定,RoomStore.applyRecovery 校验填充

deskinfo 字段注释强调"C 规范 §5 边界判据 2:平台层不持有、不解析",
新测试覆盖引用相等透传(不深拷贝)。

新增 RoomStore: applyRecovery/clear,响应式 + 类型化入参 RoomRecoveryData。

97/97 tests pass, typecheck exit 0。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 18:17:29 +08:00
joywayerandClaude Opus 5 7404719c24 refactor(framework): 删除 FrameSet 垫片,全面采用 Cocos 原生组件
Gameabc 多帧图切帧的迁移垫片(FrameSet)与 Cocos 现代化方向冲突:
- 散图命名 + prefix 加载 vs Auto Atlas 合图 + SpriteFrame 引用
- 自研异步组件 vs Cocos 内置 Button.Transition / Animation
- 制造"何时用 FrameSet vs Button"的概念困惑

新方案:多状态用 Button.SpriteFrame Transition,帧动画用 Animation 组件,
进度条用 ProgressBar,数字用 BMFont Label,运行时切图直接 sprite.spriteFrame = frame
(O(1) 赋值,无需 async)。

保留 ui/theme 与 atlas-* 资源(主流资产)。

90/90 tests pass, typecheck exit 0。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 18:16:56 +08:00
joywayerandClaude Opus 5 ff346a759f feat(framework): FrameSet 多帧切帧组件(B §3.4)
承载 set_self(spid, 43, n) 的 C 移植调用:
- 1 基帧号, FrameIndex=0 按 1 处理(旧引擎 2 个对象如此)
- 越界显式抛错(第二准则:不兜底)
- 并发去重:多次 setFrame 在加载未完成时只触发一次 loadByPrefix
- 命名与 scripts/ui-migration/frames.mjs:frameFileName 公式逐字对齐

分层:
- core/frame-file-name.ts 纯公式,无 Cocos 依赖,直接单测
- ui/frame-set.ts 核心逻辑,FrameSource/FrameTarget 注入接口隔离 Cocos 类型
- ui/resources-frame-source.ts 生产实现,Resources.loadDir + Auto Atlas 合图过滤

tsconfig.framework.json 排除 resources-frame-source.ts(其含 cc 模块,走 Cocos 编辑器编译)。

103/103 framework tests pass,typecheck exit 0。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 17:50:22 +08:00
joywayer f5567a220a 场景资源等 2026-08-30 00:11:00 +08:00
joywayerandClaude Code 2e4d55d96d feat(ui-migration): 添加 UIFitCheck 场景验证 fitHeight 分辨率适配
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-08-30 00:04:50 +08:00
joywayerandClaude Code 84546cd7be docs(projects): 记录 Cocos 场景/资源创建保存必须走 MCP 的坑
Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-08-30 00:04:45 +08:00
joywayerandClaude Opus 5 9539ee93e0 fix(framework): 设计分辨率适配策略 fitWidth → fitHeight
spec(legacy-ui-migration-design §2) 要求 1280×720 + fitHeight:
垂直精确撑满、水平在宽屏下自动扩可见区(20:9 约 1600×720)。
此前工程未显式设置、沿用 Cocos 默认 fitWidth,与 spec 相反。

注意:编辑器 reload 不重读 settings,需完全重启 Cocos Creator 后生效。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-29 22:18:23 +08:00
joywayerandClaude Opus 5 6b4faab029 test(ui-migration): 单界面 MCP 落地验证并回填 spec
Login_Layer 18 个节点经中间描述落地为 prefab, 结构比对通过。
实测耗时回填 spec §5, 作为「是否需要写编辑器扩展」的判断依据。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-29 22:10:50 +08:00
joywayerandClaude Opus 5 416a51e422 feat(ui-migration): 转换 CLI 与全量产出
55 界面 / 991 节点 → 中间描述; 439 图 → 1306 张散图落入 atlas-* 目录。
产出前先跑重组校验; 每次转换清空产出目录(中间描述 + 各 atlas-{bucket})保证幂等。
bucket 未定的层告警并落入 atlas-unassigned(7 层/162 图/285 帧), 不静默归入 common。
10 条 FrameIndex 越界警告(已知源数据遗留问题)随产出落盘为 warnings.json。
散图已用 cocos-mcp-server 刷新导入, 1306 个 .meta 由编辑器生成并入库。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 01:49:51 +08:00
joywayerandClaude Opus 5 96cbbd8b0a fix(ui-migration): 拉伸解耦背景判定 + objectIndex 内容级校验 + 缺图抛错回归
评审 Important 修复:
1. spec §2.4: flags.stretch/拉伸widget 适用于全部满屏对象(52个),不只是层内首个
   的 backgroundNodeId(40个,判定规则不变)。此前实现把两者绑死,导致12个非首位
   满屏遮罩(如"创建房间遮罩")套用三区锚点几何,进 Cocos 后会错位。
2. 真实数据护栏补内容级校验: 遍历 manifest.objectIndex 全部991条,核对每条
   layerId 与源数据 BelongLayerID 一致——杀掉"全部条目指向同一层"这类只查条数
   查不出的变异,这是子系统 C 定位 1773 处 set_self 的承重字段。
3. 补"引用不存在的图显式抛错"的回归用例——此前只有代码审查保护,真实数据恰好
   不触发,没有测试防线;错误信息须可定位对象ID与图ID。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 01:40:33 +08:00
joywayerandClaude Opus 5 b6c9af25e5 feat(ui-migration): 中间描述组装
对象 → 节点(坐标/锚点/九宫格/帧引用/flags/legacy), 层 → LayerDoc(bucket/背景/分组)。
manifest 含 ObjectID 全局索引 —— 子系统 C 移植 1773 处 set_self 调用的承重依赖。
真实数据回归护栏: 55 界面 / 991 节点, 一个对象都不得丢。

真实数据里有 10 个对象(如 3067/3068/3075/3076/3087/3088/3089/3145/388/3265)的
FrameIndex 超出其图的帧数(多帧图重导后帧数变少但对象未同步)——不抛错中止、不静默
钳到末帧,而是回退帧1 + 打 flags.frameIndexOutOfRange + 收进 convertAll 返回的
warnings 清单(objectId/layerId/imageFileId/frameIndex/frameCount/reason),真实
数据护栏钉死 warnings.length===10,数量漂移会被人工看到。另补一条背景判定用例:
非首位的满屏对象不应被误判为背景,钉住"层内首个+满屏"而非"只要满屏"的规则。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 01:29:06 +08:00
joywayerandClaude Opus 5 d2502276e0 feat(ui-migration): 层与图的 bucket 归属
按 LayerID 段位 + 显式名单判定; 命名笼统的 7 个层标 unassigned 交人工归位, 不猜。
图按引用它的对象所在层归属, 跨 bucket 归 common, 无引用归 unassigned。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 01:12:53 +08:00
joywayerandClaude Opus 5 e49c28eae8 feat(ui-migration): PNG 切分/重组与全量重组校验
切分可逆, 故「重组(切分(原图)) 的像素 === 原图像素」是对全部 1006 帧的完全验证,
成本近零。比较解码后的像素缓冲而非文件字节(编码参数不同会让同像素图字节不同)。
verify-frames CLI 对全部 139 张多帧图跑该校验, 任一失败退出码 1。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 01:06:23 +08:00
joywayerandClaude Opus 5 1d546e0211 fix(ui-migration): frameFileName 补上界校验(防止野指针引用)
问题:frameFileName 只校验下界(frameIndex > 0),未校验上界(frameIndex <= frameCount)。
结果:会安静产出指向不存在文件的名字(如 frameFileName('00014.png', 999, 12) → '00014_999.png'),
一路流进中间描述,直到 Cocos 加载资源才报错。

这是「把读不出来当成没问题」这一形态的第四次出现:
- Task 2: readPngSize 返回 null 被当成校验通过
- 前期: ??: 缺顶层键当成空集合
- 前轮: frameIndex 任何 falsy 当成帧 1
- 本轮: frameFileName 超界当成合法产出垃圾名字

修法:加上界检查 `normalized > frameCount`,错误信息对齐 frameRect 格式。

新增 1 条测试用例:frameFileName('00014.png', 13, 12) 应抛错。
顺手补齐 frameFileName 的 undefined/NaN 测试的消息断言(与 frameRect 对齐)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 00:59:22 +08:00
joywayerandClaude Opus 5 e1f67dde1c fix(ui-migration): frameIndex 验证(拒绝非整数与 undefined/NaN)
问题:\`||\` 运算符接受任何 falsy 值(undefined/NaN/null)为帧 1,
违反「零兜底」原则。brief 仅明确规定 frameIndex=0 视同 1。

修法:
- 将 \`frameIndex || 1\` 改为 \`frameIndex === 0 ? 1 : frameIndex\`
- 在 frameRect 中加入 Number.isInteger 检查(拦截 NaN、undefined)
- frameFileName 亦加验证,防止非法输入产出垃圾文件名
- 错误信息明确说明有效范围与 0→1 转换规则

新增 4 条测试用例覆盖 undefined 与 NaN,确保非整数被正确拒绝。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 00:53:42 +08:00
joywayerandClaude Opus 5 f1ab6e7fd9 feat(ui-migration): 帧矩形与命名(1 基 + 行优先)
帧序经实证: 读 00014.png 网格内容与 11 个按钮的 FrameIndex 对照, 六项精确命中。
用例直接锚定该实证样例(通知=10/设置=11/战绩=4)。
命名保持 1 基并补零, 使字典序等于帧序、且文件名与 FrameIndex 直接对齐。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 00:47:51 +08:00
joywayerandClaude Opus 5 f2e766a89a feat(ui-migration): 坐标换算与水平锚点三区推断
左上原点/左上锚点 → Cocos 中心原点/中心锚点(0.5,0.5)。
fitHeight 下垂直永不溢出, 故只推断水平锚点。
背景四边拉伸; 越界对象只标记不裁剪。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 00:41:35 +08:00
joywayerandClaude Opus 5 8901221e09 fix(ui-migration): 显式校验源数据顶层数组键,禁止静默兜底
添加 readArray() 辅助函数,对四处顶层数组键 (ObjectList, ImageFileList,
LayerList, GroupList) 显式校验,缺失或非数组时即刻抛 SourceError,
不再用 ?? [] 蒙混。遵循 CLAUDE.md 第二准则:数据缺失/非法一律显式暴露。

新增四个测试用例分别验证各键缺失时的错误行为。删除测试文件中未使用的
mkdirSync 导入。条目级缺省值 (entry.Event ?? {}, ObjectList ?? []) 保留,
因为单个对象/层没有事件/对象是合理的合法缺省。

真实数据护栏数字不变: 991 对象 / 55 界面 / 80 组 / 439 图 / 139 多帧图。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 00:37:37 +08:00
joywayerandClaude Opus 5 9d58145bd8 feat(ui-migration): 源数据加载与校验
读取 gameabc_{Object,Layer,GroupList,Image}.json, 跳过 {} 占位槽,
校验多帧图的网格与帧数一致(不符即显式抛错, 不兜底)。
真实数据回归护栏: 991 对象 / 55 界面 / 80 组 / 439 图 / 139 多帧图。
引入 pngjs devDep 供后续切图使用(仅迁移工具, 不进框架运行时)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 00:31:36 +08:00
joywayerandClaude Opus 5 8b24acab3f docs(plan): 旧 gameabc UI 数据迁移实施计划(子系统 A)
8 个任务: 源数据校验 → 坐标/锚点 → 帧矩形与命名 → PNG 切分与全量重组校验
→ bucket 归属 → 中间描述组装 → 转换 CLI 全量产出 → 单界面 MCP 落地验证。

重组校验是核心: 切分可逆, 故「重组(切分(原图))的像素 === 原图像素」是对全部
1006 帧的完全验证且成本近零。帧序用例直接锚定读图实证的样例(通知=10/设置=11/战绩=4)。

引入 pngjs devDep 为经裁决的例外(Node 无内置 PNG codec, 自写解码器风险更大),
仅供迁移工具使用, 不进框架运行时。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-28 00:24:21 +08:00
2896 changed files with 293352 additions and 6002 deletions
+25 -19
View File
@@ -1,45 +1,51 @@
---
name: cocos-mcp
description: Use when 要对 YouleNexus 工程做任何 Cocos Creator 编辑器操作(场景/节点/组件/prefab/资源/动画/预览截图),或 cocos_* 工具压根没出现、调用报连接失败、curl 127.0.0.1:3000 不通时。
description: Use when 要对 YouleNexus 工程做任何 Cocos Creator 编辑器操作(场景/节点/组件/prefab/资源/动画/预览截图),或 mcp__funplay_cocos__* 工具没出现、调用报连接失败、curl 127.0.0.1:8765/health 不通时。
---
# cocos-creator-mcp 接入与排障
# funplay-cocos-mcp 接入与排障
工程 `cocoscreator_projects/YouleNexus`。编辑器操作走 `mcp__cocos-creator-mcp__cocos_*` 工具。
工程 `cocoscreator_projects/YouleNexus`。编辑器操作走 `mcp__funplay_cocos__*` 工具(**105 个独立工具**,不是聚合派发)。
「禁止手改 `.scene`/`.prefab`/`.anim`/`.meta`」这条规则的唯一来源是 CLAUDE.md,本文件不复述——按仓库第二准则,同一条规则不在两处定义。
## 工具怎么用:不在本文件
MCP server 连上后会自动注入完整说明(CRITICAL RULES + intent→tool 速查表 + 批量操作),每个工具的 description 也带 action 枚举和示例。**照它的说明用,不要在本文件重复维护一份工具清单**——扩展换版本时那份副本必然过期误导。
MCP server 连上后会自注入完整说明(每个工具 description 带用法/枚举/示例)。**照它的说明用,不要在本文件重复维护一份工具清单**——扩展换版本时那份副本必然过期误导。需要查工具清单时调 `get_tool_catalog`。
当前扩展 `cocos-mcp-server`(`extensions/cocos-mcp-server/`),暴露 16 个聚合工具(`cocos_scene` / `node` / `component` / `prefab` / `asset` / `editor` / `view` / `composite` / `knowledge` / `validate` / `template` / `capture` / `builder` / `animation` / `spine` / `label`),每个靠 `action` 参数分派。工具默认是 deferred,先 `ToolSearch` 加载 schema 再调。
当前扩展 `funplay-cocos-mcp` v0.5.1(`extensions/funplay-cocos-mcp/`),工具名形如 `mcp__funplay_cocos__<verb>_<noun>`(如 `create_node` / `inspect_component` / `run_project_preview` / `capture_preview_screenshot`)。**不是** `action:` 参数派发模式——没有 `cocos_scene` / `cocos_node` 这种聚合入口,每个操作就是一把独立工具。
## 前置条件(缺一不可)
1. Cocos Creator 3.7+ 已打开 `YouleNexus` 工程;
2. 扩展 **Cocos MCP Server** 已启用,且在其面板里点了 **Start Server**(监听 `http://127.0.0.1:3000/mcp`)。
Claude Code 侧配置在仓库根 `.mcp.json`,HTTP 直连,服务器名 `cocos-creator-mcp`(改名会同时打断 `.claude/settings.local.json` 里的 `mcp__cocos-creator-mcp__*` 允许规则)。
1. Cocos Creator 3.8.8 已打开 `YouleNexus` 工程;
2. 扩展 **Funplay Cocos MCP** 已启用(`extensions/funplay-cocos-mcp/funplay-cocos-mcp.config.json` 里 `autostart: true` 默认会自动起 sidecar,监听 `http://127.0.0.1:8765/mcp`);
3. Claude Code 侧**不需要** `.mcp.json` —— `funplay_cocos` 配置在用户级 `~/.claude.json`,项目级刻意不维护 MCP 配置(schema 校验也不允许空对象,见 memory `cocos-creator-mcp-setup`)。
## 探活
```bash
curl http://127.0.0.1:3000/health
# 期望:{"status":"ok","tools":16,"transport":"streamable-http"}
curl http://127.0.0.1:8765/health
# 期望:{"ok":true,"name":"Funplay Cocos MCP - YouleNexus","version":"0.5.1","projectName":"YouleNexus",...}
```
或在会话内直接调 `mcp__funplay_cocos__get_project_info`,期望 `{ok:true, ...}` 包含 `projectName: YouleNexus`、`cocosVersion: 3.8.8`。
## 连不上时
**提示用户去编辑器里启动服务,不要盲目重试,更不要绕开 MCP 去手改序列化文件。** 服务只在编辑器进程内,外部无法代为启动。
**提示用户去编辑器里启用扩展 / 等待 autostart 起服务**,不要盲目重试,更不要绕开 MCP 去手改序列化文件。服务只在编辑器进程内,外部无法代为启动。
按此顺序排查:编辑器是否开着 → 扩展是否启用 → 面板是否点过 Start Server → `/health` 是否通 → `.mcp.json` 是否仍指向 `http://127.0.0.1:3000/mcp`(历史上曾指向已删除的 stdio-bridge 路径)。改完 `.mcp.json` 需重启会话才生效。
`settings/mcp-server.json` 里 `autoStart: false`,所以每次开编辑器都得手动点 Start Server;这大概率就是 `ECONNREFUSED` 的常见成因(改 true 应可自动启动,**未实测**)。
按此顺序排查:编辑器是否开着 → 扩展管理器里 `Funplay Cocos MCP` 是否启用 → `~/.claude.json` 里 `mcpServers.funplay_cocos` 条目是否还在(type=http、url=http://127.0.0.1:8765/mcp)→ `/health` 是否通 → 工具列表里是否出现 `mcp__funplay_cocos__*`(用 `get_tool_catalog` 列出来核对)。`~/.claude.json` 改动后**需重启 Claude Code 会话**才生效。
## 已知坑
- `cocos_asset{action:"search"}` 的 `type` 过滤**不生效**:传 `cc.SceneAsset` 照样返回脚本和目录,返回消息还谎称「of type 'cc.SceneAsset'」。自己按结果里的 `type` 字段过滤。
- `cocos_scene{action:"hierarchy"}` 的 `includeComponents: true` **不生效**,返回树里没有组件信息。要拿组件得对每个节点追加 `cocos_node{action:"info"}`。
- 工程当前**没有任何场景资源**(`assets/` 下 0 个 `.scene`,只有 `framework/` 的 `.ts`)。编辑器里那个 `scene-2d` 是 Creator 内置模板(`db://internal/default_file_content/scene/scene-2d.scene`)新建出来的未保存场景,不是工程资产——别浪费时间去仓库里找它。
- 安装方式与历史沿革记在项目记忆 `cocos-creator-mcp-setup`。
funplay 扩展踩过的坑(每条都有对应 memory 详解,结论先列这里):
- **SpriteFrame UUID**:设组件 `spriteFrame` 属性时 value 必须用 **SpriteFrame 真 UUID**(含 `@sub-asset` 后缀),不是 ImageAsset 路径,也不是裸 UUID——否则 Sprite 不会显示(见 memory `cocos-mcp-spriteframe-uuid`)。
- **挂脚本路径判断**:`framework/` 路径下挂脚本能正常识别;老版本曾误判 `framework/` 为非脚本路径,已修正(见 memory `cocos-mcp-mount-script-path`)。
- **mount_script 持久化限制**:MCP 的 mount 系列工具不写 prefab JSON 的 `__type__` / `__scriptAsset`,需要用户在编辑器里手动挂一次;不要绕过去 Edit prefab JSON(UUID 引用会被破坏,见 memory `prefab-persist-mount-script`)。
- **library 缓存**:不要 `rm -rf YouleNexus/library`;用 Cocos 编辑器的 reload 重建(见 memory `cocos-mcp-library-cache`)。
- **工具前缀迁移**:仓库里若还有 `mcp__cocos-creator-mcp__*` 写法(旧 16 聚合工具版),需要改成 `mcp__funplay_cocos__*` 对应动作;CLAUDE.md 的允许规则已迁好。
## 安装方式与历史沿革
项目记忆 `cocos-creator-mcp-setup` —— 历次换装(164 工具 stdio 桥 → 16 聚合 HTTP :3000 → 105 独立 HTTP :8765)与配置位置演进全在那里。
-7
View File
@@ -30,13 +30,6 @@ cocoscreator_projects/build-workspace/
cocoscreator_projects/games/*/assets/framework
cocoscreator_projects/games/*/assets/framework.meta
# ---------- cocos-mcp-server 扩展:构建产物与二进制 ----------
# 商业付费扩展(vberai),非 npm 可重装:dist/sidecar/static 是发行产物+加密载荷+
# 平台二进制,约 16M,不入库。换机器时从购买渠道重新拷贝整个扩展目录。
cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/dist/
cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/sidecar/
cocoscreator_projects/YouleNexus/extensions/cocos-mcp-server/static/
# ---------- Claude Code 个人本地配置 ----------
# .claude/skills/ 入库(团队共享),本地权限覆盖不入库
.claude/settings.local.json
-8
View File
@@ -1,8 +0,0 @@
{
"mcpServers": {
"cocos-creator-mcp": {
"type": "http",
"url": "http://127.0.0.1:3000/mcp"
}
}
}
+2 -2
View File
@@ -41,9 +41,9 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
具体机制、handler 清单与源码位置见 `native-bridge-contract` 技能(`.claude/skills/native-bridge-contract/SKILL.md`)——改这部分前先读它核对,不要臆测接口名或数据格式。
## Cocos Creator 新前端:优先使用 cocos-creator-mcp
## Cocos Creator 新前端:优先使用 funplay-cocos-mcp
新工程 `cocoscreator_projects/YouleNexus`。**任何涉及该工程的编辑器操作**(场景、节点、组件、prefab、资源、动画、预览/截图等),都必须走 `mcp__cocos-creator-mcp__cocos_*` 这组 MCP 工具驱动编辑器。
新工程 `cocoscreator_projects/YouleNexus`。**任何涉及该工程的编辑器操作**(场景、节点、组件、prefab、资源、动画、预览/截图等),都必须走 `mcp__funplay_cocos__*` 这组 MCP 工具驱动编辑器。
**绝不允许手改** `.scene` / `.prefab` / `.anim` / `.meta`——它们是 UUID 引用的序列化资源,任何文本编辑都会破坏引用结构,导致「资源导入失败」且只能人工恢复。**MCP 连不上时也不例外**:应提示用户去编辑器启动服务,而不是退而求其次去手改文件。
@@ -0,0 +1,10 @@
{
"schemaVersion": 1,
"skillId": "cocos-mcp-workflow",
"skillName": "funplay-cocos-mcp-workflow",
"templateVersion": 2,
"templateHash": "b0371afe3dd7084052927af8130a0929c0877cd48ade67ca9bd9605e1d8cee35",
"installedHash": "b0371afe3dd7084052927af8130a0929c0877cd48ade67ca9bd9605e1d8cee35",
"extensionVersion": "0.5.1",
"updatedAt": "2026-08-31T22:41:36.037Z"
}
@@ -0,0 +1,72 @@
---
name: funplay-cocos-mcp-workflow
description: "Edit, inspect, validate, preview, and debug Cocos Creator projects through Funplay Cocos MCP. Use when working with scenes, nodes, prefabs, assets, TypeScript, logs, screenshots, preview behavior, runtime state, or MCP connectivity."
---
# Funplay Cocos MCP Workflow
## Instructions
## Operating Loop
1. Establish context.
- Read `cocos://project/context` or call `get_editor_state` before assuming the project, active scene, MCP URL, selection, visible windows, or tool profile.
- Inspect the active scene with `get_scene_info` and `get_hierarchy`; use `get_selection`, `list_scenes`, `list_assets`, or `list_prefabs` when identity or ownership is unclear.
- Treat user-provided node and asset names as hints. Resolve the real hierarchy path, node UUID, asset UUID, or `db://assets/...` URL before editing.
- Call `get_tool_catalog` when a required tool may be hidden by the `core`, `full`, or custom exposure profile.
2. Choose the edit surface.
- Edit TypeScript and ordinary project files with repository tools or the MCP file tools, then refresh the affected asset and run diagnostics.
- Edit live scene nodes with focused scene/component tools or `execute_javascript` using `context="scene"`; save the scene when the change must persist.
- Use `execute_javascript` with `context="editor"` for asset-db, Editor messages, project orchestration, and filesystem work that belongs in the editor process.
- Inspect prefab ownership and references before mutation. Prefer focused prefab tools, or edit a verified linked instance and apply it back through the editor workflow.
- Preserve an existing UI or gameplay prefab hierarchy and change only the necessary nodes, components, and serialized fields; do not rebuild the entire prefab unless explicitly requested.
3. Execute the smallest coherent change.
- Prefer one guarded `execute_javascript` operation for tightly related editor work, but use focused tools when they provide clearer validation or safer arguments.
- Keep JavaScript safety checks enabled unless the code and its paths were reviewed explicitly.
- Null-check every scene, node, component, asset, and filesystem lookup. Return concise structured before/after values, including stable UUIDs or asset URLs where useful.
- Save or refresh only the assets and scenes intentionally changed.
- Do not guess alternate paths, silently create replacement objects, or run self-healing fallback loops after a missing reference or unsupported editor message.
4. Read back and validate.
- Re-inspect the exact node, component, prefab instance, or asset after mutation; a successful command response alone is not proof of the final editor state.
- Run `run_script_diagnostics` or `get_script_diagnostic_context` after TypeScript changes, then use `validate_scene` and project logs before claiming success.
- For visual or runtime work, run the appropriate browser, Game View, or simulator preview and verify with runtime state, input, logs, and screenshots.
- State exactly what was verified and what still requires a native build, device, network, store, or manual check.
## Scene, Prefab, and Asset Safety
- Do not treat Cocos `.scene`, `.prefab`, or `.meta` files as ordinary text. Prefer scene-process, prefab, and asset-db operations that preserve UUID references and editor import state.
- If `edit_prefab_json` is used, target a verified prefab path and the smallest exact JSON path or literal replacement, then run `validate_prefab_references` and inspect the result.
- Before structural prefab work, call `inspect_prefab`; for scene instances, call `inspect_prefab_instance` and choose deliberately between apply and revert.
- Replacing a prefab at the same path can keep the asset UUID while changing internal object IDs and breaking serialized references, animation tracks, nested prefab links, and scene overrides.
- Inspect dependencies with `inspect_asset_dependencies` and validate them with `validate_asset_dependencies` before and after sensitive asset changes.
- Never copy a `.meta` file when duplicating an asset. Use `duplicate_prefab` or asset-db operations so the new asset receives its own UUID.
## Tool Exposure and Execution Contexts
- The default `core` profile exposes the main inspection, diagnostics, logs, screenshots, scene, asset, and unified JavaScript workflow.
- The `full` profile adds focused mutation tools for nodes, components, prefabs, UI, runtime control, input simulation, files, and project preview.
- If a named tool is unavailable under a custom profile, adapt to the exposed catalog and report the missing capability instead of pretending it ran.
- In scene context, use the Cocos runtime and scene APIs for live hierarchy and component work. In editor context, use `Editor` APIs and messages for asset-db and extension orchestration.
- Use `execute_scene_script` and `execute_editor_script` only as compatibility entrypoints; prefer `execute_javascript` with an explicit context for new workflows.
## Script and Asset Validation
- After external script changes, refresh the affected asset or `db://assets`, then run TypeScript no-emit diagnostics. Use diagnostic context to read focused source snippets before repairing errors.
- Cocos import and compilation are asynchronous. After refresh, re-query diagnostics, logs, or asset info instead of assuming the first request observed the final state.
- Read `get_recent_logs` or `search_project_logs` for import, serialization, preview, and runtime failures. Do not clear persistent project logs without explicit confirmation.
- Use `validate_scene` as a compact final pass, not as a replacement for targeted readback of the values changed.
## Preview and Runtime Verification
- Query `get_preview_mode` before changing preview behavior. Use `run_project_preview` only when preview execution is needed and distinguish browser `localUrl` from a LAN `networkUrl`.
- Use `get_runtime_state` for pause, frame, and time-scale state; use focused runtime or component methods only when runtime behavior must be exercised.
- Use `capture_scene_screenshot` for scene-side composition, `capture_preview_screenshot` for game output, and `capture_editor_screenshot` for editor UI or extension panels.
- When low-level input is needed, list editor windows first and target the preview or simulator deliberately. Prefer semantic button events when available.
- Restore temporary runtime state such as pause or time scale before finishing unless the user explicitly wants it left changed.
## Failure Handling
- If MCP is unreachable, limit claims to safe filesystem inspection or code edits; do not claim scene, prefab, editor, preview, or runtime verification.
- If a node lookup is ambiguous, return the matching paths and UUIDs and choose only after identifying the user-visible or prefab-owned target.
- If editor readback and serialized text disagree, trust editor and asset-db readback first and investigate whether the wrong asset, scene instance, or stale import was inspected.
- Fix diagnostics or new error logs caused by the change before visual or runtime validation.
@@ -0,0 +1,10 @@
{
"schemaVersion": 1,
"skillId": "cocos-ui-composition",
"skillName": "funplay-cocos-ui-composition",
"templateVersion": 1,
"templateHash": "835f4c4b6e6dc79cabf4e5048ac056b6f2441b2b77c0c555ca502740cc175e72",
"installedHash": "835f4c4b6e6dc79cabf4e5048ac056b6f2441b2b77c0c555ca502740cc175e72",
"extensionVersion": "0.5.1",
"updatedAt": "2026-08-31T22:41:37.608Z"
}
@@ -0,0 +1,106 @@
---
name: funplay-cocos-ui-composition
description: "Build and revise responsive Cocos Creator UI for mobile, desktop, and web, including portrait and landscape layouts, safe areas, prefabs, Widget and Layout behavior, scrolling, text, input, animation, and performance validation."
---
# Funplay Cocos UI Composition
## Instructions
## Operating Loop
1. Inspect before editing.
- Confirm the active scene, Canvas, design resolution, Fit Width/Fit Height policy, target orientations, SafeArea usage, and relevant prefab asset paths.
- Inspect the existing hierarchy, `UITransform` sizes and anchor points, `Widget` constraints and align modes, `Layout` ownership, sibling order, render order, serialized references, animation targets, and prefab instance state.
- Treat screenshots and design coordinates as visual intent, not as permission to replace a working hierarchy.
2. Classify each region.
- Mark art as full-bleed or safe-area content.
- Mark placement as edge-aligned by Widget, stretched between edges, content-sized by Layout, repeated content, scrollable content, modal, overlay, or world-space UI.
- Decide which system owns position and size. Avoid letting Widget, Layout, animation, and manual code fight over the same property.
3. Make the smallest coherent change.
- Preserve the prefab root, existing children, components, names, UUID-backed references, animation tracks, nested prefab links, and scene overrides unless a specific replacement is required.
- Modify only the necessary nodes, components, fields, and children; do not rebuild the entire prefab unless explicitly requested.
- Prefer Cocos MCP scene, prefab, and asset-db workflows over generic text replacement for serialized assets.
4. Read back and validate.
- Read exact hierarchy, node UUIDs, `UITransform` sizes and anchors, Widget edges, Layout settings, sprites, labels, opacity, input blockers, event bindings, and prefab ownership back from Cocos.
- Test layout, input, safe area, localization, animation interruption, close/reopen state, and runtime data changes.
- Capture preview screenshots at representative aspect ratios. Use native builds on representative devices before claiming device performance or platform validation.
## Component Selection
| Component | Use it for | Configure deliberately | Avoid |
| --- | --- | --- | --- |
| `Canvas` | Root 2D/UI render space and design-resolution adaptation | Verify project design resolution, Fit Width/Fit Height, camera and layer behavior | Adding duplicate Canvases only to organize folders or assuming one resolution fits every aspect ratio |
| `UITransform` | UI size, anchor point, coordinate conversion, hit testing, and render priority | Set `contentSize` and anchor point intentionally; keep scale for visual effects rather than basic layout | Treating one screenshot position as universal or changing scale when size/Widget constraints should change |
| `Widget` | Edge, center, stretch, and parent-relative alignment | Choose top/bottom/left/right/center constraints and `ONCE`, `ON_WINDOW_RESIZE`, or `ALWAYS` based on runtime needs | Animating properties that an `ALWAYS` Widget rewrites at the end of the frame |
| `SafeArea` | Keeping critical controls inside notches and system gesture areas | Put it on the top interaction container; allow full-bleed backgrounds outside it | Applying safe-area insets twice or placing important controls outside the safe rectangle |
| `Layout` | Horizontal, vertical, or grid arrangement and container/child resizing | Choose type, resize mode, padding, spacing, constraint, and start axis; call `updateLayout` only when same-frame measurement is necessary | Putting Layout and Widget on the same node or manually positioning children driven by Layout |
| `Sprite` | Icons, panels, progress fills, and sliced or tiled UI art | Use sliced frames for scalable borders, preserve SpriteFrame references, and choose fill/type intentionally | Stretching bordered art as a simple Sprite or leaving decorative nodes interactive |
| `Label` / `RichText` | Localized text and formatted text | Set fonts, fallback coverage, alignment, wrapping, line height, overflow, and cache mode from actual update frequency | Broad `SHRINK` use on frequently changing labels or shipping without required CJK and symbol glyphs |
| `ScrollView` + `Mask` | Content larger than a viewport | Use a dedicated view/mask and content node, enable only required axes, and configure inertia, brake, bounce, and nested input deliberately | Combining Widget and Layout on the same Content node, or creating thousands of live rows without pooling |
| `Mask` | Rectangular, ellipse, graphics, or sprite-stencil clipping | Match the mask type to the visual requirement and keep renderer constraints in mind | Adding Sprite or Label renderers to a Mask node where Cocos requires the mask-owned Graphics/Sprite |
| `Button`, `Toggle`, `Slider`, `EditBox` | Semantic interaction | Verify transition state, target node, event handlers, hit area, keyboard/focus behavior, and disabled state | Duplicate event bindings, tiny touch targets, or visual-only disabled states |
| `UIOpacity` | Fading a UI subtree | Coordinate opacity with active state and input blocking | Hiding a modal visually while it still receives or blocks input |
| `BlockInputEvents` | Preventing pointer/touch events from passing through overlays and modals | Put it on the intended blocking region and validate sibling order and active state | Assuming a visible scrim blocks gameplay input by itself |
| `PageView` | Paged horizontal or vertical content | Recompute page and content sizes when the viewport changes and test drag thresholds | Hard-coding page positions from one device width |
## Design Resolution, Widget, and Safe Area
- Treat the design resolution as the coordinate baseline, not a physical-device whitelist. Verify the project Fit Width/Fit Height policy and any runtime `view.setDesignResolutionSize` policy before changing layout.
- Use Widget to express attachment and stretch. Use `ON_WINDOW_RESIZE` for resizable desktop/web or large-screen layouts, and use `ALWAYS` only when continuous alignment is worth its property ownership cost.
- When Widget owns an edge or size, change the Widget offsets or align mode rather than writing a Node position or UITransform size that will be overwritten later.
- Keep UI scale at one for ordinary layout. Resize through `UITransform.contentSize`, Widget constraints, Layout properties, or a deliberate design-resolution policy.
- Put decorative backgrounds outside the SafeArea interaction root so they can bleed to physical edges. Put buttons, labels, navigation, and other critical content under SafeArea.
- SafeArea already obtains `sys.getSafeAreaRect` and adjusts through Widget. Do not add a second manual inset unless the project has a documented additional margin.
- Re-evaluate layout on window resize, orientation change, or safe-area change when the target platform can change dimensions at runtime.
## Portrait and Landscape Patterns
- For portrait screens, organize persistent UI into Top, flexible Center, and Bottom regions. Let tall screens expand the center instead of multiplying every vertical coordinate by aspect ratio.
- For landscape screens, use Left, Center, Right, and stable corner regions. Verify 16:9, ultrawide, 16:10, and 4:3 instead of treating landscape as one shape.
- Separate camera/world composition from Canvas UI adaptation. A correct Widget layout does not prove the gameplay camera shows the intended world area.
- Use alternate art or deliberate cover/crop behavior when one background cannot preserve composition across phone, tablet, and desktop aspect ratios.
- Reposition only regions whose composition genuinely changes. Do not fork or rebuild the entire screen prefab for a few aspect-dependent offsets.
## Auto Layout and Dynamic Content
- A Layout component drives its children or container according to `ResizeMode`. Do not manually write the driven dimensions and expect them to survive the next layout update.
- For `ResizeMode.CHILDREN`, verify the container size and the resulting child sizes. For `ResizeMode.CONTAINER`, verify anchor point and growth direction so the container expands predictably.
- Grid Layout uses its configured cell and constraint policy. Use fixed rows or columns deliberately and do not expect heterogeneous child preferred sizes to define every cell.
- Runtime Layout property changes normally settle on the next frame. Call `updateLayout` only when code must read the final result in the same operation.
- Keep nested Layout chains shallow, batch data changes, pool repeated items, and avoid rebuilding a large hierarchy for every small model update.
## Sprites, Text, Scrolling, and Input
- Use sliced SpriteFrames for scalable bordered panels and buttons. Preserve caps/insets and inspect the actual SpriteFrame reference after prefab edits.
- For Label, choose `CLAMP`, `SHRINK`, or `RESIZE_HEIGHT` from a documented overflow policy. `SHRINK` can cost more CPU when text updates; `RESIZE_HEIGHT` transfers height ownership to the label.
- Choose Label cache mode from content behavior: avoid caching assumptions for highly dynamic text, and verify character coverage and atlas capacity for localized content.
- Structure a ScrollView as root, masked view, and content. Enable only the needed direction, verify the content reference, and test child-button cancellation and nested scroll behavior.
- Keep one intentional interactive target per control, bind events once, and validate the serialized target, component, handler, and custom event data.
- Give touch controls a project-defined minimum hit area even when the visible art is smaller. Verify with preview input rather than only inspecting dimensions.
- Synchronize modal visibility, `UIOpacity`, active state, Button interactability, and `BlockInputEvents` so hidden panels neither receive nor leak input.
## Animation and Prefab Safety
- Animate a `Visual` or `Container` child when the root is driven by Widget or Layout. Do not animate a property that layout rewrites every frame.
- Stop or cancel an existing tween/animation before replaying it and restore deterministic position, scale, opacity, active state, and input blocking on disable or close.
- Use unscaled scheduling or an explicit UI clock when menus and modal transitions must continue while gameplay time scale is zero.
- Preserve existing prefab objects by default. Replacing a prefab hierarchy can break internal object IDs, animation tracks, serialized component references, nested prefabs, and scene overrides even when the asset UUID remains stable.
- Prefer serialized references and stable semantic names over repeated `getChildByPath` lookups. If a path is required, verify it and fail clearly rather than silently creating an alternate hierarchy.
## Performance and Validation
- Profile before restructuring. Common UI costs include excessive nodes, draw-call breaks, masks/stencils, overdraw, dynamic text generation, repeated Layout work, and large unpooled lists.
- Preserve render batching by grouping compatible sprites and materials without changing intended sibling/render order. Use dynamic atlas or static batching only after verifying asset eligibility and runtime behavior.
- Use `get_performance_snapshot` to compare node, component, UI, depth, and memory-oriented counters before and after material changes.
- Validate portrait at 16:9, 19.5:9 or 20:9, a cutout phone, and a portrait tablet. Validate landscape at 16:9, ultrawide, 16:10, 4:3, and both cutout sides.
- In every profile, verify full-bleed art, safe interactive content, text overflow and glyphs, scroll bounds, modal blocking, touch targets, animation interruption, and close/reopen state.
## Official Cocos References
- [Cocos Creator 3.8 UI system](https://docs.cocos.com/creator/3.8/manual/en/ui-system/)
- [UITransform](https://docs.cocos.com/creator/3.8/api/en/class/UITransform) and [Widget](https://docs.cocos.com/creator/3.8/manual/en/ui-system/components/editor/widget.html)
- [SafeArea](https://docs.cocos.com/creator/3.8/manual/en/ui-system/components/editor/safearea.html) and [multi-resolution adaptation](https://docs.cocos.com/creator/3.8/manual/en/ui-system/components/engine/multi-resolution.html)
- [Layout](https://docs.cocos.com/creator/3.8/manual/en/ui-system/components/editor/layout.html) and [ScrollView](https://docs.cocos.com/creator/3.8/manual/en/ui-system/components/editor/scrollview.html)
- [Label](https://docs.cocos.com/creator/3.8/manual/en/ui-system/components/editor/label.html) and [Mask](https://docs.cocos.com/creator/3.8/manual/en/ui-system/components/editor/mask.html)
- [Node hierarchy and UI rendering order](https://docs.cocos.com/creator/3.8/manual/en/concepts/scene/node-tree.html)
@@ -0,0 +1,2 @@
[InternetShortcut]
URL=https://docs.cocos.com/creator/manual/en/scripting/setup.html#custom-script-template
@@ -22,9 +22,9 @@ export interface DebugProfile {
}
/** 正式 gameserver:原 00_SubGame_Config.js:11。 */
export const DEFAULT_GAMESERVER = 'http://ylyxservice1.0791ts.cn/config/update_json.txt';
export const DEFAULT_GAMESERVER = 'https://tsgames.daoqi88.cn/config/update_jsonv2.txt';
/** 测试 gameserver:原 00_SubGame_Config.js:9 注释。 */
const STAGING_GAMESERVER = 'https://tsgames.daoqi88.cn/config_test/beta_update_jsonv2.txt';
const STAGING_GAMESERVER = 'https://tsgames.daoqi88.cn/config/update_jsonv2.txt';
export const PROFILES: Record<string, DebugProfile> = {
prod: { name: 'prod', gameserver: DEFAULT_GAMESERVER, isDebugger: false },
@@ -1,15 +1,27 @@
// 1. 先 export type
export type RuntimeMode = 'debug' | 'release';
/**
* 手动覆盖运行模式。
* null = 自动跟随构建类型(推荐,发布前保持 null)。
* 'debug' / 'release' = 强制该模式(release 包里临时调试,或编辑器预览里验证正式行为)。
*/
export const MODE_OVERRIDE: RuntimeMode | null = null;
declare const DEBUG: boolean | undefined;
// 2. 函数引用 RuntimeMode(此时 type 已 export)
function readModeFromUrl(): RuntimeMode | null {
if (typeof window === 'undefined' || !window.location) return null;
const m = new URLSearchParams(window.location.search).get('mode');
if (m === 'debug' || m === 'release') return m;
return null;
}
function detectDebugBuild(): boolean {
if (typeof DEBUG !== 'undefined') return DEBUG;
return true; // Cocos 编辑器内 dev workflow 默认;生产 build 改 false
}
// 3. 导出常量
export const MODE_OVERRIDE: RuntimeMode | null =
readModeFromUrl() ?? (detectDebugBuild() ? 'debug' : 'release');
/**
* 解析运行模式:override 优先,否则由是否 debug 构建决定。
* isDebugBuild 由调用方读 Cocos 全局 DEBUG 传入(typeof DEBUG !== 'undefined' ? DEBUG : true)。
*/
export function resolveRuntimeMode(
isDebugBuild: boolean,
@@ -7,7 +7,7 @@ import type { ChannelIdentity } from '../identity.ts';
export const BUILD_IDENTITY: ChannelIdentity = {
agentid: 'veRa0qrBf0df2K1G4de2tgfmVxB2jxpv',
channelid: 'FtJf073aa0d6rI1xD8J1Y42fINTm0ziK',
gameid: 'jzWe0kzmN0tAcZ5MLks3rFWcxNecogyu',
gameid: 'J9r4022t80pxlb01zOK76DN1WD4QewFl',
marketid: 4,
version: 10000, // 数字 versionCode(联调时改成本地服要求的版本号)
version: 1, // 数字 versionCode(联调时改成本地服要求的版本号)
};
@@ -0,0 +1,152 @@
/**
* 原生↔H5 异步桥(WebViewJavascriptBridge,WVJB)+ window.settings 同步取值。
*
* 严格对齐原 gameabc 项目(05_Func.js:2627 起)的外部契约:
* - 14 个 handler 名白名单(与原生 app 逐字对齐)
* - `window.WVJBCallbacks` / `WebViewJavascriptBridgeReady` 事件名
* - `window.settings.getothername(name)` 同步取值 API
* - 回调签名 `(data, responseCallback)` 与数据结构
*
* 内部命名(接口/函数/常量)走现代专业风格,与上述外部契约清晰隔离。
*
* 架构边界:本模块只暴露受限 facade,不允许 framework 业务代码直接读
* window/bridge 内部。
*/
/** window 上的原生注入对象(05_Func.js:2467-2471 同步取值)。 */
declare global {
interface Window {
settings?: {
/** 同步取一个原生配置项,无则返回空串 */
getothername(name: string): string;
};
/** WVJB 启动前的全局回调队列(外部契约,不能改名) */
WVJBCallbacks?: Array<(bridge: NativeBridgeChannel) => void>;
/** WebView 注入 WVJB 的事件名(外部契约) */
WebViewJavascriptBridgeReady?: Array<() => void>;
}
}
/**
* 原生桥通道契约 —— WVJB bridge 实例的最小子集。
* 内部命名为 "Channel" 以避免缩写、明确"双向通信通道"语义。
*/
export interface NativeBridgeChannel {
/** H5 注册供原生侧调用的 handler */
registerHandler(name: string, handler: NativeHandler): void;
/** H5 主动调用原生侧 */
callHandler(name: string, data: unknown, responseCallback?: (resp: unknown) => void): void;
}
/** H5 注册供原生调用的 handler 函数签名(外部 WVJB 协议约定) */
export type NativeHandler = (data: unknown, responseCallback: NativeResponseCallback) => unknown;
/** 响应回调:原生侧通过此把结果回传给 H5 */
export type NativeResponseCallback = (responseData: unknown) => void;
/**
* 14 个已注册 handler 名白名单(05_Func.js:2651+ 原项目实装)。
* 与原生 app 端逐字对齐 —— app 按这些名字调,改名 app 也要改。
*
* 字面量联合类型:registerHandler/callHandler 入参必须在此集合内,
* 拼写错误编译期就发现,集合外名字运行时显式抛错(第二准则:不静默兜底)。
*/
export const NATIVE_HANDLER_NAMES = [
'getVideoinfo',
'sharelogin',
'sharesuccess',
'gameui_play_voice',
'gameui_stop_voice',
'getphoneinfo',
'getAddressBook',
'phonestate',
'appservice',
'getaudiourl',
'getBattery',
'getwifiLevel',
'getnetwork',
'shakeEnd',
] as const;
export type NativeHandlerName = (typeof NATIVE_HANDLER_NAMES)[number];
function isNativeHandlerName(name: string): name is NativeHandlerName {
return (NATIVE_HANDLER_NAMES as readonly string[]).includes(name);
}
/** createBridge 初始化选项 */
export interface BridgeOptions {
/** 通道就绪回调(原生侧 WebView bridgeReady 时触发一次) */
onReady(channel: NativeBridgeChannel): void;
/**
* 初始化超时的兜底(可省略)。
* 原生侧可能永不触发 WebViewJavascriptBridgeReady,业务可在此降级。
*/
onTimeout?(reason: string): void;
}
/**
* 受限 Bridge facade —— 框架业务访问原生能力的唯一入口。
*
* 创建后立即尝试初始化:
* 1. 把 setup 回调 push 到 window.WVJBCallbacks(原生侧约定的入口)
* 2. 原生侧 WebView bridgeReady 时遍历 WVJBCallbacks 调用每个 cb → onReady 被触发
*
* 一旦通道就绪,onReady 被调用一次。
*/
export interface Bridge {
/** 当前通道是否已就绪 */
isReady(): boolean;
/** H5 注册供原生侧调用(白名单校验) */
registerHandler(name: NativeHandlerName, handler: NativeHandler): void;
/** H5 调用原生侧(白名单校验) */
callHandler(name: NativeHandlerName, data: unknown, cb?: NativeResponseCallback): void;
}
export function createBridge(opts: BridgeOptions): Bridge {
const g = globalThis as any;
// 外部契约:把 setup cb push 到 window.WVJBCallbacks
if (!g.WVJBCallbacks) g.WVJBCallbacks = [];
let ready = false;
g.WVJBCallbacks.push((channel: NativeBridgeChannel) => {
ready = true;
// 缓存通道供 callHandler 委托(避免 onReady 闭包外传)
g.__activeChannel = channel;
opts.onReady(channel);
});
return {
isReady() { return ready; },
registerHandler(name, handler) {
if (!isNativeHandlerName(name)) {
throw new Error(`registerHandler: handler "${name}" 不在白名单(第二准则:不静默兜底)`);
}
if (!ready) throw new Error('NativeBridge not ready: 通道未初始化,请先等 onReady 回调');
g.__activeChannel.registerHandler(name, handler);
},
callHandler(name, data, cb) {
if (!isNativeHandlerName(name)) {
throw new Error(`callHandler: handler "${name}" 不在白名单(第二准则:不静默兜底)`);
}
if (!ready) throw new Error('NativeBridge not ready: 通道未初始化,请先等 onReady 回调');
g.__activeChannel.callHandler(name, data, cb);
},
};
}
/**
* 从 window 取原生注入的配置项(05_Func.js:2467-2471 的 getothername)。
*
* @param name 配置项名(渠道/包信息/gameserver 覆盖等)
* @returns 原生侧返回值;缺失时返回空串(对齐原项目:无则空字符串而非 undefined)
* @throws 原生侧未注入 window.settings 时显式抛错(不静默返回空串——避免下游误用)
*/
export function getNativeSetting(name: string): string {
const settings = (globalThis as any).window?.settings;
if (!settings || typeof settings.getothername !== 'function') {
throw new Error(
`getNativeSetting("${name}"): window.settings 未注入(原生侧未加载或非 WebView 环境)`,
);
}
return settings.getothername(name);
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "2eb9409f-115d-4a7d-bdc4-30a333b57a28",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,50 @@
import type { EventBus } from '../core/events.ts';
import type { NetClientEvents } from '../net/net-client.ts';
import type { InboundMessage } from '../core/types/envelope.ts';
import { roomHandlers } from '../protocol/room-handlers.ts';
import type { PlatformSession } from './session.ts';
/**
* RoomRPCBus:把 NetClient 的 'message' 事件按 route 分发到 room handlers。
*
* 监听 NetClient 'message' → 过滤 route==='room' → 查 roomHandlers[rpc](ctx, data)。
*
* 业务侧在 PlatformSession 构造后立即创建 RoomRPCBus 并 start(),
* 后续 stop() 退订(进/退房切换清理)。
*/
export class RoomRPCBus {
private handler: ((msg: InboundMessage) => void) | null = null;
constructor(
private readonly bus: EventBus<NetClientEvents>,
private readonly session: PlatformSession,
) {}
/** 开始监听 NetClient 消息事件 */
start(): void {
if (this.handler) return; // 幂等
this.handler = (msg) => this.dispatch(msg);
this.bus.on('message', this.handler);
}
/** 停止监听(切房/退出时调用) */
stop(): void {
if (this.handler) { this.bus.off('message', this.handler); this.handler = null; }
}
private dispatch(msg: InboundMessage): void {
if (msg.route !== 'room') return; // 平台三层 + 对局 route 由 Router 单独处理
const handler = (roomHandlers as unknown as Record<string, ((ctx: any, data: any) => void) | undefined>)[msg.rpc];
if (!handler) {
// 未注册 rpc:roomHandlers 的 Object.freeze 已确保此处为 undefined
// 不静默吞包:抛错便于真机端到端发现协议漂移
throw new Error(
`RoomRPCBus: 未注册的 room rpc "${msg.rpc}"(roomHandlers 不覆盖)`,
);
}
handler(
{ room: this.session.room, player: this.session.player },
msg.data as Record<string, unknown>,
);
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "b2f4361c-64f4-4a29-a5db-a4bd45b42f84",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,50 @@
import type { EventBus } from '../core/events.ts';
import type { NetClientEvents } from '../net/net-client.ts';
import type { LoginResponseData } from '../core/types/login.ts';
import { AppStore } from './stores/app-store.ts';
import { PlayerStore } from './stores/player-store.ts';
import { RoomStore } from './stores/room-store.ts';
/**
* PlatformSession:平台会话编排(C 规范 §6.1 启动流程)。
*
* 订阅 NetClient 事件总线,把 login/连接事件落到三个响应式 Store:
* - 'open' → phase = 'connected'
* - 'login'(data) → 解析 state:state==0 → 三 Store 填充 + phase=loggedIn;
* state!=0 → phase=loginFailed(不写入 playerid/roomcode)
* - 'reconnecting' → phase = 'reconnecting'
* - 'slow' → phase = 'slow'
* - 'kicked' → phase = 'kicked'(不再重连,由 NetClient 自身决定)
*
* 不持有业务态——只做事件→Store 的扇出。Store 是状态唯一来源。
*/
export class PlatformSession {
readonly app = new AppStore();
readonly player = new PlayerStore();
readonly room = new RoomStore();
constructor(bus: EventBus<NetClientEvents>) {
bus.on('open', () => this.app.setPhase('connected'));
bus.on('login', (data) => this.onLogin(data));
bus.on('reconnecting', () => this.app.setPhase('reconnecting'));
bus.on('slow', () => this.app.setPhase('slow'));
bus.on('kicked', () => this.app.setPhase('kicked'));
}
private onLogin(data: unknown): void {
const d = data as LoginResponseData & { state?: number };
if (d.state !== 0) {
// 登录失败:仅记 phase,不污染 playerid/roomcode 等敏感字段
this.app.setPhase('loginFailed');
return;
}
this.player.applyLogin(d);
this.app.applyLogin(d);
if (d.roomcode) {
this.room.applyRecovery(d as unknown as Parameters<RoomStore['applyRecovery']>[0]);
} else {
this.room.clear();
}
this.app.setPhase('loggedIn');
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "6831840f-689a-4fb2-bfb5-c23c45fdb59c",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,87 @@
import type { EventBus } from '../core/events.ts';
import type { NetClientEvents } from '../net/net-client.ts';
import type { ChannelIdentity } from '../config/identity.ts';
import type { BootstrapResult } from '../config/bootstrap.ts';
import type { PlatformSession } from './session.ts';
import type { RoomRPCBus } from './room-rpc-bus.ts';
/**
* StartupOrchestrator:把启动链路串起来的纯逻辑编排器。
*
* 流程:bootstrap() → onLogin()(由 PlatformSession 在收到 login 事件后调用)→ ready
*
* 不直接构造 NetClient / RoomRPCBus(依赖注入),便于单测覆盖全部阶段。
* 不主动连 WS/发包——NetClient 由业务侧触发 start(),本类只编排阶段信号。
*
* 启动门控四条件(C 规范 §6.1):资源(外部)/ WS onopen(NetClient.on)/ 配置(本类 bootstrap)/ 加载计时器(外部)。
* waitForReady() 合并后三个为「就绪信号」,UI 层 await 进入登录页。
*/
export interface StartupOptions {
session: PlatformSession;
bus: EventBus<NetClientEvents>;
/** 返回 bootstrap 结果(远程配置 + 身份 + 服务器列表) */
getBootstrapResult: () => Promise<BootstrapResult>;
/** 构造 NetClient 的工厂(注入避免硬依赖真 WS) */
makeNetClient: () => { bus: EventBus<NetClientEvents> };
/** 用于 session 初始化的身份字段 */
identity: ChannelIdentity;
/** 构造 RoomRPCBus 的工厂 */
roomBusFactory: (session: PlatformSession, bus: EventBus<NetClientEvents>) => RoomRPCBus;
}
export class StartupOrchestrator {
bootstrapResult: BootstrapResult | null = null;
private readyResolve: (() => void) | null = null;
private readyPromise: Promise<void>;
private roomBus: RoomRPCBus | null = null;
private netClient: { bus: EventBus<NetClientEvents> } | null = null;
constructor(private readonly opts: StartupOptions) {
// 立即构造一个 pending Promise,UI 层可提前 await
this.readyPromise = new Promise<void>((resolve) => {
this.readyResolve = resolve;
});
}
/** 阶段 1:拉取并解析远程配置,失败显式抛错 */
async bootstrap(): Promise<BootstrapResult> {
const result = await this.opts.getBootstrapResult();
this.bootstrapResult = result;
// 把身份 + 服务器写入 AppStore(session 在构造时已订阅 login 等事件,
// 这里直接驱动 AppStore.setServers/setIdentity 以提前有数据)
this.opts.session.app.setServers(result.servers);
this.opts.session.app.setIdentity(result.identity);
return result;
}
/**
* 阶段 2/3:构造 NetClient + RoomRPCBus,准备就绪(但不主动 start 连接;
* 业务侧在收到 phase=connected 后再 netClient.start())。
*
* 通常 PlatformSession 收到 'login' 事件后调用此方法——login 事件
* 表明 NetClient 已 start 且 server 已响应 player_login。
*/
async onLogin(): Promise<void> {
// 构造 NetClient(业务侧会调用 start() 真正建立 WS)
this.netClient = this.opts.makeNetClient();
// 构造 RoomRPCBus 并立即 start,把 room route 的包挂到 bus
this.roomBus = this.opts.roomBusFactory(this.opts.session, this.netClient.bus);
this.roomBus.start();
// 触发 ready 信号
if (this.readyResolve) { this.readyResolve(); this.readyResolve = null; }
}
/** UI 层等待「bootstrap + login + 房间 rpc 通道就绪」 */
async waitForReady(): Promise<void> {
return this.readyPromise;
}
isReady(): boolean { return this.readyResolve === null; }
/** 清理:停止 RoomRPCBus + 关闭 NetClient */
shutdown(): void {
this.roomBus?.stop();
this.roomBus = null;
this.netClient = null;
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "6a645fcf-0721-4c74-b14b-850aec6a0746",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,67 @@
import { signal, type ReadonlyReactive } from '../../core/reactive.ts';
import type { AppState, ConnectionPhase } from './types.ts';
import type { ReadonlyAppStore } from '../readonly.ts';
import type { ChannelIdentity } from '../../config/identity.ts';
import type { LoginResponseData } from '../../core/types/login.ts';
/** AppState 初始值。 */
const INITIAL: AppState = {
identity: null,
servers: [],
agentid: '',
channelid: '',
agentname: '',
agentmode: 0,
gameversion: 0,
phase: 'idle',
};
/** applyLogin 入参契约:A 组公共字段(agentid/channelid/agentname/agentmode/gameversion)。 */
export interface AppLoginData {
agentid?: string | number;
channelid?: string | number;
agentname?: string;
agentmode?: number;
gameversion?: number;
}
/**
* AppStore:应用级状态(= 旧 GameData 子集)。
* - identity/servers 由 config/bootstrap 启动编排时写入
* - phase 由 NetClient 事件总线经 PlatformSession 驱动
* - agentid/channelid/agentname/agentmode/gameversion 由 login 回包回填
*/
export class AppStore implements ReadonlyAppStore {
private s = signal<AppState>({ ...INITIAL });
get state(): ReadonlyReactive<AppState> { return this.s; }
setIdentity(identity: ChannelIdentity): void {
this.s.value = { ...this.s.value, identity };
}
setServers(servers: string[]): void {
this.s.value = { ...this.s.value, servers };
}
/** phase 由 PlatformSession 从 NetClient 事件总线驱动,session.ts 唯一调用方。 */
setPhase(phase: ConnectionPhase): void {
this.s.value = { ...this.s.value, phase };
}
/**
* login 回填 A 组字段。语义分两类:
* - 身份字段(agentid/channelid/agentmode/gameversion):缺省时保留旧值(避免身份错乱)
* - 显示字段(agentname):缺省时重置为空(避免显示旧值)
*/
applyLogin(raw: LoginResponseData | AppLoginData): void {
const r = raw as AppLoginData;
this.s.value = {
...this.s.value,
agentid: r.agentid ?? this.s.value.agentid,
channelid: r.channelid ?? this.s.value.channelid,
agentmode: r.agentmode ?? this.s.value.agentmode,
gameversion: r.gameversion ?? this.s.value.gameversion,
agentname: r.agentname ?? '',
};
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "c3961eae-ab46-4dc1-8822-25cdd39025db",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,68 @@
import { signal, type ReadonlyReactive } from '../../core/reactive.ts';
import type { RoomState, PlayerState } from './types.ts';
import type { ReadonlyRoomStore } from '../readonly.ts';
/** RoomState 初始值:所有座位为空。 */
const INITIAL: RoomState = {
inRoom: false,
roomcode: null,
isbattle: 0,
roommode: 0,
seat: -1,
isowner: 0,
asetcount: 0,
makewar: 0,
needprepare: 0,
infinite: 0,
state: 0,
players: [],
deskinfo: null,
};
/** RoomStore 的入参契约(B 组,从 login 响应或 rpc 回包填充)。 */
export interface RoomRecoveryData {
roomcode?: string | null;
isbattle?: number;
roommode?: number;
seat?: number;
isowner?: number;
asetcount?: number;
makewar?: number;
needprepare?: number;
infinite?: number;
players?: Array<PlayerState | null>;
deskinfo?: unknown;
}
/**
* RoomStore:房间/牌桌状态(= 旧 Desk)。
* login 含 roomcode 时或在房状态下填充;离开/未在房时 clear()。
*/
export class RoomStore implements ReadonlyRoomStore {
private s = signal<RoomState>({ ...INITIAL });
get state(): ReadonlyReactive<RoomState> { return this.s; }
applyRecovery(raw: RoomRecoveryData): void {
this.s.value = {
...this.s.value,
inRoom: true,
roomcode: raw.roomcode ?? null,
isbattle: raw.isbattle ?? 0,
roommode: raw.roommode ?? 0,
seat: raw.seat ?? -1,
isowner: raw.isowner ?? 0,
asetcount: raw.asetcount ?? 0,
makewar: raw.makewar ?? 0,
needprepare: raw.needprepare ?? 0,
infinite: raw.infinite ?? 0,
players: raw.players ?? [],
// deskinfo 原样透传(C 规范 §5 边界判据 2):不深拷贝、不解析
deskinfo: raw.deskinfo ?? null,
};
}
/** 离开房间/未在房,复位。 */
clear(): void {
this.s.value = { ...INITIAL };
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "9cba57c5-ce0c-4345-8ee2-0448112aa312",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -37,6 +37,18 @@ export interface RoomState {
seat: number; // 自己的座位(-1 未入座)
isowner: number; // 1房主/0非
asetcount: number; makewar: number; needprepare: number; infinite: number;
players: unknown[]; // 按座位下标(可含 null);完整建模留后续切片
deskinfo: unknown; // 子游戏对局快照,opaque 透传
/**
* 房间解散状态:0=正常 1=投票中 2=已解散。
* 业务侧触发清理/离开。
*/
state: 0 | 1 | 2;
/**
* 按座位下标的玩家数组。空座位用 null 占位(不是 undefined、不是空对象),
* 长度由 maxPlayerCount 决定,由 RoomStore.applyRecovery 校验填充。
*/
players: Array<PlayerState | null>;
/**
* 子游戏对局快照,opaque 透传(C 规范 §5 边界判据 2:平台层不持有、不解析)。
*/
deskinfo: unknown;
}
@@ -0,0 +1,17 @@
import type { IGameModule } from '../sdk/index.ts';
/**
* 当前激活的子游戏槽(= 旧 Game_Modify 的运行时角色)。
*
* 框架在进房时 setGame(),离房时 clear()。Router 据此分发对局 route 的包。
* 单例设计:同一时刻只允许一个子游戏激活(与旧 12_Logic 一致)。
*/
export class ActiveGame {
private game: IGameModule | null = null;
get(): IGameModule | null { return this.game; }
set(game: IGameModule): void { this.game = game; }
clear(): void { this.game = null; }
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "d8981b12-16b5-49a7-95d6-a51eb735462a",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,124 @@
import type { RoomStore } from '../platform/stores/room-store.ts';
import type { PlayerStore } from '../platform/stores/player-store.ts';
/**
* Room handler 上下文:注入 RoomStore + 自己的 PlayerStore(部分 handler 需要)。
*
* 业务侧(PlatformSession)在收到 room route rpc 时,从 NetClient 事件总线
* 取出 Envelope{route:'room', rpc, data} → Router 派发 → 查 roomHandlers[rpc](ctx, data)。
*/
export interface RoomHandlerContext {
room: RoomStore;
player: PlayerStore;
}
/** 单个 handler 的数据形参 —— 各 rpc 不同,这里只规定最基础形状。 */
export type RoomRpcData = Record<string, unknown>;
/** 单个 handler 的签名 */
export type RoomHandler = (ctx: RoomHandlerContext, data: RoomRpcData) => void;
/**
* 把任意 rpc 名映射到对应 handler;未注册的显式抛错(第二准则:不静默吞包)。
*
* 为避免构造后被外部写入不安全 handler,采用 Object.freeze。
*/
function defineHandlers(table: Record<string, RoomHandler>): Readonly<Record<string, RoomHandler>> {
const wrapped: Record<string, RoomHandler> = {};
for (const [k, v] of Object.entries(table)) wrapped[k] = v;
return Object.freeze(wrapped);
}
function seatIndex(seat: unknown, max: number): number {
if (typeof seat !== 'number' || !Number.isInteger(seat) || seat < 1 || seat > max) {
throw new Error(
`Room handler: 无效座位 seat=${String(seat)}(1 基,有效范围 1..${max})`,
);
}
return seat - 1; // 1 基 → 0 基
}
export const roomHandlers = defineHandlers({
/** 自己加入房间(通常在 login/self_join_room 走完) */
self_join_room(ctx, data) {
const seat = seatIndex(data.seat, ctx.room.state.value.players.length || 8);
// 自己加入:把 seat 写入(其它 fields 由 login/applyRecovery 统一管理)
(ctx.room.state.value as any).seat = seat + 1;
},
/** 第三方加入:把对应 seat 位置填上 PlayerState */
other_join_room(ctx, data) {
const len = ctx.room.state.value.players.length;
const i = seatIndex(data.seat, len);
const next = [...ctx.room.state.value.players];
next[i] = {
playerid: data.playerid as number,
nickname: data.nickname as string,
avatar: (data.avatar as string) ?? '',
sex: (data.sex as number) ?? 0,
bean: (data.bean as number) ?? 0,
roomcard: (data.roomcard as number) ?? 0,
score: 0, charm: 0, taskstate: 0, advanced: 0,
bankpower: 0, bank: 0, bankpwd: 0,
ip: '', sign: null, tel: null,
invitecode: null, initCard: 0, initBean: 0,
};
(ctx.room.state.value as any).players = next;
},
/** 自己退出:清空自己座位;PlayerStore 不动(退出房间不抹掉登录态) */
self_exit_room(ctx, data) {
const len = ctx.room.state.value.players.length;
const i = seatIndex(data.seat, len);
const next = [...ctx.room.state.value.players];
next[i] = null;
(ctx.room.state.value as any).players = next;
},
/** 第三方退出:清空对应座位 */
other_exit_room(ctx, data) {
const len = ctx.room.state.value.players.length;
const i = seatIndex(data.seat, len);
const next = [...ctx.room.state.value.players];
next[i] = null;
(ctx.room.state.value as any).players = next;
},
/** 自己发起解散投票:state=1 */
self_apply_free_room(ctx, _data) {
(ctx.room.state.value as any).state = 1;
},
/** 第三方发起解散投票:state=1 */
other_apply_free_room(ctx, _data) {
(ctx.room.state.value as any).state = 1;
},
/** 同意解散:不改变 state(agreeList 由其他协议累计) */
self_agree_free_room(_ctx, _data) { /* no-op */ },
other_agree_free_room(_ctx, _data) { /* no-op */ },
/** 拒绝解散:不改变 state */
self_refuse_free_room(_ctx, _data) { /* no-op */ },
other_refuse_free_room(_ctx, _data) { /* no-op */ },
/** 离线/上线:RoomStore 不持有 offline 字段,handler 是 no-op(业务侧订阅子游戏 IGameModule 钩子) */
other_offline(_ctx, _data) { /* no-op */ },
other_online(_ctx, _data) { /* no-op */ },
/** 换座位:从一个座位换到另一个 */
change_seat(ctx, data) {
const len = ctx.room.state.value.players.length;
const from = seatIndex(data.from, len);
const to = seatIndex(data.to, len);
if (from === to) return;
const next = [...ctx.room.state.value.players];
[next[from], next[to]] = [next[to], next[from]];
(ctx.room.state.value as any).players = next;
},
/** 解散投票结束:state=2(业务侧触发清场逻辑) */
free_room(ctx, _data) {
(ctx.room.state.value as any).state = 2;
},
});
@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "54af01a5-c0dc-4df2-8792-c609f4a74015",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,53 @@
import type { InboundMessage } from '../core/types/envelope.ts';
import { Route } from '../core/constants.ts';
import type { ActiveGame } from './active-game.ts';
/** 平台层 handler 表:key = rpc 字符串(与协议逐字对齐)。 */
export type PlatformHandlers = Record<string, (data: unknown) => void>;
/**
* Router:收包路由分界(C §4.1 的红线)。
*
* 分发规则:
* - route ∈ {platform, agent, room} → 平台 handlers(RpcList 覆盖的包)
* - route = <game route>(= activeGame.route)→ activeGame.onReceive
* - 其他或未注册:显式抛错(第二准则:不静默吞包)
*
* 不持有状态——只做消息分发。状态由 PlatformSession/子游戏维护。
*/
export class Router {
constructor(
private readonly opts: {
activeGame: ActiveGame;
platformHandlers: PlatformHandlers;
},
) {}
dispatch(msg: InboundMessage): void {
const { route, rpc, data } = msg;
// 平台三层 route:platform/agent/room 全部走平台 handlers
if (route === Route.platform || route === Route.agent || route === Route.room) {
const handler = this.opts.platformHandlers[rpc];
if (!handler) {
throw new Error(
`Router 收到未注册的平台 rpc: route=${route} rpc=${rpc}(platformHandlers 未注册,第二准则禁止兜底)`,
);
}
handler(data);
return;
}
// 对局 route:必须与 activeGame.route 严格匹配
const game = this.opts.activeGame.get();
if (!game) {
throw new Error(
`Router 收到对局包但无激活子游戏: route=${route} rpc=${rpc}(未进房或子游戏未注册)`,
);
}
if (game.route !== route) {
throw new Error(
`Router route 与 activeGame.route 不匹配: msg.route=${route} activeGame.route=${game.route}`,
);
}
game.onReceive(rpc, data);
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "b7886dd6-a54a-48de-880f-bf4c50d86a75",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -1,9 +1,133 @@
import { Route } from '../core/constants.ts';
export { Route };
/** 平台层会用到的 rpc 名(最小集)。完整清单见 docs/protocol/02、03。 */
/**
* 协议层 rpc 名(与 docs/protocol/02、03 及 projects/Game_Surface_3/js/00_Surface/02_Const.js 逐字对齐)。
*
* 字面量联合类型:业务侧发包(NetClient.send)/收包 handler(platformHandlers + roomHandlers + sdk onReceive)入参必须在此集合内,
* 拼错编译期就发现;未注册的 rpc 运行时显式抛错(第二准则:不静默兜底)。
*/
export const Rpc = {
// ---- 登录与连接 ----
player_login: 'player_login',
kick_server: 'kick_server',
connect_roomserver: 'connect_roomserver',
connect_agentserver: 'connect_agentserver',
agentserver_game: 'agentserver_game',
// ---- 房间生命周期(room route)----
create_room: 'create_room',
self_join_room: 'self_join_room',
other_join_room: 'other_join_room',
self_exit_room: 'self_exit_room',
other_exit_room: 'other_exit_room',
self_break_room: 'self_break_room',
other_break_room: 'other_break_room',
player_prepare: 'player_prepare',
change_seat: 'change_seat',
change_room: 'change_room',
switchRoomList: 'switchRoomList',
// ---- 对局(room route)----
self_makewar: 'self_makewar',
other_makewar: 'other_makewar',
over_game: 'over_game',
get_player_grade1: 'get_player_grade1',
get_player_grade2: 'get_player_grade2',
send_gift: 'send_gift',
other_send_gift: 'other_send_gift',
beanroom_surrender: 'beanroom_surrender',
send_phiz: 'send_phiz',
broadcast: 'broadcast',
other_offline: 'other_offline',
other_online: 'other_online',
// ---- 解散投票(room route)----
self_apply_free_room: 'self_apply_free_room',
other_apply_free_room: 'other_apply_free_room',
self_agree_free_room: 'self_agree_free_room',
other_agree_free_room: 'other_agree_free_room',
self_refuse_free_room: 'self_refuse_free_room',
other_refuse_free_room: 'other_refuse_free_room',
free_room: 'free_room',
// ---- 聊天 / 语音 / 电话(room route)----
send_voice: 'send_voice',
play_voice: 'play_voice',
send_text: 'send_text',
receive_chat: 'receive_chat',
call_phone: 'call_phone',
other_callphone: 'other_callphone',
hangup_phone: 'hangup_phone',
other_hangup: 'other_hangup',
kick_offline: 'kick_offline',
// ---- 资产 / 经济(agent/room route)----
update_roomcard: 'update_roomcard',
update_bean: 'update_bean',
update_charm: 'update_charm',
giveCoin: 'giveCoin',
change_star: 'change_star',
topup_card: 'topup_card',
get_paylist: 'get_paylist',
pay_succ: 'pay_succ',
get_treasurelist: 'get_treasurelist',
// ---- 任务(agent route)----
get_player_task: 'get_player_task',
player_finish_task: 'player_finish_task',
get_task_award: 'get_task_award',
can_award: 'can_award',
// ---- 社交 / 邀请 / 分享(agent route)----
binding_invitecode: 'binding_invitecode',
get_player_invitecode: 'get_player_invitecode',
share_room: 'share_room',
get_share_room: 'get_share_room',
quick_enter_share_room: 'quick_enter_share_room',
// ---- 房间列表 / 高级(agent route)----
advanced_roomlist: 'advanced_roomlist',
advanced_createroom: 'advanced_createroom',
query_player2: 'query_player2',
// ---- 银行 / 资料(agent route)----
set_bankpwd: 'set_bankpwd',
// ---- 手机绑定 / 验证码(agent route)----
binding_phone: 'binding_phone',
send_phone_checkcode: 'send_phone_checkcode',
send_phone_code_wechat: 'send_phone_code_wechat',
submit_phoneinfo: 'submit_phoneinfo',
// ---- 位置 / 反馈(agent route)----
submit_location: 'submit_location',
submit_opinion: 'submit_opinion',
// ---- 签名 / 短码房间(agent route)----
setSign: 'setSign',
getInfoByShortCode: 'getInfoByShortCode',
optBanList: 'optBanList',
getShortCodeRankList: 'getShortCodeRankList',
// ---- VIP / 黑名单 / 白名单(agent route)----
setAllCharm: 'setAllCharm',
getVipRankList: 'getVipRankList',
playerBehavior: 'playerBehavior',
setVipForbidSelect: 'setVipForbidSelect',
getPlayerWhiteList: 'getPlayerWhiteList',
optWhiteList: 'optWhiteList',
// ---- H5 专用(agent route,由 joinRoom 入口直达)----
joinRoom: 'joinRoom',
} as const;
/** 全部 rpc 名的字面量联合类型(编译期校验入参) */
export type RpcName = (typeof Rpc)[keyof typeof Rpc];
/** Route + Rpc 二元组的契约(用于 typed send / typed receive) */
export interface Envelope<T = unknown> {
route: typeof Route[keyof typeof Route];
rpc: RpcName;
data: T;
}
@@ -0,0 +1,52 @@
import { Route } from '../core/constants.ts';
import type { RpcName } from './routes.ts';
/**
* RpcRouteMap:rpc → route 的映射表(按协议分类)。
*
* - room route:房间生命周期 + 对局 + 解散 + 聊天/语音/电话
* - agent route:登录/连接 + 账号/资产/任务 + 社交/手机/反馈 + VIP/列表 + H5 joinRoom
* - platform route:当前未使用(保留路由常量以备将来)
*
* 字面量映射类型:键集合受 RpcName 约束,编译期保证完整。
* 未列出的 rpc 编译期即报错(漏报 = 编译失败)。
*/
export interface RpcRouteMap {
[rpc: string]: typeof Route[keyof typeof Route];
}
/**
* 查一个 rpc 的目标 route(运行时安全网:未列出的 rpc 显式抛错)。
*/
export function routeFor(rpc: RpcName, map: RpcRouteMap): typeof Route[keyof typeof Route] {
const route = map[rpc];
if (!route) {
throw new Error(
`routeFor: 未映射 rpc "${rpc}"(RPC_ROUTE 表里漏报;第二准则:不静默兜底)`,
);
}
return route;
}
/** Typed envelope: 与旧 Envelope<T> 对齐,业务侧可直接传给 NetClient.send */
export interface TypedEnvelope<T = unknown> {
route: typeof Route[keyof typeof Route];
rpc: RpcName;
data: T;
}
/**
* typedSend: 用映射表构造一个 typed envelope。
*
* 业务侧(NetClient 的 sendTyped 包装):
* netClient.sendRaw(typedSend(MAP, 'self_join_room', { seat: 1 }).rpc 与 route 与 data)
*
* 注:本函数只构造 envelope,不直接 send——send 由 NetClient 负责。
*/
export function typedSend<T = unknown>(
map: RpcRouteMap,
rpc: RpcName,
data: T,
): TypedEnvelope<T> {
return { route: routeFor(rpc, map), rpc, data };
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "5cf5a8d4-c476-4821-928b-d2802b05b650",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,95 @@
import type { EventBus } from '../core/events.ts';
import type { ReadonlyReactive } from '../core/reactive.ts';
import type { ReadonlyPlayerStore } from '../platform/readonly.ts';
import type { ReadonlyRoomStore } from '../platform/readonly.ts';
import type { ReadonlyAppStore } from '../platform/readonly.ts';
import type { PlayerState, RoomState, AppState } from '../platform/stores/types.ts';
/**
* 受限的发包通道(架构零耦合规则 §3)。
*
* 子游戏只能通过这个接口发包,**不能**直接拿到 transport/ws/start 等内部能力。
* 实现由 Router 在创建 GameContext 时注入(生产环境转发到 NetClient)。
*/
export interface GameNet {
send(route: string, rpc: string, data: unknown): void;
}
/**
* 座位↔视图工具(C §3 旧 ChangeToStatus 替代)。
*/
export interface GameSeat {
/** 绝对座位 → 视图位(自己=0,其余环形顺延)。 */
toView(mySeat: number, targetSeat: number, seatCount: number): number;
/** 视图位 → 绝对座位。 */
fromView(mySeat: number, viewSeat: number, seatCount: number): number;
}
/**
* GameContext:子游戏调用框架能力的唯一入口。
*
* 只暴露只读 Store + 受限 net.send + 座位工具 + 事件总线。
* 子游戏**不能**通过本接口反改 Store;Store 的修改由平台层负责(第二准则:单一来源)。
*/
export interface GameContext {
/** 受限发包。 */
readonly net: GameNet;
/** 只读 PlayerStore。 */
readonly player: ReadonlyPlayerStore & { readonly state: ReadonlyReactive<PlayerState> };
/** 只读 RoomStore(含 deskinfo 透传引用)。 */
readonly room: ReadonlyRoomStore & { readonly state: ReadonlyReactive<RoomState> };
/** 只读 AppStore(连接相位 + 身份)。 */
readonly app: ReadonlyAppStore & { readonly state: ReadonlyReactive<AppState> };
/** 座位↔视图工具。 */
readonly seat: GameSeat;
/** 事件总线(子游戏可发自定义事件供自己订阅,框架不消费)。 */
readonly events: EventBus<Record<string, unknown[]>>;
}
/**
* IGameModule:子游戏实现,被框架调用(框架 spec §4)。
*
* 子游戏实现这个接口并注册自己;框架 Router 据 route 分发对局包。
* 子游戏通过 GameContext 调用框架能力(不可 import platform/net 内部)。
*/
export interface IGameModule {
/** 本游戏的 game route(与 protocol/routes.ts 的 Route 同名)。 */
readonly route: string;
/** 进入牌桌场景,框架传入 GameContext。 */
onEnter(ctx: GameContext): void;
/** 离开牌桌场景。 */
onExit(): void;
/**
* 接收对局包(route = 本游戏 game route 的包)。
* 框架 router 已按 route 过滤;这里只按 rpc 分发。
*/
onReceive(rpc: string, data: unknown): void;
/**
* 登录回包带 deskinfo 时触发(C 规范 §5 边界判据 2:deskinfo 平台层不解析)。
* 子游戏负责反序列化对局态。
*/
onReconnect(deskinfo: unknown): void;
/**
* 可选:返回对局快照。框架在断线重连时收集 → 走 server player_login 响应 deskinfo。
* 若不实现则重连后从服务器拉取(默认行为)。
*/
serialize?(): unknown;
// ---- 平台事件钩子(全部可选,默认空实现)----
/** 玩家加入房间(seat = 对方座位)。 */
onPlayerJoin?(seat: number): void;
/** 玩家离开房间。 */
onPlayerLeave?(seat: number): void;
/** 玩家准备。 */
onReady?(seat: number): void;
/** 房间解散。 */
onDissolve?(): void;
/** 玩家离线/上线(在线由 onPlayerJoin 重复触发,离线由本钩子)。 */
onOffline?(seat: number): void;
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "1ca4b321-a8ed-4850-83a4-f18df811c7d3",
"files": [],
"subMetas": {},
"userData": {}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

@@ -0,0 +1,134 @@
{
"ver": "1.0.27",
"importer": "image",
"imported": true,
"uuid": "8eb7acc3-aba1-4fcf-b375-a3bfb5303472",
"files": [
".json",
".png"
],
"subMetas": {
"6c48a": {
"importer": "texture",
"uuid": "8eb7acc3-aba1-4fcf-b375-a3bfb5303472@6c48a",
"displayName": "00002",
"id": "6c48a",
"name": "texture",
"userData": {
"wrapModeS": "clamp-to-edge",
"wrapModeT": "clamp-to-edge",
"imageUuidOrDatabaseUri": "8eb7acc3-aba1-4fcf-b375-a3bfb5303472",
"isUuid": true,
"visible": false,
"minfilter": "linear",
"magfilter": "linear",
"mipfilter": "none",
"anisotropy": 0
},
"ver": "1.0.22",
"imported": true,
"files": [
".json"
],
"subMetas": {}
},
"f9941": {
"importer": "sprite-frame",
"uuid": "8eb7acc3-aba1-4fcf-b375-a3bfb5303472@f9941",
"displayName": "00002",
"id": "f9941",
"name": "spriteFrame",
"userData": {
"trimThreshold": 1,
"rotated": false,
"offsetX": 0.5,
"offsetY": 2.5,
"trimX": 7,
"trimY": 8,
"width": 584,
"height": 425,
"rawWidth": 597,
"rawHeight": 446,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
"pivotY": 0.5,
"meshType": 0,
"vertices": {
"rawPosition": [
-292,
-212.5,
0,
292,
-212.5,
0,
-292,
212.5,
0,
292,
212.5,
0
],
"indexes": [
0,
1,
2,
2,
1,
3
],
"uv": [
7,
438,
591,
438,
7,
13,
591,
13
],
"nuv": [
0.011725293132328308,
0.02914798206278027,
0.9899497487437185,
0.02914798206278027,
0.011725293132328308,
0.9820627802690582,
0.9899497487437185,
0.9820627802690582
],
"minPos": [
-292,
-212.5,
0
],
"maxPos": [
292,
212.5,
0
]
},
"isUuid": true,
"imageUuidOrDatabaseUri": "8eb7acc3-aba1-4fcf-b375-a3bfb5303472@6c48a",
"atlasUuid": "",
"trimType": "auto"
},
"ver": "1.0.12",
"imported": true,
"files": [
".json"
],
"subMetas": {}
}
},
"userData": {
"type": "sprite-frame",
"fixAlphaTransparencyArtifacts": false,
"hasAlpha": true,
"redirect": "8eb7acc3-aba1-4fcf-b375-a3bfb5303472@6c48a"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 809 B

@@ -0,0 +1,134 @@
{
"ver": "1.0.27",
"importer": "image",
"imported": true,
"uuid": "4b924bd2-6ccf-40d7-95b2-cfa58e897913",
"files": [
".json",
".png"
],
"subMetas": {
"6c48a": {
"importer": "texture",
"uuid": "4b924bd2-6ccf-40d7-95b2-cfa58e897913@6c48a",
"displayName": "00003",
"id": "6c48a",
"name": "texture",
"userData": {
"wrapModeS": "clamp-to-edge",
"wrapModeT": "clamp-to-edge",
"imageUuidOrDatabaseUri": "4b924bd2-6ccf-40d7-95b2-cfa58e897913",
"isUuid": true,
"visible": false,
"minfilter": "linear",
"magfilter": "linear",
"mipfilter": "none",
"anisotropy": 0
},
"ver": "1.0.22",
"imported": true,
"files": [
".json"
],
"subMetas": {}
},
"f9941": {
"importer": "sprite-frame",
"uuid": "4b924bd2-6ccf-40d7-95b2-cfa58e897913@f9941",
"displayName": "00003",
"id": "f9941",
"name": "spriteFrame",
"userData": {
"trimThreshold": 1,
"rotated": false,
"offsetX": 0,
"offsetY": 0,
"trimX": 7,
"trimY": 7,
"width": 36,
"height": 36,
"rawWidth": 50,
"rawHeight": 50,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
"pivotY": 0.5,
"meshType": 0,
"vertices": {
"rawPosition": [
-18,
-18,
0,
18,
-18,
0,
-18,
18,
0,
18,
18,
0
],
"indexes": [
0,
1,
2,
2,
1,
3
],
"uv": [
7,
43,
43,
43,
7,
7,
43,
7
],
"nuv": [
0.14,
0.14,
0.86,
0.14,
0.14,
0.86,
0.86,
0.86
],
"minPos": [
-18,
-18,
0
],
"maxPos": [
18,
18,
0
]
},
"isUuid": true,
"imageUuidOrDatabaseUri": "4b924bd2-6ccf-40d7-95b2-cfa58e897913@6c48a",
"atlasUuid": "",
"trimType": "auto"
},
"ver": "1.0.12",
"imported": true,
"files": [
".json"
],
"subMetas": {}
}
},
"userData": {
"type": "sprite-frame",
"fixAlphaTransparencyArtifacts": false,
"hasAlpha": true,
"redirect": "4b924bd2-6ccf-40d7-95b2-cfa58e897913@6c48a"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

@@ -0,0 +1,134 @@
{
"ver": "1.0.27",
"importer": "image",
"imported": true,
"uuid": "e72b79a6-2b3b-434e-b3af-9bed6261b043",
"files": [
".json",
".png"
],
"subMetas": {
"6c48a": {
"importer": "texture",
"uuid": "e72b79a6-2b3b-434e-b3af-9bed6261b043@6c48a",
"displayName": "00006_1",
"id": "6c48a",
"name": "texture",
"userData": {
"wrapModeS": "clamp-to-edge",
"wrapModeT": "clamp-to-edge",
"imageUuidOrDatabaseUri": "e72b79a6-2b3b-434e-b3af-9bed6261b043",
"isUuid": true,
"visible": false,
"minfilter": "linear",
"magfilter": "linear",
"mipfilter": "none",
"anisotropy": 0
},
"ver": "1.0.22",
"imported": true,
"files": [
".json"
],
"subMetas": {}
},
"f9941": {
"importer": "sprite-frame",
"uuid": "e72b79a6-2b3b-434e-b3af-9bed6261b043@f9941",
"displayName": "00006_1",
"id": "f9941",
"name": "spriteFrame",
"userData": {
"trimThreshold": 1,
"rotated": false,
"offsetX": 0,
"offsetY": 1.5,
"trimX": 0,
"trimY": 0,
"width": 403,
"height": 73,
"rawWidth": 403,
"rawHeight": 76,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
"pivotY": 0.5,
"meshType": 0,
"vertices": {
"rawPosition": [
-201.5,
-36.5,
0,
201.5,
-36.5,
0,
-201.5,
36.5,
0,
201.5,
36.5,
0
],
"indexes": [
0,
1,
2,
2,
1,
3
],
"uv": [
0,
76,
403,
76,
0,
3,
403,
3
],
"nuv": [
0,
0.039473684210526314,
1,
0.039473684210526314,
0,
1,
1,
1
],
"minPos": [
-201.5,
-36.5,
0
],
"maxPos": [
201.5,
36.5,
0
]
},
"isUuid": true,
"imageUuidOrDatabaseUri": "e72b79a6-2b3b-434e-b3af-9bed6261b043@6c48a",
"atlasUuid": "",
"trimType": "auto"
},
"ver": "1.0.12",
"imported": true,
"files": [
".json"
],
"subMetas": {}
}
},
"userData": {
"type": "sprite-frame",
"fixAlphaTransparencyArtifacts": false,
"hasAlpha": true,
"redirect": "e72b79a6-2b3b-434e-b3af-9bed6261b043@6c48a"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

@@ -0,0 +1,134 @@
{
"ver": "1.0.27",
"importer": "image",
"imported": true,
"uuid": "2125cf5c-a0a4-4f34-a78a-88dfb3c100e3",
"files": [
".json",
".png"
],
"subMetas": {
"6c48a": {
"importer": "texture",
"uuid": "2125cf5c-a0a4-4f34-a78a-88dfb3c100e3@6c48a",
"displayName": "00006_2",
"id": "6c48a",
"name": "texture",
"userData": {
"wrapModeS": "clamp-to-edge",
"wrapModeT": "clamp-to-edge",
"imageUuidOrDatabaseUri": "2125cf5c-a0a4-4f34-a78a-88dfb3c100e3",
"isUuid": true,
"visible": false,
"minfilter": "linear",
"magfilter": "linear",
"mipfilter": "none",
"anisotropy": 0
},
"ver": "1.0.22",
"imported": true,
"files": [
".json"
],
"subMetas": {}
},
"f9941": {
"importer": "sprite-frame",
"uuid": "2125cf5c-a0a4-4f34-a78a-88dfb3c100e3@f9941",
"displayName": "00006_2",
"id": "f9941",
"name": "spriteFrame",
"userData": {
"trimThreshold": 1,
"rotated": false,
"offsetX": 0,
"offsetY": 1.5,
"trimX": 0,
"trimY": 0,
"width": 403,
"height": 73,
"rawWidth": 403,
"rawHeight": 76,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
"pivotY": 0.5,
"meshType": 0,
"vertices": {
"rawPosition": [
-201.5,
-36.5,
0,
201.5,
-36.5,
0,
-201.5,
36.5,
0,
201.5,
36.5,
0
],
"indexes": [
0,
1,
2,
2,
1,
3
],
"uv": [
0,
76,
403,
76,
0,
3,
403,
3
],
"nuv": [
0,
0.039473684210526314,
1,
0.039473684210526314,
0,
1,
1,
1
],
"minPos": [
-201.5,
-36.5,
0
],
"maxPos": [
201.5,
36.5,
0
]
},
"isUuid": true,
"imageUuidOrDatabaseUri": "2125cf5c-a0a4-4f34-a78a-88dfb3c100e3@6c48a",
"atlasUuid": "",
"trimType": "auto"
},
"ver": "1.0.12",
"imported": true,
"files": [
".json"
],
"subMetas": {}
}
},
"userData": {
"type": "sprite-frame",
"fixAlphaTransparencyArtifacts": false,
"hasAlpha": true,
"redirect": "2125cf5c-a0a4-4f34-a78a-88dfb3c100e3@6c48a"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

@@ -0,0 +1,134 @@
{
"ver": "1.0.27",
"importer": "image",
"imported": true,
"uuid": "5febca8f-13b3-4ea3-997c-0f4534ba499d",
"files": [
".json",
".png"
],
"subMetas": {
"6c48a": {
"importer": "texture",
"uuid": "5febca8f-13b3-4ea3-997c-0f4534ba499d@6c48a",
"displayName": "00006_3",
"id": "6c48a",
"name": "texture",
"userData": {
"wrapModeS": "clamp-to-edge",
"wrapModeT": "clamp-to-edge",
"imageUuidOrDatabaseUri": "5febca8f-13b3-4ea3-997c-0f4534ba499d",
"isUuid": true,
"visible": false,
"minfilter": "linear",
"magfilter": "linear",
"mipfilter": "none",
"anisotropy": 0
},
"ver": "1.0.22",
"imported": true,
"files": [
".json"
],
"subMetas": {}
},
"f9941": {
"importer": "sprite-frame",
"uuid": "5febca8f-13b3-4ea3-997c-0f4534ba499d@f9941",
"displayName": "00006_3",
"id": "f9941",
"name": "spriteFrame",
"userData": {
"trimThreshold": 1,
"rotated": false,
"offsetX": 0,
"offsetY": 1.5,
"trimX": 0,
"trimY": 0,
"width": 403,
"height": 73,
"rawWidth": 403,
"rawHeight": 76,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
"pivotY": 0.5,
"meshType": 0,
"vertices": {
"rawPosition": [
-201.5,
-36.5,
0,
201.5,
-36.5,
0,
-201.5,
36.5,
0,
201.5,
36.5,
0
],
"indexes": [
0,
1,
2,
2,
1,
3
],
"uv": [
0,
76,
403,
76,
0,
3,
403,
3
],
"nuv": [
0,
0.039473684210526314,
1,
0.039473684210526314,
0,
1,
1,
1
],
"minPos": [
-201.5,
-36.5,
0
],
"maxPos": [
201.5,
36.5,
0
]
},
"isUuid": true,
"imageUuidOrDatabaseUri": "5febca8f-13b3-4ea3-997c-0f4534ba499d@6c48a",
"atlasUuid": "",
"trimType": "auto"
},
"ver": "1.0.12",
"imported": true,
"files": [
".json"
],
"subMetas": {}
}
},
"userData": {
"type": "sprite-frame",
"fixAlphaTransparencyArtifacts": false,
"hasAlpha": true,
"redirect": "5febca8f-13b3-4ea3-997c-0f4534ba499d@6c48a"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

@@ -0,0 +1,134 @@
{
"ver": "1.0.27",
"importer": "image",
"imported": true,
"uuid": "317bd27a-1b89-4653-9949-e9bfb980e998",
"files": [
".json",
".png"
],
"subMetas": {
"6c48a": {
"importer": "texture",
"uuid": "317bd27a-1b89-4653-9949-e9bfb980e998@6c48a",
"displayName": "00006_4",
"id": "6c48a",
"name": "texture",
"userData": {
"wrapModeS": "clamp-to-edge",
"wrapModeT": "clamp-to-edge",
"imageUuidOrDatabaseUri": "317bd27a-1b89-4653-9949-e9bfb980e998",
"isUuid": true,
"visible": false,
"minfilter": "linear",
"magfilter": "linear",
"mipfilter": "none",
"anisotropy": 0
},
"ver": "1.0.22",
"imported": true,
"files": [
".json"
],
"subMetas": {}
},
"f9941": {
"importer": "sprite-frame",
"uuid": "317bd27a-1b89-4653-9949-e9bfb980e998@f9941",
"displayName": "00006_4",
"id": "f9941",
"name": "spriteFrame",
"userData": {
"trimThreshold": 1,
"rotated": false,
"offsetX": 0,
"offsetY": 1.5,
"trimX": 0,
"trimY": 0,
"width": 403,
"height": 73,
"rawWidth": 403,
"rawHeight": 76,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
"pivotY": 0.5,
"meshType": 0,
"vertices": {
"rawPosition": [
-201.5,
-36.5,
0,
201.5,
-36.5,
0,
-201.5,
36.5,
0,
201.5,
36.5,
0
],
"indexes": [
0,
1,
2,
2,
1,
3
],
"uv": [
0,
76,
403,
76,
0,
3,
403,
3
],
"nuv": [
0,
0.039473684210526314,
1,
0.039473684210526314,
0,
1,
1,
1
],
"minPos": [
-201.5,
-36.5,
0
],
"maxPos": [
201.5,
36.5,
0
]
},
"isUuid": true,
"imageUuidOrDatabaseUri": "317bd27a-1b89-4653-9949-e9bfb980e998@6c48a",
"atlasUuid": "",
"trimType": "auto"
},
"ver": "1.0.12",
"imported": true,
"files": [
".json"
],
"subMetas": {}
}
},
"userData": {
"type": "sprite-frame",
"fixAlphaTransparencyArtifacts": false,
"hasAlpha": true,
"redirect": "317bd27a-1b89-4653-9949-e9bfb980e998@6c48a"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

@@ -0,0 +1,134 @@
{
"ver": "1.0.27",
"importer": "image",
"imported": true,
"uuid": "2d4c8b2c-7858-4720-9857-45979d7636de",
"files": [
".json",
".png"
],
"subMetas": {
"6c48a": {
"importer": "texture",
"uuid": "2d4c8b2c-7858-4720-9857-45979d7636de@6c48a",
"displayName": "00006_5",
"id": "6c48a",
"name": "texture",
"userData": {
"wrapModeS": "clamp-to-edge",
"wrapModeT": "clamp-to-edge",
"imageUuidOrDatabaseUri": "2d4c8b2c-7858-4720-9857-45979d7636de",
"isUuid": true,
"visible": false,
"minfilter": "linear",
"magfilter": "linear",
"mipfilter": "none",
"anisotropy": 0
},
"ver": "1.0.22",
"imported": true,
"files": [
".json"
],
"subMetas": {}
},
"f9941": {
"importer": "sprite-frame",
"uuid": "2d4c8b2c-7858-4720-9857-45979d7636de@f9941",
"displayName": "00006_5",
"id": "f9941",
"name": "spriteFrame",
"userData": {
"trimThreshold": 1,
"rotated": false,
"offsetX": 0,
"offsetY": 1.5,
"trimX": 0,
"trimY": 0,
"width": 403,
"height": 73,
"rawWidth": 403,
"rawHeight": 76,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
"pivotY": 0.5,
"meshType": 0,
"vertices": {
"rawPosition": [
-201.5,
-36.5,
0,
201.5,
-36.5,
0,
-201.5,
36.5,
0,
201.5,
36.5,
0
],
"indexes": [
0,
1,
2,
2,
1,
3
],
"uv": [
0,
76,
403,
76,
0,
3,
403,
3
],
"nuv": [
0,
0.039473684210526314,
1,
0.039473684210526314,
0,
1,
1,
1
],
"minPos": [
-201.5,
-36.5,
0
],
"maxPos": [
201.5,
36.5,
0
]
},
"isUuid": true,
"imageUuidOrDatabaseUri": "2d4c8b2c-7858-4720-9857-45979d7636de@6c48a",
"atlasUuid": "",
"trimType": "auto"
},
"ver": "1.0.12",
"imported": true,
"files": [
".json"
],
"subMetas": {}
}
},
"userData": {
"type": "sprite-frame",
"fixAlphaTransparencyArtifacts": false,
"hasAlpha": true,
"redirect": "2d4c8b2c-7858-4720-9857-45979d7636de@6c48a"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 104 B

@@ -0,0 +1,134 @@
{
"ver": "1.0.27",
"importer": "image",
"imported": true,
"uuid": "dcc4530b-344b-40b6-8fbb-9fa35db6a289",
"files": [
".json",
".png"
],
"subMetas": {
"6c48a": {
"importer": "texture",
"uuid": "dcc4530b-344b-40b6-8fbb-9fa35db6a289@6c48a",
"displayName": "00010",
"id": "6c48a",
"name": "texture",
"userData": {
"wrapModeS": "clamp-to-edge",
"wrapModeT": "clamp-to-edge",
"imageUuidOrDatabaseUri": "dcc4530b-344b-40b6-8fbb-9fa35db6a289",
"isUuid": true,
"visible": false,
"minfilter": "linear",
"magfilter": "linear",
"mipfilter": "none",
"anisotropy": 0
},
"ver": "1.0.22",
"imported": true,
"files": [
".json"
],
"subMetas": {}
},
"f9941": {
"importer": "sprite-frame",
"uuid": "dcc4530b-344b-40b6-8fbb-9fa35db6a289@f9941",
"displayName": "00010",
"id": "f9941",
"name": "spriteFrame",
"userData": {
"trimThreshold": 1,
"rotated": false,
"offsetX": 82.5,
"offsetY": -33.5,
"trimX": 165,
"trimY": 67,
"width": 1,
"height": 1,
"rawWidth": 166,
"rawHeight": 68,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
"pivotY": 0.5,
"meshType": 0,
"vertices": {
"rawPosition": [
-0.5,
-0.5,
0,
0.5,
-0.5,
0,
-0.5,
0.5,
0,
0.5,
0.5,
0
],
"indexes": [
0,
1,
2,
2,
1,
3
],
"uv": [
165,
1,
166,
1,
165,
0,
166,
0
],
"nuv": [
0.9939759036144579,
0,
1,
0,
0.9939759036144579,
0.014705882352941176,
1,
0.014705882352941176
],
"minPos": [
-0.5,
-0.5,
0
],
"maxPos": [
0.5,
0.5,
0
]
},
"isUuid": true,
"imageUuidOrDatabaseUri": "dcc4530b-344b-40b6-8fbb-9fa35db6a289@6c48a",
"atlasUuid": "",
"trimType": "auto"
},
"ver": "1.0.12",
"imported": true,
"files": [
".json"
],
"subMetas": {}
}
},
"userData": {
"type": "sprite-frame",
"fixAlphaTransparencyArtifacts": false,
"hasAlpha": true,
"redirect": "dcc4530b-344b-40b6-8fbb-9fa35db6a289@6c48a"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1,134 @@
{
"ver": "1.0.27",
"importer": "image",
"imported": true,
"uuid": "eabf148a-fa6a-460a-be4f-8e586eb80e07",
"files": [
".json",
".png"
],
"subMetas": {
"6c48a": {
"importer": "texture",
"uuid": "eabf148a-fa6a-460a-be4f-8e586eb80e07@6c48a",
"displayName": "00011",
"id": "6c48a",
"name": "texture",
"userData": {
"wrapModeS": "clamp-to-edge",
"wrapModeT": "clamp-to-edge",
"imageUuidOrDatabaseUri": "eabf148a-fa6a-460a-be4f-8e586eb80e07",
"isUuid": true,
"visible": false,
"minfilter": "linear",
"magfilter": "linear",
"mipfilter": "none",
"anisotropy": 0
},
"ver": "1.0.22",
"imported": true,
"files": [
".json"
],
"subMetas": {}
},
"f9941": {
"importer": "sprite-frame",
"uuid": "eabf148a-fa6a-460a-be4f-8e586eb80e07@f9941",
"displayName": "00011",
"id": "f9941",
"name": "spriteFrame",
"userData": {
"trimThreshold": 1,
"rotated": false,
"offsetX": 0,
"offsetY": 0,
"trimX": 0,
"trimY": 0,
"width": 4,
"height": 4,
"rawWidth": 4,
"rawHeight": 4,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
"pivotY": 0.5,
"meshType": 0,
"vertices": {
"rawPosition": [
-2,
-2,
0,
2,
-2,
0,
-2,
2,
0,
2,
2,
0
],
"indexes": [
0,
1,
2,
2,
1,
3
],
"uv": [
0,
4,
4,
4,
0,
0,
4,
0
],
"nuv": [
0,
0,
1,
0,
0,
1,
1,
1
],
"minPos": [
-2,
-2,
0
],
"maxPos": [
2,
2,
0
]
},
"isUuid": true,
"imageUuidOrDatabaseUri": "eabf148a-fa6a-460a-be4f-8e586eb80e07@6c48a",
"atlasUuid": "",
"trimType": "auto"
},
"ver": "1.0.12",
"imported": true,
"files": [
".json"
],
"subMetas": {}
}
},
"userData": {
"type": "sprite-frame",
"fixAlphaTransparencyArtifacts": false,
"hasAlpha": true,
"redirect": "eabf148a-fa6a-460a-be4f-8e586eb80e07@6c48a"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

@@ -0,0 +1,134 @@
{
"ver": "1.0.27",
"importer": "image",
"imported": true,
"uuid": "60da43f5-f3c2-4b9f-9123-fad81f8f8af5",
"files": [
".json",
".png"
],
"subMetas": {
"6c48a": {
"importer": "texture",
"uuid": "60da43f5-f3c2-4b9f-9123-fad81f8f8af5@6c48a",
"displayName": "00025",
"id": "6c48a",
"name": "texture",
"userData": {
"wrapModeS": "clamp-to-edge",
"wrapModeT": "clamp-to-edge",
"imageUuidOrDatabaseUri": "60da43f5-f3c2-4b9f-9123-fad81f8f8af5",
"isUuid": true,
"visible": false,
"minfilter": "linear",
"magfilter": "linear",
"mipfilter": "none",
"anisotropy": 0
},
"ver": "1.0.22",
"imported": true,
"files": [
".json"
],
"subMetas": {}
},
"f9941": {
"importer": "sprite-frame",
"uuid": "60da43f5-f3c2-4b9f-9123-fad81f8f8af5@f9941",
"displayName": "00025",
"id": "f9941",
"name": "spriteFrame",
"userData": {
"trimThreshold": 1,
"rotated": false,
"offsetX": 0,
"offsetY": 0,
"trimX": 0,
"trimY": 0,
"width": 93,
"height": 93,
"rawWidth": 93,
"rawHeight": 93,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
"pivotY": 0.5,
"meshType": 0,
"vertices": {
"rawPosition": [
-46.5,
-46.5,
0,
46.5,
-46.5,
0,
-46.5,
46.5,
0,
46.5,
46.5,
0
],
"indexes": [
0,
1,
2,
2,
1,
3
],
"uv": [
0,
93,
93,
93,
0,
0,
93,
0
],
"nuv": [
0,
0,
1,
0,
0,
1,
1,
1
],
"minPos": [
-46.5,
-46.5,
0
],
"maxPos": [
46.5,
46.5,
0
]
},
"isUuid": true,
"imageUuidOrDatabaseUri": "60da43f5-f3c2-4b9f-9123-fad81f8f8af5@6c48a",
"atlasUuid": "",
"trimType": "auto"
},
"ver": "1.0.12",
"imported": true,
"files": [
".json"
],
"subMetas": {}
}
},
"userData": {
"type": "sprite-frame",
"fixAlphaTransparencyArtifacts": false,
"hasAlpha": false,
"redirect": "60da43f5-f3c2-4b9f-9123-fad81f8f8af5@6c48a"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -0,0 +1,134 @@
{
"ver": "1.0.27",
"importer": "image",
"imported": true,
"uuid": "9239dc9e-f89e-40c4-ba76-b004e1fd5ec4",
"files": [
".json",
".png"
],
"subMetas": {
"6c48a": {
"importer": "texture",
"uuid": "9239dc9e-f89e-40c4-ba76-b004e1fd5ec4@6c48a",
"displayName": "00029_1",
"id": "6c48a",
"name": "texture",
"userData": {
"wrapModeS": "clamp-to-edge",
"wrapModeT": "clamp-to-edge",
"imageUuidOrDatabaseUri": "9239dc9e-f89e-40c4-ba76-b004e1fd5ec4",
"isUuid": true,
"visible": false,
"minfilter": "linear",
"magfilter": "linear",
"mipfilter": "none",
"anisotropy": 0
},
"ver": "1.0.22",
"imported": true,
"files": [
".json"
],
"subMetas": {}
},
"f9941": {
"importer": "sprite-frame",
"uuid": "9239dc9e-f89e-40c4-ba76-b004e1fd5ec4@f9941",
"displayName": "00029_1",
"id": "f9941",
"name": "spriteFrame",
"userData": {
"trimThreshold": 1,
"rotated": false,
"offsetX": 2,
"offsetY": -2,
"trimX": 5,
"trimY": 19,
"width": 200,
"height": 41,
"rawWidth": 206,
"rawHeight": 75,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
"pivotY": 0.5,
"meshType": 0,
"vertices": {
"rawPosition": [
-100,
-20.5,
0,
100,
-20.5,
0,
-100,
20.5,
0,
100,
20.5,
0
],
"indexes": [
0,
1,
2,
2,
1,
3
],
"uv": [
5,
56,
205,
56,
5,
15,
205,
15
],
"nuv": [
0.024271844660194174,
0.2,
0.9951456310679612,
0.2,
0.024271844660194174,
0.7466666666666667,
0.9951456310679612,
0.7466666666666667
],
"minPos": [
-100,
-20.5,
0
],
"maxPos": [
100,
20.5,
0
]
},
"isUuid": true,
"imageUuidOrDatabaseUri": "9239dc9e-f89e-40c4-ba76-b004e1fd5ec4@6c48a",
"atlasUuid": "",
"trimType": "auto"
},
"ver": "1.0.12",
"imported": true,
"files": [
".json"
],
"subMetas": {}
}
},
"userData": {
"type": "sprite-frame",
"fixAlphaTransparencyArtifacts": false,
"hasAlpha": true,
"redirect": "9239dc9e-f89e-40c4-ba76-b004e1fd5ec4@6c48a"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

@@ -0,0 +1,134 @@
{
"ver": "1.0.27",
"importer": "image",
"imported": true,
"uuid": "9ee042b7-fb9f-481e-b97f-5ce7da732655",
"files": [
".json",
".png"
],
"subMetas": {
"6c48a": {
"importer": "texture",
"uuid": "9ee042b7-fb9f-481e-b97f-5ce7da732655@6c48a",
"displayName": "00029_2",
"id": "6c48a",
"name": "texture",
"userData": {
"wrapModeS": "clamp-to-edge",
"wrapModeT": "clamp-to-edge",
"imageUuidOrDatabaseUri": "9ee042b7-fb9f-481e-b97f-5ce7da732655",
"isUuid": true,
"visible": false,
"minfilter": "linear",
"magfilter": "linear",
"mipfilter": "none",
"anisotropy": 0
},
"ver": "1.0.22",
"imported": true,
"files": [
".json"
],
"subMetas": {}
},
"f9941": {
"importer": "sprite-frame",
"uuid": "9ee042b7-fb9f-481e-b97f-5ce7da732655@f9941",
"displayName": "00029_2",
"id": "f9941",
"name": "spriteFrame",
"userData": {
"trimThreshold": 1,
"rotated": false,
"offsetX": 0,
"offsetY": -1.5,
"trimX": 2,
"trimY": 18,
"width": 202,
"height": 42,
"rawWidth": 206,
"rawHeight": 75,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
"pivotY": 0.5,
"meshType": 0,
"vertices": {
"rawPosition": [
-101,
-21,
0,
101,
-21,
0,
-101,
21,
0,
101,
21,
0
],
"indexes": [
0,
1,
2,
2,
1,
3
],
"uv": [
2,
57,
204,
57,
2,
15,
204,
15
],
"nuv": [
0.009708737864077669,
0.2,
0.9902912621359223,
0.2,
0.009708737864077669,
0.76,
0.9902912621359223,
0.76
],
"minPos": [
-101,
-21,
0
],
"maxPos": [
101,
21,
0
]
},
"isUuid": true,
"imageUuidOrDatabaseUri": "9ee042b7-fb9f-481e-b97f-5ce7da732655@6c48a",
"atlasUuid": "",
"trimType": "auto"
},
"ver": "1.0.12",
"imported": true,
"files": [
".json"
],
"subMetas": {}
}
},
"userData": {
"type": "sprite-frame",
"fixAlphaTransparencyArtifacts": false,
"hasAlpha": true,
"redirect": "9ee042b7-fb9f-481e-b97f-5ce7da732655@6c48a"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

@@ -0,0 +1,134 @@
{
"ver": "1.0.27",
"importer": "image",
"imported": true,
"uuid": "f40aef7e-d2b4-440c-b3bc-b5c194a7b037",
"files": [
".json",
".png"
],
"subMetas": {
"6c48a": {
"importer": "texture",
"uuid": "f40aef7e-d2b4-440c-b3bc-b5c194a7b037@6c48a",
"displayName": "00029_3",
"id": "6c48a",
"name": "texture",
"userData": {
"wrapModeS": "clamp-to-edge",
"wrapModeT": "clamp-to-edge",
"imageUuidOrDatabaseUri": "f40aef7e-d2b4-440c-b3bc-b5c194a7b037",
"isUuid": true,
"visible": false,
"minfilter": "linear",
"magfilter": "linear",
"mipfilter": "none",
"anisotropy": 0
},
"ver": "1.0.22",
"imported": true,
"files": [
".json"
],
"subMetas": {}
},
"f9941": {
"importer": "sprite-frame",
"uuid": "f40aef7e-d2b4-440c-b3bc-b5c194a7b037@f9941",
"displayName": "00029_3",
"id": "f9941",
"name": "spriteFrame",
"userData": {
"trimThreshold": 1,
"rotated": false,
"offsetX": -0.5,
"offsetY": -1.5,
"trimX": 1,
"trimY": 16,
"width": 203,
"height": 46,
"rawWidth": 206,
"rawHeight": 75,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
"pivotY": 0.5,
"meshType": 0,
"vertices": {
"rawPosition": [
-101.5,
-23,
0,
101.5,
-23,
0,
-101.5,
23,
0,
101.5,
23,
0
],
"indexes": [
0,
1,
2,
2,
1,
3
],
"uv": [
1,
59,
204,
59,
1,
13,
204,
13
],
"nuv": [
0.0048543689320388345,
0.17333333333333334,
0.9902912621359223,
0.17333333333333334,
0.0048543689320388345,
0.7866666666666666,
0.9902912621359223,
0.7866666666666666
],
"minPos": [
-101.5,
-23,
0
],
"maxPos": [
101.5,
23,
0
]
},
"isUuid": true,
"imageUuidOrDatabaseUri": "f40aef7e-d2b4-440c-b3bc-b5c194a7b037@6c48a",
"atlasUuid": "",
"trimType": "auto"
},
"ver": "1.0.12",
"imported": true,
"files": [
".json"
],
"subMetas": {}
}
},
"userData": {
"type": "sprite-frame",
"fixAlphaTransparencyArtifacts": false,
"hasAlpha": true,
"redirect": "f40aef7e-d2b4-440c-b3bc-b5c194a7b037@6c48a"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

@@ -0,0 +1,134 @@
{
"ver": "1.0.27",
"importer": "image",
"imported": true,
"uuid": "6be9d68d-5d12-43ae-84f5-112d0cf265a8",
"files": [
".json",
".png"
],
"subMetas": {
"6c48a": {
"importer": "texture",
"uuid": "6be9d68d-5d12-43ae-84f5-112d0cf265a8@6c48a",
"displayName": "00029_4",
"id": "6c48a",
"name": "texture",
"userData": {
"wrapModeS": "clamp-to-edge",
"wrapModeT": "clamp-to-edge",
"imageUuidOrDatabaseUri": "6be9d68d-5d12-43ae-84f5-112d0cf265a8",
"isUuid": true,
"visible": false,
"minfilter": "linear",
"magfilter": "linear",
"mipfilter": "none",
"anisotropy": 0
},
"ver": "1.0.22",
"imported": true,
"files": [
".json"
],
"subMetas": {}
},
"f9941": {
"importer": "sprite-frame",
"uuid": "6be9d68d-5d12-43ae-84f5-112d0cf265a8@f9941",
"displayName": "00029_4",
"id": "f9941",
"name": "spriteFrame",
"userData": {
"trimThreshold": 1,
"rotated": false,
"offsetX": 2.5,
"offsetY": -1.5,
"trimX": 6,
"trimY": 15,
"width": 199,
"height": 48,
"rawWidth": 206,
"rawHeight": 75,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
"pivotY": 0.5,
"meshType": 0,
"vertices": {
"rawPosition": [
-99.5,
-24,
0,
99.5,
-24,
0,
-99.5,
24,
0,
99.5,
24,
0
],
"indexes": [
0,
1,
2,
2,
1,
3
],
"uv": [
6,
60,
205,
60,
6,
12,
205,
12
],
"nuv": [
0.02912621359223301,
0.16,
0.9951456310679612,
0.16,
0.02912621359223301,
0.8,
0.9951456310679612,
0.8
],
"minPos": [
-99.5,
-24,
0
],
"maxPos": [
99.5,
24,
0
]
},
"isUuid": true,
"imageUuidOrDatabaseUri": "6be9d68d-5d12-43ae-84f5-112d0cf265a8@6c48a",
"atlasUuid": "",
"trimType": "auto"
},
"ver": "1.0.12",
"imported": true,
"files": [
".json"
],
"subMetas": {}
}
},
"userData": {
"type": "sprite-frame",
"fixAlphaTransparencyArtifacts": false,
"hasAlpha": true,
"redirect": "6be9d68d-5d12-43ae-84f5-112d0cf265a8@6c48a"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

@@ -0,0 +1,134 @@
{
"ver": "1.0.27",
"importer": "image",
"imported": true,
"uuid": "0660c388-fd83-4994-a5a7-e97a7580491a",
"files": [
".json",
".png"
],
"subMetas": {
"6c48a": {
"importer": "texture",
"uuid": "0660c388-fd83-4994-a5a7-e97a7580491a@6c48a",
"displayName": "00030_1",
"id": "6c48a",
"name": "texture",
"userData": {
"wrapModeS": "clamp-to-edge",
"wrapModeT": "clamp-to-edge",
"imageUuidOrDatabaseUri": "0660c388-fd83-4994-a5a7-e97a7580491a",
"isUuid": true,
"visible": false,
"minfilter": "linear",
"magfilter": "linear",
"mipfilter": "none",
"anisotropy": 0
},
"ver": "1.0.22",
"imported": true,
"files": [
".json"
],
"subMetas": {}
},
"f9941": {
"importer": "sprite-frame",
"uuid": "0660c388-fd83-4994-a5a7-e97a7580491a@f9941",
"displayName": "00030_1",
"id": "f9941",
"name": "spriteFrame",
"userData": {
"trimThreshold": 1,
"rotated": false,
"offsetX": 1,
"offsetY": -2,
"trimX": 6,
"trimY": 4,
"width": 160,
"height": 56,
"rawWidth": 170,
"rawHeight": 60,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
"pivotY": 0.5,
"meshType": 0,
"vertices": {
"rawPosition": [
-80,
-28,
0,
80,
-28,
0,
-80,
28,
0,
80,
28,
0
],
"indexes": [
0,
1,
2,
2,
1,
3
],
"uv": [
6,
56,
166,
56,
6,
0,
166,
0
],
"nuv": [
0.03529411764705882,
0,
0.9764705882352941,
0,
0.03529411764705882,
0.9333333333333333,
0.9764705882352941,
0.9333333333333333
],
"minPos": [
-80,
-28,
0
],
"maxPos": [
80,
28,
0
]
},
"isUuid": true,
"imageUuidOrDatabaseUri": "0660c388-fd83-4994-a5a7-e97a7580491a@6c48a",
"atlasUuid": "",
"trimType": "auto"
},
"ver": "1.0.12",
"imported": true,
"files": [
".json"
],
"subMetas": {}
}
},
"userData": {
"type": "sprite-frame",
"fixAlphaTransparencyArtifacts": false,
"hasAlpha": true,
"redirect": "0660c388-fd83-4994-a5a7-e97a7580491a@6c48a"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

@@ -0,0 +1,134 @@
{
"ver": "1.0.27",
"importer": "image",
"imported": true,
"uuid": "6849776f-5e58-4074-9911-92007d51823f",
"files": [
".json",
".png"
],
"subMetas": {
"6c48a": {
"importer": "texture",
"uuid": "6849776f-5e58-4074-9911-92007d51823f@6c48a",
"displayName": "00030_2",
"id": "6c48a",
"name": "texture",
"userData": {
"wrapModeS": "clamp-to-edge",
"wrapModeT": "clamp-to-edge",
"imageUuidOrDatabaseUri": "6849776f-5e58-4074-9911-92007d51823f",
"isUuid": true,
"visible": false,
"minfilter": "linear",
"magfilter": "linear",
"mipfilter": "none",
"anisotropy": 0
},
"ver": "1.0.22",
"imported": true,
"files": [
".json"
],
"subMetas": {}
},
"f9941": {
"importer": "sprite-frame",
"uuid": "6849776f-5e58-4074-9911-92007d51823f@f9941",
"displayName": "00030_2",
"id": "f9941",
"name": "spriteFrame",
"userData": {
"trimThreshold": 1,
"rotated": false,
"offsetX": 0,
"offsetY": -2,
"trimX": 4,
"trimY": 4,
"width": 162,
"height": 56,
"rawWidth": 170,
"rawHeight": 60,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
"pivotY": 0.5,
"meshType": 0,
"vertices": {
"rawPosition": [
-81,
-28,
0,
81,
-28,
0,
-81,
28,
0,
81,
28,
0
],
"indexes": [
0,
1,
2,
2,
1,
3
],
"uv": [
4,
56,
166,
56,
4,
0,
166,
0
],
"nuv": [
0.023529411764705882,
0,
0.9764705882352941,
0,
0.023529411764705882,
0.9333333333333333,
0.9764705882352941,
0.9333333333333333
],
"minPos": [
-81,
-28,
0
],
"maxPos": [
81,
28,
0
]
},
"isUuid": true,
"imageUuidOrDatabaseUri": "6849776f-5e58-4074-9911-92007d51823f@6c48a",
"atlasUuid": "",
"trimType": "auto"
},
"ver": "1.0.12",
"imported": true,
"files": [
".json"
],
"subMetas": {}
}
},
"userData": {
"type": "sprite-frame",
"fixAlphaTransparencyArtifacts": false,
"hasAlpha": true,
"redirect": "6849776f-5e58-4074-9911-92007d51823f@6c48a"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,134 @@
{
"ver": "1.0.27",
"importer": "image",
"imported": true,
"uuid": "8e3905be-b629-4c4d-858b-b8a76e9f7752",
"files": [
".json",
".png"
],
"subMetas": {
"6c48a": {
"importer": "texture",
"uuid": "8e3905be-b629-4c4d-858b-b8a76e9f7752@6c48a",
"displayName": "00031",
"id": "6c48a",
"name": "texture",
"userData": {
"wrapModeS": "clamp-to-edge",
"wrapModeT": "clamp-to-edge",
"imageUuidOrDatabaseUri": "8e3905be-b629-4c4d-858b-b8a76e9f7752",
"isUuid": true,
"visible": false,
"minfilter": "linear",
"magfilter": "linear",
"mipfilter": "none",
"anisotropy": 0
},
"ver": "1.0.22",
"imported": true,
"files": [
".json"
],
"subMetas": {}
},
"f9941": {
"importer": "sprite-frame",
"uuid": "8e3905be-b629-4c4d-858b-b8a76e9f7752@f9941",
"displayName": "00031",
"id": "f9941",
"name": "spriteFrame",
"userData": {
"trimThreshold": 1,
"rotated": false,
"offsetX": 0,
"offsetY": 0,
"trimX": 0,
"trimY": 0,
"width": 91,
"height": 91,
"rawWidth": 91,
"rawHeight": 91,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
"pivotY": 0.5,
"meshType": 0,
"vertices": {
"rawPosition": [
-45.5,
-45.5,
0,
45.5,
-45.5,
0,
-45.5,
45.5,
0,
45.5,
45.5,
0
],
"indexes": [
0,
1,
2,
2,
1,
3
],
"uv": [
0,
91,
91,
91,
0,
0,
91,
0
],
"nuv": [
0,
0,
1,
0,
0,
1,
1,
1
],
"minPos": [
-45.5,
-45.5,
0
],
"maxPos": [
45.5,
45.5,
0
]
},
"isUuid": true,
"imageUuidOrDatabaseUri": "8e3905be-b629-4c4d-858b-b8a76e9f7752@6c48a",
"atlasUuid": "",
"trimType": "auto"
},
"ver": "1.0.12",
"imported": true,
"files": [
".json"
],
"subMetas": {}
}
},
"userData": {
"type": "sprite-frame",
"fixAlphaTransparencyArtifacts": false,
"hasAlpha": true,
"redirect": "8e3905be-b629-4c4d-858b-b8a76e9f7752@6c48a"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

@@ -0,0 +1,134 @@
{
"ver": "1.0.27",
"importer": "image",
"imported": true,
"uuid": "eedce6d7-3ea6-434c-83e1-87d9a04b2bad",
"files": [
".json",
".png"
],
"subMetas": {
"6c48a": {
"importer": "texture",
"uuid": "eedce6d7-3ea6-434c-83e1-87d9a04b2bad@6c48a",
"displayName": "00033",
"id": "6c48a",
"name": "texture",
"userData": {
"wrapModeS": "clamp-to-edge",
"wrapModeT": "clamp-to-edge",
"imageUuidOrDatabaseUri": "eedce6d7-3ea6-434c-83e1-87d9a04b2bad",
"isUuid": true,
"visible": false,
"minfilter": "linear",
"magfilter": "linear",
"mipfilter": "none",
"anisotropy": 0
},
"ver": "1.0.22",
"imported": true,
"files": [
".json"
],
"subMetas": {}
},
"f9941": {
"importer": "sprite-frame",
"uuid": "eedce6d7-3ea6-434c-83e1-87d9a04b2bad@f9941",
"displayName": "00033",
"id": "f9941",
"name": "spriteFrame",
"userData": {
"trimThreshold": 1,
"rotated": false,
"offsetX": 0,
"offsetY": 1.5,
"trimX": 1,
"trimY": 0,
"width": 81,
"height": 81,
"rawWidth": 83,
"rawHeight": 84,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
"pivotY": 0.5,
"meshType": 0,
"vertices": {
"rawPosition": [
-40.5,
-40.5,
0,
40.5,
-40.5,
0,
-40.5,
40.5,
0,
40.5,
40.5,
0
],
"indexes": [
0,
1,
2,
2,
1,
3
],
"uv": [
1,
84,
82,
84,
1,
3,
82,
3
],
"nuv": [
0.012048192771084338,
0.03571428571428571,
0.9879518072289156,
0.03571428571428571,
0.012048192771084338,
1,
0.9879518072289156,
1
],
"minPos": [
-40.5,
-40.5,
0
],
"maxPos": [
40.5,
40.5,
0
]
},
"isUuid": true,
"imageUuidOrDatabaseUri": "eedce6d7-3ea6-434c-83e1-87d9a04b2bad@6c48a",
"atlasUuid": "",
"trimType": "auto"
},
"ver": "1.0.12",
"imported": true,
"files": [
".json"
],
"subMetas": {}
}
},
"userData": {
"type": "sprite-frame",
"fixAlphaTransparencyArtifacts": false,
"hasAlpha": true,
"redirect": "eedce6d7-3ea6-434c-83e1-87d9a04b2bad@6c48a"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

@@ -0,0 +1,134 @@
{
"ver": "1.0.27",
"importer": "image",
"imported": true,
"uuid": "c8cc3782-42f9-4e2d-9898-da1460faff26",
"files": [
".json",
".png"
],
"subMetas": {
"6c48a": {
"importer": "texture",
"uuid": "c8cc3782-42f9-4e2d-9898-da1460faff26@6c48a",
"displayName": "00034_1",
"id": "6c48a",
"name": "texture",
"userData": {
"wrapModeS": "clamp-to-edge",
"wrapModeT": "clamp-to-edge",
"imageUuidOrDatabaseUri": "c8cc3782-42f9-4e2d-9898-da1460faff26",
"isUuid": true,
"visible": false,
"minfilter": "linear",
"magfilter": "linear",
"mipfilter": "none",
"anisotropy": 0
},
"ver": "1.0.22",
"imported": true,
"files": [
".json"
],
"subMetas": {}
},
"f9941": {
"importer": "sprite-frame",
"uuid": "c8cc3782-42f9-4e2d-9898-da1460faff26@f9941",
"displayName": "00034_1",
"id": "f9941",
"name": "spriteFrame",
"userData": {
"trimThreshold": 1,
"rotated": false,
"offsetX": 0,
"offsetY": -2.5,
"trimX": 44,
"trimY": 14,
"width": 204,
"height": 79,
"rawWidth": 292,
"rawHeight": 102,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
"pivotY": 0.5,
"meshType": 0,
"vertices": {
"rawPosition": [
-102,
-39.5,
0,
102,
-39.5,
0,
-102,
39.5,
0,
102,
39.5,
0
],
"indexes": [
0,
1,
2,
2,
1,
3
],
"uv": [
44,
88,
248,
88,
44,
9,
248,
9
],
"nuv": [
0.1506849315068493,
0.08823529411764706,
0.8493150684931506,
0.08823529411764706,
0.1506849315068493,
0.8627450980392157,
0.8493150684931506,
0.8627450980392157
],
"minPos": [
-102,
-39.5,
0
],
"maxPos": [
102,
39.5,
0
]
},
"isUuid": true,
"imageUuidOrDatabaseUri": "c8cc3782-42f9-4e2d-9898-da1460faff26@6c48a",
"atlasUuid": "",
"trimType": "auto"
},
"ver": "1.0.12",
"imported": true,
"files": [
".json"
],
"subMetas": {}
}
},
"userData": {
"type": "sprite-frame",
"fixAlphaTransparencyArtifacts": false,
"hasAlpha": true,
"redirect": "c8cc3782-42f9-4e2d-9898-da1460faff26@6c48a"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

@@ -0,0 +1,134 @@
{
"ver": "1.0.27",
"importer": "image",
"imported": true,
"uuid": "65ec6a90-8980-4ba2-9e86-e36d4901b4bf",
"files": [
".json",
".png"
],
"subMetas": {
"6c48a": {
"importer": "texture",
"uuid": "65ec6a90-8980-4ba2-9e86-e36d4901b4bf@6c48a",
"displayName": "00034_2",
"id": "6c48a",
"name": "texture",
"userData": {
"wrapModeS": "clamp-to-edge",
"wrapModeT": "clamp-to-edge",
"imageUuidOrDatabaseUri": "65ec6a90-8980-4ba2-9e86-e36d4901b4bf",
"isUuid": true,
"visible": false,
"minfilter": "linear",
"magfilter": "linear",
"mipfilter": "none",
"anisotropy": 0
},
"ver": "1.0.22",
"imported": true,
"files": [
".json"
],
"subMetas": {}
},
"f9941": {
"importer": "sprite-frame",
"uuid": "65ec6a90-8980-4ba2-9e86-e36d4901b4bf@f9941",
"displayName": "00034_2",
"id": "f9941",
"name": "spriteFrame",
"userData": {
"trimThreshold": 1,
"rotated": false,
"offsetX": 0,
"offsetY": -2.5,
"trimX": 44,
"trimY": 14,
"width": 204,
"height": 79,
"rawWidth": 292,
"rawHeight": 102,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
"pivotY": 0.5,
"meshType": 0,
"vertices": {
"rawPosition": [
-102,
-39.5,
0,
102,
-39.5,
0,
-102,
39.5,
0,
102,
39.5,
0
],
"indexes": [
0,
1,
2,
2,
1,
3
],
"uv": [
44,
88,
248,
88,
44,
9,
248,
9
],
"nuv": [
0.1506849315068493,
0.08823529411764706,
0.8493150684931506,
0.08823529411764706,
0.1506849315068493,
0.8627450980392157,
0.8493150684931506,
0.8627450980392157
],
"minPos": [
-102,
-39.5,
0
],
"maxPos": [
102,
39.5,
0
]
},
"isUuid": true,
"imageUuidOrDatabaseUri": "65ec6a90-8980-4ba2-9e86-e36d4901b4bf@6c48a",
"atlasUuid": "",
"trimType": "auto"
},
"ver": "1.0.12",
"imported": true,
"files": [
".json"
],
"subMetas": {}
}
},
"userData": {
"type": "sprite-frame",
"fixAlphaTransparencyArtifacts": false,
"hasAlpha": true,
"redirect": "65ec6a90-8980-4ba2-9e86-e36d4901b4bf@6c48a"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 116 B

@@ -0,0 +1,134 @@
{
"ver": "1.0.27",
"importer": "image",
"imported": true,
"uuid": "05398908-866f-4605-b419-4a06ef63248a",
"files": [
".json",
".png"
],
"subMetas": {
"6c48a": {
"importer": "texture",
"uuid": "05398908-866f-4605-b419-4a06ef63248a@6c48a",
"displayName": "00040",
"id": "6c48a",
"name": "texture",
"userData": {
"wrapModeS": "clamp-to-edge",
"wrapModeT": "clamp-to-edge",
"imageUuidOrDatabaseUri": "05398908-866f-4605-b419-4a06ef63248a",
"isUuid": true,
"visible": false,
"minfilter": "linear",
"magfilter": "linear",
"mipfilter": "none",
"anisotropy": 0
},
"ver": "1.0.22",
"imported": true,
"files": [
".json"
],
"subMetas": {}
},
"f9941": {
"importer": "sprite-frame",
"uuid": "05398908-866f-4605-b419-4a06ef63248a@f9941",
"displayName": "00040",
"id": "f9941",
"name": "spriteFrame",
"userData": {
"trimThreshold": 1,
"rotated": false,
"offsetX": 254.5,
"offsetY": -68.5,
"trimX": 509,
"trimY": 137,
"width": 1,
"height": 1,
"rawWidth": 510,
"rawHeight": 138,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
"pivotY": 0.5,
"meshType": 0,
"vertices": {
"rawPosition": [
-0.5,
-0.5,
0,
0.5,
-0.5,
0,
-0.5,
0.5,
0,
0.5,
0.5,
0
],
"indexes": [
0,
1,
2,
2,
1,
3
],
"uv": [
509,
1,
510,
1,
509,
0,
510,
0
],
"nuv": [
0.9980392156862745,
0,
1,
0,
0.9980392156862745,
0.007246376811594203,
1,
0.007246376811594203
],
"minPos": [
-0.5,
-0.5,
0
],
"maxPos": [
0.5,
0.5,
0
]
},
"isUuid": true,
"imageUuidOrDatabaseUri": "05398908-866f-4605-b419-4a06ef63248a@6c48a",
"atlasUuid": "",
"trimType": "auto"
},
"ver": "1.0.12",
"imported": true,
"files": [
".json"
],
"subMetas": {}
}
},
"userData": {
"type": "sprite-frame",
"fixAlphaTransparencyArtifacts": false,
"hasAlpha": true,
"redirect": "05398908-866f-4605-b419-4a06ef63248a@6c48a"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

@@ -0,0 +1,134 @@
{
"ver": "1.0.27",
"importer": "image",
"imported": true,
"uuid": "3e7492cb-7ae1-4f56-806d-07096662d8fb",
"files": [
".json",
".png"
],
"subMetas": {
"6c48a": {
"importer": "texture",
"uuid": "3e7492cb-7ae1-4f56-806d-07096662d8fb@6c48a",
"displayName": "00041",
"id": "6c48a",
"name": "texture",
"userData": {
"wrapModeS": "clamp-to-edge",
"wrapModeT": "clamp-to-edge",
"imageUuidOrDatabaseUri": "3e7492cb-7ae1-4f56-806d-07096662d8fb",
"isUuid": true,
"visible": false,
"minfilter": "linear",
"magfilter": "linear",
"mipfilter": "none",
"anisotropy": 0
},
"ver": "1.0.22",
"imported": true,
"files": [
".json"
],
"subMetas": {}
},
"f9941": {
"importer": "sprite-frame",
"uuid": "3e7492cb-7ae1-4f56-806d-07096662d8fb@f9941",
"displayName": "00041",
"id": "f9941",
"name": "spriteFrame",
"userData": {
"trimThreshold": 1,
"rotated": false,
"offsetX": 0,
"offsetY": 0,
"trimX": 0,
"trimY": 0,
"width": 115,
"height": 94,
"rawWidth": 115,
"rawHeight": 94,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
"pivotY": 0.5,
"meshType": 0,
"vertices": {
"rawPosition": [
-57.5,
-47,
0,
57.5,
-47,
0,
-57.5,
47,
0,
57.5,
47,
0
],
"indexes": [
0,
1,
2,
2,
1,
3
],
"uv": [
0,
94,
115,
94,
0,
0,
115,
0
],
"nuv": [
0,
0,
1,
0,
0,
1,
1,
1
],
"minPos": [
-57.5,
-47,
0
],
"maxPos": [
57.5,
47,
0
]
},
"isUuid": true,
"imageUuidOrDatabaseUri": "3e7492cb-7ae1-4f56-806d-07096662d8fb@6c48a",
"atlasUuid": "",
"trimType": "auto"
},
"ver": "1.0.12",
"imported": true,
"files": [
".json"
],
"subMetas": {}
}
},
"userData": {
"type": "sprite-frame",
"fixAlphaTransparencyArtifacts": false,
"hasAlpha": true,
"redirect": "3e7492cb-7ae1-4f56-806d-07096662d8fb@6c48a"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 413 B

@@ -0,0 +1,134 @@
{
"ver": "1.0.27",
"importer": "image",
"imported": true,
"uuid": "34daf5f4-3869-4f71-84c4-d09103701be6",
"files": [
".json",
".png"
],
"subMetas": {
"6c48a": {
"importer": "texture",
"uuid": "34daf5f4-3869-4f71-84c4-d09103701be6@6c48a",
"displayName": "00042",
"id": "6c48a",
"name": "texture",
"userData": {
"wrapModeS": "clamp-to-edge",
"wrapModeT": "clamp-to-edge",
"imageUuidOrDatabaseUri": "34daf5f4-3869-4f71-84c4-d09103701be6",
"isUuid": true,
"visible": false,
"minfilter": "linear",
"magfilter": "linear",
"mipfilter": "none",
"anisotropy": 0
},
"ver": "1.0.22",
"imported": true,
"files": [
".json"
],
"subMetas": {}
},
"f9941": {
"importer": "sprite-frame",
"uuid": "34daf5f4-3869-4f71-84c4-d09103701be6@f9941",
"displayName": "00042",
"id": "f9941",
"name": "spriteFrame",
"userData": {
"trimThreshold": 1,
"rotated": false,
"offsetX": 0,
"offsetY": 0,
"trimX": 0,
"trimY": 0,
"width": 108,
"height": 98,
"rawWidth": 108,
"rawHeight": 98,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
"pivotY": 0.5,
"meshType": 0,
"vertices": {
"rawPosition": [
-54,
-49,
0,
54,
-49,
0,
-54,
49,
0,
54,
49,
0
],
"indexes": [
0,
1,
2,
2,
1,
3
],
"uv": [
0,
98,
108,
98,
0,
0,
108,
0
],
"nuv": [
0,
0,
1,
0,
0,
1,
1,
1
],
"minPos": [
-54,
-49,
0
],
"maxPos": [
54,
49,
0
]
},
"isUuid": true,
"imageUuidOrDatabaseUri": "34daf5f4-3869-4f71-84c4-d09103701be6@6c48a",
"atlasUuid": "",
"trimType": "auto"
},
"ver": "1.0.12",
"imported": true,
"files": [
".json"
],
"subMetas": {}
}
},
"userData": {
"type": "sprite-frame",
"fixAlphaTransparencyArtifacts": false,
"hasAlpha": true,
"redirect": "34daf5f4-3869-4f71-84c4-d09103701be6@6c48a"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 470 B

@@ -0,0 +1,134 @@
{
"ver": "1.0.27",
"importer": "image",
"imported": true,
"uuid": "f3f3eadc-4916-4192-af76-8a3a0e2d01d7",
"files": [
".json",
".png"
],
"subMetas": {
"6c48a": {
"importer": "texture",
"uuid": "f3f3eadc-4916-4192-af76-8a3a0e2d01d7@6c48a",
"displayName": "00046_01",
"id": "6c48a",
"name": "texture",
"userData": {
"wrapModeS": "clamp-to-edge",
"wrapModeT": "clamp-to-edge",
"imageUuidOrDatabaseUri": "f3f3eadc-4916-4192-af76-8a3a0e2d01d7",
"isUuid": true,
"visible": false,
"minfilter": "linear",
"magfilter": "linear",
"mipfilter": "none",
"anisotropy": 0
},
"ver": "1.0.22",
"imported": true,
"files": [
".json"
],
"subMetas": {}
},
"f9941": {
"importer": "sprite-frame",
"uuid": "f3f3eadc-4916-4192-af76-8a3a0e2d01d7@f9941",
"displayName": "00046_01",
"id": "f9941",
"name": "spriteFrame",
"userData": {
"trimThreshold": 1,
"rotated": false,
"offsetX": 0.5,
"offsetY": 0,
"trimX": 6,
"trimY": 0,
"width": 19,
"height": 30,
"rawWidth": 30,
"rawHeight": 30,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
"pivotY": 0.5,
"meshType": 0,
"vertices": {
"rawPosition": [
-9.5,
-15,
0,
9.5,
-15,
0,
-9.5,
15,
0,
9.5,
15,
0
],
"indexes": [
0,
1,
2,
2,
1,
3
],
"uv": [
6,
30,
25,
30,
6,
0,
25,
0
],
"nuv": [
0.2,
0,
0.8333333333333334,
0,
0.2,
1,
0.8333333333333334,
1
],
"minPos": [
-9.5,
-15,
0
],
"maxPos": [
9.5,
15,
0
]
},
"isUuid": true,
"imageUuidOrDatabaseUri": "f3f3eadc-4916-4192-af76-8a3a0e2d01d7@6c48a",
"atlasUuid": "",
"trimType": "auto"
},
"ver": "1.0.12",
"imported": true,
"files": [
".json"
],
"subMetas": {}
}
},
"userData": {
"type": "sprite-frame",
"fixAlphaTransparencyArtifacts": false,
"hasAlpha": true,
"redirect": "f3f3eadc-4916-4192-af76-8a3a0e2d01d7@6c48a"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 291 B

@@ -0,0 +1,134 @@
{
"ver": "1.0.27",
"importer": "image",
"imported": true,
"uuid": "49664756-6c02-4eaa-ae19-784f4aeec759",
"files": [
".json",
".png"
],
"subMetas": {
"6c48a": {
"importer": "texture",
"uuid": "49664756-6c02-4eaa-ae19-784f4aeec759@6c48a",
"displayName": "00046_02",
"id": "6c48a",
"name": "texture",
"userData": {
"wrapModeS": "clamp-to-edge",
"wrapModeT": "clamp-to-edge",
"imageUuidOrDatabaseUri": "49664756-6c02-4eaa-ae19-784f4aeec759",
"isUuid": true,
"visible": false,
"minfilter": "linear",
"magfilter": "linear",
"mipfilter": "none",
"anisotropy": 0
},
"ver": "1.0.22",
"imported": true,
"files": [
".json"
],
"subMetas": {}
},
"f9941": {
"importer": "sprite-frame",
"uuid": "49664756-6c02-4eaa-ae19-784f4aeec759@f9941",
"displayName": "00046_02",
"id": "f9941",
"name": "spriteFrame",
"userData": {
"trimThreshold": 1,
"rotated": false,
"offsetX": 0.5,
"offsetY": 0.5,
"trimX": 7,
"trimY": 0,
"width": 17,
"height": 29,
"rawWidth": 30,
"rawHeight": 30,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
"pivotY": 0.5,
"meshType": 0,
"vertices": {
"rawPosition": [
-8.5,
-14.5,
0,
8.5,
-14.5,
0,
-8.5,
14.5,
0,
8.5,
14.5,
0
],
"indexes": [
0,
1,
2,
2,
1,
3
],
"uv": [
7,
30,
24,
30,
7,
1,
24,
1
],
"nuv": [
0.23333333333333334,
0.03333333333333333,
0.8,
0.03333333333333333,
0.23333333333333334,
1,
0.8,
1
],
"minPos": [
-8.5,
-14.5,
0
],
"maxPos": [
8.5,
14.5,
0
]
},
"isUuid": true,
"imageUuidOrDatabaseUri": "49664756-6c02-4eaa-ae19-784f4aeec759@6c48a",
"atlasUuid": "",
"trimType": "auto"
},
"ver": "1.0.12",
"imported": true,
"files": [
".json"
],
"subMetas": {}
}
},
"userData": {
"type": "sprite-frame",
"fixAlphaTransparencyArtifacts": false,
"hasAlpha": true,
"redirect": "49664756-6c02-4eaa-ae19-784f4aeec759@6c48a"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 497 B

@@ -0,0 +1,134 @@
{
"ver": "1.0.27",
"importer": "image",
"imported": true,
"uuid": "fd3595c7-4ad7-4458-a4e8-0c345c9c9ebb",
"files": [
".json",
".png"
],
"subMetas": {
"6c48a": {
"importer": "texture",
"uuid": "fd3595c7-4ad7-4458-a4e8-0c345c9c9ebb@6c48a",
"displayName": "00046_03",
"id": "6c48a",
"name": "texture",
"userData": {
"wrapModeS": "clamp-to-edge",
"wrapModeT": "clamp-to-edge",
"imageUuidOrDatabaseUri": "fd3595c7-4ad7-4458-a4e8-0c345c9c9ebb",
"isUuid": true,
"visible": false,
"minfilter": "linear",
"magfilter": "linear",
"mipfilter": "none",
"anisotropy": 0
},
"ver": "1.0.22",
"imported": true,
"files": [
".json"
],
"subMetas": {}
},
"f9941": {
"importer": "sprite-frame",
"uuid": "fd3595c7-4ad7-4458-a4e8-0c345c9c9ebb@f9941",
"displayName": "00046_03",
"id": "f9941",
"name": "spriteFrame",
"userData": {
"trimThreshold": 1,
"rotated": false,
"offsetX": 0.5,
"offsetY": 0.5,
"trimX": 6,
"trimY": 0,
"width": 19,
"height": 29,
"rawWidth": 30,
"rawHeight": 30,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
"pivotY": 0.5,
"meshType": 0,
"vertices": {
"rawPosition": [
-9.5,
-14.5,
0,
9.5,
-14.5,
0,
-9.5,
14.5,
0,
9.5,
14.5,
0
],
"indexes": [
0,
1,
2,
2,
1,
3
],
"uv": [
6,
30,
25,
30,
6,
1,
25,
1
],
"nuv": [
0.2,
0.03333333333333333,
0.8333333333333334,
0.03333333333333333,
0.2,
1,
0.8333333333333334,
1
],
"minPos": [
-9.5,
-14.5,
0
],
"maxPos": [
9.5,
14.5,
0
]
},
"isUuid": true,
"imageUuidOrDatabaseUri": "fd3595c7-4ad7-4458-a4e8-0c345c9c9ebb@6c48a",
"atlasUuid": "",
"trimType": "auto"
},
"ver": "1.0.12",
"imported": true,
"files": [
".json"
],
"subMetas": {}
}
},
"userData": {
"type": "sprite-frame",
"fixAlphaTransparencyArtifacts": false,
"hasAlpha": true,
"redirect": "fd3595c7-4ad7-4458-a4e8-0c345c9c9ebb@6c48a"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 572 B

@@ -0,0 +1,134 @@
{
"ver": "1.0.27",
"importer": "image",
"imported": true,
"uuid": "f2fc6501-6409-4dd3-b5ad-b965cc438061",
"files": [
".json",
".png"
],
"subMetas": {
"6c48a": {
"importer": "texture",
"uuid": "f2fc6501-6409-4dd3-b5ad-b965cc438061@6c48a",
"displayName": "00046_04",
"id": "6c48a",
"name": "texture",
"userData": {
"wrapModeS": "clamp-to-edge",
"wrapModeT": "clamp-to-edge",
"imageUuidOrDatabaseUri": "f2fc6501-6409-4dd3-b5ad-b965cc438061",
"isUuid": true,
"visible": false,
"minfilter": "linear",
"magfilter": "linear",
"mipfilter": "none",
"anisotropy": 0
},
"ver": "1.0.22",
"imported": true,
"files": [
".json"
],
"subMetas": {}
},
"f9941": {
"importer": "sprite-frame",
"uuid": "f2fc6501-6409-4dd3-b5ad-b965cc438061@f9941",
"displayName": "00046_04",
"id": "f9941",
"name": "spriteFrame",
"userData": {
"trimThreshold": 1,
"rotated": false,
"offsetX": 0,
"offsetY": 0,
"trimX": 6,
"trimY": 0,
"width": 18,
"height": 30,
"rawWidth": 30,
"rawHeight": 30,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
"pivotY": 0.5,
"meshType": 0,
"vertices": {
"rawPosition": [
-9,
-15,
0,
9,
-15,
0,
-9,
15,
0,
9,
15,
0
],
"indexes": [
0,
1,
2,
2,
1,
3
],
"uv": [
6,
30,
24,
30,
6,
0,
24,
0
],
"nuv": [
0.2,
0,
0.8,
0,
0.2,
1,
0.8,
1
],
"minPos": [
-9,
-15,
0
],
"maxPos": [
9,
15,
0
]
},
"isUuid": true,
"imageUuidOrDatabaseUri": "f2fc6501-6409-4dd3-b5ad-b965cc438061@6c48a",
"atlasUuid": "",
"trimType": "auto"
},
"ver": "1.0.12",
"imported": true,
"files": [
".json"
],
"subMetas": {}
}
},
"userData": {
"type": "sprite-frame",
"fixAlphaTransparencyArtifacts": false,
"hasAlpha": true,
"redirect": "f2fc6501-6409-4dd3-b5ad-b965cc438061@6c48a"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 335 B

@@ -0,0 +1,134 @@
{
"ver": "1.0.27",
"importer": "image",
"imported": true,
"uuid": "afd932be-bd4e-4eab-a1a1-59d5e23d9a1b",
"files": [
".json",
".png"
],
"subMetas": {
"6c48a": {
"importer": "texture",
"uuid": "afd932be-bd4e-4eab-a1a1-59d5e23d9a1b@6c48a",
"displayName": "00046_05",
"id": "6c48a",
"name": "texture",
"userData": {
"wrapModeS": "clamp-to-edge",
"wrapModeT": "clamp-to-edge",
"imageUuidOrDatabaseUri": "afd932be-bd4e-4eab-a1a1-59d5e23d9a1b",
"isUuid": true,
"visible": false,
"minfilter": "linear",
"magfilter": "linear",
"mipfilter": "none",
"anisotropy": 0
},
"ver": "1.0.22",
"imported": true,
"files": [
".json"
],
"subMetas": {}
},
"f9941": {
"importer": "sprite-frame",
"uuid": "afd932be-bd4e-4eab-a1a1-59d5e23d9a1b@f9941",
"displayName": "00046_05",
"id": "f9941",
"name": "spriteFrame",
"userData": {
"trimThreshold": 1,
"rotated": false,
"offsetX": -1,
"offsetY": 0,
"trimX": 4,
"trimY": 0,
"width": 20,
"height": 30,
"rawWidth": 30,
"rawHeight": 30,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
"pivotY": 0.5,
"meshType": 0,
"vertices": {
"rawPosition": [
-10,
-15,
0,
10,
-15,
0,
-10,
15,
0,
10,
15,
0
],
"indexes": [
0,
1,
2,
2,
1,
3
],
"uv": [
4,
30,
24,
30,
4,
0,
24,
0
],
"nuv": [
0.13333333333333333,
0,
0.8,
0,
0.13333333333333333,
1,
0.8,
1
],
"minPos": [
-10,
-15,
0
],
"maxPos": [
10,
15,
0
]
},
"isUuid": true,
"imageUuidOrDatabaseUri": "afd932be-bd4e-4eab-a1a1-59d5e23d9a1b@6c48a",
"atlasUuid": "",
"trimType": "auto"
},
"ver": "1.0.12",
"imported": true,
"files": [
".json"
],
"subMetas": {}
}
},
"userData": {
"type": "sprite-frame",
"fixAlphaTransparencyArtifacts": false,
"hasAlpha": true,
"redirect": "afd932be-bd4e-4eab-a1a1-59d5e23d9a1b@6c48a"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 474 B

@@ -0,0 +1,134 @@
{
"ver": "1.0.27",
"importer": "image",
"imported": true,
"uuid": "0f326209-7bfa-45df-b5d2-e2f4355cdc56",
"files": [
".json",
".png"
],
"subMetas": {
"6c48a": {
"importer": "texture",
"uuid": "0f326209-7bfa-45df-b5d2-e2f4355cdc56@6c48a",
"displayName": "00046_06",
"id": "6c48a",
"name": "texture",
"userData": {
"wrapModeS": "clamp-to-edge",
"wrapModeT": "clamp-to-edge",
"imageUuidOrDatabaseUri": "0f326209-7bfa-45df-b5d2-e2f4355cdc56",
"isUuid": true,
"visible": false,
"minfilter": "linear",
"magfilter": "linear",
"mipfilter": "none",
"anisotropy": 0
},
"ver": "1.0.22",
"imported": true,
"files": [
".json"
],
"subMetas": {}
},
"f9941": {
"importer": "sprite-frame",
"uuid": "0f326209-7bfa-45df-b5d2-e2f4355cdc56@f9941",
"displayName": "00046_06",
"id": "f9941",
"name": "spriteFrame",
"userData": {
"trimThreshold": 1,
"rotated": false,
"offsetX": 0,
"offsetY": 0,
"trimX": 6,
"trimY": 0,
"width": 18,
"height": 30,
"rawWidth": 30,
"rawHeight": 30,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
"pivotY": 0.5,
"meshType": 0,
"vertices": {
"rawPosition": [
-9,
-15,
0,
9,
-15,
0,
-9,
15,
0,
9,
15,
0
],
"indexes": [
0,
1,
2,
2,
1,
3
],
"uv": [
6,
30,
24,
30,
6,
0,
24,
0
],
"nuv": [
0.2,
0,
0.8,
0,
0.2,
1,
0.8,
1
],
"minPos": [
-9,
-15,
0
],
"maxPos": [
9,
15,
0
]
},
"isUuid": true,
"imageUuidOrDatabaseUri": "0f326209-7bfa-45df-b5d2-e2f4355cdc56@6c48a",
"atlasUuid": "",
"trimType": "auto"
},
"ver": "1.0.12",
"imported": true,
"files": [
".json"
],
"subMetas": {}
}
},
"userData": {
"type": "sprite-frame",
"fixAlphaTransparencyArtifacts": false,
"hasAlpha": true,
"redirect": "0f326209-7bfa-45df-b5d2-e2f4355cdc56@6c48a"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 542 B

@@ -0,0 +1,134 @@
{
"ver": "1.0.27",
"importer": "image",
"imported": true,
"uuid": "2eee488a-272e-47e5-90d3-7232e82aeef6",
"files": [
".json",
".png"
],
"subMetas": {
"6c48a": {
"importer": "texture",
"uuid": "2eee488a-272e-47e5-90d3-7232e82aeef6@6c48a",
"displayName": "00046_07",
"id": "6c48a",
"name": "texture",
"userData": {
"wrapModeS": "clamp-to-edge",
"wrapModeT": "clamp-to-edge",
"imageUuidOrDatabaseUri": "2eee488a-272e-47e5-90d3-7232e82aeef6",
"isUuid": true,
"visible": false,
"minfilter": "linear",
"magfilter": "linear",
"mipfilter": "none",
"anisotropy": 0
},
"ver": "1.0.22",
"imported": true,
"files": [
".json"
],
"subMetas": {}
},
"f9941": {
"importer": "sprite-frame",
"uuid": "2eee488a-272e-47e5-90d3-7232e82aeef6@f9941",
"displayName": "00046_07",
"id": "f9941",
"name": "spriteFrame",
"userData": {
"trimThreshold": 1,
"rotated": false,
"offsetX": 0.5,
"offsetY": 0,
"trimX": 6,
"trimY": 0,
"width": 19,
"height": 30,
"rawWidth": 30,
"rawHeight": 30,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
"pivotY": 0.5,
"meshType": 0,
"vertices": {
"rawPosition": [
-9.5,
-15,
0,
9.5,
-15,
0,
-9.5,
15,
0,
9.5,
15,
0
],
"indexes": [
0,
1,
2,
2,
1,
3
],
"uv": [
6,
30,
25,
30,
6,
0,
25,
0
],
"nuv": [
0.2,
0,
0.8333333333333334,
0,
0.2,
1,
0.8333333333333334,
1
],
"minPos": [
-9.5,
-15,
0
],
"maxPos": [
9.5,
15,
0
]
},
"isUuid": true,
"imageUuidOrDatabaseUri": "2eee488a-272e-47e5-90d3-7232e82aeef6@6c48a",
"atlasUuid": "",
"trimType": "auto"
},
"ver": "1.0.12",
"imported": true,
"files": [
".json"
],
"subMetas": {}
}
},
"userData": {
"type": "sprite-frame",
"fixAlphaTransparencyArtifacts": false,
"hasAlpha": true,
"redirect": "2eee488a-272e-47e5-90d3-7232e82aeef6@6c48a"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 384 B

@@ -0,0 +1,134 @@
{
"ver": "1.0.27",
"importer": "image",
"imported": true,
"uuid": "afcf3cdb-e05c-4b3b-a1b4-dad3af3095f0",
"files": [
".json",
".png"
],
"subMetas": {
"6c48a": {
"importer": "texture",
"uuid": "afcf3cdb-e05c-4b3b-a1b4-dad3af3095f0@6c48a",
"displayName": "00046_08",
"id": "6c48a",
"name": "texture",
"userData": {
"wrapModeS": "clamp-to-edge",
"wrapModeT": "clamp-to-edge",
"imageUuidOrDatabaseUri": "afcf3cdb-e05c-4b3b-a1b4-dad3af3095f0",
"isUuid": true,
"visible": false,
"minfilter": "linear",
"magfilter": "linear",
"mipfilter": "none",
"anisotropy": 0
},
"ver": "1.0.22",
"imported": true,
"files": [
".json"
],
"subMetas": {}
},
"f9941": {
"importer": "sprite-frame",
"uuid": "afcf3cdb-e05c-4b3b-a1b4-dad3af3095f0@f9941",
"displayName": "00046_08",
"id": "f9941",
"name": "spriteFrame",
"userData": {
"trimThreshold": 1,
"rotated": false,
"offsetX": 0,
"offsetY": -0.5,
"trimX": 5,
"trimY": 1,
"width": 20,
"height": 29,
"rawWidth": 30,
"rawHeight": 30,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
"pivotY": 0.5,
"meshType": 0,
"vertices": {
"rawPosition": [
-10,
-14.5,
0,
10,
-14.5,
0,
-10,
14.5,
0,
10,
14.5,
0
],
"indexes": [
0,
1,
2,
2,
1,
3
],
"uv": [
5,
29,
25,
29,
5,
0,
25,
0
],
"nuv": [
0.16666666666666666,
0,
0.8333333333333334,
0,
0.16666666666666666,
0.9666666666666667,
0.8333333333333334,
0.9666666666666667
],
"minPos": [
-10,
-14.5,
0
],
"maxPos": [
10,
14.5,
0
]
},
"isUuid": true,
"imageUuidOrDatabaseUri": "afcf3cdb-e05c-4b3b-a1b4-dad3af3095f0@6c48a",
"atlasUuid": "",
"trimType": "auto"
},
"ver": "1.0.12",
"imported": true,
"files": [
".json"
],
"subMetas": {}
}
},
"userData": {
"type": "sprite-frame",
"fixAlphaTransparencyArtifacts": false,
"hasAlpha": true,
"redirect": "afcf3cdb-e05c-4b3b-a1b4-dad3af3095f0@6c48a"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 675 B

@@ -0,0 +1,134 @@
{
"ver": "1.0.27",
"importer": "image",
"imported": true,
"uuid": "54682b5d-2606-4458-b1c6-373a87000cc8",
"files": [
".json",
".png"
],
"subMetas": {
"6c48a": {
"importer": "texture",
"uuid": "54682b5d-2606-4458-b1c6-373a87000cc8@6c48a",
"displayName": "00046_09",
"id": "6c48a",
"name": "texture",
"userData": {
"wrapModeS": "clamp-to-edge",
"wrapModeT": "clamp-to-edge",
"imageUuidOrDatabaseUri": "54682b5d-2606-4458-b1c6-373a87000cc8",
"isUuid": true,
"visible": false,
"minfilter": "linear",
"magfilter": "linear",
"mipfilter": "none",
"anisotropy": 0
},
"ver": "1.0.22",
"imported": true,
"files": [
".json"
],
"subMetas": {}
},
"f9941": {
"importer": "sprite-frame",
"uuid": "54682b5d-2606-4458-b1c6-373a87000cc8@f9941",
"displayName": "00046_09",
"id": "f9941",
"name": "spriteFrame",
"userData": {
"trimThreshold": 1,
"rotated": false,
"offsetX": 0.5,
"offsetY": 0,
"trimX": 6,
"trimY": 0,
"width": 19,
"height": 30,
"rawWidth": 30,
"rawHeight": 30,
"borderTop": 0,
"borderBottom": 0,
"borderLeft": 0,
"borderRight": 0,
"packable": true,
"pixelsToUnit": 100,
"pivotX": 0.5,
"pivotY": 0.5,
"meshType": 0,
"vertices": {
"rawPosition": [
-9.5,
-15,
0,
9.5,
-15,
0,
-9.5,
15,
0,
9.5,
15,
0
],
"indexes": [
0,
1,
2,
2,
1,
3
],
"uv": [
6,
30,
25,
30,
6,
0,
25,
0
],
"nuv": [
0.2,
0,
0.8333333333333334,
0,
0.2,
1,
0.8333333333333334,
1
],
"minPos": [
-9.5,
-15,
0
],
"maxPos": [
9.5,
15,
0
]
},
"isUuid": true,
"imageUuidOrDatabaseUri": "54682b5d-2606-4458-b1c6-373a87000cc8@6c48a",
"atlasUuid": "",
"trimType": "auto"
},
"ver": "1.0.12",
"imported": true,
"files": [
".json"
],
"subMetas": {}
}
},
"userData": {
"type": "sprite-frame",
"fixAlphaTransparencyArtifacts": false,
"hasAlpha": true,
"redirect": "54682b5d-2606-4458-b1c6-373a87000cc8@6c48a"
}
}

Some files were not shown because too many files have changed in this diff Show More