@kkarum/framework 2.3.18 → 2.3.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,344 @@
1
+ # Framework Usage
2
+
3
+ ## AI 工具使用约定
4
+
5
+ Codex / AI 工具生成业务代码时必须遵守:
6
+
7
+ - `AssetConfig` 资源读取使用点访问,例如 `preLoad.prefab.ShopLayer`;不要使用 `preLoad.prefab["ShopLayer"]`。
8
+ - `LayerController` 不负责查找节点和组件。节点使用 `FWPropertyNode`、组件使用 `FWPropertyComponent`,统一定义在对应 `FW.Layer` 组件中,变量名与节点名一致。
9
+ - `invoke()` 默认不在业务层使用。只有用户明确要求性能记录、错误包装或特殊异步治理时才调用。
10
+ - 新增业务 bundle 必须提供 `FW.Registry` 注册表,并在文件末尾调用 `FW.Framework.addRegistry()`。`bundleName` 与 `addRegistry()` 的 bundle key 必须完全一致,统一使用小写 bundle 名。
11
+
12
+ 示例:
13
+
14
+ ```ts
15
+ @ccclass
16
+ export class ShopLayer extends FW.Layer {
17
+ @FWPropertyNode()
18
+ BtnClose: cc.Node = null;
19
+
20
+ @FWPropertyComponent(cc.Label)
21
+ TitleLabel: cc.Label = null;
22
+ }
23
+
24
+ export class ShopLayerController extends FW.LayerController {
25
+ get layerAssetProperty(): FW.AssetProperty {
26
+ return FW.Entry.getComponent(ShopAssetConfig).preLoad.prefab.ShopLayer;
27
+ }
28
+
29
+ onInit() {
30
+ const layer = this.layer as ShopLayer;
31
+ this.cc(layer.BtnClose, this.onCloseClick);
32
+ }
33
+ }
34
+ ```
35
+
36
+ 本文档给出业务开发时的实际用法和推荐流程。
37
+
38
+ ## 基本规则
39
+
40
+ - 所有框架能力从 `FW.Entry` 进入。
41
+ - 业务组件继承框架基类:`FW.Logic`、`FW.Data`、`FW.AssetConfig`、`FW.Sender`、`FW.Handle`、`FW.LayerController`。
42
+ - 每个 bundle 使用 `FW.Registry` 注册自身组件。
43
+ - 异步资源走 `FW.Entry.resMgr`。
44
+ - UI 打开关闭走 `FW.Entry.layerMgr`。
45
+ - 事件绑定走 `LayerController.registerEvent()`、`cc()`、`fw()` 或 `FW.Entry.evtMgr`。
46
+ - 定时器走 `FW.Entry.timeMgr`。
47
+ - 可取消/带重试异步流程走 `FW.Entry.promiseMgr`。
48
+
49
+ ## 新增业务 Bundle
50
+
51
+ 推荐目录结构:
52
+
53
+ ```text
54
+ assets/<bundle>/
55
+ config/<Bundle>AssetConfig.ts
56
+ data/<Bundle>Data.ts
57
+ logic/<Bundle>Logic.ts
58
+ registry/<Bundle>Registry.ts
59
+ ui/<Feature>/<Feature>LayerController.ts
60
+ prefab/<Feature>Layer.prefab
61
+ ```
62
+
63
+ 注册类示例:
64
+
65
+ ```ts
66
+ import { RegisterAssetConfig } from "../config/RegisterAssetConfig";
67
+ import { RegisterLogic } from "../logic/RegisterLogic";
68
+
69
+ export class RegisterRegistry extends FW.Registry {
70
+ bundleName = "register";
71
+ logic: FW.Newable<FW.Logic> = RegisterLogic;
72
+ config: FW.Newable<FW.AssetConfig> = RegisterAssetConfig;
73
+ }
74
+
75
+ FW.Framework.addRegistry("register", RegisterRegistry);
76
+ ```
77
+
78
+ 注册规则:
79
+
80
+ - `Registry` 类名使用业务名 + `Registry`,例如 `RegisterRegistry`。
81
+ - `bundleName` 使用小写业务 bundle 名,例如 `"register"`。
82
+ - `FW.Framework.addRegistry("register", RegisterRegistry)` 必须与 `bundleName` 完全一致。
83
+ - `logic/data/config/sender/handle` 字段必须显式声明为对应 `FW.Newable<...>` 类型。
84
+ - 只声明实际存在的业务组件,不要为了占位声明空的 Data/Sender/Handle。
85
+
86
+ 业务组件命名必须包含 bundle 名,并以 `Logic/Data/Config/Sender/Handle` 结尾。`FrameworkBase.initializeDependencies()` 会按类名推断 bundle 和组件类型。
87
+
88
+ ## Logic
89
+
90
+ `Logic` 放业务流程,不持有复杂 UI 节点引用。
91
+
92
+ ```ts
93
+ export class ShopLogic extends FW.Logic {
94
+ initialize() {
95
+ // 初始化轻量状态,避免在构造阶段访问尚未加载的资源节点。
96
+ }
97
+
98
+ async refreshGoods() {
99
+ return this.getSender<ShopSender>().sendGetGoods();
100
+ }
101
+ }
102
+ ```
103
+
104
+ 规范:
105
+
106
+ - 默认直接 `await` 或 `return` 异步业务方法,不使用 `this.invoke()`。
107
+ - 只有用户明确要求性能记录、错误包装或特殊异步治理时才使用 `this.invoke()`。
108
+ - 业务数据写入 `this.getData<ShopData>()`。
109
+ - UI 通知用 `FW.Entry.evtMgr.dispatch()`。
110
+ - 不直接加载 prefab 打开 UI,应调用 `FW.Entry.layerMgr`。
111
+
112
+ ## Data
113
+
114
+ `Data` 保存业务状态,避免混入 UI 节点。
115
+
116
+ ```ts
117
+ export class ShopData extends FW.Data {
118
+ goods: GoodsItem[] = [];
119
+
120
+ setGoods(list: GoodsItem[]) {
121
+ this.goods = list || [];
122
+ FW.Entry.evtMgr.dispatch("SHOP_GOODS_CHANGED", this.goods);
123
+ }
124
+ }
125
+ ```
126
+
127
+ 规范:
128
+
129
+ - `Data` 不发起网络请求。
130
+ - `Data` 不操作 Cocos 节点。
131
+ - 对外提供明确方法,不鼓励外部到处直接改字段。
132
+
133
+ ## AssetConfig
134
+
135
+ 资源地址统一放在 Config。
136
+
137
+ ```ts
138
+ export class ShopAssetConfig extends FW.AssetConfig {
139
+ preLoad: FW.LoadConfig = {
140
+ prefab: {
141
+ ShopLayer: {
142
+ bundle: "shop",
143
+ path: "prefab/ShopLayer",
144
+ type: cc.Prefab,
145
+ priorityLoaded: true,
146
+ autoRelease: true,
147
+ },
148
+ },
149
+ };
150
+
151
+ demandLoad: FW.LoadConfig = {
152
+ texture: {
153
+ IconCoin: {
154
+ bundle: "shop",
155
+ path: "texture/icon_coin",
156
+ type: cc.Texture2D,
157
+ },
158
+ },
159
+ };
160
+ }
161
+ ```
162
+
163
+ 使用:
164
+
165
+ ```ts
166
+ const cfg = FW.Entry.getComponent<ShopAssetConfig>(ShopAssetConfig);
167
+ const prefab = await FW.Entry.resMgr.loadAsset<cc.Prefab>(
168
+ cfg.preLoad.prefab["ShopLayer"] as FW.AssetProperty,
169
+ );
170
+ ```
171
+
172
+ ## LayerController
173
+
174
+ UI 控制器示例:
175
+
176
+ ```ts
177
+ export class ShopLayerController extends FW.LayerController {
178
+ renderOrder = FW.SystemDefine.FWLayerRenderOrder.UI;
179
+ layerType = FW.SystemDefine.FWLayerType.REPEAT;
180
+ autoRelease = true;
181
+ isRepeatOpen = false;
182
+
183
+ get layerAssetProperty(): FW.AssetProperty {
184
+ return this.getConfig<ShopAssetConfig>().preLoad.prefab["ShopLayer"] as FW.AssetProperty;
185
+ }
186
+
187
+ onInit(args?: { tab?: string }) {
188
+ const closeBtn = this.find("CloseButton", this.layer.node);
189
+ this.cc(closeBtn, this.onCloseClick);
190
+ this.fw("SHOP_GOODS_CHANGED", this.renderGoods);
191
+ }
192
+
193
+ private onCloseClick = () => {
194
+ this.close();
195
+ };
196
+
197
+ private renderGoods = (goods: GoodsItem[]) => {
198
+ // 更新 UI
199
+ };
200
+ }
201
+ ```
202
+
203
+ 打开:
204
+
205
+ ```ts
206
+ await FW.Entry.layerMgr.openAsync<ShopLayerController>({
207
+ type: ShopLayerController,
208
+ args: { tab: "hot" },
209
+ });
210
+ ```
211
+
212
+ ## 资源加载
213
+
214
+ ```ts
215
+ const sf = await FW.Entry.resMgr.loadAsset<cc.SpriteFrame>({
216
+ bundle: "shop",
217
+ path: "texture/icon_coin",
218
+ type: cc.Texture2D,
219
+ });
220
+ ```
221
+
222
+ 注意:
223
+
224
+ - 传入 `cc.Texture2D` 时,`FWResManager` 默认会包装成 `cc.SpriteFrame`。
225
+ - 如果只要原始 Texture,使用 `loadAsset(assetProperty, true)`。
226
+ - 同步 `getAsset()` 只能用于已加载资源,否则会报错。
227
+
228
+ ## 事件注册
229
+
230
+ 优先使用控制器便捷方法:
231
+
232
+ ```ts
233
+ this.cc(this.find("BuyButton", this.layer.node), this.onBuyClick);
234
+ this.fw("SHOP_GOODS_CHANGED", this.renderGoods);
235
+ ```
236
+
237
+ 复杂事件:
238
+
239
+ ```ts
240
+ this.registerEvent({
241
+ CCEvent: [{
242
+ target: buyButton,
243
+ eventName: cc.Node.EventType.TOUCH_END,
244
+ responseInterval: 500,
245
+ cb: this.onBuyClick,
246
+ }],
247
+ FWEvent: [{
248
+ eventName: "SHOP_REFRESH",
249
+ responseInterval: 200,
250
+ cb: this.onRefresh,
251
+ }],
252
+ });
253
+ ```
254
+
255
+ 销毁时 LayerController 会清理自身事件,普通对象必须调用 `FW.Entry.evtMgr.targetOff(target)` 或通过框架管理生命周期。
256
+
257
+ ## 定时器
258
+
259
+ ```ts
260
+ const schedule = FW.Entry.timeMgr.scheduleOnce(() => {
261
+ this.refreshGoods();
262
+ }, 1, this);
263
+
264
+ FW.Entry.timeMgr.schedule(
265
+ () => this.tick(),
266
+ 0.5,
267
+ cc.macro.REPEAT_FOREVER,
268
+ this,
269
+ "shop_tick",
270
+ );
271
+
272
+ FW.Entry.timeMgr.unSchedule(this);
273
+ ```
274
+
275
+ 不要直接使用 `setTimeout`、`setInterval` 管理业务定时器。
276
+
277
+ ## Promise
278
+
279
+ ```ts
280
+ const proxy = FW.Entry.promiseMgr.execute<string>((resolve, reject, signal) => {
281
+ // 异步操作
282
+ signal.addEventListener("abort", () => {
283
+ // 清理
284
+ });
285
+ }, {
286
+ timeout: 10000,
287
+ retryCount: 3,
288
+ retryInterval: 2,
289
+ });
290
+
291
+ const result = await proxy.promise;
292
+ ```
293
+
294
+ 需要取消时:
295
+
296
+ ```ts
297
+ proxy.abort("layer closed");
298
+ ```
299
+
300
+ ## Socket
301
+
302
+ 业务 bundle 可注册 `Sender` 和 `Handle`:
303
+
304
+ ```ts
305
+ export class GameSender extends FW.Sender {
306
+ sendHeart() {
307
+ this.send(JSON.stringify({ cmd: "heart" }));
308
+ }
309
+
310
+ async onHeart(msg: any) {
311
+ return JSON.parse(msg).cmd === "heart";
312
+ }
313
+
314
+ async onBeforeSendingMessage(msg: any) {
315
+ return typeof msg === "string" ? msg : JSON.stringify(msg);
316
+ }
317
+
318
+ getProtocolKey(msg: any) {
319
+ return JSON.parse(msg).seq;
320
+ }
321
+ }
322
+ ```
323
+
324
+ ```ts
325
+ export class GameHandle extends FW.Handle {
326
+ async onBeforeReceivingMessage(event: MessageEvent) {
327
+ return JSON.parse(event.data);
328
+ }
329
+
330
+ onMessage(msg: any) {
331
+ FW.Entry.evtMgr.dispatch(`SOCKET_${msg.cmd}`, msg);
332
+ }
333
+
334
+ async onHeart(msg: any) {
335
+ return msg.cmd === "heart";
336
+ }
337
+
338
+ getProtocolKey(msg: any) {
339
+ return msg.seq;
340
+ }
341
+ }
342
+ ```
343
+
344
+ 创建连接走 `FW.Entry.socketMgr.createSocket()`,不要直接 new `WebSocket`。
@@ -0,0 +1,6 @@
1
+ {
2
+ "ver": "2.0.2",
3
+ "uuid": "6a4c7da4-306c-4eea-9d3b-aa499018e592",
4
+ "importer": "markdown",
5
+ "subMetas": {}
6
+ }
@@ -0,0 +1,241 @@
1
+ # MCP Integration
2
+
3
+ 本文档说明如何配合 cocos-mcp 进行 UI 自动化开发,并让生成的 UI 与本框架协作。
4
+
5
+ ## 适用范围
6
+
7
+ cocos-mcp 用于:
8
+
9
+ - 读取当前 Cocos 场景树。
10
+ - 创建 UI 节点树。
11
+ - 添加 Cocos 白名单组件。
12
+ - 绑定 SpriteFrame。
13
+ - 实例化 prefab。
14
+ - 创建 prefab 资源。
15
+ - 保存场景。
16
+
17
+ 框架代码用于:
18
+
19
+ - 定义业务 LayerController。
20
+ - 管理 Layer 打开/关闭。
21
+ - 绑定运行时事件。
22
+ - 管理资源生命周期。
23
+
24
+ 二者分工:MCP 负责编辑器里的节点和资源,框架负责运行时行为。
25
+
26
+ ## 推荐流程
27
+
28
+ ### 1. 诊断连接
29
+
30
+ ```text
31
+ cocos_mcp_diagnose
32
+ ```
33
+
34
+ 确认:
35
+
36
+ - WebSocket bridge reachable。
37
+ - 当前场景已打开。
38
+ - Canvas 存在。
39
+ - Cocos Creator 版本兼容。
40
+
41
+ ### 2. 获取真实场景树
42
+
43
+ ```text
44
+ scene_get_tree({
45
+ includeDiagnostics: true,
46
+ maxDepth: 6
47
+ })
48
+ ```
49
+
50
+ 禁止猜测节点路径。后续 MCP 操作必须使用绝对路径,例如 `/Canvas/MainLayer/CloseButton`。
51
+
52
+ ### 3. 查询或解析资源
53
+
54
+ ```text
55
+ asset_query({
56
+ pattern: "db://assets/**/*.png",
57
+ type: "sprite-frame"
58
+ })
59
+ ```
60
+
61
+ 或:
62
+
63
+ ```text
64
+ asset_resolve({
65
+ path: "assets/shop/texture/icon_coin.png",
66
+ type: "sprite-frame"
67
+ })
68
+ ```
69
+
70
+ 规则:
71
+
72
+ - 资源缺失时不要继续绑定。
73
+ - 多个候选时不要随机选择。
74
+ - 使用 Sprite 时绑定 `sprite-frame`,不是 texture 原图。
75
+
76
+ ### 4. 创建 UI 树
77
+
78
+ 大结构使用 `ui_create_tree`:
79
+
80
+ ```json
81
+ {
82
+ "root": "/Canvas",
83
+ "tree": {
84
+ "name": "ShopLayer",
85
+ "props": { "width": 720, "height": 1280, "x": 0, "y": 0 },
86
+ "components": [
87
+ { "type": "cc.Widget", "props": { "isAlignTop": true, "isAlignBottom": true, "isAlignLeft": true, "isAlignRight": true } }
88
+ ],
89
+ "children": [
90
+ {
91
+ "name": "CloseButton",
92
+ "props": { "width": 96, "height": 96, "x": 300, "y": 560 },
93
+ "components": [
94
+ { "type": "cc.Button" },
95
+ { "type": "cc.Sprite" }
96
+ ]
97
+ }
98
+ ]
99
+ }
100
+ }
101
+ ```
102
+
103
+ 小改动使用:
104
+
105
+ - `ui_create_node`
106
+ - `ui_set_node_props`
107
+ - `ui_add_component`
108
+ - `ui_set_component_props`
109
+ - `ui_apply_widget`
110
+ - `ui_apply_layout`
111
+
112
+ ### 5. 绑定图片
113
+
114
+ ```text
115
+ ui_set_sprite_frame({
116
+ nodePath: "/Canvas/ShopLayer/CloseButton",
117
+ asset: {
118
+ url: "db://assets/shop/texture/close.png",
119
+ type: "sprite-frame"
120
+ },
121
+ addComponentIfMissing: true
122
+ })
123
+ ```
124
+
125
+ ### 6. 生成 prefab
126
+
127
+ 如果 UI 是运行时 Layer,推荐保存为 prefab:
128
+
129
+ ```text
130
+ prefab_create_from_node({
131
+ nodePath: "/Canvas/ShopLayer",
132
+ url: "db://assets/shop/prefab/ShopLayer.prefab",
133
+ overwrite: true
134
+ })
135
+ ```
136
+
137
+ 然后在 `ShopAssetConfig` 中声明:
138
+
139
+ ```ts
140
+ ShopLayer: {
141
+ bundle: "shop",
142
+ path: "prefab/ShopLayer",
143
+ type: cc.Prefab,
144
+ priorityLoaded: true,
145
+ autoRelease: true,
146
+ }
147
+ ```
148
+
149
+ ### 7. 运行时代码绑定事件
150
+
151
+ 优先在 `LayerController` 中绑定:
152
+
153
+ ```ts
154
+ onInit() {
155
+ this.cc(this.find("CloseButton", this.layer.node), this.onCloseClick);
156
+ }
157
+ ```
158
+
159
+ 只有当目标节点上已经有脚本组件和 handler 时,才使用 MCP 的 `ui_bind_button_event`。
160
+
161
+ ## Layer prefab 约定
162
+
163
+ 用于 `FW.Entry.layerMgr` 的 prefab 应满足:
164
+
165
+ - 根节点名和资源名清晰对应,例如 `ShopLayer`。
166
+ - 根节点挂载 `FW.Layer` 或业务继承的 Layer 组件。
167
+ - 控制器类不挂在节点上,由 `FWLayerManager` 创建并绑定到 `layer.ctr`。
168
+ - prefab 资源路径与 `AssetConfig` 一致。
169
+ - 需要全屏适配时根节点使用 `cc.Widget` full/stretch。
170
+
171
+ ## UI 节点命名约定
172
+
173
+ 推荐:
174
+
175
+ - `CloseButton`
176
+ - `ConfirmButton`
177
+ - `CancelButton`
178
+ - `TitleLabel`
179
+ - `GoodsScrollView`
180
+ - `Content`
181
+ - `ItemTemplate`
182
+ - `IconSprite`
183
+
184
+ 避免:
185
+
186
+ - `node1`
187
+ - `btn`
188
+ - 中文节点名
189
+ - 与多个兄弟节点重复的名字
190
+
191
+ 原因:`LayerController.find()` 和装饰器依赖节点名查找。重复或含义不明的节点会降低自动化可靠性。
192
+
193
+ ## Codex + MCP 协作模板
194
+
195
+ 当用户要求“创建一个商店弹窗”:
196
+
197
+ 1. 用 MCP 读取场景树,确认 `/Canvas`。
198
+ 2. 用 MCP 创建 `ShopLayer` 节点树。
199
+ 3. 查询资源并绑定 SpriteFrame。
200
+ 4. 保存为 `assets/shop/prefab/ShopLayer.prefab`。
201
+ 5. 在业务代码新增 `ShopLayerController`。
202
+ 6. 在 `ShopAssetConfig` 声明 prefab。
203
+ 7. 用 `this.cc()` 绑定 `CloseButton`、`BuyButton`。
204
+ 8. 用 `FW.Entry.layerMgr.openAsync({ type: ShopLayerController })` 打开。
205
+
206
+ ## 干运行和验证
207
+
208
+ 优先使用 `dryRun: true` 验证高风险操作:
209
+
210
+ - 删除节点。
211
+ - 覆盖 prefab。
212
+ - 批量创建大树。
213
+ - 绑定不确定资源。
214
+
215
+ 完成后验证:
216
+
217
+ ```text
218
+ scene_get_tree({ includeDiagnostics: true, maxDepth: 8 })
219
+ ```
220
+
221
+ 检查:
222
+
223
+ - 节点路径存在。
224
+ - 组件存在。
225
+ - SpriteFrame 已绑定。
226
+ - prefab 已生成。
227
+ - 没有多余测试节点遗留。
228
+
229
+ ## 保存策略
230
+
231
+ 不要在用户未确认时随意保存场景。以下情况可保存:
232
+
233
+ - 用户明确要求保存。
234
+ - 当前任务目标就是生成 prefab 或落地 UI。
235
+ - 已完成验证且保存是任务必要结果。
236
+
237
+ 保存使用:
238
+
239
+ ```text
240
+ scene_save()
241
+ ```
@@ -0,0 +1,6 @@
1
+ {
2
+ "ver": "2.0.2",
3
+ "uuid": "3ef5e4b7-ab3c-4792-b365-533e6193a30c",
4
+ "importer": "markdown",
5
+ "subMetas": {}
6
+ }