Files
rougelike-demo/client/.github/instructions/development-plan.md
2025-12-14 22:37:49 +08:00

48 KiB

Cocos3.x 项目开发规划

项目概述

本项目是一个 Cocos Creator 3.x 的 3D Roguelike 游戏项目,需要实现网络通信、状态机管理和应用状态流转等核心功能。

📐 开发规范

代码组织规范

  1. 不创建 README.md: 功能模块内不需要创建 README.md 文档,所有文档集中在 .github/instructions 目录
  2. 不创建 index.ts: 不需要为每个模块创建统一导出的 index.ts 文件,直接导入具体文件
  3. 按需导入: 使用时直接从具体文件导入,例如:
    import { NetManager } from './Framework/Net/NetManager';
    import { NetConfig } from './Framework/Net/NetConfig';
    
  4. 文档集中管理: 所有开发文档、规划文档统一放在 .github/instructions 目录
  5. 模块归属规范:
    • Framework/ 目录用于存放通用框架和工具类(FSM、Net、ResMgr、UI基类等)
    • App/ 目录用于存放应用层业务模块(AppStatus、Login、Game等)
    • 业务UI组件必须归属到 App/ 对应的业务模块下,例如:
      • 登录UI: App/Login/UILogin.ts
      • 游戏UI: App/Game/UIGame.ts

文件命名规范

  • 使用 PascalCase 命名 TypeScript 类文件: NetManager.ts, AppStatusBoot.ts
  • 配置/常量文件也使用 PascalCase: NetConfig.ts, NetEvent.ts
  • 一个文件一个主要类,文件名与类名保持一致

📊 项目进度总览

最后更新: 2025-12-14

模块 进度 状态
Framework/FSM 100% 已完成
Framework/Net 100% 已完成
Framework/ResMgr 100% 已完成
Framework/UI 100% 已完成
App/AppStatus 100% 已完成
App/Login 100% 已完成
Boot启动组件 100% 已完成
游戏逻辑 0% 未开始

图例: 未开始 | 🟡 进行中 | 已完成 | ⏸️ 已暂停


一、目录结构规划

assets/scripts/
├── Framework/           # 框架和通用工具类目录
│   ├── Net/            # 网络通信模块 ✅
│   ├── FSM/            # 状态机模块 ✅
│   ├── ResMgr/         # 资源管理模块 ✅
│   ├── UI/             # UI基类和管理器 ✅
│   │   ├── UIBase.ts   # UI基类
│   │   └── UIMgr.ts    # UI管理器
│   └── ...             # 其他框架模块
├── App/                # 应用层代码(业务模块)
│   ├── AppStatus/      # 应用状态机 ✅
│   ├── Login/          # 登录模块 ✅
│   │   └── UILogin.ts  # 登录界面
│   ├── Game/           # 游戏模块
│   │   └── ...         # 游戏相关UI和逻辑
│   └── ...             # 其他业务模块
└── Boot/               # 启动组件 ✅
    └── Boot.ts         # 场景挂载入口点

二、网络通信模块 (Framework/Net)

2.1 目标

实现基于 TSRPC 的网络通信层,支持多平台(浏览器、小程序)。

2.2 依赖包选择

平台 NPM 包
浏览器 (Web) tsrpc-browser
小程序 (微信/抖音/QQ) tsrpc-miniapp

2.3 已实现的核心类

2.3.1 NetManager.ts - 网络管理器

职责: 网络连接管理、消息收发、重连机制

核心方法:

  • setServiceProto(serviceProto) - 设置服务协议 (必须在 init 之前调用)
  • init(config: NetConfig) - 初始化网络配置
  • connect() - 创建客户端实例
  • disconnect() - 清理客户端资源
  • callApi<Req, Res>(apiName, req) - 调用 API
  • listenMsg(msgName, handler) - 监听服务器消息
  • sendMsg(msgName, msg) - 发送消息到服务器
  • on(event, callback) - 监听网络事件

使用示例:

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 HttpClientBrowserHttpClient as HttpClientMiniapp
  • 自动检测 Cocos 平台类型 (sys.platform)
  • 根据平台实例化对应的客户端

核心方法:

  • setServiceProto(serviceProto) - 设置服务协议
  • detectPlatform() - 检测当前运行平台
  • createClient(config) - 创建对应平台的客户端实例
  • isMiniApp() / isBrowser() - 平台判断
  • getPlatformInfo() - 获取平台详细信息

使用示例:

import { PlatformAdapter } from './Framework/Net/PlatformAdapter';

const platform = PlatformAdapter.getCurrentPlatform();
console.log(PlatformAdapter.getPlatformInfo());

2.3.3 NetConfig.ts - 网络配置

职责: 网络相关配置参数

export interface NetConfig {
    serverUrl: string;          // 服务器地址
    timeout?: number;            // 超时时间(ms) 默认 30000
    autoReconnect?: boolean;     // 是否自动重连 默认 true
    reconnectInterval?: number;  // 重连间隔(ms) 默认 3000
    maxReconnectTimes?: number;  // 最大重连次数 默认 5
}

export const DefaultNetConfig: Partial<NetConfig>;  // 默认配置

#### 2.3.4 `NetEvent.ts` - 网络事件 ✅
**职责**: 定义网络相关事件常量

```typescript
export enum NetEvent {
    Connected = "net_connected",           // 连接成功
    Disconnected = "net_disconnected",     // 连接断开
    Reconnecting = "net_reconnecting",     // 正在重连
    ReconnectSuccess = "net_reconnect_success",  // 重连成功
    ReconnectFailed = "net_reconnect_failed",    // 重连失败
    Error = "net_error",                   // 网络错误
    Timeout = "net_timeout"                // 连接超时
}

2.4 共享协议同步

同步脚本: sync-shared.js (根目录)

  • 服务端路径: ../server/src/shared
  • 客户端路径: assets/scripts/Shared
  • 运行命令: npm run sync-shared

使用步骤:

  1. 确保服务端项目在 ../server 目录
  2. 运行 npm run sync-shared 同步协议
  3. 从 Shared 目录导入协议: import { serviceProto } from '../Shared/protocols/serviceProto'

2.5 完整使用流程

// 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 - 状态接口

职责: 定义状态的标准接口

/**
 * 状态接口
 */
export interface IState {
    // 状态名称
    readonly name: string;
    
    // 进入状态
    onEnter(params?: any): void;
    
    // 更新状态
    onUpdate?(dt: number): void;
    
    // 退出状态
    onExit(): void;
}

3.2.2 FSM.ts - 状态机

职责: 管理状态切换和生命周期

/**
 * 有限状态机
 */
export class FSM {
    private _states: Map<string, IState>;
    private _currentState: IState | null;
    
    // 添加状态
    addState(state: IState): void;
    
    // 移除状态
    removeState(stateName: string): void;
    
    // 切换状态
    changeState(stateName: string, params?: any): void;
    
    // 获取当前状态
    getCurrentState(): IState | null;
    
    // 更新(在需要的情况下)
    update(dt: number): void;
}

3.2.3 BaseState.ts - 状态基类

职责: 提供状态的基础实现

/**
 * 状态基类
 */
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 - 清空所有状态

使用示例:

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 - 资源管理器

职责: 管理资源加载、缓存和释放

/**
 * 资源管理器
 */
export class ResMgr {
    private static _instance: ResMgr;
    
    static getInstance(): ResMgr;
    
    /**
     * 加载单个资源
     * @param bundleName bundle名称
     * @param path 资源路径
     * @param type 资源类型
     */
    load<T extends Asset>(
        bundleName: string, 
        path: string, 
        type: typeof Asset
    ): Promise<T>;
    
    /**
     * 预加载资源
     * @param bundleName bundle名称
     * @param path 资源路径
     * @param type 资源类型
     */
    preload<T extends Asset>(
        bundleName: string, 
        path: string, 
        type: typeof Asset,
        onProgress?: (finished: number, total: number) => void
    ): Promise<void>;
    
    /**
     * 加载目录
     * @param bundleName bundle名称
     * @param dir 目录路径
     * @param type 资源类型
     */
    loadDir<T extends Asset>(
        bundleName: string, 
        dir: string, 
        type: typeof Asset
    ): Promise<T[]>;
    
    /**
     * 释放资源
     * @param bundleName bundle名称
     * @param path 资源路径
     */
    release(bundleName: string, path: string): void;
    
    /**
     * 释放目录资源
     * @param bundleName bundle名称
     * @param dir 目录路径
     */
    releaseDir(bundleName: string, dir: string): void;
}

4.2.2 ResConfig.ts - 资源配置

职责: 定义资源加载相关的配置和类型

/**
 * 资源加载配置
 */
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 加载单个资源

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 预加载资源

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 加载目录

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 释放资源

import { ResMgr } from './Framework/ResMgr/ResMgr';

// 释放单个资源
ResMgr.getInstance().release('resources', 'prefabs/Player');

// 释放目录资源
ResMgr.getInstance().releaseDir('resources', 'textures/ui');

4.4 已实现的核心类

4.4.1 ResMgr.ts - 资源管理器

职责:

  • 管理资源加载、缓存和释放
  • 提供从bundle中按路径加载资源的接口
  • 支持资源预加载
  • 支持目录加载

核心方法:

  • static getInstance(): ResMgr - 获取单例
  • load<T>(bundleName, path, type): Promise<T> - 加载单个资源
  • preload<T>(bundleName, path, type, onProgress?): Promise<void> - 预加载资源
  • loadDir<T>(bundleName, dir, type): Promise<T[]> - 加载目录
  • release(bundleName, path): void - 释放单个资源
  • releaseDir(bundleName, dir): void - 释放目录资源
  • releaseAll(): void - 释放所有资源
  • getCacheSize(): number - 获取缓存大小

特性:

  • 单例模式
  • 资源缓存(避免重复加载)
  • Bundle管理
  • 完整的日志输出

4.4.2 ResConfig.ts - 资源配置

职责:

  • 定义资源加载配置接口
  • 提供常用资源路径常量
  • 定义资源类型枚举

核心内容:

  • ResLoadConfig - 资源加载配置接口
  • PreloadConfig - 预加载配置接口
  • ResPath - 常用资源路径常量类
  • ResType - 资源类型枚举

4.4.3 ResMgrExample.ts - 使用示例

包含完整的使用示例:

  • 加载单个资源(Prefab, SpriteFrame, AudioClip)
  • 预加载资源(带进度回调)
  • 加载目录
  • 资源释放
  • 游戏场景资源管理完整流程

五、应用状态机 (App/AppStatus)

4.1 目标

管理应用的整体状态流转,如启动、登录、游戏中等。

4.2 状态设计

4.2.1 状态流转图

[启动Boot] -> [登录Login] -> [游戏Game]
  ↑              ↓              ↓
  └──────────────┘              │
      (退出/死亡)          │
                           │
      ┌────────────────────┘
      │ 监听服务器广播:
      │ - PlayerJoin (玩家加入)
      │ - PlayerMove (玩家移动)
      │ - Chat (聊天消息)
      └─────────────────────

4.3 已实现的核心类

4.3.1 AppStatusBoot.ts - 启动状态

职责:

  • 初始化网络管理器
  • 连接服务器
  • 切换到登录状态

核心方法:

  • onEnter(params?: any): Promise<void> - 进入启动状态,执行初始化
  • initAndConnectNet(): Promise<void> - 初始化并连接网络
  • onExit(): void - 退出启动状态

4.3.2 AppStatusLogin.ts - 登录状态

职责:

  • 显示登录界面
  • 处理玩家ID输入
  • 发送Login API请求
  • 登录成功后直接进入游戏世界

核心方法:

  • onEnter(params?: any): void - 进入登录状态,显示登录界面
  • showLoginUI(): void - 显示登录UI
  • login(playerId: string, playerName?: string): Promise<void> - 执行登录(由UI调用)
  • onExit(): void - 退出登录状态,隐藏登录界面

协议对应:

  • API: Login (ReqLogin -> ResLogin)
  • 请求: { playerId: string, playerName?: string }
  • 响应: { success: boolean, player: PlayerInfo, isNewPlayer: boolean }

4.3.3 AppStatusGame.ts - 游戏状态

职责:

  • 加载游戏场景
  • 初始化玩家角色
  • 监听服务器广播(其他玩家加入、移动等)
  • 游戏主循环

核心方法:

  • onEnter(params?: any): Promise<void> - 进入游戏状态,接收玩家信息
  • loadGameScene(): Promise<void> - 加载游戏场景
  • initGame(): Promise<void> - 初始化游戏(创建玩家角色)
  • listenServerMessages(): void - 监听服务器广播消息
  • startGame(): void - 开始游戏
  • onUpdate(dt: number): void - 游戏主循环(每帧调用)
  • pauseGame(): void - 暂停游戏
  • resumeGame(): void - 恢复游戏
  • onPlayerDeath(): void - 玩家死亡
  • quitGame(): void - 退出游戏(返回登录)
  • onExit(): void - 退出游戏状态,清理资源

监听的服务器广播:

  • PlayerJoin (MsgPlayerJoin) - 其他玩家加入游戏
  • PlayerMove (MsgPlayerMove) - 其他玩家移动
  • Chat (MsgChat) - 聊天消息

发送的API:

  • Move (ReqMove -> ResMove) - 发送移动请求
  • Send (ReqSend -> ResSend) - 发送聊天消息

4.3.4 AppStatusManager.ts - 应用状态管理器

职责:

  • 管理应用的整体状态流转
  • 提供单例访问
  • 初始化所有应用状态(Boot, Login, Game)

核心方法:

  • static getInstance(): AppStatusManager - 获取单例
  • start(): void - 启动应用(从Boot状态开始)
  • changeState(stateName: string, params?: any): void - 切换状态
  • getCurrentStateName(): string | null - 获取当前状态名称
  • getCurrentState(): any - 获取当前状态实例
  • update(dt: number): void - 更新状态机(在主循环中调用)
  • getFSM(): FSM - 获取底层FSM实例
  • destroy(): void - 销毁管理器

状态流程: Boot -> Login -> Game

使用示例:

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的基础接口和生命周期

import { Node } from 'cc';

/**
 * UI基类
 * 所有UI必须继承此类
 */
export abstract class UIBase {
    /** UI根节点 */
    protected _node: Node | null = null;
    
    /** UI是否已加载 */
    protected _isLoaded: boolean = false;
    
    /** UI是否显示中 */
    protected _isShowing: boolean = false;
    
    /**
     * 获取UI资源路径 (必须重载)
     * @returns UI预制体路径
     */
    abstract onGetUrl(): string;
    
    /**
     * UI开始时调用 (可选重载)
     * 在UI预制体加载完成后调用
     */
    onStart?(): void | Promise<void>;
    
    /**
     * UI结束时调用 (可选重载)
     * 在UI被卸载前调用
     */
    onEnd?(): void;
    
    /**
     * UI更新 (可选重载)
     * 在每帧调用
     * @param dt 距离上一帧的时间(秒)
     */
    onUpdate?(dt: number): void;
    
    /**
     * 设置UI根节点
     */
    setNode(node: Node): void {
        this._node = node;
        this._isLoaded = true;
    }
    
    /**
     * 获取UI根节点
     */
    getNode(): Node | null {
        return this._node;
    }
    
    /**
     * 显示UI
     */
    show(): void {
        if (this._node) {
            this._node.active = true;
            this._isShowing = true;
        }
    }
    
    /**
     * 隐藏UI
     */
    hide(): void {
        if (this._node) {
            this._node.active = false;
            this._isShowing = false;
        }
    }
    
    /**
     * 检查UI是否显示中
     */
    isShowing(): boolean {
        return this._isShowing;
    }
    
    /**
     * 检查UI是否已加载
     */
    isLoaded(): boolean {
        return this._isLoaded;
    }
}

6.2.2 UIMgr.ts - UI管理器

职责: 管理UI的加载、卸载和生命周期

import { Node, Prefab, instantiate } from 'cc';
import { UIBase } from './UIBase';
import { ResMgr } from '../ResMgr/ResMgr';

/**
 * UI管理器
 * 管理所有UI的加载、显示、隐藏和销毁
 */
export class UIMgr {
    private static _instance: UIMgr;
    
    /** 已加载的UI实例缓存 */
    private _uiCache: Map<string, UIBase> = new Map();
    
    /** UI父节点(Canvas) */
    private _uiRoot: Node | null = null;
    
    /** 需要更新的UI列表 */
    private _updateList: UIBase[] = [];
    
    static getInstance(): UIMgr {
        if (!this._instance) {
            this._instance = new UIMgr();
        }
        return this._instance;
    }
    
    /**
     * 设置UI根节点
     * @param root Canvas节点或UI容器节点
     */
    setUIRoot(root: Node): void {
        this._uiRoot = root;
    }
    
    /**
     * 加载并显示UI
     * @param uiClass UI类
     * @param params 传递给onStart的参数
     * @returns UI实例
     */
    async load<T extends UIBase>(
        uiClass: new () => T,
        params?: any
    ): Promise<T> {
        const className = uiClass.name;
        
        // 检查缓存
        let ui = this._uiCache.get(className) as T;
        if (ui) {
            console.log(`[UIMgr] 从缓存加载UI: ${className}`);
            ui.show();
            if (ui.onStart) {
                await ui.onStart();
            }
            return ui;
        }
        
        // 创建UI实例
        ui = new uiClass();
        
        // 获取UI资源路径
        const url = ui.onGetUrl();
        if (!url) {
            console.error(`[UIMgr] UI未定义资源路径: ${className}`);
            throw new Error(`UI未定义资源路径: ${className}`);
        }
        
        console.log(`[UIMgr] 开始加载UI: ${className} (${url})`);
        
        // 通过ResMgr加载预制体
        try {
            const prefab = await ResMgr.getInstance().load<Prefab>(
                'resources',
                url,
                Prefab
            );
            
            // 实例化预制体
            const node = instantiate(prefab);
            
            // 添加到UI根节点
            if (this._uiRoot) {
                node.setParent(this._uiRoot);
            }
            
            // 设置UI节点
            ui.setNode(node);
            
            // 缓存UI
            this._uiCache.set(className, ui);
            
            // 如果UI有onUpdate方法,添加到更新列表
            if (ui.onUpdate) {
                this._updateList.push(ui);
            }
            
            // 调用onStart生命周期
            if (ui.onStart) {
                await ui.onStart();
            }
            
            // 显示UI
            ui.show();
            
            console.log(`[UIMgr] UI加载完成: ${className}`);
            return ui;
            
        } catch (error) {
            console.error(`[UIMgr] 加载UI失败: ${className}`, error);
            throw error;
        }
    }
    
    /**
     * 卸载UI
     * @param uiClass UI类或类名
     */
    unload(uiClass: (new () => UIBase) | string): void {
        const className = typeof uiClass === 'string' 
            ? uiClass 
            : uiClass.name;
        
        const ui = this._uiCache.get(className);
        if (!ui) {
            console.warn(`[UIMgr] UI未加载,无法卸载: ${className}`);
            return;
        }
        
        console.log(`[UIMgr] 卸载UI: ${className}`);
        
        // 调用onEnd生命周期
        if (ui.onEnd) {
            ui.onEnd();
        }
        
        // 从更新列表移除
        const index = this._updateList.indexOf(ui);
        if (index !== -1) {
            this._updateList.splice(index, 1);
        }
        
        // 销毁节点
        const node = ui.getNode();
        if (node) {
            node.destroy();
        }
        
        // 从缓存移除
        this._uiCache.delete(className);
        
        // 释放资源
        const url = ui.onGetUrl();
        if (url) {
            ResMgr.getInstance().release('resources', url);
        }
    }
    
    /**
     * 获取已加载的UI实例
     * @param uiClass UI类或类名
     */
    get<T extends UIBase>(uiClass: (new () => T) | string): T | null {
        const className = typeof uiClass === 'string' 
            ? uiClass 
            : uiClass.name;
        
        return (this._uiCache.get(className) as T) || null;
    }
    
    /**
     * 检查UI是否已加载
     * @param uiClass UI类或类名
     */
    has(uiClass: (new () => UIBase) | string): boolean {
        const className = typeof uiClass === 'string' 
            ? uiClass 
            : uiClass.name;
        
        return this._uiCache.has(className);
    }
    
    /**
     * 更新所有需要更新的UI
     * @param dt 距离上一帧的时间(秒)
     */
    update(dt: number): void {
        for (const ui of this._updateList) {
            if (ui.isShowing() && ui.onUpdate) {
                ui.onUpdate(dt);
            }
        }
    }
    
    /**
     * 卸载所有UI
     */
    unloadAll(): void {
        console.log(`[UIMgr] 卸载所有UI`);
        
        const classNames = Array.from(this._uiCache.keys());
        for (const className of classNames) {
            this.unload(className);
        }
    }
    
    /**
     * 销毁管理器
     */
    destroy(): void {
        this.unloadAll();
        this._uiCache.clear();
        this._updateList = [];
        this._uiRoot = null;
    }
}

6.3 使用示例

6.3.1 创建自定义UI

import { UIBase } from './Framework/UI/UIBase';
import { Label, Button } from 'cc';

/**
 * 登录界面
 */
export class UILogin extends UIBase {
    private _labelTitle: Label | null = null;
    private _btnLogin: Button | null = null;
    
    /**
     * 必须重载: 返回UI预制体路径
     */
    onGetUrl(): string {
        return 'prefabs/ui/UILogin';
    }
    
    /**
     * 可选重载: UI开始时调用
     */
    async onStart(): Promise<void> {
        console.log('[UILogin] 开始初始化');
        
        // 获取UI组件
        const node = this.getNode();
        if (node) {
            this._labelTitle = node.getChildByName('Title')
                ?.getComponent(Label) || null;
            
            this._btnLogin = node.getChildByName('BtnLogin')
                ?.getComponent(Button) || null;
        }
        
        // 设置UI内容
        if (this._labelTitle) {
            this._labelTitle.string = '欢迎登录';
        }
        
        // 绑定按钮事件
        if (this._btnLogin) {
            this._btnLogin.node.on('click', this.onClickLogin, this);
        }
    }
    
    /**
     * 可选重载: UI结束时调用
     */
    onEnd(): void {
        console.log('[UILogin] 清理资源');
        
        // 解绑事件
        if (this._btnLogin) {
            this._btnLogin.node.off('click', this.onClickLogin, this);
        }
    }
    
    /**
     * 可选重载: 每帧更新
     */
    onUpdate(dt: number): void {
        // 可以在这里处理动画、倒计时等
    }
    
    private onClickLogin(): void {
        console.log('[UILogin] 点击登录按钮');
        // 处理登录逻辑
    }
}

6.3.2 使用UIMgr管理UI

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 - 登录按钮

核心方法:

class UILogin extends UIBase {
    /**
     * 获取UI资源路径
     */
    onGetUrl(): string {
        return 'res/UI/Login/UILogin';
    }
    
    /**
     * UI初始化 - 查找节点并绑定事件
     */
    async onStart(params?: any): Promise<void>;
    
    /**
     * 登录按钮点击处理
     */
    private async onLoginClick(): Promise<void>;
    
    /**
     * 执行登录逻辑
     * @param account 账号
     */
    private async login(account: string): Promise<void>;
    
    /**
     * UI清理 - 解绑事件
     */
    onEnd(): void;
}

使用示例:

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. 发送登录请求:
    const result = await NetManager.getInstance().callApi<ReqLogin, ResLogin>('Login', { 
        account: account 
    });
    
  5. 处理登录响应:
    • 成功: 调用 AppStatusManager.getInstance().changeState('Game', params) 切换到游戏状态
    • 失败: 显示错误提示(TODO)
  6. 隐藏登录界面: 自动调用 this.hide() 隐藏登录界面

7.5 登录协议

7.5.1 LoginProtocol.ts - 协议类型定义

位置: Framework/Net/LoginProtocol.ts (临时定义,待服务端协议同步后替换)

请求类型: ReqLogin

interface ReqLogin {
    account: string;     // 账号
    password?: string;   // 密码(可选)
}

响应类型: ResLogin

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 处理
import { UIMgr } from "../../Framework/UI/UIMgr";
import { UILogin } from "../Login/UILogin";

export class AppStatusLogin extends BaseState {
    async onEnter(params?: any): Promise<void> {
        // 显示登录界面
        await UIMgr.getInstance().load(UILogin);
    }
    
    onExit(): void {
        // 隐藏登录界面
        UIMgr.getInstance().hide(UILogin);
    }
}

7.7 注意事项

  1. UI资源准备: 需要在 assets/res/UI/Login/ 目录下创建 UILogin 预制体
  2. 节点结构: 预制体中必须包含 mid/input_accountbtn_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

{
    playerId: string;      // 玩家ID
    playerName?: string;   // 玩家昵称(可选,新玩家时使用)
}

响应: ResLogin

{
    success: boolean;      // 是否成功
    message: string;       // 消息
    player?: PlayerInfo;   // 玩家信息
    isNewPlayer?: boolean; // 是否新玩家
}

6.1.2 Move - 移动

请求: ReqMove

{
    x: number;  // 目标位置 X
    y: number;  // 目标位置 Y
}

响应: ResMove

{
    success: boolean;       // 是否成功
    message?: string;       // 消息
    position?: Position;    // 实际移动后的位置
}

6.1.3 Send - 发送聊天

请求: ReqSend

{
    content: string;  // 聊天内容
}

响应: ResSend

{
    success: boolean;  // 是否成功
}

6.2 MSG (服务器广播)

6.2.1 PlayerJoin - 玩家加入

{
    playerId: string;      // 加入的玩家ID
    playerName: string;    // 玩家昵称
    position: Position;    // 玩家位置
    isNewPlayer: boolean;  // 是否新玩家
    timestamp: number;     // 加入时间戳
}

6.2.2 PlayerMove - 玩家移动

{
    playerId: string;      // 移动的玩家ID
    playerName: string;    // 玩家昵称
    position: Position;    // 移动后的位置
    timestamp: number;     // 移动时间戳
}

6.2.3 Chat - 聊天消息

{
    playerId: string;      // 发送者ID
    playerName: string;    // 发送者昵称
    content: string;       // 聊天内容
    timestamp: number;     // 发送时间戳
}

6.3 基础类型

Position - 位置

{
    x: number;  // X坐标
    y: number;  // Y坐标
    z: number;  // Z坐标
}

PlayerInfo - 玩家信息

{
    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 启动应用

// 1. 在Cocos Creator中:
// - 打开 main.scene
// - 创建 Boot 节点
// - 挂载 Boot.ts 组件
// - 运行游戏

// 2. Boot组件会自动:
// - 初始化 AppStatusManager
// - 启动应用(Boot状态)
// - 依次流转: Boot -> Login -> Game

9.2 登录流程

// 在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 游戏内操作

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 退出游戏

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