开源!! Creator3.8+ 游戏框架(涉及UI(FGUI)、实体组件、Http、行为树、四叉树、全局定时器、全局事件、资源管理、红点解决方案等内容)

嗯,赞同,用了一段时间积累了一些公共常用组件效率肯定有提升,但是这是放之四海而皆准的用啥工具都是这样,最后还有兼容性问题和没有跨引擎需求所以我最后放弃fgui了

嗯,最终还是适合自己的才是最好的

添加UI包资源自动加载,使用请参考demo

github demo地址
gitee demo地址

项目配置

  1. 新建项目,创建首场景文件

  2. 编写入口脚本

    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, 并关联对应的脚本

  4. 配置完毕

UI模块

特点

  • 基于FairyGUI, 查看FairyGUI官方文档

  • 灵活的 UI 装饰器(配合插件 kunpo-fgui 使用,一键导出界面配置,省时省力省代码)

  • 控制窗口之间的相互关系(eg: 打开界面时,是隐藏/关闭前一个界面,还是隐藏/关闭所有界面)

  • 多窗口组管理

  • 顶部显示金币钻石的资源栏(header),一次实现,多界面复用,

  • 支持不同界面使用不同 header

插件链接

kunpo-fgui

使用

一、FairyGUI界面

二、UI 装饰器使用

注:只有使用了装饰器的内容才能在 kunpo-fgui 插件中识别,kunpo-fgui插件操作界面如下图

  1. 窗口装饰器

    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 装饰器

    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组件装饰器

    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属性装饰器

    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. 点击事件装饰器

    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. 新建窗口类

    /**
    * 窗口名必须和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: 点击窗口空白区域时调用

四、窗口资源加载配置

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;
}

五、窗口管理接口

export class WindowManager {
  	/** 
  	 * 配置UI包的一些信息 (可以不配置 完全手动管理资源) 
  	 */
		public static initPackageConfig(res: IPackageConfigRes): void;
  
    /**
     * 异步打开一个窗口 (如果UI包的资源未加载, 会自动加载 配合 WindowManager.initPackageConfig一起使用)
     */
    public static async showWindow(windowName: string, userdata?: any): Promise<void>

    /**
     * 打开一个窗口 (用于已加载过资源的窗口)
     */
    public static showWindowIm(windowName: string, userdata?: any): void;

    /**
     * 关闭窗口
     */
    public static closeWindow(windowName: string);

    /* 
     * 获取窗口实例
     */
    public static getWindow<T extends Window>(windowName: string): T;

    /**
     * 获取当前最顶层窗口
     */
    public static getTopWindow(): Window;

    /**
     * 检查窗口是否存在
     */
    public static hasWindow(windowName: string): boolean;
}

实体组件模块

实体组件系统是一种用于游戏开发的架构模式,它将游戏对象(实体)的数据(组件)和行为分离。

特点

  • 不同实体上的组件更新顺序管理(只根据注册的组件更新顺序更新,跟实体无关
  • 灵活的EC装饰器 (配合插件 kunpo-ec 使用,配置实体组件信息,一键导出)
  • 支持多世界(多战斗场景,互不影响)
  • 区分数据组件和逻辑组件,只更新逻辑组件

使用

creator插件kunpo-ec

插件链接 kunpo-ec

kunpo-cc可以方便创建、配置、导出实体,操作界面如下图:

使用

  1. 组件类型声明

    /**
     * @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. 编写组件脚本

    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世界,并设置更新

    /**
     * @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

    /**注册所有组件 如果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<string, any>
    
    /**
     * 创建实体
     * @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

    /**
     * 通过实体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<T extends Component>(componentType: number): T
    
    /** 删除单例组件 */
    public removeSingleton(componentType: number): void
    
    /** 是否存在对应的单例组件 */
    public hasSingleton(componentType: number): boolean
    
    /** 激活单例组件 */
    public activeSingleton(): void
    
    
    /** 更新 需要外部调用 */
    public update(dt: number): void
    
  3. 实体 Entity

    /** 实体名称 */
    public name: string;
    
    /** 实体ID */
    public id: EntityId;
    
    /** 实体标识 */
    public tags: Set<number>;
    
    /** 实体状态 */
    public states: Map<number, number>;
    
    /** 是否被激活 (添加到实体管理器时激活) */
    public active: boolean = false;
    
    /** 所属实体管理器 (实体创建后直接赋值) */
    public entityManager: EntityManager;
    
    /** 所有组件 */
    public readonly components: Map<number, Component> = new Map();
    
    /** 添加标签 标签除了表示Entity,还可以通过EntityManager获取指定标签的Entity */
    public addTag(...tag: number[]): void
    
    /** 删除标签 */
    public removeTag(tag: number): void
    
    /** 是否包含标签 */
    public hasTag(...tag: number[]): boolean
    
    /** 获取组件 */
    public getComponent<T extends Component>(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

    /** 组件名 */
    public name: string;
    
    /** 组件类型 */
    public type: number;
    
    /** 是否需要更新 */
    public needUpdate: boolean;
    
    /** 所属实体 */
    public entity: Entity;
    
    /** 所属组件管理器 */
    public componentManager: ComponentManager;
    
    /**
     * 获取同实体上的组件
     * @param {number} componentType 组件类型
     */
    public getComponent<T extends Component>(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
    

Http模块

特点

  • 封装 XMLHttpRequest
  • 完整的请求响应接口
  • 独立使用简单,一行代码发送一个请求
  • 大型项目,管理简单

使用

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):
const event: IHttpEvent = {
    name: "自定义名称",
    data?: "自定义数据",  // 可选
    onComplete: (response) => {
        // 成功回调
    },
    onError: (response) => {
        // 失败回调
    }
};
  1. 全局事件方式:
GlobalEvent.add(HttpManager.HttpEvent, (result, response) => {
    // result: "succeed" | "fail"
    // response: IHttpResponse
}, this);

四叉树

四叉树是一种通过空间划分来进行高效碰撞查询的数据结构。

基本概念

  1. 形状类型

    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. 配置参数

    // 四叉树配置
    const QTConfig = {
        MAX_SHAPES: 12,  // 每个节点最大形状数量
        MAX_LEVELS: 5,   // 最大深度
    }
    

使用示例

  1. 创建和初始化

    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. 碰撞检测

    // 检测指定形状与特定标签的碰撞
    const collisions = quadTree.collide(player, 2);  // 检测玩家与 tag=2 的形状碰撞
    if (collisions.length > 0) {
        console.log('发生碰撞!');
        for (const target of collisions) {
            // 处理碰撞逻辑
        }
    }
    
  3. 动态更新

    // 在游戏循环中更新四叉树
    function update() {
        // 更新形状位置
        player.position = v2(newX, newY);
        enemy.position = v2(newX, newY);
        
        // 更新四叉树
        quadTree.update();
        
        // 检测碰撞
        const collisions = quadTree.collide(player, 2);
    }
    
  4. 清理

    // 清理四叉树
    quadTree.clear();
    

形状操作

  1. 位置和缩放

    // 设置位置
    shape.position = v2(x, y);
    
    // 设置缩放
    shape.scale = 1.5;
    
    // 获取包围盒
    const boundingBox = shape.getBoundingBox();
    
  2. 特定形状操作

    // 矩形重置
    box.resetPoints(x, y, width, height);
    
    // 圆形半径
    circle.radius = newRadius;
    
    // 多边形顶点
    polygon.points = newPoints;
    

性能优化建议

  1. 合理设置配置参数:

    • MAX_SHAPES:较小的值会导致更频繁的分裂,较大的值会降低查询效率
    • MAX_LEVELS:控制树的最大深度,防止过度分割
  2. 碰撞检测优化:

    • 使用合适的标签系统,只检测需要的碰撞
    • 根据游戏需求选择合适的形状(圆形计算最快)
    • 避免使用过于复杂的多边形
  3. 更新策略:

    • 仅在必要时更新四叉树
    • 对于静态物体,可以使用单独的四叉树
    • 动态物体频繁更新时,考虑使用更大的边界范围

行为树

行为树是一种强大的 AI 决策系统,用于实现复杂的游戏 AI 行为。

基本概念

  1. 节点状态
enum Status {
    SUCCESS,  // 成功
    FAILURE,  // 失败
    RUNNING   // 运行中
}
  1. 节点类型
  • 动作节点 (Action):执行具体行为的叶子节点
  • 组合节点 (Composite):控制子节点执行顺序的节点
  • 条件节点 (Condition):判断条件的节点
  • 装饰节点 (Decorator):修饰其他节点行为的节点

使用示例

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. 组合节点

    // 顺序节点:按顺序执行所有子节点,直到遇到失败或运行中的节点
    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. 动作节点

    // 成功节点
    new Success(() => {
        // 执行动作
    });
    
    // 失败节点
    new Failure(() => {
        // 执行动作
    });
    
    // 运行中节点
    new Running(() => {
        // 持续执行的动作
    });
    
    // 等待节点
    new WaitTime(2);  // 等待2秒
    new WaitTicks(5);  // 等待5个tick
    
  3. 使用黑板共享数据

    // 在节点中使用黑板
    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. 性能优化:
    • 使用黑板共享数据,避免重复计算
    • 合理使用记忆节点,减少重复执行
    • 控制行为树的深度,避免过于复杂

资源加载

!!! 注意:资源加载多次和一次效果一样

特点

  • 可通过路径或者uuid获取资源
  • 只适合手动管理资源,单无论加载多少次,卸载一次后删除

使用

    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);
        }
    });

接口

资源加载器

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

资源池

/** 资源是否已加载 */
public static has(path: string, bundlename: string = "resources"): boolean

/** 获取资源 */
public static get<T extends Asset>(path: string, bundlename: string = "resources"): T

/** 按 uuid 判断资源是否已加载 */
public static hasUUID(uuid: string): boolean

/** 按 uuid 获取资源 */
public static getByUUID<T extends Asset>(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<void>

/** 按 uuid 释放资源 */
public static releaseUUID(uuid: string): void

/** 释放所有加载的资源 */
public static releaseAll(): void

条件显示节点

特点

  • 用于在游戏中显示或隐藏特定UI元素。
  • 一般用于红点提示
  • 主要是解耦用,条件单独实现,节点关联单条件或者多个条件

使用

定义条件

// 定义条件类型枚举
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;
    }
}

节点关联条件

/** 任意一个满足 显示节点 */
new kunpo.ConditionAnyNode(fgui.GObject, ConditionType.condition1, ConditionType.condition2);

/** 所有条件都满足 显示节点 */
new kunpo.ConditionAllNode(fgui.GObject, ConditionType.Condition1, ConditionType.Condition2, ConditionType.Condition3);

全局事件系统

使用


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);

计时器

使用


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 类提供了游戏运行平台的相关信息和判断方法。

平台类型

import { Platform, PlatformType } from 'kunpocc';

// 平台类型枚举
enum PlatformType {
    Android = 1,    // 安卓
    IOS,            // iOS
    HarmonyOS,      // 鸿蒙
    WX,             // 微信小游戏
    Alipay,         // 支付宝小游戏
    Bytedance,      // 字节小游戏
    HuaweiQuick,    // 华为快游戏
    Browser         // 浏览器
}

// 获取当前平台类型
const currentPlatform = Platform.platform;

平台判断

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('当前是浏览器环境');
}

使用示例

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 {
    // 浏览器平台的处理
}

屏幕


/** 屏幕宽度 */

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)

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

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 (不需要关心)

马住马住马住马住

:+1::+1::+1:

大喊666!

git给个星吧,感谢~