dzkcc-mflow 0.0.23 → 0.0.25
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 +145 -1622
- package/dist/core/Api.d.ts +14 -6
- package/dist/core/Core.d.ts +37 -6
- package/dist/core/Core.js +30 -1
- package/dist/core/Decorators.d.ts +29 -3
- package/dist/core/Decorators.js +4 -4
- package/dist/mflow-tools.zip +0 -0
- package/package.json +5 -3
- package/scripts/generate-type-map.js +300 -0
package/README.md
CHANGED
|
@@ -1,32 +1,10 @@
|
|
|
1
1
|
# Modular Flow Framework
|
|
2
2
|
|
|
3
3
|
一个专为 Cocos Creator 引擎开发的模块化设计与流程管理框架。
|
|
4
|
-
- github地址:https://github.com/duanzhk/cocos-modular-flow-framework
|
|
5
|
-
|
|
6
|
-
## 📚 目录
|
|
7
|
-
|
|
8
|
-
- [1. 框架概述](#1-框架概述)
|
|
9
|
-
- [2. 快速开始](#2-快速开始)
|
|
10
|
-
- [3. 核心概念](#3-核心概念)
|
|
11
|
-
- [4. 装饰器系统](#4-装饰器系统)
|
|
12
|
-
- [5. UI 系统](#5-ui-系统)
|
|
13
|
-
- [6. 事件系统](#6-事件系统)
|
|
14
|
-
- [7. 资源管理](#7-资源管理)
|
|
15
|
-
- [8. 网络通信](#8-网络通信)
|
|
16
|
-
- [9. 红点系统](#9-红点系统)
|
|
17
|
-
- [10. 开发工具](#10-开发工具)
|
|
18
|
-
- [11. 完整示例](#11-完整示例)
|
|
19
|
-
- [12. 最佳实践](#12-最佳实践)
|
|
20
4
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
## 1. 框架概述
|
|
24
|
-
|
|
25
|
-
### 1.1 简介
|
|
26
|
-
|
|
27
|
-
Modular Flow Framework (MF) 是一个为 Cocos Creator 引擎开发的模块化设计和流程管理框架。该框架旨在提供解耦和依赖注入的能力,帮助开发者构建更加清晰、可维护的游戏项目。
|
|
5
|
+
- **GitHub**: https://github.com/duanzhk/cocos-modular-flow-framework
|
|
28
6
|
|
|
29
|
-
|
|
7
|
+
## 核心特性
|
|
30
8
|
|
|
31
9
|
✨ **模块化设计** - 通过 Manager 和 Model 模式实现业务逻辑的模块化管理
|
|
32
10
|
✨ **依赖注入** - 基于装饰器的自动依赖注入和 Symbol 映射
|
|
@@ -37,43 +15,36 @@ Modular Flow Framework (MF) 是一个为 Cocos Creator 引擎开发的模块化
|
|
|
37
15
|
✨ **HTTP 网络** - 简洁易用的 HTTP 客户端,支持 RESTful API
|
|
38
16
|
✨ **WebSocket 实时通信** - 支持自动重连、心跳检测的 WebSocket 客户端
|
|
39
17
|
✨ **红点系统** - 树形结构的红点提示管理系统
|
|
18
|
+
✨ **类型自动推断** - 无需泛型即可享受完整的类型提示
|
|
40
19
|
✨ **开发工具** - 配套的 Cocos Creator 编辑器插件
|
|
41
20
|
|
|
42
|
-
|
|
21
|
+
## 快速开始
|
|
43
22
|
|
|
44
|
-
|
|
23
|
+
### 安装
|
|
45
24
|
|
|
46
25
|
```bash
|
|
47
26
|
npm i dzkcc-mflow@beta
|
|
48
27
|
```
|
|
49
|
-
|
|
50
|
-
|
|
28
|
+
|
|
29
|
+
### 配置 TypeScript
|
|
30
|
+
|
|
31
|
+
修改项目的 `tsconfig.json`:
|
|
32
|
+
|
|
33
|
+
```json
|
|
51
34
|
{
|
|
52
|
-
/* Base configuration. Do not edit this field. */
|
|
53
35
|
"extends": "./temp/tsconfig.cocos.json",
|
|
54
|
-
|
|
55
|
-
/* Add your custom configuration here. */
|
|
56
36
|
"compilerOptions": {
|
|
57
37
|
"strict": false,
|
|
58
38
|
"paths": {
|
|
59
|
-
"dzkcc-mflow/*": ["./node_modules/dzkcc-mflow/dist/*"]
|
|
39
|
+
"dzkcc-mflow/*": ["./node_modules/dzkcc-mflow/dist/*"]
|
|
60
40
|
}
|
|
61
41
|
}
|
|
62
42
|
}
|
|
63
43
|
```
|
|
64
44
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
---
|
|
68
|
-
|
|
69
|
-
## 2. 快速开始
|
|
70
|
-
|
|
71
|
-
### 2.1 创建 Core 入口
|
|
72
|
-
|
|
73
|
-
在项目中创建一个继承自 `CocosCore` 的类:
|
|
45
|
+
### 创建入口
|
|
74
46
|
|
|
75
47
|
```typescript
|
|
76
|
-
// GameCore.ts
|
|
77
48
|
import { CocosCore } from 'dzkcc-mflow/libs';
|
|
78
49
|
import { _decorator } from 'cc';
|
|
79
50
|
|
|
@@ -85,522 +56,52 @@ export class GameCore extends CocosCore {
|
|
|
85
56
|
}
|
|
86
57
|
```
|
|
87
58
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
1. 在 Cocos Creator 编辑器中打开主场景
|
|
91
|
-
2. 在 Canvas 节点上添加 `GameCore` 组件
|
|
92
|
-
3. 保存场景
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
### 2.3 使用全局对象
|
|
96
|
-
|
|
97
|
-
框架提供了全局对象 `mf`(Modular Flow 的缩写)用于访问框架功能:
|
|
98
|
-
|
|
99
|
-
```typescript
|
|
100
|
-
// 访问 Manager
|
|
101
|
-
const gameManager = mf.core.getManager(ManagerNames.GameManager);
|
|
102
|
-
|
|
103
|
-
// 访问 Model
|
|
104
|
-
const userModel = mf.core.getModel(ModelNames.UserModel);
|
|
105
|
-
|
|
106
|
-
// 打开 UI
|
|
107
|
-
await mf.gui.open(ViewNames.HomeView);
|
|
108
|
-
|
|
109
|
-
// 发送事件
|
|
110
|
-
mf.event.dispatch('gameStart');
|
|
111
|
-
|
|
112
|
-
// 加载资源
|
|
113
|
-
const prefab = await mf.res.loadPrefab('prefabs/player');
|
|
114
|
-
|
|
115
|
-
// HTTP 请求
|
|
116
|
-
const data = await mf.http.get('/api/user/profile');
|
|
117
|
-
|
|
118
|
-
// WebSocket 连接
|
|
119
|
-
mf.socket.connect('wss://game-server.com/ws');
|
|
120
|
-
|
|
121
|
-
// 红点提示
|
|
122
|
-
mf.reddot.setCount('main/bag', 5);
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
## 3. 核心概念
|
|
126
|
-
|
|
127
|
-
### 3.1 架构图
|
|
128
|
-
|
|
129
|
-
```
|
|
130
|
-
┌─────────────────────────────────────────────────┐
|
|
131
|
-
│ 全局对象 mf │
|
|
132
|
-
│ (统一访问入口,暴露所有框架能力) │
|
|
133
|
-
└──────────────────┬──────────────────────────────┘
|
|
134
|
-
│
|
|
135
|
-
┌──────────────┼──────────────┐
|
|
136
|
-
│ │ │
|
|
137
|
-
▼ ▼ ▼
|
|
138
|
-
┌─────────┐ ┌──────────┐ ┌──────────┐
|
|
139
|
-
│ Core │ │ Services │ │ Views │
|
|
140
|
-
│(核心容器)│ │(基础服务) │ │ (UI界面) │
|
|
141
|
-
└─────────┘ └──────────┘ └──────────┘
|
|
142
|
-
│ │ │
|
|
143
|
-
├─ Manager ─┐ ├─ UIManager ├─ BaseView
|
|
144
|
-
│ │ ├─ ResLoader └─ 自动资源管理
|
|
145
|
-
├─ Model ───┤ ├─ EventMgr 自动事件清理
|
|
146
|
-
│ │ ├─ HttpMgr
|
|
147
|
-
└─ Symbol ──┘ ├─ SocketMgr
|
|
148
|
-
映射系统 └─ RedDotMgr
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
### 3.2 Core 核心容器
|
|
152
|
-
|
|
153
|
-
`Core` 是框架的核心,负责管理所有 Manager 和 Model 实例。
|
|
154
|
-
|
|
155
|
-
**核心职责:**
|
|
156
|
-
- 注册和实例化 Manager
|
|
157
|
-
- 注册和实例化 Model
|
|
158
|
-
- 提供统一的访问接口
|
|
159
|
-
- 自动调用初始化方法
|
|
160
|
-
|
|
161
|
-
**使用方式:**
|
|
162
|
-
|
|
163
|
-
```typescript
|
|
164
|
-
// 获取 Manager
|
|
165
|
-
const gameManager = mf.core.getManager(ManagerNames.GameManager);
|
|
166
|
-
|
|
167
|
-
// 获取 Model
|
|
168
|
-
const userModel = mf.core.getModel(ModelNames.UserModel);
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
### 3.3 ServiceLocator 服务定位器
|
|
172
|
-
|
|
173
|
-
`ServiceLocator` 用于管理跨领域的基础服务,实现解耦。
|
|
174
|
-
|
|
175
|
-
**内置服务:**
|
|
176
|
-
- `core` - Core 实例
|
|
177
|
-
- `EventManager` - 事件管理器
|
|
178
|
-
- `UIManager` - UI 管理器
|
|
179
|
-
- `ResLoader` - 资源加载器
|
|
180
|
-
- `HttpManager` - HTTP 管理器
|
|
181
|
-
- `WebSocketManager` - WebSocket 管理器
|
|
182
|
-
- `RedDotManager` - 红点管理器
|
|
183
|
-
|
|
184
|
-
**自定义服务:**
|
|
185
|
-
|
|
186
|
-
```typescript
|
|
187
|
-
import { ServiceLocator } from 'dzkcc-mflow/core';
|
|
188
|
-
|
|
189
|
-
// 注册服务
|
|
190
|
-
ServiceLocator.regService('MyService', new MyService());
|
|
191
|
-
|
|
192
|
-
// 获取服务
|
|
193
|
-
const myService = ServiceLocator.getService<MyService>('MyService');
|
|
194
|
-
|
|
195
|
-
// 移除服务
|
|
196
|
-
ServiceLocator.remove('MyService');
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
### 3.4 Manager 管理器
|
|
200
|
-
|
|
201
|
-
Manager 负责处理特定领域的业务逻辑。
|
|
202
|
-
|
|
203
|
-
**基类:** `AbstractManager`
|
|
204
|
-
|
|
205
|
-
**生命周期:**
|
|
206
|
-
1. `initialize()` - 在注册时被调用
|
|
207
|
-
2. `dispose()` - 在销毁时被调用
|
|
208
|
-
|
|
209
|
-
**内置能力:**
|
|
210
|
-
- 获取 Model:`this.getModel(modelSymbol)`
|
|
211
|
-
- 获取事件管理器:`this.getEventManager()`
|
|
212
|
-
- 获取 HTTP 管理器:`this.getHttpManager()`
|
|
213
|
-
- 获取 WebSocket 管理器:`this.getWebSocketManager()`
|
|
214
|
-
|
|
215
|
-
### 3.5 Model 数据模型
|
|
216
|
-
|
|
217
|
-
Model 用于数据管理,遵循单一职责原则。
|
|
218
|
-
|
|
219
|
-
**接口:** `IModel`
|
|
220
|
-
|
|
221
|
-
**生命周期:**
|
|
222
|
-
- `initialize()` - 在注册时被调用
|
|
223
|
-
|
|
224
|
-
### 3.6 View 视图
|
|
225
|
-
|
|
226
|
-
View 是 UI 界面的基类,提供完整的生命周期管理。
|
|
227
|
-
|
|
228
|
-
**基类:** `BaseView`
|
|
229
|
-
|
|
230
|
-
**生命周期:**
|
|
231
|
-
1. `onEnter(args?)` - 界面打开时调用
|
|
232
|
-
2. `onPause()` - 界面被覆盖时调用(栈模式)
|
|
233
|
-
3. `onResume()` - 界面恢复显示时调用(栈模式)
|
|
234
|
-
4. `onExit()` - 界面关闭时调用(自动清理事件)
|
|
235
|
-
5. `onDestroy()` - 界面销毁时调用(自动释放资源)
|
|
59
|
+
📖 **详细指南**: [快速开始](./docs/QUICK_START.md)
|
|
236
60
|
|
|
237
|
-
|
|
238
|
-
- 自动事件管理:通过 `this.event` 监听的事件会自动清理
|
|
239
|
-
- 自动资源管理:通过 `this.res` 加载的资源会自动释放
|
|
240
|
-
- 获取 Manager:`this.getManager(managerSymbol)`
|
|
241
|
-
- 获取 Model:`this.getModel(modelSymbol)`
|
|
61
|
+
## 核心功能模块
|
|
242
62
|
|
|
243
|
-
###
|
|
63
|
+
### 1. 核心概念
|
|
244
64
|
|
|
245
|
-
|
|
65
|
+
了解框架的基础架构、Core 容器、ServiceLocator、Manager、Model、View 和 Symbol 映射系统。
|
|
246
66
|
|
|
247
|
-
|
|
248
|
-
- `ModelNames` - Model 的 Symbol 映射
|
|
249
|
-
- `ManagerNames` - Manager 的 Symbol 映射
|
|
250
|
-
- `ViewNames` - View 的 Symbol 映射
|
|
67
|
+
📖 **查看文档**: [核心概念](./docs/CORE_CONCEPTS.md)
|
|
251
68
|
|
|
252
|
-
|
|
253
|
-
- ✅ IDE 代码补全
|
|
254
|
-
- ✅ 类型安全
|
|
255
|
-
- ✅ 避免字符串拼写错误
|
|
256
|
-
- ✅ 便于重构
|
|
69
|
+
### 2. 装饰器系统
|
|
257
70
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
框架提供了三个核心装饰器,用于注册 Manager、Model 和 View。
|
|
261
|
-
|
|
262
|
-
### 4.1 @manager() - Manager 装饰器
|
|
263
|
-
|
|
264
|
-
用于注册 Manager 到全局注册表。
|
|
265
|
-
|
|
266
|
-
```typescript
|
|
267
|
-
import { manager, AbstractManager, ManagerNames } from 'dzkcc-mflow/core';
|
|
268
|
-
|
|
269
|
-
@manager('Game') // 指定名称为 'Game'
|
|
270
|
-
export class GameManager extends AbstractManager {
|
|
271
|
-
private score: number = 0;
|
|
272
|
-
|
|
273
|
-
initialize(): void {
|
|
274
|
-
console.log('GameManager 初始化');
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
dispose(): void {
|
|
278
|
-
console.log('GameManager 销毁');
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
addScore(value: number): void {
|
|
282
|
-
this.score += value;
|
|
283
|
-
this.getEventManager().dispatch('scoreChanged', this.score);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// 使用
|
|
288
|
-
const gameManager = mf.core.getManager(ManagerNames.Game);
|
|
289
|
-
gameManager.addScore(10);
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
### 4.2 @model() - Model 装饰器
|
|
293
|
-
|
|
294
|
-
用于注册 Model 到全局注册表。
|
|
71
|
+
使用 `@manager()`、`@model()`、`@view()` 装饰器注册组件,实现自动依赖注入。
|
|
295
72
|
|
|
296
73
|
```typescript
|
|
297
|
-
import { model, IModel, ModelNames } from 'dzkcc-mflow/core';
|
|
298
|
-
|
|
299
|
-
@model('User') // 指定名称为 'User'
|
|
300
|
-
export class UserModel implements IModel {
|
|
301
|
-
private _playerName: string = '';
|
|
302
|
-
private _level: number = 1;
|
|
303
|
-
|
|
304
|
-
initialize(): void {
|
|
305
|
-
console.log('UserModel 初始化');
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
get playerName(): string {
|
|
309
|
-
return this._playerName;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
set playerName(name: string) {
|
|
313
|
-
this._playerName = name;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
get level(): number {
|
|
317
|
-
return this._level;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
levelUp(): void {
|
|
321
|
-
this._level++;
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
// 使用
|
|
326
|
-
const userModel = mf.core.getModel(ModelNames.User);
|
|
327
|
-
userModel.playerName = 'Alice';
|
|
328
|
-
userModel.levelUp();
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
### 4.3 @view() - View 装饰器
|
|
332
|
-
|
|
333
|
-
用于注册 View 到全局注册表,配合 `ViewNames` 使用。
|
|
334
|
-
|
|
335
|
-
```typescript
|
|
336
|
-
import { view, ViewNames } from 'dzkcc-mflow/core';
|
|
337
|
-
import { BaseView } from 'dzkcc-mflow/libs';
|
|
338
|
-
import { _decorator, Button, Label } from 'cc';
|
|
339
|
-
|
|
340
|
-
const { ccclass, property } = _decorator;
|
|
341
|
-
|
|
342
|
-
@view('Home') // 注册为 'Home'
|
|
343
|
-
@ccclass('HomeView')
|
|
344
|
-
export class HomeView extends BaseView {
|
|
345
|
-
@property(Label)
|
|
346
|
-
titleLabel: Label = null!;
|
|
347
|
-
|
|
348
|
-
@property(Button)
|
|
349
|
-
startButton: Button = null!;
|
|
350
|
-
|
|
351
|
-
onEnter(args?: any): void {
|
|
352
|
-
console.log('HomeView 打开', args);
|
|
353
|
-
this.startButton.node.on('click', this.onStartClick, this);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
onExit(): void {
|
|
357
|
-
// 自动清理事件监听
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
onPause(): void {
|
|
361
|
-
console.log('HomeView 暂停');
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
onResume(): void {
|
|
365
|
-
console.log('HomeView 恢复');
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
private onStartClick(): void {
|
|
369
|
-
mf.gui.open(ViewNames.Game);
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
// 使用 - 通过 Symbol 打开视图
|
|
374
|
-
await mf.gui.open(ViewNames.Home, { userId: 123 });
|
|
375
|
-
|
|
376
|
-
// 关闭视图
|
|
377
|
-
mf.gui.close(ViewNames.Home); // 关闭但保留缓存
|
|
378
|
-
mf.gui.close(ViewNames.Home, true); // 关闭并销毁
|
|
379
|
-
```
|
|
380
|
-
|
|
381
|
-
### 4.4 装饰器参数
|
|
382
|
-
|
|
383
|
-
所有装饰器都支持可选的 `name` 参数:
|
|
384
|
-
|
|
385
|
-
```typescript
|
|
386
|
-
// 指定名称
|
|
387
74
|
@manager('Game')
|
|
388
|
-
|
|
389
|
-
@view('Home')
|
|
390
|
-
|
|
391
|
-
// 不指定名称时,自动使用类名
|
|
392
|
-
@manager() // 使用 'GameManager'
|
|
393
|
-
@model() // 使用 'UserModel'
|
|
394
|
-
@view() // 使用 'HomeView'
|
|
395
|
-
```
|
|
396
|
-
|
|
397
|
-
### 4.5 Names 对象代码补全
|
|
75
|
+
export class GameManager extends AbstractManager {}
|
|
398
76
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
```typescript
|
|
402
|
-
// ManagerNames 自动包含所有注册的 Manager
|
|
403
|
-
ManagerNames.Game
|
|
404
|
-
ManagerNames.Player
|
|
405
|
-
ManagerNames.Audio
|
|
406
|
-
|
|
407
|
-
// ModelNames 自动包含所有注册的 Model
|
|
408
|
-
ModelNames.User
|
|
409
|
-
ModelNames.Inventory
|
|
410
|
-
ModelNames.Config
|
|
411
|
-
|
|
412
|
-
// ViewNames 自动包含所有注册的 View
|
|
413
|
-
ViewNames.Home
|
|
414
|
-
ViewNames.Game
|
|
415
|
-
ViewNames.Settings
|
|
416
|
-
```
|
|
417
|
-
|
|
418
|
-
---
|
|
419
|
-
|
|
420
|
-
## 5. UI 系统
|
|
421
|
-
|
|
422
|
-
### 5.1 BaseView 基础视图
|
|
423
|
-
|
|
424
|
-
所有 UI 界面都应该继承 `BaseView` 类。
|
|
425
|
-
|
|
426
|
-
**核心特性:**
|
|
427
|
-
- ✅ 自动事件管理 - `this.event` 监听的事件会自动清理
|
|
428
|
-
- ✅ 自动资源管理 - `this.res` 加载的资源会自动释放
|
|
429
|
-
- ✅ 生命周期方法 - 完整的生命周期钩子
|
|
430
|
-
- ✅ 内置访问能力 - 可直接获取 Manager 和 Model
|
|
431
|
-
|
|
432
|
-
**基本用法:**
|
|
77
|
+
@model('User')
|
|
78
|
+
export class UserModel implements IModel {}
|
|
433
79
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
import { _decorator, Sprite, Label } from 'cc';
|
|
438
|
-
|
|
439
|
-
const { ccclass, property } = _decorator;
|
|
440
|
-
|
|
441
|
-
@view('Game')
|
|
442
|
-
@ccclass('GameView')
|
|
443
|
-
export class GameView extends BaseView {
|
|
444
|
-
@property(Label)
|
|
445
|
-
scoreLabel: Label = null!;
|
|
446
|
-
|
|
447
|
-
@property(Sprite)
|
|
448
|
-
playerSprite: Sprite = null!;
|
|
449
|
-
|
|
450
|
-
onEnter(args?: any): void {
|
|
451
|
-
// 监听事件(自动清理)
|
|
452
|
-
this.event.on('scoreChanged', this.onScoreChanged, this);
|
|
453
|
-
|
|
454
|
-
// 加载资源(自动释放)
|
|
455
|
-
this.res.loadSpriteFrame(this.playerSprite, 'textures/player');
|
|
456
|
-
|
|
457
|
-
// 获取 Manager
|
|
458
|
-
const gameManager = this.getManager(ManagerNames.Game);
|
|
459
|
-
|
|
460
|
-
// 获取 Model
|
|
461
|
-
const userModel = this.getModel(ModelNames.User);
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
onExit(): void {
|
|
465
|
-
// 事件监听会自动清理,无需手动 off
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
onPause(): void {
|
|
469
|
-
// 界面被其他界面覆盖时调用
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
onResume(): void {
|
|
473
|
-
// 界面从暂停状态恢复时调用
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
private onScoreChanged(score: number): void {
|
|
477
|
-
this.scoreLabel.string = `分数: ${score}`;
|
|
478
|
-
}
|
|
479
|
-
}
|
|
80
|
+
@view('Home')
|
|
81
|
+
@ccclass('HomeView')
|
|
82
|
+
export class HomeView extends BaseView {}
|
|
480
83
|
```
|
|
481
84
|
|
|
482
|
-
|
|
85
|
+
📖 **查看文档**: [装饰器系统](./docs/DECORATORS.md)
|
|
483
86
|
|
|
484
|
-
|
|
87
|
+
### 3. UI 系统
|
|
485
88
|
|
|
486
|
-
|
|
89
|
+
完整的 UI 界面管理,支持生命周期、视图栈、自动事件清理和资源释放。
|
|
487
90
|
|
|
488
91
|
```typescript
|
|
489
|
-
import { ViewNames } from 'dzkcc-mflow/core';
|
|
490
|
-
|
|
491
92
|
// 打开界面
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
// 打开界面并传参
|
|
495
|
-
await mf.gui.open(ViewNames.Game, { level: 1, difficulty: 'hard' });
|
|
496
|
-
|
|
497
|
-
// 关闭界面(保留缓存)
|
|
498
|
-
mf.gui.close(ViewNames.Home);
|
|
93
|
+
await mf.gui.open(ViewNames.Home, { data: 'value' });
|
|
499
94
|
|
|
500
|
-
//
|
|
501
|
-
mf.gui.
|
|
502
|
-
|
|
503
|
-
// 通过视图实例关闭
|
|
504
|
-
const view = await mf.gui.open(ViewNames.Settings);
|
|
505
|
-
mf.gui.close(view);
|
|
95
|
+
// 视图栈管理
|
|
96
|
+
await mf.gui.openAndPush(ViewNames.Level1, 'game');
|
|
97
|
+
mf.gui.closeAndPop('game');
|
|
506
98
|
```
|
|
507
99
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
支持分组的视图栈,适用于关卡、向导等场景。
|
|
511
|
-
使用分组功能,还可以实现诸如游戏启动后,需要优先弹出一堆界面,比如各种活动的展示dialog、各种签到UI等等。把这些UI都归类一个group中,那么就可以做到,关闭一个UI后会再弹出下一个,直到所有的弹窗都关闭。同时在加入group的时候,还可以控制各个UI的先后顺序。
|
|
512
|
-
|
|
513
|
-
```typescript
|
|
514
|
-
import { ViewNames } from 'dzkcc-mflow/core';
|
|
515
|
-
|
|
516
|
-
// 打开视图并入栈
|
|
517
|
-
await mf.gui.openAndPush(ViewNames.Level1, 'game', { levelId: 1 });
|
|
518
|
-
await mf.gui.openAndPush(ViewNames.Level2, 'game', { levelId: 2 });
|
|
519
|
-
|
|
520
|
-
// 关闭栈顶视图并弹出(会自动恢复上一个视图)
|
|
521
|
-
mf.gui.closeAndPop('game'); // Level2 关闭,Level1 恢复
|
|
100
|
+
📖 **查看文档**: [UI 系统](./docs/UI_SYSTEM.md)
|
|
522
101
|
|
|
523
|
-
|
|
524
|
-
mf.gui.clearStack('game'); // 所有关卡视图关闭
|
|
102
|
+
### 4. 事件系统
|
|
525
103
|
|
|
526
|
-
|
|
527
|
-
mf.gui.clearStack('game', true);
|
|
528
|
-
|
|
529
|
-
// 获取栈顶视图
|
|
530
|
-
const topView = mf.gui.getTopView();
|
|
531
|
-
```
|
|
532
|
-
|
|
533
|
-
### 5.4 视图生命周期详解
|
|
534
|
-
|
|
535
|
-
```typescript
|
|
536
|
-
@view('Example')
|
|
537
|
-
@ccclass('ExampleView')
|
|
538
|
-
export class ExampleView extends BaseView {
|
|
539
|
-
// 1. 界面打开时调用
|
|
540
|
-
onEnter(args?: any): void {
|
|
541
|
-
console.log('界面打开', args);
|
|
542
|
-
// 注册事件监听
|
|
543
|
-
// 初始化界面数据
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
// 2. 界面被其他界面覆盖时调用(栈模式)
|
|
547
|
-
onPause(): void {
|
|
548
|
-
console.log('界面暂停');
|
|
549
|
-
// 暂停动画
|
|
550
|
-
// 暂停计时器
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
// 3. 界面从暂停状态恢复时调用(栈模式)
|
|
554
|
-
onResume(): void {
|
|
555
|
-
console.log('界面恢复');
|
|
556
|
-
// 恢复动画
|
|
557
|
-
// 恢复计时器
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
// 4. 界面关闭时调用
|
|
561
|
-
onExit(): void {
|
|
562
|
-
console.log('界面关闭');
|
|
563
|
-
// 自动清理通过 this.event 注册的事件
|
|
564
|
-
// 可以在这里做额外的清理工作
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
// 5. 界面销毁时调用(框架自动调用)
|
|
568
|
-
protected onDestroy(): void {
|
|
569
|
-
console.log('界面销毁');
|
|
570
|
-
// 自动释放通过 this.res 加载的资源
|
|
571
|
-
// 可以在这里做额外的清理工作
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
```
|
|
575
|
-
|
|
576
|
-
### 5.5 Prefab 路径配置
|
|
577
|
-
|
|
578
|
-
视图需要配置 Prefab 路径,框架提供了开发工具自动生成。
|
|
579
|
-
|
|
580
|
-
**手动配置方式:**
|
|
581
|
-
|
|
582
|
-
```typescript
|
|
583
|
-
@view('Home')
|
|
584
|
-
@ccclass('HomeView')
|
|
585
|
-
export class HomeView extends BaseView {
|
|
586
|
-
/** @internal */
|
|
587
|
-
private static readonly __path__: string = 'ui/home'; // Prefab 路径
|
|
588
|
-
|
|
589
|
-
onEnter(): void {}
|
|
590
|
-
onPause(): void {}
|
|
591
|
-
onResume(): void {}
|
|
592
|
-
}
|
|
593
|
-
```
|
|
594
|
-
|
|
595
|
-
**推荐:使用开发工具自动生成**(见第 10 章)
|
|
596
|
-
|
|
597
|
-
---
|
|
598
|
-
|
|
599
|
-
## 6. 事件系统
|
|
600
|
-
|
|
601
|
-
框架提供了强大的事件广播和监听机制,基于 `Broadcaster` 实现。
|
|
602
|
-
|
|
603
|
-
### 6.1 基本用法
|
|
104
|
+
强大的事件广播和监听机制,支持粘性事件和类型安全。
|
|
604
105
|
|
|
605
106
|
```typescript
|
|
606
107
|
// 监听事件
|
|
@@ -610,1168 +111,190 @@ mf.event.on('gameStart', (data) => {
|
|
|
610
111
|
|
|
611
112
|
// 派发事件
|
|
612
113
|
mf.event.dispatch('gameStart', { level: 1 });
|
|
613
|
-
|
|
614
|
-
// 一次性监听
|
|
615
|
-
mf.event.once('gameOver', (score) => {
|
|
616
|
-
console.log('游戏结束,分数:', score);
|
|
617
|
-
});
|
|
618
|
-
|
|
619
|
-
// 移除监听
|
|
620
|
-
const handler = (data) => console.log(data);
|
|
621
|
-
mf.event.on('test', handler);
|
|
622
|
-
mf.event.off('test', handler);
|
|
623
|
-
|
|
624
|
-
// 移除所有监听
|
|
625
|
-
mf.event.offAll('test');
|
|
626
|
-
```
|
|
627
|
-
|
|
628
|
-
### 6.2 粘性事件
|
|
629
|
-
|
|
630
|
-
粘性事件会保存最后一次派发的数据,新的监听者会立即收到。
|
|
631
|
-
|
|
632
|
-
```typescript
|
|
633
|
-
// 派发粘性事件
|
|
634
|
-
mf.event.dispatchSticky('userLogin', { userId: 123, name: 'Alice' });
|
|
635
|
-
|
|
636
|
-
// 即使在派发之后才监听,也能立即收到数据
|
|
637
|
-
setTimeout(() => {
|
|
638
|
-
mf.event.on('userLogin', (userData) => {
|
|
639
|
-
console.log('用户登录信息:', userData); // 立即触发
|
|
640
|
-
});
|
|
641
|
-
}, 1000);
|
|
642
|
-
|
|
643
|
-
// 移除粘性事件
|
|
644
|
-
mf.event.removeStickyBroadcast('userLogin');
|
|
645
|
-
```
|
|
646
|
-
|
|
647
|
-
### 6.3 在 View 中使用事件
|
|
648
|
-
|
|
649
|
-
`BaseView` 提供了自动清理的事件监听:
|
|
650
|
-
|
|
651
|
-
```typescript
|
|
652
|
-
@view('Game')
|
|
653
|
-
@ccclass('GameView')
|
|
654
|
-
export class GameView extends BaseView {
|
|
655
|
-
onEnter(): void {
|
|
656
|
-
// 使用 this.event 监听,会在 onExit 时自动清理
|
|
657
|
-
this.event.on('scoreChanged', this.updateScore, this);
|
|
658
|
-
this.event.on('levelUp', this.onLevelUp, this);
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
onExit(): void {
|
|
662
|
-
// 自动清理所有通过 this.event 监听的事件
|
|
663
|
-
// 无需手动 off
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
private updateScore(score: number): void {
|
|
667
|
-
console.log('分数更新:', score);
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
private onLevelUp(level: number): void {
|
|
671
|
-
console.log('等级提升:', level);
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
```
|
|
675
|
-
|
|
676
|
-
### 6.4 在 Manager 中使用事件
|
|
677
|
-
|
|
678
|
-
```typescript
|
|
679
|
-
@manager('Game')
|
|
680
|
-
export class GameManager extends AbstractManager {
|
|
681
|
-
private score: number = 0;
|
|
682
|
-
|
|
683
|
-
initialize(): void {
|
|
684
|
-
// 在 Manager 中获取事件管理器
|
|
685
|
-
const eventMgr = this.getEventManager();
|
|
686
|
-
|
|
687
|
-
eventMgr.on('enemyKilled', this.onEnemyKilled, this);
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
addScore(value: number): void {
|
|
691
|
-
this.score += value;
|
|
692
|
-
// 派发事件
|
|
693
|
-
this.getEventManager().dispatch('scoreChanged', this.score);
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
private onEnemyKilled(enemyData: any): void {
|
|
697
|
-
this.addScore(enemyData.reward);
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
dispose(): void {
|
|
701
|
-
// Manager 销毁时清理事件监听
|
|
702
|
-
this.getEventManager().offAll(undefined, this);
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
```
|
|
706
|
-
|
|
707
|
-
### 6.5 带回调的事件
|
|
708
|
-
|
|
709
|
-
事件支持回调机制,用于双向通信:
|
|
710
|
-
|
|
711
|
-
```typescript
|
|
712
|
-
// 监听事件并提供回调
|
|
713
|
-
mf.event.on('requestData', (params, callback) => {
|
|
714
|
-
const result = fetchData(params);
|
|
715
|
-
callback?.(result); // 回调返回结果
|
|
716
|
-
});
|
|
717
|
-
|
|
718
|
-
// 派发事件并接收回调
|
|
719
|
-
mf.event.dispatch('requestData', { id: 123 }, (result) => {
|
|
720
|
-
console.log('收到结果:', result);
|
|
721
|
-
});
|
|
722
|
-
```
|
|
723
|
-
|
|
724
|
-
### 6.6 类型安全的事件(可选)
|
|
725
|
-
|
|
726
|
-
可以扩展 `IEventMsgKey` 接口实现类型安全:
|
|
727
|
-
|
|
728
|
-
```typescript
|
|
729
|
-
// 定义事件消息键类型
|
|
730
|
-
declare module 'dzkcc-mflow/core' {
|
|
731
|
-
interface IEventMsgKey {
|
|
732
|
-
'gameStart': { level: number };
|
|
733
|
-
'scoreChanged': number;
|
|
734
|
-
'userLogin': { userId: number; name: string };
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
// 现在事件会有类型提示
|
|
739
|
-
mf.event.dispatch('scoreChanged', 100); // ✅ 正确
|
|
740
|
-
mf.event.dispatch('scoreChanged', 'abc'); // ❌ 类型错误
|
|
741
|
-
```
|
|
742
|
-
|
|
743
|
-
---
|
|
744
|
-
|
|
745
|
-
## 7. 资源管理
|
|
746
|
-
|
|
747
|
-
框架提供了统一的资源加载和释放管理,通过 `mf.res` 访问。
|
|
748
|
-
|
|
749
|
-
### 7.1 基本用法
|
|
750
|
-
|
|
751
|
-
```typescript
|
|
752
|
-
import { Prefab, SpriteFrame } from 'cc';
|
|
753
|
-
|
|
754
|
-
// 加载 Prefab
|
|
755
|
-
const prefab = await mf.res.loadPrefab('prefabs/enemy');
|
|
756
|
-
const node = instantiate(prefab);
|
|
757
|
-
|
|
758
|
-
// 加载 SpriteFrame(自动设置到 Sprite 组件)
|
|
759
|
-
const sprite = this.node.getComponent(Sprite)!;
|
|
760
|
-
await mf.res.loadSpriteFrame(sprite, 'textures/player');
|
|
761
|
-
|
|
762
|
-
// 加载 Spine 动画(自动设置到 Skeleton 组件)
|
|
763
|
-
const skeleton = this.node.getComponent(sp.Skeleton)!;
|
|
764
|
-
await mf.res.loadSpine(skeleton, 'spine/hero');
|
|
765
|
-
|
|
766
|
-
// 加载通用资源
|
|
767
|
-
const asset = await mf.res.loadAsset('path/to/asset', AssetType);
|
|
768
114
|
```
|
|
769
115
|
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
```typescript
|
|
773
|
-
// 通过路径释放
|
|
774
|
-
mf.res.release('prefabs/enemy', Prefab);
|
|
775
|
-
|
|
776
|
-
// 通过资源对象释放
|
|
777
|
-
mf.res.release(asset);
|
|
778
|
-
|
|
779
|
-
// 强制释放(立即销毁,不管引用计数)
|
|
780
|
-
mf.res.release(asset, true);
|
|
781
|
-
```
|
|
116
|
+
📖 **查看文档**: [事件系统](./docs/EVENT_SYSTEM.md)
|
|
782
117
|
|
|
783
|
-
###
|
|
118
|
+
### 5. 资源管理
|
|
784
119
|
|
|
785
|
-
|
|
120
|
+
统一的资源加载和释放管理,支持自动资源管理。
|
|
786
121
|
|
|
787
122
|
```typescript
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
export class GameView extends BaseView {
|
|
791
|
-
@property(Sprite)
|
|
792
|
-
avatarSprite: Sprite = null!;
|
|
793
|
-
|
|
794
|
-
onEnter(): void {
|
|
795
|
-
// 使用 this.res 加载,会在界面销毁时自动释放
|
|
796
|
-
this.res.loadSpriteFrame(this.avatarSprite, 'textures/avatar');
|
|
797
|
-
|
|
798
|
-
// 加载多个资源
|
|
799
|
-
this.res.loadPrefab('prefabs/item1');
|
|
800
|
-
this.res.loadPrefab('prefabs/item2');
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
// 界面销毁时,所有通过 this.res 加载的资源会自动释放
|
|
804
|
-
}
|
|
805
|
-
```
|
|
806
|
-
|
|
807
|
-
### 7.4 Bundle 资源包
|
|
808
|
-
|
|
809
|
-
支持从不同的 Bundle 加载资源:
|
|
810
|
-
|
|
811
|
-
```typescript
|
|
812
|
-
// 从 resources Bundle 加载(默认)
|
|
813
|
-
await mf.res.loadPrefab('prefabs/ui');
|
|
814
|
-
|
|
815
|
-
// 从自定义 Bundle 加载
|
|
816
|
-
await mf.res.loadPrefab('prefabs/level1', 'game-bundle');
|
|
817
|
-
|
|
818
|
-
// 加载 SpriteFrame 从自定义 Bundle
|
|
819
|
-
await mf.res.loadSpriteFrame(sprite, 'textures/bg', 'ui-bundle');
|
|
820
|
-
```
|
|
821
|
-
|
|
822
|
-
### 7.5 资源引用计数
|
|
823
|
-
|
|
824
|
-
框架使用 Cocos Creator 的引用计数系统:
|
|
825
|
-
|
|
826
|
-
- `loadAsset()` 会自动 `addRef()`
|
|
827
|
-
- `release()` 会自动 `decRef()`
|
|
828
|
-
- 引用计数为 0 时自动销毁资源
|
|
829
|
-
|
|
830
|
-
```typescript
|
|
831
|
-
// 多次加载同一资源,共享同一实例,引用计数累加
|
|
832
|
-
const asset1 = await mf.res.loadPrefab('prefabs/common'); // refCount = 1
|
|
833
|
-
const asset2 = await mf.res.loadPrefab('prefabs/common'); // refCount = 2
|
|
123
|
+
// 加载资源
|
|
124
|
+
const prefab = await mf.res.loadPrefab('prefabs/player');
|
|
834
125
|
|
|
835
|
-
//
|
|
836
|
-
|
|
837
|
-
|
|
126
|
+
// View 中自动管理
|
|
127
|
+
this.res.loadSpriteFrame(this.sprite, 'textures/icon');
|
|
128
|
+
// onDestroy 时自动释放
|
|
838
129
|
```
|
|
839
130
|
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
### 8.1 HTTP 请求
|
|
843
|
-
|
|
844
|
-
框架提供了简洁易用的 HTTP 客户端,通过 `mf.http` 访问。
|
|
131
|
+
📖 **查看文档**: [资源管理](./docs/RESOURCE_MANAGEMENT.md)
|
|
845
132
|
|
|
846
|
-
|
|
133
|
+
### 6. 网络通信
|
|
847
134
|
|
|
848
|
-
|
|
849
|
-
// GET 请求
|
|
850
|
-
const userData = await mf.http.get('/api/users/123');
|
|
851
|
-
|
|
852
|
-
// GET 请求带参数
|
|
853
|
-
const list = await mf.http.get('/api/users', { page: 1, size: 20 });
|
|
854
|
-
|
|
855
|
-
// POST 请求
|
|
856
|
-
const newUser = await mf.http.post('/api/users', {
|
|
857
|
-
name: 'Alice',
|
|
858
|
-
email: 'alice@example.com'
|
|
859
|
-
});
|
|
860
|
-
|
|
861
|
-
// PUT 请求
|
|
862
|
-
const updated = await mf.http.put('/api/users/123', {
|
|
863
|
-
name: 'Alice Updated'
|
|
864
|
-
});
|
|
865
|
-
|
|
866
|
-
// DELETE 请求
|
|
867
|
-
await mf.http.delete('/api/users/123');
|
|
868
|
-
|
|
869
|
-
// 自定义请求
|
|
870
|
-
const result = await mf.http.request({
|
|
871
|
-
url: '/api/upload',
|
|
872
|
-
method: 'POST',
|
|
873
|
-
data: formData,
|
|
874
|
-
headers: {
|
|
875
|
-
'Authorization': 'Bearer token123'
|
|
876
|
-
},
|
|
877
|
-
timeout: 30000
|
|
878
|
-
});
|
|
879
|
-
```
|
|
880
|
-
|
|
881
|
-
**在 Manager 中使用:**
|
|
135
|
+
简洁易用的 HTTP 和 WebSocket 客户端。
|
|
882
136
|
|
|
883
137
|
```typescript
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
async login(username: string, password: string): Promise<any> {
|
|
889
|
-
try {
|
|
890
|
-
// 通过 getHttpManager() 获取 HTTP 管理器
|
|
891
|
-
const result = await this.getHttpManager().post('/api/auth/login', {
|
|
892
|
-
username,
|
|
893
|
-
password
|
|
894
|
-
});
|
|
895
|
-
|
|
896
|
-
// 登录成功,派发事件
|
|
897
|
-
this.getEventManager().dispatch('userLogin', result);
|
|
898
|
-
return result;
|
|
899
|
-
} catch (error) {
|
|
900
|
-
console.error('登录失败:', error);
|
|
901
|
-
throw error;
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
async getUserProfile(userId: number): Promise<any> {
|
|
906
|
-
const token = this.getAuthToken();
|
|
907
|
-
return await this.getHttpManager().get(`/api/users/${userId}`, {}, {
|
|
908
|
-
'Authorization': `Bearer ${token}`
|
|
909
|
-
});
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
private getAuthToken(): string {
|
|
913
|
-
// 从本地存储获取 token
|
|
914
|
-
return localStorage.getItem('token') || '';
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
```
|
|
918
|
-
|
|
919
|
-
### 8.2 WebSocket 实时通信
|
|
920
|
-
|
|
921
|
-
框架提供了功能完整的 WebSocket 客户端,支持自动重连、心跳检测等特性。
|
|
922
|
-
|
|
923
|
-
**基本用法:**
|
|
138
|
+
// HTTP 请求
|
|
139
|
+
const data = await mf.http.get('/api/user/profile');
|
|
140
|
+
await mf.http.post('/api/login', { username, password });
|
|
924
141
|
|
|
925
|
-
|
|
926
|
-
// 连接服务器
|
|
142
|
+
// WebSocket 连接
|
|
927
143
|
mf.socket.connect('wss://game-server.com/ws');
|
|
928
|
-
|
|
929
|
-
// 配置连接参数
|
|
930
|
-
mf.socket.configure({
|
|
931
|
-
reconnect: true, // 启用自动重连
|
|
932
|
-
reconnectInterval: 3000, // 重连间隔 3 秒
|
|
933
|
-
reconnectAttempts: 5, // 最多重连 5 次
|
|
934
|
-
heartbeat: true, // 启用心跳
|
|
935
|
-
heartbeatInterval: 30000, // 心跳间隔 30 秒
|
|
936
|
-
heartbeatMessage: 'ping' // 心跳消息
|
|
937
|
-
});
|
|
938
|
-
|
|
939
|
-
// 监听事件
|
|
940
|
-
mf.socket.on('open', (event) => {
|
|
941
|
-
console.log('WebSocket 连接成功');
|
|
942
|
-
});
|
|
943
|
-
|
|
944
|
-
mf.socket.on('message', (event) => {
|
|
945
|
-
const data = JSON.parse(event.data);
|
|
946
|
-
console.log('收到消息:', data);
|
|
947
|
-
});
|
|
948
|
-
|
|
949
|
-
mf.socket.on('error', (event) => {
|
|
950
|
-
console.error('WebSocket 错误:', event);
|
|
951
|
-
});
|
|
952
|
-
|
|
953
|
-
mf.socket.on('close', (event) => {
|
|
954
|
-
console.log('WebSocket 连接关闭');
|
|
955
|
-
});
|
|
956
|
-
|
|
957
|
-
// 发送消息
|
|
958
144
|
mf.socket.send({ type: 'move', x: 100, y: 200 });
|
|
959
|
-
|
|
960
|
-
// 检查连接状态
|
|
961
|
-
if (mf.socket.isConnected()) {
|
|
962
|
-
console.log('已连接');
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
// 断开连接
|
|
966
|
-
mf.socket.disconnect();
|
|
967
145
|
```
|
|
968
146
|
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
```typescript
|
|
972
|
-
// 1. JSON 对象(推荐)
|
|
973
|
-
mf.socket.send({ type: 'chat', message: 'Hello' });
|
|
974
|
-
|
|
975
|
-
// 2. 字符串
|
|
976
|
-
mf.socket.send('ping');
|
|
977
|
-
|
|
978
|
-
// 3. 二进制数据(ArrayBuffer)
|
|
979
|
-
const buffer = new ArrayBuffer(8);
|
|
980
|
-
const view = new DataView(buffer);
|
|
981
|
-
view.setInt32(0, 12345);
|
|
982
|
-
mf.socket.send(buffer);
|
|
147
|
+
📖 **查看文档**: [网络通信](./docs/NETWORK.md)
|
|
983
148
|
|
|
984
|
-
|
|
985
|
-
const blob = new Blob(['data'], { type: 'text/plain' });
|
|
986
|
-
mf.socket.send(blob);
|
|
987
|
-
```
|
|
149
|
+
### 7. 红点系统
|
|
988
150
|
|
|
989
|
-
|
|
151
|
+
树形结构的红点提示管理,支持自动累加和变化监听。
|
|
990
152
|
|
|
991
153
|
```typescript
|
|
992
|
-
|
|
993
|
-
export class NetworkManager extends AbstractManager {
|
|
994
|
-
initialize(): void {
|
|
995
|
-
// 通过 getWebSocketManager() 获取 WebSocket 管理器
|
|
996
|
-
const socket = this.getWebSocketManager();
|
|
997
|
-
|
|
998
|
-
// 配置 WebSocket
|
|
999
|
-
socket.configure({
|
|
1000
|
-
reconnect: true,
|
|
1001
|
-
reconnectInterval: 3000,
|
|
1002
|
-
reconnectAttempts: 10,
|
|
1003
|
-
heartbeat: true,
|
|
1004
|
-
heartbeatInterval: 30000
|
|
1005
|
-
});
|
|
1006
|
-
|
|
1007
|
-
// 监听事件
|
|
1008
|
-
socket.on('open', this.onConnected.bind(this));
|
|
1009
|
-
socket.on('message', this.onMessage.bind(this));
|
|
1010
|
-
socket.on('error', this.onError.bind(this));
|
|
1011
|
-
socket.on('close', this.onClose.bind(this));
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
connect(token: string): void {
|
|
1015
|
-
const wsUrl = `wss://game-server.com/ws?token=${token}`;
|
|
1016
|
-
this.getWebSocketManager().connect(wsUrl);
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
sendGameAction(action: string, data: any): void {
|
|
1020
|
-
this.getWebSocketManager().send({
|
|
1021
|
-
type: action,
|
|
1022
|
-
data,
|
|
1023
|
-
timestamp: Date.now()
|
|
1024
|
-
});
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
private onConnected(event: Event): void {
|
|
1028
|
-
console.log('WebSocket 连接成功');
|
|
1029
|
-
this.getEventManager().dispatch('socketConnected');
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
private onMessage(event: MessageEvent): void {
|
|
1033
|
-
try {
|
|
1034
|
-
const data = JSON.parse(event.data);
|
|
1035
|
-
// 通过事件系统分发消息
|
|
1036
|
-
this.getEventManager().dispatch(`socket_${data.type}`, data);
|
|
1037
|
-
} catch (error) {
|
|
1038
|
-
console.error('解析消息失败:', error);
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
private onError(event: Event): void {
|
|
1043
|
-
console.error('WebSocket 错误');
|
|
1044
|
-
}
|
|
1045
|
-
|
|
1046
|
-
private onClose(event: CloseEvent): void {
|
|
1047
|
-
console.log('WebSocket 连接关闭');
|
|
1048
|
-
this.getEventManager().dispatch('socketClosed');
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
dispose(): void {
|
|
1052
|
-
this.getWebSocketManager().disconnect();
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
```
|
|
1056
|
-
|
|
1057
|
-
## 9. 红点系统
|
|
1058
|
-
|
|
1059
|
-
框架提供了树形结构的红点提示管理系统,通过 `mf.reddot` 访问。
|
|
1060
|
-
|
|
1061
|
-
### 9.1 基本用法
|
|
1062
|
-
|
|
1063
|
-
```typescript
|
|
1064
|
-
// 设置红点数量
|
|
154
|
+
// 设置红点
|
|
1065
155
|
mf.reddot.setCount('main/bag/weapon', 5);
|
|
1066
|
-
mf.reddot.setCount('main/bag/armor', 3);
|
|
1067
|
-
mf.reddot.setCount('main/mail', 10);
|
|
1068
|
-
|
|
1069
|
-
// 获取红点数量(包含子节点)
|
|
1070
|
-
const weaponCount = mf.reddot.getCount('main/bag/weapon'); // 5
|
|
1071
|
-
const bagCount = mf.reddot.getCount('main/bag'); // 8 (weapon + armor)
|
|
1072
|
-
const mainCount = mf.reddot.getCount('main'); // 18 (bag + mail)
|
|
1073
|
-
|
|
1074
|
-
// 增加/减少红点数量
|
|
1075
|
-
mf.reddot.addCount('main/bag/weapon', 2); // 增加 2
|
|
1076
|
-
mf.reddot.addCount('main/bag/weapon', -1); // 减少 1
|
|
1077
|
-
|
|
1078
|
-
// 检查是否有红点
|
|
1079
|
-
if (mf.reddot.hasRedDot('main/bag')) {
|
|
1080
|
-
console.log('背包有新物品');
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
// 清空红点
|
|
1084
|
-
mf.reddot.clearRedDot('main/mail');
|
|
1085
|
-
```
|
|
1086
|
-
|
|
1087
|
-
### 9.2 监听红点变化
|
|
1088
156
|
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
console.log(`背包红点: ${totalCount} (自身: ${selfCount})`);
|
|
1093
|
-
// 更新 UI 显示
|
|
157
|
+
// 监听变化
|
|
158
|
+
mf.reddot.on('main/bag', (totalCount) => {
|
|
159
|
+
this.updateUI(totalCount);
|
|
1094
160
|
});
|
|
1095
|
-
|
|
1096
|
-
// 移除监听
|
|
1097
|
-
const handler = (total, self) => console.log(total, self);
|
|
1098
|
-
mf.reddot.on('main/bag', handler);
|
|
1099
|
-
mf.reddot.off('main/bag', handler);
|
|
1100
|
-
```
|
|
1101
|
-
|
|
1102
|
-
### 9.3 在 View 中使用
|
|
1103
|
-
|
|
1104
|
-
```typescript
|
|
1105
|
-
import { view, ViewNames } from 'dzkcc-mflow/core';
|
|
1106
|
-
import { BaseView } from 'dzkcc-mflow/libs';
|
|
1107
|
-
import { _decorator, Label } from 'cc';
|
|
1108
|
-
|
|
1109
|
-
const { ccclass, property } = _decorator;
|
|
1110
|
-
|
|
1111
|
-
@view('Main')
|
|
1112
|
-
@ccclass('MainView')
|
|
1113
|
-
export class MainView extends BaseView {
|
|
1114
|
-
@property(Label)
|
|
1115
|
-
bagRedDot: Label = null!;
|
|
1116
|
-
|
|
1117
|
-
@property(Label)
|
|
1118
|
-
mailRedDot: Label = null!;
|
|
1119
|
-
|
|
1120
|
-
onEnter(): void {
|
|
1121
|
-
// 监听红点变化
|
|
1122
|
-
mf.reddot.on('main/bag', this.updateBagRedDot.bind(this));
|
|
1123
|
-
mf.reddot.on('main/mail', this.updateMailRedDot.bind(this));
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
|
-
onExit(): void {
|
|
1127
|
-
// 移除监听
|
|
1128
|
-
mf.reddot.off('main/bag', this.updateBagRedDot.bind(this));
|
|
1129
|
-
mf.reddot.off('main/mail', this.updateMailRedDot.bind(this));
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
|
-
private updateBagRedDot(totalCount: number): void {
|
|
1133
|
-
this.bagRedDot.string = totalCount > 0 ? totalCount.toString() : '';
|
|
1134
|
-
this.bagRedDot.node.active = totalCount > 0;
|
|
1135
|
-
}
|
|
1136
|
-
|
|
1137
|
-
private updateMailRedDot(totalCount: number): void {
|
|
1138
|
-
this.mailRedDot.string = totalCount > 0 ? totalCount.toString() : '';
|
|
1139
|
-
this.mailRedDot.node.active = totalCount > 0;
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
onPause(): void {}
|
|
1143
|
-
onResume(): void {}
|
|
1144
|
-
}
|
|
1145
161
|
```
|
|
1146
162
|
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
红点系统使用树形结构,路径使用 `/` 分隔:
|
|
163
|
+
📖 **查看文档**: [红点系统](./docs/REDDOT_SYSTEM.md)
|
|
1150
164
|
|
|
1151
|
-
|
|
1152
|
-
main
|
|
1153
|
-
├── bag
|
|
1154
|
-
│ ├── weapon
|
|
1155
|
-
│ ├── armor
|
|
1156
|
-
│ └── consumable
|
|
1157
|
-
├── mail
|
|
1158
|
-
│ ├── system
|
|
1159
|
-
│ └── friend
|
|
1160
|
-
└── quest
|
|
1161
|
-
├── main
|
|
1162
|
-
└── daily
|
|
1163
|
-
```
|
|
165
|
+
### 8. 类型自动推断 ⭐
|
|
1164
166
|
|
|
1165
|
-
|
|
1166
|
-
- 子节点的红点会自动累加到父节点
|
|
1167
|
-
- 支持任意深度的树形结构
|
|
1168
|
-
- 路径大小写敏感
|
|
167
|
+
无需泛型、无需 import 具体类,自动推断 `getModel` 和 `getManager` 的返回类型!
|
|
1169
168
|
|
|
1170
|
-
---
|
|
1171
|
-
|
|
1172
|
-
## 10. 开发工具
|
|
1173
|
-
|
|
1174
|
-
框架配套了 Cocos Creator 编辑器插件 `mflow-tools`,用于提升开发效率。
|
|
1175
|
-
|
|
1176
|
-
### 10.1 功能特性
|
|
1177
|
-
|
|
1178
|
-
✨ **自动生成 UI 脚本** - 根据 Prefab 自动生成基础视图类
|
|
1179
|
-
✨ **自动引用组件** - 自动设置 `@property` 引用
|
|
1180
|
-
✨ **自动挂载脚本** - 自动将脚本挂载到 Prefab
|
|
1181
|
-
✨ **命名约定识别** - 通过节点命名自动识别组件类型
|
|
1182
|
-
|
|
1183
|
-
### 10.2 命名约定
|
|
1184
|
-
|
|
1185
|
-
在 Prefab 中,将需要引用的节点重命名为 `#属性名#组件类型` 格式:
|
|
1186
|
-
|
|
1187
|
-
```
|
|
1188
|
-
#titleLabel#Label -> 引用 Label 组件
|
|
1189
|
-
#closeButton#Button -> 引用 Button 组件
|
|
1190
|
-
#avatarSprite#Sprite -> 引用 Sprite 组件
|
|
1191
|
-
#contentNode -> 引用 Node 节点(省略组件类型)
|
|
1192
|
-
#listView#ScrollView -> 引用 ScrollView 组件
|
|
1193
|
-
```
|
|
1194
|
-
|
|
1195
|
-
### 10.3 使用方法
|
|
1196
|
-
|
|
1197
|
-
1. **设置节点命名**
|
|
1198
|
-
|
|
1199
|
-
在 Cocos Creator 编辑器中创建 Prefab,将需要引用的节点按照命名约定重命名:
|
|
1200
|
-
|
|
1201
|
-
![节点命名示例]
|
|
1202
|
-
```
|
|
1203
|
-
HomeView (Prefab 根节点)
|
|
1204
|
-
├── #titleLabel#Label
|
|
1205
|
-
├── #contentNode
|
|
1206
|
-
│ ├── #avatarSprite#Sprite
|
|
1207
|
-
│ └── #nameLabel#Label
|
|
1208
|
-
└── #closeButton#Button
|
|
1209
|
-
```
|
|
1210
|
-
|
|
1211
|
-
2. **导出脚本**
|
|
1212
|
-
|
|
1213
|
-
在 Hierarchy 面板中右键点击 Prefab 根节点,选择 **"MFlow Tools → 导出到脚本"**
|
|
1214
|
-
|
|
1215
|
-
3. **自动生成**
|
|
1216
|
-
|
|
1217
|
-
插件会自动生成两个文件:
|
|
1218
|
-
|
|
1219
|
-
**BaseHomeView.ts**(基础类,由工具生成,不要手动修改)
|
|
1220
|
-
```typescript
|
|
1221
|
-
import { _decorator, Label, Node, Sprite, Button } from 'cc';
|
|
1222
|
-
import { BaseView } from 'dzkcc-mflow/libs';
|
|
1223
|
-
|
|
1224
|
-
const { ccclass, property } = _decorator;
|
|
1225
|
-
|
|
1226
|
-
@ccclass('BaseHomeView')
|
|
1227
|
-
export abstract class BaseHomeView extends BaseView {
|
|
1228
|
-
/** @internal */
|
|
1229
|
-
private static readonly __path__: string = 'ui/home';
|
|
1230
|
-
|
|
1231
|
-
@property(Label) titleLabel: Label = null!;
|
|
1232
|
-
@property(Node) contentNode: Node = null!;
|
|
1233
|
-
@property(Sprite) avatarSprite: Sprite = null!;
|
|
1234
|
-
@property(Label) nameLabel: Label = null!;
|
|
1235
|
-
@property(Button) closeButton: Button = null!;
|
|
1236
|
-
|
|
1237
|
-
// 抽象方法由子类实现
|
|
1238
|
-
abstract onEnter(args?: any): void;
|
|
1239
|
-
abstract onExit(): void;
|
|
1240
|
-
abstract onPause(): void;
|
|
1241
|
-
abstract onResume(): void;
|
|
1242
|
-
}
|
|
1243
|
-
```
|
|
1244
|
-
|
|
1245
|
-
**HomeView.ts**(业务类,手动实现业务逻辑)
|
|
1246
169
|
```typescript
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
@view('Home')
|
|
1254
|
-
@ccclass('HomeView')
|
|
1255
|
-
export class HomeView extends BaseHomeView {
|
|
1256
|
-
onEnter(args?: any): void {
|
|
1257
|
-
// 实现业务逻辑
|
|
170
|
+
// 只需要一次性配置类型映射
|
|
171
|
+
// types/core-types.d.ts
|
|
172
|
+
declare module 'dzkcc-mflow/core' {
|
|
173
|
+
interface ModelTypeMap {
|
|
174
|
+
[ModelNames.User]: UserModel;
|
|
1258
175
|
}
|
|
1259
|
-
|
|
1260
|
-
onExit(): void {}
|
|
1261
|
-
onPause(): void {}
|
|
1262
|
-
onResume(): void {}
|
|
1263
176
|
}
|
|
1264
|
-
```
|
|
1265
|
-
|
|
1266
|
-
4. **脚本自动挂载**
|
|
1267
|
-
|
|
1268
|
-
插件会自动将生成的脚本挂载到 Prefab 上,并设置好所有组件引用。
|
|
1269
|
-
|
|
1270
|
-
---
|
|
1271
|
-
|
|
1272
|
-
## 11. 完整示例
|
|
1273
|
-
|
|
1274
|
-
下面是一个简单的塔防游戏示例,展示框架的完整使用流程。
|
|
1275
177
|
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
assets/
|
|
1280
|
-
├── scripts/
|
|
1281
|
-
│ ├── GameCore.ts # 游戏入口
|
|
1282
|
-
│ ├── managers/
|
|
1283
|
-
│ │ ├── GameManager.ts # 游戏管理器
|
|
1284
|
-
│ │ ├── EnemyManager.ts # 敌人管理器
|
|
1285
|
-
│ │ └── TowerManager.ts # 塔管理器
|
|
1286
|
-
│ ├── models/
|
|
1287
|
-
│ │ ├── GameModel.ts # 游戏数据模型
|
|
1288
|
-
│ │ └── PlayerModel.ts # 玩家数据模型
|
|
1289
|
-
│ └── views/
|
|
1290
|
-
│ ├── HomeView.ts # 主界面
|
|
1291
|
-
│ ├── GameView.ts # 游戏界面
|
|
1292
|
-
│ └── ResultView.ts # 结算界面
|
|
1293
|
-
└── resources/
|
|
1294
|
-
└── prefabs/
|
|
1295
|
-
├── ui/
|
|
1296
|
-
│ ├── home.prefab
|
|
1297
|
-
│ ├── game.prefab
|
|
1298
|
-
│ └── result.prefab
|
|
1299
|
-
└── entities/
|
|
1300
|
-
├── tower.prefab
|
|
1301
|
-
└── enemy.prefab
|
|
178
|
+
// 使用时自动推断类型
|
|
179
|
+
const userModel = mf.core.getModel(ModelNames.User);
|
|
180
|
+
userModel.name; // ✅ 有完整的代码补全
|
|
1302
181
|
```
|
|
1303
182
|
|
|
1304
|
-
|
|
183
|
+
**维护类型映射**:
|
|
1305
184
|
|
|
1306
|
-
|
|
1307
|
-
import { CocosCore } from 'dzkcc-mflow/libs';
|
|
1308
|
-
import { _decorator } from 'cc';
|
|
185
|
+
框架提供了自动类型生成工具,支持两种使用方式:
|
|
1309
186
|
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
import './managers/EnemyManager';
|
|
1313
|
-
import './managers/TowerManager';
|
|
1314
|
-
import './models/GameModel';
|
|
1315
|
-
import './models/PlayerModel';
|
|
1316
|
-
import './views/HomeView';
|
|
1317
|
-
import './views/GameView';
|
|
1318
|
-
import './views/ResultView';
|
|
187
|
+
1. **在 Cocos Creator 编辑器中(推荐)**:**项目菜单 -> 生成类型映射**
|
|
188
|
+
2. **命令行方式**:`node node_modules/dzkcc-mflow/scripts/generate-type-map.js`
|
|
1319
189
|
|
|
1320
|
-
|
|
190
|
+
📖 **查看文档**: [类型自动推断](./docs/TYPE_INFERENCE.md) | [类型生成工具](./docs/TYPE_GENERATION.md)
|
|
1321
191
|
|
|
1322
|
-
|
|
1323
|
-
export class GameCore extends CocosCore {
|
|
1324
|
-
protected onLoad(): void {
|
|
1325
|
-
super.onLoad();
|
|
1326
|
-
|
|
1327
|
-
// 框架初始化完成后,打开主界面
|
|
1328
|
-
this.scheduleOnce(() => {
|
|
1329
|
-
mf.gui.open(ViewNames.Home);
|
|
1330
|
-
}, 0);
|
|
1331
|
-
}
|
|
1332
|
-
}
|
|
1333
|
-
```
|
|
192
|
+
### 9. 开发工具
|
|
1334
193
|
|
|
1335
|
-
|
|
194
|
+
Cocos Creator 编辑器插件,自动生成 UI 脚本,自动设置组件引用。
|
|
1336
195
|
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
export class GameModel implements IModel {
|
|
1342
|
-
private _level: number = 1;
|
|
1343
|
-
private _score: number = 0;
|
|
1344
|
-
private _gold: number = 500;
|
|
1345
|
-
private _life: number = 10;
|
|
1346
|
-
|
|
1347
|
-
initialize(): void {
|
|
1348
|
-
console.log('GameModel 初始化');
|
|
1349
|
-
}
|
|
1350
|
-
|
|
1351
|
-
get level(): number {
|
|
1352
|
-
return this._level;
|
|
1353
|
-
}
|
|
1354
|
-
|
|
1355
|
-
set level(value: number) {
|
|
1356
|
-
this._level = value;
|
|
1357
|
-
}
|
|
1358
|
-
|
|
1359
|
-
get score(): number {
|
|
1360
|
-
return this._score;
|
|
1361
|
-
}
|
|
1362
|
-
|
|
1363
|
-
addScore(value: number): void {
|
|
1364
|
-
this._score += value;
|
|
1365
|
-
}
|
|
1366
|
-
|
|
1367
|
-
get gold(): number {
|
|
1368
|
-
return this._gold;
|
|
1369
|
-
}
|
|
1370
|
-
|
|
1371
|
-
addGold(value: number): void {
|
|
1372
|
-
this._gold += value;
|
|
1373
|
-
}
|
|
1374
|
-
|
|
1375
|
-
get life(): number {
|
|
1376
|
-
return this._life;
|
|
1377
|
-
}
|
|
1378
|
-
|
|
1379
|
-
loseLife(value: number = 1): void {
|
|
1380
|
-
this._life = Math.max(0, this._life - value);
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1383
|
-
reset(): void {
|
|
1384
|
-
this._level = 1;
|
|
1385
|
-
this._score = 0;
|
|
1386
|
-
this._gold = 500;
|
|
1387
|
-
this._life = 10;
|
|
1388
|
-
}
|
|
1389
|
-
}
|
|
1390
|
-
```
|
|
196
|
+
**使用方法**:
|
|
197
|
+
1. 按命名约定重命名节点(如 `#titleLabel#Label`)
|
|
198
|
+
2. 右键选择 "MFlow Tools → 导出到脚本"
|
|
199
|
+
3. 自动生成基类和业务类
|
|
1391
200
|
|
|
1392
|
-
|
|
201
|
+
📖 **查看文档**: [开发工具](./docs/DEV_TOOLS.md)
|
|
1393
202
|
|
|
1394
|
-
|
|
1395
|
-
import { model, IModel } from 'dzkcc-mflow/core';
|
|
1396
|
-
|
|
1397
|
-
@model('Player')
|
|
1398
|
-
export class PlayerModel implements IModel {
|
|
1399
|
-
private _name: string = '';
|
|
1400
|
-
private _highScore: number = 0;
|
|
1401
|
-
|
|
1402
|
-
initialize(): void {
|
|
1403
|
-
console.log('PlayerModel 初始化');
|
|
1404
|
-
this.loadFromStorage();
|
|
1405
|
-
}
|
|
1406
|
-
|
|
1407
|
-
get name(): string {
|
|
1408
|
-
return this._name;
|
|
1409
|
-
}
|
|
1410
|
-
|
|
1411
|
-
set name(value: string) {
|
|
1412
|
-
this._name = value;
|
|
1413
|
-
this.saveToStorage();
|
|
1414
|
-
}
|
|
1415
|
-
|
|
1416
|
-
get highScore(): number {
|
|
1417
|
-
return this._highScore;
|
|
1418
|
-
}
|
|
1419
|
-
|
|
1420
|
-
updateHighScore(score: number): void {
|
|
1421
|
-
if (score > this._highScore) {
|
|
1422
|
-
this._highScore = score;
|
|
1423
|
-
this.saveToStorage();
|
|
1424
|
-
}
|
|
1425
|
-
}
|
|
1426
|
-
|
|
1427
|
-
private loadFromStorage(): void {
|
|
1428
|
-
const data = localStorage.getItem('playerData');
|
|
1429
|
-
if (data) {
|
|
1430
|
-
const parsed = JSON.parse(data);
|
|
1431
|
-
this._name = parsed.name || '';
|
|
1432
|
-
this._highScore = parsed.highScore || 0;
|
|
1433
|
-
}
|
|
1434
|
-
}
|
|
1435
|
-
|
|
1436
|
-
private saveToStorage(): void {
|
|
1437
|
-
localStorage.setItem('playerData', JSON.stringify({
|
|
1438
|
-
name: this._name,
|
|
1439
|
-
highScore: this._highScore
|
|
1440
|
-
}));
|
|
1441
|
-
}
|
|
1442
|
-
}
|
|
1443
|
-
```
|
|
203
|
+
## 完整示例
|
|
1444
204
|
|
|
1445
|
-
|
|
205
|
+
查看一个完整的塔防游戏示例,了解如何使用框架的各个功能。
|
|
1446
206
|
|
|
1447
|
-
|
|
1448
|
-
import { manager, AbstractManager, ManagerNames, ModelNames } from 'dzkcc-mflow/core';
|
|
1449
|
-
import { GameModel } from '../models/GameModel';
|
|
1450
|
-
import { PlayerModel } from '../models/PlayerModel';
|
|
207
|
+
📖 **查看示例**: [完整示例](./docs/COMPLETE_EXAMPLE.md)
|
|
1451
208
|
|
|
1452
|
-
|
|
1453
|
-
export class GameManager extends AbstractManager {
|
|
1454
|
-
private gameModel: GameModel = null!;
|
|
1455
|
-
private playerModel: PlayerModel = null!;
|
|
1456
|
-
|
|
1457
|
-
initialize(): void {
|
|
1458
|
-
console.log('GameManager 初始化');
|
|
1459
|
-
|
|
1460
|
-
// 获取 Model
|
|
1461
|
-
this.gameModel = this.getModel<GameModel>(ModelNames.Game);
|
|
1462
|
-
this.playerModel = this.getModel<PlayerModel>(ModelNames.Player);
|
|
1463
|
-
}
|
|
1464
|
-
|
|
1465
|
-
startGame(level: number): void {
|
|
1466
|
-
// 重置游戏数据
|
|
1467
|
-
this.gameModel.reset();
|
|
1468
|
-
this.gameModel.level = level;
|
|
1469
|
-
|
|
1470
|
-
// 派发游戏开始事件
|
|
1471
|
-
this.getEventManager().dispatch('gameStart', { level });
|
|
1472
|
-
}
|
|
1473
|
-
|
|
1474
|
-
killEnemy(enemyType: string): void {
|
|
1475
|
-
// 增加分数和金币
|
|
1476
|
-
const reward = this.getEnemyReward(enemyType);
|
|
1477
|
-
this.gameModel.addScore(reward.score);
|
|
1478
|
-
this.gameModel.addGold(reward.gold);
|
|
1479
|
-
|
|
1480
|
-
// 派发事件
|
|
1481
|
-
this.getEventManager().dispatch('enemyKilled', {
|
|
1482
|
-
enemyType,
|
|
1483
|
-
score: this.gameModel.score,
|
|
1484
|
-
gold: this.gameModel.gold
|
|
1485
|
-
});
|
|
1486
|
-
}
|
|
1487
|
-
|
|
1488
|
-
enemyEscape(): void {
|
|
1489
|
-
// 减少生命
|
|
1490
|
-
this.gameModel.loseLife(1);
|
|
1491
|
-
|
|
1492
|
-
// 派发事件
|
|
1493
|
-
this.getEventManager().dispatch('lifeChanged', this.gameModel.life);
|
|
1494
|
-
|
|
1495
|
-
// 检查游戏是否结束
|
|
1496
|
-
if (this.gameModel.life <= 0) {
|
|
1497
|
-
this.gameOver();
|
|
1498
|
-
}
|
|
1499
|
-
}
|
|
1500
|
-
|
|
1501
|
-
private gameOver(): void {
|
|
1502
|
-
// 更新最高分
|
|
1503
|
-
this.playerModel.updateHighScore(this.gameModel.score);
|
|
1504
|
-
|
|
1505
|
-
// 派发游戏结束事件
|
|
1506
|
-
this.getEventManager().dispatch('gameOver', {
|
|
1507
|
-
score: this.gameModel.score,
|
|
1508
|
-
highScore: this.playerModel.highScore
|
|
1509
|
-
});
|
|
1510
|
-
}
|
|
1511
|
-
|
|
1512
|
-
private getEnemyReward(enemyType: string): { score: number; gold: number } {
|
|
1513
|
-
// 根据敌人类型返回奖励
|
|
1514
|
-
const rewards: Record<string, { score: number; gold: number }> = {
|
|
1515
|
-
'small': { score: 10, gold: 5 },
|
|
1516
|
-
'medium': { score: 20, gold: 10 },
|
|
1517
|
-
'large': { score: 50, gold: 25 }
|
|
1518
|
-
};
|
|
1519
|
-
return rewards[enemyType] || rewards['small'];
|
|
1520
|
-
}
|
|
1521
|
-
}
|
|
1522
|
-
```
|
|
209
|
+
## 最佳实践
|
|
1523
210
|
|
|
1524
|
-
|
|
211
|
+
了解设计原则、命名规范、项目结构、性能优化和常见注意事项。
|
|
1525
212
|
|
|
1526
|
-
|
|
1527
|
-
import { view, ViewNames, ModelNames } from 'dzkcc-mflow/core';
|
|
1528
|
-
import { BaseView } from 'dzkcc-mflow/libs';
|
|
1529
|
-
import { _decorator, Button, Label } from 'cc';
|
|
1530
|
-
import { PlayerModel } from '../models/PlayerModel';
|
|
213
|
+
📖 **查看文档**: [最佳实践](./docs/BEST_PRACTICES.md)
|
|
1531
214
|
|
|
1532
|
-
|
|
215
|
+
## 架构图
|
|
1533
216
|
|
|
1534
|
-
@view('Home')
|
|
1535
|
-
@ccclass('HomeView')
|
|
1536
|
-
export class HomeView extends BaseView {
|
|
1537
|
-
@property(Label)
|
|
1538
|
-
welcomeLabel: Label = null!;
|
|
1539
|
-
|
|
1540
|
-
@property(Label)
|
|
1541
|
-
highScoreLabel: Label = null!;
|
|
1542
|
-
|
|
1543
|
-
@property(Button)
|
|
1544
|
-
startButton: Button = null!;
|
|
1545
|
-
|
|
1546
|
-
private playerModel: PlayerModel = null!;
|
|
1547
|
-
|
|
1548
|
-
onEnter(args?: any): void {
|
|
1549
|
-
// 获取 Model
|
|
1550
|
-
this.playerModel = this.getModel<PlayerModel>(ModelNames.Player);
|
|
1551
|
-
|
|
1552
|
-
// 更新 UI
|
|
1553
|
-
this.welcomeLabel.string = `欢迎, ${this.playerModel.name || '玩家'}!`;
|
|
1554
|
-
this.highScoreLabel.string = `最高分: ${this.playerModel.highScore}`;
|
|
1555
|
-
|
|
1556
|
-
// 监听按钮点击
|
|
1557
|
-
this.startButton.node.on(Button.EventType.CLICK, this.onStartClick, this);
|
|
1558
|
-
}
|
|
1559
|
-
|
|
1560
|
-
onExit(): void {
|
|
1561
|
-
// BaseView 会自动清理事件监听
|
|
1562
|
-
}
|
|
1563
|
-
|
|
1564
|
-
onPause(): void {}
|
|
1565
|
-
onResume(): void {}
|
|
1566
|
-
|
|
1567
|
-
private async onStartClick(): Promise<void> {
|
|
1568
|
-
// 关闭主界面
|
|
1569
|
-
mf.gui.close(ViewNames.Home);
|
|
1570
|
-
|
|
1571
|
-
// 打开游戏界面
|
|
1572
|
-
await mf.gui.open(ViewNames.Game, { level: 1 });
|
|
1573
|
-
}
|
|
1574
|
-
}
|
|
1575
217
|
```
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
goldLabel: Label = null!;
|
|
1596
|
-
|
|
1597
|
-
@property(Label)
|
|
1598
|
-
lifeLabel: Label = null!;
|
|
1599
|
-
|
|
1600
|
-
@property(Node)
|
|
1601
|
-
gameContainer: Node = null!;
|
|
1602
|
-
|
|
1603
|
-
private gameManager: GameManager = null!;
|
|
1604
|
-
private gameModel: GameModel = null!;
|
|
1605
|
-
|
|
1606
|
-
onEnter(args?: any): void {
|
|
1607
|
-
// 获取 Manager 和 Model
|
|
1608
|
-
this.gameManager = this.getManager<GameManager>(ManagerNames.Game);
|
|
1609
|
-
this.gameModel = this.getModel<GameModel>(ModelNames.Game);
|
|
1610
|
-
|
|
1611
|
-
// 监听游戏事件(会自动清理)
|
|
1612
|
-
this.event.on('enemyKilled', this.onEnemyKilled, this);
|
|
1613
|
-
this.event.on('lifeChanged', this.onLifeChanged, this);
|
|
1614
|
-
this.event.on('gameOver', this.onGameOver, this);
|
|
1615
|
-
|
|
1616
|
-
// 开始游戏
|
|
1617
|
-
const level = args?.level || 1;
|
|
1618
|
-
this.gameManager.startGame(level);
|
|
1619
|
-
|
|
1620
|
-
// 更新 UI
|
|
1621
|
-
this.updateUI();
|
|
1622
|
-
}
|
|
1623
|
-
|
|
1624
|
-
onExit(): void {
|
|
1625
|
-
// BaseView 会自动清理事件监听
|
|
1626
|
-
}
|
|
1627
|
-
|
|
1628
|
-
onPause(): void {}
|
|
1629
|
-
onResume(): void {}
|
|
1630
|
-
|
|
1631
|
-
private onEnemyKilled(data: any): void {
|
|
1632
|
-
this.updateUI();
|
|
1633
|
-
}
|
|
1634
|
-
|
|
1635
|
-
private onLifeChanged(life: number): void {
|
|
1636
|
-
this.lifeLabel.string = `生命: ${life}`;
|
|
1637
|
-
}
|
|
1638
|
-
|
|
1639
|
-
private async onGameOver(data: any): Promise<void> {
|
|
1640
|
-
// 关闭游戏界面
|
|
1641
|
-
mf.gui.close(ViewNames.Game);
|
|
1642
|
-
|
|
1643
|
-
// 打开结算界面
|
|
1644
|
-
await mf.gui.open(ViewNames.Result, {
|
|
1645
|
-
score: data.score,
|
|
1646
|
-
highScore: data.highScore
|
|
1647
|
-
});
|
|
1648
|
-
}
|
|
1649
|
-
|
|
1650
|
-
private updateUI(): void {
|
|
1651
|
-
this.scoreLabel.string = `分数: ${this.gameModel.score}`;
|
|
1652
|
-
this.goldLabel.string = `金币: ${this.gameModel.gold}`;
|
|
1653
|
-
this.lifeLabel.string = `生命: ${this.gameModel.life}`;
|
|
1654
|
-
}
|
|
1655
|
-
}
|
|
218
|
+
┌─────────────────────────────────────────────────┐
|
|
219
|
+
│ 全局对象 mf │
|
|
220
|
+
│ (统一访问入口,暴露所有框架能力) │
|
|
221
|
+
└──────────────────┬──────────────────────────────┘
|
|
222
|
+
│
|
|
223
|
+
┌──────────────┼──────────────┐
|
|
224
|
+
│ │ │
|
|
225
|
+
▼ ▼ ▼
|
|
226
|
+
┌─────────┐ ┌──────────┐ ┌──────────┐
|
|
227
|
+
│ Core │ │ Services │ │ Views │
|
|
228
|
+
│(核心容器)│ │(基础服务) │ │ (UI界面) │
|
|
229
|
+
└─────────┘ └──────────┘ └──────────┘
|
|
230
|
+
│ │ │
|
|
231
|
+
├─ Manager ─┐ ├─ UIManager ├─ BaseView
|
|
232
|
+
│ │ ├─ ResLoader └─ 自动资源管理
|
|
233
|
+
├─ Model ───┤ ├─ EventMgr 自动事件清理
|
|
234
|
+
│ │ ├─ HttpMgr
|
|
235
|
+
└─ Symbol ──┘ ├─ SocketMgr
|
|
236
|
+
映射系统 └─ RedDotMgr
|
|
1656
237
|
```
|
|
1657
238
|
|
|
1658
|
-
|
|
239
|
+
## 使用全局对象
|
|
1659
240
|
|
|
1660
|
-
|
|
1661
|
-
import { view, ViewNames } from 'dzkcc-mflow/core';
|
|
1662
|
-
import { BaseView } from 'dzkcc-mflow/libs';
|
|
1663
|
-
import { _decorator, Button, Label } from 'cc';
|
|
1664
|
-
|
|
1665
|
-
const { ccclass, property } = _decorator;
|
|
1666
|
-
|
|
1667
|
-
@view('Result')
|
|
1668
|
-
@ccclass('ResultView')
|
|
1669
|
-
export class ResultView extends BaseView {
|
|
1670
|
-
@property(Label)
|
|
1671
|
-
scoreLabel: Label = null!;
|
|
1672
|
-
|
|
1673
|
-
@property(Label)
|
|
1674
|
-
highScoreLabel: Label = null!;
|
|
1675
|
-
|
|
1676
|
-
@property(Button)
|
|
1677
|
-
restartButton: Button = null!;
|
|
1678
|
-
|
|
1679
|
-
@property(Button)
|
|
1680
|
-
homeButton: Button = null!;
|
|
1681
|
-
|
|
1682
|
-
onEnter(args?: any): void {
|
|
1683
|
-
// 显示分数
|
|
1684
|
-
this.scoreLabel.string = `本局分数: ${args?.score || 0}`;
|
|
1685
|
-
this.highScoreLabel.string = `最高分: ${args?.highScore || 0}`;
|
|
1686
|
-
|
|
1687
|
-
// 监听按钮点击
|
|
1688
|
-
this.restartButton.node.on(Button.EventType.CLICK, this.onRestartClick, this);
|
|
1689
|
-
this.homeButton.node.on(Button.EventType.CLICK, this.onHomeClick, this);
|
|
1690
|
-
}
|
|
1691
|
-
|
|
1692
|
-
onExit(): void {}
|
|
1693
|
-
onPause(): void {}
|
|
1694
|
-
onResume(): void {}
|
|
1695
|
-
|
|
1696
|
-
private async onRestartClick(): Promise<void> {
|
|
1697
|
-
mf.gui.close(ViewNames.Result);
|
|
1698
|
-
await mf.gui.open(ViewNames.Game, { level: 1 });
|
|
1699
|
-
}
|
|
1700
|
-
|
|
1701
|
-
private async onHomeClick(): Promise<void> {
|
|
1702
|
-
mf.gui.close(ViewNames.Result);
|
|
1703
|
-
await mf.gui.open(ViewNames.Home);
|
|
1704
|
-
}
|
|
1705
|
-
}
|
|
1706
|
-
```
|
|
241
|
+
框架提供了全局对象 `mf` 用于访问所有功能:
|
|
1707
242
|
|
|
1708
|
-
|
|
243
|
+
```typescript
|
|
244
|
+
// 访问 Manager
|
|
245
|
+
mf.core.getManager(ManagerNames.GameManager);
|
|
1709
246
|
|
|
1710
|
-
|
|
247
|
+
// 访问 Model
|
|
248
|
+
mf.core.getModel(ModelNames.UserModel);
|
|
1711
249
|
|
|
1712
|
-
|
|
250
|
+
// UI 管理
|
|
251
|
+
mf.gui.open(ViewNames.HomeView);
|
|
1713
252
|
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
✅ **事件驱动** - 通过事件系统实现模块解耦
|
|
1717
|
-
✅ **资源管理** - 使用 BaseView 自动管理资源生命周期
|
|
253
|
+
// 事件系统
|
|
254
|
+
mf.event.dispatch('gameStart');
|
|
1718
255
|
|
|
1719
|
-
|
|
256
|
+
// 资源加载
|
|
257
|
+
mf.res.loadPrefab('prefabs/player');
|
|
1720
258
|
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
- **View**: 以 `View` 结尾,如 `HomeView`、`GameView`
|
|
1724
|
-
- **装饰器名称**: 简短清晰,如 `@manager('Game')`、`@model('User')`
|
|
259
|
+
// HTTP 请求
|
|
260
|
+
mf.http.get('/api/user/profile');
|
|
1725
261
|
|
|
1726
|
-
|
|
262
|
+
// WebSocket
|
|
263
|
+
mf.socket.connect('wss://server.com/ws');
|
|
1727
264
|
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
├── GameCore.ts # 游戏入口
|
|
1731
|
-
├── managers/ # 业务管理器
|
|
1732
|
-
│ ├── GameManager.ts
|
|
1733
|
-
│ ├── AudioManager.ts
|
|
1734
|
-
│ └── NetworkManager.ts
|
|
1735
|
-
├── models/ # 数据模型
|
|
1736
|
-
│ ├── UserModel.ts
|
|
1737
|
-
│ ├── ConfigModel.ts
|
|
1738
|
-
│ └── InventoryModel.ts
|
|
1739
|
-
├── views/ # UI 视图
|
|
1740
|
-
│ ├── HomeView.ts
|
|
1741
|
-
│ ├── BattleView.ts
|
|
1742
|
-
│ └── SettingsView.ts
|
|
1743
|
-
├── components/ # 可复用组件
|
|
1744
|
-
│ ├── ItemSlot.ts
|
|
1745
|
-
│ └── HealthBar.ts
|
|
1746
|
-
└── utils/ # 工具函数
|
|
1747
|
-
├── MathUtil.ts
|
|
1748
|
-
└── StringUtil.ts
|
|
265
|
+
// 红点系统
|
|
266
|
+
mf.reddot.setCount('main/bag', 5);
|
|
1749
267
|
```
|
|
1750
268
|
|
|
1751
|
-
|
|
269
|
+
## 文档索引
|
|
1752
270
|
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
271
|
+
| 文档 | 说明 |
|
|
272
|
+
|------|------|
|
|
273
|
+
| [快速开始](./docs/QUICK_START.md) | 安装和基本使用 |
|
|
274
|
+
| [核心概念](./docs/CORE_CONCEPTS.md) | 框架核心概念和架构 |
|
|
275
|
+
| [装饰器系统](./docs/DECORATORS.md) | @manager、@model、@view 使用 |
|
|
276
|
+
| [UI 系统](./docs/UI_SYSTEM.md) | 界面管理和生命周期 |
|
|
277
|
+
| [事件系统](./docs/EVENT_SYSTEM.md) | 事件广播和监听 |
|
|
278
|
+
| [资源管理](./docs/RESOURCE_MANAGEMENT.md) | 资源加载和释放 |
|
|
279
|
+
| [网络通信](./docs/NETWORK.md) | HTTP 和 WebSocket |
|
|
280
|
+
| [红点系统](./docs/REDDOT_SYSTEM.md) | 红点提示管理 |
|
|
281
|
+
| [类型自动推断](./docs/TYPE_INFERENCE.md) | 类型映射和自动推断 |
|
|
282
|
+
| [类型生成工具](./docs/TYPE_GENERATION.md) | 自动生成类型映射文件 |
|
|
283
|
+
| [开发工具](./docs/DEV_TOOLS.md) | 编辑器插件使用 |
|
|
284
|
+
| [完整示例](./docs/COMPLETE_EXAMPLE.md) | 塔防游戏示例 |
|
|
285
|
+
| [最佳实践](./docs/BEST_PRACTICES.md) | 设计原则和规范 |
|
|
1758
286
|
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
## 13. License
|
|
287
|
+
## License
|
|
1762
288
|
|
|
1763
289
|
MIT License
|
|
1764
290
|
|
|
1765
291
|
Copyright (c) 2024 duanzhk
|
|
1766
292
|
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
## 14. 支持与反馈
|
|
293
|
+
## 支持与反馈
|
|
1770
294
|
|
|
1771
295
|
- **GitHub**: [cocos-modular-flow-framework](https://github.com/duanzhk/cocos-modular-flow-framework)
|
|
1772
|
-
- **文档**: [在线文档](https://github.com/duanzhk/cocos-modular-flow-framework/blob/main/README.md)
|
|
1773
296
|
- **问题反馈**: [Issues](https://github.com/duanzhk/cocos-modular-flow-framework/issues)
|
|
1774
297
|
|
|
1775
298
|
---
|
|
1776
299
|
|
|
1777
|
-
Made with ❤️ by duanzhk
|
|
300
|
+
Made with ❤️ by duanzhk
|