dzkcc-mflow 0.0.21 → 0.0.23

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 CHANGED
@@ -1,769 +1,1777 @@
1
1
  # Modular Flow Framework
2
2
 
3
- ## 1.1 框架概述
3
+ 一个专为 Cocos Creator 引擎开发的模块化设计与流程管理框架。
4
+ - github地址:https://github.com/duanzhk/cocos-modular-flow-framework
4
5
 
5
- Cocos模块化流程框架(Modular Flow Framework)是一个为Cocos Creator引擎开发的模块化设计和流程管理框架。该框架旨在提供解耦和依赖注入的能力,帮助开发者构建更加清晰、可维护的游戏项目。
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
+
21
+ ---
22
+
23
+ ## 1. 框架概述
24
+
25
+ ### 1.1 简介
26
+
27
+ Modular Flow Framework (MF) 是一个为 Cocos Creator 引擎开发的模块化设计和流程管理框架。该框架旨在提供解耦和依赖注入的能力,帮助开发者构建更加清晰、可维护的游戏项目。
6
28
 
7
29
  ### 1.2 核心特性
8
30
 
9
- - **模块化设计**:通过Manager和Model模式实现业务逻辑的模块化管理
10
- - **依赖注入**:通过装饰器实现自动依赖注入
11
- - **服务定位器**:统一的服务管理机制
12
- - **UI管理系统**:完整的UI界面管理方案
13
- - **事件系统**:强大的事件广播和监听机制
14
- - **资源加载系统**:统一的资源加载和释放管理
15
- - **HTTP网络请求系统**:简洁易用的HTTP客户端
16
- - **WebSocket实时通信**:支持自动重连、心跳检测的WebSocket客户端
17
- - **开发工具**:配套的Cocos Creator编辑器插件
31
+ ✨ **模块化设计** - 通过 Manager Model 模式实现业务逻辑的模块化管理
32
+ ✨ **依赖注入** - 基于装饰器的自动依赖注入和 Symbol 映射
33
+ ✨ **服务定位器** - 统一的服务管理机制,实现服务解耦
34
+ **UI 管理系统** - 完整的 UI 界面管理方案,支持视图栈和分组
35
+ ✨ **事件系统** - 强大的事件广播和监听机制,支持粘性事件
36
+ ✨ **资源加载系统** - 统一的资源加载和自动释放管理
37
+ **HTTP 网络** - 简洁易用的 HTTP 客户端,支持 RESTful API
38
+ **WebSocket 实时通信** - 支持自动重连、心跳检测的 WebSocket 客户端
39
+ ✨ **红点系统** - 树形结构的红点提示管理系统
40
+ ✨ **开发工具** - 配套的 Cocos Creator 编辑器插件
41
+
42
+ ### 1.3 安装
18
43
 
19
- ### 1.3 安装说明
44
+ 创建自己的cocos项目,在项目根目录执行如下命令:
20
45
 
21
46
  ```bash
22
47
  npm i dzkcc-mflow@beta
23
48
  ```
49
+ 安装完成后,修改自己项目的tsconfig.json,关键添加内容 **"dzkcc-mflow/*": ["./node_modules/dzkcc-mflow/dist/*"]**
50
+ ```
51
+ {
52
+ /* Base configuration. Do not edit this field. */
53
+ "extends": "./temp/tsconfig.cocos.json",
54
+
55
+ /* Add your custom configuration here. */
56
+ "compilerOptions": {
57
+ "strict": false,
58
+ "paths": {
59
+ "dzkcc-mflow/*": ["./node_modules/dzkcc-mflow/dist/*"] //cocos不解析,仅为了vscode提示
60
+ }
61
+ }
62
+ }
63
+ ```
24
64
 
25
- 安装完成后,重启Cocos Creator引擎。
65
+ 之后,**重启 Cocos Creator 编辑器**,框架会自动安装配套的编辑器插件。
26
66
 
27
- ## 2. 核心概念
67
+ ---
28
68
 
29
- ### 2.1 Core核心
69
+ ## 2. 快速开始
30
70
 
31
- Core是框架的核心,负责管理所有的Manager和Model实例。它继承自`AbstractCore`类,提供了注册和获取Manager/Model的接口。
71
+ ### 2.1 创建 Core 入口
72
+
73
+ 在项目中创建一个继承自 `CocosCore` 的类:
32
74
 
33
75
  ```typescript
34
- // 自定义Core需要继承CocosCore
35
- export class GameCore extends CocosCore { }
36
- ```
76
+ // GameCore.ts
77
+ import { CocosCore } from 'dzkcc-mflow/libs';
78
+ import { _decorator } from 'cc';
37
79
 
38
- 在场景中挂载GameCore组件即可初始化框架。
80
+ const { ccclass } = _decorator;
39
81
 
40
- ### 2.2 ServiceLocator服务定位器
82
+ @ccclass('GameCore')
83
+ export class GameCore extends CocosCore {
84
+ // CocosCore 会自动初始化框架
85
+ }
86
+ ```
41
87
 
42
- ServiceLocator用于管理跨领域的基础服务,如EventManager、ResLoader、UIManager等。
88
+ ### 2.2 挂载到场景
43
89
 
44
- ```typescript
45
- // 注册服务
46
- ServiceLocator.regService('serviceKey', serviceInstance);
90
+ 1. 在 Cocos Creator 编辑器中打开主场景
91
+ 2. 在 Canvas 节点上添加 `GameCore` 组件
92
+ 3. 保存场景
47
93
 
48
- // 获取服务
49
- const service = ServiceLocator.getService<ServiceType>('serviceKey');
50
- ```
51
94
 
52
- ### 2.3 Manager管理器
95
+ ### 2.3 使用全局对象
53
96
 
54
- Manager负责管理业务领域内的具体实现,通常处理业务逻辑。Manager需要实现`IManager`接口。
97
+ 框架提供了全局对象 `mf`(Modular Flow 的缩写)用于访问框架功能:
55
98
 
56
99
  ```typescript
57
- export abstract class AbstractManager implements IManager {
58
- abstract initialize(): void;
59
- dispose(): void;
60
- }
61
- ```
100
+ // 访问 Manager
101
+ const gameManager = mf.core.getManager(ManagerNames.GameManager);
62
102
 
63
- ### 2.4 Model模型
103
+ // 访问 Model
104
+ const userModel = mf.core.getModel(ModelNames.UserModel);
64
105
 
65
- Model用于数据管理,实现`IModel`接口。
106
+ // 打开 UI
107
+ await mf.gui.open(ViewNames.HomeView);
66
108
 
67
- ```typescript
68
- export interface IModel {
69
- initialize(): void;
70
- }
71
- ```
109
+ // 发送事件
110
+ mf.event.dispatch('gameStart');
72
111
 
73
- ### 2.5 装饰器系统
112
+ // 加载资源
113
+ const prefab = await mf.res.loadPrefab('prefabs/player');
74
114
 
75
- 框架提供了装饰器来简化Manager和Model的注册:
115
+ // HTTP 请求
116
+ const data = await mf.http.get('/api/user/profile');
76
117
 
77
- ```typescript
78
- // 注册Manager
79
- @manager()
80
- export class GameManager extends AbstractManager {
81
- // 实现逻辑
82
- }
118
+ // WebSocket 连接
119
+ mf.socket.connect('wss://game-server.com/ws');
83
120
 
84
- // 注册Model
85
- @model()
86
- export class GameModel implements IModel {
87
- initialize(): void {
88
- // 初始化逻辑
89
- }
90
- }
121
+ // 红点提示
122
+ mf.reddot.setCount('main/bag', 5);
91
123
  ```
92
124
 
93
- ## 3. UI系统
125
+ ## 3. 核心概念
94
126
 
95
- ### 3.1 BaseView基础视图
127
+ ### 3.1 架构图
96
128
 
97
- 所有UI界面都应该继承`BaseView`类,它提供了以下特性:
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
+ ```
98
150
 
99
- - 自动事件监听管理(自动注册和注销)
100
- - 自动资源加载管理(自动释放)
101
- - 统一的生命周期方法
151
+ ### 3.2 Core 核心容器
102
152
 
103
- ```typescript
104
- export abstract class BaseView extends Component implements IView {
105
- abstract onPause(): void;
106
- abstract onResume(): void;
107
- abstract onEnter(args?: any): void;
108
- onExit(): void;
109
- }
110
- ```
153
+ `Core` 是框架的核心,负责管理所有 Manager 和 Model 实例。
111
154
 
112
- ### 3.2 UIManager界面管理器
155
+ **核心职责:**
156
+ - 注册和实例化 Manager
157
+ - 注册和实例化 Model
158
+ - 提供统一的访问接口
159
+ - 自动调用初始化方法
113
160
 
114
- UIManager负责管理UI界面的打开、关闭、层级等操作。
161
+ **使用方式:**
115
162
 
116
163
  ```typescript
117
- // 打开界面
118
- const view = await mf.gui.open(ViewType, args);
164
+ // 获取 Manager
165
+ const gameManager = mf.core.getManager(ManagerNames.GameManager);
119
166
 
120
- // 关闭界面
121
- mf.gui.close(viewInstance);
122
-
123
- // 带分组的界面管理
124
- const view = await mf.gui.openAndPush(ViewType, 'group', args);
125
- mf.gui.closeAndPop('group');
167
+ // 获取 Model
168
+ const userModel = mf.core.getModel(ModelNames.UserModel);
126
169
  ```
127
170
 
128
- ## 4. 事件系统
171
+ ### 3.3 ServiceLocator 服务定位器
172
+
173
+ `ServiceLocator` 用于管理跨领域的基础服务,实现解耦。
129
174
 
130
- ### 4.1 Broadcaster事件广播器
175
+ **内置服务:**
176
+ - `core` - Core 实例
177
+ - `EventManager` - 事件管理器
178
+ - `UIManager` - UI 管理器
179
+ - `ResLoader` - 资源加载器
180
+ - `HttpManager` - HTTP 管理器
181
+ - `WebSocketManager` - WebSocket 管理器
182
+ - `RedDotManager` - 红点管理器
131
183
 
132
- 框架提供了基于类型的事件系统,通过Broadcaster实现。
184
+ **自定义服务:**
133
185
 
134
186
  ```typescript
135
- // 监听事件
136
- mf.event.on('eventKey', (data) => {
137
- // 处理事件
138
- });
187
+ import { ServiceLocator } from 'dzkcc-mflow/core';
139
188
 
140
- // 广播事件
141
- mf.event.dispatch('eventKey', data);
189
+ // 注册服务
190
+ ServiceLocator.regService('MyService', new MyService());
142
191
 
143
- // 一次性监听
144
- mf.event.once('eventKey', (data) => {
145
- // 只会触发一次
146
- });
192
+ // 获取服务
193
+ const myService = ServiceLocator.getService<MyService>('MyService');
194
+
195
+ // 移除服务
196
+ ServiceLocator.remove('MyService');
147
197
  ```
148
198
 
149
- ### 4.2 粘性事件
199
+ ### 3.4 Manager 管理器
150
200
 
151
- 粘性事件可以在没有监听者时暂存,等有监听者时再触发:
201
+ Manager 负责处理特定领域的业务逻辑。
152
202
 
153
- ```typescript
154
- // 发送粘性事件
155
- mf.event.dispatchSticky('stickyEvent', data);
203
+ **基类:** `AbstractManager`
156
204
 
157
- // 移除粘性事件
158
- mf.event.removeStickyBroadcast('stickyEvent');
159
- ```
205
+ **生命周期:**
206
+ 1. `initialize()` - 在注册时被调用
207
+ 2. `dispose()` - 在销毁时被调用
160
208
 
161
- ## 5. 资源加载系统
209
+ **内置能力:**
210
+ - 获取 Model:`this.getModel(modelSymbol)`
211
+ - 获取事件管理器:`this.getEventManager()`
212
+ - 获取 HTTP 管理器:`this.getHttpManager()`
213
+ - 获取 WebSocket 管理器:`this.getWebSocketManager()`
162
214
 
163
- ### 5.1 ResLoader资源加载器
215
+ ### 3.5 Model 数据模型
164
216
 
165
- ResLoader提供了统一的资源加载和释放接口:
217
+ Model 用于数据管理,遵循单一职责原则。
166
218
 
167
- ```typescript
168
- // 加载预制体
169
- const prefab = await mf.res.loadPrefab('path/to/prefab');
219
+ **接口:** `IModel`
170
220
 
171
- // 加载精灵帧
172
- const spriteFrame = await mf.res.loadSpriteFrame(spriteComponent, 'path/to/sprite');
221
+ **生命周期:**
222
+ - `initialize()` - 在注册时被调用
173
223
 
174
- // 加载Spine动画
175
- const spineData = await mf.res.loadSpine(spineComponent, 'path/to/spine');
224
+ ### 3.6 View 视图
176
225
 
177
- // 释放资源
178
- mf.res.release('path/to/asset');
179
- ```
226
+ View 是 UI 界面的基类,提供完整的生命周期管理。
180
227
 
181
- ## 6. HTTP网络请求系统
228
+ **基类:** `BaseView`
182
229
 
183
- ### 6.1 HttpManager HTTP管理器
230
+ **生命周期:**
231
+ 1. `onEnter(args?)` - 界面打开时调用
232
+ 2. `onPause()` - 界面被覆盖时调用(栈模式)
233
+ 3. `onResume()` - 界面恢复显示时调用(栈模式)
234
+ 4. `onExit()` - 界面关闭时调用(自动清理事件)
235
+ 5. `onDestroy()` - 界面销毁时调用(自动释放资源)
184
236
 
185
- HttpManager提供了简洁易用的HTTP客户端功能,支持常见的HTTP方法:
237
+ **内置能力:**
238
+ - 自动事件管理:通过 `this.event` 监听的事件会自动清理
239
+ - 自动资源管理:通过 `this.res` 加载的资源会自动释放
240
+ - 获取 Manager:`this.getManager(managerSymbol)`
241
+ - 获取 Model:`this.getModel(modelSymbol)`
186
242
 
187
- ```typescript
188
- // GET 请求
189
- const userData = await mf.http.get('/api/users/123', { includeProfile: true });
243
+ ### 3.7 Symbol 映射系统
190
244
 
191
- // POST 请求
192
- const newUser = await mf.http.post('/api/users', {
193
- name: 'John',
194
- email: 'john@example.com'
195
- });
245
+ 框架使用 Symbol 作为标识符,配合 Names 对象实现类型安全和代码补全。
196
246
 
197
- // PUT 请求
198
- const updatedUser = await mf.http.put('/api/users/123', {
199
- name: 'John Doe'
200
- });
247
+ **三种 Names 对象:**
248
+ - `ModelNames` - Model Symbol 映射
249
+ - `ManagerNames` - Manager 的 Symbol 映射
250
+ - `ViewNames` - View 的 Symbol 映射
201
251
 
202
- // DELETE 请求
203
- await mf.http.delete('/api/users/123');
252
+ **优势:**
253
+ - ✅ IDE 代码补全
254
+ - ✅ 类型安全
255
+ - ✅ 避免字符串拼写错误
256
+ - ✅ 便于重构
204
257
 
205
- // 自定义请求
206
- const result = await mf.http.request({
207
- url: '/api/upload',
208
- method: 'POST',
209
- data: formData,
210
- headers: {
211
- 'Authorization': 'Bearer token'
212
- },
213
- timeout: 30000
214
- });
215
- ```
258
+ ## 4. 装饰器系统
216
259
 
217
- ### 6.2 功能特性
260
+ 框架提供了三个核心装饰器,用于注册 Manager、Model 和 View。
218
261
 
219
- 1. **Promise-based API**:所有请求都返回Promise,支持async/await
220
- 2. **超时控制**:默认10秒超时,可自定义
221
- 3. **自动JSON解析**:自动解析JSON响应
222
- 4. **错误处理**:统一的错误处理机制
223
- 5. **请求拦截**:支持自定义请求头
224
- 6. **URL参数处理**:自动处理GET请求的查询参数
262
+ ### 4.1 @manager() - Manager 装饰器
225
263
 
226
- ### 6.3 使用示例
264
+ 用于注册 Manager 到全局注册表。
227
265
 
228
266
  ```typescript
229
- // 在Manager中使用
230
- @manager()
231
- export class UserManager extends AbstractManager {
232
- async getUserProfile(userId: string): Promise<any> {
233
- try {
234
- const profile = await mf.http.get(`/api/users/${userId}/profile`);
235
- return profile;
236
- } catch (error) {
237
- console.error('Failed to fetch user profile:', error);
238
- throw error;
239
- }
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 初始化');
240
275
  }
241
276
 
242
- async updateUser(userId: string, data: any): Promise<any> {
243
- try {
244
- const result = await mf.http.put(`/api/users/${userId}`, data, {
245
- 'Authorization': `Bearer ${this.getAuthToken()}`
246
- });
247
- return result;
248
- } catch (error) {
249
- console.error('Failed to update user:', error);
250
- throw error;
251
- }
277
+ dispose(): void {
278
+ console.log('GameManager 销毁');
252
279
  }
253
280
 
254
- private getAuthToken(): string {
255
- // 获取认证令牌的逻辑
256
- return 'your-auth-token';
281
+ addScore(value: number): void {
282
+ this.score += value;
283
+ this.getEventManager().dispatch('scoreChanged', this.score);
257
284
  }
258
285
  }
259
- ```
260
286
 
261
- ## 7. WebSocket 实时通信系统
287
+ // 使用
288
+ const gameManager = mf.core.getManager(ManagerNames.Game);
289
+ gameManager.addScore(10);
290
+ ```
262
291
 
263
- ### 7.1 WebSocketManager WebSocket管理器
292
+ ### 4.2 @model() - Model 装饰器
264
293
 
265
- WebSocketManager提供了完整的 WebSocket 客户端功能,支持:
294
+ 用于注册 Model 到全局注册表。
266
295
 
267
296
  ```typescript
268
- // 连接 WebSocket 服务器
269
- mf.socket.connect('ws://localhost:8080/game');
270
-
271
- // 或使用安全连接
272
- mf.socket.connect('wss://game-server.example.com/ws');
273
-
274
- // 配置自动重连和心跳
275
- mf.socket.configure({
276
- reconnect: true, // 启用自动重连
277
- reconnectInterval: 3000, // 重连间隔 3 秒
278
- reconnectAttempts: 5, // 最多重连 5 次
279
- heartbeat: true, // 启用心跳
280
- heartbeatInterval: 30000, // 心跳间隔 30 秒
281
- heartbeatMessage: 'ping' // 心跳消息
282
- });
283
-
284
- // 监听事件
285
- mf.socket.on('open', (event) => {
286
- console.log('连接成功');
287
- });
288
-
289
- mf.socket.on('message', (event) => {
290
- console.log('收到消息:', event.data);
291
- });
292
-
293
- mf.socket.on('error', (event) => {
294
- console.error('连接错误:', event);
295
- });
296
-
297
- mf.socket.on('close', (event) => {
298
- console.log('连接关闭');
299
- });
297
+ import { model, IModel, ModelNames } from 'dzkcc-mflow/core';
300
298
 
301
- // 发送消息(支持多种数据类型)
302
- // 1. 发送对象(自动转换为 JSON)
303
- mf.socket.send({ type: 'move', x: 100, y: 200 });
304
-
305
- // 2. 发送字符串
306
- mf.socket.send('Hello Server');
307
-
308
- // 检查连接状态
309
- if (mf.socket.isConnected()) {
310
- // 已连接
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
+ }
311
323
  }
312
324
 
313
- // 断开连接
314
- mf.socket.disconnect();
325
+ // 使用
326
+ const userModel = mf.core.getModel(ModelNames.User);
327
+ userModel.playerName = 'Alice';
328
+ userModel.levelUp();
315
329
  ```
316
330
 
317
- ### 7.2 发送不同类型的数据
331
+ ### 4.3 @view() - View 装饰器
318
332
 
319
- WebSocket 支持多种数据类型的发送:
333
+ 用于注册 View 到全局注册表,配合 `ViewNames` 使用。
320
334
 
321
335
  ```typescript
322
- // ==================== 1. 发送 JSON 对象(推荐)====================
323
- // 自动序列化为 JSON 字符串
324
- mf.socket.send({
325
- type: 'player_move',
326
- position: { x: 100, y: 200 },
327
- timestamp: Date.now()
328
- });
336
+ import { view, ViewNames } from 'dzkcc-mflow/core';
337
+ import { BaseView } from 'dzkcc-mflow/libs';
338
+ import { _decorator, Button, Label } from 'cc';
329
339
 
330
- // ==================== 2. 发送纯文本 ====================
331
- mf.socket.send('ping');
340
+ const { ccclass, property } = _decorator;
332
341
 
333
- // ==================== 3. 发送二进制数据(ArrayBuffer)====================
334
- // 适用场景:发送游戏状态快照、地图数据等需要高效传输的场景
335
- function sendBinaryData() {
336
- // 创建 ArrayBuffer(8 字节)
337
- const buffer = new ArrayBuffer(8);
338
- const view = new DataView(buffer);
339
-
340
- // 写入玩家 ID(4 字节整数)
341
- view.setInt32(0, 12345, true);
342
+ @view('Home') // 注册为 'Home'
343
+ @ccclass('HomeView')
344
+ export class HomeView extends BaseView {
345
+ @property(Label)
346
+ titleLabel: Label = null!;
342
347
 
343
- // 写入玩家位置(2 个 2 字节整数)
344
- view.setInt16(4, 100, true); // x 坐标
345
- view.setInt16(6, 200, true); // y 坐标
348
+ @property(Button)
349
+ startButton: Button = null!;
346
350
 
347
- // 发送二进制数据
348
- mf.socket.send(buffer);
349
- }
350
-
351
- // 接收二进制数据示例
352
- mf.socket.on('message', (event: MessageEvent) => {
353
- if (event.data instanceof ArrayBuffer) {
354
- const view = new DataView(event.data);
355
- const playerId = view.getInt32(0, true);
356
- const x = view.getInt16(4, true);
357
- const y = view.getInt16(6, true);
358
- console.log(`玩家 ${playerId} 移动到 (${x}, ${y})`);
351
+ onEnter(args?: any): void {
352
+ console.log('HomeView 打开', args);
353
+ this.startButton.node.on('click', this.onStartClick, this);
359
354
  }
360
- });
361
-
362
- // ==================== 4. 发送文件(Blob)====================
363
- // 适用场景:上传截图、录像回放、自定义地图等
364
- async function sendScreenshot() {
365
- // 方式 1:从 Canvas 获取 Blob
366
- const canvas = document.querySelector('canvas') as HTMLCanvasElement;
367
- canvas.toBlob((blob) => {
368
- if (blob) {
369
- mf.socket.send(blob);
370
- }
371
- }, 'image/png');
372
355
 
373
- // 方式 2:从文件选择器获取
374
- const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
375
- const file = fileInput.files?.[0];
376
- if (file) {
377
- mf.socket.send(file);
356
+ onExit(): void {
357
+ // 自动清理事件监听
378
358
  }
379
359
 
380
- // 方式 3:创建自定义 Blob
381
- const data = new Blob(['自定义数据内容'], { type: 'text/plain' });
382
- mf.socket.send(data);
383
- }
384
-
385
- // 接收 Blob 数据示例
386
- mf.socket.on('message', async (event: MessageEvent) => {
387
- if (event.data instanceof Blob) {
388
- // 读取 Blob 数据
389
- const text = await event.data.text();
390
- console.log('收到文件数据:', text);
391
-
392
- // 或者作为 ArrayBuffer 读取
393
- const buffer = await event.data.arrayBuffer();
394
- console.log('文件大小:', buffer.byteLength, '字节');
360
+ onPause(): void {
361
+ console.log('HomeView 暂停');
395
362
  }
396
- });
397
-
398
- // ==================== 5. 发送 TypedArray(Uint8Array 等)====================
399
- // 适用场景:发送图像数据、音频流等
400
- function sendImageData() {
401
- // 创建一个 256 字节的数据
402
- const imageData = new Uint8Array(256);
403
- for (let i = 0; i < imageData.length; i++) {
404
- imageData[i] = i;
363
+
364
+ onResume(): void {
365
+ console.log('HomeView 恢复');
405
366
  }
406
367
 
407
- // 发送 TypedArray(会自动转换为 ArrayBuffer)
408
- mf.socket.send(imageData.buffer);
368
+ private onStartClick(): void {
369
+ mf.gui.open(ViewNames.Game);
370
+ }
409
371
  }
410
- ```
411
372
 
373
+ // 使用 - 通过 Symbol 打开视图
374
+ await mf.gui.open(ViewNames.Home, { userId: 123 });
412
375
 
413
- ### 7.3 功能特性
376
+ // 关闭视图
377
+ mf.gui.close(ViewNames.Home); // 关闭但保留缓存
378
+ mf.gui.close(ViewNames.Home, true); // 关闭并销毁
379
+ ```
414
380
 
415
- 1. **自动重连**:连接断开后自动尝试重连,可配置重连次数和间隔
416
- 2. **心跳检测**:定期发送心跳消息保持连接
417
- 3. **消息队列**:连接断开时缓存消息,重连后自动发送
418
- 4. **事件管理**:统一的事件监听和触发机制
419
- 5. **连接状态管理**:实时获取连接状态
420
- 6. **自动序列化**:对象类型自动转换为 JSON 字符串
421
- 7. **多数据类型支持**:支持 string、object、ArrayBuffer、Blob 等
381
+ ### 4.4 装饰器参数
422
382
 
423
- ### 7.4 实战案例:多人对战游戏
383
+ 所有装饰器都支持可选的 `name` 参数:
424
384
 
425
385
  ```typescript
426
- // ==================== 案例 1:实时对战位置同步 ====================
427
- // 使用二进制数据传输,减少带宽占用
428
- class BattleNetworkManager {
429
- // 发送玩家位置(二进制,只需 12 字节)
430
- sendPlayerPosition(playerId: number, x: number, y: number, rotation: number) {
431
- const buffer = new ArrayBuffer(12);
432
- const view = new DataView(buffer);
433
-
434
- view.setInt32(0, playerId, true); // 玩家 ID(4 字节)
435
- view.setFloat32(4, x, true); // X 坐标(4 字节)
436
- view.setFloat32(8, y, true); // Y 坐标(4 字节)
437
-
438
- mf.socket.send(buffer);
439
- }
440
-
441
- // 接收其他玩家位置
442
- setupPositionReceiver() {
443
- mf.socket.on('message', (event: MessageEvent) => {
444
- if (event.data instanceof ArrayBuffer) {
445
- const view = new DataView(event.data);
446
- const playerId = view.getInt32(0, true);
447
- const x = view.getFloat32(4, true);
448
- const y = view.getFloat32(8, true);
449
-
450
- // 更新其他玩家位置
451
- this.updateOtherPlayerPosition(playerId, x, y);
452
- }
453
- });
454
- }
455
-
456
- private updateOtherPlayerPosition(playerId: number, x: number, y: number) {
457
- // 更新游戏中其他玩家的位置
458
- console.log(`玩家 ${playerId} 移动到 (${x}, ${y})`);
459
- }
460
- }
386
+ // 指定名称
387
+ @manager('Game')
388
+ @model('User')
389
+ @view('Home')
390
+
391
+ // 不指定名称时,自动使用类名
392
+ @manager() // 使用 'GameManager'
393
+ @model() // 使用 'UserModel'
394
+ @view() // 使用 'HomeView'
395
+ ```
461
396
 
462
- // ==================== 案例 2:截图分享功能 ====================
463
- // 使用 Blob 上传游戏截图
464
- class ScreenshotManager {
465
- async captureAndSend() {
466
- // 获取游戏 Canvas
467
- const canvas = document.querySelector('canvas') as HTMLCanvasElement;
468
-
469
- // 转换为 Blob
470
- canvas.toBlob((blob) => {
471
- if (blob) {
472
- // 发送截图到服务器
473
- mf.socket.send(blob);
474
- console.log(`发送截图,大小: ${blob.size} 字节`);
475
- }
476
- }, 'image/jpeg', 0.8); // JPEG 格式,80% 质量
477
- }
478
-
479
- // 接收其他玩家的截图
480
- setupScreenshotReceiver() {
481
- mf.socket.on('message', async (event: MessageEvent) => {
482
- if (event.data instanceof Blob) {
483
- // 创建图片 URL
484
- const imageUrl = URL.createObjectURL(event.data);
485
-
486
- // 显示图片
487
- const img = new Image();
488
- img.src = imageUrl;
489
- document.body.appendChild(img);
490
-
491
- console.log('收到截图');
492
- }
493
- });
494
- }
495
- }
397
+ ### 4.5 Names 对象代码补全
496
398
 
497
- // ==================== 案例 3:混合数据类型 ====================
498
- // 根据消息类型选择最优传输方式
499
- class SmartNetworkManager {
500
- send(messageType: string, data: any) {
501
- switch (messageType) {
502
- case 'chat':
503
- // 聊天消息:使用 JSON
504
- mf.socket.send({
505
- type: 'chat',
506
- message: data.message,
507
- playerId: data.playerId
508
- });
509
- break;
510
-
511
- case 'position':
512
- // 位置更新:使用二进制(高频更新)
513
- this.sendPositionBinary(data);
514
- break;
515
-
516
- case 'screenshot':
517
- // 截图:使用 Blob
518
- mf.socket.send(data.blob);
519
- break;
520
-
521
- case 'skill':
522
- // 技能释放:使用 JSON
523
- mf.socket.send({
524
- type: 'skill',
525
- skillId: data.skillId,
526
- targetId: data.targetId,
527
- timestamp: Date.now()
528
- });
529
- break;
530
- }
531
- }
532
-
533
- private sendPositionBinary(data: any) {
534
- const buffer = new ArrayBuffer(12);
535
- const view = new DataView(buffer);
536
- view.setInt32(0, data.playerId, true);
537
- view.setFloat32(4, data.x, true);
538
- view.setFloat32(8, data.y, true);
539
- mf.socket.send(buffer);
540
- }
541
- }
399
+ 装饰器注册后,对应的 Names 对象会自动添加属性,IDE 会提供代码补全:
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
542
416
  ```
543
417
 
544
- ### 7.5 Manager 集成示例
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
+ **基本用法:**
545
433
 
546
434
  ```typescript
547
- // 在Manager中使用
548
- @manager()
549
- export class GameNetworkManager extends AbstractManager {
550
- initialize() {
551
- // 配置 WebSocket
552
- mf.socket.configure({
553
- reconnect: true,
554
- reconnectInterval: 3000,
555
- reconnectAttempts: 10,
556
- heartbeat: true,
557
- heartbeatInterval: 30000
558
- });
559
-
560
- // 设置事件监听
561
- mf.socket.on('open', this.onConnected.bind(this));
562
- mf.socket.on('message', this.onMessage.bind(this));
563
- mf.socket.on('error', this.onError.bind(this));
564
- mf.socket.on('close', this.onClose.bind(this));
565
- }
566
-
567
- connect(token: string) {
568
- const wsUrl = `wss://game-server.example.com/ws?token=${token}`;
569
- mf.socket.connect(wsUrl);
570
- }
571
-
572
- private onConnected(event: Event) {
573
- console.log('连接成功');
574
- this.sendLogin();
575
- }
435
+ import { view, ViewNames, ManagerNames, ModelNames } from 'dzkcc-mflow/core';
436
+ import { BaseView } from 'dzkcc-mflow/libs';
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!;
576
446
 
577
- private onMessage(event: MessageEvent) {
578
- const data = JSON.parse(event.data);
579
- // 处理服务器消息
580
- this.handleServerMessage(data);
581
- }
447
+ @property(Sprite)
448
+ playerSprite: Sprite = null!;
582
449
 
583
- private onError(event: Event) {
584
- console.error('连接错误');
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);
585
462
  }
586
463
 
587
- private onClose(event: CloseEvent) {
588
- console.log('连接关闭');
464
+ onExit(): void {
465
+ // 事件监听会自动清理,无需手动 off
589
466
  }
590
467
 
591
- sendPlayerMove(x: number, y: number) {
592
- // 直接发送对象,自动序列化为 JSON
593
- mf.socket.send({
594
- type: 'player_move',
595
- position: { x, y },
596
- timestamp: Date.now()
597
- });
468
+ onPause(): void {
469
+ // 界面被其他界面覆盖时调用
598
470
  }
599
471
 
600
- private sendLogin() {
601
- // 直接发送对象,自动序列化为 JSON
602
- mf.socket.send({
603
- type: 'login',
604
- userId: this.getUserId()
605
- });
472
+ onResume(): void {
473
+ // 界面从暂停状态恢复时调用
606
474
  }
607
475
 
608
- private handleServerMessage(data: any) {
609
- // 使用事件系统分发消息
610
- mf.event.dispatch(`server_${data.type}`, data);
476
+ private onScoreChanged(score: number): void {
477
+ this.scoreLabel.string = `分数: ${score}`;
611
478
  }
612
479
  }
613
480
  ```
614
481
 
615
- ## 8. 开发工具
482
+ ### 5.2 UIManager 界面管理器
616
483
 
617
- 框架配套了Cocos Creator编辑器插件`mflow-tools`,可以:
484
+ 通过 `mf.gui` 访问 UI 管理功能。
618
485
 
619
- 1. 自动生成UI脚本
620
- 2. 自动引用Prefab上需要操作的元素
621
- 3. 自动挂载脚本组件
486
+ **基本操作:**
622
487
 
623
- ### 8.1 使用方法
488
+ ```typescript
489
+ import { ViewNames } from 'dzkcc-mflow/core';
490
+
491
+ // 打开界面
492
+ const view = await mf.gui.open(ViewNames.Home);
493
+
494
+ // 打开界面并传参
495
+ await mf.gui.open(ViewNames.Game, { level: 1, difficulty: 'hard' });
624
496
 
625
- 1. 在Prefab中,将需要引用的节点重命名为`#属性名#组件类型`格式,例如:
626
- - `#titleLabel#Label` 表示引用Label组件
627
- - `#closeButton#Button` 表示引用Button组件
628
- - `#contentNode` 表示引用Node节点
497
+ // 关闭界面(保留缓存)
498
+ mf.gui.close(ViewNames.Home);
629
499
 
630
- 2. 在Hierarchy面板中右键点击Prefab节点,选择"导出到脚本"
500
+ // 关闭并销毁界面(释放资源)
501
+ mf.gui.close(ViewNames.Home, true);
631
502
 
632
- 3. 插件会自动生成基础脚本和业务脚本,并自动设置引用关系
503
+ // 通过视图实例关闭
504
+ const view = await mf.gui.open(ViewNames.Settings);
505
+ mf.gui.close(view);
506
+ ```
633
507
 
634
- ## 9. 完整示例
508
+ ### 5.3 视图栈管理
635
509
 
636
- ### 9.1 创建Manager
510
+ 支持分组的视图栈,适用于关卡、向导等场景。
511
+ 使用分组功能,还可以实现诸如游戏启动后,需要优先弹出一堆界面,比如各种活动的展示dialog、各种签到UI等等。把这些UI都归类一个group中,那么就可以做到,关闭一个UI后会再弹出下一个,直到所有的弹窗都关闭。同时在加入group的时候,还可以控制各个UI的先后顺序。
637
512
 
638
513
  ```typescript
639
- @manager()
640
- export class GameManager extends AbstractManager {
641
- private _score: number = 0;
642
-
643
- initialize(): void {
644
- console.log('GameManager initialized');
645
- }
646
-
647
- get score(): number {
648
- return this._score;
649
- }
650
-
651
- addScore(value: number): void {
652
- this._score += value;
653
- // 广播分数变化事件
654
- this.getEventManager().dispatch('scoreChanged', this._score);
655
- }
656
- }
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 恢复
522
+
523
+ // 清空整个栈
524
+ mf.gui.clearStack('game'); // 所有关卡视图关闭
525
+
526
+ // 清空栈并销毁
527
+ mf.gui.clearStack('game', true);
528
+
529
+ // 获取栈顶视图
530
+ const topView = mf.gui.getTopView();
657
531
  ```
658
532
 
659
- ### 9.2 创建Model
533
+ ### 5.4 视图生命周期详解
660
534
 
661
535
  ```typescript
662
- @model()
663
- export class GameModel implements IModel {
664
- private _playerName: string = '';
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
+ }
665
545
 
666
- initialize(): void {
667
- console.log('GameModel initialized');
546
+ // 2. 界面被其他界面覆盖时调用(栈模式)
547
+ onPause(): void {
548
+ console.log('界面暂停');
549
+ // 暂停动画
550
+ // 暂停计时器
668
551
  }
669
552
 
670
- get playerName(): string {
671
- return this._playerName;
553
+ // 3. 界面从暂停状态恢复时调用(栈模式)
554
+ onResume(): void {
555
+ console.log('界面恢复');
556
+ // 恢复动画
557
+ // 恢复计时器
672
558
  }
673
559
 
674
- set playerName(name: string) {
675
- this._playerName = name;
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
+ // 可以在这里做额外的清理工作
676
572
  }
677
573
  }
678
574
  ```
679
575
 
680
- ### 9.3 创建UI界面
576
+ ### 5.5 Prefab 路径配置
681
577
 
682
- ```typescript
683
- // BaseHomeView.ts (由工具自动生成)
684
- import { _decorator, Component, Label, Button } from 'cc';
685
- import { BaseView } from "dzkcc-mflow/libs";
578
+ 视图需要配置 Prefab 路径,框架提供了开发工具自动生成。
686
579
 
687
- const { ccclass, property, disallowMultiple } = _decorator;
580
+ **手动配置方式:**
688
581
 
689
- @disallowMultiple()
690
- export abstract class BaseHomeView extends BaseView {
582
+ ```typescript
583
+ @view('Home')
584
+ @ccclass('HomeView')
585
+ export class HomeView extends BaseView {
691
586
  /** @internal */
692
- private static readonly __path__: string = "ui/home";
587
+ private static readonly __path__: string = 'ui/home'; // Prefab 路径
693
588
 
694
- @property({ type: Label }) titleLabel: Label = null!;
695
- @property({ type: Button }) startButton: Button = null!;
589
+ onEnter(): void {}
590
+ onPause(): void {}
591
+ onResume(): void {}
696
592
  }
593
+ ```
697
594
 
698
- // HomeView.ts (业务实现)
699
- import { BaseHomeView } from './BaseHomeView';
700
- import { _decorator } from 'cc';
595
+ **推荐:使用开发工具自动生成**(见第 10 章)
701
596
 
702
- const { ccclass, property } = _decorator;
597
+ ---
703
598
 
704
- @ccclass('HomeView')
705
- export class HomeView extends BaseHomeView {
599
+ ## 6. 事件系统
600
+
601
+ 框架提供了强大的事件广播和监听机制,基于 `Broadcaster` 实现。
602
+
603
+ ### 6.1 基本用法
604
+
605
+ ```typescript
606
+ // 监听事件
607
+ mf.event.on('gameStart', (data) => {
608
+ console.log('游戏开始', data);
609
+ });
610
+
611
+ // 派发事件
612
+ 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
+ ```
769
+
770
+ ### 7.2 释放资源
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
+ ```
782
+
783
+ ### 7.3 在 View 中自动管理资源
784
+
785
+ `BaseView` 提供了自动资源管理:
786
+
787
+ ```typescript
788
+ @view('Game')
789
+ @ccclass('GameView')
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
834
+
835
+ // 释放资源,引用计数递减
836
+ mf.res.release(asset1); // refCount = 1
837
+ mf.res.release(asset2); // refCount = 0,资源被销毁
838
+ ```
839
+
840
+ ## 8. 网络通信
841
+
842
+ ### 8.1 HTTP 请求
843
+
844
+ 框架提供了简洁易用的 HTTP 客户端,通过 `mf.http` 访问。
845
+
846
+ **基本用法:**
847
+
848
+ ```typescript
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 中使用:**
882
+
883
+ ```typescript
884
+ @manager('User')
885
+ export class UserManager extends AbstractManager {
886
+ initialize(): void {}
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
+ **基本用法:**
924
+
925
+ ```typescript
926
+ // 连接服务器
927
+ 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
+ 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
+ ```
968
+
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);
983
+
984
+ // 4. Blob(文件数据)
985
+ const blob = new Blob(['data'], { type: 'text/plain' });
986
+ mf.socket.send(blob);
987
+ ```
988
+
989
+ **在 Manager 中使用:**
990
+
991
+ ```typescript
992
+ @manager('Network')
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
+ // 设置红点数量
1065
+ 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
+
1089
+ ```typescript
1090
+ // 监听红点变化
1091
+ mf.reddot.on('main/bag', (totalCount, selfCount) => {
1092
+ console.log(`背包红点: ${totalCount} (自身: ${selfCount})`);
1093
+ // 更新 UI 显示
1094
+ });
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
+ ```
1146
+
1147
+ ### 9.4 红点路径规则
1148
+
1149
+ 红点系统使用树形结构,路径使用 `/` 分隔:
1150
+
1151
+ ```
1152
+ main
1153
+ ├── bag
1154
+ │ ├── weapon
1155
+ │ ├── armor
1156
+ │ └── consumable
1157
+ ├── mail
1158
+ │ ├── system
1159
+ │ └── friend
1160
+ └── quest
1161
+ ├── main
1162
+ └── daily
1163
+ ```
1164
+
1165
+ **特性:**
1166
+ - 子节点的红点会自动累加到父节点
1167
+ - 支持任意深度的树形结构
1168
+ - 路径大小写敏感
1169
+
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
+ ```typescript
1247
+ import { _decorator } from 'cc';
1248
+ import { BaseHomeView } from './BaseHomeView';
1249
+ import { view } from 'dzkcc-mflow/core';
1250
+
1251
+ const { ccclass } = _decorator;
1252
+
1253
+ @view('Home')
1254
+ @ccclass('HomeView')
1255
+ export class HomeView extends BaseHomeView {
1256
+ onEnter(args?: any): void {
1257
+ // 实现业务逻辑
1258
+ }
1259
+
1260
+ onExit(): void {}
1261
+ onPause(): void {}
1262
+ onResume(): void {}
1263
+ }
1264
+ ```
1265
+
1266
+ 4. **脚本自动挂载**
1267
+
1268
+ 插件会自动将生成的脚本挂载到 Prefab 上,并设置好所有组件引用。
1269
+
1270
+ ---
1271
+
1272
+ ## 11. 完整示例
1273
+
1274
+ 下面是一个简单的塔防游戏示例,展示框架的完整使用流程。
1275
+
1276
+ ### 11.1 项目结构
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
1302
+ ```
1303
+
1304
+ ### 11.2 GameCore.ts - 游戏入口
1305
+
1306
+ ```typescript
1307
+ import { CocosCore } from 'dzkcc-mflow/libs';
1308
+ import { _decorator } from 'cc';
1309
+
1310
+ // 导入所有模块(使用装饰器后会自动注册)
1311
+ import './managers/GameManager';
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';
1319
+
1320
+ const { ccclass } = _decorator;
1321
+
1322
+ @ccclass('GameCore')
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
+ ```
1334
+
1335
+ ### 11.3 GameModel.ts - 游戏数据模型
1336
+
1337
+ ```typescript
1338
+ import { model, IModel } from 'dzkcc-mflow/core';
1339
+
1340
+ @model('Game')
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
+ ```
1391
+
1392
+ ### 11.4 PlayerModel.ts - 玩家数据模型
1393
+
1394
+ ```typescript
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
+ ```
1444
+
1445
+ ### 11.5 GameManager.ts - 游戏管理器
1446
+
1447
+ ```typescript
1448
+ import { manager, AbstractManager, ManagerNames, ModelNames } from 'dzkcc-mflow/core';
1449
+ import { GameModel } from '../models/GameModel';
1450
+ import { PlayerModel } from '../models/PlayerModel';
1451
+
1452
+ @manager('Game')
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
+ ```
1523
+
1524
+ ### 11.6 HomeView.ts - 主界面
1525
+
1526
+ ```typescript
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';
1531
+
1532
+ const { ccclass, property } = _decorator;
1533
+
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
+
706
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
+
707
1556
  // 监听按钮点击
708
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);
709
1570
 
710
- // 监听分数变化
711
- this.event.on('scoreChanged', this.onScoreChanged, this);
1571
+ // 打开游戏界面
1572
+ await mf.gui.open(ViewNames.Game, { level: 1 });
1573
+ }
1574
+ }
1575
+ ```
1576
+
1577
+ ### 11.7 GameView.ts - 游戏界面
1578
+
1579
+ ```typescript
1580
+ import { view, ViewNames, ManagerNames, ModelNames } from 'dzkcc-mflow/core';
1581
+ import { BaseView } from 'dzkcc-mflow/libs';
1582
+ import { _decorator, Label, Node } from 'cc';
1583
+ import { GameManager } from '../managers/GameManager';
1584
+ import { GameModel } from '../models/GameModel';
1585
+
1586
+ const { ccclass, property } = _decorator;
1587
+
1588
+ @view('Game')
1589
+ @ccclass('GameView')
1590
+ export class GameView extends BaseView {
1591
+ @property(Label)
1592
+ scoreLabel: Label = null!;
1593
+
1594
+ @property(Label)
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();
712
1622
  }
713
1623
 
714
1624
  onExit(): void {
715
- // BaseView会自动清理事件监听
1625
+ // BaseView 会自动清理事件监听
716
1626
  }
717
1627
 
718
- onPause(): void {
719
- // 界面暂停时的处理
1628
+ onPause(): void {}
1629
+ onResume(): void {}
1630
+
1631
+ private onEnemyKilled(data: any): void {
1632
+ this.updateUI();
720
1633
  }
721
1634
 
722
- onResume(): void {
723
- // 界面恢复时的处理
1635
+ private onLifeChanged(life: number): void {
1636
+ this.lifeLabel.string = `生命: ${life}`;
724
1637
  }
725
1638
 
726
- private onStartClick(): void {
727
- // 获取GameManager并调用方法
728
- const gameManager = this.getManager(GameManager);
729
- gameManager.addScore(10);
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
+ });
730
1648
  }
731
1649
 
732
- private onScoreChanged(score: number): void {
733
- this.titleLabel.string = `分数: ${score}`;
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}`;
734
1654
  }
735
1655
  }
736
1656
  ```
737
1657
 
738
- ### 9.4 在场景中使用
1658
+ ### 11.8 ResultView.ts - 结算界面
739
1659
 
740
1660
  ```typescript
741
- // 在游戏启动时
742
- export class GameApp extends Component {
743
- start(): void {
744
- // 打开主界面
745
- mf.gui.open(HomeView);
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);
746
1704
  }
747
1705
  }
748
1706
  ```
749
1707
 
750
- ## 10. 最佳实践
1708
+ ---
1709
+
1710
+ ## 12. 最佳实践
1711
+
1712
+ ### 12.1 设计原则
1713
+
1714
+ ✅ **单一职责** - 每个 Manager/Model 只负责一个特定领域
1715
+ ✅ **依赖注入** - 使用装饰器自动注入依赖
1716
+ ✅ **事件驱动** - 通过事件系统实现模块解耦
1717
+ ✅ **资源管理** - 使用 BaseView 自动管理资源生命周期
1718
+
1719
+ ### 12.2 命名规范
1720
+
1721
+ - **Manager**: 以 `Manager` 结尾,如 `GameManager`、`AudioManager`
1722
+ - **Model**: 以 `Model` 结尾,如 `UserModel`、`ConfigModel`
1723
+ - **View**: 以 `View` 结尾,如 `HomeView`、`GameView`
1724
+ - **装饰器名称**: 简短清晰,如 `@manager('Game')`、`@model('User')`
1725
+
1726
+ ### 12.3 项目结构
1727
+
1728
+ ```
1729
+ assets/scripts/
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
1749
+ ```
1750
+
1751
+ ### 12.4 注意事项
1752
+
1753
+ ⚠️ **避免循环依赖** - Manager 不应该相互依赖,通过事件系统通信
1754
+ ⚠️ **资源释放** - 使用 `BaseView` 的自动资源管理,避免内存泄漏
1755
+ ⚠️ **事件清理** - 在 Manager 的 `dispose()` 中清理事件监听
1756
+ ⚠️ **异步处理** - 注意 UI 打开/关闭的异步操作,使用 `await`
1757
+ ⚠️ **WebSocket 连接** - 在场景切换时记得断开连接
1758
+
1759
+ ---
1760
+
1761
+ ## 13. License
1762
+
1763
+ MIT License
1764
+
1765
+ Copyright (c) 2024 duanzhk
1766
+
1767
+ ---
1768
+
1769
+ ## 14. 支持与反馈
751
1770
 
752
- 1. **模块化设计**:将相关的业务逻辑封装在对应的Manager中
753
- 2. **数据驱动**:使用Model管理数据状态
754
- 3. **事件解耦**:通过事件系统实现模块间通信
755
- 4. **资源管理**:使用BaseView自动管理资源加载和释放
756
- 5. **依赖注入**:使用装饰器简化依赖管理
757
- 6. **网络请求**:使用HttpManager统一管理网络请求
758
- 7. **实时通信**:使用WebSocketManager处理实时消息,配合事件系统分发
759
- 8. **工具辅助**:使用mflow-tools提高开发效率
1771
+ - **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
+ - **问题反馈**: [Issues](https://github.com/duanzhk/cocos-modular-flow-framework/issues)
760
1774
 
761
- ## 11. 注意事项
1775
+ ---
762
1776
 
763
- 1. 确保在使用框架功能前Core已经初始化
764
- 2. 注意资源的正确加载和释放,避免内存泄漏
765
- 3. 合理使用事件系统,避免事件监听过多影响性能
766
- 4. 使用BaseView的子类时,确保正确实现所有抽象方法
767
- 5. 网络请求时注意错误处理和超时设置
768
- 6. WebSocket 连接时记得在场景切换时断开连接,避免内存泄漏
769
- 7. 合理配置心跳和重连参数,平衡连接稳定性和服务器压力
1777
+ Made with ❤️ by duanzhk