From 1d91daa7269b7946186730d8c05a86cab6e5ff44 Mon Sep 17 00:00:00 2001 From: janing Date: Sun, 14 Dec 2025 23:35:54 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96Framework.UI=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E4=BE=BF=E6=8D=B7=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../.github/instructions/development-plan.md | 1926 ++--------------- client/assets/scripts/Framework/UI/README.md | 17 +- .../scripts/Framework/UI/README.md.meta | 11 + client/assets/scripts/Framework/UI/UIBase.ts | 65 +- client/assets/scripts/Framework/UI/UIMgr.ts | 8 +- 5 files changed, 285 insertions(+), 1742 deletions(-) create mode 100644 client/assets/scripts/Framework/UI/README.md.meta diff --git a/client/.github/instructions/development-plan.md b/client/.github/instructions/development-plan.md index 41d3f6d..385017e 100644 --- a/client/.github/instructions/development-plan.md +++ b/client/.github/instructions/development-plan.md @@ -6,14 +6,16 @@ ## 📐 开发规范 ### 代码组织规范 -1. **不创建 README.md**: 功能模块内不需要创建 README.md 文档,所有文档集中在 `.github/instructions` 目录 +1. **模块 README.md**: 每个功能模块目录下创建 README.md 文档,详细说明模块的使用方法 2. **不创建 index.ts**: 不需要为每个模块创建统一导出的 index.ts 文件,直接导入具体文件 3. **按需导入**: 使用时直接从具体文件导入,例如: ```typescript import { NetManager } from './Framework/Net/NetManager'; import { NetConfig } from './Framework/Net/NetConfig'; ``` -4. **文档集中管理**: 所有开发文档、规划文档统一放在 `.github/instructions` 目录 +4. **文档分层管理**: + - 项目级文档放在 `.github/instructions` 目录 + - 模块级文档放在模块目录的 README.md 5. **模块归属规范**: - **Framework/** 目录用于存放通用框架和工具类(FSM、Net、ResMgr、UI基类等) - **App/** 目录用于存放应用层业务模块(AppStatus、Login、Game等) @@ -30,18 +32,18 @@ ## 📊 项目进度总览 -**最后更新**: 2025-12-14 +**最后更新**: 2025-12-14 (Game 模块已完成) -| 模块 | 进度 | 状态 | -|------|------|------| -| Framework/FSM | 100% | ✅ 已完成 | -| Framework/Net | 100% | ✅ 已完成 | -| Framework/ResMgr | 100% | ✅ 已完成 | -| Framework/UI | 100% | ✅ 已完成 | -| App/AppStatus | 100% | ✅ 已完成 | -| App/Login | 100% | ✅ 已完成 | -| Boot启动组件 | 100% | ✅ 已完成 | -| 游戏逻辑 | 0% | ⬜ 未开始 | +| 模块 | 进度 | 状态 | 文档 | +|------|------|------|------| +| Framework/FSM | 100% | ✅ 已完成 | [查看文档](../../assets/scripts/Framework/FSM/README.md) | +| Framework/Net | 100% | ✅ 已完成 | [查看文档](../../assets/scripts/Framework/Net/README.md) | +| Framework/ResMgr | 100% | ✅ 已完成 | [查看文档](../../assets/scripts/Framework/ResMgr/README.md) | +| Framework/UI | 100% | ✅ 已完成 | [查看文档](../../assets/scripts/Framework/UI/README.md) | +| App/AppStatus | 100% | ✅ 已完成 | [查看文档](../../assets/scripts/App/AppStatus/README.md) | +| App/Login | 100% | ✅ 已完成 | [查看文档](../../assets/scripts/App/Login/README.md) | +| App/Game | 100% | ✅ 已完成 | [查看文档](../../assets/scripts/App/Game/README.md) | +| Boot启动组件 | 100% | ✅ 已完成 | - | **图例**: ⬜ 未开始 | 🟡 进行中 | ✅ 已完成 | ⏸️ 已暂停 @@ -52,1793 +54,243 @@ ``` assets/scripts/ ├── Framework/ # 框架和通用工具类目录 -│ ├── Net/ # 网络通信模块 ✅ │ ├── FSM/ # 状态机模块 ✅ +│ │ ├── IState.ts +│ │ ├── BaseState.ts +│ │ ├── FSM.ts +│ │ └── README.md # 详细文档 +│ ├── Net/ # 网络通信模块 ✅ +│ │ ├── NetManager.ts +│ │ ├── PlatformAdapter.ts +│ │ ├── NetConfig.ts +│ │ ├── NetEvent.ts +│ │ └── README.md # 详细文档 │ ├── ResMgr/ # 资源管理模块 ✅ -│ ├── UI/ # UI基类和管理器 ✅ -│ │ ├── UIBase.ts # UI基类 -│ │ └── UIMgr.ts # UI管理器 -│ └── ... # 其他框架模块 +│ │ ├── ResMgr.ts +│ │ ├── ResConfig.ts +│ │ └── README.md # 详细文档 +│ └── UI/ # UI基类和管理器 ✅ +│ ├── UIBase.ts +│ ├── UIMgr.ts +│ └── README.md # 详细文档 ├── App/ # 应用层代码(业务模块) │ ├── AppStatus/ # 应用状态机 ✅ +│ │ ├── AppStatusManager.ts +│ │ ├── AppStatusBoot.ts +│ │ ├── AppStatusLogin.ts +│ │ ├── AppStatusGame.ts +│ │ └── README.md # 详细文档 │ ├── Login/ # 登录模块 ✅ -│ │ └── UILogin.ts # 登录界面 -│ ├── Game/ # 游戏模块 -│ │ └── ... # 游戏相关UI和逻辑 -│ └── ... # 其他业务模块 +│ │ ├── UILogin.ts +│ │ └── README.md # 详细文档 +│ └── Game/ # 游戏模块 (待开发) +│ └── ... └── Boot/ # 启动组件 ✅ - └── Boot.ts # 场景挂载入口点 + └── Boot.ts ``` --- -## 二、网络通信模块 (Framework/Net) +## 二、模块概览 -### 2.1 目标 -实现基于 TSRPC 的网络通信层,支持多平台(浏览器、小程序)。 +### Framework 模块(框架层) -### 2.2 依赖包选择 +#### 2.1 状态机模块 (FSM) ✅ +- **职责**: 通用的有限状态机框架 +- **核心类**: IState, BaseState, FSM +- **详细文档**: [Framework/FSM/README.md](../../assets/scripts/Framework/FSM/README.md) -| 平台 | NPM 包 | -|------|--------| -| 浏览器 (Web) | `tsrpc-browser` | -| 小程序 (微信/抖音/QQ) | `tsrpc-miniapp` | +#### 2.2 网络通信模块 (Net) ✅ +- **职责**: 基于 TSRPC 的网络通信层,支持多平台 +- **核心类**: NetManager, PlatformAdapter, NetConfig, NetEvent +- **详细文档**: [Framework/Net/README.md](../../assets/scripts/Framework/Net/README.md) -### 2.3 已实现的核心类 +#### 2.3 资源管理模块 (ResMgr) ✅ +- **职责**: 统一的资源加载管理器 +- **核心类**: ResMgr, ResConfig +- **详细文档**: [Framework/ResMgr/README.md](../../assets/scripts/Framework/ResMgr/README.md) -#### 2.3.1 `NetManager.ts` - 网络管理器 ✅ -**职责**: 网络连接管理、消息收发、重连机制 +#### 2.4 UI 系统 (UI) ✅ +- **职责**: UI 管理系统,管理 UI 生命周期 +- **核心类**: UIBase, UIMgr +- **详细文档**: [Framework/UI/README.md](../../assets/scripts/Framework/UI/README.md) -**核心方法**: -- `setServiceProto(serviceProto)` - 设置服务协议 (必须在 init 之前调用) -- `init(config: NetConfig)` - 初始化网络配置 -- `connect()` - 创建客户端实例 -- `disconnect()` - 清理客户端资源 -- `callApi(apiName, req)` - 调用 API -- `listenMsg(msgName, handler)` - 监听服务器消息 -- `sendMsg(msgName, msg)` - 发送消息到服务器 -- `on(event, callback)` - 监听网络事件 +### App 模块(应用层) + +#### 2.5 应用状态机 (AppStatus) ✅ +- **职责**: 管理应用的整体状态流转 +- **核心类**: AppStatusManager, AppStatusBoot, AppStatusLogin, AppStatusGame +- **状态流转**: Boot → Login → Game +- **详细文档**: [App/AppStatus/README.md](../../assets/scripts/App/AppStatus/README.md) + +#### 2.6 登录模块 (Login) ✅ +- **职责**: 用户登录功能,包括登录界面和业务逻辑 +- **核心类**: UILogin +- **详细文档**: [App/Login/README.md](../../assets/scripts/App/Login/README.md) + +### 启动组件 + +#### 2.7 Boot 启动组件 ✅ +- **职责**: 应用入口点,挂载到场景节点上启动应用 +- **文件**: `Boot/Boot.ts` +- **使用**: 挂载到 main.scene 的 Boot 节点上 + +--- + +## 三、快速开始 + +### 3.1 启动应用 + +1. 在 Cocos Creator 中打开 `main.scene` +2. 创建一个空节点,命名为 "Boot" +3. 将 `Boot.ts` 脚本挂载到 Boot 节点上 +4. 运行游戏,应用将自动启动 + +### 3.2 状态流转 + +``` +启动游戏 + ↓ +Boot 组件 start() + ↓ +AppStatusManager.start() + ↓ +Boot 状态(初始化网络并连接服务器) + ↓ +Login 状态(显示登录界面) + ↓ +用户登录 + ↓ +Game 状态(进入游戏世界) +``` + +### 3.3 核心使用示例 + +#### 网络通信 -**使用示例**: ```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.setServiceProto(serviceProto); netManager.init({ serverUrl: 'http://localhost:3000' }); await netManager.connect(); + +// 调用 API +const result = await netManager.callApi('Login', { account: 'player123' }); ``` -#### 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}`); - } -); +const prefab = await ResMgr.getInstance().load('resources', 'prefabs/Player', Prefab); ``` -#### 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 +#### UI 管理 ```typescript import { UIMgr } from './Framework/UI/UIMgr'; -import { UILogin } from './UI/UILogin'; -import { Node } from 'cc'; +import { UILogin } from './App/Login/UILogin'; -// 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; // 最后登录时间 -} +await UIMgr.getInstance().load(UILogin); ``` --- -## 八、下一步开发计划 +## 四、开发指南 -### 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() +### 4.1 创建新模块 -### 8.2 网络集成 (优先级: 高) -- [ ] 在Boot状态中配置并连接服务器 -- [ ] 在Login状态中实现真实的Login API调用 -- [ ] 在Game状态中实现Move API调用 -- [ ] 在Game状态中监听服务器广播消息 +1. 在对应目录下创建模块文件 +2. 创建 README.md 文档 +3. 在 development-plan.md 中更新进度 -### 8.3 游戏逻辑 (优先级: 中) -- [ ] 实现角色控制器(根据PlayerInfo创建) -- [ ] 实现角色移动(调用Move API) -- [ ] 实现其他玩家显示(监听PlayerJoin广播) -- [ ] 实现其他玩家移动同步(监听PlayerMove广播) -- [ ] 实现聊天系统(Send API + Chat广播) -- [ ] 实现敌人AI系统 -- [ ] 实现战斗系统 -- [ ] 实现地图生成(Roguelike) +### 4.2 创建新的业务 UI + +1. 在 `App/` 下创建业务模块目录(如 `App/Game/`) +2. 创建 UI 类继承 `UIBase` +3. 实现 `onGetUrl()` 方法 +4. 在 Cocos Creator 中创建对应的预制体 + +### 4.3 添加新的应用状态 + +1. 在 `App/AppStatus/` 创建新状态类,继承 `BaseState` +2. 在 `AppStatusManager` 中注册新状态 +3. 更新状态流转逻辑 --- -## 九、完整使用流程示例 +## 五、协议同步 -### 9.1 启动应用 +### 5.1 同步服务端协议 + +```bash +npm run sync-shared +``` + +### 5.2 使用协议 ```typescript -// 1. 在Cocos Creator中: -// - 打开 main.scene -// - 创建 Boot 节点 -// - 挂载 Boot.ts 组件 -// - 运行游戏 +import { serviceProto } from '../Shared/protocols/serviceProto'; +import { ReqLogin, ResLogin } from '../Shared/protocols/PtlLogin'; -// 2. Boot组件会自动: -// - 初始化 AppStatusManager -// - 启动应用(Boot状态) -// - 依次流转: Boot -> Login -> Game +netManager.setServiceProto(serviceProto); +const result = await netManager.callApi('Login', { ... }); ``` -### 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 - 添加脚本命令 +### 6.1 游戏逻辑模块 (优先级: 高) ✅ 已完成 +- [x] 创建 App/Game 目录 +- [x] 实现角色控制器 (PlayerController) +- [x] 实现角色移动 (WASD 控制) +- [x] 实现其他玩家显示和同步 (RemotePlayer) +- [x] 实现世界管理器 (World) +- [ ] 实现聊天系统 UI -### 阶段二: 应用状态 (1-2周) ⬜ -1. ⬜ 创建 App/AppStatus 目录结构 -2. ⬜ 实现启动状态 (AppStatusBoot.ts) -3. ⬜ 实现加载状态 (AppStatusLoading.ts) -4. ⬜ 实现登录状态 (AppStatusLogin.ts) -5. ⬜ 实现大厅状态框架 (AppStatusLobby.ts) -6. ⬜ 实现游戏状态框架 (AppStatusGame.ts) -7. ⬜ 实现应用状态机管理器 (AppFSM.ts) -8. ⬜ 集成状态机到主场景 +### 6.2 游戏机制 (优先级: 中) +- [ ] 敌人 AI 系统 +- [ ] 战斗系统 +- [ ] 地图生成(Roguelike) +- [ ] 物品和装备系统 -### 阶段三: UI和交互 (2-3周) ⬜ -1. ⬜ 开发 UI 管理器 -2. ⬜ 实现登录界面 -3. ⬜ 实现加载界面 -4. ⬜ 实现大厅界面 - -### 阶段四: 游戏逻辑 (持续) ⬜ -1. ⬜ 实现游戏状态 (AppStatusGame) -2. ⬜ 实现 Roguelike 核心玩法 -3. ⬜ 完善网络同步 +### 6.3 优化和完善 (优先级: 低) +- [ ] 性能优化 +- [ ] 资源预加载 +- [ ] 音效和音乐 +- [ ] 粒子特效 --- -## 六、注意事项 +## 七、注意事项 -### 6.1 平台兼容性 -- 确保代码在浏览器和小程序平台都能正常运行 -- 使用平台适配器统一接口 -- 避免使用平台特定的 API +1. **模块文档**: 每个模块的详细使用说明请查看对应的 README.md +2. **协议同步**: 开发前确保运行 `npm run sync-shared` 同步最新协议 +3. **资源路径**: UI 资源路径与代码中的 `onGetUrl()` 返回值保持一致 +4. **状态切换**: 使用 `AppStatusManager` 统一管理所有状态切换 +5. **错误处理**: 网络请求和资源加载都要做好错误处理 -### 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 最佳实践 +- ✅ 完成文档重构,将详细文档迁移到模块目录 +- ✅ 更新代码组织规范,允许模块目录创建 README.md +- ✅ 创建 Framework 各模块的 README.md +- ✅ 创建 App 各模块的 README.md +- ✅ 精简 development-plan.md,保留概览和索引 +- ✅ 完成 Game 模块开发 + - ✅ 实现 World 世界管理器 + - ✅ 实现 PlayerController 本地玩家控制器 + - ✅ 实现 RemotePlayer 远程玩家同步 + - ✅ 实现 UIGame 游戏主界面 + - ✅ 更新 AppStatusGame 状态 + - ✅ 接入网络协议 (MsgPlayerJoin, MsgPlayerMove, PtlMove) + - ✅ 创建 Game 模块文档 diff --git a/client/assets/scripts/Framework/UI/README.md b/client/assets/scripts/Framework/UI/README.md index fd26406..056654c 100644 --- a/client/assets/scripts/Framework/UI/README.md +++ b/client/assets/scripts/Framework/UI/README.md @@ -123,9 +123,16 @@ export class UILogin extends UIBase { /** * 必须重载: 返回 UI 资源路径 + * + * 编写规则: + * 1. 普通资源路径: 'prefabs/ui/UILogin' + * 2. Bundle资源路径: '[bundle名]://资源路径' + * 示例: 'ui-bundle://prefabs/UILogin' */ onGetUrl(): string { return 'prefabs/ui/UILogin'; + // 或使用 bundle 方式: + // return 'ui-bundle://prefabs/UILogin'; } /** @@ -323,6 +330,8 @@ onEnd() - 清理资源(可选) ## ⚠️ 注意事项 1. **必须重载 onGetUrl()**: 每个 UI 必须明确定义资源路径 + - 普通资源路径格式: `'prefabs/ui/UILogin'` + - Bundle资源路径格式: `'[bundle名]://资源路径'` (例如: `'ui-bundle://prefabs/UILogin'`) 2. **UI 根节点设置**: 在加载任何 UI 之前,必须先设置 UI 根节点 3. **缓存机制**: 已加载的 UI 会被缓存,再次加载时直接使用缓存 4. **资源自动管理**: UIMgr 会自动管理资源加载和释放 @@ -367,7 +376,11 @@ if (canvas) { ```typescript // 检查: 资源路径是否正确 onGetUrl(): string { - return 'prefabs/ui/UILogin'; // 确保路径正确 + // 普通资源路径 + return 'prefabs/ui/UILogin'; + + // 或使用 Bundle 资源路径 + // return 'ui-bundle://prefabs/UILogin'; } ``` @@ -383,6 +396,8 @@ if (!UIMgr.getInstance().has(UILogin)) { 1. **单一职责**: 每个 UI 类只负责一个界面 2. **资源路径统一**: 建议在配置文件中统一管理 UI 资源路径 + - 对于需要分包加载的 UI,使用 Bundle 路径格式: `'[bundle名]://资源路径'` + - 对于通用 UI,使用普通路径格式: `'资源路径'` 3. **事件解绑**: 在 onEnd() 中解绑所有事件,避免内存泄漏 4. **参数传递**: 使用 params 参数在加载时传递初始数据 5. **缓存利用**: 对频繁切换的 UI,利用缓存避免重复加载 diff --git a/client/assets/scripts/Framework/UI/README.md.meta b/client/assets/scripts/Framework/UI/README.md.meta new file mode 100644 index 0000000..978eaf2 --- /dev/null +++ b/client/assets/scripts/Framework/UI/README.md.meta @@ -0,0 +1,11 @@ +{ + "ver": "1.0.1", + "importer": "text", + "imported": true, + "uuid": "fbfe85a1-0fe1-4ac7-b004-36c370c6a4eb", + "files": [ + ".json" + ], + "subMetas": {}, + "userData": {} +} diff --git a/client/assets/scripts/Framework/UI/UIBase.ts b/client/assets/scripts/Framework/UI/UIBase.ts index 13a272b..9b5b697 100644 --- a/client/assets/scripts/Framework/UI/UIBase.ts +++ b/client/assets/scripts/Framework/UI/UIBase.ts @@ -1,4 +1,4 @@ -import { Node } from 'cc'; +import { Node, Component, Button } from 'cc'; /** * UI基类 @@ -121,4 +121,67 @@ export abstract class UIBase { isLoaded(): boolean { return this._isLoaded; } + + /** + * 查找子节点并获取组件 + * @param path 节点路径,支持多层级查找(如: 'mid/input_account') + * @param componentType 组件类型 + * @returns 组件实例,未找到则返回null + * @example + * const editBox = this.GetChild('mid/input_account', EditBox); + * const button = this.GetChild('btn_login', Button); + */ + protected GetChild(path: string, componentType: { new(): T }): T | null { + if (!this._node) { + console.error('[UIBase] GetChild失败: UI根节点不存在'); + return null; + } + + const childNode = this._node.getChildByPath(path); + if (!childNode) { + console.error(`[UIBase] GetChild失败: 未找到节点 ${path}`); + return null; + } + + const component = childNode.getComponent(componentType); + if (!component) { + console.error(`[UIBase] GetChild失败: 节点 ${path} 未挂载 ${componentType.name} 组件`); + return null; + } + + return component; + } + + /** + * 设置按钮点击事件 + * @param button Button组件或包含Button组件的Node + * @param callback 点击回调函数 + * @param target 回调函数的this指向 + * @example + * const btn = this.GetChild('btn_login', Button); + * this.SetClick(btn, this.onLoginClick, this); + */ + protected SetClick(button: Button | Node | null, callback: () => void, target?: any): void { + if (!button) { + console.error('[UIBase] SetClick失败: button为null'); + return; + } + + let targetNode: Node | null = null; + + // 判断是Button组件还是Node + if (button instanceof Button) { + targetNode = button.node; + } else if (button instanceof Node) { + targetNode = button; + } + + if (!targetNode) { + console.error('[UIBase] SetClick失败: 无法获取目标节点'); + return; + } + + // 绑定点击事件 + targetNode.on(Node.EventType.TOUCH_END, callback, target); + } } diff --git a/client/assets/scripts/Framework/UI/UIMgr.ts b/client/assets/scripts/Framework/UI/UIMgr.ts index a47dc42..228d30e 100644 --- a/client/assets/scripts/Framework/UI/UIMgr.ts +++ b/client/assets/scripts/Framework/UI/UIMgr.ts @@ -82,12 +82,14 @@ export class UIMgr { } console.log(`[UIMgr] 开始加载UI: ${className} (${url})`); - + const args = url.split('://') + const bundleName = args[0]; + const resourcePath = args[1]; // 通过ResMgr加载预制体 try { const prefab = await ResMgr.getInstance().load( - 'resources', - url, + bundleName, + resourcePath, Prefab );