@kkarum/framework 2.3.17 → 2.3.19
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.
- package/README.md +167 -93
- package/docs/CODEX_GUIDE.md +151 -0
- package/docs/CODEX_GUIDE.md.meta +6 -0
- package/docs/ERROR_HANDLING.md +233 -0
- package/docs/ERROR_HANDLING.md.meta +6 -0
- package/docs/FRAMEWORK_API.md +414 -0
- package/docs/FRAMEWORK_API.md.meta +6 -0
- package/docs/FRAMEWORK_EXAMPLES.md +309 -0
- package/docs/FRAMEWORK_EXAMPLES.md.meta +6 -0
- package/docs/FRAMEWORK_OVERVIEW.md +127 -0
- package/docs/FRAMEWORK_OVERVIEW.md.meta +6 -0
- package/docs/FRAMEWORK_PATTERNS.md +261 -0
- package/docs/FRAMEWORK_PATTERNS.md.meta +6 -0
- package/docs/FRAMEWORK_USAGE.md +309 -0
- package/docs/FRAMEWORK_USAGE.md.meta +6 -0
- package/docs/MCP_INTEGRATION.md +241 -0
- package/docs/MCP_INTEGRATION.md.meta +6 -0
- package/docs/UI_DEVELOPMENT_GUIDE.md +260 -0
- package/docs/UI_DEVELOPMENT_GUIDE.md.meta +6 -0
- package/docs.meta +13 -0
- package/package.json +1 -1
- package/service/http/FWHttp.ts +80 -1
- package/types/FW.d.ts +32 -18
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
# Framework API
|
|
2
|
+
|
|
3
|
+
本文档记录业务开发最常复用的框架 API。除非维护框架本身,不要直接修改 `assets/framework`。
|
|
4
|
+
|
|
5
|
+
## 全局入口
|
|
6
|
+
|
|
7
|
+
### `FW.Entry`
|
|
8
|
+
|
|
9
|
+
常用属性:
|
|
10
|
+
|
|
11
|
+
| 属性 | 用途 |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| `bundleName` | 当前业务 bundle 名 |
|
|
14
|
+
| `scene` | 当前 `FW.Scene` |
|
|
15
|
+
| `resMgr` | 资源和 bundle 管理 |
|
|
16
|
+
| `layerMgr` | Layer 打开、关闭、栈、队列 |
|
|
17
|
+
| `uiMgr` | UI 事件、节点查找 |
|
|
18
|
+
| `evtMgr` | 框架事件总线 |
|
|
19
|
+
| `timeMgr` | 定时器和 update |
|
|
20
|
+
| `promiseMgr` | 可取消/超时/重试 Promise |
|
|
21
|
+
| `socketMgr` | Socket 连接 |
|
|
22
|
+
| `objectMgr` | 对象池 |
|
|
23
|
+
| `taskMgr` | 分帧任务 |
|
|
24
|
+
| `languageMgr` | 多语言 |
|
|
25
|
+
| `performanceMgr` | 性能采样 |
|
|
26
|
+
| `utils` | 通用工具 |
|
|
27
|
+
|
|
28
|
+
方法:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
FW.Entry.getComponent<T>(identifier): T
|
|
32
|
+
FW.Entry.getComponents<T>(identifier?): T[]
|
|
33
|
+
FW.Entry.launchScene(bundleName, forceRelease?)
|
|
34
|
+
FW.Entry.register({ bundleName, sceneName, depend, autoRelease })
|
|
35
|
+
FW.Entry.unRegister(...)
|
|
36
|
+
FW.Entry.getDepend(bundleName): string[]
|
|
37
|
+
FW.Entry.update(dt)
|
|
38
|
+
FW.Entry.restart()
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## 依赖容器
|
|
42
|
+
|
|
43
|
+
### `FW.Framework`
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
FW.Framework.register({
|
|
47
|
+
bundleName,
|
|
48
|
+
logic,
|
|
49
|
+
data,
|
|
50
|
+
config,
|
|
51
|
+
sender,
|
|
52
|
+
handle,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
FW.Framework.unRegister(...)
|
|
56
|
+
FW.Framework.getComponent<T>(ClassOrKey)
|
|
57
|
+
FW.Framework.getComponents<T>(BaseClass)
|
|
58
|
+
FW.Framework.addRegistry(bundleName, RegistryClass)
|
|
59
|
+
FW.Framework.createRegistry(bundleName)
|
|
60
|
+
FW.Framework.destroyRegistry(bundleName)
|
|
61
|
+
FW.Framework.restart()
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
绑定 key 规则:`${bundleName}${FW.SystemDefine.FWBindTag.*}`。
|
|
65
|
+
|
|
66
|
+
业务代码通常通过 `FW.Entry.getComponent()` 间接使用,不直接操作容器。
|
|
67
|
+
|
|
68
|
+
## 基类
|
|
69
|
+
|
|
70
|
+
### `FW.FrameworkBase`
|
|
71
|
+
|
|
72
|
+
继承者:
|
|
73
|
+
|
|
74
|
+
- `FW.Logic`
|
|
75
|
+
- `FW.Data`
|
|
76
|
+
- `FW.Service`
|
|
77
|
+
- `FW.Sender`
|
|
78
|
+
- `FW.Handle`
|
|
79
|
+
- `FWManager`
|
|
80
|
+
- `FW.LayerController`
|
|
81
|
+
|
|
82
|
+
可用方法:
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
this.entry
|
|
86
|
+
this.getLogic<T>()
|
|
87
|
+
this.getData<T>()
|
|
88
|
+
this.getConfig<T>()
|
|
89
|
+
this.getSender<T>()
|
|
90
|
+
this.getHandle<T>()
|
|
91
|
+
this.initializeDependencies()
|
|
92
|
+
await this.invoke(promise, "operationName")
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
自动注入依赖依赖类名约定:
|
|
96
|
+
|
|
97
|
+
- `*Logic`
|
|
98
|
+
- `*Data`
|
|
99
|
+
- `*Config` 或 `*AssetConfig`
|
|
100
|
+
- `*Sender` 或 `*SocketSender`
|
|
101
|
+
- `*Handle` 或 `*SocketHandle`
|
|
102
|
+
|
|
103
|
+
类名还必须包含 bundle 名。例如 bundle 为 `shop`,推荐 `ShopLogic`、`ShopData`。
|
|
104
|
+
|
|
105
|
+
## Registry
|
|
106
|
+
|
|
107
|
+
### `FW.Registry`
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
abstract readonly bundleName: string;
|
|
111
|
+
readonly autoRelease?: boolean;
|
|
112
|
+
readonly sceneName?: string;
|
|
113
|
+
readonly depend?: string[];
|
|
114
|
+
readonly logic?: FW.Newable<FW.Logic>;
|
|
115
|
+
readonly data?: FW.Newable<FW.Data>;
|
|
116
|
+
readonly config?: FW.Newable<FW.AssetConfig>;
|
|
117
|
+
readonly sender?: FW.Newable<FW.Sender>;
|
|
118
|
+
readonly handle?: FW.Newable<FW.Handle>;
|
|
119
|
+
register(): void;
|
|
120
|
+
unRegister(): void;
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
注册表用于把 bundle 元信息和业务组件交给框架。bundle 加载/释放会自动调用。
|
|
124
|
+
|
|
125
|
+
## Layer API
|
|
126
|
+
|
|
127
|
+
### `FW.Entry.layerMgr`
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
openAsync<Ctr>({ type, parent?, position?, args? }): Promise<Ctr>
|
|
131
|
+
openSync<Ctr>({ type, parent?, position?, args? }): Ctr
|
|
132
|
+
close(ctr): Promise<Ctr>
|
|
133
|
+
closeFromStack(count?)
|
|
134
|
+
closeFromLayerName(name | name[])
|
|
135
|
+
displayLayer(ctr)
|
|
136
|
+
hideLayer(ctr)
|
|
137
|
+
removeAllChildren(node)
|
|
138
|
+
clear()
|
|
139
|
+
openDebug()
|
|
140
|
+
getLayerMap()
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
`LayerOpenArgs`:
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
type LayerOpenArgs = {
|
|
147
|
+
type: new () => FW.LayerController;
|
|
148
|
+
parent?: cc.Node;
|
|
149
|
+
position?: { x: number; y: number };
|
|
150
|
+
args?: any;
|
|
151
|
+
};
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### `FW.LayerController`
|
|
155
|
+
|
|
156
|
+
业务控制器必须实现:
|
|
157
|
+
|
|
158
|
+
```ts
|
|
159
|
+
renderOrder: FW.SystemDefine.FWLayerRenderOrder;
|
|
160
|
+
layerType: FW.SystemDefine.FWLayerType;
|
|
161
|
+
layer: FW.Layer;
|
|
162
|
+
layerAssetProperty: FW.AssetProperty;
|
|
163
|
+
autoRelease: boolean;
|
|
164
|
+
isRepeatOpen: boolean;
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
生命周期:
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
initialize()
|
|
171
|
+
onInit(...args)
|
|
172
|
+
onLoad()
|
|
173
|
+
onStart()
|
|
174
|
+
onEnable()
|
|
175
|
+
onDisable()
|
|
176
|
+
onUpdate(dt)
|
|
177
|
+
onLateUpdate(dt)
|
|
178
|
+
onClose()
|
|
179
|
+
onDestroy()
|
|
180
|
+
destroy()
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
便捷方法:
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
find(path, referenceNode)
|
|
187
|
+
findComponent<T>(path, referenceNode, type)
|
|
188
|
+
registerEvent(args)
|
|
189
|
+
cc(targetNode, cb, responseInterval?, eventName?)
|
|
190
|
+
fw(eventName, cb, target?, responseInterval?)
|
|
191
|
+
pauseEvent()
|
|
192
|
+
resumeEvent()
|
|
193
|
+
hide()
|
|
194
|
+
display()
|
|
195
|
+
close()
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## 资源 API
|
|
199
|
+
|
|
200
|
+
### `FW.AssetProperty`
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
type AssetProperty = {
|
|
204
|
+
resourceId?: number;
|
|
205
|
+
bundle?: string;
|
|
206
|
+
path: string;
|
|
207
|
+
type?: typeof cc.Asset;
|
|
208
|
+
cb?: (asset: FW.AssetData | FW.AssetData[]) => void;
|
|
209
|
+
progress?: (finish: number, total: number, item: cc.AssetManager.RequestItem) => void;
|
|
210
|
+
user?: string;
|
|
211
|
+
autoRelease?: boolean;
|
|
212
|
+
priorityLoaded?: boolean;
|
|
213
|
+
};
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### `FW.Entry.resMgr`
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
loadBundle(bundleName): Promise<cc.AssetManager.Bundle>
|
|
220
|
+
getBundle(bundleName): cc.AssetManager.Bundle
|
|
221
|
+
releaseBundle(bundleName): void
|
|
222
|
+
hasBundle(bundleName): boolean
|
|
223
|
+
|
|
224
|
+
loadAssetData<T>(assetProperty): Promise<FW.AssetData>
|
|
225
|
+
loadAsset<T>(assetProperty, texture?): Promise<T>
|
|
226
|
+
getAssetData<T>(assetProperty, texture?): FW.AssetData
|
|
227
|
+
getAsset<T>(assetProperty, texture?): T
|
|
228
|
+
preLoad(assetProperty | assetProperty[])
|
|
229
|
+
loadDir(assetProperty): Promise<FW.AssetData[]>
|
|
230
|
+
loadRemote<T>(url, options?): Promise<T>
|
|
231
|
+
loadSpineDataFromRemote({ img, ske, atlas, bin? }): Promise<sp.SkeletonData>
|
|
232
|
+
releaseAsset(assetProperty): void
|
|
233
|
+
hasAsset(assetProperty): boolean
|
|
234
|
+
registerRejectHandler(listener): void
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
纹理注意:默认 `Texture2D` 会转换为 `SpriteFrame`。需要原始纹理时传 `texture: true`。
|
|
238
|
+
|
|
239
|
+
## 事件 API
|
|
240
|
+
|
|
241
|
+
### `FW.Entry.evtMgr`
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
register(eventName, cb, target, options?)
|
|
245
|
+
registerOnce(eventName, cb, target, options?)
|
|
246
|
+
dispatch(eventName, ...args)
|
|
247
|
+
unRegister(eventName, target)
|
|
248
|
+
eventOff(eventName)
|
|
249
|
+
targetOff(target)
|
|
250
|
+
targetPause(target)
|
|
251
|
+
targetResume(target)
|
|
252
|
+
has(eventName, target)
|
|
253
|
+
getListenerCount(eventName)
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
`options`:
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
{
|
|
260
|
+
priority?: FW.SystemDefine.FWPriorityOrder;
|
|
261
|
+
intercept?: boolean;
|
|
262
|
+
once?: boolean;
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### `FW.Entry.uiMgr`
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
register({ target?, CCEvent?, FWEvent? })
|
|
270
|
+
find(path, rootNode): cc.Node | null
|
|
271
|
+
pauseEvent(target)
|
|
272
|
+
resumeEvent(target)
|
|
273
|
+
unRegisterEvent(args)
|
|
274
|
+
unRegisterTarget(target)
|
|
275
|
+
unRegisterAll()
|
|
276
|
+
hasRegister(eventName, target)
|
|
277
|
+
buttonEnable(target)
|
|
278
|
+
buttonDisable(target)
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
## Time API
|
|
282
|
+
|
|
283
|
+
```ts
|
|
284
|
+
FW.Entry.timeMgr.schedule(cb, intervalSeconds?, repeat?, target?, tag?)
|
|
285
|
+
FW.Entry.timeMgr.scheduleOnce(cbOrTime?, timeOrTargetOrTag?, targetOrTag?)
|
|
286
|
+
FW.Entry.timeMgr.update(cb, tagOrTarget?, target?)
|
|
287
|
+
FW.Entry.timeMgr.pauseSchedule(tag | id | target)
|
|
288
|
+
FW.Entry.timeMgr.resumeSchedule(tag | id | target)
|
|
289
|
+
FW.Entry.timeMgr.unSchedule(tag | id | target)
|
|
290
|
+
FW.Entry.timeMgr.onUpdate(dt)
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
返回的 `TimerSchedule` 支持:
|
|
294
|
+
|
|
295
|
+
```ts
|
|
296
|
+
unSchedule()
|
|
297
|
+
pauseSchedule()
|
|
298
|
+
resumeSchedule()
|
|
299
|
+
promise? // scheduleOnce
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## Promise API
|
|
303
|
+
|
|
304
|
+
```ts
|
|
305
|
+
const proxy = FW.Entry.promiseMgr.execute(executor, {
|
|
306
|
+
reason,
|
|
307
|
+
timeout,
|
|
308
|
+
retryCount,
|
|
309
|
+
retryInterval,
|
|
310
|
+
retryCondition,
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
proxy.id
|
|
314
|
+
proxy.promise
|
|
315
|
+
proxy.status
|
|
316
|
+
proxy.abort(reason?)
|
|
317
|
+
proxy.addAbortEventListener(listener)
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
批量:
|
|
321
|
+
|
|
322
|
+
```ts
|
|
323
|
+
FW.Entry.promiseMgr.all([proxy1, proxy2], options)
|
|
324
|
+
FW.Entry.promiseMgr.cancel(id, reason?)
|
|
325
|
+
FW.Entry.promiseMgr.cancelMultiple(ids, reason?)
|
|
326
|
+
FW.Entry.promiseMgr.cancelAll(reason?)
|
|
327
|
+
FW.Entry.promiseMgr.getStatus(id)
|
|
328
|
+
FW.Entry.promiseMgr.getAllStatus()
|
|
329
|
+
FW.Entry.promiseMgr.getActiveCount()
|
|
330
|
+
FW.Entry.promiseMgr.clearCompletedPromise()
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
## Socket API
|
|
334
|
+
|
|
335
|
+
### `FW.Entry.socketMgr`
|
|
336
|
+
|
|
337
|
+
```ts
|
|
338
|
+
createSocket(tag, address, sender, handle, config): Promise<FW.Socket>
|
|
339
|
+
getSocket(tag?): FW.Socket
|
|
340
|
+
getSocketMap(): Map<string, FW.Socket>
|
|
341
|
+
closeSocket(socket)
|
|
342
|
+
closeAll()
|
|
343
|
+
pauseMessageHandle()
|
|
344
|
+
resumeMessageHandle()
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### `FW.Sender`
|
|
348
|
+
|
|
349
|
+
```ts
|
|
350
|
+
sendHeart?()
|
|
351
|
+
onHeart?(msg): Promise<boolean>
|
|
352
|
+
onBeforeSendingMessage?(msg): Promise<FW.SocketMessage>
|
|
353
|
+
getProtocolKey?(msg): string
|
|
354
|
+
send(msg)
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### `FW.Handle`
|
|
358
|
+
|
|
359
|
+
```ts
|
|
360
|
+
onMessage(msg): void
|
|
361
|
+
onBeforeReceivingMessage?(msg): Promise<FW.SocketMessage>
|
|
362
|
+
onHeart?(msg): Promise<boolean>
|
|
363
|
+
getProtocolKey?(msg): string
|
|
364
|
+
onOpen?()
|
|
365
|
+
onClose?()
|
|
366
|
+
onError?(msg)
|
|
367
|
+
onTimeout?()
|
|
368
|
+
onWeakNetWork?()
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
## Object Pool API
|
|
372
|
+
|
|
373
|
+
```ts
|
|
374
|
+
const pool = await FW.Entry.objectMgr.createObjectPool(prefabOrNodeOrAsset, parent, tagOrType?)
|
|
375
|
+
FW.Entry.objectMgr.getObjectPool(tag)
|
|
376
|
+
FW.Entry.objectMgr.destroyObjectPool(tag)
|
|
377
|
+
FW.Entry.objectMgr.getPoolStats()
|
|
378
|
+
FW.Entry.objectMgr.clearAllPools()
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
`FW.ObjectPool`:
|
|
382
|
+
|
|
383
|
+
```ts
|
|
384
|
+
get(...args): FW.ObjectProperty
|
|
385
|
+
put(node | component | uniqueId)
|
|
386
|
+
puts(array)
|
|
387
|
+
findObjectProperty(node | component | uniqueId)
|
|
388
|
+
isEmpty()
|
|
389
|
+
getEnableObjectMap()
|
|
390
|
+
getDisableObjectMap()
|
|
391
|
+
getAllObjectMap()
|
|
392
|
+
onDestroy()
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
池对象组件继承 `FW.Object`,实现 `onInit()`、`onGet()`、`onPut()`、`onDestroy()`。
|
|
396
|
+
|
|
397
|
+
## 装饰器
|
|
398
|
+
|
|
399
|
+
全局装饰器由 `initializeFramework()` 挂到 `globalThis`:
|
|
400
|
+
|
|
401
|
+
```ts
|
|
402
|
+
@FWPropertyNode({ path? })
|
|
403
|
+
@FWPropertyNodes(...paths)
|
|
404
|
+
@FWPropertyComponent(ComponentClass, childName?, mute?)
|
|
405
|
+
@FWPropertyComponents(ComponentClass, childName?)
|
|
406
|
+
@PerformanceMonitor(operationName?)
|
|
407
|
+
@FWDeprecated(description?)
|
|
408
|
+
@FWSocketAutoProcessPause()
|
|
409
|
+
@FWSocketAutoProcessResume()
|
|
410
|
+
@AutoRegisterCCEvent(nodePath, eventName?)
|
|
411
|
+
@AutoRegisterFWEvent(eventName)
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
装饰器会改写 `onLoad` 或方法 descriptor。使用时必须确认目标类生命周期和路径稳定。
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
# Framework Examples
|
|
2
|
+
|
|
3
|
+
本文档提供可复制的业务代码骨架。示例中的 bundle 名使用 `shop`,实际开发时替换为目标业务名。
|
|
4
|
+
|
|
5
|
+
## Registry
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { FWRegistry } from "../../framework/registry/FWRegistry";
|
|
9
|
+
import { ShopLogic } from "../logic/ShopLogic";
|
|
10
|
+
import { ShopData } from "../data/ShopData";
|
|
11
|
+
import { ShopAssetConfig } from "../config/ShopAssetConfig";
|
|
12
|
+
import { ShopSender } from "../socket/ShopSender";
|
|
13
|
+
import { ShopHandle } from "../socket/ShopHandle";
|
|
14
|
+
|
|
15
|
+
export class ShopRegistry extends FWRegistry {
|
|
16
|
+
readonly bundleName = "shop";
|
|
17
|
+
readonly sceneName = "ShopScene";
|
|
18
|
+
readonly depend = ["common"];
|
|
19
|
+
readonly autoRelease = true;
|
|
20
|
+
readonly logic = ShopLogic;
|
|
21
|
+
readonly data = ShopData;
|
|
22
|
+
readonly config = ShopAssetConfig;
|
|
23
|
+
readonly sender = ShopSender;
|
|
24
|
+
readonly handle = ShopHandle;
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## AssetConfig
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
export class ShopAssetConfig extends FW.AssetConfig {
|
|
32
|
+
preLoad: FW.LoadConfig = {
|
|
33
|
+
prefab: {
|
|
34
|
+
ShopLayer: {
|
|
35
|
+
bundle: "shop",
|
|
36
|
+
path: "prefab/ShopLayer",
|
|
37
|
+
type: cc.Prefab,
|
|
38
|
+
priorityLoaded: true,
|
|
39
|
+
autoRelease: true,
|
|
40
|
+
},
|
|
41
|
+
ShopItem: {
|
|
42
|
+
bundle: "shop",
|
|
43
|
+
path: "prefab/ShopItem",
|
|
44
|
+
type: cc.Prefab,
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
demandLoad: FW.LoadConfig = {
|
|
50
|
+
texture: {
|
|
51
|
+
CoinIcon: {
|
|
52
|
+
bundle: "shop",
|
|
53
|
+
path: "texture/CoinIcon",
|
|
54
|
+
type: cc.Texture2D,
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Data
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
export type ShopGoods = {
|
|
65
|
+
id: number;
|
|
66
|
+
name: string;
|
|
67
|
+
price: number;
|
|
68
|
+
icon: string;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export class ShopData extends FW.Data {
|
|
72
|
+
private goods: ShopGoods[] = [];
|
|
73
|
+
|
|
74
|
+
setGoods(goods: ShopGoods[]) {
|
|
75
|
+
this.goods = goods || [];
|
|
76
|
+
FW.Entry.evtMgr.dispatch("SHOP_GOODS_CHANGED", this.goods);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
getGoods() {
|
|
80
|
+
return this.goods;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Logic
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
export class ShopLogic extends FW.Logic {
|
|
89
|
+
async openShop() {
|
|
90
|
+
await FW.Entry.layerMgr.openAsync({
|
|
91
|
+
type: ShopLayerController,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async refreshGoods() {
|
|
96
|
+
const response = await this.invoke(
|
|
97
|
+
this.getSender<ShopSender>().requestGoods(),
|
|
98
|
+
"requestGoods",
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
if (!response) return;
|
|
102
|
+
this.getData<ShopData>().setGoods(response.goods);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## LayerController
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
export class ShopLayerController extends FW.LayerController {
|
|
111
|
+
renderOrder = FW.SystemDefine.FWLayerRenderOrder.UI;
|
|
112
|
+
layerType = FW.SystemDefine.FWLayerType.REPEAT;
|
|
113
|
+
autoRelease = true;
|
|
114
|
+
isRepeatOpen = false;
|
|
115
|
+
|
|
116
|
+
private listNode: cc.Node;
|
|
117
|
+
private closeButton: cc.Node;
|
|
118
|
+
|
|
119
|
+
get layerAssetProperty(): FW.AssetProperty {
|
|
120
|
+
return this.getConfig<ShopAssetConfig>().preLoad.prefab["ShopLayer"] as FW.AssetProperty;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
onInit() {
|
|
124
|
+
this.cacheNodes();
|
|
125
|
+
this.bindEvents();
|
|
126
|
+
this.getLogic<ShopLogic>().refreshGoods();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private cacheNodes() {
|
|
130
|
+
this.listNode = this.find("GoodsList", this.layer.node);
|
|
131
|
+
this.closeButton = this.find("CloseButton", this.layer.node);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private bindEvents() {
|
|
135
|
+
this.cc(this.closeButton, this.onCloseClick);
|
|
136
|
+
this.fw("SHOP_GOODS_CHANGED", this.renderGoods);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private onCloseClick = () => {
|
|
140
|
+
this.close();
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
private renderGoods = (goods: ShopGoods[]) => {
|
|
144
|
+
// 渲染列表或刷新虚拟列表
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Button Event
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
private bindEvents() {
|
|
153
|
+
const buyButton = this.find("BuyButton", this.layer.node);
|
|
154
|
+
this.cc(buyButton, this.onBuyClick, 500);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private onBuyClick = () => {
|
|
158
|
+
this.getLogic<ShopLogic>().buySelectedGoods();
|
|
159
|
+
};
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Framework Event
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
// dispatch
|
|
166
|
+
FW.Entry.evtMgr.dispatch("SHOP_BUY_SUCCESS", goodsId);
|
|
167
|
+
|
|
168
|
+
// listen inside LayerController
|
|
169
|
+
this.fw("SHOP_BUY_SUCCESS", this.onBuySuccess);
|
|
170
|
+
|
|
171
|
+
private onBuySuccess = (goodsId: number) => {
|
|
172
|
+
FW.Log.debug("buy success", goodsId);
|
|
173
|
+
};
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Resource Load
|
|
177
|
+
|
|
178
|
+
```ts
|
|
179
|
+
async loadCoinIcon(sprite: cc.Sprite) {
|
|
180
|
+
const cfg = this.getConfig<ShopAssetConfig>();
|
|
181
|
+
const frame = await this.invoke(
|
|
182
|
+
FW.Entry.resMgr.loadAsset<cc.SpriteFrame>(
|
|
183
|
+
cfg.demandLoad.texture["CoinIcon"] as FW.AssetProperty,
|
|
184
|
+
),
|
|
185
|
+
"loadCoinIcon",
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
if (frame && cc.isValid(sprite)) {
|
|
189
|
+
sprite.spriteFrame = frame;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Object Pool Item
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
export class ShopItem extends FW.Object {
|
|
198
|
+
private label: cc.Label;
|
|
199
|
+
|
|
200
|
+
onInit() {
|
|
201
|
+
this.label = this.node.getChildByName("Name").getComponent(cc.Label);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
onGet(args: any[]) {
|
|
205
|
+
const goods = args[0] as ShopGoods;
|
|
206
|
+
this.label.string = goods.name;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
onPut() {
|
|
210
|
+
this.label.string = "";
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
const cfg = this.getConfig<ShopAssetConfig>();
|
|
217
|
+
const prefab = await FW.Entry.resMgr.loadAsset<cc.Prefab>(
|
|
218
|
+
cfg.preLoad.prefab["ShopItem"] as FW.AssetProperty,
|
|
219
|
+
);
|
|
220
|
+
const pool = await FW.Entry.objectMgr.createObjectPool(prefab, this.listNode, "shop_item");
|
|
221
|
+
const item = pool.get(goods);
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
## Virtual View
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
const list = this.findComponent<FW.VirtualViewComponent>(
|
|
228
|
+
"GoodsScrollView",
|
|
229
|
+
this.layer.node,
|
|
230
|
+
FWVirtualViewComponent,
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
list.onRender = (item: cc.Node, index: number) => {
|
|
234
|
+
const goods = this.getData<ShopData>().getGoods()[index];
|
|
235
|
+
item.getChildByName("Name").getComponent(cc.Label).string = goods.name;
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
list.count = this.getData<ShopData>().getGoods().length;
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Promise With Timeout
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
async waitForAnimation(node: cc.Node) {
|
|
245
|
+
const proxy = FW.Entry.promiseMgr.execute<void>((resolve) => {
|
|
246
|
+
cc.tween(node)
|
|
247
|
+
.to(0.2, { opacity: 255 })
|
|
248
|
+
.call(() => resolve())
|
|
249
|
+
.start();
|
|
250
|
+
}, {
|
|
251
|
+
timeout: 1000,
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
await proxy.promise;
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Sender And Handle
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
export class ShopSender extends FW.Sender {
|
|
262
|
+
requestGoods() {
|
|
263
|
+
const msg = { cmd: "shop.goods", seq: Date.now() };
|
|
264
|
+
this.send(msg);
|
|
265
|
+
return new Promise<any>((resolve) => {
|
|
266
|
+
FW.Entry.evtMgr.registerOnce(`SOCKET_RESPONSE_${msg.seq}`, resolve, this);
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
sendHeart() {
|
|
271
|
+
this.send({ cmd: "heart" });
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async onHeart(msg: any) {
|
|
275
|
+
return msg?.cmd === "heart";
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async onBeforeSendingMessage(msg: any) {
|
|
279
|
+
return JSON.stringify(msg);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
getProtocolKey(msg: any) {
|
|
283
|
+
return msg?.seq ? String(msg.seq) : "";
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
```ts
|
|
289
|
+
export class ShopHandle extends FW.Handle {
|
|
290
|
+
async onBeforeReceivingMessage(event: MessageEvent) {
|
|
291
|
+
return JSON.parse(event.data);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
onMessage(msg: any) {
|
|
295
|
+
if (msg.seq) {
|
|
296
|
+
FW.Entry.evtMgr.dispatch(`SOCKET_RESPONSE_${msg.seq}`, msg);
|
|
297
|
+
}
|
|
298
|
+
FW.Entry.evtMgr.dispatch(`SOCKET_CMD_${msg.cmd}`, msg);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async onHeart(msg: any) {
|
|
302
|
+
return msg?.cmd === "heart";
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
getProtocolKey(msg: any) {
|
|
306
|
+
return msg?.seq ? String(msg.seq) : "";
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
```
|