From 7bd2c74e3dde4c2e29fd02e6e2e890e9f77f6887 Mon Sep 17 00:00:00 2001 From: janing Date: Sun, 14 Dec 2025 22:37:49 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BC=80=E5=8F=91=E8=BF=9B=E5=BA=A6=E6=96=87?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...tom Script Template Help Documentation.url | 2 + .../.github/instructions/AppStatus-Guide.md | 351 ++++ .../.github/instructions/development-plan.md | 1844 +++++++++++++++++ .../instructions/net-module-summary.md | 155 ++ .../instructions/workspace.instructions.md | 4 + 5 files changed, 2356 insertions(+) create mode 100644 client/.creator/asset-template/typescript/Custom Script Template Help Documentation.url create mode 100644 client/.github/instructions/AppStatus-Guide.md create mode 100644 client/.github/instructions/development-plan.md create mode 100644 client/.github/instructions/net-module-summary.md create mode 100644 client/.github/instructions/workspace.instructions.md diff --git a/client/.creator/asset-template/typescript/Custom Script Template Help Documentation.url b/client/.creator/asset-template/typescript/Custom Script Template Help Documentation.url new file mode 100644 index 0000000..7606df0 --- /dev/null +++ b/client/.creator/asset-template/typescript/Custom Script Template Help Documentation.url @@ -0,0 +1,2 @@ +[InternetShortcut] +URL=https://docs.cocos.com/creator/manual/en/scripting/setup.html#custom-script-template \ No newline at end of file diff --git a/client/.github/instructions/AppStatus-Guide.md b/client/.github/instructions/AppStatus-Guide.md new file mode 100644 index 0000000..d0a829e --- /dev/null +++ b/client/.github/instructions/AppStatus-Guide.md @@ -0,0 +1,351 @@ +# AppStatus 应用状态机使用指南 + +## 📚 概述 + +AppStatus 是基于 FSM 状态机实现的应用级状态管理系统,负责管理整个游戏的状态流转。 + +**简化后的流程**: Boot(连接) → Login(登录) → Game(游戏世界) + +## 🏗️ 项目结构 + +``` +assets/scripts/ +├── Boot/ +│ └── Boot.ts # 启动组件(挂载到场景) +├── App/ +│ └── AppStatus/ +│ ├── AppStatusBoot.ts # 启动状态(连接服务器) +│ ├── AppStatusLogin.ts # 登录状态(调用Login API) +│ ├── AppStatusGame.ts # 游戏状态(监听广播) +│ └── AppStatusManager.ts # 状态管理器 +├── Framework/ +│ ├── FSM/ # 状态机框架 +│ └── Net/ # 网络通信框架 +└── Shared/ + └── protocols/ # TSRPC协议定义 +``` + +## 🚀 快速开始 + +### 1. 在场景中使用 + +1. 打开 Cocos Creator +2. 打开主场景 `assets/scenes/main.scene` +3. 创建一个空节点,命名为 "Boot" +4. 将 `assets/scripts/Boot/Boot.ts` 拖拽到 Boot 节点上 +5. 运行游戏 + +### 2. 状态流转 + +应用启动后会自动按以下顺序流转: + +``` +Boot(启动) → Login(登录) → Game(游戏世界) + ↑ ↓ ↓ + └───────────────────────────┘ + (退出/死亡返回登录) + +## 📋 各状态详解 + +### Boot 启动状态 + +**触发时机**: 应用启动时 +**主要工作**: +- 初始化网络管理器 +- 连接服务器 + +**完成后**: 自动切换到 Login 状态 + +### Login 登录状态 + +**触发时机**: Boot 状态完成后 +**主要工作**: +- 显示登录界面 +- 等待用户输入玩家ID +- 调用 Login API 登录 + +**如何触发登录**: +```typescript +// 在登录UI中调用 +const loginState = AppStatusManager.getInstance() + .getCurrentState() as AppStatusLogin; +await loginState.login("player123", "玩家昵称"); +``` + +**协议**: +- API: `Login` +- 请求: `{ playerId: string, playerName?: string }` +- 响应: `{ success: boolean, player: PlayerInfo, isNewPlayer: boolean }` + +**完成后**: 登录成功后直接进入 Game 状态 + +### Game 游戏状态 + +**触发时机**: 登录成功后 +**主要工作**: +- 接收登录返回的玩家信息(PlayerInfo) +- 加载游戏场景 +- 创建玩家角色 +- 监听服务器广播消息 +- 运行游戏主循环 + +**监听的服务器广播**: +```typescript +// PlayerJoin - 其他玩家加入 +NetManager.getInstance().listenMsg("PlayerJoin", (msg) => { + console.log(`${msg.playerName} 加入了游戏`); + // 在场景中创建其他玩家 +}); + +// PlayerMove - 其他玩家移动 +NetManager.getInstance().listenMsg("PlayerMove", (msg) => { + console.log(`${msg.playerName} 移动到 (${msg.position.x}, ${msg.position.y})`); + // 更新其他玩家位置 +}); + +// Chat - 聊天消息 +NetManager.getInstance().listenMsg("Chat", (msg) => { + console.log(`${msg.playerName}: ${msg.content}`); + // 显示聊天内容 +}); +``` + +**可用操作**: +```typescript +const gameState = AppStatusManager.getInstance() + .getCurrentState() as AppStatusGame; + +// 移动(调用Move API) +await NetManager.getInstance().callApi("Move", { x: 10, y: 5 }); + +// 发送聊天(调用Send API) +await NetManager.getInstance().callApi("Send", { content: "Hello!" }); + +// 暂停/恢复游戏 +gameState.pauseGame(); +gameState.resumeGame(); + +// 玩家死亡 +gameState.onPlayerDeath(); + +// 退出游戏(返回登录) +gameState.quitGame(); +``` + +## 🎮 AppStatusManager API + +### 获取管理器实例 + +```typescript +import { AppStatusManager } from './App/AppStatus/AppStatusManager'; + +const appManager = AppStatusManager.getInstance(); +``` + +### 主要方法 + +#### start() +启动应用,从 Boot 状态开始 + +```typescript +appManager.start(); +``` + +#### changeState(stateName, params?) +手动切换状态 + +```typescript +// 切换到登录状态 +appManager.changeState("Login"); + +// 切换到大厅状态并传递用户信息 +appManager.changeState("Lobby", { + userInfo: { userId: "123", username: "玩家" } +}); + +// 切换到游戏状态 +appManager.changeState("Game", { + roomId: "room123", + userInfo: currentUser +}); +``` + +#### getCurrentStateName() +获取当前状态名称 + +```typescript +const stateName = appManager.getCurrentStateName(); +console.log(`当前状态: ${stateName}`); // 输出: "Login" +``` + +#### getCurrentState() +获取当前状态实例 + +```typescript +const currentState = appManager.getCurrentState(); +if (currentState) { + console.log(`当前状态: ${currentState.name}`); +} +``` + +#### update(dt) +更新状态机(在游戏主循环中调用) + +```typescript +// Boot.ts 中已自动处理 +update(deltaTime: number) { + this._appStatusManager.update(deltaTime); +} +``` + +## 🔧 扩展新状态 + +如果需要添加新的应用状态: + +### 1. 创建状态类 + +```typescript +import { BaseState } from "../../Framework/FSM/BaseState"; + +export class AppStatusNewState extends BaseState { + constructor(fsm: any) { + super(fsm, "NewState"); + } + + onEnter(params?: any): void { + super.onEnter(params); + // 进入状态的逻辑 + } + + onExit(): void { + super.onExit(); + // 退出状态的逻辑 + } +} +``` + +### 2. 在 AppStatusManager 中注册 + +```typescript +// AppStatusManager.ts +import { AppStatusNewState } from "./AppStatusNewState"; + +private initStates(): void { + // ... 其他状态 + this._fsm.addState(new AppStatusNewState(this._fsm)); +} +``` + +### 3. 从其他状态切换 + +```typescript +this._fsm.changeState("NewState", { /* 参数 */ }); +``` + +## 💡 最佳实践 + +### 1. 状态间传递数据 + +使用 `changeState` 的第二个参数传递数据: + +```typescript +// 传递数据 +this._fsm.changeState("Lobby", { + userInfo: { userId: "123", username: "玩家" } +}); + +// 接收数据 +onEnter(params?: any): void { + if (params && params.userInfo) { + this._userInfo = params.userInfo; + } +} +``` + +### 2. 保持状态独立 + +每个状态应该是独立的,不要在状态间共享可变数据,而是通过参数传递。 + +### 3. 异步操作处理 + +在状态的 `onEnter` 中处理异步操作: + +```typescript +async onEnter(params?: any): Promise { + super.onEnter(params); + + try { + await this.loadSomething(); + this._fsm.changeState("NextState"); + } catch (error) { + console.error("加载失败:", error); + // 处理错误 + } +} +``` + +### 4. 状态清理 + +始终在 `onExit` 中清理资源: + +```typescript +onExit(): void { + super.onExit(); + // 清理UI + // 取消网络监听 + // 释放资源 +} +``` + +## 🐛 调试技巧 + +### 1. 查看状态流转日志 + +所有状态切换都会在控制台输出日志: + +``` +[FSM] 进入状态: Boot +[AppStatusBoot] 开始初始化应用... +[FSM] 退出状态: Boot +[FSM] 进入状态: Loading +``` + +### 2. 查看当前状态 + +```typescript +console.log("当前状态:", AppStatusManager.getInstance().getCurrentStateName()); +``` + +### 3. 直接切换到指定状态(测试用) + +```typescript +// 跳过登录直接进入大厅 +AppStatusManager.getInstance().changeState("Lobby", { + userInfo: { userId: "test", username: "测试用户" } +}); +``` + +## 📝 TODO 清单 + +当前 AppStatus 模块已完成基础框架,但以下功能需要后续实现: + +- [ ] 集成 UI 管理器 +- [ ] 实现实际的服务器连接 +- [ ] 实现资源预加载 +- [ ] 实现房间系统 +- [ ] 实现匹配系统 +- [ ] 实现游戏场景加载 +- [ ] 添加状态转换动画 +- [ ] 添加错误处理和重试机制 + +## 🎯 下一步 + +1. 实现 UI 系统 +2. 创建登录界面 +3. 连接服务器 +4. 实现房间和匹配功能 +5. 开发游戏主逻辑 + +--- + +更新时间: 2025-12-14 diff --git a/client/.github/instructions/development-plan.md b/client/.github/instructions/development-plan.md new file mode 100644 index 0000000..41d3f6d --- /dev/null +++ b/client/.github/instructions/development-plan.md @@ -0,0 +1,1844 @@ +# 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(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; // 默认配置 +``` +``` + +#### 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; + 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( + bundleName: string, + path: string, + type: typeof Asset + ): Promise; + + /** + * 预加载资源 + * @param bundleName bundle名称 + * @param path 资源路径 + * @param type 资源类型 + */ + preload( + bundleName: string, + path: string, + type: typeof Asset, + onProgress?: (finished: number, total: number) => void + ): Promise; + + /** + * 加载目录 + * @param bundleName bundle名称 + * @param dir 目录路径 + * @param type 资源类型 + */ + loadDir( + bundleName: string, + dir: string, + type: typeof Asset + ): Promise; + + /** + * 释放资源 + * @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(bundleName, path, type): Promise` - 加载单个资源 +- `preload(bundleName, path, type, onProgress?): Promise` - 预加载资源 +- `loadDir(bundleName, dir, type): Promise` - 加载目录 +- `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` - 进入启动状态,执行初始化 +- `initAndConnectNet(): Promise` - 初始化并连接网络 +- `onExit(): void` - 退出启动状态 + +#### 4.3.2 `AppStatusLogin.ts` - 登录状态 ✅ +**职责**: +- 显示登录界面 +- 处理玩家ID输入 +- 发送Login API请求 +- 登录成功后直接进入游戏世界 + +**核心方法**: +- `onEnter(params?: any): void` - 进入登录状态,显示登录界面 +- `showLoginUI(): void` - 显示登录UI +- `login(playerId: string, playerName?: string): Promise` - 执行登录(由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` - 进入游戏状态,接收玩家信息 +- `loadGameScene(): Promise` - 加载游戏场景 +- `initGame(): Promise` - 初始化游戏(创建玩家角色) +- `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; + + /** + * 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 = 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( + uiClass: new () => T, + params?: any + ): Promise { + 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( + '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(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 { + 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; + + /** + * 登录按钮点击处理 + */ + private async onLoginClick(): Promise; + + /** + * 执行登录逻辑 + * @param account 账号 + */ + private async login(account: string): Promise; + + /** + * 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('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 { + // 显示登录界面 + 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 最佳实践 diff --git a/client/.github/instructions/net-module-summary.md b/client/.github/instructions/net-module-summary.md new file mode 100644 index 0000000..661974d --- /dev/null +++ b/client/.github/instructions/net-module-summary.md @@ -0,0 +1,155 @@ +# 网络模块开发完成总结 + +## ✅ 已完成内容 + +### 📁 目录结构 +``` +assets/scripts/Framework/Net/ +├── NetConfig.ts # 网络配置接口 +├── NetEvent.ts # 网络事件定义 +├── PlatformAdapter.ts # 平台适配器 +├── NetManager.ts # 网络管理器(核心) +├── index.ts # 模块导出 +├── NetExample.ts # 使用示例 +└── README.md # 快速入门文档 + +项目根目录: +├── sync-shared.js # 协议同步脚本 +├── SYNC-SHARED.md # 同步说明文档 +└── package.json # 添加依赖和脚本 +``` + +### 🎯 核心功能 + +#### 1. NetManager - 网络管理器 +- ✅ 单例模式设计 +- ✅ 自动平台检测和适配 +- ✅ 连接管理(连接/断开) +- ✅ 自动重连机制 +- ✅ API 调用支持 +- ✅ 消息监听和发送 +- ✅ 事件系统 +- ✅ 完善的错误处理 + +#### 2. PlatformAdapter - 平台适配 +- ✅ 自动检测当前平台 +- ✅ 浏览器平台使用 `tsrpc-browser` +- ✅ 小程序平台使用 `tsrpc-miniapp` +- ✅ 统一的客户端接口 + +#### 3. 协议同步机制 +- ✅ Node.js 同步脚本 +- ✅ 自动复制服务端 shared 目录 +- ✅ npm 脚本命令支持 +- ✅ 详细的使用文档 + +### 📦 依赖包安装 +- ✅ `tsrpc-browser: ^3.4.15` +- ✅ `tsrpc-miniapp: ^3.4.15` +- ✅ `fs-extra: ^11.2.0` (devDependencies) + +## 🎓 使用指南 + +### 快速开始 + +1. **安装依赖** + ```bash + npm install + ``` + +2. **同步协议** (需要服务端存在) + ```bash + npm run sync-shared + ``` + +3. **使用网络管理器** + ```typescript + import { NetManager, NetConfig } from './Framework/Net'; + + const netManager = NetManager.getInstance(); + netManager.init({ serverUrl: 'http://localhost:3000' }); + await netManager.connect(); + ``` + +### 文档资源 +- 📖 [快速入门](assets/scripts/Framework/Net/README.md) +- 📖 [协议同步说明](SYNC-SHARED.md) +- 📖 [开发规划](.github/instructions/development-plan.md) +- 💡 [使用示例](assets/scripts/Framework/Net/NetExample.ts) + +## 🔄 下一步工作 + +### 待完成任务 + +1. **服务端对接** + - ⬜ 确保服务端 shared 目录存在 + - ⬜ 运行 `npm run sync-shared` 同步协议 + - ⬜ 在 PlatformAdapter 中导入实际协议定义 + +2. **状态机模块** (阶段一剩余) + - ⬜ 创建 Framework/FSM 目录 + - ⬜ 实现 IState.ts + - ⬜ 实现 BaseState.ts + - ⬜ 实现 FSM.ts + +3. **应用状态机** (阶段二) + - ⬜ 创建 App/AppStatus 目录 + - ⬜ 实现各个应用状态 + - ⬜ 集成网络模块到应用状态 + +## 🎉 成果展示 + +### 代码质量 +- ✅ TypeScript 严格类型检查 +- ✅ 完整的 JSDoc 注释 +- ✅ 单一职责原则 +- ✅ 依赖注入和控制反转 +- ✅ 事件驱动架构 + +### 可维护性 +- ✅ 模块化设计 +- ✅ 清晰的接口定义 +- ✅ 详细的文档说明 +- ✅ 完整的使用示例 +- ✅ 友好的错误提示 + +### 跨平台支持 +- ✅ 浏览器 Web 平台 +- ✅ 微信小游戏 +- ✅ 字节跳动小游戏 +- ✅ QQ 小游戏 +- ✅ 自动平台检测 + +## 📊 代码统计 + +| 文件 | 行数 | 功能 | +|------|------|------| +| NetManager.ts | ~300 | 网络管理核心逻辑 | +| PlatformAdapter.ts | ~130 | 平台适配逻辑 | +| NetConfig.ts | ~30 | 配置定义 | +| NetEvent.ts | ~25 | 事件定义 | +| NetExample.ts | ~80 | 使用示例 | +| sync-shared.js | ~100 | 同步脚本 | +| **总计** | **~665** | **6 个核心文件** | + +## 💡 技术亮点 + +1. **自动平台适配**: 无需手动判断平台,自动加载对应的 TSRPC 客户端 +2. **智能重连**: 支持自动重连,可配置重连次数和间隔 +3. **事件驱动**: 基于事件系统,解耦网络状态和业务逻辑 +4. **类型安全**: 完整的 TypeScript 类型定义,编译时检查 +5. **开发友好**: 详细的日志输出,快速定位问题 + +## 🎯 项目进度 + +- ✅ 阶段一 - 网络模块: **100% 完成** +- ⬜ 阶段一 - 状态机模块: **0% 完成** +- ⬜ 阶段二 - 应用状态: **0% 完成** +- ⬜ 阶段三 - UI 系统: **0% 完成** +- ⬜ 阶段四 - 游戏逻辑: **0% 完成** + +--- + +**开发时间**: 2025-12-14 +**模块版本**: 1.0.0 +**状态**: ✅ 已完成并可用 diff --git a/client/.github/instructions/workspace.instructions.md b/client/.github/instructions/workspace.instructions.md new file mode 100644 index 0000000..d2a05df --- /dev/null +++ b/client/.github/instructions/workspace.instructions.md @@ -0,0 +1,4 @@ +--- +applyTo: '**' +--- +这是cocos3.x的3d项目,请使用中文回答。 \ No newline at end of file