From 4e2726dd1be1a59439bdd95a9b9028c1e1a6cc96 Mon Sep 17 00:00:00 2001 From: gongxh Date: Thu, 27 Nov 2025 10:54:14 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=A1=B9=E7=9B=AE=E8=A7=84?= =?UTF-8?q?=E5=88=99;=E4=BF=AE=E5=A4=8D=E5=87=A0=E5=A4=84=E6=BD=9C?= =?UTF-8?q?=E5=9C=A8=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claudeignore | 55 --- .cursor/rules/README.md | 133 +++++++ .cursor/rules/architecture-patterns.mdc | 383 ++++++++++++++++++++ .cursor/rules/cocos-creator.mdc | 171 +++++++++ .cursor/rules/data-binding.mdc | 347 ++++++++++++++++++ .cursor/rules/decorator-patterns.mdc | 276 +++++++++++++++ .cursor/rules/fairygui.mdc | 256 ++++++++++++++ .cursor/rules/hot-update.mdc | 413 ++++++++++++++++++++++ .cursor/rules/logging-debugging.mdc | 258 ++++++++++++++ .cursor/rules/minigame-platform.mdc | 449 ++++++++++++++++++++++++ .cursor/rules/project-overview.mdc | 260 ++++++++++++++ .cursor/rules/typescript-general.mdc | 111 ++++++ package.json | 2 +- src/condition/ConditionManager.ts | 3 + src/condition/node/ConditionBase.ts | 2 +- src/fgui/WindowHeader.ts | 2 +- src/hotupdate/HotUpdateManager.ts | 2 +- src/minigame/MiniHelper.ts | 2 +- src/minigame/wechat/WechatAds.ts | 2 +- src/tool/DataStruct/BinaryHeap.ts | 14 +- src/tool/timer/Timer.ts | 1 + src/ui/WindowGroup.ts | 5 +- src/ui/WindowResPool.ts | 2 +- 23 files changed, 3081 insertions(+), 68 deletions(-) delete mode 100644 .claudeignore create mode 100644 .cursor/rules/README.md create mode 100644 .cursor/rules/architecture-patterns.mdc create mode 100644 .cursor/rules/cocos-creator.mdc create mode 100644 .cursor/rules/data-binding.mdc create mode 100644 .cursor/rules/decorator-patterns.mdc create mode 100644 .cursor/rules/fairygui.mdc create mode 100644 .cursor/rules/hot-update.mdc create mode 100644 .cursor/rules/logging-debugging.mdc create mode 100644 .cursor/rules/minigame-platform.mdc create mode 100644 .cursor/rules/project-overview.mdc create mode 100644 .cursor/rules/typescript-general.mdc diff --git a/.claudeignore b/.claudeignore deleted file mode 100644 index dfca827..0000000 --- a/.claudeignore +++ /dev/null @@ -1,55 +0,0 @@ -# Claude Code ignore rules for kunpolibrary -# 昆坡库项目过滤规则,减少上下文消耗 - -# Demo and example files - 示例和演示文件 -demo/ -**/demo/ - -# Dependencies - 依赖包 -node_modules/ -**/node_modules/ - -# Generated/build output - 构建产物 -dist/ -**/dist/ - -# Type definitions - 类型定义文件 -libs/ -**/libs/ - -# Images and assets - 图片和静态资源 -image/ -**/image/ -*.jpg -*.jpeg -*.png -*.gif -*.ico - -# Other common build artifacts - 其他常见构建产物 -build/ -**/build/ -temp/ -**/temp/ -.temp/ -**/.temp/ - -# Cache directories - 缓存目录 -.cache/ -**/.cache/ - -# Log files - 日志文件 -*.log -logs/ -**/logs/ - -# IDE and editor files - IDE和编辑器文件 -.vscode/ -.idea/ -*.swp -*.swo -*~ - -# OS generated files - 操作系统生成的文件 -.DS_Store -Thumbs.db \ No newline at end of file diff --git a/.cursor/rules/README.md b/.cursor/rules/README.md new file mode 100644 index 0000000..b32d84d --- /dev/null +++ b/.cursor/rules/README.md @@ -0,0 +1,133 @@ +# KunpoCC Cursor Rules + +这个目录包含了 KunpoCC 项目的 Cursor AI 编程助手规则文件。这些规则帮助 Cursor 更好地理解项目架构和编码规范,提供更准确的代码建议和自动完成。 + +## 规则文件结构 + +### 📋 [project-overview.mdc](./project-overview.mdc) +**总体项目规范** - 始终应用 +- 项目架构和目录结构 +- 开发流程和质量保证 +- 版本管理和部署规范 +- 适用范围:所有文件 + +### 🔧 [typescript-general.mdc](./typescript-general.mdc) +**TypeScript 通用规范** - 始终应用 +- 命名约定和代码风格 +- 类型定义和泛型使用 +- JSDoc 注释规范 +- 错误处理模式 +- 适用范围:`src/**/*.ts` + +### 🎮 [cocos-creator.mdc](./cocos-creator.mdc) +**Cocos Creator 开发规范** +- Component 基类设计模式 +- 生命周期管理 +- 平台适配和节点管理 +- 适配器模式应用 +- 适用范围:`src/cocos/**/*.ts`, `src/global/**/*.ts` + +### 🖼️ [fairygui.mdc](./fairygui.mdc) +**FairyGUI UI 系统规范** +- 窗口基类和生命周期 +- 窗口管理器模式 +- 装饰器系统使用 +- 资源管理和屏幕适配 +- 适用范围:`src/fgui/**/*.ts`, `src/ui/**/*.ts` + +### 🎨 [decorator-patterns.mdc](./decorator-patterns.mdc) +**装饰器模式规范** +- namespace 封装模式 +- 类装饰器和属性装饰器 +- 方法装饰器和元数据管理 +- 动态注册支持 +- 适用范围:`**/*Decorator*.ts`, `**/*decorator*.ts` + +### 📊 [data-binding.mdc](./data-binding.mdc) +**数据绑定系统规范** +- DataBase 基类设计 +- 强类型绑定装饰器 +- 绑定管理器和批量更新 +- 路径解析和类型安全 +- 适用范围:`src/data/**/*.ts`, `**/*Data*.ts` + +### 🐛 [logging-debugging.mdc](./logging-debugging.mdc) +**日志系统和调试规范** - 始终应用 +- 统一日志接口和级别 +- 调试模式管理 +- 全局调试工具 +- 性能监控日志 +- 适用范围:`src/tool/log.ts`, `**/*.ts` + +### 🏗️ [architecture-patterns.mdc](./architecture-patterns.mdc) +**架构模式规范** +- 单例管理器模式 +- 抽象基类设计 +- 接口和契约定义 +- 工厂模式和适配器模式 +- 适用范围:`src/**/*.ts` + +### 📱 [minigame-platform.mdc](./minigame-platform.mdc) +**小游戏平台规范** +- 平台检测和分类 +- 平台适配器设计 +- 统一接口封装 +- 平台特定功能实现 +- 适用范围:`src/minigame/**/*.ts`, `src/global/Platform.ts` + +### 🔄 [hot-update.mdc](./hot-update.mdc) +**热更新系统规范** +- 管理器单例模式 +- Promise 结果模式 +- manifest 管理 +- 进度监控和错误处理 +- 适用范围:`src/hotupdate/**/*.ts` + +## 规则应用方式 + +### 自动应用的规则 +以下规则会自动应用到相关文件: +- `project-overview.mdc` - 所有文件 +- `typescript-general.mdc` - 所有 TypeScript 文件 +- `logging-debugging.mdc` - 所有文件 + +### 条件应用的规则 +其他规则通过文件路径模式匹配自动应用到相关文件。 + +### 手动引用规则 +你也可以在与 Cursor 的对话中手动引用特定的规则文件: +``` +请参考 FairyGUI 规范来实现这个窗口类 +请按照数据绑定规范来设计这个数据类 +``` + +## 使用建议 + +### 开发新功能时 +1. 先查看 `project-overview.mdc` 了解整体架构 +2. 根据功能类型查看相应的专门规范 +3. 遵循 `typescript-general.mdc` 的基本编码规范 + +### 重构代码时 +1. 参考 `architecture-patterns.mdc` 的设计模式 +2. 检查是否符合相关模块的特定规范 +3. 确保日志和错误处理符合规范 + +### 添加新平台支持时 +1. 参考 `minigame-platform.mdc` 的适配模式 +2. 遵循统一接口设计原则 +3. 添加相应的平台检测和错误处理 + +## 规则维护 + +### 更新规则 +- 当项目架构发生重大变更时,及时更新相关规则 +- 新增功能模块时,考虑是否需要新的规则文件 +- 定期检查规则的准确性和完整性 + +### 规则版本控制 +- 规则文件纳入版本控制 +- 重大规则变更记录在项目 CHANGELOG 中 +- 确保团队成员了解规则更新 + +这些规则文件帮助确保代码质量和一致性,同时让 Cursor AI 能够更好地理解项目上下文,提供更准确的编程辅助。 diff --git a/.cursor/rules/architecture-patterns.mdc b/.cursor/rules/architecture-patterns.mdc new file mode 100644 index 0000000..f5a9f2e --- /dev/null +++ b/.cursor/rules/architecture-patterns.mdc @@ -0,0 +1,383 @@ +--- +description: "架构模式和设计原则规范" +globs: ["src/**/*.ts"] +alwaysApply: false +type: "architecture" +--- + +# 架构模式和设计原则 + +## 单例管理器模式 + +### 管理器类设计规范 +```typescript +export class WindowManager { + /** 使用私有静态属性存储状态 */ + private static _groups: Map = new Map(); + private static _windows: Map = new Map(); + private static _resPool: WindowResPool; + + /** 禁用构造函数 */ + private constructor() {} + + /** 提供静态方法访问功能 */ + public static showWindow(windowName: string): Promise { + // 实现逻辑 + } + + /** 内部方法使用下划线前缀和 @internal 注释 */ + /** + * 添加窗口 (框架内部使用) + * @internal + */ + public static _addWindow(name: string, window: IWindow): void { + this._windows.set(name, window); + } +} +``` + +### 管理器初始化模式 +```typescript +export class SomeManager { + private static _initialized: boolean = false; + + /** + * 初始化管理器 (框架内部调用) + * @internal + */ + public static _init(dependencies: Dependencies): void { + if (this._initialized) { + warn("管理器已经初始化"); + return; + } + + this._initialized = true; + // 初始化逻辑 + } + + private static ensureInitialized(): void { + if (!this._initialized) { + throw new Error("管理器未初始化,请先调用 _init 方法"); + } + } +} +``` + +## 抽象基类模式 + +### 基类设计原则 +```typescript +/** + * 抽象基类 - 定义共同接口和默认实现 + */ +export abstract class WindowBase extends Component { + /** 共同属性 */ + protected _gcom: GComponent; + protected _isShow: boolean = false; + + /** 抽象方法 - 子类必须实现 */ + protected abstract onInit(): void; + + /** 虚方法 - 子类可选择重写 */ + protected onShow(userdata?: any): void { + // 默认实现 + } + + protected onClose(): void { + // 默认实现 + } + + /** 最终方法 - 不允许重写 */ + public final show(userdata?: any): void { + if (this._isShow) return; + + this.onShow(userdata); + this._isShow = true; + } +} + +/** + * 具体实现类 + */ +export abstract class Window extends WindowBase { + /** 进一步的抽象和默认实现 */ + protected onAdapted(): void { + // 适配逻辑 + } + + /** 新增的生命周期方法 */ + protected onHide(): void { } + protected onShowFromHide(): void { } + protected onCover(): void { } + protected onRecover(): void { } +} +``` + +### 模块基类设计 +```typescript +export abstract class ModuleBase extends Component implements IModule { + /** 模块标识 */ + public moduleName: string; + + /** 框架调用的初始化方法 */ + public init(): void { + debug(`模块初始化: ${this.moduleName}`); + this.onInit(); + } + + /** 子类实现的初始化逻辑 */ + protected abstract onInit(): void; +} +``` + +## 接口和契约设计 + +### 接口定义规范 +```typescript +/** + * 窗口接口 - 定义窗口必须实现的方法 + */ +export interface IWindow { + /** 窗口显示 */ + _show(userdata?: any): void; + + /** 窗口隐藏 */ + _hide(): void; + + /** 窗口关闭 */ + _close(): void; + + /** 窗口恢复 */ + _recover(): void; + + /** 屏幕尺寸变化适配 */ + screenResize(): void; +} + +/** + * 模块接口 + */ +export interface IModule { + /** 模块名称 */ + moduleName: string; + + /** 模块初始化 */ + init(): void; +} +``` + +### 配置接口设计 +```typescript +export interface IPackageConfigRes { + [windowName: string]: { + group: string; + pkg: string; + bundle?: string; + }; +} + +export interface FrameConfig { + /** 开启debug 默认: false */ + debug?: boolean; +} +``` + +## 工厂模式应用 + +### 动态注册工厂 +```typescript +export class ComponentExtendHelper { + private static _componentMaps: Map = new Map(); + + /** + * 注册组件类型 + */ + public static register(): void { + for (const { ctor, res } of _uidecorator.getComponentMaps().values()) { + // 使用 FairyGUI 的工厂方法注册 + UIObjectFactory.setPackageItemExtension( + `ui://${res.pkg}/${res.name}`, + ctor + ); + } + } + + /** + * 动态注册单个组件 + */ + public static dynamicRegister(ctor: any, pkg: string, name: string): void { + UIObjectFactory.setPackageItemExtension(`ui://${pkg}/${name}`, ctor); + } +} +``` + +## 资源池模式 + +### 资源管理设计 +```typescript +export class WindowResPool { + /** 资源信息映射 */ + private _windowInfos: Map = new Map(); + private _headerInfos: Map = new Map(); + + /** 引用计数管理 */ + private _refCounts: Map = new Map(); + + /** + * 添加资源引用 + */ + public addResRef(windowName: string): void { + const count = this._refCounts.get(windowName) || 0; + this._refCounts.set(windowName, count + 1); + } + + /** + * 释放资源引用 + */ + public releaseWindowRes(windowName: string): void { + const count = this._refCounts.get(windowName) || 0; + if (count > 0) { + const newCount = count - 1; + this._refCounts.set(windowName, newCount); + + // 引用为0时释放资源 + if (newCount === 0) { + this.unloadResource(windowName); + } + } + } +} +``` + +## 事件驱动架构 + +### 事件系统集成 +```typescript +// 使用外部事件系统 +import { EventEmitter } from "kunpocc-event"; + +export class DataBase extends EventEmitter { + protected notify(path: string, value: any): void { + // 触发事件通知 + this.emit(`change:${path}`, value); + + // 同时支持绑定系统 + BindManager.notifyChange(`${this.constructor.name}:${path}`, value, this); + } +} +``` + +### 生命周期事件 +```typescript +export class WindowManager { + /** 窗口生命周期事件 */ + public static readonly events = { + WINDOW_SHOW: 'window:show', + WINDOW_HIDE: 'window:hide', + WINDOW_CLOSE: 'window:close' + } as const; + + public static showWindow(windowName: string): void { + // 业务逻辑... + + // 触发事件 + GlobalEvent.emit(this.events.WINDOW_SHOW, { windowName }); + } +} +``` + +## 适配器模式 + +### 平台适配设计 +```typescript +export abstract class PlatformAdapter { + /** 平台特定的实现 */ + public abstract showBanner(): void; + public abstract vibrate(): void; + public abstract getSystemInfo(): SystemInfo; +} + +export class WechatAdapter extends PlatformAdapter { + public showBanner(): void { + wx.createBannerAd(/* ... */); + } + + public vibrate(): void { + wx.vibrateShort(); + } +} + +export class AlipayAdapter extends PlatformAdapter { + public showBanner(): void { + my.createBannerAd(/* ... */); + } + + public vibrate(): void { + my.vibrate(); + } +} +``` + +### 引擎适配 +```typescript +export class CocosAdapter { + public init(): void { + this.initUI(); + this.initInput(); + this.initAudio(); + } + + private initUI(): void { + // 初始化UI系统适配 + const uiModule = new CocosUIModule(); + uiModule.init(); + } +} +``` + +## 依赖注入模式 + +### 服务定位器模式 +```typescript +export class ServiceLocator { + private static _services: Map = new Map(); + + public static register(name: string, service: T): void { + this._services.set(name, service); + } + + public static get(name: string): T { + const service = this._services.get(name); + if (!service) { + throw new Error(`Service not found: ${name}`); + } + return service as T; + } +} + +// 使用示例 +ServiceLocator.register('WindowManager', WindowManager); +const windowManager = ServiceLocator.get('WindowManager'); +``` + +## 架构最佳实践 + +### 1. 职责分离 +- 管理器负责全局状态和协调 +- 基类提供共同功能和接口 +- 具体实现类专注业务逻辑 + +### 2. 依赖管理 +- 使用接口定义依赖契约 +- 通过工厂和注入管理依赖关系 +- 避免循环依赖 + +### 3. 扩展性设计 +- 使用装饰器支持动态注册 +- 提供插件化的扩展点 +- 保持向后兼容性 + +### 4. 错误处理 +- 在架构边界处理错误 +- 提供降级和容错机制 +- 记录详细的错误信息 \ No newline at end of file diff --git a/.cursor/rules/cocos-creator.mdc b/.cursor/rules/cocos-creator.mdc new file mode 100644 index 0000000..7954de7 --- /dev/null +++ b/.cursor/rules/cocos-creator.mdc @@ -0,0 +1,171 @@ +--- +description: "Cocos Creator 3.x 开发规范和最佳实践" +globs: ["src/cocos/**/*.ts", "src/global/**/*.ts"] +alwaysApply: false +type: "framework-specific" +--- + +# Cocos Creator 3.x 开发规范 + +## 组件基类设计 + +### 继承 Component 的标准模式 +```typescript +import { _decorator, Component } from "cc"; +const { property } = _decorator; + +export abstract class CocosEntry extends Component { + @property({ displayName: "uiConfig", type: JsonAsset, tooltip: "编辑器导出的UI配置" }) + uiConfig: JsonAsset = null; + + @property({ displayName: "游戏帧率" }) + fps: number = 60; + + /** + * 虚函数,子类需要实现 + * kunpo库初始化完成后调用 + */ + public abstract onInit(): void; +} +``` + +### 模块基类模式 +```typescript +export abstract class ModuleBase extends Component implements IModule { + /** 模块名称 */ + public moduleName: string; + + /** 模块初始化 (内部使用) */ + public init(): void { } + + /** 模块初始化完成后调用的函数 */ + protected abstract onInit(): void; +} +``` + +## 生命周期管理 + +### 组件生命周期规范 +- `start()`: 用于框架初始化,标记为 `@internal` +- `onInit()`: 用户自定义初始化,需要子类实现 +- `onDestroy()`: 清理资源和取消事件监听 + +### 时间和更新管理 +```typescript +// 使用统一的时间系统 +import { GlobalTimer, InnerTimer } from "../global"; + +private initTime(): void { + Time._configBoot(); + InnerTimer.initTimer(); + GlobalTimer.initTimer(); + this.schedule(this.tick.bind(this), 0, macro.REPEAT_FOREVER); +} + +private tick(dt: number): void { + InnerTimer.update(dt); + GlobalTimer.update(dt); +} +``` + +## 平台适配模式 + +### 平台检测和初始化 +```typescript +private initPlatform(): void { + Platform.isNative = sys.isNative; + Platform.isMobile = sys.isMobile; + Platform.isNativeMobile = sys.isNative && sys.isMobile; + + switch (sys.platform) { + case sys.Platform.WECHAT_GAME: + Platform.isWX = true; + Platform.platform = PlatformType.WX; + break; + case sys.Platform.ALIPAY_MINI_GAME: + Platform.isAlipay = true; + Platform.platform = PlatformType.Alipay; + break; + // ... 其他平台 + } +} +``` + +### 平台特定功能 +- 使用 `Platform` 类进行统一的平台判断 +- 小游戏平台适配通过专门的适配类实现 +- 避免在业务代码中直接使用 `sys` 对象 + +## 节点管理 + +### 持久化节点模式 +```typescript +protected start(): void { + // 设置为持久化节点 + director.addPersistRootNode(this.node); + this.node.setSiblingIndex(this.node.children.length - 1); +} +``` + +### 组件查找模式 +```typescript +private initModule(): void { + // 递归查找所有子节点中的模块组件 + for (const module of this.getComponentsInChildren(ModuleBase)) { + debug(`module:${module.moduleName}`); + module.init(); + } +} +``` + +## 适配器模式 + +### 引擎适配器设计 +- 通过 `CocosAdapter` 统一处理引擎特定功能 +- UI系统通过 `CocosUIModule` 进行适配 +- 窗口容器使用 `CocosWindowContainer` + +```typescript +class CocosAdapter { + init(): void { + // 初始化引擎特定功能 + this.initUI(); + this.initScreen(); + } +} +``` + +## 资源管理 + +### 配置文件处理 +```typescript +// 使用 JsonAsset 处理配置 +@property({ type: JsonAsset }) +uiConfig: JsonAsset = null; + +protected start(): void { + PropsHelper.setConfig(this.uiConfig?.json); +} +``` + +### Bundle 资源管理 +- UI 资源通过 bundle 参数指定包名 +- 支持动态资源加载和释放 +- 使用资源池管理资源生命周期 + +## 调试和开发工具 + +### 全局调试接口 +```typescript +// 暴露调试接口到全局对象 +let _global = globalThis || window || global; +(_global as any)["getKunpoRegisterWindowMaps"] = function () { + return _uidecorator.getWindowMaps() as any; +}; +``` + +### 帧率控制 +```typescript +// 统一的帧率设置 +game.frameRate = this.fps; +``` \ No newline at end of file diff --git a/.cursor/rules/data-binding.mdc b/.cursor/rules/data-binding.mdc new file mode 100644 index 0000000..e56cd81 --- /dev/null +++ b/.cursor/rules/data-binding.mdc @@ -0,0 +1,347 @@ +--- +description: "强类型数据绑定系统开发规范" +globs: ["src/data/**/*.ts", "**/*Data*.ts"] +alwaysApply: false +type: "data-architecture" +--- + +# 强类型数据绑定系统规范 + +## 数据基类设计 + +### DataBase 基类模式 +```typescript +export class DataBase { + /** 数据变化监听器 */ + private _watchers: Map = new Map(); + + /** + * 注册属性监听器 + * @param path 属性路径 + * @param callback 回调函数 + */ + public watch(path: string, callback: Function): void { + if (!this._watchers.has(path)) { + this._watchers.set(path, []); + } + this._watchers.get(path)!.push(callback); + } + + /** + * 触发属性变化通知 + * @param path 属性路径 + * @param value 新值 + */ + protected notify(path: string, value: any): void { + if (this._watchers.has(path)) { + this._watchers.get(path)!.forEach(callback => { + callback(value); + }); + } + } +} +``` + +### 数据类定义规范 +```typescript +class GameData extends DataBase { + private _level: number = 1; + private _coins: number = 0; + private _items: Item[] = []; + + // 使用 getter/setter 实现响应式 + get level(): number { + return this._level; + } + + set level(value: number) { + if (this._level !== value) { + this._level = value; + this.notify('level', value); + } + } + + get coins(): number { + return this._coins; + } + + set coins(value: number) { + if (this._coins !== value) { + this._coins = value; + this.notify('coins', value); + } + } + + // 复杂属性的变化通知 + addItem(item: Item): void { + this._items.push(item); + this.notify('items', this._items); + this.notify('items.length', this._items.length); + } +} +``` + +## 装饰器绑定系统 + +### 强类型属性绑定 +```typescript +export namespace data { + /** + * 强类型属性绑定装饰器 + * @param dataClass 数据类构造函数 + * @param selector 类型安全的路径选择器 + * @param callback 变化回调函数 + * @param immediate 是否立即触发 + */ + export function bindProp( + dataClass: new () => T, + selector: (data: T) => any, + callback: (item: any, value?: any, data?: T) => void, + immediate: boolean = false + ) { + return function (target: any, prop: string | symbol) { + const path = `${dataClass.name}:${extractPathFromSelector(selector)}`; + + let ctor = target.constructor; + ctor[BIND_METADATA_KEY] = ctor[BIND_METADATA_KEY] || []; + ctor[BIND_METADATA_KEY].push({ + prop, callback, path, immediate, isMethod: false + }); + }; + } +} +``` + +### 方法绑定装饰器 +```typescript +/** + * 强类型方法绑定装饰器 + * @param dataClass 数据类构造函数 + * @param selector 类型安全的路径选择器 + * @param immediate 是否立即触发 + */ +export function bindMethod( + dataClass: new () => T, + selector: (data: T) => any, + immediate: boolean = false +) { + return function (target: any, method: string | symbol, descriptor?: PropertyDescriptor) { + const path = `${dataClass.name}:${extractPathFromSelector(selector)}`; + + let ctor = target.constructor; + ctor[BIND_METADATA_KEY] = ctor[BIND_METADATA_KEY] || []; + ctor[BIND_METADATA_KEY].push({ + prop: method, + callback: descriptor!.value, + path, + immediate, + isMethod: true + }); + return descriptor; + }; +} +``` + +## 使用示例 + +### UI 数据绑定示例 +```typescript +class PlayerData extends DataBase { + name: string = ""; + level: number = 1; + exp: number = 0; + maxExp: number = 100; + + // 计算属性 + get expProgress(): number { + return this.exp / this.maxExp; + } +} + +@uiclass("main", "player", "PlayerPanel") +export class PlayerPanel extends Window { + @uiprop nameLabel: GLabel; + @uiprop levelLabel: GLabel; + @uiprop expBar: GProgressBar; + + // 绑定玩家名称到标签 + @data.bindProp(PlayerData, data => data.name, function(item, value) { + this.nameLabel.text = value; + }) + private _nameBinding: any; + + // 绑定等级显示 + @data.bindMethod(PlayerData, data => data.level) + private onLevelChanged(value: number): void { + this.levelLabel.text = `Lv.${value}`; + } + + // 绑定经验条 + @data.bindMethod(PlayerData, data => data.expProgress) + private onExpChanged(progress: number): void { + this.expBar.value = progress * 100; + } + + protected onInit(): void { + // 初始化绑定 + data.initializeBindings(this); + } + + protected onClose(): void { + // 清理绑定 + data.cleanupBindings(this); + } +} +``` + +## 绑定管理器 + +### BindManager 设计 +```typescript +export class BindManager { + private static _bindings: Map = new Map(); + + /** + * 添加绑定信息 + */ + public static addBinding(bindInfo: BindInfo): void { + const key = bindInfo.path; + if (!this._bindings.has(key)) { + this._bindings.set(key, []); + } + this._bindings.get(key)!.push(bindInfo); + + // 如果需要立即触发 + if (bindInfo.immediate) { + this.triggerBinding(bindInfo); + } + } + + /** + * 清理目标对象的绑定 + */ + public static cleanup(target: any): void { + this._bindings.forEach((bindings, path) => { + const newBindings = bindings.filter(binding => binding.target !== target); + if (newBindings.length === 0) { + this._bindings.delete(path); + } else { + this._bindings.set(path, newBindings); + } + }); + } + + /** + * 触发路径对应的所有绑定 + */ + public static notifyChange(path: string, value: any, data: any): void { + const bindings = this._bindings.get(path); + if (bindings) { + bindings.forEach(binding => { + try { + if (binding.isMethod) { + binding.callback.call(binding.target, value, data); + } else { + binding.callback.call(binding.target, binding.target[binding.prop], value, data); + } + } catch (error) { + console.error(`绑定回调执行错误: ${path}`, error); + } + }); + } + } +} +``` + +## 路径解析器 + +### 路径提取函数 +```typescript +/** + * 从选择器函数中提取路径字符串 + * 支持编译期类型检查的运行时路径解析 + */ +function extractPathFromSelector(selector: Function): string { + const fnString = selector.toString(); + + // 匹配箭头函数: data => data.property.path + let match = fnString.match(/\w+\s*=>\s*\w+\.(.+)/); + + if (!match) { + // 匹配普通函数: function(data) { return data.property.path; } + match = fnString.match(/return\s+\w+\.(.+);?\s*}/); + } + + if (!match) { + throw new Error('无效的路径选择器函数,请使用 data => data.property.path 格式'); + } + + return match[1].trim(); +} +``` + +## 批量更新优化 + +### BatchUpdater 设计 +```typescript +export class BatchUpdater { + private static _pendingUpdates: Set = new Set(); + private static _updateTimer: number = 0; + + /** + * 批量更新通知 + * @param path 变化路径 + * @param value 新值 + * @param data 数据对象 + */ + public static scheduleUpdate(path: string, value: any, data: any): void { + this._pendingUpdates.add(path); + + if (this._updateTimer === 0) { + this._updateTimer = requestAnimationFrame(() => { + this.flushUpdates(); + }); + } + } + + /** + * 执行批量更新 + */ + private static flushUpdates(): void { + this._pendingUpdates.forEach(path => { + // 获取最新值并触发更新 + const [dataClassName, propertyPath] = path.split(':'); + const dataInstance = DataRegistry.getInstance(dataClassName); + if (dataInstance) { + const value = this.getValueByPath(dataInstance, propertyPath); + BindManager.notifyChange(path, value, dataInstance); + } + }); + + this._pendingUpdates.clear(); + this._updateTimer = 0; + } +} +``` + +## 数据绑定最佳实践 + +### 1. 类型安全 +- 使用泛型约束确保绑定的类型安全 +- 路径选择器函数提供编译期检查 +- 避免字符串路径,优先使用选择器函数 + +### 2. 性能优化 +- 使用批量更新减少频繁触发 +- 及时清理不需要的绑定 +- 避免在绑定回调中进行重度计算 + +### 3. 内存管理 +- 在组件销毁时调用 `data.cleanupBindings()` +- 避免循环引用导致的内存泄漏 +- 使用弱引用管理临时绑定 + +### 4. 调试支持 +- 提供绑定信息的调试接口 +- 记录绑定的创建和销毁日志 +- 支持绑定状态的实时监控 \ No newline at end of file diff --git a/.cursor/rules/decorator-patterns.mdc b/.cursor/rules/decorator-patterns.mdc new file mode 100644 index 0000000..cfe9444 --- /dev/null +++ b/.cursor/rules/decorator-patterns.mdc @@ -0,0 +1,276 @@ +--- +description: "装饰器模式和元数据管理规范" +globs: ["**/*Decorator*.ts", "**/*decorator*.ts"] +alwaysApply: false +type: "design-pattern" +--- + +# 装饰器模式开发规范 + +## 装饰器设计原则 + +### namespace 封装模式 +```typescript +export namespace _uidecorator { + /** 元数据存储键 */ + const UIPropMeta = "__uipropmeta__"; + const UICBMeta = "__uicbmeta__"; + + /** 注册映射 */ + const uiclassMap: Map = new Map(); + + /** 对外接口 */ + export function getWindowMaps(): Map { + return uiclassMap; + } +} +``` + +### 装饰器工厂模式 +```typescript +/** + * 类装饰器工厂 + * @param groupName 窗口组名称 + * @param pkgName fgui包名 + * @param name 窗口名 + * @param bundle 可选bundle名 + */ +export function uiclass(groupName: string, pkgName: string, name: string, bundle?: string): Function { + return function (ctor: any): any { + // 元数据收集 + uiclassMap.set(ctor, { + ctor: ctor, + props: ctor[UIPropMeta] || null, + callbacks: ctor[UICBMeta] || null, + res: { + group: groupName, + pkg: pkgName, + name: name, + bundle: bundle || "" + } + }); + + // 动态注册支持 + _registerFinish && WindowManager.dynamicRegisterWindow(ctor, groupName, pkgName, name, bundle || ""); + return ctor; + }; +} +``` + +## 属性装饰器模式 + +### 属性标记装饰器 +```typescript +/** + * UI属性装饰器 + * @param target 实例成员的类的原型 + * @param name 属性名 + */ +export function uiprop(target: Object, name: string): any { + ObjectHelper.getObjectProp(target.constructor, UIPropMeta)[name] = 1; +} + +/** + * UI控制器装饰器 + */ +export function uicontrol(target: Object, name: string): any { + ObjectHelper.getObjectProp(target.constructor, UIControlMeta)[name] = 1; +} + +/** + * UI动画装饰器 + */ +export function uitransition(target: Object, name: string): any { + ObjectHelper.getObjectProp(target.constructor, UITransitionMeta)[name] = 1; +} +``` + +### 使用示例 +```typescript +@uiclass("popup", "common", "SettingsWindow") +export class SettingsWindow extends Window { + @uiprop + btnClose: GButton; + + @uiprop + list: GList; + + @uicontrol + tabController: GController; + + @uitransition + showAnim: GTransition; +} +``` + +## 方法装饰器模式 + +### 方法绑定装饰器 +```typescript +/** + * 点击事件装饰器 + * @param target 实例成员的类的原型 + * @param name 方法名 + * @param descriptor 属性描述符 + */ +export function uiclick(target: Object, name: string, descriptor: PropertyDescriptor): void { + ObjectHelper.getObjectProp(target.constructor, UICBMeta)[name] = descriptor.value; +} +``` + +### 使用示例 +```typescript +export class SettingsWindow extends Window { + @uiclick + private onBtnCloseClick(): void { + WindowManager.closeWindow("SettingsWindow"); + } + + @uiclick + private onBtnSaveClick(): void { + this.saveSettings(); + } +} +``` + +## 数据绑定装饰器 + +### 强类型绑定装饰器 +```typescript +export namespace data { + const BIND_METADATA_KEY = Symbol('__bind_metadata__'); + + /** + * 属性绑定装饰器 + * @param dataClass 数据类构造函数 + * @param selector 路径选择器函数 + * @param callback 回调函数 + * @param immediate 是否立即触发 + */ + export function bindProp( + dataClass: new () => T, + selector: (data: T) => any, + callback: (item: any, value?: any, data?: T) => void, + immediate: boolean = false + ) { + return function (target: any, prop: string | symbol) { + const path = `${dataClass.name}:${extractPathFromSelector(selector)}`; + + let ctor = target.constructor; + ctor[BIND_METADATA_KEY] = ctor[BIND_METADATA_KEY] || []; + ctor[BIND_METADATA_KEY].push({ + prop, callback, path, immediate, isMethod: false + }); + }; + } + + /** + * 方法绑定装饰器 + */ + export function bindMethod( + dataClass: new () => T, + selector: (data: T) => any, + immediate: boolean = false + ) { + return function (target: any, method: string | symbol, descriptor?: PropertyDescriptor) { + const path = `${dataClass.name}:${extractPathFromSelector(selector)}`; + + let ctor = target.constructor; + ctor[BIND_METADATA_KEY] = ctor[BIND_METADATA_KEY] || []; + ctor[BIND_METADATA_KEY].push({ + prop: method, + callback: descriptor!.value, + path, immediate, + isMethod: true + }); + return descriptor; + }; + } +} +``` + +### 数据绑定使用示例 +```typescript +class GameData extends DataBase { + level: number = 1; + coins: number = 0; +} + +export class GameUI extends Window { + @uiprop + levelLabel: GLabel; + + @uiprop + coinsLabel: GLabel; + + // 绑定属性到UI + @data.bindProp(GameData, data => data.level, function(item, value) { + this.levelLabel.text = `Level: ${value}`; + }) + private _levelBinding: any; + + // 绑定方法到数据变化 + @data.bindMethod(GameData, data => data.coins) + private onCoinsChanged(value: number): void { + this.coinsLabel.text = `Coins: ${value}`; + } +} +``` + +## 条件装饰器 + +### 条件注册装饰器 +```typescript +export namespace _conditionDecorator { + const cdClassMap: Map = new Map(); + + export function getConditionMaps(): Map { + return cdClassMap; + } + + /** + * 条件装饰器 + * @param conditionType 条件类型ID + */ + export function conditionClass(conditionType: number): Function { + return function (ctor: any): void { + cdClassMap.set(conditionType, ctor); + return ctor; + }; + } +} +``` + +### 条件使用示例 +```typescript +@conditionClass(1001) +export class LevelCondition extends ConditionBase { + protected check(): boolean { + return GameData.getInstance().level >= this.targetLevel; + } +} +``` + +## 装饰器最佳实践 + +### 元数据收集模式 +1. 使用 Symbol 作为元数据键避免冲突 +2. 在类原型上存储元数据信息 +3. 提供统一的元数据访问接口 + +### 动态注册支持 +```typescript +let _registerFinish: boolean = false; + +export function setRegisterFinish(): void { + _registerFinish = true; +} + +// 在装饰器中支持动态注册 +_registerFinish && WindowManager.dynamicRegisterWindow(ctor, groupName, pkgName, name, bundle); +``` + +### 类型安全 +- 使用泛型约束确保装饰器类型安全 +- 提供明确的参数类型定义 +- 避免使用 any 类型,优先使用具体类型 \ No newline at end of file diff --git a/.cursor/rules/fairygui.mdc b/.cursor/rules/fairygui.mdc new file mode 100644 index 0000000..e88f323 --- /dev/null +++ b/.cursor/rules/fairygui.mdc @@ -0,0 +1,256 @@ +--- +description: "FairyGUI UI 系统开发规范" +globs: ["src/fgui/**/*.ts", "src/ui/**/*.ts"] +alwaysApply: false +type: "ui-framework" +--- + +# FairyGUI UI 系统开发规范 + +## 窗口基类设计模式 + +### 窗口继承层次 +```typescript +// 基础窗口类 - 提供核心功能 +export abstract class WindowBase extends Component { + // 核心窗口管理逻辑 +} + +// 抽象窗口类 - 定义生命周期 +export abstract class Window extends WindowBase { + protected abstract onInit(): void; + protected onClose(): void { } + protected onShow(userdata?: any): void { } + protected onHide(): void { } + protected onShowFromHide(): void { } + protected onCover(): void { } + protected onRecover(): void { } +} +``` + +### 窗口生命周期管理 +- `onInit()`: 窗口初始化,必须实现 +- `onShow()`: 窗口显示时调用 +- `onHide()`: 窗口隐藏时调用 +- `onClose()`: 窗口关闭时调用 +- `onCover()`: 被其他窗口覆盖时调用 +- `onRecover()`: 从覆盖状态恢复时调用 +- `onShowFromHide()`: 从隐藏状态重新显示时调用 + +## 窗口管理器模式 + +### 静态管理器设计 +```typescript +export class WindowManager { + /** 窗口组映射 */ + private static _groups: Map = new Map(); + /** 所有窗口映射 */ + private static _windows: Map = new Map(); + /** 资源池 */ + private static _resPool: WindowResPool; + + /** + * 异步显示窗口(自动加载资源) + */ + public static showWindow(windowName: string, userdata?: any): Promise { + return new Promise((resolve, reject) => { + this._resPool.loadWindowRes(windowName, { + complete: () => { + this.showWindowIm(windowName, userdata); + resolve(); + }, + fail: (pkgs: string[]) => reject(pkgs) + }); + }); + } + + /** + * 立即显示窗口(资源已加载) + */ + public static showWindowIm(windowName: string, userdata?: any): void { + const info = this._resPool.get(windowName); + const windowGroup = this.getWindowGroup(info.group); + this._resPool.addResRef(windowName); + windowGroup.showWindow(info, userdata); + } +} +``` + +### 窗口组管理 +- 使用 `WindowGroup` 管理同类型窗口 +- 支持窗口层级和遮挡关系 +- 自动处理窗口恢复和覆盖 + +## 装饰器系统 + +### UI 装饰器使用规范 +```typescript +// 窗口类装饰器 +@uiclass("popup", "common", "SettingsWindow", "ui") +export class SettingsWindow extends Window { + + // UI 属性装饰器 + @uiprop + btnClose: GButton; + + @uiprop + list: GList; + + // UI 控制器装饰器 + @uicontrol + controller: GController; + + // UI 动画装饰器 + @uitransition + showTransition: GTransition; + + // 点击事件装饰器 + @uiclick + private onBtnCloseClick(): void { + WindowManager.closeWindow("SettingsWindow"); + } +} + +// 组件装饰器 +@uicom("common", "CustomButton") +export class CustomButton extends GButton { + // 自定义组件逻辑 +} + +// Header 装饰器 +@uiheader("common", "CommonHeader", "ui") +export class CommonHeader extends WindowHeader { + // 通用头部逻辑 +} +``` + +### 装饰器参数规范 +- `@uiclass(group, pkg, name, bundle?)`: 窗口类注册 + - `group`: 窗口组名 + - `pkg`: FairyGUI 包名 + - `name`: 组件名(与 FairyGUI 中一致) + - `bundle`: 可选的 bundle 名称 +- `@uicom(pkg, name)`: 自定义组件注册 +- `@uiheader(pkg, name, bundle?)`: 窗口头部注册 + +## 资源管理模式 + +### 资源池设计 +```typescript +export class WindowResPool { + private _windowInfos: Map = new Map(); + private _headerInfos: Map = new Map(); + + /** + * 加载窗口资源 + */ + loadWindowRes(windowName: string, callbacks: { + complete: () => void; + fail: (pkgs: string[]) => void; + }): void { + // 检查包资源是否已加载 + // 自动加载依赖的UI包 + // 调用相应的回调 + } + + /** + * 释放窗口资源 + */ + releaseWindowRes(windowName: string): void { + // 减少引用计数 + // 必要时卸载资源 + } +} +``` + +### 包配置管理 +```typescript +interface IPackageConfigRes { + [windowName: string]: { + group: string; + pkg: string; + bundle?: string; + }; +} + +// 初始化包配置 +WindowManager.initPackageConfig(packageConfig); +``` + +## UI 组件扩展 + +### 组件扩展模式 +```typescript +export class ComponentExtendHelper { + private static _componentMaps: Map = new Map(); + + /** + * 注册自定义组件 + */ + public static register(): void { + for (const { ctor, res } of _uidecorator.getComponentMaps().values()) { + UIObjectFactory.setPackageItemExtension( + `ui://${res.pkg}/${res.name}`, + ctor + ); + } + } + + /** + * 动态注册组件 + */ + public static dynamicRegister(ctor: any, pkg: string, name: string): void { + UIObjectFactory.setPackageItemExtension( + `ui://${pkg}/${name}`, + ctor + ); + } +} +``` + +## 窗口头部系统 + +### WindowHeader 模式 +```typescript +export class WindowHeader { + /** 头部组件实例 */ + protected _header: GComponent; + + /** + * 创建头部 + */ + public createHeader(pkg: string, name: string): GComponent { + this._header = UIPackage.createObject(pkg, name).asCom; + return this._header; + } + + /** + * 头部适配 + */ + public adapter(window: GComponent): void { + // 头部适配逻辑 + // 处理安全区域 + // 设置头部位置和尺寸 + } +} +``` + +## 屏幕适配 + +### 屏幕尺寸变化处理 +```typescript +// 在 WindowManager 中处理屏幕变化 +public static _screenResize(): void { + this._windows.forEach((window: IWindow) => { + window.screenResize(); + }); + this._groups.forEach((group: WindowGroup) => { + group._screenResize(); + }); +} + +// 在窗口中实现屏幕适配 +protected screenResize(): void { + // 处理窗口在屏幕尺寸变化时的适配逻辑 +} +``` \ No newline at end of file diff --git a/.cursor/rules/hot-update.mdc b/.cursor/rules/hot-update.mdc new file mode 100644 index 0000000..dacdc96 --- /dev/null +++ b/.cursor/rules/hot-update.mdc @@ -0,0 +1,413 @@ +--- +description: "热更新系统开发规范" +globs: ["src/hotupdate/**/*.ts"] +alwaysApply: false +type: "system-module" +--- + +# 热更新系统开发规范 + +## 热更新架构设计 + +### 管理器单例模式 +```typescript +export class HotUpdateManager { + private static _instance: HotUpdateManager; + + /** 获取单例实例 */ + public static getInstance(): HotUpdateManager { + if (!this._instance) { + this._instance = new HotUpdateManager(); + } + return this._instance; + } + + /** 禁用直接构造 */ + private constructor() {} + + /** 配置属性 */ + public manifestUrl: string = ""; + public versionUrl: string = ""; + + /** 初始化热更新 */ + public init(manifestUrl: string, versionUrl: string): void { + this.manifestUrl = manifestUrl; + this.versionUrl = versionUrl; + } +} +``` + +### 热更新状态码定义 +```typescript +export enum HotUpdateCode { + /** 成功 */ + Succeed = 0, + /** 已是最新版本 */ + LatestVersion = 1, + /** 检查更新失败 */ + CheckFailed = 2, + /** 下载失败 */ + DownloadFailed = 3, + /** 解压失败 */ + UnzipFailed = 4, + /** 网络错误 */ + NetworkError = 5, + /** 空间不足 */ + NoSpace = 6, + /** 未知错误 */ + UnknownError = 7 +} +``` + +## Promise 结果模式 + +### 统一结果接口 +```typescript +export interface IPromiseResult { + /** 状态码 */ + code: HotUpdateCode; + + /** 消息描述 */ + message: string; + + /** 扩展数据 */ + data?: any; +} +``` + +### 热更新配置接口 +```typescript +export interface IHotUpdateConfig { + /** 版本号 */ + version: string; + + /** 远程manifest文件URL */ + remoteManifestUrl: string; + + /** 远程version文件URL */ + remoteVersionUrl: string; + + /** 资源包URL */ + packageUrl: string; + + /** 资源文件列表 */ + assets?: { [key: string]: any }; + + /** 搜索路径 */ + searchPaths?: string[]; +} +``` + +## 热更新核心实现 + +### HotUpdate 核心类 +```typescript +export class HotUpdate { + private _am: jsb.AssetsManager; + private _updating: boolean = false; + + constructor(manifestUrl: string) { + // 初始化 AssetsManager + this._am = new jsb.AssetsManager(manifestUrl, jsb.fileUtils.getWritablePath() + 'remote-assets'); + this._am.setEventCallback(this.onUpdateEvent.bind(this)); + this._am.setVerifyCallback(this.onVerifyCallback.bind(this)); + } + + /** + * 检查更新 + * @returns Promise + */ + public checkUpdate(): Promise { + return new Promise((resolve) => { + if (this._updating) { + resolve({ code: HotUpdateCode.UnknownError, message: "正在更新中" }); + return; + } + + if (!this._am.getLocalManifest() || !this._am.getLocalManifest().isLoaded()) { + resolve({ code: HotUpdateCode.CheckFailed, message: "本地manifest加载失败" }); + return; + } + + this._am.setEventCallback((event) => { + switch (event.getEventCode()) { + case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST: + resolve({ code: HotUpdateCode.CheckFailed, message: "本地manifest不存在" }); + break; + case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST: + resolve({ code: HotUpdateCode.NetworkError, message: "下载manifest失败" }); + break; + case jsb.EventAssetsManager.ALREADY_UP_TO_DATE: + resolve({ code: HotUpdateCode.LatestVersion, message: "已是最新版本" }); + break; + case jsb.EventAssetsManager.NEW_VERSION_FOUND: + resolve({ code: HotUpdateCode.Succeed, message: "发现新版本" }); + break; + } + }); + + this._am.checkUpdate(); + }); + } + + /** + * 执行热更新 + * @returns Promise + */ + public hotUpdate(): Promise { + return new Promise((resolve) => { + if (this._updating) { + resolve({ code: HotUpdateCode.UnknownError, message: "正在更新中" }); + return; + } + + this._updating = true; + + this._am.setEventCallback((event) => { + switch (event.getEventCode()) { + case jsb.EventAssetsManager.UPDATE_FINISHED: + this._updating = false; + resolve({ code: HotUpdateCode.Succeed, message: "更新完成" }); + break; + case jsb.EventAssetsManager.UPDATE_FAILED: + this._updating = false; + resolve({ code: HotUpdateCode.DownloadFailed, message: "更新失败" }); + break; + case jsb.EventAssetsManager.ERROR_DECOMPRESS: + this._updating = false; + resolve({ code: HotUpdateCode.UnzipFailed, message: "解压失败" }); + break; + } + }); + + this._am.update(); + }); + } + + /** + * 事件回调处理 + */ + private onUpdateEvent(event: jsb.EventAssetsManager): void { + const code = event.getEventCode(); + debug(`热更新事件: ${code}`); + + switch (code) { + case jsb.EventAssetsManager.UPDATE_PROGRESSION: + const progress = event.getPercent(); + debug(`更新进度: ${progress}%`); + break; + case jsb.EventAssetsManager.ASSET_UPDATED: + const assetId = event.getAssetId(); + debug(`资源更新: ${assetId}`); + break; + } + } + + /** + * 验证回调 + */ + private onVerifyCallback(path: string, asset: any): boolean { + // 资源验证逻辑 + return true; + } +} +``` + +## manifest 管理 + +### 本地 manifest 刷新 +```typescript +/** + * 替换 project.manifest 中的内容并刷新本地manifest + */ +private refreshLocalManifest(manifest: IHotUpdateConfig, versionManifest: IHotUpdateConfig): Promise { + return new Promise((resolve) => { + // 版本比较 + if (Utils.compareVersion(manifest.version, versionManifest.version) >= 0) { + resolve({ code: HotUpdateCode.LatestVersion, message: "已是最新版本" }); + return; + } + + // 更新 manifest 配置 + manifest.remoteManifestUrl = Utils.addUrlParam(versionManifest.remoteManifestUrl, "timeStamp", `${Time.now()}`); + manifest.remoteVersionUrl = Utils.addUrlParam(versionManifest.remoteVersionUrl, "timeStamp", `${Time.now()}`); + manifest.packageUrl = versionManifest.packageUrl; + + // 计算 manifest 根目录 + let manifestRoot = ""; + let manifestUrl = HotUpdateManager.getInstance().manifestUrl; + let found = manifestUrl.lastIndexOf("/"); + if (found === -1) { + found = manifestUrl.lastIndexOf("\\"); + } + if (found !== -1) { + manifestRoot = manifestUrl.substring(0, found + 1); + } + + // 解析并设置本地 manifest + this._am.getLocalManifest().parseJSONString(JSON.stringify(manifest), manifestRoot); + + resolve({ code: HotUpdateCode.Succeed, message: "更新热更新配置成功" }); + }); +} +``` + +## 版本比较工具 + +### 版本号比较函数 +```typescript +export class Utils { + /** + * 版本号比较 + * @param versionA 版本A + * @param versionB 版本B + * @returns 0: 相等, 1: A > B, -1: A < B + */ + public static compareVersion(versionA: string, versionB: string): number { + const a = versionA.split('.'); + const b = versionB.split('.'); + + for (let i = 0; i < Math.max(a.length, b.length); i++) { + const numA = parseInt(a[i] || '0', 10); + const numB = parseInt(b[i] || '0', 10); + + if (numA > numB) return 1; + if (numA < numB) return -1; + } + + return 0; + } + + /** + * 给URL添加参数 + */ + public static addUrlParam(url: string, key: string, value: string): string { + const separator = url.indexOf('?') !== -1 ? '&' : '?'; + return `${url}${separator}${key}=${encodeURIComponent(value)}`; + } +} +``` + +## 进度监控 + +### 更新进度回调 +```typescript +export interface HotUpdateProgress { + /** 当前进度百分比 (0-100) */ + percent: number; + + /** 已下载字节数 */ + downloadedBytes: number; + + /** 总字节数 */ + totalBytes: number; + + /** 当前下载的文件 */ + currentFile?: string; +} + +export interface HotUpdateCallbacks { + /** 进度回调 */ + onProgress?: (progress: HotUpdateProgress) => void; + + /** 完成回调 */ + onComplete?: (result: IPromiseResult) => void; + + /** 错误回调 */ + onError?: (error: IPromiseResult) => void; +} + +export class HotUpdate { + public updateWithCallbacks(callbacks: HotUpdateCallbacks): void { + this._am.setEventCallback((event) => { + switch (event.getEventCode()) { + case jsb.EventAssetsManager.UPDATE_PROGRESSION: + callbacks.onProgress?.({ + percent: event.getPercent(), + downloadedBytes: event.getDownloadedBytes(), + totalBytes: event.getTotalBytes() + }); + break; + case jsb.EventAssetsManager.UPDATE_FINISHED: + callbacks.onComplete?.({ code: HotUpdateCode.Succeed, message: "更新完成" }); + break; + case jsb.EventAssetsManager.UPDATE_FAILED: + callbacks.onError?.({ code: HotUpdateCode.DownloadFailed, message: "更新失败" }); + break; + } + }); + + this._am.update(); + } +} +``` + +## 错误处理和重试 + +### 重试机制 +```typescript +export class HotUpdate { + private _retryCount: number = 0; + private readonly _maxRetryCount: number = 3; + + /** + * 带重试的热更新 + */ + public async hotUpdateWithRetry(): Promise { + for (let i = 0; i < this._maxRetryCount; i++) { + try { + const result = await this.hotUpdate(); + + if (result.code === HotUpdateCode.Succeed) { + return result; + } + + // 网络错误可以重试 + if (result.code === HotUpdateCode.NetworkError && i < this._maxRetryCount - 1) { + warn(`热更新失败,准备重试 (${i + 1}/${this._maxRetryCount})`); + await this.delay(1000 * (i + 1)); // 递增延迟 + continue; + } + + return result; + } catch (error) { + error('热更新异常', error); + if (i === this._maxRetryCount - 1) { + return { code: HotUpdateCode.UnknownError, message: `重试${this._maxRetryCount}次后仍然失败` }; + } + } + } + } + + private delay(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); + } +} +``` + +## 热更新最佳实践 + +### 1. 版本管理 +- 使用语义化版本号 (major.minor.patch) +- 提供版本比较和检查功能 +- 记录版本更新历史 + +### 2. 网络处理 +- 实现重试机制处理网络不稳定 +- 添加超时控制 +- 支持断点续传 + +### 3. 用户体验 +- 提供详细的进度反馈 +- 支持后台下载 +- 提供更新取消选项 + +### 4. 错误恢复 +- 验证下载文件完整性 +- 支持回滚到上一版本 +- 提供修复工具清理损坏文件 + +### 5. 安全性 +- 验证manifest签名 +- 检查文件哈希值 +- 使用HTTPS传输 \ No newline at end of file diff --git a/.cursor/rules/logging-debugging.mdc b/.cursor/rules/logging-debugging.mdc new file mode 100644 index 0000000..ff4aff5 --- /dev/null +++ b/.cursor/rules/logging-debugging.mdc @@ -0,0 +1,258 @@ +--- +description: "日志系统和调试规范" +globs: ["src/tool/log.ts", "**/*.ts"] +alwaysApply: true +type: "logging" +--- + +# 日志系统和调试规范 + +## 统一日志接口 + +### 日志级别定义 +```typescript +import { debug, info, log, warn, error } from "../tool/log"; + +/** + * 日志级别使用指南: + * - debug: 调试信息,仅在开发模式显示 + * - log: 一般信息输出 + * - info: 信息性消息,带有特殊图标 + * - warn: 警告信息,黄色背景 + * - error: 错误消息,红色背景 + */ +``` + +### 日志使用规范 +```typescript +// ✅ 正确的日志使用 +export class WindowManager { + public static showWindow(windowName: string): void { + debug(`显示窗口: ${windowName}`); // 调试信息 + + if (!this._windows.has(windowName)) { + warn(`窗口不存在 ${windowName}`); // 警告 + return; + } + + try { + this.doShowWindow(windowName); + log(`窗口 ${windowName} 显示成功`); // 普通信息 + } catch (e) { + error(`显示窗口失败: ${windowName}`, e); // 错误信息 + } + } +} +``` + +## 调试模式管理 + +### 调试开关设计 +```typescript +// header.ts - 调试配置 +export let KUNPO_DEBUG: boolean = false; + +/** + * 启用或禁用调试模式 + * @param enable 是否启用调试模式 + */ +export function enableDebugMode(enable: boolean): void { + if (enable === true) { + KUNPO_DEBUG = true; + warn("调试模式已开启"); + } else { + KUNPO_DEBUG = false; + } +} + +// log.ts - 条件日志输出 +function debug(...args: any[]): void { + KUNPO_DEBUG && console.log("kunpo:", ...args); +} +``` + +### 框架配置集成 +```typescript +interface FrameConfig { + /** 开启debug 默认: false */ + debug?: boolean; +} + +export class CocosEntry extends Component { + public getConfig(): FrameConfig { + return { + debug: true // 在开发环境设置为 true + }; + } + + protected start(): void { + const config = this.getConfig(); + enableDebugMode(config.debug); // 根据配置启用调试 + } +} +``` + +## 日志格式规范 + +### 统一前缀格式 +```typescript +// 所有日志都带有 "kunpo:" 前缀 +function log(...args: any[]) { + console.log("kunpo:", ...args); +} + +function debug(...args: any[]): void { + KUNPO_DEBUG && console.log("kunpo:", ...args); +} +``` + +### 结构化日志信息 +```typescript +// ✅ 推荐的日志格式 +debug(`窗口注册 - 窗口名:${name} 包名:${pkg} 组名:${group}`); +log(`模块初始化完成: ${moduleName}`); +warn(`资源加载失败: ${resourcePath}`); +error(`网络请求异常: ${url}`, errorDetails); + +// ❌ 避免的日志格式 +debug("window registered"); // 信息不够具体 +log(window); // 直接输出对象 +``` + +## 开发调试工具 + +### 全局调试接口 +```typescript +// 暴露调试接口到全局对象 +let _global = globalThis || window || global; + +(_global as any)["getKunpoRegisterWindowMaps"] = function () { + return _uidecorator.getWindowMaps() as any; +}; + +(_global as any)["getKunpoRegisterComponentMaps"] = function () { + return _uidecorator.getComponentMaps() as any; +}; + +(_global as any)["getKunpoRegisterHeaderMaps"] = function () { + return _uidecorator.getHeaderMaps() as any; +}; +``` + +### 运行时信息输出 +```typescript +// 系统信息调试 +private initPlatform(): void { + debug(`系统类型: ${sys.os}`); + debug(`平台类型: ${PlatformType[Platform.platform]}`); + debug(`是否原生: ${Platform.isNative}`); + debug(`是否移动端: ${Platform.isMobile}`); +} + +// 模块注册调试 +public static registerUI(): void { + for (const { ctor, res } of _uidecorator.getWindowMaps().values()) { + debug(`窗口注册 - 窗口名:${res.name} 包名:${res.pkg} 组名:${res.group}`); + } +} +``` + +## 错误处理和日志 + +### Promise 错误处理 +```typescript +public static showWindow(windowName: string, userdata?: any): Promise { + return new Promise((resolve, reject) => { + debug(`准备显示窗口: ${windowName}`); + + this._resPool.loadWindowRes(windowName, { + complete: () => { + debug(`窗口资源加载完成: ${windowName}`); + this.showWindowIm(windowName, userdata); + resolve(); + }, + fail: (pkgs: string[]) => { + error(`窗口资源加载失败: ${windowName}`, pkgs); + reject(pkgs); + } + }); + }); +} +``` + +### 异常捕获和记录 +```typescript +try { + this.doSomethingRisky(); +} catch (error) { + // 记录详细的错误信息 + error(`操作执行失败: ${operation}`, { + error: error.message, + stack: error.stack, + context: this.getContext() + }); + + // 根据错误类型决定是否继续执行 + if (error instanceof CriticalError) { + throw error; // 关键错误需要向上抛出 + } +} +``` + +## 性能监控日志 + +### 时间统计 +```typescript +export class WindowManager { + public static showWindow(windowName: string): Promise { + const startTime = performance.now(); + debug(`开始显示窗口: ${windowName}`); + + return new Promise((resolve) => { + this._resPool.loadWindowRes(windowName, { + complete: () => { + const loadTime = performance.now() - startTime; + debug(`窗口显示完成: ${windowName}, 耗时: ${loadTime.toFixed(2)}ms`); + resolve(); + } + }); + }); + } +} +``` + +### 资源使用监控 +```typescript +export class WindowResPool { + private logResourceUsage(): void { + if (KUNPO_DEBUG) { + const windowCount = this._windowInfos.size; + const headerCount = this._headerInfos.size; + debug(`资源池状态 - 窗口: ${windowCount}, 头部: ${headerCount}`); + } + } +} +``` + +## 调试最佳实践 + +### 1. 日志分级使用 +- `debug`: 仅开发时需要的详细信息 +- `log`: 系统运行的关键节点信息 +- `warn`: 可能的问题但不影响运行 +- `error`: 必须处理的错误情况 + +### 2. 信息完整性 +- 包含足够的上下文信息 +- 记录输入参数和状态 +- 包含时间戳和调用路径 + +### 3. 性能考虑 +- 避免在日志中进行重度计算 +- 使用条件编译减少生产环境开销 +- 延迟计算日志参数 + +### 4. 安全性 +- 避免输出敏感信息到日志 +- 在生产环境关闭详细日志 +- 注意日志文件的权限设置 \ No newline at end of file diff --git a/.cursor/rules/minigame-platform.mdc b/.cursor/rules/minigame-platform.mdc new file mode 100644 index 0000000..cec7d29 --- /dev/null +++ b/.cursor/rules/minigame-platform.mdc @@ -0,0 +1,449 @@ +--- +description: "小游戏平台开发规范" +globs: ["src/minigame/**/*.ts", "src/global/Platform.ts"] +alwaysApply: false +type: "platform-specific" +--- + +# 小游戏平台开发规范 + +## 平台检测和分类 + +### 平台类型定义 +```typescript +export enum PlatformType { + Unknown = 0, + Browser = 1, + WX = 2, // 微信小游戏 + Alipay = 3, // 支付宝小游戏 + Bytedance = 4, // 字节跳动小游戏 + HuaweiQuick = 5 // 华为快游戏 +} + +export class Platform { + /** 平台类型 */ + public static platform: PlatformType = PlatformType.Unknown; + + /** 平台标识 */ + public static isWX: boolean = false; + public static isAlipay: boolean = false; + public static isBytedance: boolean = false; + public static isHuaweiQuick: boolean = false; + public static isBrowser: boolean = false; + + /** 设备类型 */ + public static isNative: boolean = false; + public static isMobile: boolean = false; + public static isNativeMobile: boolean = false; + + /** 系统类型 */ + public static isAndroid: boolean = false; + public static isIOS: boolean = false; + public static isHarmonyOS: boolean = false; +} +``` + +### 平台初始化模式 +```typescript +export class CocosEntry extends Component { + private initPlatform(): void { + // 设备类型检测 + Platform.isNative = sys.isNative; + Platform.isMobile = sys.isMobile; + Platform.isNativeMobile = sys.isNative && sys.isMobile; + + // 系统类型检测 + switch (sys.os) { + case sys.OS.ANDROID: + Platform.isAndroid = true; + debug("系统类型 Android"); + break; + case sys.OS.IOS: + Platform.isIOS = true; + debug("系统类型 IOS"); + break; + case sys.OS.OPENHARMONY: + Platform.isHarmonyOS = true; + debug("系统类型 HarmonyOS"); + break; + } + + // 平台类型检测 + switch (sys.platform) { + case sys.Platform.WECHAT_GAME: + Platform.isWX = true; + Platform.platform = PlatformType.WX; + break; + case sys.Platform.ALIPAY_MINI_GAME: + Platform.isAlipay = true; + Platform.platform = PlatformType.Alipay; + break; + case sys.Platform.BYTEDANCE_MINI_GAME: + Platform.isBytedance = true; + Platform.platform = PlatformType.Bytedance; + break; + case sys.Platform.HUAWEI_QUICK_GAME: + Platform.isHuaweiQuick = true; + Platform.platform = PlatformType.HuaweiQuick; + break; + default: + Platform.isBrowser = true; + Platform.platform = PlatformType.Browser; + break; + } + + debug(`platform: ${PlatformType[Platform.platform]}`); + } +} +``` + +## 平台适配器设计 + +### 通用适配器接口 +```typescript +export interface IMiniGameAdapter { + /** 显示分享菜单 */ + showShareMenu(): void; + + /** 分享应用 */ + shareAppMessage(options: ShareOptions): void; + + /** 显示 loading */ + showLoading(options: LoadingOptions): void; + + /** 隐藏 loading */ + hideLoading(): void; + + /** 显示 toast */ + showToast(options: ToastOptions): void; + + /** 获取系统信息 */ + getSystemInfo(): Promise; + + /** 震动反馈 */ + vibrateShort(): void; + vibrateLong(): void; +} +``` + +### 微信小游戏适配 +```typescript +export class WechatCommon implements IMiniGameAdapter { + public showShareMenu(): void { + if (Platform.isWX && wx.showShareMenu) { + wx.showShareMenu({ + withShareTicket: true, + menus: ['shareAppMessage', 'shareTimeline'] + }); + } + } + + public shareAppMessage(options: ShareOptions): void { + if (Platform.isWX && wx.shareAppMessage) { + wx.shareAppMessage({ + title: options.title, + imageUrl: options.imageUrl, + query: options.query, + success: options.success, + fail: options.fail + }); + } + } + + public showLoading(options: LoadingOptions): void { + if (Platform.isWX && wx.showLoading) { + wx.showLoading({ + title: options.title || '加载中...', + mask: options.mask !== false + }); + } + } + + public getSystemInfo(): Promise { + return new Promise((resolve, reject) => { + if (Platform.isWX && wx.getSystemInfo) { + wx.getSystemInfo({ + success: resolve, + fail: reject + }); + } else { + reject(new Error('不支持的平台')); + } + }); + } +} +``` + +### 支付宝小游戏适配 +```typescript +export class AlipayCommon implements IMiniGameAdapter { + public showShareMenu(): void { + // 支付宝特定实现 + } + + public shareAppMessage(options: ShareOptions): void { + if (Platform.isAlipay && my.shareAppMessage) { + my.shareAppMessage({ + title: options.title, + desc: options.desc, + path: options.path, + success: options.success, + fail: options.fail + }); + } + } + + public showLoading(options: LoadingOptions): void { + if (Platform.isAlipay && my.showLoading) { + my.showLoading({ + content: options.title || '加载中...' + }); + } + } +} +``` + +### 字节跳动小游戏适配 +```typescript +export class BytedanceCommon implements IMiniGameAdapter { + public shareAppMessage(options: ShareOptions): void { + if (Platform.isBytedance && tt.shareAppMessage) { + tt.shareAppMessage({ + title: options.title, + imageUrl: options.imageUrl, + query: options.query, + success: options.success, + fail: options.fail + }); + } + } + + public vibrateShort(): void { + if (Platform.isBytedance && tt.vibrateShort) { + tt.vibrateShort({ + success: () => debug('震动成功'), + fail: (err) => warn('震动失败', err) + }); + } + } +} +``` + +## 统一接口封装 + +### MiniHelper 统一接口 +```typescript +export class MiniHelper { + private static _adapter: IMiniGameAdapter | null = null; + + /** 初始化适配器 */ + public static init(): void { + switch (Platform.platform) { + case PlatformType.WX: + this._adapter = new WechatCommon(); + break; + case PlatformType.Alipay: + this._adapter = new AlipayCommon(); + break; + case PlatformType.Bytedance: + this._adapter = new BytedanceCommon(); + break; + default: + warn('当前平台不支持小游戏功能'); + break; + } + } + + /** 统一的分享接口 */ + public static share(options: ShareOptions): void { + if (this._adapter) { + this._adapter.shareAppMessage(options); + } else { + warn('未初始化小游戏适配器'); + } + } + + /** 统一的震动接口 */ + public static vibrate(type: 'short' | 'long' = 'short'): void { + if (this._adapter) { + if (type === 'short') { + this._adapter.vibrateShort(); + } else { + this._adapter.vibrateLong(); + } + } + } + + /** 统一的系统信息获取 */ + public static async getSystemInfo(): Promise { + if (this._adapter) { + try { + return await this._adapter.getSystemInfo(); + } catch (error) { + error('获取系统信息失败', error); + return null; + } + } + return null; + } +} +``` + +## 平台特定功能 + +### 广告系统封装 +```typescript +interface AdOptions { + adUnitId: string; + success?: () => void; + fail?: (error: any) => void; +} + +export class AdManager { + /** 显示激励视频广告 */ + public static showRewardedVideoAd(options: AdOptions): void { + switch (Platform.platform) { + case PlatformType.WX: + this.showWXRewardedAd(options); + break; + case PlatformType.Alipay: + this.showAlipayRewardedAd(options); + break; + default: + options.fail?.('当前平台不支持广告'); + break; + } + } + + private static showWXRewardedAd(options: AdOptions): void { + if (wx.createRewardedVideoAd) { + const rewardedVideoAd = wx.createRewardedVideoAd({ + adUnitId: options.adUnitId + }); + + rewardedVideoAd.onLoad(() => { + rewardedVideoAd.show(); + }); + + rewardedVideoAd.onClose((res) => { + if (res && res.isEnded) { + options.success?.(); + } else { + options.fail?.('用户取消观看'); + } + }); + } + } +} +``` + +### 支付系统封装 +```typescript +interface PaymentOptions { + amount: number; + orderInfo: string; + success?: (result: any) => void; + fail?: (error: any) => void; +} + +export class PaymentManager { + public static pay(options: PaymentOptions): void { + switch (Platform.platform) { + case PlatformType.WX: + this.wxPay(options); + break; + case PlatformType.Alipay: + this.alipayPay(options); + break; + default: + options.fail?.('当前平台不支持支付'); + break; + } + } + + private static wxPay(options: PaymentOptions): void { + // 微信支付实现 + } + + private static alipayPay(options: PaymentOptions): void { + // 支付宝支付实现 + } +} +``` + +## 数据存储适配 + +### 本地存储封装 +```typescript +export class StorageManager { + /** 设置数据 */ + public static setItem(key: string, value: any): void { + try { + const jsonValue = JSON.stringify(value); + + switch (Platform.platform) { + case PlatformType.WX: + wx.setStorageSync(key, jsonValue); + break; + case PlatformType.Alipay: + my.setStorageSync({ key, data: jsonValue }); + break; + default: + localStorage.setItem(key, jsonValue); + break; + } + } catch (error) { + error('存储数据失败', key, error); + } + } + + /** 获取数据 */ + public static getItem(key: string, defaultValue?: T): T | null { + try { + let jsonValue: string | null = null; + + switch (Platform.platform) { + case PlatformType.WX: + jsonValue = wx.getStorageSync(key); + break; + case PlatformType.Alipay: + jsonValue = my.getStorageSync({ key }).data; + break; + default: + jsonValue = localStorage.getItem(key); + break; + } + + if (jsonValue) { + return JSON.parse(jsonValue) as T; + } + return defaultValue || null; + } catch (error) { + error('读取数据失败', key, error); + return defaultValue || null; + } + } +} +``` + +## 平台开发最佳实践 + +### 1. 统一接口设计 +- 为所有平台提供一致的API接口 +- 使用适配器模式处理平台差异 +- 提供降级方案处理不支持的功能 + +### 2. 平台检测 +- 在应用启动时进行平台检测 +- 使用枚举定义平台类型 +- 提供便捷的平台判断属性 + +### 3. 错误处理 +- 对不支持的平台提供友好的错误信息 +- 使用 try-catch 处理平台API调用 +- 提供回调函数处理异步操作结果 + +### 4. 性能优化 +- 延迟加载平台特定功能 +- 避免在不支持的平台上创建无用对象 +- 使用条件编译减少包体积 \ No newline at end of file diff --git a/.cursor/rules/project-overview.mdc b/.cursor/rules/project-overview.mdc new file mode 100644 index 0000000..0bca9ad --- /dev/null +++ b/.cursor/rules/project-overview.mdc @@ -0,0 +1,260 @@ +--- +description: "KunpoCC 项目总体开发规范" +globs: ["**/*.ts", "**/*.md", "**/*.json"] +alwaysApply: true +type: "project-guidelines" +--- + +# KunpoCC 项目总体开发规范 + +## 项目介绍 + +KunpoCC 是一个基于 Cocos Creator 3.x 的游戏开发框架库,提供了一套完整的游戏开发工具和模块: + +- **UI 管理系统**: 基于 FairyGUI 的窗口管理和组件系统 +- **数据绑定系统**: 强类型的响应式数据绑定框架 +- **平台适配**: 微信、支付宝、字节跳动等小游戏平台支持 +- **热更新系统**: 完整的资源热更新解决方案 +- **工具模块**: 时间管理、日志系统、数学工具等实用工具 + +## 项目结构规范 + +### 目录组织 +``` +src/ +├── cocos/ # Cocos Creator 适配层 +├── condition/ # 条件管理系统 (红点系统) +├── data/ # 数据绑定系统 +├── fgui/ # FairyGUI 窗口基类 +├── global/ # 全局工具和配置 +├── hotupdate/ # 热更新系统 +├── interface/ # 通用接口定义 +├── minigame/ # 小游戏平台适配 +├── module/ # 模块基类 +├── tool/ # 工具函数集合 +└── ui/ # UI 管理系统 +``` + +### 文件命名规范 +- **类文件**: PascalCase (如 `WindowManager.ts`) +- **接口文件**: 以 `I` 开头 (如 `IWindow.ts`) +- **工具文件**: 功能描述命名 (如 `log.ts`, `Math.ts`) +- **装饰器文件**: 以 `Decorator` 结尾 (如 `UIDecorator.ts`) + +## 编码规范总览 + +### 类型系统 +```typescript +// 严格的 TypeScript 配置 +{ + "strict": true, + "strictNullChecks": false, // 项目特殊需求 + "experimentalDecorators": true +} + +// 优先使用接口和泛型 +interface IWindow { + _show(userdata?: any): void; + _close(): void; +} + +// 使用泛型约束确保类型安全 +public static getWindow(name: string): T | null { + return this._windows.get(name) as T; +} +``` + +### 装饰器系统 +```typescript +// UI 装饰器 +@uiclass("popup", "common", "SettingsWindow") +export class SettingsWindow extends Window { + @uiprop btnClose: GButton; + @uiclick private onBtnCloseClick(): void { } +} + +// 数据绑定装饰器 +@data.bindProp(GameData, data => data.level, function(item, value) { + this.levelLabel.text = `Level: ${value}`; +}) +private _levelBinding: any; + +// 条件装饰器 +@conditionClass(1001) +export class LevelCondition extends ConditionBase { } +``` + +### 架构模式 +```typescript +// 单例管理器模式 +export class WindowManager { + private static _instance: WindowManager; + public static getInstance(): WindowManager { } +} + +// 抽象基类模式 +export abstract class Window extends WindowBase { + protected abstract onInit(): void; + protected onShow(userdata?: any): void { } +} + +// 适配器模式 +export class CocosAdapter implements IAdapter { + public init(): void { } +} +``` + +## 开发流程 + +### 1. 新增功能模块 +1. 在相应目录创建模块文件 +2. 实现必要的接口和基类 +3. 添加相应的装饰器支持 +4. 编写单元测试 +5. 更新文档和示例 + +### 2. 窗口开发 +```typescript +@uiclass("main", "game", "GameWindow") +export class GameWindow extends Window { + @uiprop playerPanel: GComponent; + @uiprop settingsBtn: GButton; + + @uiclick + private onSettingsBtnClick(): void { + WindowManager.showWindow("SettingsWindow"); + } + + protected onInit(): void { + // 窗口初始化逻辑 + } +} +``` + +### 3. 数据系统集成 +```typescript +class GameData extends DataBase { + private _score: number = 0; + + get score(): number { return this._score; } + set score(value: number) { + if (this._score !== value) { + this._score = value; + this.notify('score', value); + } + } +} +``` + +## 质量保证 + +### 日志和调试 +```typescript +import { debug, warn, error, log } from "../tool/log"; + +// 统一的日志格式 +debug(`窗口注册 - 窗口名:${name} 包名:${pkg}`); +warn(`资源加载失败: ${resourcePath}`); +error(`网络请求异常: ${url}`, errorDetails); +``` + +### 错误处理 +```typescript +// Promise 错误处理 +public static showWindow(name: string): Promise { + return new Promise((resolve, reject) => { + try { + // 业务逻辑 + resolve(); + } catch (error) { + error('窗口显示失败', error); + reject(error); + } + }); +} +``` + +### 性能优化 +- 使用资源池管理UI资源生命周期 +- 实现批量更新减少频繁触发 +- 及时清理事件监听和数据绑定 +- 使用对象池复用临时对象 + +## 文档和注释 + +### JSDoc 规范 +```typescript +/** + * @Author: Gongxh + * @Date: 2024-12-07 + * @Description: 窗口管理类 + */ + +/** + * 显示指定名称的窗口 + * @param windowName - 窗口的名称 + * @param userdata - 可选的用户数据 + * @returns Promise + * @internal - 标记内部方法 + */ +public static showWindow(windowName: string, userdata?: any): Promise { +``` + +### 代码注释原则 +- 公共API必须有完整的JSDoc注释 +- 复杂逻辑添加行内注释说明 +- 内部方法使用 `@internal` 标记 +- 废弃功能使用 `@deprecated` 标记 + +## 版本管理 + +### 语义化版本 +- **主版本号**: 不兼容的API修改 +- **次版本号**: 向后兼容的功能性新增 +- **修订号**: 向后兼容的问题修正 + +### 变更记录 +- 维护 CHANGELOG.md 记录版本变更 +- 每个版本包含新增功能、修复问题、破坏性变更 +- 提供迁移指南帮助用户升级 + +## 测试规范 + +### 单元测试 +```typescript +describe('WindowManager', () => { + test('should show window correctly', () => { + // 测试逻辑 + }); + + test('should handle window not found', () => { + // 错误处理测试 + }); +}); +``` + +### 集成测试 +- 测试模块间的交互 +- 验证装饰器系统的完整性 +- 测试平台适配功能 + +## 部署和发布 + +### 构建配置 +```json +{ + "scripts": { + "build": "rollup -c rollup.config.mjs", + "build:all": "npm run build && npm run copy" + } +} +``` + +### 发布检查清单 +1. 代码通过所有测试 +2. 文档更新完整 +3. 版本号正确更新 +4. CHANGELOG.md 记录变更 +5. 类型定义文件正确生成 + +这个规范文档作为项目开发的总体指导,具体的技术细节请参考各个专门的规则文件。 \ No newline at end of file diff --git a/.cursor/rules/typescript-general.mdc b/.cursor/rules/typescript-general.mdc new file mode 100644 index 0000000..47a463c --- /dev/null +++ b/.cursor/rules/typescript-general.mdc @@ -0,0 +1,111 @@ +--- +description: "KunpoCC TypeScript 通用开发规范" +globs: ["src/**/*.ts"] +alwaysApply: true +type: "code-style" +--- + +# KunpoCC TypeScript 通用开发规范 + +## 代码风格和命名规范 + +### 命名约定 +- **类名**: 使用 PascalCase (如 `WindowManager`, `DataBase`) +- **接口**: 以 `I` 开头,使用 PascalCase (如 `IWindow`, `IModule`) +- **方法和属性**: 使用 camelCase (如 `showWindow`, `onInit`) +- **常量**: 全大写,下划线分隔 (如 `KUNPO_DEBUG`, `TAG`) +- **私有方法**: 以下划线开头 (如 `_init`, `_removeWindow`) +- **内部方法**: 添加 `@internal` JSDoc 注释 + +### 类型定义 +```typescript +// 优先使用接口定义对象结构 +interface size { + width: number; + height: number; +} + +// 使用泛型约束确保类型安全 +public static getWindow(name: string): T | null { + return this._windows.get(name) as T; +} +``` + +## 注释规范 + +### JSDoc 注释要求 +- 所有公共方法必须有完整的 JSDoc 注释 +- 包含参数说明、返回值说明和描述 +- 内部方法使用 `@internal` 标记 + +```typescript +/** + * @Author: Gongxh + * @Date: 2024-12-07 + * @Description: 窗口管理类 + */ + +/** + * 显示指定名称的窗口,并传递可选的用户数据 + * @param windowName - 窗口的名称 + * @param userdata - 可选参数,用于传递给窗口的用户数据 + * @internal + */ +public static showWindowIm(windowName: string, userdata?: any): void { +``` + +## 错误处理和日志 + +### 使用统一的日志系统 +```typescript +import { debug, warn, error, log } from "../tool/log"; + +// 调试信息(仅在 debug 模式下显示) +debug("窗口注册完成", windowName); + +// 警告信息 +warn(`窗口不存在 ${windowName} 不需要关闭`); + +// 错误信息 +error("初始化失败", errorMessage); +``` + +### 错误处理模式 +```typescript +// 使用 Promise 进行异步错误处理 +public static showWindow(windowName: string, userdata?: any): Promise { + return new Promise((resolve, reject) => { + this._resPool.loadWindowRes(windowName, { + complete: () => { + this.showWindowIm(windowName, userdata); + resolve(); + }, + fail: (pkgs: string[]) => { + reject(pkgs); + } + }); + }); +} +``` + +## TypeScript 最佳实践 + +### 严格类型检查 +- 启用 `strict: true` +- 禁用 `strictNullChecks: false` (项目特殊要求) +- 使用明确的类型注解,避免 `any` + +### 装饰器使用 +- 启用 `experimentalDecorators: true` +- 遵循项目的装饰器模式 (详见装饰器规范) + +### 模块导入 +```typescript +// 使用相对路径导入 +import { WindowBase } from "./WindowBase"; +import { debug, warn } from "../tool/log"; + +// 导出时保持清晰的结构 +export { WindowManager } from "./ui/WindowManager"; +export { _uidecorator } from "./ui/UIDecorator"; +``` \ No newline at end of file diff --git a/package.json b/package.json index fc3627b..dbaad45 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "kunpocc", - "version": "1.1.9", + "version": "1.1.10", "description": "基于creator3.0+的kunpocc库", "main": "./dist/kunpocc.cjs", "module": "./dist/kunpocc.mjs", diff --git a/src/condition/ConditionManager.ts b/src/condition/ConditionManager.ts index 2369cf6..c5a8209 100644 --- a/src/condition/ConditionManager.ts +++ b/src/condition/ConditionManager.ts @@ -118,6 +118,9 @@ export class ConditionManager { */ public static _removeConditionNode(conditionNode: ConditionNode): void { let types = this._nodeToConditionTypes.get(conditionNode); + if (!types) { + return; + } for (const conditionType of types.values()) { let nodes = this._typeToNotifyNodes.get(conditionType); nodes.delete(conditionNode); diff --git a/src/condition/node/ConditionBase.ts b/src/condition/node/ConditionBase.ts index 0f940e9..4e29f4c 100644 --- a/src/condition/node/ConditionBase.ts +++ b/src/condition/node/ConditionBase.ts @@ -36,7 +36,7 @@ export abstract class ConditionBase { public _updateCondition(): boolean { let canNotify = this.evaluate(); if (canNotify == this._canNotify) { - return; + return false; } this._canNotify = canNotify; return true; diff --git a/src/fgui/WindowHeader.ts b/src/fgui/WindowHeader.ts index e0df8c9..6ae509b 100644 --- a/src/fgui/WindowHeader.ts +++ b/src/fgui/WindowHeader.ts @@ -7,9 +7,9 @@ import { GComponent } from "fairygui-cc"; import { Screen } from "../global/Screen"; -import { AdapterType } from "../kunpocc"; import { IWindow } from "../ui/IWindow"; import { IWindowHeader } from "../ui/IWindowHeader"; +import { AdapterType } from "../ui/header"; export abstract class WindowHeader extends GComponent implements IWindowHeader { diff --git a/src/hotupdate/HotUpdateManager.ts b/src/hotupdate/HotUpdateManager.ts index ebbab23..ee159fa 100644 --- a/src/hotupdate/HotUpdateManager.ts +++ b/src/hotupdate/HotUpdateManager.ts @@ -5,8 +5,8 @@ */ import { native } from "cc"; +import { Platform } from "../global/Platform"; import { ICheckUpdatePromiseResult } from "../interface/PromiseResult"; -import { Platform } from "../kunpocc"; import { log } from "../tool/log"; import { HotUpdate, HotUpdateCode } from "./HotUpdate"; diff --git a/src/minigame/MiniHelper.ts b/src/minigame/MiniHelper.ts index 6927832..5aca740 100644 --- a/src/minigame/MiniHelper.ts +++ b/src/minigame/MiniHelper.ts @@ -5,11 +5,11 @@ */ import { Platform } from "../global/Platform"; -import { BytedanceCommon } from "../kunpocc"; import { AlipayAds } from "./alipay/AlipayAds"; import { AlipayCommon } from "./alipay/AlipayCommon"; import { AlipayPay } from "./alipay/AlipayPay"; import { BytedanceAds } from "./bytedance/BytedanceAds"; +import { BytedanceCommon } from "./bytedance/BytedanceCommon"; import { BytedancePay } from "./bytedance/BytedancePay"; import { IMiniRewardAds } from "./interface/IMiniAds"; import { IMiniCommon } from "./interface/IMiniCommon"; diff --git a/src/minigame/wechat/WechatAds.ts b/src/minigame/wechat/WechatAds.ts index 43e56f1..6640915 100644 --- a/src/minigame/wechat/WechatAds.ts +++ b/src/minigame/wechat/WechatAds.ts @@ -4,7 +4,7 @@ * @Description: 微信广告 */ -import { warn } from "../../kunpocc"; +import { warn } from "../../tool/log"; import { MiniErrorCode } from "../header"; import { IMiniRewardAds } from "../interface/IMiniAds"; diff --git a/src/tool/DataStruct/BinaryHeap.ts b/src/tool/DataStruct/BinaryHeap.ts index 4a136fe..3177a86 100644 --- a/src/tool/DataStruct/BinaryHeap.ts +++ b/src/tool/DataStruct/BinaryHeap.ts @@ -106,11 +106,17 @@ export class BinaryHeap { const size = --this._size; const nodes = this._nodes; - const newNode = (nodes[node.index] = nodes[size]); - newNode.index = node.index; - nodes[size] = null; - this.update(newNode); + + // 如果删除的不是最后一个元素,需要调整堆 + if (node.index < size) { + const newNode = (nodes[node.index] = nodes[size]); + newNode.index = node.index; + nodes[size] = null; + this.update(newNode); + } else { + nodes[size] = null; + } node.index = -1; } diff --git a/src/tool/timer/Timer.ts b/src/tool/timer/Timer.ts index 4c04160..8a1b5e8 100644 --- a/src/tool/timer/Timer.ts +++ b/src/tool/timer/Timer.ts @@ -83,6 +83,7 @@ export class Timer { const timerNode = this._pool.get(timerId); if (timerNode) { + timerNode.pause = true; timerNode.pauseRemainTime = timerNode.expireTime - this._elapsedTime; this._heap.remove(timerNode); this._pausedTimers.set(timerId, timerNode); diff --git a/src/ui/WindowGroup.ts b/src/ui/WindowGroup.ts index 33c337b..61301a6 100644 --- a/src/ui/WindowGroup.ts +++ b/src/ui/WindowGroup.ts @@ -177,7 +177,7 @@ export class WindowGroup { let isMoved = false; if (this.size == 0) { warn(`WindowGroup.moveWindowToTop: window group 【${this._name}】 is empty`); - return; + return false; } if (this._windowNames[this.size - 1] == name) { // 已经在最顶层了 @@ -185,7 +185,7 @@ export class WindowGroup { const index = this._windowNames.indexOf(name); if (index == -1) { warn(`WindowGroup.moveWindowToTop: window 【${name}】 not found in window group 【${this._name}】`); - return; + return false; } if (index < this._windowNames.length - 1) { this._windowNames.splice(index, 1); @@ -202,6 +202,7 @@ export class WindowGroup { window._setDepth(this._root.numChildren - 1); // 处理窗口显示和隐藏状态 this._processWindowHideStatus(this.size - 1, isMoved); + return true; } /** diff --git a/src/ui/WindowResPool.ts b/src/ui/WindowResPool.ts index f7ae411..34fec34 100644 --- a/src/ui/WindowResPool.ts +++ b/src/ui/WindowResPool.ts @@ -6,7 +6,7 @@ import { assetManager, resources } from "cc"; import { UIObjectFactory, UIPackage } from "fairygui-cc"; -import { warn } from "../kunpocc"; +import { warn } from "../tool/log"; import { IPackageConfigRes } from "./IPackageConfig"; export interface WindowInfo {