diff --git a/README.md b/README.md index 2352c75..af45bcc 100644 --- a/README.md +++ b/README.md @@ -1,1612 +1,29 @@ # KunpoLib - 基于 Cocos Creator 3.0+ 的一套游戏框架,提供了一系列实用模块,帮助开发者快速构建高质量的游戏项目。 +项目持续优化中,敬请期待~~~ -项目持续优化中,敬请期待~~ - -## 目录 -- [模块](#模块) -- [安装kunpocc](#安装kunpocc) -- [项目基础配置](#项目基础配置) -- [使用方法](#使用方法) - - [一、UI 系统](#一ui-系统) - - [UI 装饰器使用](#ui-装饰器使用) - - [创建窗口](#创建窗口) - - [二、实体组件系统(EC)](#二实体组件系统ec) - - [creator插件`kunpo-ec`](#creator插件kunpo-ec) - - [重点接口说明](#重点接口说明) - - [使用](#使用) - - [三、网络模块](#三-网络模块) - - [HTTP 请求](#http-请求) - - [请求方法](#请求方法) - - [参数说明](#参数说明) - - [响应处理](#响应处理) - - [四、四叉树碰撞检测](#四四叉树碰撞检测) - - [基本概念](#基本概念) - - [使用示例](#使用示例) - - [形状操作](#形状操作) - - [性能优化建议](#性能优化建议) - - [五、行为树系统](#五行为树系统) - - [行为树基本概念](#行为树基本概念) - - [行为树使用示例](#行为树使用示例) - - [常用节点](#常用节点) - - [注意事项](#注意事项) - - [六、资源加载工具](#六资源加载工具) - - [资源加载器](#资源加载器) - - [资源池](#资源池) - - [七、条件显示节点](#七条件显示节点用来处理游戏中的提示红点信息) - - [定义条件](#定义条件) - - [节点关联条件](#节点关联条件) - - [八、全局事件系统](#八-全局事件系统) - - [九、全局计时器](#九全局计时器) - - [十、平台相关](#十平台相关) - - [平台类型](#平台类型) - - [平台判断](#平台判断) - - [使用示例](#使用示例-2) - - [十一、屏幕](#十一屏幕) - - [十二、工具类](#十二工具类) - - [数学工具 (MathTool)](#数学工具-mathtool) - - [MD5 加密](#md5-加密) - -## 模块 - -- **UI 系统 (基于 FairyGUI)** - - * 灵活的 UI 装饰器(配合插件 `kunpo-fgui` 使用,一键导出界面配置,省时省力省代码) - - * 控制窗口之间的相互关系(eg: 打开界面时,是隐藏/关闭前一个界面,还是隐藏/关闭所有界面) - - * 多窗口组管理 - - * 顶部显示金币钻石的资源栏(header),一次实现,多界面复用, - - * 支持不同界面使用不同 header - -- **EC 框架** - - * 不同实体上的组件更新顺序管理(`只根据注册的组件更新顺序更新,跟实体无关`) - - - 灵活的EC装饰器 (配合插件 `kunpo-ec` 使用,配置实体组件信息,一键导出) - - 支持多世界(多战斗场景,互不影响) - - 区分数据组件和逻辑组件,只更新逻辑组件 - -- **网络模块 (Http)** - - HTTP 请求管理 - - 完整的请求响应接口 - -- **四叉树(碰撞查询)** - - * 矩形碰撞 - * 圆形碰撞 - * 多边形碰撞 - -- **行为树系统** - - 用来实现复杂的怪物行为 - -- **资源管理** - * 资源加载后放入资源池 - * 资源池 可通过路径或者uuid获取资源 - * 只适合手动管理资源,单无论加载多少次,卸载一次后删除 - -- **条件显示节点系统 (用来处理游戏中的提示红点信息)** - - * 主要是解耦用,条件单独实现,节点关联单条件或者多个条件 - -- **全局事件系统 `GlobalEvent`** - -- **全局计时器 `GlobalTimer`** - -- **平台相关 `Platform`** - -- **屏幕尺寸 `Screen`** - -- **数据结构** - - - 二叉堆(`BinaryHeap` 最大、最小堆) - - 单向(`LinkedList`)、双向链表 (`DoublyLinkedList`) - - 栈(`Stack`) - -- **适配相关 `Adapter` (不需要关心) ** - -## 安装kunpocc +## 安装kunpocc +项目已发布到 `npm`, 安装方法如下: +> 如果连不上npm, 可使用国内镜像 比如: 阿里 ```bash npm install kunpocc ``` -## 项目基础配置 - -1. 新建项目,创建首场景文件 - -2. 编写入口脚本 - - ```typescript - import { _decorator } from "cc"; - /** 引入kunpocc入口 */ - import { CocosEntry, log } from "kunpocc"; - - const { ccclass, property, menu } = _decorator; - @ccclass("GameEntry") - @menu("kunpo/GameEntry") - export class GameEntry extends CocosEntry { - @property(cc.Node) - private root: cc.Node = null; - onInit(): void { - log("GameEntry onInit"); - } - } - ``` - -3. 创建入口节点`GameEntry`,创建UI模块节点 `UI` 、UI容器节点`window`, 并关联对应的脚本 - - ![image-20250213102309047](https://gitee.com/gongxinhai/public-image/raw/master/image-20250213102309047.png) - -4. 配置完毕 - - - -## 使用方法 - -### 一、UI 系统 - -> 注:UI制作需要使用 FairyGUI,[FairyGUI官方文档](https://www.fairygui.com/docs/editor) - -> ![image-20250213105921142](https://gitee.com/gongxinhai/public-image/raw/master/image-20250213105921142.png) - -#### *UI 装饰器使用* - -> 注:只有使用了装饰器的内容才能在 `kunpo-fgui` 插件中识别,`kunpo-fgui`插件操作界面如下图 - -> ![image-20250213110353385](https://gitee.com/gongxinhai/public-image/raw/master/image-20250213110353385.png) - - - -1. 窗口装饰器 - - ```typescript - import { Window, _uidecorator } from 'kunpocc'; - const { uiclass, uiprop, uiclick } = _uidecorator; - - /** - * 窗口装饰器 - * @param 参数1: 窗口容器节点名字 - * @param 参数2: FairyGUI中的UI包名 - * @param 参数3: FairyGUI中的组件名 必须和 class 类同名 这里是 MyWindow - */ - @uiclass("Window", "UI包名", "MyWindow") - export class MyWindow extends Window { - // ... 窗口实现 - } - ``` - -2. 窗口 Header 装饰器 - - ```typescript - import { WindowHeader, _uidecorator } from 'kunpocc'; - const { uiheader } = _uidecorator; - - /** - * 窗口顶部资源栏装饰器 - * @param 参数1: FairyGUI中的UI包名 - * @param 参数2: FairyGUI中的组件名 必须和 class 类同名 这里是 MyWindowHeader - */ - @uiheader("UI包名", "WindowHeader") - export class MyWindowHeader extends WindowHeader { - // ... Header 实现 - } - ``` - -3. UI组件装饰器 - - ```typescript - import { _uidecorator } from 'kunpocc'; - const { uicom, uiprop, uiclick } = _uidecorator; - - /** - * UI组件类装饰器 - * @param 参数1: FairyGUI中的UI包名 - * @param 参数2: FairyGUI中的组件名 必须和 class 类同名 这里是 MyComponent - */ - @uicom("Home", "MyComponent") - export class MyComponent { - // ... 组件实现 - } - ``` - -4. UI属性装饰器 - - ```typescript - import { Window, _uidecorator } from 'kunpocc'; - const { uiclass, uiprop, uiclick } = _uidecorator; - - @uiclass("Window", "Home", "MyWindow") - export class MyWindow extends Window { - // FairyGUI 组件属性装饰器 - @uiprop private btnConfirm: GButton; // 按钮组件 - @uiprop private txtTitle: GTextField; // 文本组件 - @uiprop private listItems: GList; // 列表组件 - } - ``` - -5. 点击事件装饰器 - - ```typescript - import { Window, _uidecorator } from 'kunpocc'; - const { uiclass, uiprop, uiclick } = _uidecorator; - - @uiclass("Window", "Home", "MyWindow") - export class MyWindow extends Window { - // 点击事件装饰器 - @uiclick - private onTouchEvent(event: cc.Event): void { - console.log('确认按钮被点击'); - } - } - ``` - - -#### *创建窗口* - -1. 创建窗口类 - - ```typescript - /** - * 窗口名必须和FairyGUI中的组件同名 - */ - import { Window, _uidecorator } from 'kunpocc'; - const { uiclass, uiprop, uiclick } = _uidecorator; - - @uiclass("Window", "UI包名", "MyWindow") - export class MyWindow extends Window { - protected onInit(): void { - // 初始化窗口 - } - - protected onShow(userdata?: any): void { - // 窗口显示时的逻辑 - } - - protected onClose(): void { - // 窗口关闭时的逻辑 - } - } - ``` - -2. 窗口管理 - - * 重要:显示窗口前必须确保窗口资源已加载 !!! - - ```typescript - // 显示窗口 - WindowManager.showWindow("MyWindow", { /* 用户数据 */ }); - - // 关闭窗口 - WindowManager.closeWindow("MyWindow"); - - // 获取窗口实例 - const window = WindowManager.getWindow("MyWindow"); - - // 获取当前最顶层窗口 - const topWindow = WindowManager.getTopWindow(); - - // 检查窗口是否存在 - const exists = WindowManager.hasWindow("MyWindow"); - ``` - -3. 窗口生命周期 -- `onInit`: 窗口初始化时调用 -- `onShow`: 窗口显示时调用 -- `onClose`: 窗口关闭时调用 -- `onHide`: 窗口隐藏时调用 -- `onShowFromHide`: 窗口从隐藏状态恢复时调用 -- `onCover`: 窗口被覆盖时调用 -- `onRecover`: 窗口恢复时调用 -- `onEmptyAreaClick`: 点击窗口空白区域时调用 - - - -### 二、实体组件系统(EC) - -* 不使用creator官方的挂载脚本的方式,有以下几个原因 - > 1. node挂脚本的方式效率低 - > 1. 支持多世界,方便管理 - > 3. 通过装饰器注册属性给creator插件 kunpo-ec 使用 - > 4. 组件分数据组件和逻辑组件,只更新逻辑组件 - -* 实体组件系统是一种用于游戏开发的架构模式,它将游戏对象(实体)的数据(组件)和行为分离。 - -#### *creator插件`kunpo-ec`* - -> `kunpo-cc`可以方便创建、配置、导出实体,操作界面如下图: - -![image-20250213111622050](https://gitee.com/gongxinhai/public-image/raw/master/image-20250213111622050.png) - - - -#### *重点接口说明* - -注:详细说明查看声明文件 `kunpocc.d.ts` - -1. 总管理器 `ECManager` - - ```typescript - /**注册所有组件 如果GameEntry因分包导致,组件的代码注册晚于CocosEntry的onInit函数, 则需要在合适的时机手动调用此方法*/ - public static registerComponents(): void - - /** - * 创建EC世界 创建EC世界前必须先注册组件 - * @param {string} worldName 名称 - * @param {Node} node 世界节点 - * @param {number[]} componentUpdateOrderList 组件更新顺序列表 (只传需要更新的组件列表) - * @param {number} [maxCapacityInPool=128] 实体池最大容量,多余的实体不会缓存 - * @param {number} [preloadEntityCount=32] 预加载Entity数量 - */ - public static createECWorld(worldName: string, node: Node, componentUpdateOrderList: number[], maxCapacityInPool = 128, preloadEntityCount = 32): EntityManager - - /** 获取EC世界 */ - public static getECWorld(worldName: string): EntityManager - - /** 获取EC世界节点 */ - public static getECWorldNode(worldName: string): Node - - /** 销毁EC世界 */ - public static destroyECWorld(worldName: string): void - - /** - * 注册配置表中的实体信息 - * 如果在GameEntry中配置了ecConfig,则此方法会自动调用 - * @param config 实体配置信息,格式为 {实体名: {组件名: 组件数据}} - */ - public static registerEntityConfig(config: { [entityName: string]: IEntityConfig }): void - - /** - * 添加实体信息 (如果已经存在, 则数据组合) - * 如果存在编辑器编辑不了的数据 用来给编辑器导出的实体信息 添加扩展数据 - * @param name 实体名 - * @param info 实体信息 - */ - public static addEntityInfo(name: string, info: IEntityConfig): void - - /** 获取实体配置信息 */ - public static getEntityInfo(name: string): Record - - /** - * 创建实体 - * @param worldName 实体管理器名称 - * @param name 实体名字 - * @returns {kunpo.Entity} 实体 - */ - public static createEntity(worldName: string, name: string): Entity - - /** - * 销毁实体 - * @param worldName 世界名称 - * @param entity 实体 - */ - public static destroyEntity(worldName: string, entity: Entity): void - - /** - * 通过实体ID销毁实体 - * @param worldName 世界名称 - * @param entityId 实体ID - */ - public static destroyEntityById(worldName: string, entityId: number): void - ``` - -2. 实体管理器 (创建的world)`EntityManager ` - - ```typescript - /** - * 通过实体ID获取实体 - * @param {EntityId} entityId 实体Id - * @returns {(Entity | null)} 实体 - */ - public getEntity(entityId: EntityId): Entity | null - - /** - * 获取指定标签的实体 - * @param {number} tag 标签 - * @returns {Entity[]} 返回的实体池 - */ - public getEntitiesByTag(tag: number): Entity[] - - /** - * 根据实体ID判断实体是否存在 - * @param {EntityId} entityId 实体Id - * @returns {boolean} - */ - public exists(entityId: EntityId): boolean - - /** 添加单例组件 */ - public addSingleton(component: Component): void - - /** 获取单例组件 */ - public getSingleton(componentType: number): T - - /** 删除单例组件 */ - public removeSingleton(componentType: number): void - - /** 是否存在对应的单例组件 */ - public hasSingleton(componentType: number): boolean - - /** 激活单例组件 */ - public activeSingleton(): void - - - /** 更新 需要外部调用 */ - public update(dt: number): void - ``` - -3. 实体 `Entity` - - ```typescript - /** 实体名称 */ - public name: string; - - /** 实体ID */ - public id: EntityId; - - /** 实体标识 */ - public tags: Set; - - /** 实体状态 */ - public states: Map; - - /** 是否被激活 (添加到实体管理器时激活) */ - public active: boolean = false; - - /** 所属实体管理器 (实体创建后直接赋值) */ - public entityManager: EntityManager; - - /** 所有组件 */ - public readonly components: Map = new Map(); - - /** 添加标签 标签除了表示Entity,还可以通过EntityManager获取指定标签的Entity */ - public addTag(...tag: number[]): void - - /** 删除标签 */ - public removeTag(tag: number): void - - /** 是否包含标签 */ - public hasTag(...tag: number[]): boolean - - /** 获取组件 */ - public getComponent(componentType: number): T - - /** 添加组件 */ - public addComponent(component: Component): void - - /** 删除组件 */ - public removeComponent(componentType: number): void - - /** 删除所有组件 */ - public removeAllComponents(): void - - /** - * 是否包含组件 - * @param {number} componentType 组件类型 - */ - public hasComponent(componentType: number): boolean - - /** 销毁自己 */ - public destroy(): void { - this.entityManager.destroyEntityById(this.id); - } - - /** - * 添加监听 - * @param eventName 监听的消息名 - * @param callback 回调 - * @param entityId 实体ID - * @param once 是否单次监听 - */ - public addEvent(eventName: string, callback: (...args: any[]) => void, once: boolean = false): void - - /** - * 发送消息 - * @param eventName 消息名 - * @param entityId 实体ID - * @param args 发送参数 - */ - public sendListener(eventName: string, ...args: any[]): void - - /** 删除监听 */ - public removeListener(eventName: string, callback?: (...args: any[]) => void): void - - /** - * 添加状态 - * 状态采用计数方式,对状态处理时需要保证addState和removeState成对存在 - * @param {number} state 状态类型 - */ - public addState(state: number): void - - /** - * 删除状态 - * @param {number} state 状态类型 - * @returns {boolean} 如果计数为0或状态不存在,则返回true - */ - public removeState(state: number): boolean - - /** 是否包含指定状态 */ - public hasState(state: number): boolean - - /** 清除状态 */ - public clearState(state: number): void - - /** 清除所有状态 */ - public clearAllStates(): void - ``` - - - -4. 组件 `Component` - - ```typescript - /** 组件名 */ - public name: string; - - /** 组件类型 */ - public type: number; - - /** 是否需要更新 */ - public needUpdate: boolean; - - /** 所属实体 */ - public entity: Entity; - - /** 所属组件管理器 */ - public componentManager: ComponentManager; - - /** - * 获取同实体上的组件 - * @param {number} componentType 组件类型 - */ - public getComponent(componentType: number): T - - /** 删除自己 */ - public destroySelf(): void - - /** - * 生命周期函数 - * 被添加到实体 对应onDestroy - */ - protected onAdd(): void - - /** - * 生命周期函数 - * 组件被销毁 对应onAdd - */ - protected onDestroy(): void - - /** - * 生命周期函数 - * 可在此方法获取实体其他组件 - */ - protected abstract onEnter(): void; - - /** - * 生命周期函数 - * 从实体中删除前执行的函数 在此函数中清理初始化的数据 - */ - protected abstract onRemove(): void; - - /** - * 更新函数 - */ - protected onUpdate(dt: number): void - ``` - - - -#### *使用* - -1. 组件类型声明 - - ```typescript - /** - * @Author: gongxh - * @Date: 2025-01-23 - * @Description: 组件枚举 - */ - - import { cc } from "../header"; - - /** 数据组件类型 */ - enum DataComponentType { - Health, - Transform, - RootNode, - LimitMove, - /** 渲染组件 (多个) */ - Render, - } - - /** 逻辑组件类型 (组件更新数据从上到下) */ - export enum SystemComponentType { - Move = 100000, - ScreenRebound, - - /** 位置更新系统 */ - PositionUpdateSystem = 120000, - } - - export const ComponentType = { - ...DataComponentType, - ...SystemComponentType - }; - export type ComponentType = DataComponentType | SystemComponentType; - - /** 自定义组件更新顺序列表 */ - export const componentUpdateOrderList = cc.Enum.getList(cc.Enum(SystemComponentType)).map(item => item.value).sort((a, b) => a - b); - ``` - -2. 编写组件脚本 - - * 在组件的 onAdd 方法中,设置组件是否更新,只有需要更新的组件才需要设置 - - ```typescript - protected onAdd(): void { - this.needUpdate = true; - } - ``` - - - - 组件完整示例内容如下: - - ```typescript - import { AnimationClip, Asset, AudioClip, Color, Enum, JsonAsset, ParticleAsset, Prefab, Size, Skeleton, SpriteFrame, Vec2, Vec3 } from "cc"; - import { _ecdecorator, Component } from "kunpocc"; - import { ComponentType } from "../../ComponentTypes"; - const { ecclass, ecprop } = _ecdecorator; - - enum HealthType { - HP = 1, - Max = 2, - Current = 3 - } - - // 注册组件 (必须) - @ecclass("Health", ComponentType.Health, { describe: "血量组件" }) - export class Health extends Component { - // 注册组件属性 (可选: 使用kunpo-ec插件则必须注册) - @ecprop({ type: "entity", defaultValue: "", displayName: "实体", tips: "实体" }) - private testentity: string = ""; - - @ecprop({ type: "array", format: "entity", displayName: "实体数组", tips: "实体数组" }) - private testentityarray: string[] = []; - - @ecprop({ type: 'int', defaultValue: 0, displayName: "血量", tips: "当前血量提示" }) - private hp: number = 0; - - @ecprop({ type: 'float', defaultValue: 0, displayName: "最大血量", tips: "最大血量提示" }) - private maxHp: number = 0; - - @ecprop({ type: 'string', defaultValue: "", displayName: "字符串", tips: "字符串提示" }) - private string: string = ""; - - @ecprop({ type: 'boolean', defaultValue: false, displayName: "布尔值", tips: "布尔值提示" }) - private bool: boolean = true; - - @ecprop({ type: "enum", format: Enum(HealthType), defaultValue: HealthType.Current, displayName: "枚举", tips: "枚举提示" }) - private hpeunm: HealthType = HealthType.Current; - - @ecprop({ type: "spriteframe", displayName: "精灵帧" }) - private spriteFrame: SpriteFrame; - - @ecprop({ type: "asset", displayName: "资源" }) - private asset: Asset; - - @ecprop({ type: "prefab", displayName: "预制体" }) - private prefab: Prefab; - - @ecprop({ type: "skeleton", displayName: "骨骼动画" }) - private skeleton: Skeleton; - - @ecprop({ type: "particle", displayName: "粒子" }) - private particle: ParticleAsset; - - @ecprop({ type: "animation", displayName: "动画" }) - private animation: AnimationClip; - - @ecprop({ type: "audio", displayName: "音频" }) - private audio: AudioClip; - - @ecprop({ type: "jsonAsset", displayName: "json资源" }) - private jsonAsset: JsonAsset; - - @ecprop({ - type: "object", format: { - hp1: { - type: "object", - format: { - hp: "int", - max: "int" - } - }, - hp2: { - type: "object", - format: { - hp: "int", - max: "int" - } - }, - }, - }) - private obj: { hp1: { hp: number, max: number }, hp2: { hp: number, max: number } }; - - @ecprop({ - type: "array", format: "int", - }) - private arr: number[]; - - @ecprop({ - type: "array", format: { type: "object", format: { hp: "int", max: "int" } } - }) - private arrobj: { hp: number, max: number }[]; - - @ecprop({ type: "vec2", displayName: "向量2" }) - private vec2: Vec2; - - @ecprop({ type: "vec3", displayName: "向量3" }) - private vec3: Vec3; - - @ecprop({ type: "color", defaultValue: Color.RED, displayName: "颜色" }) - private color: Color; - - @ecprop({ type: "size", displayName: "尺寸" }) - private size: Size; - - protected onAdd(): void { - this.needUpdate = true; - } - - protected onEnter(): void { - // 可在此获取同实体上的其他组件 - let transform = this.getComponent(ComponentType.Transform); - /** 获取单例组件 */ - let signleton = this.entity.entityManager.getSingleton(ComponentType.XXXX); - } - - protected onRemove(): void { - // 清理组件数据 - } - } - ``` - -3. 创建ec世界,并设置更新 - - ```typescript - /** - * @Author: Gongxh - * @Date: 2025-01-16 - * @Description: 战斗界面 - */ - - import { ECManager } from "kunpocc"; - import { componentUpdateOrderList } from "../../ec/ComponentTypes"; - import { cc, fgui, kunpo } from "../../header"; - const { uiclass, uiprop, uiclick } = kunpo._uidecorator; - - @uiclass("Window", "Game", "GameWindow") - export class GameWindow extends kunpo.Window { - @uiprop container: fgui.GComponent; - public onInit() { - console.log("GameWindow onInit"); - this.adapterType = kunpo.AdapterType.Full; - this.type = kunpo.WindowType.CloseAll; - this.bgAlpha = 0; - } - - protected onShow() { - console.log("GameWindow onShow"); - /** 创建一个ec世界的节点 */ - let node = new cc.Node(); - this.container.node.addChild(node); - - /** - * 创建一个ec世界 - * 参数1: 世界名称 - * 参数2: 世界节点 - * 参数3: 组件更新顺序列表 - * 参数4: 实体池的最大缓存数量,多余的不会被缓存,根据需要调整 - * 参数5: 预创建的实体数量,根据需要调整 - */ - kunpo.log("需要更新的组件", componentUpdateOrderList); - ECManager.createECWorld("world", node, componentUpdateOrderList, 100, 10); - } - - protected onClose() { - /** 退出游戏时 销毁ec世界 */ - ECManager.destroyECWorld("world"); - } - - @uiclick - private onBack(): void { - kunpo.WindowManager.showWindow("HomeWindow"); - } - - @uiclick - private onCreateEntity(): void { - /** 创建一个实体 */ - ECManager.createEntity("world", "entity1"); - } - - protected onUpdate(dt: number): void { - /** 更新ec世界 */ - ECManager.getECWorld("world").update(dt); - } - } - ``` - - - -### 三、 网络模块 - -#### *HTTP 请求* - -```typescript -import { HttpManager, IHttpEvent, HttpResponseType } from 'kunpocc'; - -// 1. 使用回调方式处理响应 -const event: IHttpEvent = { - name: "login", - onComplete: (response) => { - console.log('请求成功:', response.data); - }, - onError: (response) => { - console.log('请求失败:', response.error); - } -}; - -// POST 请求 -HttpManager.post( - "https://api.example.com/login", - { username: "test", password: "123456" }, - "json", // 响应类型:'json' | 'text' | 'arraybuffer' - event, - ["Content-Type", "application/json"], // 请求头 - 5 // 超时时间(秒) -); - -// GET 请求 -HttpManager.get( - "https://api.example.com/users", - { id: 1 }, - "json", - event -); - -// 2. 使用全局事件方式处理响应 -GlobalEvent.add(HttpManager.HttpEvent, (result, response) => { - if (result === "succeed") { - console.log('请求成功:', response.data); - } else { - console.log('请求失败:', response.error); - } -}, this); - -// 发送请求(不传入 event 参数) -HttpManager.post("https://api.example.com/data", { /* data */ }); -``` - -#### *请求方法* -- `post(url, data, responseType?, event?, headers?, timeout?)` -- `get(url, data, responseType?, event?, headers?, timeout?)` -- `put(url, data, responseType?, event?, headers?, timeout?)` -- `head(url, data, responseType?, event?, headers?, timeout?)` - -#### *参数说明* -- `url`: 请求地址 -- `data`: 请求数据 -- `responseType`: 响应类型(可选,默认 'json') - - `'json'`: JSON 格式 - - `'text'`: 文本格式 - - `'arraybuffer'`: 二进制数据 -- `event`: 请求事件回调(可选) -- `headers`: 请求头(可选) -- `timeout`: 超时时间,单位秒(可选,0表示不超时) - -#### *响应处理* -1. 回调方式(通过 IHttpEvent): -```typescript -const event: IHttpEvent = { - name: "自定义名称", - data?: "自定义数据", // 可选 - onComplete: (response) => { - // 成功回调 - }, - onError: (response) => { - // 失败回调 - } -}; -``` - -2. 全局事件方式: -```typescript -GlobalEvent.add(HttpManager.HttpEvent, (result, response) => { - // result: "succeed" | "fail" - // response: IHttpResponse -}, this); -``` - - - -### 四、四叉树碰撞检测 - -> 四叉树是一种用于高效进行空间划分和碰撞检测的数据结构。 - -#### *基本概念* - -1. 形状类型 - - ```typescript - import { QuadTree, Box, Circle, Polygon } from 'kunpocc'; - - // 1. 矩形 - const box = new Box(x, y, width, height, tag); - - // 2. 圆形 - const circle = new Circle(x, y, radius, tag); - - // 3. 多边形 - const points = [v2(x1, y1), v2(x2, y2), v2(x3, y3)]; - const polygon = new Polygon(points, tag); - ``` - -2. 配置参数 - - ```typescript - // 四叉树配置 - const QTConfig = { - MAX_SHAPES: 12, // 每个节点最大形状数量 - MAX_LEVELS: 5, // 最大深度 - } - ``` - - -#### *使用示例* - -1. 创建和初始化 - - ```typescript - import { QuadTree, Box, rect } from 'kunpocc'; - - // 创建四叉树(参数:区域范围,层级,绘制组件) - const bounds = rect(0, 0, 800, 600); // x, y, width, height - const quadTree = new QuadTree(bounds); - - // 添加形状 - const player = new Box(100, 100, 50, 50, 1); // 玩家碰撞体,tag=1 - const enemy = new Circle(200, 200, 25, 2); // 敌人碰撞体,tag=2 - quadTree.insert(player); - quadTree.insert(enemy); - ``` - -2. 碰撞检测 - - ```typescript - // 检测指定形状与特定标签的碰撞 - const collisions = quadTree.collide(player, 2); // 检测玩家与 tag=2 的形状碰撞 - if (collisions.length > 0) { - console.log('发生碰撞!'); - for (const target of collisions) { - // 处理碰撞逻辑 - } - } - ``` - -3. 动态更新 - - ```typescript - // 在游戏循环中更新四叉树 - function update() { - // 更新形状位置 - player.position = v2(newX, newY); - enemy.position = v2(newX, newY); - - // 更新四叉树 - quadTree.update(); - - // 检测碰撞 - const collisions = quadTree.collide(player, 2); - } - ``` - -4. 清理 - - ```typescript - // 清理四叉树 - quadTree.clear(); - ``` - - -#### *形状操作* - -1. 位置和缩放 - - ```typescript - // 设置位置 - shape.position = v2(x, y); - - // 设置缩放 - shape.scale = 1.5; - - // 获取包围盒 - const boundingBox = shape.getBoundingBox(); - ``` - -2. 特定形状操作 - - ```typescript - // 矩形重置 - box.resetPoints(x, y, width, height); - - // 圆形半径 - circle.radius = newRadius; - - // 多边形顶点 - polygon.points = newPoints; - ``` - - -#### *性能优化建议* - -1. 合理设置配置参数: - - `MAX_SHAPES`:较小的值会导致更频繁的分裂,较大的值会降低查询效率 - - `MAX_LEVELS`:控制树的最大深度,防止过度分割 - -2. 碰撞检测优化: - - 使用合适的标签系统,只检测需要的碰撞 - - 根据游戏需求选择合适的形状(圆形计算最快) - - 避免使用过于复杂的多边形 - -3. 更新策略: - - 仅在必要时更新四叉树 - - 对于静态物体,可以使用单独的四叉树 - - 动态物体频繁更新时,考虑使用更大的边界范围 - - -### 五、行为树系统 - -> 行为树是一个强大的 AI 决策系统,用于实现复杂的游戏 AI 行为。 - -#### *行为树基本概念* - -1. 节点状态 -```typescript -enum Status { - SUCCESS, // 成功 - FAILURE, // 失败 - RUNNING // 运行中 -} -``` - -2. 节点类型 -- **动作节点 (Action)**:执行具体行为的叶子节点 -- **组合节点 (Composite)**:控制子节点执行顺序的节点 -- **条件节点 (Condition)**:判断条件的节点 -- **装饰节点 (Decorator)**:修饰其他节点行为的节点 - -#### *行为树使用示例* - -```typescript -import { - BehaviorTree, - Sequence, - Selector, - Parallel, - Success, - Failure, - WaitTime, - Agent, - Blackboard -} from 'kunpocc'; - -// 1. 创建行为树 -const tree = new BehaviorTree( - new Sequence( // 顺序节点:按顺序执行所有子节点 - new WaitTime(2), // 等待2秒 - new Selector( // 选择节点:选择一个可执行的子节点 - new Success(() => { - console.log("执行成功动作"); - }), - new Failure(() => { - console.log("执行失败动作"); - }) - ) - ) -); - -// 2. 创建代理和黑板 -const agent = new Agent(); // AI代理 -const blackboard = new Blackboard(); // 共享数据黑板 - -// 3. 执行行为树 -tree.tick(agent, blackboard); -``` - -#### *常用节点* - -1. 组合节点 - - ```typescript - // 顺序节点:按顺序执行所有子节点,直到遇到失败或运行中的节点 - new Sequence(childNode1, childNode2, childNode3); - - // 选择节点:选择第一个成功或运行中的子节点 - new Selector(childNode1, childNode2, childNode3); - - // 并行节点:同时执行所有子节点 - new Parallel(childNode1, childNode2, childNode3); - - // 记忆顺序节点:记住上次执行的位置 - new MemSequence(childNode1, childNode2, childNode3); - - // 记忆选择节点:记住上次执行的位置 - new MemSelector(childNode1, childNode2, childNode3); - - // 随机选择节点:随机选择一个子节点执行 - new RandomSelector(childNode1, childNode2, childNode3); - ``` - -2. 动作节点 - - ```typescript - // 成功节点 - new Success(() => { - // 执行动作 - }); - - // 失败节点 - new Failure(() => { - // 执行动作 - }); - - // 运行中节点 - new Running(() => { - // 持续执行的动作 - }); - - // 等待节点 - new WaitTime(2); // 等待2秒 - new WaitTicks(5); // 等待5个tick - ``` - -3. 使用黑板共享数据 - - ```typescript - // 在节点中使用黑板 - class CustomAction extends Action { - tick(ticker: Ticker): Status { - // 获取数据 - const data = ticker.blackboard.get("key"); - - // 设置数据 - ticker.blackboard.set("key", "value"); - - return Status.SUCCESS; - } - } - ``` - - -#### *注意事项* - -1. 节点状态说明: - - `SUCCESS`:节点执行成功 - - `FAILURE`:节点执行失败 - - `RUNNING`:节点正在执行中 -2. 组合节点特性: - - `Sequence`:所有子节点返回 SUCCESS 才返回 SUCCESS - - `Selector`:任一子节点返回 SUCCESS 就返回 SUCCESS - - `Parallel`:并行执行所有子节点 - - `MemSequence/MemSelector`:会记住上次执行位置 -3. 性能优化: - - 使用黑板共享数据,避免重复计算 - - 合理使用记忆节点,减少重复执行 - - 控制行为树的深度,避免过于复杂 - - - -### 六、资源加载工具 - -#### *资源加载器* - -> 注意:资源就算加载多次和一次效果一样 - -```typescript -interface IAssetConfig { - /** 资源类型 */ - type: typeof Asset; - /** 资源路径 */ - path: string; - /** 是否是单个文件 默认是文件夹 */ - isFile?: boolean; - /** 资源包名 默认 resources */ - bundle?: string; -} - -/** - * 开始加载资源 - * @param {IAssetConfig[]} res.configs 资源配置 - * @param {number} res.parallel 并行加载数量 默认 10 - * @param {number} res.retry 失败重试次数 默认 3 - * @param {Function} res.complete 加载完成回调 - * @param {Function} res.progress 加载进度回调 - * @param {Function} res.fail 加载失败回调 - */ -public start(res: { configs: IAssetConfig[], parallel?: number, retry?: number, complete: () => void, fail: (msg: string, err: Error) => void, progress?: (percent: number) => void }): void - -/** 重试 重新加载失败的资源 */ -public retry(): void -``` - -#### *资源池* - -```typescript -/** 资源是否已加载 */ -public static has(path: string, bundlename: string = "resources"): boolean - -/** 获取资源 */ -public static get(path: string, bundlename: string = "resources"): T - -/** 按 uuid 判断资源是否已加载 */ -public static hasUUID(uuid: string): boolean - -/** 按 uuid 获取资源 */ -public static getByUUID(uuid: string): T - -/** 按资源路径释放资源 */ -public static releasePath(path: string, bundlename: string = "resources"): void - -/** 按 bundle 和 文件夹释放资源 */ -public static async releaseDir(dir: string, bundlename: string = "resources", asset: typeof Asset): Promise - -/** 按 uuid 释放资源 */ -public static releaseUUID(uuid: string): void - -/** 释放所有加载的资源 */ -public static releaseAll(): void -``` - - - -### 七、条件显示节点(用来处理游戏中的提示红点信息) - -#### *定义条件* - -```typescript -// 定义条件类型枚举 -enum ConditionType { - condition1, - condition2, - condition3, -} - -// 定义条件 -@conditionClass(ConditionType.condition1) -export class Condition1 extends kunpo.ConditionBase { - protected onInit(): void { - // 监听条件发生变化, 则调用一次 this.tryUpdate(); - kunpo.GlobalEvent.add("condition1", () => { - this.tryUpdate(); - }, this); - } - - protected evaluate(): boolean { - //TODO:: 根据条件数据,返回true or false - return true; - } -} -``` - -#### *节点关联条件* - -```typescript -/** 任意一个满足 显示节点 */ -new kunpo.ConditionAnyNode(fgui.GObject, ConditionType.condition1, ConditionType.condition2); - -/** 所有条件都满足 显示节点 */ -new kunpo.ConditionAllNode(fgui.GObject, ConditionType.Condition1, ConditionType.Condition2, ConditionType.Condition3); -``` - - - -### 八、 全局事件系统 - -```typescript -import { GlobalEvent } from 'kunpocc'; - -// 添加事件监听 -GlobalEvent.add('eventName', (arg1, arg2) => { - console.log('事件触发:', arg1, arg2); -}, this); - -// 添加一次性事件监听 -GlobalEvent.addOnce('oneTimeEvent', (data) => { - console.log('一次性事件触发:', data); -}, this); - -// 发送事件 -GlobalEvent.send('eventName', 'arg1', 'arg2'); - -// 发送事件到指定目标 -GlobalEvent.sendToTarget('eventName', target, 'arg1', 'arg2'); - -// 移除事件监听 -GlobalEvent.remove('eventName', callback, this); - -// 移除指定目标的所有事件监听 -GlobalEvent.removeByTarget(this); - -// 移除指定事件名和目标的事件监听 -GlobalEvent.removeByNameAndTarget('eventName', this); -``` - - - -### 九、全局计时器 - -```typescript -import { GlobalTimer } from 'kunpocc'; - -// 启动一次性定时器(2秒后执行) -const timerId1 = GlobalTimer.startTimer(() => { - console.log('2秒后执行一次'); -}, 2); - -// 启动循环定时器(每3秒执行一次,执行5次) -const timerId2 = GlobalTimer.startTimer(() => { - console.log('每3秒执行一次,总共执行5次'); -}, 3, 5); - -// 启动无限循环定时器(每1秒执行一次) -const timerId3 = GlobalTimer.startTimer(() => { - console.log('每1秒执行一次,无限循环'); -}, 1, -1); - -// 停止定时器 -GlobalTimer.stopTimer(timerId1); -GlobalTimer.stopTimer(timerId2); -GlobalTimer.stopTimer(timerId3); -``` - -注意事项: -- 定时器的时间间隔单位为秒 -- loop 参数说明: - - 0:执行一次 - - 正整数 n:执行 n 次 - - -1:无限循环 - -### 十、平台相关 - -> Platform 类提供了游戏运行平台的相关信息和判断方法。 - -#### *平台类型* - -```typescript -import { Platform, PlatformType } from 'kunpocc'; - -// 平台类型枚举 -enum PlatformType { - Android = 1, // 安卓 - IOS, // iOS - HarmonyOS, // 鸿蒙 - WX, // 微信小游戏 - Alipay, // 支付宝小游戏 - Bytedance, // 字节小游戏 - HuaweiQuick, // 华为快游戏 - Browser // 浏览器 -} - -// 获取当前平台类型 -const currentPlatform = Platform.platform; -``` - -#### *平台判断* - -```typescript -import { Platform } from 'kunpocc'; - -// 原生平台判断 -if (Platform.isNative) { - console.log('当前是原生平台'); -} - -// 移动平台判断 -if (Platform.isMobile) { - console.log('当前是移动平台'); -} - -// 原生移动平台判断 -if (Platform.isNativeMobile) { - console.log('当前是原生移动平台'); -} - -// 具体平台判断 -if (Platform.isAndroid) { - console.log('当前是安卓平台'); -} - -if (Platform.isIOS) { - console.log('当前是iOS平台'); -} - -if (Platform.isHarmonyOS) { - console.log('当前是鸿蒙系统'); -} - -// 小游戏平台判断 -if (Platform.isWX) { - console.log('当前是微信小游戏'); -} - -if (Platform.isAlipay) { - console.log('当前是支付宝小游戏'); -} - -if (Platform.isBytedance) { - console.log('当前是字节小游戏'); -} - -if (Platform.isHuaweiQuick) { - console.log('当前是华为快游戏'); -} - -// 浏览器判断 -if (Platform.isBrowser) { - console.log('当前是浏览器环境'); -} -``` - -#### *使用示例* - -```typescript -import { Platform, PlatformType } from 'kunpocc'; - -// 根据平台类型执行不同逻辑 -switch (Platform.platform) { - case PlatformType.Android: - // 安卓平台特定逻辑 - break; - case PlatformType.IOS: - // iOS平台特定逻辑 - break; - case PlatformType.WX: - // 微信小游戏特定逻辑 - break; - default: - // 其他平台逻辑 - break; -} - -// 针对不同平台进行适配 -if (Platform.isNativeMobile) { - // 原生移动平台的处理 - if (Platform.isAndroid) { - // 安卓特有功能 - } else if (Platform.isIOS) { - // iOS特有功能 - } -} else if (Platform.isWX || Platform.isAlipay || Platform.isBytedance) { - // 小游戏平台的处理 -} else { - // 浏览器平台的处理 -} -``` - - -### 十一、屏幕 - -```typescript -/** 屏幕宽度 */ -public static ScreenWidth: number; -/** 屏幕高度 */ -public static ScreenHeight: number; -/** 设计分辨率宽 */ -public static DesignWidth: number; -/** 设计分辨率高 */ -public static DesignHeight: number; -/** 安全区外一侧的高度 或 宽度 */ -public static SafeAreaHeight: number; -/** 安全区的宽度 */ -public static SafeWidth: number; -/** 安全区的高度 */ -public static SafeHeight: number; -``` - - - -### 十二、工具类 - -#### *数学工具 (MathTool)* - -```typescript -import { MathTool } from 'kunpocc'; - -// 1. 数值限制 -// 将数值限制在指定范围内 -const value = MathTool.clampf(75, 0, 100); // 返回75,因为在0-100范围内 -const value2 = MathTool.clampf(150, 0, 100); // 返回100,因为超出上限 -const value3 = MathTool.clampf(-50, 0, 100); // 返回0,因为低于下限 - -// 2. 随机数生成 -// 生成指定范围内的整数(包含边界值) -const randomInt = MathTool.rand(1, 10); // 返回1到10之间的整数 - -// 生成指定范围内的浮点数(包含最小值,不包含最大值) -const randomFloat = MathTool.randRange(0, 1); // 返回0到1之间的浮点数 - -// 3. 角度与弧度转换 -// 角度转弧度 -const radian = MathTool.rad(90); // 90度转换为弧度:约1.57 - -// 弧度转角度 -const degree = MathTool.deg(Math.PI); // π弧度转换为角度:180 - -// 4. 平滑过渡 -// 用于实现数值的平滑变化,常用于相机跟随、UI动画等 -const smoothValue = MathTool.smooth( - 0, // 起始值 - 100, // 目标值 - 0.16, // 已经过时间(秒) - 0.3 // 响应时间(秒) -); // 返回一个平滑过渡的中间值 -``` - -使用说明: - -1. `clampf(value: number, min: number, max: number): number` - - 将数值限制在指定范围内 - - 如果小于最小值,返回最小值 - - 如果大于最大值,返回最大值 - - 否则返回原值 - -2. `rand(min: number, max: number): number` - - 生成指定范围内的随机整数 - - 包含最小值和最大值 - - 常用于随机选择、随机掉落等场景 - -3. `randRange(min: number, max: number): number` - - 生成指定范围内的随机浮点数 - - 包含最小值,不包含最大值 - - 常用于需要精确浮点随机数的场景 - -4. `rad(angle: number): number` - - 将角度转换为弧度 - - 计算公式:angle * Math.PI / 180 - -5. `deg(radian: number): number` - - 将弧度转换为角度 - - 计算公式:radian * 180 / Math.PI - -6. `smooth(current: number, target: number, elapsedTime: number, responseTime: number): number` - - 计算平滑过渡的值 - - current: 当前值 - - target: 目标值 - - elapsedTime: 已经过时间(秒) - - responseTime: 响应时间(秒) - - 常用于实现平滑的相机移动、UI动画等 - -#### *MD5 加密* - -```typescript -import { md5 } from 'kunpocc'; - -// 字符串 MD5 加密 -const hash = md5('Hello, World!'); -console.log(hash); // 输出32位MD5哈希值 - -// 注意: -// 1. 输入必须是字符串类型 -// 2. 不能传入 undefined 或 null -try { - md5(null); // 将抛出错误 -} catch (error) { - console.error('MD5输入不能为null或undefined'); -} -``` +# 目录 +1. [项目配置](https://github.com/Gongxh0901/kunpolibrary/blob/main/docs/Basic.md) +2. [UI模块](https://github.com/Gongxh0901/kunpolibrary/blob/main/docs/UI.md) +3. [实体组件模块](https://github.com/Gongxh0901/kunpolibrary/blob/main/docs/EC.md) +4. [网络模块](https://github.com/Gongxh0901/kunpolibrary/blob/main/docs/HTTP.md) +5. [四叉树](https://github.com/Gongxh0901/kunpolibrary/blob/main/docs/QuadTree.md) +6. [行为树](https://github.com/Gongxh0901/kunpolibrary/blob/main/docs/BehaviorTree.md) +7. [资源管理](https://github.com/Gongxh0901/kunpolibrary/blob/main/docs/Asset.md) +8. [条件显示节点 (一般用于UI上的红点)](https://github.com/Gongxh0901/kunpolibrary/blob/main/docs/Condition.md) +9. [全局事件](https://github.com/Gongxh0901/kunpolibrary/blob/main/docs/Event.md) +10. [全局计时器](https://github.com/Gongxh0901/kunpolibrary/blob/main/docs/Timer.md) +11. [平台工具](https://github.com/Gongxh0901/kunpolibrary/blob/main/docs/Platform.md) +12. [屏幕尺寸](https://github.com/Gongxh0901/kunpolibrary/blob/main/docs/Screen.md) +13. [小工具](https://github.com/Gongxh0901/kunpolibrary/blob/main/docs/Tool.md) ## 类型支持 @@ -1625,8 +42,11 @@ gongxh * 邮箱: gong.xinhai@163.com ## 仓库 +[kunpocc gitree地址](https://gitee.com/gongxinhai/kunpolibrary) + [kunpocc github地址](https://github.com/Gongxh0901/kunpolibrary) + [github demo地址](https://github.com/Gongxh0901/KunpoDemo) [gitee demo地址](https://gitee.com/gongxinhai/kunpo-demo) diff --git a/docs/Asset.md b/docs/Asset.md new file mode 100644 index 0000000..17e0dbc --- /dev/null +++ b/docs/Asset.md @@ -0,0 +1,89 @@ +## 资源加载 +> !!! 注意:资源加载多次和一次效果一样 + +### 特点 + * 可通过路径或者uuid获取资源 + * 只适合手动管理资源,单无论加载多少次,卸载一次后删除 + +### 使用 +```typescript + let paths: kunpo.IAssetConfig[] = [ + { path: "ui/manual", type: cc.Asset }, + { path: "prefab", type: cc.Prefab }, + { path: "icon", type: cc.SpriteFrame }, + { path: "texture/6101/spriteFrame", type: cc.SpriteFrame, isFile: true }, + { path: "pet", type: cc.SpriteFrame, bundle: "bundle_res" }, + ]; + let loader = new kunpo.AssetLoader("load"); + loader.start({ + configs: paths, + complete: () => { + console.log("加载完成"); + }, + fail: (msg: string, err: Error) => { + console.log("加载失败", msg, err); + }, + progress: (percent: number) => { + console.log("加载进度", percent); + } + }); +``` + +### 接口 +#### *资源加载器* + +```typescript +interface IAssetConfig { + /** 资源类型 */ + type: typeof Asset; + /** 资源路径 */ + path: string; + /** 是否是单个文件 默认是文件夹 */ + isFile?: boolean; + /** 资源包名 默认 resources */ + bundle?: string; +} + +/** + * 开始加载资源 + * @param {IAssetConfig[]} res.configs 资源配置 + * @param {number} res.parallel 并行加载数量 默认 10 + * @param {number} res.retry 失败重试次数 默认 3 + * @param {Function} res.complete 加载完成回调 + * @param {Function} res.progress 加载进度回调 + * @param {Function} res.fail 加载失败回调 + */ +public start(res: { configs: IAssetConfig[], parallel?: number, retry?: number, complete: () => void, fail: (msg: string, err: Error) => void, progress?: (percent: number) => void }): void + +/** 重试 重新加载失败的资源 */ +public retry(): void +``` + +#### *资源池* + +```typescript +/** 资源是否已加载 */ +public static has(path: string, bundlename: string = "resources"): boolean + +/** 获取资源 */ +public static get(path: string, bundlename: string = "resources"): T + +/** 按 uuid 判断资源是否已加载 */ +public static hasUUID(uuid: string): boolean + +/** 按 uuid 获取资源 */ +public static getByUUID(uuid: string): T + +/** 按资源路径释放资源 */ +public static releasePath(path: string, bundlename: string = "resources"): void + +/** 按 bundle 和 文件夹释放资源 */ +public static async releaseDir(dir: string, bundlename: string = "resources", asset: typeof Asset): Promise + +/** 按 uuid 释放资源 */ +public static releaseUUID(uuid: string): void + +/** 释放所有加载的资源 */ +public static releaseAll(): void +``` + diff --git a/docs/Basic.md b/docs/Basic.md new file mode 100644 index 0000000..c00483e --- /dev/null +++ b/docs/Basic.md @@ -0,0 +1,28 @@ +## 项目配置 + +1. 新建项目,创建首场景文件 + +2. 编写入口脚本 + + ```typescript + import { _decorator } from "cc"; + /** 引入kunpocc入口 */ + import { CocosEntry, log } from "kunpocc"; + + const { ccclass, property, menu } = _decorator; + @ccclass("GameEntry") + @menu("kunpo/GameEntry") + export class GameEntry extends CocosEntry { + @property(cc.Node) + private root: cc.Node = null; + onInit(): void { + log("GameEntry onInit"); + } + } + ``` + +3. 创建入口节点`GameEntry`,创建UI模块节点 `UI` 、UI容器节点`window`, 并关联对应的脚本 + + ![image-20250213102309047](https://gitee.com/gongxinhai/public-image/raw/master/image-20250213102309047.png) + +4. 配置完毕 \ No newline at end of file diff --git a/docs/BehaviorTree.md b/docs/BehaviorTree.md new file mode 100644 index 0000000..1b15e04 --- /dev/null +++ b/docs/BehaviorTree.md @@ -0,0 +1,141 @@ +## 行为树 + +> 行为树是一种强大的 AI 决策系统,用于实现复杂的游戏 AI 行为。 + +#### 基本概念 + +1. 节点状态 +```typescript +enum Status { + SUCCESS, // 成功 + FAILURE, // 失败 + RUNNING // 运行中 +} +``` + +2. 节点类型 +- **动作节点 (Action)**:执行具体行为的叶子节点 +- **组合节点 (Composite)**:控制子节点执行顺序的节点 +- **条件节点 (Condition)**:判断条件的节点 +- **装饰节点 (Decorator)**:修饰其他节点行为的节点 + +#### 使用示例 + +```typescript +import { + BehaviorTree, + Sequence, + Selector, + Parallel, + Success, + Failure, + WaitTime, + Agent, + Blackboard +} from 'kunpocc'; + +// 1. 创建行为树 +const tree = new BehaviorTree( + new Sequence( // 顺序节点:按顺序执行所有子节点 + new WaitTime(2), // 等待2秒 + new Selector( // 选择节点:选择一个可执行的子节点 + new Success(() => { + console.log("执行成功动作"); + }), + new Failure(() => { + console.log("执行失败动作"); + }) + ) + ) +); + +// 2. 创建代理和黑板 +const agent = new Agent(); // AI代理 +const blackboard = new Blackboard(); // 共享数据黑板 + +// 3. 执行行为树 +tree.tick(agent, blackboard); +``` + +#### 常用节点 + +1. 组合节点 + + ```typescript + // 顺序节点:按顺序执行所有子节点,直到遇到失败或运行中的节点 + new Sequence(childNode1, childNode2, childNode3); + + // 选择节点:选择第一个成功或运行中的子节点 + new Selector(childNode1, childNode2, childNode3); + + // 并行节点:同时执行所有子节点 + new Parallel(childNode1, childNode2, childNode3); + + // 记忆顺序节点:记住上次执行的位置 + new MemSequence(childNode1, childNode2, childNode3); + + // 记忆选择节点:记住上次执行的位置 + new MemSelector(childNode1, childNode2, childNode3); + + // 随机选择节点:随机选择一个子节点执行 + new RandomSelector(childNode1, childNode2, childNode3); + ``` + +2. 动作节点 + + ```typescript + // 成功节点 + new Success(() => { + // 执行动作 + }); + + // 失败节点 + new Failure(() => { + // 执行动作 + }); + + // 运行中节点 + new Running(() => { + // 持续执行的动作 + }); + + // 等待节点 + new WaitTime(2); // 等待2秒 + new WaitTicks(5); // 等待5个tick + ``` + +3. 使用黑板共享数据 + + ```typescript + // 在节点中使用黑板 + class CustomAction extends Action { + tick(ticker: Ticker): Status { + // 获取数据 + const data = ticker.blackboard.get("key"); + + // 设置数据 + ticker.blackboard.set("key", "value"); + + return Status.SUCCESS; + } + } + ``` + + +#### 注意事项 + +1. 节点状态说明: + - `SUCCESS`:节点执行成功 + - `FAILURE`:节点执行失败 + - `RUNNING`:节点正在执行中 +2. 组合节点特性: + - `Sequence`:所有子节点返回 SUCCESS 才返回 SUCCESS + - `Selector`:任一子节点返回 SUCCESS 就返回 SUCCESS + - `Parallel`:并行执行所有子节点 + - `MemSequence/MemSelector`:会记住上次执行位置 +3. 性能优化: + - 使用黑板共享数据,避免重复计算 + - 合理使用记忆节点,减少重复执行 + - 控制行为树的深度,避免过于复杂 + + diff --git a/docs/Condition.md b/docs/Condition.md new file mode 100644 index 0000000..38df5b1 --- /dev/null +++ b/docs/Condition.md @@ -0,0 +1,47 @@ +## 条件显示节点 + +### 特点 + * 用于在游戏中显示或隐藏特定UI元素。 + * 一般用于红点提示 + * 主要是解耦用,条件单独实现,节点关联单条件或者多个条件 + + + +### 使用 + +#### *定义条件* + +```typescript +// 定义条件类型枚举 +enum ConditionType { + condition1, + condition2, + condition3, +} + +// 定义条件 +@conditionClass(ConditionType.condition1) +export class Condition1 extends kunpo.ConditionBase { + protected onInit(): void { + // 监听条件发生变化, 则调用一次 this.tryUpdate(); + kunpo.GlobalEvent.add("condition1", () => { + this.tryUpdate(); + }, this); + } + + protected evaluate(): boolean { + //TODO:: 根据条件数据,返回true or false + return true; + } +} +``` + +#### *节点关联条件* + +```typescript +/** 任意一个满足 显示节点 */ +new kunpo.ConditionAnyNode(fgui.GObject, ConditionType.condition1, ConditionType.condition2); + +/** 所有条件都满足 显示节点 */ +new kunpo.ConditionAllNode(fgui.GObject, ConditionType.Condition1, ConditionType.Condition2, ConditionType.Condition3); +``` \ No newline at end of file diff --git a/docs/EC.md b/docs/EC.md new file mode 100644 index 0000000..3de72d4 --- /dev/null +++ b/docs/EC.md @@ -0,0 +1,520 @@ +## 实体组件模块 +> 实体组件系统是一种用于游戏开发的架构模式,它将游戏对象(实体)的数据(组件)和行为分离。 + +### 特点 + * 不同实体上的组件更新顺序管理(`只根据注册的组件更新顺序更新,跟实体无关`) + * 灵活的EC装饰器 (配合插件 `kunpo-ec` 使用,配置实体组件信息,一键导出) + * 支持多世界(多战斗场景,互不影响) + * 区分数据组件和逻辑组件,只更新逻辑组件 + +### 使用 + +#### *creator插件`kunpo-ec`* +> `kunpo-cc`可以方便创建、配置、导出实体,操作界面如下图: + +![image-20250213111622050](https://gitee.com/gongxinhai/public-image/raw/master/image-20250213111622050.png) + +#### *使用* + +1. 组件类型声明 + + ```typescript + /** + * @Author: gongxh + * @Date: 2025-01-23 + * @Description: 组件枚举 + */ + + import { cc } from "../header"; + + /** 数据组件类型 */ + enum DataComponentType { + Health, + Transform, + RootNode, + LimitMove, + /** 渲染组件 (多个) */ + Render, + } + + /** 逻辑组件类型 (组件更新数据从上到下) */ + export enum SystemComponentType { + Move = 100000, + ScreenRebound, + + /** 位置更新系统 */ + PositionUpdateSystem = 120000, + } + + export const ComponentType = { + ...DataComponentType, + ...SystemComponentType + }; + export type ComponentType = DataComponentType | SystemComponentType; + + /** 自定义组件更新顺序列表 */ + export const componentUpdateOrderList = cc.Enum.getList(cc.Enum(SystemComponentType)).map(item => item.value).sort((a, b) => a - b); + ``` + +2. 编写组件脚本 + + ```typescript + import { AnimationClip, Asset, AudioClip, Color, Enum, JsonAsset, ParticleAsset, Prefab, Size, Skeleton, SpriteFrame, Vec2, Vec3 } from "cc"; + import { _ecdecorator, Component } from "kunpocc"; + import { ComponentType } from "../../ComponentTypes"; + const { ecclass, ecprop } = _ecdecorator; + + enum HealthType { + HP = 1, + Max = 2, + Current = 3 + } + + // 注册组件 (必须) + @ecclass("Health", ComponentType.Health, { describe: "血量组件" }) + export class Health extends Component { + // 注册组件属性 (可选: 使用kunpo-ec插件则必须注册) + @ecprop({ type: "entity", defaultValue: "", displayName: "实体", tips: "实体" }) + private testentity: string = ""; + + @ecprop({ type: "array", format: "entity", displayName: "实体数组", tips: "实体数组" }) + private testentityarray: string[] = []; + + @ecprop({ type: 'int', defaultValue: 0, displayName: "血量", tips: "当前血量提示" }) + private hp: number = 0; + + @ecprop({ type: 'float', defaultValue: 0, displayName: "最大血量", tips: "最大血量提示" }) + private maxHp: number = 0; + + @ecprop({ type: 'string', defaultValue: "", displayName: "字符串", tips: "字符串提示" }) + private string: string = ""; + + @ecprop({ type: 'boolean', defaultValue: false, displayName: "布尔值", tips: "布尔值提示" }) + private bool: boolean = true; + + @ecprop({ type: "enum", format: Enum(HealthType), defaultValue: HealthType.Current, displayName: "枚举", tips: "枚举提示" }) + private hpeunm: HealthType = HealthType.Current; + + @ecprop({ type: "spriteframe", displayName: "精灵帧" }) + private spriteFrame: SpriteFrame; + + @ecprop({ type: "asset", displayName: "资源" }) + private asset: Asset; + + @ecprop({ type: "prefab", displayName: "预制体" }) + private prefab: Prefab; + + @ecprop({ type: "skeleton", displayName: "骨骼动画" }) + private skeleton: Skeleton; + + @ecprop({ type: "particle", displayName: "粒子" }) + private particle: ParticleAsset; + + @ecprop({ type: "animation", displayName: "动画" }) + private animation: AnimationClip; + + @ecprop({ type: "audio", displayName: "音频" }) + private audio: AudioClip; + + @ecprop({ type: "jsonAsset", displayName: "json资源" }) + private jsonAsset: JsonAsset; + + @ecprop({ + type: "object", format: { + hp1: { + type: "object", + format: { + hp: "int", + max: "int" + } + }, + hp2: { + type: "object", + format: { + hp: "int", + max: "int" + } + }, + }, + }) + private obj: { hp1: { hp: number, max: number }, hp2: { hp: number, max: number } }; + + @ecprop({ + type: "array", format: "int", + }) + private arr: number[]; + + @ecprop({ + type: "array", format: { type: "object", format: { hp: "int", max: "int" } } + }) + private arrobj: { hp: number, max: number }[]; + + @ecprop({ type: "vec2", displayName: "向量2" }) + private vec2: Vec2; + + @ecprop({ type: "vec3", displayName: "向量3" }) + private vec3: Vec3; + + @ecprop({ type: "color", defaultValue: Color.RED, displayName: "颜色" }) + private color: Color; + + @ecprop({ type: "size", displayName: "尺寸" }) + private size: Size; + + protected onAdd(): void { + // 设置组件是否更新,只有需要更新的组件才设置 + this.needUpdate = true; + } + + protected onEnter(): void { + // 可在此获取同实体上的其他组件 + let transform = this.getComponent(ComponentType.Transform); + /** 获取单例组件 */ + let signleton = this.entity.entityManager.getSingleton(ComponentType.XXXX); + } + + protected onRemove(): void { + // 清理组件数据 + } + } + ``` + +3. 创建ec世界,并设置更新 + + ```typescript + /** + * @Author: Gongxh + * @Date: 2025-01-16 + * @Description: 战斗界面 + */ + + import { ECManager } from "kunpocc"; + import { componentUpdateOrderList } from "../../ec/ComponentTypes"; + import { cc, fgui, kunpo } from "../../header"; + const { uiclass, uiprop, uiclick } = kunpo._uidecorator; + + @uiclass("Window", "Game", "GameWindow") + export class GameWindow extends kunpo.Window { + @uiprop container: fgui.GComponent; + public onInit() { + console.log("GameWindow onInit"); + this.adapterType = kunpo.AdapterType.Full; + this.type = kunpo.WindowType.CloseAll; + this.bgAlpha = 0; + } + + protected onShow() { + console.log("GameWindow onShow"); + /** 创建一个ec世界的节点 */ + let node = new cc.Node(); + this.container.node.addChild(node); + + /** + * 创建一个ec世界 + * 参数1: 世界名称 + * 参数2: 世界节点 + * 参数3: 组件更新顺序列表 + * 参数4: 实体池的最大缓存数量,多余的不会被缓存,根据需要调整 + * 参数5: 预创建的实体数量,根据需要调整 + */ + kunpo.log("需要更新的组件", componentUpdateOrderList); + ECManager.createECWorld("world", node, componentUpdateOrderList, 100, 10); + } + + protected onClose() { + /** 退出游戏时 销毁ec世界 */ + ECManager.destroyECWorld("world"); + } + + @uiclick + private onBack(): void { + kunpo.WindowManager.showWindow("HomeWindow"); + } + + @uiclick + private onCreateEntity(): void { + /** 创建一个实体 */ + ECManager.createEntity("world", "entity1"); + } + + protected onUpdate(dt: number): void { + /** 更新ec世界 */ + ECManager.getECWorld("world").update(dt); + } + } + ``` + +#### 重点接口 + +注:详细说明查看声明文件 `kunpocc.d.ts` + +1. 总管理器 `ECManager` + + ```typescript + /**注册所有组件 如果GameEntry因分包导致,组件的代码注册晚于CocosEntry的onInit函数, 则需要在合适的时机手动调用此方法*/ + public static registerComponents(): void + + /** + * 创建EC世界 创建EC世界前必须先注册组件 + * @param {string} worldName 名称 + * @param {Node} node 世界节点 + * @param {number[]} componentUpdateOrderList 组件更新顺序列表 (只传需要更新的组件列表) + * @param {number} [maxCapacityInPool=128] 实体池最大容量,多余的实体不会缓存 + * @param {number} [preloadEntityCount=32] 预加载Entity数量 + */ + public static createECWorld(worldName: string, node: Node, componentUpdateOrderList: number[], maxCapacityInPool = 128, preloadEntityCount = 32): EntityManager + + /** 获取EC世界 */ + public static getECWorld(worldName: string): EntityManager + + /** 获取EC世界节点 */ + public static getECWorldNode(worldName: string): Node + + /** 销毁EC世界 */ + public static destroyECWorld(worldName: string): void + + /** + * 注册配置表中的实体信息 + * 如果在GameEntry中配置了ecConfig,则此方法会自动调用 + * @param config 实体配置信息,格式为 {实体名: {组件名: 组件数据}} + */ + public static registerEntityConfig(config: { [entityName: string]: IEntityConfig }): void + + /** + * 添加实体信息 (如果已经存在, 则数据组合) + * 如果存在编辑器编辑不了的数据 用来给编辑器导出的实体信息 添加扩展数据 + * @param name 实体名 + * @param info 实体信息 + */ + public static addEntityInfo(name: string, info: IEntityConfig): void + + /** 获取实体配置信息 */ + public static getEntityInfo(name: string): Record + + /** + * 创建实体 + * @param worldName 实体管理器名称 + * @param name 实体名字 + * @returns {kunpo.Entity} 实体 + */ + public static createEntity(worldName: string, name: string): Entity + + /** + * 销毁实体 + * @param worldName 世界名称 + * @param entity 实体 + */ + public static destroyEntity(worldName: string, entity: Entity): void + + /** + * 通过实体ID销毁实体 + * @param worldName 世界名称 + * @param entityId 实体ID + */ + public static destroyEntityById(worldName: string, entityId: number): void + ``` + +2. 实体管理器 (创建的world)`EntityManager ` + + ```typescript + /** + * 通过实体ID获取实体 + * @param {EntityId} entityId 实体Id + * @returns {(Entity | null)} 实体 + */ + public getEntity(entityId: EntityId): Entity | null + + /** + * 获取指定标签的实体 + * @param {number} tag 标签 + * @returns {Entity[]} 返回的实体池 + */ + public getEntitiesByTag(tag: number): Entity[] + + /** + * 根据实体ID判断实体是否存在 + * @param {EntityId} entityId 实体Id + * @returns {boolean} + */ + public exists(entityId: EntityId): boolean + + /** 添加单例组件 */ + public addSingleton(component: Component): void + + /** 获取单例组件 */ + public getSingleton(componentType: number): T + + /** 删除单例组件 */ + public removeSingleton(componentType: number): void + + /** 是否存在对应的单例组件 */ + public hasSingleton(componentType: number): boolean + + /** 激活单例组件 */ + public activeSingleton(): void + + + /** 更新 需要外部调用 */ + public update(dt: number): void + ``` + +3. 实体 `Entity` + + ```typescript + /** 实体名称 */ + public name: string; + + /** 实体ID */ + public id: EntityId; + + /** 实体标识 */ + public tags: Set; + + /** 实体状态 */ + public states: Map; + + /** 是否被激活 (添加到实体管理器时激活) */ + public active: boolean = false; + + /** 所属实体管理器 (实体创建后直接赋值) */ + public entityManager: EntityManager; + + /** 所有组件 */ + public readonly components: Map = new Map(); + + /** 添加标签 标签除了表示Entity,还可以通过EntityManager获取指定标签的Entity */ + public addTag(...tag: number[]): void + + /** 删除标签 */ + public removeTag(tag: number): void + + /** 是否包含标签 */ + public hasTag(...tag: number[]): boolean + + /** 获取组件 */ + public getComponent(componentType: number): T + + /** 添加组件 */ + public addComponent(component: Component): void + + /** 删除组件 */ + public removeComponent(componentType: number): void + + /** 删除所有组件 */ + public removeAllComponents(): void + + /** + * 是否包含组件 + * @param {number} componentType 组件类型 + */ + public hasComponent(componentType: number): boolean + + /** 销毁自己 */ + public destroy(): void { + this.entityManager.destroyEntityById(this.id); + } + + /** + * 添加监听 + * @param eventName 监听的消息名 + * @param callback 回调 + * @param entityId 实体ID + * @param once 是否单次监听 + */ + public addEvent(eventName: string, callback: (...args: any[]) => void, once: boolean = false): void + + /** + * 发送消息 + * @param eventName 消息名 + * @param entityId 实体ID + * @param args 发送参数 + */ + public sendListener(eventName: string, ...args: any[]): void + + /** 删除监听 */ + public removeListener(eventName: string, callback?: (...args: any[]) => void): void + + /** + * 添加状态 + * 状态采用计数方式,对状态处理时需要保证addState和removeState成对存在 + * @param {number} state 状态类型 + */ + public addState(state: number): void + + /** + * 删除状态 + * @param {number} state 状态类型 + * @returns {boolean} 如果计数为0或状态不存在,则返回true + */ + public removeState(state: number): boolean + + /** 是否包含指定状态 */ + public hasState(state: number): boolean + + /** 清除状态 */ + public clearState(state: number): void + + /** 清除所有状态 */ + public clearAllStates(): void + ``` + + + +4. 组件 `Component` + + ```typescript + /** 组件名 */ + public name: string; + + /** 组件类型 */ + public type: number; + + /** 是否需要更新 */ + public needUpdate: boolean; + + /** 所属实体 */ + public entity: Entity; + + /** 所属组件管理器 */ + public componentManager: ComponentManager; + + /** + * 获取同实体上的组件 + * @param {number} componentType 组件类型 + */ + public getComponent(componentType: number): T + + /** 删除自己 */ + public destroySelf(): void + + /** + * 生命周期函数 + * 被添加到实体 对应onDestroy + */ + protected onAdd(): void + + /** + * 生命周期函数 + * 组件被销毁 对应onAdd + */ + protected onDestroy(): void + + /** + * 生命周期函数 + * 可在此方法获取实体其他组件 + */ + protected abstract onEnter(): void; + + /** + * 生命周期函数 + * 从实体中删除前执行的函数 在此函数中清理初始化的数据 + */ + protected abstract onRemove(): void; + + /** + * 更新函数 + */ + protected onUpdate(dt: number): void + ``` + + \ No newline at end of file diff --git a/docs/Event.md b/docs/Event.md new file mode 100644 index 0000000..4f04b04 --- /dev/null +++ b/docs/Event.md @@ -0,0 +1,32 @@ +## 全局事件系统 + +### 使用 + +```typescript +import { GlobalEvent } from 'kunpocc'; + +// 添加事件监听 +GlobalEvent.add('eventName', (arg1, arg2) => { + console.log('事件触发:', arg1, arg2); +}, this); + +// 添加一次性事件监听 +GlobalEvent.addOnce('oneTimeEvent', (data) => { + console.log('一次性事件触发:', data); +}, this); + +// 发送事件 +GlobalEvent.send('eventName', 'arg1', 'arg2'); + +// 发送事件到指定目标 +GlobalEvent.sendToTarget('eventName', target, 'arg1', 'arg2'); + +// 移除事件监听 +GlobalEvent.remove('eventName', callback, this); + +// 移除指定目标的所有事件监听 +GlobalEvent.removeByTarget(this); + +// 移除指定事件名和目标的事件监听 +GlobalEvent.removeByNameAndTarget('eventName', this); +``` diff --git a/docs/HTTP.md b/docs/HTTP.md new file mode 100644 index 0000000..faf12b9 --- /dev/null +++ b/docs/HTTP.md @@ -0,0 +1,94 @@ +## Http模块 + +### 特点 + - 封装 XMLHttpRequest + - 完整的请求响应接口 + - 独立使用简单,一行代码发送一个请求 + - 大型项目,管理简单 + +### 使用 + +```typescript +import { HttpManager, IHttpEvent, HttpResponseType } from 'kunpocc'; + +// 1. 使用回调方式处理响应 +const event: IHttpEvent = { + name: "login", + onComplete: (response) => { + console.log('请求成功:', response.data); + }, + onError: (response) => { + console.log('请求失败:', response.error); + } +}; + +// POST 请求 +HttpManager.post( + "https://api.example.com/login", + { username: "test", password: "123456" }, + "json", // 响应类型:'json' | 'text' | 'arraybuffer' + event, + ["Content-Type", "application/json"], // 请求头 + 5 // 超时时间(秒) +); + +// GET 请求 +HttpManager.get( + "https://api.example.com/users", + { id: 1 }, + "json", + event +); + +// 2. 使用全局事件方式处理响应 +GlobalEvent.add(HttpManager.HttpEvent, (result, response) => { + if (result === "succeed") { + console.log('请求成功:', response.data); + } else { + console.log('请求失败:', response.error); + } +}, this); + +// 发送请求(不传入 event 参数) +HttpManager.post("https://api.example.com/data", { /* data */ }); +``` + +#### *请求方法* +- `post(url, data, responseType?, event?, headers?, timeout?)` +- `get(url, data, responseType?, event?, headers?, timeout?)` +- `put(url, data, responseType?, event?, headers?, timeout?)` +- `head(url, data, responseType?, event?, headers?, timeout?)` + +#### *参数说明* +- `url`: 请求地址 +- `data`: 请求数据 +- `responseType`: 响应类型(可选,默认 'json') + - `'json'`: JSON 格式 + - `'text'`: 文本格式 + - `'arraybuffer'`: 二进制数据 +- `event`: 请求事件回调(可选) +- `headers`: 请求头(可选) +- `timeout`: 超时时间,单位秒(可选,0表示不超时) + +#### *响应处理* +1. 回调方式(通过 IHttpEvent): +```typescript +const event: IHttpEvent = { + name: "自定义名称", + data?: "自定义数据", // 可选 + onComplete: (response) => { + // 成功回调 + }, + onError: (response) => { + // 失败回调 + } +}; +``` + +2. 全局事件方式: +```typescript +GlobalEvent.add(HttpManager.HttpEvent, (result, response) => { + // result: "succeed" | "fail" + // response: IHttpResponse +}, this); +``` diff --git a/docs/Platform.md b/docs/Platform.md new file mode 100644 index 0000000..ccdb38c --- /dev/null +++ b/docs/Platform.md @@ -0,0 +1,117 @@ +## 平台 + + +> Platform 类提供了游戏运行平台的相关信息和判断方法。 + +#### *平台类型* + +```typescript +import { Platform, PlatformType } from 'kunpocc'; + +// 平台类型枚举 +enum PlatformType { + Android = 1, // 安卓 + IOS, // iOS + HarmonyOS, // 鸿蒙 + WX, // 微信小游戏 + Alipay, // 支付宝小游戏 + Bytedance, // 字节小游戏 + HuaweiQuick, // 华为快游戏 + Browser // 浏览器 +} + +// 获取当前平台类型 +const currentPlatform = Platform.platform; +``` + +#### *平台判断* + +```typescript +import { Platform } from 'kunpocc'; + +// 原生平台判断 +if (Platform.isNative) { + console.log('当前是原生平台'); +} + +// 移动平台判断 +if (Platform.isMobile) { + console.log('当前是移动平台'); +} + +// 原生移动平台判断 +if (Platform.isNativeMobile) { + console.log('当前是原生移动平台'); +} + +// 具体平台判断 +if (Platform.isAndroid) { + console.log('当前是安卓平台'); +} + +if (Platform.isIOS) { + console.log('当前是iOS平台'); +} + +if (Platform.isHarmonyOS) { + console.log('当前是鸿蒙系统'); +} + +// 小游戏平台判断 +if (Platform.isWX) { + console.log('当前是微信小游戏'); +} + +if (Platform.isAlipay) { + console.log('当前是支付宝小游戏'); +} + +if (Platform.isBytedance) { + console.log('当前是字节小游戏'); +} + +if (Platform.isHuaweiQuick) { + console.log('当前是华为快游戏'); +} + +// 浏览器判断 +if (Platform.isBrowser) { + console.log('当前是浏览器环境'); +} +``` + +#### *使用示例* + +```typescript +import { Platform, PlatformType } from 'kunpocc'; + +// 根据平台类型执行不同逻辑 +switch (Platform.platform) { + case PlatformType.Android: + // 安卓平台特定逻辑 + break; + case PlatformType.IOS: + // iOS平台特定逻辑 + break; + case PlatformType.WX: + // 微信小游戏特定逻辑 + break; + default: + // 其他平台逻辑 + break; +} + +// 针对不同平台进行适配 +if (Platform.isNativeMobile) { + // 原生移动平台的处理 + if (Platform.isAndroid) { + // 安卓特有功能 + } else if (Platform.isIOS) { + // iOS特有功能 + } +} else if (Platform.isWX || Platform.isAlipay || Platform.isBytedance) { + // 小游戏平台的处理 +} else { + // 浏览器平台的处理 +} +``` \ No newline at end of file diff --git a/docs/QuadTree.md b/docs/QuadTree.md new file mode 100644 index 0000000..5a7118c --- /dev/null +++ b/docs/QuadTree.md @@ -0,0 +1,132 @@ +## 四叉树 +> 四叉树是一种通过空间划分来进行高效碰撞查询的数据结构。 + +#### 基本概念 + +1. 形状类型 + + ```typescript + import { QuadTree, Box, Circle, Polygon } from 'kunpocc'; + + // 1. 矩形 + const box = new Box(x, y, width, height, tag); + + // 2. 圆形 + const circle = new Circle(x, y, radius, tag); + + // 3. 多边形 + const points = [v2(x1, y1), v2(x2, y2), v2(x3, y3)]; + const polygon = new Polygon(points, tag); + ``` + +2. 配置参数 + + ```typescript + // 四叉树配置 + const QTConfig = { + MAX_SHAPES: 12, // 每个节点最大形状数量 + MAX_LEVELS: 5, // 最大深度 + } + ``` + + +#### 使用示例 + +1. 创建和初始化 + + ```typescript + import { QuadTree, Box, rect } from 'kunpocc'; + + // 创建四叉树(参数:区域范围,层级,绘制组件) + const bounds = rect(0, 0, 800, 600); // x, y, width, height + const quadTree = new QuadTree(bounds); + + // 添加形状 + const player = new Box(100, 100, 50, 50, 1); // 玩家碰撞体,tag=1 + const enemy = new Circle(200, 200, 25, 2); // 敌人碰撞体,tag=2 + quadTree.insert(player); + quadTree.insert(enemy); + ``` + +2. 碰撞检测 + + ```typescript + // 检测指定形状与特定标签的碰撞 + const collisions = quadTree.collide(player, 2); // 检测玩家与 tag=2 的形状碰撞 + if (collisions.length > 0) { + console.log('发生碰撞!'); + for (const target of collisions) { + // 处理碰撞逻辑 + } + } + ``` + +3. 动态更新 + + ```typescript + // 在游戏循环中更新四叉树 + function update() { + // 更新形状位置 + player.position = v2(newX, newY); + enemy.position = v2(newX, newY); + + // 更新四叉树 + quadTree.update(); + + // 检测碰撞 + const collisions = quadTree.collide(player, 2); + } + ``` + +4. 清理 + + ```typescript + // 清理四叉树 + quadTree.clear(); + ``` + + +#### 形状操作 + +1. 位置和缩放 + + ```typescript + // 设置位置 + shape.position = v2(x, y); + + // 设置缩放 + shape.scale = 1.5; + + // 获取包围盒 + const boundingBox = shape.getBoundingBox(); + ``` + +2. 特定形状操作 + + ```typescript + // 矩形重置 + box.resetPoints(x, y, width, height); + + // 圆形半径 + circle.radius = newRadius; + + // 多边形顶点 + polygon.points = newPoints; + ``` + + +#### 性能优化建议 + +1. 合理设置配置参数: + - `MAX_SHAPES`:较小的值会导致更频繁的分裂,较大的值会降低查询效率 + - `MAX_LEVELS`:控制树的最大深度,防止过度分割 + +2. 碰撞检测优化: + - 使用合适的标签系统,只检测需要的碰撞 + - 根据游戏需求选择合适的形状(圆形计算最快) + - 避免使用过于复杂的多边形 + +3. 更新策略: + - 仅在必要时更新四叉树 + - 对于静态物体,可以使用单独的四叉树 + - 动态物体频繁更新时,考虑使用更大的边界范围 \ No newline at end of file diff --git a/docs/Screen.md b/docs/Screen.md new file mode 100644 index 0000000..2f805ad --- /dev/null +++ b/docs/Screen.md @@ -0,0 +1,18 @@ +## 屏幕 + +```typescript +/** 屏幕宽度 */ +public static ScreenWidth: number; +/** 屏幕高度 */ +public static ScreenHeight: number; +/** 设计分辨率宽 */ +public static DesignWidth: number; +/** 设计分辨率高 */ +public static DesignHeight: number; +/** 安全区外一侧的高度 或 宽度 */ +public static SafeAreaHeight: number; +/** 安全区的宽度 */ +public static SafeWidth: number; +/** 安全区的高度 */ +public static SafeHeight: number; +``` \ No newline at end of file diff --git a/docs/Timer.md b/docs/Timer.md new file mode 100644 index 0000000..fe9bc2d --- /dev/null +++ b/docs/Timer.md @@ -0,0 +1,34 @@ +## 计时器 + +### 使用 + +```typescript +import { GlobalTimer } from 'kunpocc'; + +// 启动一次性定时器(2秒后执行) +const timerId1 = GlobalTimer.startTimer(() => { + console.log('2秒后执行一次'); +}, 2); + +// 启动循环定时器(每3秒执行一次,执行5次) +const timerId2 = GlobalTimer.startTimer(() => { + console.log('每3秒执行一次,总共执行5次'); +}, 3, 5); + +// 启动无限循环定时器(每1秒执行一次) +const timerId3 = GlobalTimer.startTimer(() => { + console.log('每1秒执行一次,无限循环'); +}, 1, -1); + +// 停止定时器 +GlobalTimer.stopTimer(timerId1); +GlobalTimer.stopTimer(timerId2); +GlobalTimer.stopTimer(timerId3); +``` + +注意事项: +- 定时器的时间间隔单位为秒 +- loop 参数说明: + - 0:执行一次 + - 正整数 n:执行 n 次 + - -1:无限循环 \ No newline at end of file diff --git a/docs/Tools.md b/docs/Tools.md new file mode 100644 index 0000000..93fee66 --- /dev/null +++ b/docs/Tools.md @@ -0,0 +1,98 @@ +## 工具 + + +#### 一、数学工具 (MathTool) + +```typescript +import { MathTool } from 'kunpocc'; + +// 1. 数值限制 +// 将数值限制在指定范围内 +const value = MathTool.clampf(75, 0, 100); // 返回75,因为在0-100范围内 +const value2 = MathTool.clampf(150, 0, 100); // 返回100,因为超出上限 +const value3 = MathTool.clampf(-50, 0, 100); // 返回0,因为低于下限 + +// 2. 随机数生成 +// 生成指定范围内的整数(包含边界值) +const randomInt = MathTool.rand(1, 10); // 返回1到10之间的整数 + +// 生成指定范围内的浮点数(包含最小值,不包含最大值) +const randomFloat = MathTool.randRange(0, 1); // 返回0到1之间的浮点数 + +// 3. 角度与弧度转换 +// 角度转弧度 +const radian = MathTool.rad(90); // 90度转换为弧度:约1.57 + +// 弧度转角度 +const degree = MathTool.deg(Math.PI); // π弧度转换为角度:180 + +// 4. 平滑过渡 +// 用于实现数值的平滑变化,常用于相机跟随、UI动画等 +const smoothValue = MathTool.smooth( + 0, // 起始值 + 100, // 目标值 + 0.16, // 已经过时间(秒) + 0.3 // 响应时间(秒) +); // 返回一个平滑过渡的中间值 +``` + +使用说明: + +1. `clampf(value: number, min: number, max: number): number` + - 将数值限制在指定范围内 + - 如果小于最小值,返回最小值 + - 如果大于最大值,返回最大值 + - 否则返回原值 + +2. `rand(min: number, max: number): number` + - 生成指定范围内的随机整数 + - 包含最小值和最大值 + - 常用于随机选择、随机掉落等场景 + +3. `randRange(min: number, max: number): number` + - 生成指定范围内的随机浮点数 + - 包含最小值,不包含最大值 + - 常用于需要精确浮点随机数的场景 + +4. `rad(angle: number): number` + - 将角度转换为弧度 + - 计算公式:angle * Math.PI / 180 + +5. `deg(radian: number): number` + - 将弧度转换为角度 + - 计算公式:radian * 180 / Math.PI + +6. `smooth(current: number, target: number, elapsedTime: number, responseTime: number): number` + - 计算平滑过渡的值 + - current: 当前值 + - target: 目标值 + - elapsedTime: 已经过时间(秒) + - responseTime: 响应时间(秒) + - 常用于实现平滑的相机移动、UI动画等 + +#### 二、MD5 + +```typescript +import { md5 } from 'kunpocc'; + +// 字符串 MD5 加密 +const hash = md5('Hello, World!'); +console.log(hash); // 输出32位MD5哈希值 + +// 注意: +// 1. 输入必须是字符串类型 +// 2. 不能传入 undefined 或 null +try { + md5(null); // 将抛出错误 +} catch (error) { + console.error('MD5输入不能为null或undefined'); +} +``` + +#### 三、数据结构 + - 二叉堆(`BinaryHeap` 最大、最小堆) + - 单向(`LinkedList`)、双向链表 (`DoublyLinkedList`) + - 栈(`Stack`) + + +#### 四、适配相关 `Adapter` (不需要关心) \ No newline at end of file diff --git a/docs/UI.md b/docs/UI.md new file mode 100644 index 0000000..ddbbc00 --- /dev/null +++ b/docs/UI.md @@ -0,0 +1,227 @@ +## UI模块 + +### 特点 + + * 基于FairyGUI, 查看[FairyGUI官方文档](https://www.fairygui.com/docs/editor) + + * 灵活的 UI 装饰器(配合插件 `kunpo-fgui` 使用,一键导出界面配置,省时省力省代码) + + * 控制窗口之间的相互关系(eg: 打开界面时,是隐藏/关闭前一个界面,还是隐藏/关闭所有界面) + + * 多窗口组管理 + + * 顶部显示金币钻石的资源栏(header),一次实现,多界面复用, + + * 支持不同界面使用不同 header + + +### 使用 + +#### *一、FairyGUI界面* +> ![image-20250213105921142](https://gitee.com/gongxinhai/public-image/raw/master/image-20250213105921142.png) + +#### *二、UI 装饰器使用* + +> 注:只有使用了装饰器的内容才能在 `kunpo-fgui` 插件中识别,`kunpo-fgui`插件操作界面如下图 + +> ![image-20250213110353385](https://gitee.com/gongxinhai/public-image/raw/master/image-20250213110353385.png) + + +1. 窗口装饰器 + + ```typescript + import { Window, _uidecorator } from 'kunpocc'; + const { uiclass, uiprop, uiclick } = _uidecorator; + + /** + * 窗口装饰器 + * @param 参数1: 窗口容器节点名字 + * @param 参数2: FairyGUI中的UI包名 + * @param 参数3: FairyGUI中的组件名 必须和 class 类同名 这里是 MyWindow + */ + @uiclass("Window", "UI包名", "MyWindow") + export class MyWindow extends Window { + // ... 窗口实现 + } + ``` + +2. Header 装饰器 + + ```typescript + import { WindowHeader, _uidecorator } from 'kunpocc'; + const { uiheader } = _uidecorator; + + /** + * 窗口顶部资源栏装饰器 + * @param 参数1: FairyGUI中的UI包名 + * @param 参数2: FairyGUI中的组件名 必须和 class 类同名 这里是 MyWindowHeader + */ + @uiheader("UI包名", "WindowHeader") + export class MyWindowHeader extends WindowHeader { + // ... Header 实现 + } + ``` + +3. UI组件装饰器 + + ```typescript + import { _uidecorator } from 'kunpocc'; + const { uicom, uiprop, uiclick } = _uidecorator; + + /** + * UI组件类装饰器 + * @param 参数1: FairyGUI中的UI包名 + * @param 参数2: FairyGUI中的组件名 必须和 class 类同名 这里是 MyComponent + */ + @uicom("Home", "MyComponent") + export class MyComponent { + // ... 组件实现 + } + ``` + +4. UI属性装饰器 + + ```typescript + import { Window, _uidecorator } from 'kunpocc'; + const { uiclass, uiprop, uiclick } = _uidecorator; + + @uiclass("Window", "Home", "MyWindow") + export class MyWindow extends Window { + // FairyGUI 组件属性装饰器 + @uiprop private btnConfirm: GButton; // 按钮组件 + @uiprop private txtTitle: GTextField; // 文本组件 + @uiprop private listItems: GList; // 列表组件 + } + ``` + +5. 点击事件装饰器 + + ```typescript + import { Window, _uidecorator } from 'kunpocc'; + const { uiclass, uiprop, uiclick } = _uidecorator; + + @uiclass("Window", "Home", "MyWindow") + export class MyWindow extends Window { + // 点击事件装饰器 + @uiclick + private onTouchEvent(event: cc.Event): void { + console.log('确认按钮被点击'); + } + } + ``` + + +#### *三、创建窗口* + +1. 新建窗口类 + + ```typescript + /** + * 窗口名必须和FairyGUI中的组件同名 + */ + import { Window, _uidecorator } from 'kunpocc'; + const { uiclass, uiprop, uiclick } = _uidecorator; + + @uiclass("Window", "UI包名", "MyWindow") + export class MyWindow extends Window { + protected onInit(): void { + // 初始化窗口 + } + + protected onShow(userdata?: any): void { + // 窗口显示时的逻辑 + } + + protected onClose(): void { + // 窗口关闭时的逻辑 + } + } + ``` + +2. 窗口生命周期 +- `onInit`: 窗口初始化时调用 +- `onShow`: 窗口显示时调用 +- `onClose`: 窗口关闭时调用 +- `onHide`: 窗口隐藏时调用 +- `onShowFromHide`: 窗口从隐藏状态恢复时调用 +- `onCover`: 窗口被覆盖时调用 +- `onRecover`: 窗口恢复时调用 +- `onEmptyAreaClick`: 点击窗口空白区域时调用 + +#### *四、窗口资源加载配置* +```typescript +interface IPackageConfig { + /** UI所在resources中的路径 */ + uiPath: string; + /** + * 手动管理资源的包 + * 1. 用于基础UI包, 提供一些最基础的组件,所有其他包都可能引用其中的内容 + * 2. 资源header所在的包 + * 3. 用于一些特殊场景, 比如需要和其他资源一起加载, 并且显示进度条的包 + */ + manualPackages: string[]; + /** + * 不推荐配置 只是提供一种特殊需求的实现方式 + * 窗口引用到其他包中的资源 需要的配置信息 + */ + linkPackages: { [windowName: string]: string[] }; + + /** + * 关闭界面后,需要立即释放资源的包名(建议尽量少) + * 一般不建议包进行频繁装载卸载,因为每次装载卸载必然是要消耗CPU时间(意味着耗电)和产生大量GC的。UI系统占用的内存是可以精确估算的,你可以按照包的使用频率设定哪些包是需要立即释放的。 + * 不包括手动管理的包 + */ + imReleasePackages: string[]; +} + +export interface IPackageConfigRes { + /** 配置信息 */ + config: IPackageConfig; + /** 显示加载等待窗 */ + showWaitWindow: () => void; + /** 隐藏加载等待窗 */ + hideWaitWindow: () => void; + /** 打开窗口时UI包加载失败 */ + fail: (windowName: string, errmsg: string, pkgs: string[]) => void; +} +``` + +#### *五、窗口管理接口* + ```typescript + export class WindowManager { + /** + * 配置UI包的一些信息 (可以不配置 完全手动管理资源) + */ + public static initPackageConfig(res: IPackageConfigRes): void; + + /** + * 异步打开一个窗口 (如果UI包的资源未加载, 会自动加载 配合 WindowManager.initPackageConfig一起使用) + */ + public static async showWindow(windowName: string, userdata?: any): Promise + + /** + * 打开一个窗口 (用于已加载过资源的窗口) + */ + public static showWindowIm(windowName: string, userdata?: any): void; + + /** + * 关闭窗口 + */ + public static closeWindow(windowName: string); + + /* + * 获取窗口实例 + */ + public static getWindow(windowName: string): T; + + /** + * 获取当前最顶层窗口 + */ + public static getTopWindow(): Window; + + /** + * 检查窗口是否存在 + */ + public static hasWindow(windowName: string): boolean; + } + ``` \ No newline at end of file diff --git a/package.json b/package.json index 5bd4637..24195c8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "kunpocc", - "version": "1.0.20", + "version": "1.0.22", "type": "module", "description": "基于creator3.0+的kunpocc库", "main": "./dist/kunpocc.cjs", diff --git a/src/kunpocc.ts b/src/kunpocc.ts index f34a519..7b71987 100644 --- a/src/kunpocc.ts +++ b/src/kunpocc.ts @@ -42,7 +42,7 @@ export { Ticker as BTTicker } from "./behaviortree/Ticker"; export { Window } from "./fgui/Window"; export { WindowHeader } from "./fgui/WindowHeader"; export { AdapterType, WindowType } from "./ui/header"; -export { IPackageConfig } from "./ui/IPackageConfig"; +export { IPackageConfigRes } from "./ui/IPackageConfig"; export { _uidecorator } from "./ui/UIDecorator"; export { WindowGroup } from "./ui/WindowGroup"; export { WindowHeaderInfo } from "./ui/WindowHeaderInfo"; diff --git a/src/ui/IPackageConfig.ts b/src/ui/IPackageConfig.ts index 5f07bbf..7c4121d 100644 --- a/src/ui/IPackageConfig.ts +++ b/src/ui/IPackageConfig.ts @@ -4,9 +4,7 @@ * @Description: 包配置格式 */ - - -export interface IPackageConfig { +interface IPackageConfig { /** UI所在resources中的路径 */ uiPath: string; /**