1845 lines
48 KiB
Markdown
1845 lines
48 KiB
Markdown
# Cocos3.x 项目开发规划
|
|
|
|
## 项目概述
|
|
本项目是一个 Cocos Creator 3.x 的 3D Roguelike 游戏项目,需要实现网络通信、状态机管理和应用状态流转等核心功能。
|
|
|
|
## 📐 开发规范
|
|
|
|
### 代码组织规范
|
|
1. **不创建 README.md**: 功能模块内不需要创建 README.md 文档,所有文档集中在 `.github/instructions` 目录
|
|
2. **不创建 index.ts**: 不需要为每个模块创建统一导出的 index.ts 文件,直接导入具体文件
|
|
3. **按需导入**: 使用时直接从具体文件导入,例如:
|
|
```typescript
|
|
import { NetManager } from './Framework/Net/NetManager';
|
|
import { NetConfig } from './Framework/Net/NetConfig';
|
|
```
|
|
4. **文档集中管理**: 所有开发文档、规划文档统一放在 `.github/instructions` 目录
|
|
5. **模块归属规范**:
|
|
- **Framework/** 目录用于存放通用框架和工具类(FSM、Net、ResMgr、UI基类等)
|
|
- **App/** 目录用于存放应用层业务模块(AppStatus、Login、Game等)
|
|
- **业务UI组件必须归属到 App/ 对应的业务模块下**,例如:
|
|
- 登录UI: `App/Login/UILogin.ts`
|
|
- 游戏UI: `App/Game/UIGame.ts`
|
|
|
|
### 文件命名规范
|
|
- 使用 PascalCase 命名 TypeScript 类文件: `NetManager.ts`, `AppStatusBoot.ts`
|
|
- 配置/常量文件也使用 PascalCase: `NetConfig.ts`, `NetEvent.ts`
|
|
- 一个文件一个主要类,文件名与类名保持一致
|
|
|
|
---
|
|
|
|
## 📊 项目进度总览
|
|
|
|
**最后更新**: 2025-12-14
|
|
|
|
| 模块 | 进度 | 状态 |
|
|
|------|------|------|
|
|
| Framework/FSM | 100% | ✅ 已完成 |
|
|
| Framework/Net | 100% | ✅ 已完成 |
|
|
| Framework/ResMgr | 100% | ✅ 已完成 |
|
|
| Framework/UI | 100% | ✅ 已完成 |
|
|
| App/AppStatus | 100% | ✅ 已完成 |
|
|
| App/Login | 100% | ✅ 已完成 |
|
|
| Boot启动组件 | 100% | ✅ 已完成 |
|
|
| 游戏逻辑 | 0% | ⬜ 未开始 |
|
|
|
|
**图例**: ⬜ 未开始 | 🟡 进行中 | ✅ 已完成 | ⏸️ 已暂停
|
|
|
|
---
|
|
|
|
## 一、目录结构规划
|
|
|
|
```
|
|
assets/scripts/
|
|
├── Framework/ # 框架和通用工具类目录
|
|
│ ├── Net/ # 网络通信模块 ✅
|
|
│ ├── FSM/ # 状态机模块 ✅
|
|
│ ├── ResMgr/ # 资源管理模块 ✅
|
|
│ ├── UI/ # UI基类和管理器 ✅
|
|
│ │ ├── UIBase.ts # UI基类
|
|
│ │ └── UIMgr.ts # UI管理器
|
|
│ └── ... # 其他框架模块
|
|
├── App/ # 应用层代码(业务模块)
|
|
│ ├── AppStatus/ # 应用状态机 ✅
|
|
│ ├── Login/ # 登录模块 ✅
|
|
│ │ └── UILogin.ts # 登录界面
|
|
│ ├── Game/ # 游戏模块
|
|
│ │ └── ... # 游戏相关UI和逻辑
|
|
│ └── ... # 其他业务模块
|
|
└── Boot/ # 启动组件 ✅
|
|
└── Boot.ts # 场景挂载入口点
|
|
```
|
|
|
|
---
|
|
|
|
## 二、网络通信模块 (Framework/Net)
|
|
|
|
### 2.1 目标
|
|
实现基于 TSRPC 的网络通信层,支持多平台(浏览器、小程序)。
|
|
|
|
### 2.2 依赖包选择
|
|
|
|
| 平台 | NPM 包 |
|
|
|------|--------|
|
|
| 浏览器 (Web) | `tsrpc-browser` |
|
|
| 小程序 (微信/抖音/QQ) | `tsrpc-miniapp` |
|
|
|
|
### 2.3 已实现的核心类
|
|
|
|
#### 2.3.1 `NetManager.ts` - 网络管理器 ✅
|
|
**职责**: 网络连接管理、消息收发、重连机制
|
|
|
|
**核心方法**:
|
|
- `setServiceProto(serviceProto)` - 设置服务协议 (必须在 init 之前调用)
|
|
- `init(config: NetConfig)` - 初始化网络配置
|
|
- `connect()` - 创建客户端实例
|
|
- `disconnect()` - 清理客户端资源
|
|
- `callApi<Req, Res>(apiName, req)` - 调用 API
|
|
- `listenMsg(msgName, handler)` - 监听服务器消息
|
|
- `sendMsg(msgName, msg)` - 发送消息到服务器
|
|
- `on(event, callback)` - 监听网络事件
|
|
|
|
**使用示例**:
|
|
```typescript
|
|
import { NetManager } from './Framework/Net/NetManager';
|
|
import { NetConfig } from './Framework/Net/NetConfig';
|
|
import { serviceProto } from '../Shared/protocols/serviceProto';
|
|
|
|
const netManager = NetManager.getInstance();
|
|
netManager.setServiceProto(serviceProto); // 必须先设置协议
|
|
netManager.init({ serverUrl: 'http://localhost:3000' });
|
|
await netManager.connect();
|
|
```
|
|
|
|
#### 2.3.2 `PlatformAdapter.ts` - 平台适配器 ✅
|
|
**职责**: 根据运行平台创建对应的 TSRPC 客户端
|
|
|
|
**技术实现**:
|
|
- 使用别名导入: `HttpClient as HttpClientBrowser` 和 `HttpClient as HttpClientMiniapp`
|
|
- 自动检测 Cocos 平台类型 (sys.platform)
|
|
- 根据平台实例化对应的客户端
|
|
|
|
**核心方法**:
|
|
- `setServiceProto(serviceProto)` - 设置服务协议
|
|
- `detectPlatform()` - 检测当前运行平台
|
|
- `createClient(config)` - 创建对应平台的客户端实例
|
|
- `isMiniApp()` / `isBrowser()` - 平台判断
|
|
- `getPlatformInfo()` - 获取平台详细信息
|
|
|
|
**使用示例**:
|
|
```typescript
|
|
import { PlatformAdapter } from './Framework/Net/PlatformAdapter';
|
|
|
|
const platform = PlatformAdapter.getCurrentPlatform();
|
|
console.log(PlatformAdapter.getPlatformInfo());
|
|
```
|
|
#### 2.3.3 `NetConfig.ts` - 网络配置 ✅
|
|
**职责**: 网络相关配置参数
|
|
|
|
```typescript
|
|
export interface NetConfig {
|
|
serverUrl: string; // 服务器地址
|
|
timeout?: number; // 超时时间(ms) 默认 30000
|
|
autoReconnect?: boolean; // 是否自动重连 默认 true
|
|
reconnectInterval?: number; // 重连间隔(ms) 默认 3000
|
|
maxReconnectTimes?: number; // 最大重连次数 默认 5
|
|
}
|
|
|
|
export const DefaultNetConfig: Partial<NetConfig>; // 默认配置
|
|
```
|
|
```
|
|
|
|
#### 2.3.4 `NetEvent.ts` - 网络事件 ✅
|
|
**职责**: 定义网络相关事件常量
|
|
|
|
```typescript
|
|
export enum NetEvent {
|
|
Connected = "net_connected", // 连接成功
|
|
Disconnected = "net_disconnected", // 连接断开
|
|
Reconnecting = "net_reconnecting", // 正在重连
|
|
ReconnectSuccess = "net_reconnect_success", // 重连成功
|
|
ReconnectFailed = "net_reconnect_failed", // 重连失败
|
|
Error = "net_error", // 网络错误
|
|
Timeout = "net_timeout" // 连接超时
|
|
}
|
|
```
|
|
|
|
### 2.4 共享协议同步 ✅
|
|
|
|
**同步脚本**: `sync-shared.js` (根目录)
|
|
- **服务端路径**: `../server/src/shared`
|
|
- **客户端路径**: `assets/scripts/Shared`
|
|
- **运行命令**: `npm run sync-shared`
|
|
|
|
**使用步骤**:
|
|
1. 确保服务端项目在 `../server` 目录
|
|
2. 运行 `npm run sync-shared` 同步协议
|
|
3. 从 Shared 目录导入协议: `import { serviceProto } from '../Shared/protocols/serviceProto'`
|
|
|
|
### 2.5 完整使用流程 ✅
|
|
|
|
```typescript
|
|
// 1. 导入模块
|
|
import { NetManager } from './Framework/Net/NetManager';
|
|
import { NetConfig } from './Framework/Net/NetConfig';
|
|
import { NetEvent } from './Framework/Net/NetEvent';
|
|
import { serviceProto } from '../Shared/protocols/serviceProto';
|
|
|
|
// 2. 获取实例并设置协议
|
|
const netManager = NetManager.getInstance();
|
|
netManager.setServiceProto(serviceProto); // 必须在 init 之前
|
|
|
|
// 3. 监听事件
|
|
netManager.on(NetEvent.Connected, () => console.log('已连接'));
|
|
netManager.on(NetEvent.Error, (err) => console.error('错误:', err));
|
|
|
|
// 4. 初始化和连接
|
|
const config: NetConfig = { serverUrl: 'http://localhost:3000' };
|
|
netManager.init(config);
|
|
await netManager.connect();
|
|
|
|
// 5. 调用 API
|
|
const result = await netManager.callApi('login', { username: 'test' });
|
|
```
|
|
|
|
---
|
|
|
|
## 三、状态机模块 (Framework/FSM)
|
|
|
|
### 3.1 目标
|
|
实现通用的有限状态机 (FSM) 框架,支持状态切换、状态生命周期管理。
|
|
|
|
### 3.2 核心类设计
|
|
|
|
#### 3.2.1 `IState.ts` - 状态接口
|
|
**职责**: 定义状态的标准接口
|
|
|
|
```typescript
|
|
/**
|
|
* 状态接口
|
|
*/
|
|
export interface IState {
|
|
// 状态名称
|
|
readonly name: string;
|
|
|
|
// 进入状态
|
|
onEnter(params?: any): void;
|
|
|
|
// 更新状态
|
|
onUpdate?(dt: number): void;
|
|
|
|
// 退出状态
|
|
onExit(): void;
|
|
}
|
|
```
|
|
|
|
#### 3.2.2 `FSM.ts` - 状态机
|
|
**职责**: 管理状态切换和生命周期
|
|
|
|
```typescript
|
|
/**
|
|
* 有限状态机
|
|
*/
|
|
export class FSM {
|
|
private _states: Map<string, IState>;
|
|
private _currentState: IState | null;
|
|
|
|
// 添加状态
|
|
addState(state: IState): void;
|
|
|
|
// 移除状态
|
|
removeState(stateName: string): void;
|
|
|
|
// 切换状态
|
|
changeState(stateName: string, params?: any): void;
|
|
|
|
// 获取当前状态
|
|
getCurrentState(): IState | null;
|
|
|
|
// 更新(在需要的情况下)
|
|
update(dt: number): void;
|
|
}
|
|
```
|
|
|
|
#### 3.2.3 `BaseState.ts` - 状态基类
|
|
**职责**: 提供状态的基础实现
|
|
|
|
```typescript
|
|
/**
|
|
* 状态基类
|
|
*/
|
|
export abstract class BaseState implements IState {
|
|
protected _fsm: FSM;
|
|
|
|
constructor(fsm: FSM, public readonly name: string) {
|
|
this._fsm = fsm;
|
|
}
|
|
|
|
onEnter(params?: any): void {
|
|
console.log(`[FSM] Enter state: ${this.name}`);
|
|
}
|
|
|
|
onUpdate?(dt: number): void {}
|
|
|
|
onExit(): void {
|
|
console.log(`[FSM] Exit state: ${this.name}`);
|
|
}
|
|
}
|
|
```
|
|
|
|
### 3.3 已实现的核心类
|
|
|
|
#### 3.3.1 `IState.ts` - 状态接口 ✅
|
|
**职责**: 定义状态机中状态的标准接口
|
|
|
|
**核心方法**:
|
|
- `readonly name: string` - 状态名称
|
|
- `onEnter(params?: any): void` - 进入状态时调用
|
|
- `onUpdate?(dt: number): void` - 更新状态(可选)
|
|
- `onExit(): void` - 退出状态时调用
|
|
|
|
#### 3.3.2 `BaseState.ts` - 状态基类 ✅
|
|
**职责**: 提供状态接口的基础实现
|
|
|
|
**核心属性和方法**:
|
|
- `protected _fsm: FSM` - 状态所属的状态机
|
|
- `readonly name: string` - 状态名称
|
|
- `constructor(fsm: FSM, name: string)` - 构造函数
|
|
- `onEnter(params?: any): void` - 进入状态(可重写)
|
|
- `onUpdate?(dt: number): void` - 更新状态(可重写)
|
|
- `onExit(): void` - 退出状态(可重写)
|
|
|
|
#### 3.3.3 `FSM.ts` - 状态机核心 ✅
|
|
**职责**: 管理状态的添加、移除和切换
|
|
|
|
**核心方法**:
|
|
- `addState(state: IState): void` - 添加状态
|
|
- `removeState(stateName: string): void` - 移除状态
|
|
- `changeState(stateName: string, params?: any): void` - 切换状态
|
|
- `getCurrentState(): IState | null` - 获取当前状态
|
|
- `getCurrentStateName(): string | null` - 获取当前状态名称
|
|
- `hasState(stateName: string): boolean` - 检查状态是否存在
|
|
- `getState(stateName: string): IState | undefined` - 获取状态实例
|
|
- `update(dt: number): void` - 更新状态机
|
|
- `clear(): void` - 清空所有状态
|
|
|
|
**使用示例**:
|
|
```typescript
|
|
import { FSM } from './Framework/FSM/FSM';
|
|
import { BaseState } from './Framework/FSM/BaseState';
|
|
|
|
// 1. 创建状态机
|
|
const fsm = new FSM();
|
|
|
|
// 2. 定义状态类
|
|
class IdleState extends BaseState {
|
|
constructor(fsm: FSM) {
|
|
super(fsm, "Idle");
|
|
}
|
|
|
|
onEnter(params?: any): void {
|
|
super.onEnter(params);
|
|
console.log("角色进入待机状态");
|
|
}
|
|
}
|
|
|
|
class MoveState extends BaseState {
|
|
constructor(fsm: FSM) {
|
|
super(fsm, "Move");
|
|
}
|
|
|
|
onUpdate(dt: number): void {
|
|
console.log("移动中...");
|
|
}
|
|
}
|
|
|
|
// 3. 添加状态
|
|
fsm.addState(new IdleState(fsm));
|
|
fsm.addState(new MoveState(fsm));
|
|
|
|
// 4. 切换状态
|
|
fsm.changeState("Idle");
|
|
fsm.changeState("Move", { speed: 5 });
|
|
|
|
// 5. 在游戏循环中更新
|
|
// fsm.update(dt);
|
|
```
|
|
|
|
---
|
|
|
|
## 四、资源管理模块 (Framework/ResMgr)
|
|
|
|
### 4.1 目标
|
|
实现统一的资源加载管理器,封装Cocos Creator的资源加载API,支持从bundle中按路径加载资源。
|
|
|
|
### 4.2 核心类设计
|
|
|
|
#### 4.2.1 `ResMgr.ts` - 资源管理器
|
|
**职责**: 管理资源加载、缓存和释放
|
|
|
|
```typescript
|
|
/**
|
|
* 资源管理器
|
|
*/
|
|
export class ResMgr {
|
|
private static _instance: ResMgr;
|
|
|
|
static getInstance(): ResMgr;
|
|
|
|
/**
|
|
* 加载单个资源
|
|
* @param bundleName bundle名称
|
|
* @param path 资源路径
|
|
* @param type 资源类型
|
|
*/
|
|
load<T extends Asset>(
|
|
bundleName: string,
|
|
path: string,
|
|
type: typeof Asset
|
|
): Promise<T>;
|
|
|
|
/**
|
|
* 预加载资源
|
|
* @param bundleName bundle名称
|
|
* @param path 资源路径
|
|
* @param type 资源类型
|
|
*/
|
|
preload<T extends Asset>(
|
|
bundleName: string,
|
|
path: string,
|
|
type: typeof Asset,
|
|
onProgress?: (finished: number, total: number) => void
|
|
): Promise<void>;
|
|
|
|
/**
|
|
* 加载目录
|
|
* @param bundleName bundle名称
|
|
* @param dir 目录路径
|
|
* @param type 资源类型
|
|
*/
|
|
loadDir<T extends Asset>(
|
|
bundleName: string,
|
|
dir: string,
|
|
type: typeof Asset
|
|
): Promise<T[]>;
|
|
|
|
/**
|
|
* 释放资源
|
|
* @param bundleName bundle名称
|
|
* @param path 资源路径
|
|
*/
|
|
release(bundleName: string, path: string): void;
|
|
|
|
/**
|
|
* 释放目录资源
|
|
* @param bundleName bundle名称
|
|
* @param dir 目录路径
|
|
*/
|
|
releaseDir(bundleName: string, dir: string): void;
|
|
}
|
|
```
|
|
|
|
#### 4.2.2 `ResConfig.ts` - 资源配置
|
|
**职责**: 定义资源加载相关的配置和类型
|
|
|
|
```typescript
|
|
/**
|
|
* 资源加载配置
|
|
*/
|
|
export interface ResLoadConfig {
|
|
/** 是否显示加载进度 */
|
|
showProgress?: boolean;
|
|
|
|
/** 加载超时时间(ms) */
|
|
timeout?: number;
|
|
|
|
/** 失败重试次数 */
|
|
retryCount?: number;
|
|
}
|
|
|
|
/**
|
|
* 预加载配置
|
|
*/
|
|
export interface PreloadConfig extends ResLoadConfig {
|
|
/** 进度回调 */
|
|
onProgress?: (finished: number, total: number) => void;
|
|
}
|
|
|
|
/**
|
|
* 默认配置
|
|
*/
|
|
export const DefaultResConfig: ResLoadConfig;
|
|
```
|
|
|
|
### 4.3 使用示例
|
|
|
|
#### 4.3.1 加载单个资源
|
|
|
|
```typescript
|
|
import { ResMgr } from './Framework/ResMgr/ResMgr';
|
|
import { Prefab, SpriteFrame } from 'cc';
|
|
|
|
// 加载预制体
|
|
const prefab = await ResMgr.getInstance().load(
|
|
'resources',
|
|
'prefabs/Player',
|
|
Prefab
|
|
);
|
|
|
|
// 加载图片
|
|
const spriteFrame = await ResMgr.getInstance().load(
|
|
'resources',
|
|
'textures/icon',
|
|
SpriteFrame
|
|
);
|
|
```
|
|
|
|
#### 4.3.2 预加载资源
|
|
|
|
```typescript
|
|
import { ResMgr } from './Framework/ResMgr/ResMgr';
|
|
import { Prefab } from 'cc';
|
|
|
|
// 预加载资源(不实例化)
|
|
await ResMgr.getInstance().preload(
|
|
'resources',
|
|
'prefabs/Enemy',
|
|
Prefab,
|
|
(finished, total) => {
|
|
console.log(`预加载进度: ${finished}/${total}`);
|
|
}
|
|
);
|
|
```
|
|
|
|
#### 4.3.3 加载目录
|
|
|
|
```typescript
|
|
import { ResMgr } from './Framework/ResMgr/ResMgr';
|
|
import { SpriteFrame } from 'cc';
|
|
|
|
// 加载整个目录的资源
|
|
const sprites = await ResMgr.getInstance().loadDir(
|
|
'resources',
|
|
'textures/ui',
|
|
SpriteFrame
|
|
);
|
|
|
|
console.log(`加载了 ${sprites.length} 个图片资源`);
|
|
```
|
|
|
|
#### 4.3.4 释放资源
|
|
|
|
```typescript
|
|
import { ResMgr } from './Framework/ResMgr/ResMgr';
|
|
|
|
// 释放单个资源
|
|
ResMgr.getInstance().release('resources', 'prefabs/Player');
|
|
|
|
// 释放目录资源
|
|
ResMgr.getInstance().releaseDir('resources', 'textures/ui');
|
|
```
|
|
|
|
### 4.4 已实现的核心类
|
|
|
|
#### 4.4.1 `ResMgr.ts` - 资源管理器 ✅
|
|
**职责**:
|
|
- 管理资源加载、缓存和释放
|
|
- 提供从bundle中按路径加载资源的接口
|
|
- 支持资源预加载
|
|
- 支持目录加载
|
|
|
|
**核心方法**:
|
|
- `static getInstance(): ResMgr` - 获取单例
|
|
- `load<T>(bundleName, path, type): Promise<T>` - 加载单个资源
|
|
- `preload<T>(bundleName, path, type, onProgress?): Promise<void>` - 预加载资源
|
|
- `loadDir<T>(bundleName, dir, type): Promise<T[]>` - 加载目录
|
|
- `release(bundleName, path): void` - 释放单个资源
|
|
- `releaseDir(bundleName, dir): void` - 释放目录资源
|
|
- `releaseAll(): void` - 释放所有资源
|
|
- `getCacheSize(): number` - 获取缓存大小
|
|
|
|
**特性**:
|
|
- ✅ 单例模式
|
|
- ✅ 资源缓存(避免重复加载)
|
|
- ✅ Bundle管理
|
|
- ✅ 完整的日志输出
|
|
|
|
#### 4.4.2 `ResConfig.ts` - 资源配置 ✅
|
|
**职责**:
|
|
- 定义资源加载配置接口
|
|
- 提供常用资源路径常量
|
|
- 定义资源类型枚举
|
|
|
|
**核心内容**:
|
|
- `ResLoadConfig` - 资源加载配置接口
|
|
- `PreloadConfig` - 预加载配置接口
|
|
- `ResPath` - 常用资源路径常量类
|
|
- `ResType` - 资源类型枚举
|
|
|
|
#### 4.4.3 `ResMgrExample.ts` - 使用示例 ✅
|
|
**包含完整的使用示例**:
|
|
- 加载单个资源(Prefab, SpriteFrame, AudioClip)
|
|
- 预加载资源(带进度回调)
|
|
- 加载目录
|
|
- 资源释放
|
|
- 游戏场景资源管理完整流程
|
|
|
|
---
|
|
|
|
## 五、应用状态机 (App/AppStatus)
|
|
|
|
### 4.1 目标
|
|
管理应用的整体状态流转,如启动、登录、游戏中等。
|
|
|
|
### 4.2 状态设计
|
|
|
|
#### 4.2.1 状态流转图
|
|
|
|
```
|
|
[启动Boot] -> [登录Login] -> [游戏Game]
|
|
↑ ↓ ↓
|
|
└──────────────┘ │
|
|
(退出/死亡) │
|
|
│
|
|
┌────────────────────┘
|
|
│ 监听服务器广播:
|
|
│ - PlayerJoin (玩家加入)
|
|
│ - PlayerMove (玩家移动)
|
|
│ - Chat (聊天消息)
|
|
└─────────────────────
|
|
```
|
|
|
|
### 4.3 已实现的核心类
|
|
|
|
#### 4.3.1 `AppStatusBoot.ts` - 启动状态 ✅
|
|
**职责**:
|
|
- 初始化网络管理器
|
|
- 连接服务器
|
|
- 切换到登录状态
|
|
|
|
**核心方法**:
|
|
- `onEnter(params?: any): Promise<void>` - 进入启动状态,执行初始化
|
|
- `initAndConnectNet(): Promise<void>` - 初始化并连接网络
|
|
- `onExit(): void` - 退出启动状态
|
|
|
|
#### 4.3.2 `AppStatusLogin.ts` - 登录状态 ✅
|
|
**职责**:
|
|
- 显示登录界面
|
|
- 处理玩家ID输入
|
|
- 发送Login API请求
|
|
- 登录成功后直接进入游戏世界
|
|
|
|
**核心方法**:
|
|
- `onEnter(params?: any): void` - 进入登录状态,显示登录界面
|
|
- `showLoginUI(): void` - 显示登录UI
|
|
- `login(playerId: string, playerName?: string): Promise<void>` - 执行登录(由UI调用)
|
|
- `onExit(): void` - 退出登录状态,隐藏登录界面
|
|
|
|
**协议对应**:
|
|
- API: `Login` (ReqLogin -> ResLogin)
|
|
- 请求: `{ playerId: string, playerName?: string }`
|
|
- 响应: `{ success: boolean, player: PlayerInfo, isNewPlayer: boolean }`
|
|
|
|
#### 4.3.3 `AppStatusGame.ts` - 游戏状态 ✅
|
|
**职责**:
|
|
- 加载游戏场景
|
|
- 初始化玩家角色
|
|
- 监听服务器广播(其他玩家加入、移动等)
|
|
- 游戏主循环
|
|
|
|
**核心方法**:
|
|
- `onEnter(params?: any): Promise<void>` - 进入游戏状态,接收玩家信息
|
|
- `loadGameScene(): Promise<void>` - 加载游戏场景
|
|
- `initGame(): Promise<void>` - 初始化游戏(创建玩家角色)
|
|
- `listenServerMessages(): void` - 监听服务器广播消息
|
|
- `startGame(): void` - 开始游戏
|
|
- `onUpdate(dt: number): void` - 游戏主循环(每帧调用)
|
|
- `pauseGame(): void` - 暂停游戏
|
|
- `resumeGame(): void` - 恢复游戏
|
|
- `onPlayerDeath(): void` - 玩家死亡
|
|
- `quitGame(): void` - 退出游戏(返回登录)
|
|
- `onExit(): void` - 退出游戏状态,清理资源
|
|
|
|
**监听的服务器广播**:
|
|
- `PlayerJoin` (MsgPlayerJoin) - 其他玩家加入游戏
|
|
- `PlayerMove` (MsgPlayerMove) - 其他玩家移动
|
|
- `Chat` (MsgChat) - 聊天消息
|
|
|
|
**发送的API**:
|
|
- `Move` (ReqMove -> ResMove) - 发送移动请求
|
|
- `Send` (ReqSend -> ResSend) - 发送聊天消息
|
|
|
|
#### 4.3.4 `AppStatusManager.ts` - 应用状态管理器 ✅
|
|
**职责**:
|
|
- 管理应用的整体状态流转
|
|
- 提供单例访问
|
|
- 初始化所有应用状态(Boot, Login, Game)
|
|
|
|
**核心方法**:
|
|
- `static getInstance(): AppStatusManager` - 获取单例
|
|
- `start(): void` - 启动应用(从Boot状态开始)
|
|
- `changeState(stateName: string, params?: any): void` - 切换状态
|
|
- `getCurrentStateName(): string | null` - 获取当前状态名称
|
|
- `getCurrentState(): any` - 获取当前状态实例
|
|
- `update(dt: number): void` - 更新状态机(在主循环中调用)
|
|
- `getFSM(): FSM` - 获取底层FSM实例
|
|
- `destroy(): void` - 销毁管理器
|
|
|
|
**状态流程**: Boot -> Login -> Game
|
|
|
|
**使用示例**:
|
|
```typescript
|
|
import { AppStatusManager } from './App/AppStatus/AppStatusManager';
|
|
|
|
// 获取管理器实例
|
|
const appManager = AppStatusManager.getInstance();
|
|
|
|
// 启动应用
|
|
appManager.start();
|
|
|
|
// 在游戏主循环中更新
|
|
function update(dt: number) {
|
|
appManager.update(dt);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 五、Boot启动组件
|
|
|
|
### 5.1 目标
|
|
作为整个应用的入口点,挂载到场景节点上启动应用。
|
|
|
|
### 5.2 已实现的核心类
|
|
|
|
#### 5.2.1 `Boot.ts` - 启动组件 ✅
|
|
**职责**:
|
|
- 作为整个应用的入口点
|
|
- 初始化AppStatusManager
|
|
- 启动应用状态流转
|
|
- 在每帧更新状态机
|
|
|
|
**核心方法**:
|
|
- `start(): void` - 组件首次激活时调用,初始化应用
|
|
- `initApp(): void` - 初始化应用(私有)
|
|
- `update(deltaTime: number): void` - 每帧更新状态机
|
|
- `onDestroy(): void` - 组件销毁时清理资源
|
|
|
|
**使用方法**:
|
|
1. 在Cocos Creator中打开主场景(`main.scene`)
|
|
2. 创建一个空节点,命名为 "Boot"
|
|
3. 将此脚本挂载到Boot节点上
|
|
4. 运行游戏,应用将自动启动
|
|
|
|
**完整流程**:
|
|
```
|
|
场景加载 -> Boot组件start() -> AppStatusManager.start()
|
|
-> Boot状态(连接服务器) -> Login状态(登录) -> Game状态(进入游戏世界)
|
|
```
|
|
|
|
**协议流程**:
|
|
```
|
|
1. Boot状态: 初始化网络,连接服务器
|
|
2. Login状态: 调用 Login API -> 获取 PlayerInfo
|
|
3. Game状态:
|
|
- 接收 PlayerInfo,初始化玩家
|
|
- 监听 PlayerJoin, PlayerMove, Chat 广播
|
|
- 发送 Move API 进行移动
|
|
- 发送 Send API 发送聊天
|
|
```
|
|
|
|
---
|
|
|
|
## 六、UI 系统 (Framework/UI)
|
|
|
|
### 6.1 目标
|
|
实现统一的UI管理系统,管理UI的加载、显示、隐藏和生命周期。
|
|
|
|
### 6.2 核心类设计
|
|
|
|
#### 6.2.1 `UIBase.ts` - UI基类
|
|
**职责**: 定义UI的基础接口和生命周期
|
|
|
|
```typescript
|
|
import { Node } from 'cc';
|
|
|
|
/**
|
|
* UI基类
|
|
* 所有UI必须继承此类
|
|
*/
|
|
export abstract class UIBase {
|
|
/** UI根节点 */
|
|
protected _node: Node | null = null;
|
|
|
|
/** UI是否已加载 */
|
|
protected _isLoaded: boolean = false;
|
|
|
|
/** UI是否显示中 */
|
|
protected _isShowing: boolean = false;
|
|
|
|
/**
|
|
* 获取UI资源路径 (必须重载)
|
|
* @returns UI预制体路径
|
|
*/
|
|
abstract onGetUrl(): string;
|
|
|
|
/**
|
|
* UI开始时调用 (可选重载)
|
|
* 在UI预制体加载完成后调用
|
|
*/
|
|
onStart?(): void | Promise<void>;
|
|
|
|
/**
|
|
* UI结束时调用 (可选重载)
|
|
* 在UI被卸载前调用
|
|
*/
|
|
onEnd?(): void;
|
|
|
|
/**
|
|
* UI更新 (可选重载)
|
|
* 在每帧调用
|
|
* @param dt 距离上一帧的时间(秒)
|
|
*/
|
|
onUpdate?(dt: number): void;
|
|
|
|
/**
|
|
* 设置UI根节点
|
|
*/
|
|
setNode(node: Node): void {
|
|
this._node = node;
|
|
this._isLoaded = true;
|
|
}
|
|
|
|
/**
|
|
* 获取UI根节点
|
|
*/
|
|
getNode(): Node | null {
|
|
return this._node;
|
|
}
|
|
|
|
/**
|
|
* 显示UI
|
|
*/
|
|
show(): void {
|
|
if (this._node) {
|
|
this._node.active = true;
|
|
this._isShowing = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 隐藏UI
|
|
*/
|
|
hide(): void {
|
|
if (this._node) {
|
|
this._node.active = false;
|
|
this._isShowing = false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 检查UI是否显示中
|
|
*/
|
|
isShowing(): boolean {
|
|
return this._isShowing;
|
|
}
|
|
|
|
/**
|
|
* 检查UI是否已加载
|
|
*/
|
|
isLoaded(): boolean {
|
|
return this._isLoaded;
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 6.2.2 `UIMgr.ts` - UI管理器
|
|
**职责**: 管理UI的加载、卸载和生命周期
|
|
|
|
```typescript
|
|
import { Node, Prefab, instantiate } from 'cc';
|
|
import { UIBase } from './UIBase';
|
|
import { ResMgr } from '../ResMgr/ResMgr';
|
|
|
|
/**
|
|
* UI管理器
|
|
* 管理所有UI的加载、显示、隐藏和销毁
|
|
*/
|
|
export class UIMgr {
|
|
private static _instance: UIMgr;
|
|
|
|
/** 已加载的UI实例缓存 */
|
|
private _uiCache: Map<string, UIBase> = new Map();
|
|
|
|
/** UI父节点(Canvas) */
|
|
private _uiRoot: Node | null = null;
|
|
|
|
/** 需要更新的UI列表 */
|
|
private _updateList: UIBase[] = [];
|
|
|
|
static getInstance(): UIMgr {
|
|
if (!this._instance) {
|
|
this._instance = new UIMgr();
|
|
}
|
|
return this._instance;
|
|
}
|
|
|
|
/**
|
|
* 设置UI根节点
|
|
* @param root Canvas节点或UI容器节点
|
|
*/
|
|
setUIRoot(root: Node): void {
|
|
this._uiRoot = root;
|
|
}
|
|
|
|
/**
|
|
* 加载并显示UI
|
|
* @param uiClass UI类
|
|
* @param params 传递给onStart的参数
|
|
* @returns UI实例
|
|
*/
|
|
async load<T extends UIBase>(
|
|
uiClass: new () => T,
|
|
params?: any
|
|
): Promise<T> {
|
|
const className = uiClass.name;
|
|
|
|
// 检查缓存
|
|
let ui = this._uiCache.get(className) as T;
|
|
if (ui) {
|
|
console.log(`[UIMgr] 从缓存加载UI: ${className}`);
|
|
ui.show();
|
|
if (ui.onStart) {
|
|
await ui.onStart();
|
|
}
|
|
return ui;
|
|
}
|
|
|
|
// 创建UI实例
|
|
ui = new uiClass();
|
|
|
|
// 获取UI资源路径
|
|
const url = ui.onGetUrl();
|
|
if (!url) {
|
|
console.error(`[UIMgr] UI未定义资源路径: ${className}`);
|
|
throw new Error(`UI未定义资源路径: ${className}`);
|
|
}
|
|
|
|
console.log(`[UIMgr] 开始加载UI: ${className} (${url})`);
|
|
|
|
// 通过ResMgr加载预制体
|
|
try {
|
|
const prefab = await ResMgr.getInstance().load<Prefab>(
|
|
'resources',
|
|
url,
|
|
Prefab
|
|
);
|
|
|
|
// 实例化预制体
|
|
const node = instantiate(prefab);
|
|
|
|
// 添加到UI根节点
|
|
if (this._uiRoot) {
|
|
node.setParent(this._uiRoot);
|
|
}
|
|
|
|
// 设置UI节点
|
|
ui.setNode(node);
|
|
|
|
// 缓存UI
|
|
this._uiCache.set(className, ui);
|
|
|
|
// 如果UI有onUpdate方法,添加到更新列表
|
|
if (ui.onUpdate) {
|
|
this._updateList.push(ui);
|
|
}
|
|
|
|
// 调用onStart生命周期
|
|
if (ui.onStart) {
|
|
await ui.onStart();
|
|
}
|
|
|
|
// 显示UI
|
|
ui.show();
|
|
|
|
console.log(`[UIMgr] UI加载完成: ${className}`);
|
|
return ui;
|
|
|
|
} catch (error) {
|
|
console.error(`[UIMgr] 加载UI失败: ${className}`, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 卸载UI
|
|
* @param uiClass UI类或类名
|
|
*/
|
|
unload(uiClass: (new () => UIBase) | string): void {
|
|
const className = typeof uiClass === 'string'
|
|
? uiClass
|
|
: uiClass.name;
|
|
|
|
const ui = this._uiCache.get(className);
|
|
if (!ui) {
|
|
console.warn(`[UIMgr] UI未加载,无法卸载: ${className}`);
|
|
return;
|
|
}
|
|
|
|
console.log(`[UIMgr] 卸载UI: ${className}`);
|
|
|
|
// 调用onEnd生命周期
|
|
if (ui.onEnd) {
|
|
ui.onEnd();
|
|
}
|
|
|
|
// 从更新列表移除
|
|
const index = this._updateList.indexOf(ui);
|
|
if (index !== -1) {
|
|
this._updateList.splice(index, 1);
|
|
}
|
|
|
|
// 销毁节点
|
|
const node = ui.getNode();
|
|
if (node) {
|
|
node.destroy();
|
|
}
|
|
|
|
// 从缓存移除
|
|
this._uiCache.delete(className);
|
|
|
|
// 释放资源
|
|
const url = ui.onGetUrl();
|
|
if (url) {
|
|
ResMgr.getInstance().release('resources', url);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取已加载的UI实例
|
|
* @param uiClass UI类或类名
|
|
*/
|
|
get<T extends UIBase>(uiClass: (new () => T) | string): T | null {
|
|
const className = typeof uiClass === 'string'
|
|
? uiClass
|
|
: uiClass.name;
|
|
|
|
return (this._uiCache.get(className) as T) || null;
|
|
}
|
|
|
|
/**
|
|
* 检查UI是否已加载
|
|
* @param uiClass UI类或类名
|
|
*/
|
|
has(uiClass: (new () => UIBase) | string): boolean {
|
|
const className = typeof uiClass === 'string'
|
|
? uiClass
|
|
: uiClass.name;
|
|
|
|
return this._uiCache.has(className);
|
|
}
|
|
|
|
/**
|
|
* 更新所有需要更新的UI
|
|
* @param dt 距离上一帧的时间(秒)
|
|
*/
|
|
update(dt: number): void {
|
|
for (const ui of this._updateList) {
|
|
if (ui.isShowing() && ui.onUpdate) {
|
|
ui.onUpdate(dt);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 卸载所有UI
|
|
*/
|
|
unloadAll(): void {
|
|
console.log(`[UIMgr] 卸载所有UI`);
|
|
|
|
const classNames = Array.from(this._uiCache.keys());
|
|
for (const className of classNames) {
|
|
this.unload(className);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 销毁管理器
|
|
*/
|
|
destroy(): void {
|
|
this.unloadAll();
|
|
this._uiCache.clear();
|
|
this._updateList = [];
|
|
this._uiRoot = null;
|
|
}
|
|
}
|
|
```
|
|
|
|
### 6.3 使用示例
|
|
|
|
#### 6.3.1 创建自定义UI
|
|
|
|
```typescript
|
|
import { UIBase } from './Framework/UI/UIBase';
|
|
import { Label, Button } from 'cc';
|
|
|
|
/**
|
|
* 登录界面
|
|
*/
|
|
export class UILogin extends UIBase {
|
|
private _labelTitle: Label | null = null;
|
|
private _btnLogin: Button | null = null;
|
|
|
|
/**
|
|
* 必须重载: 返回UI预制体路径
|
|
*/
|
|
onGetUrl(): string {
|
|
return 'prefabs/ui/UILogin';
|
|
}
|
|
|
|
/**
|
|
* 可选重载: UI开始时调用
|
|
*/
|
|
async onStart(): Promise<void> {
|
|
console.log('[UILogin] 开始初始化');
|
|
|
|
// 获取UI组件
|
|
const node = this.getNode();
|
|
if (node) {
|
|
this._labelTitle = node.getChildByName('Title')
|
|
?.getComponent(Label) || null;
|
|
|
|
this._btnLogin = node.getChildByName('BtnLogin')
|
|
?.getComponent(Button) || null;
|
|
}
|
|
|
|
// 设置UI内容
|
|
if (this._labelTitle) {
|
|
this._labelTitle.string = '欢迎登录';
|
|
}
|
|
|
|
// 绑定按钮事件
|
|
if (this._btnLogin) {
|
|
this._btnLogin.node.on('click', this.onClickLogin, this);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 可选重载: UI结束时调用
|
|
*/
|
|
onEnd(): void {
|
|
console.log('[UILogin] 清理资源');
|
|
|
|
// 解绑事件
|
|
if (this._btnLogin) {
|
|
this._btnLogin.node.off('click', this.onClickLogin, this);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 可选重载: 每帧更新
|
|
*/
|
|
onUpdate(dt: number): void {
|
|
// 可以在这里处理动画、倒计时等
|
|
}
|
|
|
|
private onClickLogin(): void {
|
|
console.log('[UILogin] 点击登录按钮');
|
|
// 处理登录逻辑
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 6.3.2 使用UIMgr管理UI
|
|
|
|
```typescript
|
|
import { UIMgr } from './Framework/UI/UIMgr';
|
|
import { UILogin } from './UI/UILogin';
|
|
import { Node } from 'cc';
|
|
|
|
// 1. 设置UI根节点
|
|
const canvas = find('Canvas');
|
|
if (canvas) {
|
|
UIMgr.getInstance().setUIRoot(canvas);
|
|
}
|
|
|
|
// 2. 加载并显示UI
|
|
const loginUI = await UIMgr.getInstance().load(UILogin);
|
|
|
|
// 3. 在游戏主循环中更新UI
|
|
function update(dt: number) {
|
|
UIMgr.getInstance().update(dt);
|
|
}
|
|
|
|
// 4. 卸载UI
|
|
UIMgr.getInstance().unload(UILogin);
|
|
// 或者使用类名
|
|
UIMgr.getInstance().unload('UILogin');
|
|
|
|
// 5. 获取已加载的UI实例
|
|
const ui = UIMgr.getInstance().get(UILogin);
|
|
if (ui) {
|
|
ui.hide(); // 隐藏UI
|
|
ui.show(); // 显示UI
|
|
}
|
|
|
|
// 6. 检查UI是否已加载
|
|
if (UIMgr.getInstance().has(UILogin)) {
|
|
console.log('UILogin已加载');
|
|
}
|
|
|
|
// 7. 卸载所有UI
|
|
UIMgr.getInstance().unloadAll();
|
|
```
|
|
|
|
### 6.4 UI加载流程
|
|
|
|
```
|
|
UIMgr.load(UIClass)
|
|
↓
|
|
1. 创建UI实例
|
|
↓
|
|
2. 调用 ui.onGetUrl() 获取资源路径
|
|
↓
|
|
3. 通过 ResMgr.load() 加载预制体
|
|
↓
|
|
4. 实例化预制体节点
|
|
↓
|
|
5. 调用 ui.setNode(node) 设置节点
|
|
↓
|
|
6. 调用 ui.onStart() 初始化UI
|
|
↓
|
|
7. 调用 ui.show() 显示UI
|
|
↓
|
|
8. 返回UI实例
|
|
```
|
|
|
|
### 6.5 UI生命周期
|
|
|
|
```
|
|
创建UI实例
|
|
↓
|
|
onGetUrl() - 获取资源路径 (必须)
|
|
↓
|
|
加载资源和实例化节点
|
|
↓
|
|
setNode() - 设置节点
|
|
↓
|
|
onStart() - 初始化UI (可选)
|
|
↓
|
|
show() - 显示UI
|
|
↓
|
|
onUpdate() - 每帧更新 (可选)
|
|
↓
|
|
onEnd() - 清理资源 (可选)
|
|
↓
|
|
销毁节点和释放资源
|
|
```
|
|
|
|
### 6.6 设计特点
|
|
|
|
1. **必须重载 onGetUrl()**: 每个UI必须明确定义资源路径
|
|
2. **可选生命周期**: onStart/onEnd/onUpdate 根据需要选择性重载
|
|
3. **自动资源管理**: 通过 ResMgr 统一管理资源加载和释放
|
|
4. **缓存机制**: 已加载的UI会被缓存,再次加载时直接使用
|
|
5. **生命周期完整**: 支持初始化、更新、清理的完整流程
|
|
6. **类型安全**: 使用泛型保证类型推断正确
|
|
|
|
---
|
|
|
|
## 七、登录模块 (App/Login)
|
|
|
|
### 7.1 目标
|
|
实现用户登录功能,包括登录界面UI和登录业务逻辑,登录成功后进入游戏状态。
|
|
|
|
### 7.2 模块结构
|
|
|
|
```
|
|
assets/scripts/App/Login/
|
|
└── UILogin.ts # 登录界面组件
|
|
```
|
|
|
|
### 7.3 核心类设计
|
|
|
|
#### 7.3.1 `UILogin.ts` - 登录界面 ✅
|
|
**职责**:
|
|
- 显示登录UI界面
|
|
- 处理用户账号输入
|
|
- 处理登录按钮点击
|
|
- 调用 NetManager 发送登录请求
|
|
- 登录成功后切换到游戏状态
|
|
|
|
**UI资源路径**: `res/UI/Login/UILogin`
|
|
|
|
**UI节点结构**:
|
|
- `mid/input_account` - 账号输入框 (需挂载 EditBox 组件)
|
|
- `btn_login` - 登录按钮
|
|
|
|
**核心方法**:
|
|
```typescript
|
|
class UILogin extends UIBase {
|
|
/**
|
|
* 获取UI资源路径
|
|
*/
|
|
onGetUrl(): string {
|
|
return 'res/UI/Login/UILogin';
|
|
}
|
|
|
|
/**
|
|
* UI初始化 - 查找节点并绑定事件
|
|
*/
|
|
async onStart(params?: any): Promise<void>;
|
|
|
|
/**
|
|
* 登录按钮点击处理
|
|
*/
|
|
private async onLoginClick(): Promise<void>;
|
|
|
|
/**
|
|
* 执行登录逻辑
|
|
* @param account 账号
|
|
*/
|
|
private async login(account: string): Promise<void>;
|
|
|
|
/**
|
|
* UI清理 - 解绑事件
|
|
*/
|
|
onEnd(): void;
|
|
}
|
|
```
|
|
|
|
**使用示例**:
|
|
```typescript
|
|
import { UIMgr } from '../../Framework/UI/UIMgr';
|
|
import { UILogin } from '../Login/UILogin';
|
|
|
|
// 显示登录界面
|
|
const loginUI = await UIMgr.getInstance().load(UILogin);
|
|
|
|
// 隐藏登录界面
|
|
UIMgr.getInstance().hide(UILogin);
|
|
```
|
|
|
|
### 7.4 登录流程
|
|
|
|
1. **进入登录状态**: `AppStatusLogin.onEnter()` 通过 UIMgr 加载并显示 UILogin
|
|
2. **用户输入账号**: 在 `mid/input_account` 输入框中输入账号
|
|
3. **点击登录按钮**: 点击 `btn_login` 触发登录逻辑
|
|
4. **发送登录请求**:
|
|
```typescript
|
|
const result = await NetManager.getInstance().callApi<ReqLogin, ResLogin>('Login', {
|
|
account: account
|
|
});
|
|
```
|
|
5. **处理登录响应**:
|
|
- 成功: 调用 `AppStatusManager.getInstance().changeState('Game', params)` 切换到游戏状态
|
|
- 失败: 显示错误提示(TODO)
|
|
6. **隐藏登录界面**: 自动调用 `this.hide()` 隐藏登录界面
|
|
|
|
### 7.5 登录协议
|
|
|
|
#### 7.5.1 `LoginProtocol.ts` - 协议类型定义 ✅
|
|
**位置**: `Framework/Net/LoginProtocol.ts` (临时定义,待服务端协议同步后替换)
|
|
|
|
**请求类型**: `ReqLogin`
|
|
```typescript
|
|
interface ReqLogin {
|
|
account: string; // 账号
|
|
password?: string; // 密码(可选)
|
|
}
|
|
```
|
|
|
|
**响应类型**: `ResLogin`
|
|
```typescript
|
|
interface ResLogin {
|
|
success: boolean; // 是否成功
|
|
message?: string; // 消息
|
|
player?: { // 玩家信息
|
|
id: string;
|
|
name: string;
|
|
position: { x: number; y: number; z: number };
|
|
spawnPoint: { x: number; y: number; z: number };
|
|
hp: number;
|
|
maxHp: number;
|
|
isAlive: boolean;
|
|
createdAt: number;
|
|
lastLoginAt: number;
|
|
};
|
|
isNewPlayer?: boolean; // 是否新玩家
|
|
}
|
|
```
|
|
|
|
### 7.6 AppStatusLogin 集成
|
|
|
|
`AppStatusLogin` 已集成登录模块:
|
|
- 进入状态时自动加载并显示 UILogin
|
|
- 退出状态时自动隐藏 UILogin
|
|
- 移除了旧的 mock 登录逻辑,改为由 UILogin 处理
|
|
|
|
```typescript
|
|
import { UIMgr } from "../../Framework/UI/UIMgr";
|
|
import { UILogin } from "../Login/UILogin";
|
|
|
|
export class AppStatusLogin extends BaseState {
|
|
async onEnter(params?: any): Promise<void> {
|
|
// 显示登录界面
|
|
await UIMgr.getInstance().load(UILogin);
|
|
}
|
|
|
|
onExit(): void {
|
|
// 隐藏登录界面
|
|
UIMgr.getInstance().hide(UILogin);
|
|
}
|
|
}
|
|
```
|
|
|
|
### 7.7 注意事项
|
|
|
|
1. **UI资源准备**: 需要在 `assets/res/UI/Login/` 目录下创建 UILogin 预制体
|
|
2. **节点结构**: 预制体中必须包含 `mid/input_account` 和 `btn_login` 节点
|
|
3. **EditBox组件**: `input_account` 节点必须挂载 EditBox 组件
|
|
4. **协议同步**: 运行 `npm run sync-shared` 后,需要将 LoginProtocol 替换为服务端的实际协议
|
|
5. **模块归属**: 业务UI(如UILogin)必须放在 `App/` 对应的业务模块下,不应放在 `Framework/` 目录
|
|
|
|
---
|
|
|
|
## 八、协议说明
|
|
|
|
### 6.1 API (客户端请求)
|
|
|
|
#### 6.1.1 Login - 登录
|
|
**请求**: `ReqLogin`
|
|
```typescript
|
|
{
|
|
playerId: string; // 玩家ID
|
|
playerName?: string; // 玩家昵称(可选,新玩家时使用)
|
|
}
|
|
```
|
|
|
|
**响应**: `ResLogin`
|
|
```typescript
|
|
{
|
|
success: boolean; // 是否成功
|
|
message: string; // 消息
|
|
player?: PlayerInfo; // 玩家信息
|
|
isNewPlayer?: boolean; // 是否新玩家
|
|
}
|
|
```
|
|
|
|
#### 6.1.2 Move - 移动
|
|
**请求**: `ReqMove`
|
|
```typescript
|
|
{
|
|
x: number; // 目标位置 X
|
|
y: number; // 目标位置 Y
|
|
}
|
|
```
|
|
|
|
**响应**: `ResMove`
|
|
```typescript
|
|
{
|
|
success: boolean; // 是否成功
|
|
message?: string; // 消息
|
|
position?: Position; // 实际移动后的位置
|
|
}
|
|
```
|
|
|
|
#### 6.1.3 Send - 发送聊天
|
|
**请求**: `ReqSend`
|
|
```typescript
|
|
{
|
|
content: string; // 聊天内容
|
|
}
|
|
```
|
|
|
|
**响应**: `ResSend`
|
|
```typescript
|
|
{
|
|
success: boolean; // 是否成功
|
|
}
|
|
```
|
|
|
|
### 6.2 MSG (服务器广播)
|
|
|
|
#### 6.2.1 PlayerJoin - 玩家加入
|
|
```typescript
|
|
{
|
|
playerId: string; // 加入的玩家ID
|
|
playerName: string; // 玩家昵称
|
|
position: Position; // 玩家位置
|
|
isNewPlayer: boolean; // 是否新玩家
|
|
timestamp: number; // 加入时间戳
|
|
}
|
|
```
|
|
|
|
#### 6.2.2 PlayerMove - 玩家移动
|
|
```typescript
|
|
{
|
|
playerId: string; // 移动的玩家ID
|
|
playerName: string; // 玩家昵称
|
|
position: Position; // 移动后的位置
|
|
timestamp: number; // 移动时间戳
|
|
}
|
|
```
|
|
|
|
#### 6.2.3 Chat - 聊天消息
|
|
```typescript
|
|
{
|
|
playerId: string; // 发送者ID
|
|
playerName: string; // 发送者昵称
|
|
content: string; // 聊天内容
|
|
timestamp: number; // 发送时间戳
|
|
}
|
|
```
|
|
|
|
### 6.3 基础类型
|
|
|
|
#### Position - 位置
|
|
```typescript
|
|
{
|
|
x: number; // X坐标
|
|
y: number; // Y坐标
|
|
z: number; // Z坐标
|
|
}
|
|
```
|
|
|
|
#### PlayerInfo - 玩家信息
|
|
```typescript
|
|
{
|
|
id: string; // 玩家ID
|
|
name: string; // 玩家昵称
|
|
position: Position; // 当前位置
|
|
spawnPoint: Position; // 出生点
|
|
hp: number; // 当前生命值
|
|
maxHp: number; // 最大生命值
|
|
isAlive: boolean; // 是否存活
|
|
createdAt: number; // 创建时间
|
|
lastLoginAt: number; // 最后登录时间
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 八、下一步开发计划
|
|
|
|
### 8.1 UI系统 (优先级: 高) 📋 已规划
|
|
- [ ] 在Framework下创建UI目录
|
|
- [ ] 实现UIBase基类
|
|
- [ ] 必须重载onGetUrl()方法
|
|
- [ ] 可选重载onStart()方法
|
|
- [ ] 可选重载onEnd()方法
|
|
- [ ] 可选重载onUpdate()方法
|
|
- [ ] 实现UIMgr管理器
|
|
- [ ] Load接口: 根据UIBase获取GetUrl,通过ResMgr加载资源
|
|
- [ ] Unload接口: 卸载UI并释放资源
|
|
- [ ] 调用UI生命周期(onStart/onEnd/onUpdate)
|
|
- [ ] 创建登录界面UI(输入playerId和playerName)
|
|
- [ ] 创建游戏内UI(血条、聊天框等)
|
|
- [ ] 在Boot组件中集成UIMgr.update()
|
|
|
|
### 8.2 网络集成 (优先级: 高)
|
|
- [ ] 在Boot状态中配置并连接服务器
|
|
- [ ] 在Login状态中实现真实的Login API调用
|
|
- [ ] 在Game状态中实现Move API调用
|
|
- [ ] 在Game状态中监听服务器广播消息
|
|
|
|
### 8.3 游戏逻辑 (优先级: 中)
|
|
- [ ] 实现角色控制器(根据PlayerInfo创建)
|
|
- [ ] 实现角色移动(调用Move API)
|
|
- [ ] 实现其他玩家显示(监听PlayerJoin广播)
|
|
- [ ] 实现其他玩家移动同步(监听PlayerMove广播)
|
|
- [ ] 实现聊天系统(Send API + Chat广播)
|
|
- [ ] 实现敌人AI系统
|
|
- [ ] 实现战斗系统
|
|
- [ ] 实现地图生成(Roguelike)
|
|
|
|
---
|
|
|
|
## 九、完整使用流程示例
|
|
|
|
### 9.1 启动应用
|
|
|
|
```typescript
|
|
// 1. 在Cocos Creator中:
|
|
// - 打开 main.scene
|
|
// - 创建 Boot 节点
|
|
// - 挂载 Boot.ts 组件
|
|
// - 运行游戏
|
|
|
|
// 2. Boot组件会自动:
|
|
// - 初始化 AppStatusManager
|
|
// - 启动应用(Boot状态)
|
|
// - 依次流转: Boot -> Login -> Game
|
|
```
|
|
|
|
### 9.2 登录流程
|
|
|
|
```typescript
|
|
// 在UI中调用登录
|
|
import { AppStatusManager } from './App/AppStatus/AppStatusManager';
|
|
import { AppStatusLogin } from './App/AppStatus/AppStatusLogin';
|
|
|
|
const loginState = AppStatusManager.getInstance()
|
|
.getCurrentState() as AppStatusLogin;
|
|
|
|
// 调用登录
|
|
await loginState.login("player123", "玩家昵称");
|
|
|
|
// 登录成功后会自动切换到Game状态
|
|
```
|
|
|
|
### 9.3 游戏内操作
|
|
|
|
```typescript
|
|
import { NetManager } from './Framework/Net/NetManager';
|
|
|
|
// 移动
|
|
const result = await NetManager.getInstance().callApi("Move", {
|
|
x: 10,
|
|
y: 5
|
|
});
|
|
|
|
// 发送聊天
|
|
await NetManager.getInstance().callApi("Send", {
|
|
content: "Hello World!"
|
|
});
|
|
|
|
// 监听其他玩家加入
|
|
NetManager.getInstance().listenMsg("PlayerJoin", (msg) => {
|
|
console.log(`${msg.playerName} 加入了游戏`);
|
|
// 在场景中创建其他玩家角色
|
|
});
|
|
|
|
// 监听其他玩家移动
|
|
NetManager.getInstance().listenMsg("PlayerMove", (msg) => {
|
|
console.log(`${msg.playerName} 移动到 (${msg.position.x}, ${msg.position.y})`);
|
|
// 更新其他玩家位置
|
|
});
|
|
```
|
|
|
|
### 9.4 退出游戏
|
|
|
|
```typescript
|
|
import { AppStatusManager } from './App/AppStatus/AppStatusManager';
|
|
import { AppStatusGame } from './App/AppStatus/AppStatusGame';
|
|
|
|
const gameState = AppStatusManager.getInstance()
|
|
.getCurrentState() as AppStatusGame;
|
|
|
|
// 退出游戏(返回登录)
|
|
gameState.quitGame();
|
|
```
|
|
this._fsm = new FSM();
|
|
this.initStates();
|
|
}
|
|
|
|
private initStates(): void {
|
|
// 添加所有应用状态
|
|
this._fsm.addState(new AppStatusBoot(this._fsm));
|
|
this._fsm.addState(new AppStatusLoading(this._fsm));
|
|
this._fsm.addState(new AppStatusLogin(this._fsm));
|
|
this._fsm.addState(new AppStatusLobby(this._fsm));
|
|
this._fsm.addState(new AppStatusGame(this._fsm));
|
|
}
|
|
|
|
start(): void {
|
|
// 从启动状态开始
|
|
this._fsm.changeState("Boot");
|
|
}
|
|
|
|
update(dt: number): void {
|
|
this._fsm.update(dt);
|
|
}
|
|
}
|
|
```
|
|
|
|
### 4.3 实现步骤
|
|
|
|
1. ✅ 创建 `AppStatusBoot.ts` 启动状态
|
|
|
|
2. ✅ 创建 `AppStatusLoading.ts` 加载状态
|
|
|
|
3. ✅ 创建 `AppStatusLogin.ts` 登录状态
|
|
|
|
4. ✅ 创建 `AppStatusLobby.ts` 大厅状态(基础框架)
|
|
|
|
5. ✅ 创建 `AppStatusGame.ts` 游戏状态(基础框架)
|
|
|
|
6. ✅ 创建 `AppFSM.ts` 状态机管理器
|
|
|
|
7. ✅ 在主场景中启动应用状态机
|
|
|
|
---
|
|
|
|
## 五、开发优先级
|
|
|
|
### 阶段一: 基础框架 (1-2周) 🟡
|
|
1. ✅ 创建 Framework 目录结构
|
|
2. ⬜ 实现状态机模块 (Framework/FSM)
|
|
- ⬜ IState.ts - 状态接口
|
|
- ⬜ BaseState.ts - 状态基类
|
|
- ⬜ FSM.ts - 状态机核心
|
|
3. ✅ 实现网络通信模块 (Framework/Net)
|
|
- ✅ 安装 tsrpc-browser 和 tsrpc-miniapp
|
|
- ✅ NetConfig.ts - 网络配置
|
|
- ✅ NetEvent.ts - 网络事件
|
|
- ✅ PlatformAdapter.ts - 平台适配器
|
|
- ✅ NetManager.ts - 网络管理器
|
|
- ✅ index.ts - 模块导出
|
|
- ✅ NetExample.ts - 使用示例
|
|
4. ✅ 配置共享协议同步机制
|
|
- ✅ sync-shared.js - 同步脚本
|
|
- ✅ SYNC-SHARED.md - 使用说明
|
|
- ✅ package.json - 添加脚本命令
|
|
|
|
### 阶段二: 应用状态 (1-2周) ⬜
|
|
1. ⬜ 创建 App/AppStatus 目录结构
|
|
2. ⬜ 实现启动状态 (AppStatusBoot.ts)
|
|
3. ⬜ 实现加载状态 (AppStatusLoading.ts)
|
|
4. ⬜ 实现登录状态 (AppStatusLogin.ts)
|
|
5. ⬜ 实现大厅状态框架 (AppStatusLobby.ts)
|
|
6. ⬜ 实现游戏状态框架 (AppStatusGame.ts)
|
|
7. ⬜ 实现应用状态机管理器 (AppFSM.ts)
|
|
8. ⬜ 集成状态机到主场景
|
|
|
|
### 阶段三: UI和交互 (2-3周) ⬜
|
|
1. ⬜ 开发 UI 管理器
|
|
2. ⬜ 实现登录界面
|
|
3. ⬜ 实现加载界面
|
|
4. ⬜ 实现大厅界面
|
|
|
|
### 阶段四: 游戏逻辑 (持续) ⬜
|
|
1. ⬜ 实现游戏状态 (AppStatusGame)
|
|
2. ⬜ 实现 Roguelike 核心玩法
|
|
3. ⬜ 完善网络同步
|
|
|
|
---
|
|
|
|
## 六、注意事项
|
|
|
|
### 6.1 平台兼容性
|
|
- 确保代码在浏览器和小程序平台都能正常运行
|
|
- 使用平台适配器统一接口
|
|
- 避免使用平台特定的 API
|
|
|
|
### 6.2 错误处理
|
|
- 网络断线重连机制
|
|
- 状态切换异常处理
|
|
- 资源加载失败处理
|
|
|
|
### 6.3 性能优化
|
|
- 资源预加载和懒加载
|
|
- 对象池复用
|
|
- 网络消息批量处理
|
|
|
|
### 6.4 调试支持
|
|
- 添加详细的日志输出
|
|
## 七、变更日志
|
|
|
|
### 2025-12-14
|
|
|
|
#### 📐 确定开发规范
|
|
**代码组织规范**:
|
|
- ✅ 不为功能模块创建 README.md (文档集中在 .github/instructions)
|
|
- ✅ 不创建 index.ts 统一导出文件 (按需直接导入)
|
|
- ✅ 使用 PascalCase 命名文件和类
|
|
- ✅ 一个文件一个主要类,保持文件名与类名一致
|
|
|
|
**已删除文件**:
|
|
- `Framework/Net/README.md` - 详细文档已移至开发规划
|
|
- `Framework/Net/index.ts` - 改为直接导入具体文件
|
|
|
|
#### 🔄 优化网络通信模块 (Framework/Net) - 符合 TSRPC 官方规范
|
|
**优化内容**:
|
|
- ✅ 使用别名导入避免命名冲突: `HttpClient as HttpClientBrowser` 和 `HttpClient as HttpClientMiniapp`
|
|
- ✅ 简化客户端创建逻辑,直接实例化而非动态导入
|
|
- ✅ 添加 `setServiceProto()` 方法设置协议定义
|
|
- ✅ 添加 `ClientConfig` 接口规范化配置
|
|
- ✅ 增强平台检测,支持所有小程序/小游戏平台
|
|
- ✅ 添加 `getPlatformInfo()` 方法获取详细平台信息
|
|
- ✅ 优化 HttpClient 生命周期管理(无需显式连接/断开)
|
|
|
|
**技术改进**:
|
|
- 符合 TSRPC 官方跨平台使用指南
|
|
- 更清晰的 API 设计和错误提示
|
|
- 完善的类型定义和注释
|
|
|
|
---
|
|
|
|
### 2025-12-14 (早期)
|
|
|
|
#### ✅ 完成网络通信模块 (Framework/Net)
|
|
**已创建文件**:
|
|
- `NetConfig.ts` - 网络配置接口和默认配置
|
|
- `NetEvent.ts` - 网络事件枚举定义
|
|
- `PlatformAdapter.ts` - 多平台适配器(浏览器/小程序)
|
|
- `NetManager.ts` - 网络管理器单例,支持连接、API 调用、消息监听
|
|
- `index.ts` - 模块统一导出
|
|
- `NetExample.ts` - 完整的使用示例
|
|
|
|
**功能特性**:
|
|
- ✅ 单例模式的网络管理器
|
|
- ✅ 自动平台检测和适配(浏览器使用 tsrpc-browser,小程序使用 tsrpc-miniapp)
|
|
- ✅ 自动重连机制
|
|
- ✅ 事件系统(连接/断开/重连/错误)
|
|
- ✅ 支持 API 调用和消息监听
|
|
- ✅ 完善的错误处理和日志输出
|
|
|
|
#### ✅ 完成共享协议同步机制
|
|
**已创建文件**:
|
|
- `sync-shared.js` - Node.js 同步脚本
|
|
- `SYNC-SHARED.md` - 详细使用说明
|
|
- `package.json` - 添加依赖和 sync-shared 命令
|
|
|
|
**功能说明**:
|
|
- 自动将服务端 `../server/src/shared` 同步到客户端 `assets/scripts/Shared`
|
|
- 支持递归复制,自动过滤 node_modules 等
|
|
- 提供友好的命令行输出和错误提示
|
|
- 使用 `npm run sync-shared` 一键同步
|
|
|
|
**依赖包**:
|
|
- `tsrpc-browser: ^3.4.15` - 浏览器平台 TSRPC 客户端
|
|
- `tsrpc-miniapp: ^3.4.15` - 小程序平台 TSRPC 客户端
|
|
- `fs-extra: ^11.2.0` - 文件操作工具(开发依赖)
|
|
|
|
**下一步**:
|
|
1. 运行 `npm install` 安装依赖
|
|
2. 运行 `npm run sync-shared` 同步服务端协议(需要确保服务端 shared 目录存在)
|
|
3. 在实际项目中导入协议定义并使用 NetManager
|
|
|
|
---
|
|
|
|
### 2025-12-14 (早期)
|
|
- 📝 创建项目开发规划文档
|
|
- 📊 添加进度跟踪系统
|
|
- 🎯 定义四个开发阶段和详细任务清单
|
|
### 2025-12-14
|
|
- 📝 创建项目开发规划文档
|
|
- 📊 添加进度跟踪系统
|
|
- 🎯 定义四个开发阶段和详细任务清单
|
|
- ⬜ 当前状态: 项目初始化阶段,所有模块未开始
|
|
|
|
---
|
|
|
|
## 八、待办事项 (TODO)
|
|
|
|
- [ ] 实现 UI 管理器
|
|
- [ ] 设计并实现登录界面
|
|
- [ ] 配置服务器地址和网络参数
|
|
- [ ] 实现配置文件加载系统
|
|
- [ ] 实现资源管理器
|
|
- [ ] 设计大厅界面
|
|
- [ ] 实现房间系统
|
|
- [ ] 实现匹配系统
|
|
- [ ] 设计 Roguelike 核心玩法
|
|
- [ ] 实现战斗系统
|
|
|
|
---
|
|
|
|
## 九、参考资料
|
|
|
|
- [TSRPC 官方文档](https://tsrpc.cn/)
|
|
- [Cocos Creator 3.x 文档](https://docs.cocos.com/creator/3.8/manual/zh/)
|
|
- 状态机设计模式
|
|
- TypeScript 最佳实践
|