# 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 最佳实践