模块拆分

This commit is contained in:
gongxh 2025-06-04 22:19:20 +08:00
parent 4ccd8bde50
commit c25c1554f3
42 changed files with 48 additions and 5188 deletions

View File

@ -21,4 +21,20 @@
## 1.0.35
- 修复未配置GameEntiry中的ecConfig时报错的问题
## 1.0.38
- 修复适配器设计尺寸设置错误的问题
- 修复适配器设计尺寸设置错误的问题
## 1.1.0 模块拆分
- 拆分资源管理模块,使用 `npm install kunpocc-assets` 安装
* 仓库地址: https://github.com/Gongxh0901/kunpocc-assets
- 拆分ec模块使用 `npm install kunpocc-ec` 安装
* 仓库地址: https://github.com/Gongxh0901/kunpo-ec
- 拆分ecs模块使用 `npm install kunpocc-ecs` 安装
* 仓库地址: https://github.com/Gongxh0901/kunpo-esc
- 拆分四叉树模块,使用 `npm install kunpocc-quadtree` 安装
* 仓库地址: https://github.com/Gongxh0901/kunpo-quadtree
- 拆分行为树模块,使用 `npm install kunpocc-behaviortree` 安装
* 仓库地址: https://github.com/Gongxh0901/kunpocc-behaviortree

View File

@ -40,21 +40,22 @@ npm set registry https://npm.aliyun.com
2. [项目配置](./docs/Basic.md)
3. [UI模块](./docs/UI.md)
4. [实体组件模块](./docs/EC.md)
5. [网络模块](./docs/HTTP.md)
6. [四叉树](./docs/QuadTree.md)
7. [行为树](./docs/BehaviorTree.md)
8. [资源管理](./docs/Asset.md)
9. [条件显示节点 (一般用于UI上的红点)](./docs/Condition.md)
10. [全局事件](./docs/Event.md)
11. [全局计时器](./docs/Timer.md)
12. [平台工具](./docs/Platform.md)
13. [屏幕尺寸](./docs/Screen.md)
14. [小工具](./docs/Tools.md)
15. [时间](./docs/Time.md)
16. [socket网络模块](./docs/Socket.md)
17. [小游戏接口封装](./docs/MiniGame.md)
18. [热更新](./docs/HotUpdate.md)
4. [ec模块](https://github.com/Gongxh0901/kunpo-ec)
5. [ecs模块](https://github.com/Gongxh0901/kunpo-esc)
6. [网络模块](./docs/HTTP.md)
7. [四叉树](https://github.com/Gongxh0901/kunpo-quadtree)
8. [行为树](https://github.com/Gongxh0901/kunpocc-behaviortree)
9. [资源管理](https://github.com/Gongxh0901/kunpocc-assets)
10. [条件显示节点 (一般用于UI上的红点)](./docs/Condition.md)
11. [全局事件](./docs/Event.md)
12. [全局计时器](./docs/Timer.md)
13. [平台工具](./docs/Platform.md)
14. [屏幕尺寸](./docs/Screen.md)
15. [小工具](./docs/Tools.md)
16. [时间](./docs/Time.md)
17. [socket网络模块](./docs/Socket.md)
18. [小游戏接口封装](./docs/MiniGame.md)
19. [热更新](./docs/HotUpdate.md)
## 类型支持
该库完全使用 TypeScript 编写,提供完整的类型定义文件。

View File

@ -1,102 +0,0 @@
## 资源加载
> !!! 注意:资源加载多次和一次效果一样
### 特点
* 可通过路径或者uuid获取资源
* 只适合手动管理资源,单无论加载多少次,卸载一次后删除
* 可根据 `new kunpo.AssetLoader("batchName")` 传入的 `batchName`批量卸载资源
> 比如进入战斗时创建了多个new kunpo.AssetLoader("batchName") 来加载资源传入的batchName相同
>
> 等退出战斗后,可以通过 AssetPool.releaseBatchAssets("batchName") 一键释放所有等于batchName的资源
### 使用
```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("batchName");
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<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 releaseDir(dir: string, bundlename: string = "resources", asset: typeof Asset): Promise<boolean>
/** 按 uuid 释放资源 */
public static releaseUUID(uuid: string): void
/** 释放所有加载的资源 */
public static releaseAll(): void
/**
* 按资源加载批次释放资源
* @param batchName 资源加载批次名 对应 AssetLoader 实例化时传入的 name
*/
public static releaseBatchAssets(batchName: string): void;
```

View File

@ -1,141 +0,0 @@
## 行为树
> 行为树是一种强大的 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. 性能优化:
- 使用黑板共享数据,避免重复计算
- 合理使用记忆节点,减少重复执行
- 控制行为树的深度,避免过于复杂

View File

@ -1,524 +0,0 @@
## 实体组件模块
> 实体组件系统是一种用于游戏开发的架构模式,它将游戏对象(实体)的数据(组件)和行为分离。
### 特点
* 不同实体上的组件更新顺序管理(`只根据注册的组件更新顺序更新,跟实体无关`
* 灵活的EC装饰器 (配合插件 `kunpo-ec` 使用,配置实体组件信息,一键导出)
* 支持多世界(多战斗场景,互不影响)
* 区分数据组件和逻辑组件,只更新逻辑组件
### 插件链接
* **kunpo-ec**: [https://store.cocos.com/app/detail/7311](https://store.cocos.com/app/detail/7311)
### 使用
#### *creator插件`kunpo-ec`*
> `kunpo-cc`可以方便创建、配置、导出实体,操作界面如下图:
![image-entity-editor](../image/image-entity-editor.png#pic_left)
#### *使用*
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<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 `
```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<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`
```typescript
/** 实体名称 */
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`
```typescript
/** 组件名 */
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
```

View File

@ -1,132 +0,0 @@
## 四叉树
> 四叉树是一种通过空间划分来进行高效碰撞查询的数据结构。
#### 基本概念
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. 更新策略:
- 仅在必要时更新四叉树
- 对于静态物体,可以使用单独的四叉树
- 动态物体频繁更新时,考虑使用更大的边界范围

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

View File

@ -1,6 +1,6 @@
{
"name": "kunpocc",
"version": "1.0.38",
"version": "1.1.3",
"description": "基于creator3.0+的kunpocc库",
"main": "./dist/kunpocc.cjs",
"module": "./dist/kunpocc.mjs",
@ -28,7 +28,10 @@
"dist/kunpocc.mjs",
"dist/kunpocc.min.cjs",
"dist/kunpocc.min.mjs",
"dist/kunpocc.d.ts"
"dist/kunpocc.d.ts",
"libs/lib.ali.api.d.ts",
"libs/lib.bytedance.api.d.ts",
"libs/lib.wx.api.d.ts"
],
"author": "gongxh",
"license": "ISC",

View File

@ -1,336 +0,0 @@
/**
* @Author: Gongxh
* @Date: 2025-02-11
* @Description:
*/
import { Asset, AssetManager, resources } from "cc";
import { log } from "../tool/log";
import { MathTool } from "../tool/Math";
import { AssetPool } from "./AssetPool";
import { AssetUtils } from "./AssetUtils";
export interface IAssetConfig {
/** 资源类型 */
type: typeof Asset;
/** 资源路径 */
path: string;
/** 是否是单个文件 默认是文件夹 */
isFile?: boolean;
/** 资源包名 默认 resources */
bundle?: string;
}
/**
*
* @internal
*/
enum StateType {
Error,
Wait,
Loading,
Finish,
}
export class AssetLoader {
/**
*
* @internal
*/
private _name: string = "";
/**
*
* @internal
*/
private _total: number = 0;
/**
*
* @internal
*/
private _maxParallel: number = 10;
/**
*
* @internal
*/
private _parallel: number = 0;
/**
*
* @internal
*/
private _maxRetry: number = 3;
/**
*
* @internal
*/
private _retry: number = 0;
/**
*
* @internal
*/
private _initSuccess: boolean = false;
/**
*
* @internal
*/
private _progress: (percent: number) => void;
/**
*
* @internal
*/
private _complete: () => void;
/**
*
* @internal
*/
private _fail: (msg: string, err: Error) => void;
/**
*
* @internal
*/
private _configs: IAssetConfig[] = [];
/**
*
* @internal
*/
private _items: { type: typeof Asset, bundle: string, path: string, isFile?: boolean, status: StateType, count: number }[] = [];
/**
*
* @internal
*/
private _completeCounts: Map<string, number> = new Map();
constructor(batchName?: string) {
this._name = batchName || "";
}
/**
*
* @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 {
this._configs = res.configs;
this._maxParallel = res.parallel || 10;
this._maxRetry = res.retry || 3;
this._complete = res.complete;
this._progress = res.progress;
this._fail = res.fail;
this._total = 0;
this._initSuccess = false;
this._items.length = 0;
let initCount = res.configs.length;
for (const item of res.configs) {
let bundlename = item.bundle || "resources";
let count = 0;
if (bundlename == "resources") {
count = AssetUtils.getResourceCount(item.path, item.type);
this._total += count;
this._items.push({ type: item.type, bundle: item.bundle || "resources", path: item.path, isFile: item.isFile || false, status: StateType.Wait, count: count })
initCount--;
initCount <= 0 && this.initSuccess();
} else {
AssetUtils.loadBundle(bundlename).then((bundle: AssetManager.Bundle) => {
count = AssetUtils.getResourceCount(item.path, item.type, bundle);
this._total += count;
this._items.push({ type: item.type, bundle: item.bundle || "resources", path: item.path, isFile: item.isFile || false, status: StateType.Wait, count: count })
initCount--;
initCount <= 0 && this.initSuccess();
}).catch((err: Error) => {
if (this._retry < this._maxRetry) {
this.retryStart();
} else {
this._fail(`加载资源包[${bundlename}]失败`, err);
}
});
}
}
}
/** 重试 (重新加载失败的资源) */
public retry(): void {
this._parallel = 0;
this._retry = 0;
if (!this._initSuccess) {
this.retryStart();
} else {
this.retryLoad();
}
}
/**
*
* @internal
*/
private retryStart(): void {
this._retry++;
this.start({
configs: this._configs,
parallel: this._maxParallel,
retry: this._maxRetry,
complete: this._complete,
fail: this._fail,
progress: this._progress
});
}
/**
*
* @internal
*/
private retryLoad(): void {
this._retry++;
let count = this.resetErrorItem();
let maxLoad = Math.min(count, this._maxParallel);
for (let i = 0; i < maxLoad; i++) {
this.loadNext();
}
}
/**
*
* @internal
*/
private initSuccess(): void {
this._initSuccess = true;
this._parallel = 0;
let maxLoad = Math.min(this._items.length, this._maxParallel);
for (let i = 0; i < maxLoad; i++) {
this.loadNext();
}
}
/**
*
* @internal
*/
private loadNext(): void {
// 找到第一个等待中的资源
let index = this._items.findIndex(item => item.status == StateType.Wait);
if (index > -1) {
this.loadItem(index);
} else if (!this._items.some(item => item.status != StateType.Finish)) {
// 所有资源全部完成了
this._complete();
} else if (this._parallel <= 0 && this._retry < this._maxRetry) {
this.retryLoad();
}
}
/**
*
* @internal
*/
private resetErrorItem(): number {
let count = 0;
for (const item of this._items) {
if (item.status == StateType.Error) {
item.status = StateType.Wait;
count++;
}
}
return count;
}
/**
*
* @internal
*/
private loadItem(index: number): void {
let item = this._items[index];
item.status = StateType.Loading;
this._parallel++;
if (item.bundle == "resources") {
if (item.isFile) {
this.loadFile(index, resources);
} else {
this.loadDir(index, resources);
}
} else {
AssetUtils.loadBundle(item.bundle).then((bundle: AssetManager.Bundle) => {
if (item.isFile) {
this.loadFile(index, bundle);
} else {
this.loadDir(index, bundle);
}
}).catch((err: Error) => {
log(`load bundle error, bundle:${item.bundle}, filename:${item.path}`);
item.status = StateType.Error;
});
}
}
/**
*
* @internal
*/
private loadDir(index: number, bundle: AssetManager.Bundle): void {
let item = this._items[index];
bundle.loadDir(item.path, item.type, (finish: number, total: number) => {
if (total > 0 && finish > 0) {
this._completeCounts.set(`${item.bundle}:${item.path}`, finish);
this._progress && this.updateProgress();
}
}, (error: Error, assets: Array<Asset>) => {
this._parallel--;
if (error) {
log(`load dir error, bundle:${item.bundle}, dir:${item.path}`);
item.status = StateType.Error;
} else {
item.status = StateType.Finish;
this._completeCounts.set(`${item.bundle}:${item.path}`, assets.length);
AssetPool.add(assets, bundle, this._name);
}
this._progress && this.updateProgress();
this.loadNext();
});
}
/**
*
* @internal
*/
private loadFile(index: number, bundle: AssetManager.Bundle): void {
let item = this._items[index];
bundle.load(item.path, item.type, (error: Error, asset: Asset) => {
this._parallel--;
if (error) {
log(`load file error, bundle:${item.bundle}, filename:${item.path}`);
item.status = StateType.Error;
} else {
item.status = StateType.Finish;
this._completeCounts.set(`${item.bundle}:${item.path}`, 1);
AssetPool.add(asset, bundle, this._name);
}
this._progress && this.updateProgress();
this.loadNext();
});
}
/**
*
* @internal
*/
private updateProgress(): void {
let value = 0;
for (const count of this._completeCounts.values()) {
value += count;
}
this._progress(MathTool.clampf(value / this._total, 0, 1));
}
}

View File

@ -1,170 +0,0 @@
/**
* @Author: Gongxh
* @Date: 2025-02-11
* @Description:
*/
import { Asset, AssetManager, resources } from "cc";
import { log } from "../tool/log";
import { AssetUtils } from "./AssetUtils";
export class AssetPool {
/**
*
* @internal
*/
private static _assets: { [path: string]: Asset } = {};
/**
* uuid
* @internal
*/
private static _uuidToName: Map<string, string> = new Map();
/**
*
* @internal
*/
private static _batchAssetNames: Map<string, string[]> = new Map();
/** 批量添加资源 */
public static add(asset: Asset[] | Asset, bundle: AssetManager.Bundle = resources, batchName: string = ""): void {
if (Array.isArray(asset)) {
for (const item of asset) {
this.add(item, bundle, batchName);
}
} else {
let uuid = asset.uuid || asset._uuid;
if (this._uuidToName.has(uuid)) {
return;
}
// 增加引用计数
asset.addRef();
let info = bundle.getAssetInfo(uuid);
let key = this.getKey(info.path, bundle.name);
// log(`>>>uuid:${uuid}, path:${info.path}`);
this._uuidToName.set(uuid, key);
this._assets[key] = asset;
if (batchName) {
let names = this._batchAssetNames.get(batchName) || [];
names.push(key);
this._batchAssetNames.set(batchName, names);
}
}
}
public static has(path: string, bundlename: string = "resources"): boolean {
let key = this.getKey(path, bundlename);
if (!this._assets[key]) {
return false;
}
return true;
}
public static get<T extends Asset>(path: string, bundlename: string = "resources"): T {
let key = this.getKey(path, bundlename);
if (!this._assets[key]) {
log(`获取资源失败: 资源 bundle:${bundlename}, path:${path} 未加载`);
}
return this._assets[key] as T;
}
/** 按 uuid 判断资源是否存在 */
public static hasUUID(uuid: string): boolean {
if (!this._uuidToName.has(uuid)) {
return false;
}
return true;
}
/** 按 uuid 获取资源 */
public static getByUUID<T extends Asset>(uuid: string): T {
if (!this._uuidToName.has(uuid)) {
log(`获取资源失败: 资源 uuid:${uuid} 未加载`);
}
let key = this._uuidToName.get(uuid);
return this._assets[key] as T;
}
/**
*
* @param batchName AssetLoader name
*/
public static releaseBatchAssets(batchName: string): void {
if (!this._batchAssetNames.has(batchName)) {
return;
}
let names = this._batchAssetNames.get(batchName);
for (const name of names) {
this.release(name);
}
this._batchAssetNames.delete(batchName);
}
/** 按资源路径释放资源 */
public static releasePath(path: string, bundlename: string = "resources"): void {
let key = this.getKey(path, bundlename);
this.release(key);
}
/** 按 bundle 和 文件夹释放资源 */
public static releaseDir(dir: string, bundlename: string = "resources", asset: typeof Asset): Promise<boolean> {
return new Promise((resolve, reject) => {
if (bundlename == "resources") {
let uuids = AssetUtils.getUUIDs(dir, asset, resources);
for (const uuid of uuids) {
this.releaseUUID(uuid);
}
resolve(true);
} else {
AssetUtils.loadBundle(bundlename).then((bundle: AssetManager.Bundle) => {
let uuids = AssetUtils.getUUIDs(dir, asset, bundle);
for (const uuid of uuids) {
this.releaseUUID(uuid);
}
resolve(true);
}).catch((err: Error) => {
reject(false);
});
}
});
}
/** 按 uuid 释放资源 */
public static releaseUUID(uuid: string): void {
if (this._uuidToName.has(uuid)) {
let key = this._uuidToName.get(uuid);
this.release(key);
}
}
/** 释放所有加载的资源 */
public static releaseAll(): void {
for (const key in this._assets) {
this._assets[key].decRef();
}
this._assets = {};
this._uuidToName.clear();
this._batchAssetNames.clear();
}
/**
* key释放资源
* @internal
*/
private static release(key: string): void {
if (this._assets[key]) {
this._uuidToName.delete(this._assets[key].uuid);
this._assets[key].decRef();
delete this._assets[key];
}
}
/**
* key
* @internal
*/
private static getKey(path: string, bundlename: string = "resources"): string {
return `${bundlename}:${path}`;
}
}

View File

@ -1,52 +0,0 @@
/**
* @Author: Gongxh
* @Date: 2025-02-11
* @Description:
*/
import { Asset, AssetManager, assetManager, resources } from "cc";
export class AssetUtils {
/** 获取资源数量 */
public static getResourceCount(dir: string, type: typeof Asset, bundle: AssetManager.Bundle = resources): number {
dir = assetManager.utils.normalize(dir);
if (dir[dir.length - 1] === "/") {
dir = dir.slice(0, -1);
}
let list = bundle.getDirWithPath(dir, type);
return list.length;
}
/** 获取资源名称 */
public static getUUIDs(dir: string, type: typeof Asset, bundle: AssetManager.Bundle = resources): string[] {
let uuids: string[] = [];
let path = assetManager.utils.normalize(dir);
if (path[path.length - 1] === "/") {
path = path.slice(0, -1);
}
let list = bundle.getDirWithPath(path, type);
for (const asset of list) {
uuids.push(asset.uuid);
}
return uuids;
}
/** 加载 bundle */
public static loadBundle(bundlename: string): Promise<AssetManager.Bundle> {
return new Promise((resolve, reject) => {
let bundle = assetManager.getBundle(bundlename);
if (bundle) {
resolve(bundle);
} else {
assetManager.loadBundle(bundlename, (err: Error, bundle: AssetManager.Bundle) => {
if (err) {
reject(err);
} else {
resolve(bundle);
}
});
}
});
}
}

View File

@ -1,48 +0,0 @@
import { BehaviorTree } from "./BehaviorTree";
import { Blackboard } from "./Blackboard";
import { Ticker } from "./Ticker";
/** 代理 */
export class Agent {
/** 行为树 */
public tree: BehaviorTree;
/** 黑板 */
public blackboard: Blackboard;
/** 更新器 */
public ticker: Ticker;
/**
* constructor
* @param subject // 主体
* @param tree
*/
constructor(subject: any, tree: BehaviorTree) {
this.tree = tree;
this.blackboard = new Blackboard();
this.ticker = new Ticker(subject, this.blackboard, tree);
}
/**
*
*/
public tick(): void {
this.tree.tick(this, this.blackboard, this.ticker);
if (this.blackboard.interrupt) {
this.blackboard.interrupt = false;
let ticker = this.ticker;
ticker.openNodes.length = 0;
ticker.nodeCount = 0;
this.blackboard.clear();
}
}
/**
*
*/
public interruptBTree(): void {
if (!this.blackboard.interruptDefend) {
this.blackboard.interrupt = true;
}
}
}

View File

@ -1,189 +0,0 @@
import { Status } from "../header";
import { Ticker } from "../Ticker";
import { BaseNode } from "./BaseNode";
/**
*
*
*/
export abstract class Action extends BaseNode {
constructor() {
super();
}
}
/**
* ()
* FAILURE
*/
export class Failure extends Action {
/** 执行函数 @internal */
private _func: () => void;
constructor(func: () => void) {
super();
this._func = func;
}
/**
*
* @param {Ticker} ticker
* @returns {Status}
*/
public tick(ticker: Ticker): Status {
this._func();
return Status.FAILURE;
}
}
/**
* ()
* RUNING
*/
export class Running extends Action {
/** 执行函数 @internal */
private _func: () => void;
constructor(func: () => void) {
super();
this._func = func;
}
/**
*
* @param {Ticker} ticker
* @returns {Status}
*/
public tick(ticker: Ticker): Status {
this._func();
return Status.RUNNING;
}
}
/**
*
* SUCCESS
*/
export class Success extends Action {
/** 执行函数 @internal */
private _func: () => void;
constructor(func: () => void) {
super();
this._func = func;
}
/**
*
* @param {Ticker} ticker
* @returns {Status}
*/
public tick(ticker: Ticker): Status {
this._func();
return Status.SUCCESS;
}
}
/**
* ()
* RUNING
* SUCCESS
*/
export class WaitTicks extends Action {
/** 最大次数 @internal */
private _maxTicks: number;
/** 经过的次数 @internal */
private _elapsedTicks: number;
constructor(maxTicks: number = 0) {
super();
this._maxTicks = maxTicks;
this._elapsedTicks = 0;
}
/**
*
* @param {Ticker} ticker
*/
public open(ticker: Ticker): void {
this._elapsedTicks = 0;
}
/**
*
* @param {Ticker} ticker
* @returns {Status}
*/
public tick(ticker: Ticker): Status {
if (++this._elapsedTicks >= this._maxTicks) {
this._elapsedTicks = 0;
return Status.SUCCESS;
}
return Status.RUNNING;
}
}
/**
* ()
* SUCCESSRUNING
*/
export class WaitTime extends Action {
/** 等待时间(毫秒 ms) @internal */
private _duration: number;
constructor(duration: number = 0) {
super();
this._duration = duration * 1000;
}
/**
*
* @param {Ticker} ticker
*/
public open(ticker: Ticker): void {
let startTime = new Date().getTime();
ticker.blackboard.set("startTime", startTime, ticker.tree.id, this.id);
}
/**
*
* @param {Ticker} ticker
* @returns {Status}
*/
public tick(ticker: Ticker): Status {
let currTime = new Date().getTime();
let startTime = ticker.blackboard.get("startTime", ticker.tree.id, this.id);
if (currTime - startTime >= this._duration) {
return Status.SUCCESS;
}
return Status.RUNNING;
}
}
/**
*
* SUCCESS
* InterruptDefendCancel
*/
export class InterruptDefend extends Action {
/**
*
* @param {Ticker} ticker
* @returns {Status}
*/
public tick(ticker: Ticker): Status {
ticker.blackboard.interruptDefend = true;
return Status.SUCCESS;
}
}
/**
*
* SUCCESS
* InterruptDefend
*/
export class InterruptDefendCancel extends Action {
/**
*
* @param {Ticker} ticker
* @returns {Status}
*/
public tick(ticker: Ticker): Status {
ticker.blackboard.interruptDefend = false;
return Status.SUCCESS;
}
}

View File

@ -1,113 +0,0 @@
import { createUUID, Status } from "../header";
import { Ticker } from "../Ticker";
/**
*
* BaseNode
*/
export abstract class BaseNode {
/** 唯一标识 */
public id: string;
/** 子节点 */
public children: BaseNode[];
/**
*
* @param children
*/
constructor(children?: BaseNode[]) {
this.id = createUUID();
this.children = [];
if (!children) {
return;
}
for (let i = 0; i < children.length; i++) {
this.children.push(children[i]);
}
}
/**
*
* @param ticker
* @returns {Status}
*/
public _execute(ticker: Ticker): Status {
/* ENTER */
this._enter(ticker);
if (!ticker.blackboard.get("isOpen", ticker.tree.id, this.id)) {
this._open(ticker);
}
let status = this._tick(ticker);
if (status !== Status.RUNNING) {
this._close(ticker);
}
this._exit(ticker);
return status;
}
/**
*
* @param ticker
* @internal
*/
public _enter(ticker: Ticker): void {
ticker.enterNode(this);
this.enter(ticker);
}
/**
*
* @param ticker
* @internal
*/
public _open(ticker: Ticker): void {
ticker.openNode(this);
ticker.blackboard.set("isOpen", true, ticker.tree.id, this.id);
this.open(ticker);
}
/**
*
* @param ticker
* @internal
*/
public _tick(ticker: Ticker): Status {
ticker.tickNode(this);
return this.tick(ticker);
}
/**
*
* @param ticker
* @internal
*/
public _close(ticker: Ticker): void {
ticker.closeNode(this);
ticker.blackboard.set("isOpen", false, ticker.tree.id, this.id);
this.close(ticker);
}
/**
* 退
* @param ticker
* @internal
*/
public _exit(ticker: Ticker): void {
ticker.exitNode(this);
this.exit(ticker);
}
enter(ticker: Ticker): void {
}
open(ticker: Ticker): void {
}
close(ticker: Ticker): void {
}
exit(ticker: Ticker): void {
}
abstract tick(ticker: Ticker): Status;
}

View File

@ -1,206 +0,0 @@
import { Status } from "../header";
import { Ticker } from "../Ticker";
import { BaseNode } from "./BaseNode";
/**
*
*
*/
export abstract class Composite extends BaseNode {
constructor(...children: BaseNode[]) {
super(children);
}
}
/**
*
* FAILURE
* Child Node返回不为 FAILURE, Node向自己的Parent Node也返回Child Node状态
*/
export class MemSelector extends Composite {
/**
*
* @param {Ticker} ticker
*/
public open(ticker: Ticker): void {
super.open(ticker);
ticker.blackboard.set("runningChild", 0, ticker.tree.id, this.id);
}
/**
*
* @param {Ticker} ticker
* @returns {Status}
*/
public tick(ticker: Ticker): Status {
let childIndex = ticker.blackboard.get("runningChild", ticker.tree.id, this.id) as number;
for (let i = childIndex; i < this.children.length; i++) {
let status = this.children[i]._execute(ticker);
if (status !== Status.FAILURE) {
if (status === Status.RUNNING) {
ticker.blackboard.set("runningChild", i, ticker.tree.id, this.id);
}
return status;
}
}
return Status.FAILURE;
}
}
/**
*
* RUNING , , RUNING
* RUNING FAILURE
* Child Node返回不为 SUCCESS, Node向自己的Parent Node也返回Child Node状态
* SUCCESS, SUCCESS
*/
export class MemSequence extends Composite {
/**
*
* @param {Ticker} ticker
*/
public open(ticker: Ticker): void {
super.open(ticker);
ticker.blackboard.set("runningChild", 0, ticker.tree.id, this.id);
}
/**
*
* @param {Ticker} ticker
* @returns {Status}
*/
public tick(ticker: Ticker): Status {
let childIndex = ticker.blackboard.get("runningChild", ticker.tree.id, this.id) as number;
for (let i = childIndex; i < this.children.length; i++) {
let status = this.children[i]._execute(ticker);
if (status !== Status.SUCCESS) {
if (status === Status.RUNNING) {
ticker.blackboard.set("runningChild", i, ticker.tree.id, this.id);
}
return status;
}
}
return Status.SUCCESS;
}
}
/**
*
* Child Node中随机选择一个执行
*/
export class RandomSelector extends Composite {
/**
*
* @param {Ticker} ticker
* @returns {Status}
*/
public tick(ticker: Ticker): Status {
let childIndex = (Math.random() * this.children.length) | 0;
let child = this.children[childIndex];
let status = child._execute(ticker);
return status;
}
}
/**
* FAILURE
* Node时begin到end迭代执行自己的Child Node
* Child Node执行后返回 SUCCESS RUNINGNode向自己的Parent Node也返回 SUCCESS RUNING
*/
export class Selector extends Composite {
/**
*
* @param {Ticker} ticker
* @returns {Status}
*/
public tick(ticker: Ticker): Status {
for (let i = 0; i < this.children.length; i++) {
let status = this.children[i]._execute(ticker);
if (status !== Status.FAILURE) {
return status;
}
}
return Status.FAILURE;
}
}
/**
*
* Node时begin到end迭代执行自己的Child Node
* FAILURE RUNING, FAILURE RUNING
* SUCCESS, SUCCESS
*/
export class Sequence extends Composite {
/**
*
* @param {Ticker} ticker
* @returns {Status}
*/
public tick(ticker: Ticker): Status {
for (let i = 0; i < this.children.length; i++) {
let status = this.children[i]._execute(ticker);
if (status !== Status.SUCCESS) {
return status;
}
}
return Status.SUCCESS;
}
}
/**
*
* Node时begin到end迭代执行自己的Child Node
* 1. Child Node执行后返回 FAILURE, FAILURE
* 2. Child Node执行后返回 RUNING, RUNING
* SUCCESS, SUCCESS
*/
export class Parallel extends Composite {
/**
*
* @param {Ticker} ticker
* @returns {Status}
*/
public tick(ticker: Ticker): Status {
let result = Status.SUCCESS;
for (let i = 0; i < this.children.length; i++) {
let status = this.children[i]._execute(ticker);
if (status == Status.FAILURE) {
result = Status.FAILURE;
} else if (result == Status.SUCCESS && status == Status.RUNNING) {
result = Status.RUNNING;
}
}
return result;
}
}
/**
*
* Node时begin到end迭代执行自己的Child Node
* 1. Child Node执行后返回 FAILURE, FAILURE
* 2. Child Node SUCCESS, SUCCESS
* RUNNING
*/
export class ParallelAnySuccess extends Composite {
/**
*
* @param {Ticker} ticker
* @returns {Status}
*/
public tick(ticker: Ticker): Status {
let result = Status.RUNNING;
for (let i = 0; i < this.children.length; i++) {
let status = this.children[i]._execute(ticker);
if (status == Status.FAILURE) {
result = Status.FAILURE;
} else if (result == Status.RUNNING && status == Status.SUCCESS) {
result = Status.SUCCESS;
}
}
return result;
}
}

View File

@ -1,24 +0,0 @@
import { Status } from "../header";
import { Ticker } from "../Ticker";
import { Action } from "./Action";
/**
*
*/
export class Condition extends Action {
/** 执行函数 @internal */
private _func: (subject: any) => boolean = null;
constructor(func: (subject: any) => boolean) {
super();
this._func = func;
}
/**
*
* @param {Ticker} ticker
* @returns {Status}
*/
public tick(ticker: Ticker): Status {
return this._func(ticker.subject) ? Status.SUCCESS : Status.FAILURE;
}
}

View File

@ -1,367 +0,0 @@
import { Status } from "../header";
import { Ticker } from "../Ticker";
import { BaseNode } from "./BaseNode";
/**
*
*
*/
export abstract class Decorator extends BaseNode {
constructor(child: BaseNode) {
super([child]);
}
}
/**
*
*
* FAILURE
* @extends Decorator
*/
export class Failer extends Decorator {
/**
*
* @param {Ticker} ticker
* @returns {Status}
*/
public tick(ticker: Ticker): Status {
if (this.children.length !== 1) {
throw new Error("(Failer)节点必须包含一个子节点");
}
let child = this.children[0];
child._execute(ticker);
return Status.FAILURE;
}
}
/**
*
*
* Child Node节点, FAILURE, Node向自己的Parent Node也返回 SUCCESS
* Child Node节点, SUCCESS, Node向自己的Parent Node也返回 FAILURE
*/
export class Inverter extends Decorator {
/**
*
* @param {Ticker} ticker
* @returns {Status}
*/
public tick(ticker: Ticker): Status {
if (this.children.length !== 1) {
throw new Error("(Inverter)节点必须包含一个子节点");
}
let child = this.children[0];
let status = child._execute(ticker);
if (status === Status.SUCCESS) {
status = Status.FAILURE;
} else if (status === Status.FAILURE) {
status = Status.SUCCESS;
}
return status;
}
}
/**
*
*
* , Child Node的结果, Node向自己的Parent Node也返回相同的结果
* , FAILURE
*/
export class LimiterTicks extends Decorator {
/** 最大次数 @internal */
private _maxTicks: number;
/** 当前执行过的次数 @internal */
private _elapsedTicks: number;
/**
*
* @param maxTicks
* @param child
*/
constructor(maxTicks: number, child: BaseNode) {
super(child);
this._maxTicks = maxTicks;
this._elapsedTicks = 0;
}
/**
*
* @param {Ticker} ticker
*/
public open(ticker: Ticker): void {
super.open(ticker);
this._elapsedTicks = 0;
}
/**
*
* @param {Ticker} ticker
* @returns {Status}
*/
public tick(ticker: Ticker): Status {
if (this.children.length !== 1) {
throw new Error("(LimiterTicks)节点必须包含一个子节点");
}
let child = this.children[0];
if (++this._elapsedTicks > this._maxTicks) {
this._elapsedTicks = 0;
return Status.FAILURE;
}
return child._execute(ticker);
}
}
/**
*
*
* , Child Node的结果, Node向自己的Parent Node也返回相同的结果
* , FAILURE
*/
export class LimiterTime extends Decorator {
/** 最大时间 (毫秒 ms) @internal */
private _maxTime: number;
/**
*
* @param maxTime (ms)
* @param child
*/
constructor(maxTime: number, child: BaseNode) {
super(child);
this._maxTime = maxTime * 1000;
}
/**
*
* @param {Ticker} ticker
*/
public open(ticker: Ticker): void {
super.open(ticker);
let startTime = new Date().getTime();
ticker.blackboard.set("startTime", startTime, ticker.tree.id, this.id);
}
/**
*
* @param {Ticker} ticker
* @returns {Status}
*/
public tick(ticker: Ticker): Status {
if (this.children.length !== 1) {
throw new Error("(LimiterTime)节点必须包含一个子节点");
}
let child = this.children[0];
let currTime = new Date().getTime();
let startTime = ticker.blackboard.get("startTime", ticker.tree.id, this.id);
if (currTime - startTime > this._maxTime) {
return Status.FAILURE;
}
return child._execute(ticker);
}
}
/**
*
*
* maxLoop < 0,
* , Child Node的结果RUNING的次数不计算在内
*/
export class Repeater extends Decorator {
/** 最大循环次数 @internal */
private _maxLoop: number;
/**
*
* @param child
* @param maxLoop
*/
constructor(child: BaseNode, maxLoop: number = -1) {
super(child);
this._maxLoop = maxLoop;
}
/**
*
* @param {Ticker} ticker
*/
public open(ticker: Ticker): void {
ticker.blackboard.set("i", 0, ticker.tree.id, this.id);
}
/**
*
* @param {Ticker} ticker
* @returns {Status}
*/
public tick(ticker: Ticker): Status {
if (this.children.length !== 1) {
throw new Error("(Repeater)节点必须包含一个子节点");
}
let child = this.children[0];
let i = ticker.blackboard.get("i", ticker.tree.id, this.id);
let status = Status.SUCCESS;
while (this._maxLoop < 0 || i < this._maxLoop) {
status = child._execute(ticker);
if (status === Status.SUCCESS || status === Status.FAILURE) {
i++;
} else {
break;
}
}
ticker.blackboard.set("i", i, ticker.tree.id, this.id);
return status;
}
}
/**
*
*
* maxLoop < 0,
* Child Node返回 FAILURE, Node向自己的Parent Node返回 FAILURE
* maxLoop时, Child Node的结果
*/
export class RepeatUntilFailure extends Decorator {
/** 最大循环次数 @internal */
private _maxLoop: number;
constructor(child: BaseNode, maxLoop: number = -1) {
super(child);
this._maxLoop = maxLoop;
}
/**
*
* @param {Ticker} ticker
*/
public open(ticker: Ticker): void {
ticker.blackboard.set("i", 0, ticker.tree.id, this.id);
}
/**
*
* @param {Ticker} ticker
* @returns {Status}
*/
public tick(ticker: Ticker): Status {
if (this.children.length !== 1) {
throw new Error("(RepeatUntilFailure)节点必须包含一个子节点");
}
let child = this.children[0];
let i = ticker.blackboard.get("i", ticker.tree.id, this.id);
let status = Status.SUCCESS;
while (this._maxLoop < 0 || i < this._maxLoop) {
status = child._execute(ticker);
if (status === Status.SUCCESS) {
i++;
} else {
break;
}
}
ticker.blackboard.set("i", i, ticker.tree.id, this.id);
return status;
}
}
/**
* ()
* maxLoop < 0,
* Child Node返回 SUCCESS, Node向自己的Parent Node返回 SUCCESS
* maxLoop时, Child Node的结果
*/
export class RepeatUntilSuccess extends Decorator {
/** 最大循环次数 @internal */
private _maxLoop: number;
/**
*
* @param child
* @param maxLoop
*/
constructor(child: BaseNode, maxLoop: number = -1) {
super(child);
this._maxLoop = maxLoop;
}
/**
*
* @param {Ticker} ticker
*/
public open(ticker: Ticker): void {
ticker.blackboard.set("i", 0, ticker.tree.id, this.id);
}
/**
*
* @param {Ticker} ticker
* @returns {Status}
*/
public tick(ticker: Ticker): Status {
if (this.children.length !== 1) {
throw new Error("(RepeatUntilSuccess)节点必须包含一个子节点");
}
let child = this.children[0];
let i = ticker.blackboard.get("i", ticker.tree.id, this.id);
let status = Status.FAILURE;
while (this._maxLoop < 0 || i < this._maxLoop) {
status = child._execute(ticker);
if (status === Status.FAILURE) {
i++;
} else {
break;
}
}
ticker.blackboard.set("i", i, ticker.tree.id, this.id);
return status;
}
}
/**
* , ()
* RUNING
*/
export class Runner extends Decorator {
/**
*
* @param {Ticker} ticker
* @returns {Status}
*/
public tick(ticker: Ticker): Status {
if (this.children.length !== 1) {
throw new Error("(Runner)节点必须包含一个子节点");
}
let child = this.children[0];
child._execute(ticker);
return Status.RUNNING;
}
}
/**
* ()
* SUCCESS
*/
export class Succeeder extends Decorator {
/**
*
* @param {Ticker} ticker
* @returns {Status}
*/
public tick(ticker: Ticker): Status {
if (this.children.length !== 1) {
throw new Error("(Succeeder)节点必须包含一个子节点");
}
let child = this.children[0];
child._execute(ticker);
return Status.SUCCESS;
}
}

View File

@ -1,61 +0,0 @@
import { Blackboard } from "./Blackboard";
import { BaseNode } from "./BTNode/BaseNode";
import { createUUID } from "./header";
import { Ticker } from "./Ticker";
/**
*
*
*/
export class BehaviorTree {
/** 行为树ID @internal */
private _id: string;
/** 行为树跟节点 @internal */
private _root: BaseNode;
/**
* constructor
* @param root
*/
constructor(root: BaseNode) {
this._id = createUUID();
this._root = root;
}
/**
*
* @param subject
* @param blackboard
* @param ticker
*/
public tick(subject: any, blackboard: Blackboard, ticker?: Ticker): void {
ticker = ticker || new Ticker(subject, blackboard, this);
ticker.openNodes.length = 0;
this._root._execute(ticker);
// 上次打开的节点
let lastOpenNodes = blackboard.get("openNodes", this._id) as BaseNode[];
// 当前打开的节点
let currOpenNodes = ticker.openNodes;
let start = 0;
for (let i = 0; i < Math.min(lastOpenNodes.length, currOpenNodes.length); i++) {
start = i + 1;
if (lastOpenNodes[i] !== currOpenNodes[i]) {
break;
}
}
// 关闭不需要的节点
for (let i = lastOpenNodes.length - 1; i >= start; i--) {
lastOpenNodes[i]._close(ticker);
}
/* POPULATE BLACKBOARD */
blackboard.set("openNodes", currOpenNodes, this._id);
blackboard.set("nodeCount", ticker.nodeCount, this._id);
}
get id(): string {
return this._id;
}
get root(): BaseNode {
return this._root;
}
}

View File

@ -1,105 +0,0 @@
/**
*
*/
interface ITreeData {
nodeMemory: { [nodeScope: string]: any };
openNodes: any[];
}
/** 平台 */
export class Blackboard {
/** 行为树打断保护 */
public interruptDefend: boolean = false;
/** 打断行为树的标记 */
public interrupt: boolean = false;
/** 基础记忆 @internal */
private _baseMemory: any;
/** 树记忆 @internal */
private _treeMemory: { [treeScope: string]: ITreeData };
constructor() {
this._baseMemory = {};
this._treeMemory = {};
}
/**
*
*/
public clear(): void {
this._baseMemory = {};
this._treeMemory = {};
}
/**
*
* @param key
* @param value
* @param treeScope
* @param nodeScope
*/
public set(key: string, value: any, treeScope?: string, nodeScope?: string): void {
let memory = this._getMemory(treeScope, nodeScope);
memory[key] = value;
}
/**
*
* @param key
* @param treeScope
* @param nodeScope
* @returns
*/
public get(key: string, treeScope?: string, nodeScope?: string): any {
let memory = this._getMemory(treeScope, nodeScope);
return memory[key];
}
/**
*
* @param treeScope
* @returns
* @internal
*/
private _getTreeMemory(treeScope: string): ITreeData {
if (!this._treeMemory[treeScope]) {
this._treeMemory[treeScope] = {
nodeMemory: {},
openNodes: [],
};
}
return this._treeMemory[treeScope];
}
/**
*
* @param treeMemory
* @param nodeScope
* @returns
* @internal
*/
private _getNodeMemory(treeMemory: ITreeData, nodeScope: string): { [key: string]: any } {
let memory = treeMemory.nodeMemory;
if (!memory[nodeScope]) {
memory[nodeScope] = {};
}
return memory[nodeScope];
}
/**
*
* @param treeScope
* @param nodeScope
* @returns
* @internal
*/
private _getMemory(treeScope?: string, nodeScope?: string): { [key: string]: any } {
let memory = this._baseMemory;
if (treeScope) {
memory = this._getTreeMemory(treeScope);
if (nodeScope) {
memory = this._getNodeMemory(memory, nodeScope);
}
}
return memory;
}
}

View File

@ -1,55 +0,0 @@
import { BehaviorTree } from "./BehaviorTree";
import { Blackboard } from "./Blackboard";
import { BaseNode } from "./BTNode/BaseNode";
export class Ticker {
tree: BehaviorTree; // 行为树跟节点
openNodes: BaseNode[]; // 当前打开的节点
nodeCount: number; // 当前打开的节点数量
blackboard: Blackboard; // 数据容器
debug: any;
subject: any;
constructor(subject: any, blackboard: Blackboard, tree: BehaviorTree) {
this.tree = tree;
this.openNodes = [];
this.nodeCount = 0;
this.debug = null;
this.subject = subject;
this.blackboard = blackboard;
}
/**
*
* @param node
*/
public enterNode(node: BaseNode): void {
this.nodeCount++;
this.openNodes.push(node);
}
/**
*
* @param node
*/
public openNode(node: BaseNode): void { }
/**
*
* @param node
*/
public tickNode(node: BaseNode): void { }
/**
*
* @param node
*/
public closeNode(node: BaseNode): void {
this.openNodes.pop();
}
/**
* 退
* @param node
*/
public exitNode(node: BaseNode): void { }
}

View File

@ -1,27 +0,0 @@
export const enum Status {
FAILURE,
SUCCESS,
RUNNING,
}
/**
* UUID
* @returns UUID
* @internal
*/
export function createUUID(): string {
let s: string[] = Array(36);
let hexDigits = "0123456789abcdef";
for (let i = 0; i < 36; i++) {
let start = Math.floor(Math.random() * 0x10);
s[i] = hexDigits.substring(start, start + 1);
}
// bits 12-15 of the time_hi_and_version field to 0010
s[14] = "4";
// bits 6-7 of the clock_seq_hi_and_reserved to 01
let start = (parseInt(s[19], 16) & 0x3) | 0x8;
s[19] = hexDigits.substring(start, start + 1);
s[8] = s[13] = s[18] = s[23] = "-";
let uuid = s.join("");
return uuid;
}

View File

@ -5,13 +5,11 @@
*/
import { _decorator, Component, director, game, JsonAsset, macro, sys } from "cc";
import { ECDataHelper } from "../ecmodule/ECDataHelper";
import { GlobalEvent } from "../global/GlobalEvent";
import { GlobalTimer } from "../global/GlobalTimer";
import { enableDebugMode, FrameConfig } from "../global/header";
import { InnerTimer } from "../global/InnerTimer";
import { Platform, PlatformType } from "../global/Platform";
import { ECManager } from "../kunpocc";
import { ModuleBase } from "../module/ModuleBase";
import { debug, log } from "../tool/log";
import { Time } from "../tool/Time";
@ -21,7 +19,6 @@ const _global = (globalThis || window || global) as any;
const { property } = _decorator;
export abstract class CocosEntry extends Component {
@property({ displayName: "uiConfig", type: JsonAsset, tooltip: "编辑器导出的UI配置, 可不设置, 之后通过 PropsHelper.setConfig 手动设置" }) uiConfig: JsonAsset = null;
@property({ displayName: "ecConfig", type: JsonAsset, tooltip: "编辑器导出的实体配置, 可不设置, 之后通过 ECManager.registerEntityConfig 手动设置" }) ecConfig: JsonAsset = null;
@property({ displayName: "游戏帧率" }) fps: number = 60;
/**
@ -49,17 +46,11 @@ export abstract class CocosEntry extends Component {
director.addPersistRootNode(this.node);
this.node.setSiblingIndex(this.node.children.length - 1);
PropsHelper.setConfig(this.uiConfig?.json);
let ecsMaps = _global["getKunpoRegisterECSMaps"]?.();
if (this.ecConfig && (!ecsMaps || ecsMaps.size <= 0)) {
ECManager.registerEntityConfig(this.ecConfig.json);
}
this.initPlatform();
this.initEvent();
this.initTime();
this.initAdapter();
this.initModule();
// 注册所有组件
ECDataHelper.registerComponents();
log("kunpo框架初始化完成");
this.onInit();
}

View File

@ -1,112 +0,0 @@
import { ComponentManager } from "./ComponentManager";
import { Entity } from "./Entity";
import { ObjectBase } from "./ObjectBase";
export abstract class Component extends ObjectBase {
/** 组件名 */
public name: string;
/** 组件类型 */
public type: number;
/** 是否需要更新 */
public needUpdate: boolean;
/** 所属实体 */
public entity: Entity;
/** 所属组件管理器 */
public componentManager: ComponentManager;
/** 是否需要销毁 @internal */
public _needDestroy: boolean;
/** 更新ID @internal */
public _updateId: number = -1;
/** 是否更新中 @internal */
public get _updating(): boolean {
return this._updateId != -1;
}
/** 生命周期函数 添加到实体 @internal */
public _add(): void {
this.onAdd();
}
/** 生命周期函数 销毁 @internal */
public _destroy(): void {
this.onDestroy();
}
/** 生命周期函数 添加到实体后 在这个函数中可以获取其他组件 @internal */
public _enter(): void {
// 自动开启更新
if (this.needUpdate) {
this.componentManager.startUpdateComponent(this);
}
this.onEnter();
}
/** 生命周期函数 从实体中移除 @internal */
public _remove(): void {
this.stopUpdate();
this.onRemove();
this.componentManager._destroyComponent(this);
}
/** 更新 @internal */
public _update(dt: number): void {
this.onUpdate(dt);
}
/** 开启更新 */
public startUpdate(): void {
if (!this.needUpdate) {
this.needUpdate = true;
this.componentManager?.startUpdateComponent(this);
}
}
/** 停止更新 */
public stopUpdate(): void {
if (this.needUpdate) {
this.needUpdate = false;
this.componentManager?.stopUpdateComponent(this);
}
}
/**
*
* @param {number} componentType
* @returns {T}
*/
public getComponent<T extends Component>(componentType: number): T {
return this.entity.getComponent<T>(componentType);
}
/**
*
*/
public destroySelf(): void {
this.entity.removeComponent(this.type);
}
/**
* onDestroy
*/
protected onAdd(): void { }
/**
* onAdd
*/
protected onDestroy(): void { }
protected onUpdate(dt: number): void { }
/** 可在此方法获取实体其他组件 */
protected abstract onEnter(): void;
/** 从实体中删除 */
protected abstract onRemove(): void;
}

View File

@ -1,290 +0,0 @@
import { Component } from "./Component";
import { ComponentPool } from "./ComponentPool";
/**
*
* @internal
*/
export class ComponentUpdate {
/** 组件更新类型 */
public componentType: number;
/** 组件更新列表 @internal */
private readonly _components: Component[] = [];
/** create constructor @internal */
public constructor(componentType: number) {
this.componentType = componentType;
}
/**
*
* @param component
* @internal
*/
public addComponent(component: Component): void {
this._components.push(component);
component._updateId = this._components.length - 1;
}
/**
*
* @param {Component} component
* @internal
*/
public removeComponent(component: Component): void {
const components = this._components;
const finalUpdateID = components.length - 1;
const updateID = component._updateId;
component._updateId = -1;
/** 最后一个和当前要删除的不是同一个,交换位置 */
if (finalUpdateID != updateID) {
const finalComponent = components[finalUpdateID];
// #EC_DEBUG_BEGIN
if (finalComponent._updateId != finalUpdateID) {
throw new Error(`组件(${finalComponent.toString()})更新ID(${finalUpdateID})与存储更新ID(${finalComponent._updateId})不一致`);
}
// #EC_DEBUG_END
finalComponent._updateId = updateID;
components[updateID] = finalComponent;
}
components.pop();
}
/** 更新 @internal */
public _update(dt: number): void {
const components = this._components;
const componentCount = components.length;
if (componentCount > 0) {
for (let i = 0; i < componentCount; ++i) {
const component = components[i];
if (component.needUpdate && component._updating) {
component._update(dt);
}
}
}
}
}
export class ComponentManager {
/**
*
* @type {ComponentPool}
* @internal
*/
protected componentPool: ComponentPool;
/** 更新组件池 @internal */
protected readonly updatingComponents: ComponentUpdate[] = [];
/** 组件更新顺序 @internal */
protected readonly componentUpdateOrderList: number[] = [];
/** 新添加的或者新停止更新的组件池 @internal */
private readonly _toUpdateComponents: Component[] = [];
/** 新停止更新的组件池 @internal */
private readonly _toStopComponents: Component[] = [];
/** 当前更新的组件类型 @internal */
private _currentUpdateComponentType: number = -1;
/**
*Creates an instance of ComponentManager.
* @param {ComponentPool} componentPool
* @param {number[]} componentUpdateOrderList
* @internal
*/
constructor(componentPool: ComponentPool, componentUpdateOrderList: number[]) {
this.componentPool = componentPool;
this._toUpdateComponents.length = 0;
this._toStopComponents.length = 0;
for (const componentType of componentUpdateOrderList) {
this._addComponentUpdateOrder(componentType);
}
}
/**
*
* @internal
*/
public destroy(): void {
this.componentPool.clear();
this.updatingComponents.length = 0;
this.componentUpdateOrderList.length = 0;
this._toUpdateComponents.length = 0;
this._toStopComponents.length = 0;
}
/**
*
* @template T
* @param {string} componentName
* @returns {T}
* @internal
*/
public createComponent<T extends Component>(componentName: string): T {
const component = this.componentPool.get(componentName) as T;
// component._enable = true;
// component.needDestroy = false;
component.componentManager = this;
return component;
}
/**
*
* @param {Component} component
*/
public startUpdateComponent(component: Component): void {
if (component._updating) {
return;
}
if (this._currentUpdateComponentType != component.type) {
this._addComponentToUpdateList(component);
return;
}
this._toUpdateComponents.push(component);
}
/**
*
* @param {Component} component
*/
public stopUpdateComponent(component: Component): void {
if (!component._updating) {
return;
}
if (this._currentUpdateComponentType != component.type) {
this._removeComponentToUpdateList(component);
return;
}
this._toStopComponents.push(component);
}
/**
* 使
* @param {Component} component
* @internal
*/
public _destroyComponent(component: Component): void {
if (!component._updating) {
component._destroy();
this.componentPool.recycle(component);
} else {
component._needDestroy = true;
}
}
/**
* 使
* @param {number} dt
* @internal
*/
public _update(dt: number): void {
this._updateAllComponents(dt);
this._currentUpdateComponentType = -1;
this._clearStopComponents();
this._addUpdateComponents();
}
/**
*
* @param {number} componentType
* @returns {ComponentManager}
* @internal
*/
private _addComponentUpdateOrder(componentType: number): ComponentManager {
this.componentUpdateOrderList.push(componentType);
const updatingComponents = this.updatingComponents;
for (let i = updatingComponents.length; i <= componentType; ++i) {
updatingComponents.push(null);
}
if (updatingComponents[componentType]) {
throw new Error(`组件类型(${componentType}:${this.componentPool.className(componentType)})已经添加到更新列表`);
}
updatingComponents[componentType] = new ComponentUpdate(componentType);
return this;
}
/**
*
* @param {Component} component
* @internal
*/
private _addComponentToUpdateList(component: Component): void {
if (component.type >= this.updatingComponents.length || !this.updatingComponents[component.type]) {
throw new Error(`组件(${component.constructor.name}没有添加到组件更新列表请使用addComponentUpdateOrder添加更新`);
}
this.updatingComponents[component.type].addComponent(component);
}
/**
*
* @param {Component} component
* @internal
*/
private _removeComponentToUpdateList(component: Component): void {
this.updatingComponents[component.type].removeComponent(component);
}
/**
*
* @param {number} dt
* @internal
*/
private _updateAllComponents(dt: number): void {
// 按优先级更新所有组件
const updateList = this.componentUpdateOrderList;
const updatingComponents = this.updatingComponents;
let componentType: number;
for (let i = 0, l = updateList.length; i < l; ++i) {
componentType = updateList[i];
this._currentUpdateComponentType = componentType;
updatingComponents[componentType]._update(dt);
}
}
/**
*
* @internal
*/
private _clearStopComponents(): void {
const toStopComponents = this._toStopComponents;
const l = toStopComponents.length;
if (l > 0) {
for (let i = 0; i < l; ++i) {
const component = toStopComponents[i];
if (!component.needUpdate && component._updating) {
this._removeComponentToUpdateList(component);
if (component._needDestroy) {
this._destroyComponent(component);
}
}
}
toStopComponents.length = 0;
}
}
/**
*
* @internal
*/
private _addUpdateComponents(): void {
const toUpdateComponents = this._toUpdateComponents;
const l = toUpdateComponents.length;
if (l > 0) {
for (let i = 0; i < l; ++i) {
const component = toUpdateComponents[i];
if (component.needUpdate && !component._updating) {
this._addComponentToUpdateList(component);
}
}
toUpdateComponents.length = 0;
}
}
}

View File

@ -1,99 +0,0 @@
import { Component } from "./Component";
import { ObjectBase } from "./ObjectBase";
import { ObjectFactory } from "./ObjectFactory";
export class ComponentPool {
/** 组件对象类型到组件类型转换 @internal */
private readonly _objectTypeToComponentType: number[] = new Array<number>(128);
/** 组件池 @internal */
private _pools: Map<number, ObjectFactory> = new Map();
/** 组件名称到组件对象类型转换 @internal */
private _nameToObjectType: Map<string, number> = new Map();
/**
*
* @param {number} componentObjectType
* @param {number} componentType
* @param {string} name
* @param {new () => Component} ctor
* @internal
*/
public register(componentObjectType: number, componentType: number, name: string, ctor: new () => ObjectBase): void {
if (this._pools.has(componentObjectType)) {
throw new Error(`组件(${name})已注册, 不允许重复注册`);
}
this._pools.set(componentObjectType, new ObjectFactory(componentObjectType, 128, name, ctor));
this._nameToObjectType.set(name, componentObjectType);
const objectTypeToComponentType = this._objectTypeToComponentType;
for (let i = objectTypeToComponentType.length; i <= componentObjectType; ++i) {
objectTypeToComponentType.push(i);
}
objectTypeToComponentType[componentObjectType] = componentType;
}
/**
*
* @param {string} componentName
* @returns {number}
*/
public getObjectTypeByName(componentName: string): number {
return this._nameToObjectType.get(componentName);
}
/**
*
* @param {number} componentName
* @returns {T}
* @template T
* @internal
*/
public get<T extends Component>(componentName: string): T {
let objectType = this.getObjectTypeByName(componentName);
const factory = this._pools.get(objectType);
if (!factory) {
throw new Error(`组件(${componentName})未注册,使用组件装饰器 ecclass 注册组件`);
}
const component = factory.allocate() as T;
component.name = factory.name;
component.type = this._objectTypeToComponentType[objectType];
return component;
}
/**
*
* @param {number} componentObjectType
* @returns {string}
* @internal
*/
public className(componentObjectType: number): string {
const factory = this._pools.get(componentObjectType);
if (!factory) {
throw new Error(
`组件(${componentObjectType}没有注册使用ComponentPool.register(componentObjectType, componentType, componentClass)注册组件`
);
}
return factory.name;
}
/**
*
* @param {BaseComponent} component
* @memberof ComponentPool
* @internal
*/
public recycle(component: Component): void {
const objectFactory = this._pools.get(component.objectType);
objectFactory.recycle(component);
}
/**
*
* @internal
*/
public clear(): void {
for (const factory of this._pools.values()) {
factory._clear();
}
}
}

View File

@ -1,109 +0,0 @@
import { color, size, v2, v3 } from "cc";
import { _ecdecorator, ComponentPool, warn } from "../kunpocc";
import { Component } from "./Component";
/**
* @Author: Gongxh
* @Date: 2025-01-24
* @Description:
*/
export class ECDataHelper {
/** 组件池 @internal */
public static _componentPool: ComponentPool = new ComponentPool();
/** 注册所有组件 */
public static registerComponents(): void {
let index = 0;
let maps = _ecdecorator.getComponentMaps();
maps.forEach((info: _ecdecorator.ECComponentInfo, ctor: any) => {
this._componentPool.register(index++, info.componentType, info.name, ctor);
});
}
public static getComponentPool(): ComponentPool {
return this._componentPool;
}
/** 解析组件数据 */
public static parse(component: Component, data: Record<string, any>): void {
const maps = _ecdecorator.getComponentMaps();
const ctor = component.constructor;
if (!maps.has(ctor)) {
return;
}
const info = maps.get(ctor);
for (const property in data) {
let propInfo = info.props[property];
if (!propInfo) {
warn(`组件 ${component.name} 属性 ${property} 未注册`);
continue;
}
let value = data[property];
(component as any)[property] = this.getPropValue(propInfo, value);
}
}
private static getPropValue(propInfo: _ecdecorator.ECPropInfo, value: any): any {
switch (propInfo.type) {
case "int":
if (typeof value === "number") {
return value;
}
return propInfo.defaultValue || 0;
case "float":
if (typeof value === "number") {
return value;
}
return propInfo.defaultValue || 0;
case "boolean":
if (typeof value === "boolean") {
return value;
}
return propInfo.defaultValue || false;
case "size":
if (typeof value === "object" && typeof value.width === "number" && typeof value.height === "number") {
return size(value.width, value.height);
}
return propInfo.defaultValue || size(0, 0);
case "vec2":
if (typeof value === "object" && typeof value.x === "number" && typeof value.y === "number") {
return v2(value.x, value.y);
}
return propInfo.defaultValue || v2(0, 0);
case "vec3":
if (typeof value === "object" && typeof value.x === "number" && typeof value.y === "number" && typeof value.z === "number") {
return v3(value.x, value.y, value.z);
}
return propInfo.defaultValue || v3(0, 0, 0);
case "color":
if (typeof value === "object" && typeof value[0] === "number" && typeof value[1] === "number" && typeof value[2] === "number") {
return color(value[0], value[1], value[2], typeof value[3] === "number" ? value[3] : 255);
}
return propInfo.defaultValue || color(255, 255, 255, 255);
case "asset":
case "spriteframe":
case "prefab":
case "jsonAsset":
case "particle":
case "animation":
case "audio":
case "skeleton":
case "entity":
return typeof value === "string" ? value : (propInfo.defaultValue || "");
case "enum":
return value;
case "array":
if (Array.isArray(value)) {
return value;
}
return propInfo.defaultValue || [];
case "object":
if (typeof value === "object") {
return value;
}
return propInfo.defaultValue || {};
default:
break;
}
return undefined;
}
}

View File

@ -1,141 +0,0 @@
/**
* @Author: Gongxh
* @Date: 2025-01-14
* @Description:
*/
import { Color, Size, Vec2, Vec3 } from "cc";
import { ObjectHelper } from "../tool/helper/ObjectHelper";
export namespace _ecdecorator {
/** @internal */
const ECPropMeta = "__ecpropmeta__"
type ECPropType = "int" | "float" | "string" | "boolean" | "size" | "vec2" | "vec3" | "color" | "asset" | "spriteframe" | "jsonAsset" | "particle" | "animation" | "audio" | "prefab" | "skeleton" | "enum" | "array" | "object" | "entity";
interface ECPropInfoBase {
/** 属性默认值 */
defaultValue?: any,
/** 编辑器中的显示名称 */
displayName?: string,
/** 编辑器中的提示 */
tips?: string
}
interface ECPropInfoNumber extends ECPropInfoBase {
/** 属性类型 */
type: "int" | "float";
/** 默认值:0 */
defaultValue?: number;
}
interface ECPropInfoBoolean extends ECPropInfoBase {
/** 属性类型 */
type: "boolean";
/** 默认值:false */
defaultValue?: boolean;
}
interface ECPropInfoSize extends ECPropInfoBase {
/** 属性类型 */
type: "size";
/** 默认值:Size(0,0) */
defaultValue?: Size;
}
interface ECPropInfoVec extends ECPropInfoBase {
/** 属性类型 */
type: "vec2" | "vec3";
/** 默认值: Vec2(0,0) | Vec3(0,0,0) */
defaultValue?: Vec2 | Vec3;
}
interface ECPropInfoString extends ECPropInfoBase {
/** 属性类型 */
type: "string" | "asset" | "spriteframe" | "jsonAsset" | "particle" | "animation" | "audio" | "prefab" | "skeleton" | "entity";
/** 默认值: "" */
defaultValue?: string;
}
interface ECPropInfoColor extends ECPropInfoBase {
/** 属性类型 */
type: "color";
/** 默认值:Color(255, 255, 255, 255) */
defaultValue?: Color;
}
interface ECPropInfoArray extends ECPropInfoBase {
/** 属性类型 */
type: "array";
/** 类型格式 当类型是复合类型enum、array、object时必须 */
format: ECPropType | ECPropInfo;
}
interface ECPropInfoObject extends ECPropInfoBase {
/** 属性类型 */
type: "object";
/** 类型格式 当类型是复合类型enum、array、object时必须 */
format: Record<string, ECPropType> | Record<string, ECPropInfo>;
}
interface ECPropInfoEnum extends ECPropInfoBase {
type: "enum";
/** 枚举值 */
format: object;
/** 默认值 */
defaultValue?: string | number;
}
export type ECPropInfo = ECPropInfoNumber | ECPropInfoBoolean | ECPropInfoSize | ECPropInfoVec | ECPropInfoString | ECPropInfoColor | ECPropInfoArray | ECPropInfoObject | ECPropInfoEnum;
/**
*
*/
export interface ECComponentInfo {
/** 组件名 */
name: string;
/** 组件类型 */
componentType: number;
/** 组件描述 */
describe: string;
/** 属性 */
props: Record<string, ECPropInfo>;
}
/** 用来存储组件注册信息 */
const eclassMap: Map<any, ECComponentInfo> = new Map();
/** 获取组件注册信息 */
export function getComponentMaps(): Map<any, ECComponentInfo> {
return eclassMap;
}
/**
*
* @param {string} res.describe
*/
export function ecclass(name: string, componentType: number, res?: { describe?: string }): Function {
/** target 类的构造函数 */
return function (ctor: any): void {
// console.log(`组件装饰器 组件【${name}】属性:`, JSON.stringify(ctor[ECPropMeta]));
eclassMap.set(ctor, {
name: name,
componentType: componentType,
props: ctor[ECPropMeta],
describe: res?.describe || name
});
};
}
/** 组件属性装饰器 */
export function ecprop(options: ECPropInfo): any {
return function (target: any, propName: any): void {
ObjectHelper.getObjectProp(target.constructor, ECPropMeta)[propName] = options;
};
}
}
let _global = globalThis || window || global;
(_global as any)["getKunpoRegisterECMaps"] = function () {
return _ecdecorator.getComponentMaps() as any;
};

View File

@ -1,172 +0,0 @@
/**
* @Author: Gongxh
* @Date: 2025-01-14
* @Description:
*/
import { Node } from "cc";
import { ECDataHelper } from "./ECDataHelper";
import { Entity } from "./Entity";
import { EntityManager } from "./EntityManager";
interface IEntityConfig {
[componentName: string]: Record<string, any>
}
interface IWorldConfig {
/** 实体管理器 */
world: EntityManager;
/** 世界节点 */
worldNode: Node;
}
export class ECManager {
/** 实体管理器 @internal */
private static _worlds: Map<string, IWorldConfig> = new Map();
/** 实体配置信息 @internal */
private static _entityList: { [name: string]: Record<string, any> } = {};
/** 注册所有组件 如果GameEntry因分包导致组件的代码注册晚于 CocosEntry的 onInit函数, 则需要在合适的时机手动调用此方法 */
public static registerComponents(): void {
ECDataHelper.registerComponents();
}
/**
* 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 {
if (this._worlds.has(worldName)) {
throw new Error(`ECWorld ${worldName} already exists`);
}
const entityManager = new EntityManager(worldName, ECDataHelper.getComponentPool(), componentUpdateOrderList, maxCapacityInPool, preloadEntityCount);
this._worlds.set(worldName, { world: entityManager, worldNode: node });
return entityManager;
}
/** 获取EC世界 */
public static getECWorld(worldName: string): EntityManager {
if (!this._worlds.has(worldName)) {
throw new Error(`ECWorld ${worldName} not found`);
}
const entityManager = this._worlds.get(worldName).world;
if (!entityManager) {
throw new Error(`ECWorld ${worldName} is null`);
}
return entityManager;
}
/** 获取EC世界节点 */
public static getECWorldNode(worldName: string): Node {
if (!this._worlds.has(worldName)) {
throw new Error(`ECWorld ${worldName} not found`);
}
const node = this._worlds.get(worldName).worldNode;
if (!node) {
throw new Error(`ECWorld ${worldName} is null`);
}
return node;
}
/** 销毁EC世界 */
public static destroyECWorld(worldName: string): void {
let entityManager = this.getECWorld(worldName);
if (entityManager) {
entityManager.destroy();
this._worlds.delete(worldName);
}
}
/**
*
* @param config {: {组件名: 组件数据}}
*/
public static registerEntityConfig(config: { [entityName: string]: IEntityConfig }): void {
if (!config) {
return;
}
// 遍历并注册每个实体的配置
for (const entityName in config) {
this._entityList[entityName] = config[entityName];
}
}
/**
* (, )
*
* @param name
* @param info
*/
public static addEntityInfo(name: string, info: IEntityConfig): void {
if (this._entityList[name]) {
this._entityList[name] = Object.assign(this._entityList[name], info);
} else {
this._entityList[name] = info;
}
}
/** 获取实体配置信息 */
public static getEntityInfo(name: string): Record<string, any> {
if (!this._entityList[name]) {
throw new Error(`Entity ${name} info not found, please register it first`);
}
return this._entityList[name];
}
/**
*
* @param worldName
* @param name
* @returns {kunpo.Entity}
*/
public static createEntity(worldName: string, name: string): Entity {
let info = this.getEntityInfo(name);
let world = this.getECWorld(worldName);
let entity = world.createEntity(name);
info && this._addComponentToEntity(world, entity, info);
world.addEntity(entity);
return entity;
}
/**
*
* @param {EntityManager} world
* @param {Entity} entity
* @param {Record<string, any>} componentsData
* @internal
*/
private static _addComponentToEntity(world: EntityManager, entity: Entity, componentsData: Record<string, any>): void {
for (const componentName in componentsData) {
let component = world.createComponent(componentName);
ECDataHelper.parse(component, componentsData[componentName]);
entity.addComponent(component);
}
}
/**
*
* @param worldName
* @param entity
*/
public static destroyEntity(worldName: string, entity: Entity): void {
if (!entity || !entity.id) {
return;
}
this.destroyEntityById(worldName, entity.id);
}
/**
*
* @param worldName
* @param entityId ID
*/
public static destroyEntityById(worldName: string, entityId: number): void {
let world = this.getECWorld(worldName);
world.destroyEntityById(entityId);
}
}

View File

@ -1,90 +0,0 @@
/**
* @type {&} AND 1 1 0
* @type {|} OR 1 1
* @type {~} 使 1 00 1
* @type {^} 1 0
* @type {<<} << << 0; 24
* @type {>>} >> >>
* @type {>>>} 使0
*/
import { Stack } from "../tool/DataStruct/Stack";
/** 实体索引位数 @internal */
export const EntityIndexBits = 16;
/** 实体索引掩码 @internal */
export const EntityIndexMask = (1 << EntityIndexBits) - 1;
/** 最大实体数量 @internal */
export const MaxEntityCount = 1 << EntityIndexBits;
export type EntityId = number;
/**
* 210 ()
* @param {number} bitNumber
* @return {number}
* @internal
*/
export function bit2Decimal(bitNumber: number): number {
let bitString = String(bitNumber);
let len = bitString.length;
let index = len - 1;
let result: number = 0;
do {
result += Number(bitString[index]) * Math.pow(2, len - index - 1);
index--;
} while (index >= 0);
return result;
}
/**
* 102 ()
* @param {number} num
* @return {number}
* @internal
*/
export function decimal2Bit(num: number): number {
let stack = new Stack<number>();
let dividend: number = Math.floor(num);
let remainder: number;
do {
remainder = dividend % 2;
stack.push(remainder);
dividend = Math.floor(dividend / 2);
} while (dividend > 0);
let result = "";
while (!stack.isEmpty()) {
result += stack.pop().toString();
}
return Number(result);
}
/**
* id获取实体index
* @param id id
* @return {number} index
* @internal
*/
export function getEntityIndex(id: EntityId): number {
return id & EntityIndexMask;
}
/**
* id获取实体版本
* @param id id
* @return {number}
* @internal
*/
export function getEntityVersion(id: EntityId): number {
return id >>> EntityIndexBits;
}
/**
*
* @param id id
* @return {string}
* @internal
*/
export function entityIdString(id: EntityId): string {
return `${getEntityIndex(id)}:${getEntityVersion(id)}`;
}
// console.log("-------->", EntityIndexBits); 16
// console.log("-------->", EntityIndexMask); 65535
// console.log("-------->", MaxEntityCount); 65536

View File

@ -1,276 +0,0 @@
import { Component } from "./Component";
import { EntityId } from "./ECType";
import { EntityManager } from "./EntityManager";
export class Entity {
/**
*
* @type {String}
*/
public name: string;
/**
* ID
* @type {EntityId}
*/
public id: EntityId;
/**
*
* @type {Set<number>}
* @memberof Entity
*/
public tags: Set<number>;
/**
*
* @type {Map<number, number>}
* @memberof Entity
*/
public states: Map<number, number>;
/**
*
* @type {boolean}
*/
public active: boolean = false;
/**
*
* @private
* @type {EntityManager}
*/
public entityManager: EntityManager;
/**
*
* @type {Map<number, Component>}
* @type {number}
* @type {Component}
*/
public readonly components: Map<number, Component> = new Map();
/**
* EntityManager
* @internal
*/
public _add(): void {
this.active = true;
for (const component of this.components.values()) {
component._enter();
}
}
/**
*
* @internal
*/
public _destroy(): void {
this.removeAllComponents();
this.tags && this.tags.clear();
this.states && this.states.clear();
this.active = false;
this.entityManager = null;
}
/**
*
* @param {number[]} ...tags EntityEntityManager获取指定标签的Entity
*/
public addTag(...tag: number[]): void {
let tags = this.tags;
if (!tags) {
tags = this.tags = new Set<number>();
}
for (let i = 0; i < tag.length; i++) {
tags.add(tag[i]);
this.active && this.entityManager && this.entityManager._addEntityTag(this.id, tag[i]);
}
}
/**
*
* @param {number} tag
*/
public removeTag(tag: number): void {
if (this.tags) {
this.tags.delete(tag);
this.active && this.entityManager && this.entityManager._removeEntityTagById(this.id, tag);
}
}
/**
*
* @param {number} tag
* @returns {boolean}
*/
public hasTag(...tag: number[]): boolean {
let tags = this.tags;
if (!tags) {
return false;
}
for (let i = 0; i < tag.length; i++) {
if (tags.has(tag[i])) {
return true;
}
}
return false;
}
/**
*
* @param {number} componentType
* @returns {T}
*/
public getComponent<T extends Component>(componentType: number): T {
return this.components.get(componentType) as T;
}
/**
*
* @param {Component} component
*/
public addComponent(component: Component): void {
if (this.hasComponent(component.type)) {
throw new Error(`组件{${component.constructor.name}类型:${component.type})已经存在,不允许添加同一类型组件`);
}
this.components.set(component.type, component);
component.entity = this;
component._add();
if (this.active) {
component._enter();
}
}
/**
*
* @param {number} componentType
*/
public removeComponent(componentType: number): void {
const component = this.components.get(componentType);
if (component) {
this.components.delete(componentType);
component._remove();
}
}
/**
*
*/
public removeAllComponents(): void {
for (const component of this.components.values()) {
component._remove();
}
this.components.clear();
}
/**
*
* @param {number} componentType
* @returns {boolean}
*/
public hasComponent(componentType: number): boolean {
return this.components.has(componentType);
}
/**
*
*/
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 {
this.entityManager && this.entityManager._addEvent(eventName, callback, this, once);
}
/**
*
* @param eventName
* @param entityId ID
* @param args
*/
public sendListener(eventName: string, ...args: any[]): void {
this.entityManager && this.entityManager._sendEvent(eventName, this, ...args);
}
public removeListener(eventName: string, callback?: (...args: any[]) => void): void {
this.entityManager && this.entityManager._removeEvent(eventName, this, callback);
}
/**
*
* addState和removeState成对存在
* @param {number} state
* @memberof Entity
*/
public addState(state: number): void {
let states = this.states;
if (!states) {
states = this.states = new Map<number, number>();
}
states.set(state, (states.get(state) || 0) + 1);
}
/**
*
*
* @param {number} state
* @returns {boolean} 0true
* @memberof Entity
*/
public removeState(state: number): boolean {
const states = this.states;
if (!states) {
return false;
}
let stateCount = states.get(state);
if (stateCount) {
// 处理状态计数为0则删除状态
--stateCount;
if (stateCount == 0) {
states.delete(state);
return true;
}
states.set(state, stateCount);
return false;
}
return true;
}
/**
*
* @param {number} state
* @returns {boolean}
* @memberof Entity
*/
public hasState(state: number): boolean {
return this.states && this.states.has(state);
}
/**
*
* @param {number} state
* @memberof Entity
*/
public clearState(state: number): void {
this.states && this.states.delete(state);
}
/**
*
* @memberof Entity
*/
public clearAllStates(): void {
this.states && this.states.clear();
}
}

View File

@ -1,442 +0,0 @@
import { EventManager } from "../event/EventManager";
import { warn } from "../tool/log";
import { Component } from "./Component";
import { ComponentManager } from "./ComponentManager";
import { ComponentPool } from "./ComponentPool";
import { EntityId, entityIdString, EntityIndexBits, getEntityIndex, getEntityVersion, MaxEntityCount } from "./ECType";
import { Entity } from "./Entity";
export class EntityManager {
/**
*
* @type {string}
*/
public name: string;
/**
*
* @type {Entity}
* @internal
*/
public readonly insEntity: Entity = new Entity();
/**
*
* @type {boolean}
* @internal
*/
public insActive: boolean = false;
/**
*
* @type {ComponentManager}
*/
public componentManager: ComponentManager;
/**
*
* @type {EventManager}
* @internal
*/
private _eventManager: EventManager;
/**
*
* @type {EventManager}
* @internal
*/
private _insEventManager: EventManager;
/** 实体池 @internal */
private readonly _entityPool: Entity[] = [];
/** tag标记池 @internal */
private readonly _tagToEntity: Map<number, Set<EntityId>> = new Map<number, Set<EntityId>>();
/** 实体回收池 @internal */
private _recyclePool: Entity[] = [];
/** 实体回收池最大容量 @internal */
private _maxCapacityInPool: number;
/** 实体回收版本 @internal */
private _entityVersion: number[] = [];
/** 回收实体ID @internal */
private _recycleEntityIds: EntityId[] = [];
/** 世界是否删除 @internal */
private _isDestroyed: boolean;
/** 是否正在更新 @internal */
private _updating: boolean;
/**
*
* @param {string} name
* @param {ComponentPool} componentPool
* @param {ComponentPool} componentUpdateOrderList
* @param {number} [maxCapacityInPool=128]
* @param {number} [preloadEntityCount=32] Entity数量
*/
constructor(name: string, componentPool: ComponentPool, componentUpdateOrderList: number[], maxCapacityInPool: number = 128, preloadEntityCount: number = 32) {
this.name = name;
if (preloadEntityCount >= MaxEntityCount) {
throw new Error(`预加载超出实体最大数量:${preloadEntityCount} >= max(${MaxEntityCount})`);
}
// 占位
this._entityPool.push(null);
this._entityVersion.push(1);
this._maxCapacityInPool = maxCapacityInPool;
// 预创建
for (let i = 0; i < preloadEntityCount; ++i) {
this._recyclePool.push(new Entity());
}
// 组件管理器
this.componentManager = new ComponentManager(componentPool, componentUpdateOrderList);
this.insEntity.entityManager = this;
}
/**
* 使
* @param {EntityId} entityId Id
* @param {number} tag
* @internal
*/
public _addEntityTag(entityId: EntityId, tag: number): void {
this._validateEntityById(entityId);
let entitiesByTag = this._tagToEntity.get(tag);
if (!entitiesByTag) {
entitiesByTag = new Set<EntityId>();
this._tagToEntity.set(tag, entitiesByTag);
}
entitiesByTag.add(entityId);
}
/**
* Tag使
* @param {Entity} entity
* @param {number} tag
* @internal
*/
public _removeEntityTag(entity: Entity, tag: number): void {
this._removeEntityTagById(entity.id, tag);
}
/**
* ID删除实体Tag使
* @param {EntityId} entityId Id
* @param {number} tag
* @internal
*/
public _removeEntityTagById(entityId: EntityId, tag: number): void {
this._validateEntityById(entityId);
const entitiesByTag = this._tagToEntity.get(tag);
if (entitiesByTag) {
entitiesByTag.delete(entityId);
}
}
/**
*
* @returns {Entity}
*/
public createEntity(name: string): Entity {
const entity = this._recyclePool.pop() || new Entity();
entity.id = 0;
entity.name = name;
entity.entityManager = this;
return entity;
}
/**
*
* @param {Entity} entity
*/
public addEntity(entity: Entity): void {
if (this.exists(entity.id)) {
throw new Error(`实体(${entityIdString(entity.id)}已经添加到EntityManager`);
}
// 分配实体Id
if (this._recycleEntityIds.length > 0) {
const newIndex = this._recycleEntityIds.pop();
this._entityPool[newIndex] = entity;
entity.id = (this._entityVersion[newIndex] << EntityIndexBits) | newIndex;
} else {
this._entityPool.push(entity);
this._entityVersion.push(1);
entity.id = MaxEntityCount | (this._entityPool.length - 1);
}
this._addEntityToTag(entity);
entity._add();
}
/**
*
* @param {Entity} entity
*/
public destroyEntity(entity: Entity): void {
this.destroyEntityById(entity.id);
}
/**
* ID实体
* @param {EntityId} entityId Id
*/
public destroyEntityById(entityId: EntityId): void {
const entity = this.getEntity(entityId);
if (!entity) {
warn(`实体(${entityIdString(entityId)})已经被销毁`);
return;
}
this._recycleEntity(entity);
this._eventManager && this._eventManager.removeList(entity);
}
/**
*
* @param {boolean} ignoreSingletonEntity
*/
public destroyAllEntities(ignoreSingletonEntity: boolean): void {
const entities = this._entityPool;
for (let i = 1, len = entities.length; i < len; ++i) {
if (entities[i]) {
this._destroyEntity(entities[i]);
}
}
this._recycleEntityIds.length = 0;
this._entityPool.length = 0;
this._entityVersion.length = 0;
this._tagToEntity.clear();
// 占位
this._entityPool.push(null);
this._entityVersion.push(1);
this._eventManager && this._eventManager.destroyAll();
// 销毁单例实体组件
if (!ignoreSingletonEntity) {
this.insEntity._destroy();
this.insActive = false;
this._insEventManager && this._insEventManager.destroyAll();
}
}
/**
* ID获取实体
* @param {EntityId} entityId Id
* @returns {(Entity | null)}
*/
public getEntity(entityId: EntityId): Entity | null {
const index = getEntityIndex(entityId);
if (index <= 0 || index >= this._entityPool.length) {
return null;
}
if (this._entityVersion[index] == getEntityVersion(entityId)) {
return this._entityPool[index];
}
return null;
}
/**
*
* @param {number} tag
* @returns {Entity[]}
*/
public getEntitiesByTag(tag: number): Entity[] {
let buffer: Entity[] = [];
const entitiesByTag = this._tagToEntity.get(tag);
if (entitiesByTag) {
for (const entityId of entitiesByTag) {
const entity = this.getEntity(entityId);
entity && buffer.push(entity);
}
}
return buffer;
}
/**
* ID判断实体是否存在
* @param {EntityId} entityId Id
* @returns {boolean}
*/
public exists(entityId: EntityId): boolean {
const index = getEntityIndex(entityId);
if (index <= 0 || index >= this._entityPool.length) {
return false;
}
const entity = this._entityPool[index];
return entity && this._entityVersion[index] == getEntityVersion(entityId);
}
/**
*
* @template T
* @param {string} componentName
* @returns {T}
*/
public createComponent<T extends Component>(componentName: string): T {
return this.componentManager.createComponent<T>(componentName);
}
/**
*
* @param component
*/
public addSingleton(component: Component): void {
this.insEntity.addComponent(component);
}
/**
*
*/
public getSingleton<T extends Component>(componentType: number): T {
return this.insEntity.getComponent<T>(componentType);
}
/**
*
*/
public removeSingleton(componentType: number): void {
this.insEntity.removeComponent(componentType);
}
/**
*
*/
public hasSingleton(componentType: number): boolean {
return this.insEntity.hasComponent(componentType);
}
/**
*
*/
public activeSingleton(): void {
const insEntity = this.insEntity;
if (this.insActive) {
throw new Error("单例实体已经被激活");
}
this.insActive = true;
insEntity.id = -1;
insEntity._add();
}
/**
* EntityManager
*/
public destroy(): void {
if (this._isDestroyed) {
return;
}
if (this._updating) {
throw new Error("请勿在更新时销毁EntityManager");
}
this.destroyAllEntities(false);
this.componentManager.destroy();
this._isDestroyed = true;
}
/**
* (使)
* @param eventName
* @param callback
* @param entityId ID
* @param once
* @internal
*/
public _addEvent(eventName: string, callback: (...args: any[]) => void, entity: Entity, once: boolean = false): void {
if (entity == this.insEntity) {
this._insEventManager = this._insEventManager ? this._insEventManager : new EventManager();
this._insEventManager._addEvent(eventName, callback, once, entity);
return;
}
this._eventManager = this._eventManager ? this._eventManager : new EventManager();
this._eventManager._addEvent(eventName, callback, once, entity);
}
/**
* (使)
* @param eventName
* @param entityId ID
* @param args
* @internal
*/
public _sendEvent(eventName: string, entity: Entity, ...args: any[]): void {
if (entity == this.insEntity) {
this._insEventManager && this._insEventManager.send(eventName, entity, ...args);
return;
}
this._eventManager && this._eventManager.send(eventName, entity, ...args);
}
/**
* (使)
* @param eventName
* @param entity
* @param callback
* @internal
*/
public _removeEvent(eventName: string, entity: Entity, callback?: (...args: any[]) => void): void {
if (entity == this.insEntity) {
this._insEventManager && this._insEventManager.remove(eventName, callback, entity);
return;
}
this._eventManager && this._eventManager.remove(eventName, callback, entity);
}
/**
*
* @param {number} dt
*/
public update(dt: number): void {
this._updating = true;
this.componentManager._update(dt);
this._updating = false;
}
/**
* Entity
* @param {Entity} entity Entity
* @internal
*/
private _recycleEntity(entity: Entity): void {
// 回收实体Id
const entityIndex = getEntityIndex(entity.id);
this._recycleEntityIds.push(entityIndex);
this._entityPool[entityIndex] = null;
++this._entityVersion[entityIndex];
this._destroyEntity(entity);
}
/**
*
* @param {Entity} entity
* @internal
*/
private _destroyEntity(entity: Entity): void {
entity._destroy();
if (this._recyclePool.length < this._maxCapacityInPool) {
this._recyclePool.push(entity);
}
}
/**
* tag添加到tag列表中
* @param entity
* @internal
*/
private _addEntityToTag(entity: Entity): void {
const tags = entity.tags;
if (!tags || tags.size == 0) {
return;
}
const entityId = entity.id;
for (const tag of tags.values()) {
this._addEntityTag(entityId, tag);
}
}
/**
* ID是否存在
* @param {EntityId} entityId ID
* @internal
*/
private _validateEntityById(entityId: EntityId): void {
if (!this.exists(entityId)) {
throw new Error(`实体(${entityId})不存在`);
}
}
}

View File

@ -1,17 +0,0 @@
export class ObjectBase {
/** 是否被回收 */
public recycled: boolean;
/** 对象类型 */
public objectType: number;
/** 回收 @internal */
public _recycle(): void {
this.recycled = true;
}
/** 重新利用 @internal */
public _reuse(): void {
this.recycled = false;
}
}

View File

@ -1,65 +0,0 @@
import { ObjectBase } from "./ObjectBase";
/** @internal */
export class ObjectFactory {
/** 对象类 */
private _ctor: new () => ObjectBase;
/** 对象名称 */
private _name: string;
/** 对象类型 */
private _objectType: number;
/** 最大容量 */
private _maxCapacity: number;
/** 对象池 */
private _stack: ObjectBase[] = [];
constructor(objectType: number, capacity: number, name: string, objectClass: new () => ObjectBase) {
this._objectType = objectType;
this._maxCapacity = capacity;
this._name = name;
this._ctor = objectClass;
}
/**
*
* @returns {string}
*/
public get name(): string {
return this._name;
}
/**
*
* @returns {T}
*/
public allocate<T extends ObjectBase>(): T {
if (this._stack.length == 0) {
const ret = new this._ctor() as T;
ret.objectType = this._objectType;
return ret;
}
const ret = this._stack.pop() as T;
ret._reuse();
return ret;
}
/**
*
* @returns {boolean}
*/
public recycle(ret: ObjectBase): boolean {
if (ret.recycled) {
throw new Error(`对象(${ret.constructor.name})已经被回收了`);
}
if (this._maxCapacity > 0 && this._stack.length < this._maxCapacity) {
ret._recycle();
this._stack.push(ret);
return true;
}
return false;
}
public _clear(): void {
this._stack.length = 0;
}
}

View File

@ -23,6 +23,8 @@ export abstract class WindowBase extends GComponent implements IWindow {
private _header: IWindowHeader = null;
/** 窗口是否被遮挡了 @internal */
private _isCover: boolean = false;
/** 吞噬触摸的节点 @internal */
private _swallowComponent: GComponent = null;
/**
* (使)
* @param swallowTouch
@ -33,14 +35,16 @@ export abstract class WindowBase extends GComponent implements IWindow {
if (swallowTouch) {
// 吞噬触摸事件,需要一个全屏的节点, 窗口本身可能留有安全区的边
let bgNode = new GComponent();
bgNode.name = "swallow";
bgNode.setSize(Screen.ScreenWidth, Screen.ScreenHeight, true);
bgNode.setPivot(0.5, 0.5, true);
bgNode.setPosition(Screen.ScreenWidth * 0.5, Screen.ScreenHeight * 0.5);
bgNode.setPosition(this.width * 0.5, this.height * 0.5);
this.addChild(bgNode);
// 调整显示层级
bgNode.parent.setChildIndex(bgNode, 0);
bgNode.onClick(this.onEmptyAreaClick, this);
bgNode.opaque = swallowTouch;
this._swallowComponent = bgNode;
}
// 窗口自身也要设置是否吞噬触摸
this.opaque = swallowTouch;
@ -65,6 +69,8 @@ export abstract class WindowBase extends GComponent implements IWindow {
default:
break;
}
this._swallowComponent?.setSize(Screen.ScreenWidth, Screen.ScreenHeight, true);
this._swallowComponent?.setPosition(this.width * 0.5, + this.height * 0.5);
this.onAdapted();
}

View File

@ -28,25 +28,6 @@ export { Socket } from "./net/socket/Socket";
/** 读取网络文件 */
export { ReadNetFile } from "./net/nettools/ReadNetFile";
/** 四叉树 */
export { Box } from "./quadtree/Box";
export { Circle } from "./quadtree/Circle";
export { IShape } from "./quadtree/IShape";
export { Polygon } from "./quadtree/Polygon";
export { QTConfig, QuadTree } from "./quadtree/QuadTree";
/** 行为树 */
export { Agent as BTAgent } from "./behaviortree/Agent";
export { BehaviorTree } from "./behaviortree/BehaviorTree";
export { Blackboard as BTBlackboard } from "./behaviortree/Blackboard";
export * as BTAction from "./behaviortree/BTNode/Action";
export * as BTNode from "./behaviortree/BTNode/BaseNode";
export * as BTComposite from "./behaviortree/BTNode/Composite";
export * as BTCondition from "./behaviortree/BTNode/Condition";
export * as BTDecorator from "./behaviortree/BTNode/Decorator";
export { Status as BTStatus } from "./behaviortree/header";
export { Ticker as BTTicker } from "./behaviortree/Ticker";
/** UI */
export { Window } from "./fgui/Window";
export { WindowHeader } from "./fgui/WindowHeader";
@ -57,23 +38,10 @@ export { WindowGroup } from "./ui/WindowGroup";
export { WindowHeaderInfo } from "./ui/WindowHeaderInfo";
export { WindowManager } from "./ui/WindowManager";
/** EC */
export { Component } from './ecmodule/Component';
export { ComponentManager } from './ecmodule/ComponentManager';
export { ComponentPool } from './ecmodule/ComponentPool';
export { _ecdecorator } from './ecmodule/ECDecorator';
export { ECManager } from './ecmodule/ECManager';
export { Entity } from './ecmodule/Entity';
export { EntityManager } from './ecmodule/EntityManager';
/** 引擎相关 */
export { CocosEntry } from "./cocos/CocosEntry";
export { CocosUIModule } from "./cocos/CocosUIModule";
/** 资源相关 */
export { AssetLoader, IAssetConfig } from "./asset/AssetLoader";
export { AssetPool } from "./asset/AssetPool";
/** 条件显示节点 */
export { _conditionDecorator } from "./condition/ConditionDecorator";
export { ConditionManager } from "./condition/ConditionManager";

View File

@ -4,7 +4,6 @@
* @Description:
*/
import { GlobalEvent } from "../../global/GlobalEvent";
import { HttpRequest } from "./HttpRequest";
import { IHttpEvent } from "./IHttpEvent";
import { IHttpResponse } from "./IHttpResponse";
@ -82,23 +81,15 @@ export class HttpManager {
* @param {number} timeout (s) 0 (0)
* @internal
*/
private static _send(method: HttpRequestMethod, url: string, data: any, responseType: HttpResponseType, netEvent?: IHttpEvent, headers?: any[], timeout?: number): HttpRequest {
private static _send(method: HttpRequestMethod, url: string, data: any, responseType: HttpResponseType, netEvent: IHttpEvent, headers?: any[], timeout?: number): HttpRequest {
let http = new HttpRequest()
http.setNetCallback((result: "succeed" | "fail", response: IHttpResponse) => {
switch (result) {
case "succeed":
if (netEvent) {
netEvent.onComplete(response);
} else {
GlobalEvent.send(HttpManager.HttpEvent, result, response);
}
netEvent?.onComplete(response);
break;
case "fail":
if (netEvent) {
netEvent.onError(response);
} else {
GlobalEvent.send(HttpManager.HttpEvent, result, response);
}
netEvent?.onError(response);
break;
}
});

View File

@ -1,38 +0,0 @@
/**
* @Author: Gongxh
* @Date: 2024-12-21
* @Description:
*/
import { v2, Vec2 } from "cc";
import { ShapeType } from "./IShape";
import { Polygon } from "./Polygon";
// 3|2
// --
// 0|1
// 矩形的四个点
export class Box extends Polygon {
public get shapeType(): ShapeType {
return ShapeType.BOX;
}
constructor(x: number, y: number, width: number, height: number, tag: number = -1) {
let points: Vec2[] = new Array(4);
points[0] = v2(x, y);
points[1] = v2(x + width, y);
points[2] = v2(x + width, y + height);
points[3] = v2(x, y + height);
super(points, tag);
}
public resetPoints(x: number, y: number, width: number, height: number): void {
let points: Vec2[] = new Array(4);
points[0] = v2(x, y);
points[1] = v2(x + width, y);
points[2] = v2(x + width, y + height);
points[3] = v2(x, y + height);
this.points = points;
}
}

View File

@ -1,42 +0,0 @@
/**
* @Author: Gongxh
* @Date: 2024-12-21
* @Description:
*/
import { Rect } from "cc";
import { ShapeType } from "./IShape";
import { Shape } from "./Shape";
export class Circle extends Shape {
private _radius: number; // 半径
public get shapeType(): ShapeType {
return ShapeType.CIRCLE;
}
constructor(radius: number, tag: number = -1) {
super(tag);
this._radius = radius;
this._boundingBox.x = -this._radius;
this._boundingBox.y = -this._radius;
this._boundingBox.width = this._radius * 2;
this._boundingBox.height = this._radius * 2;
}
public getBoundingBox(): Rect {
return this._boundingBox;
}
public get radius(): number {
return this._radius;
}
public set radius(value: number) {
this._radius = value;
this._boundingBox.x = -this._radius;
this._boundingBox.y = -this._radius;
this._boundingBox.width = this._radius * 2;
this._boundingBox.height = this._radius * 2;
}
}

View File

@ -1,32 +0,0 @@
/**
* @Author: Gongxh
* @Date: 2025-05-27
* @Description:
*/
import { Rect, Vec2 } from "cc";
export enum ShapeType {
CIRCLE = 1,
BOX = 2,
POLYGON = 3,
}
export interface IShape {
/** 形状类型 */
get shapeType(): ShapeType;
/** 形状掩码 @internal */
get mask(): number;
/** 是否有效 @internal */
get isValid(): boolean;
get position(): Vec2;
get scale(): number;
get rotation(): number;
/** 获取包围盒 */
getBoundingBox(): Rect;
setPosition(x: number, y: number): void;
setScale(value: number): void;
setRotation(angle: number): void;
destroy(): void;
}

View File

@ -1,92 +0,0 @@
/**
* @Author: Gongxh
* @Date: 2024-12-21
* @Description:
*/
import { Rect, v2, Vec2 } from "cc";
import { ShapeType } from "./IShape";
import { Shape } from "./Shape";
const vec2 = new Vec2();
/** 点绕原点旋转 radians 弧度后的新点 */
function rotate(radians: number, x: number, y: number): Vec2 {
let sin = Math.sin(radians);
let cos = Math.cos(radians);
vec2.x = x * cos - y * sin;
vec2.y = y * cos + x * sin;
return vec2;
}
// /** 点绕点旋转 radians 弧度后的新点 */
// export function rotateByPoint(radians: number, x: number, y: number, cx: number, cy: number): Vec2 {
// let sin = Math.sin(radians);
// let cos = Math.cos(radians);
// vec2.x = (x - cx) * cos - (y - cy) * sin + cx;
// vec2.y = (y - cy) * cos + (x - cx) * sin + cy;
// return vec2;
// }
export class Polygon extends Shape {
protected _points: Vec2[] = []; // 多边形
protected _realPoints: Vec2[];
public get shapeType(): ShapeType {
return ShapeType.POLYGON;
}
constructor(points: Vec2[], tag: number = -1) {
super(tag);
this._points = points;
this._realPoints = new Array(points.length);
for (let i = 0, len = points.length; i < len; i++) {
this._realPoints[i] = v2(points[i].x, points[i].y);
}
this.getBoundingBox();
}
public getBoundingBox(): Rect {
if (this._isDirty) {
let minX = Number.MAX_VALUE;
let maxX = Number.MIN_VALUE;
let minY = Number.MAX_VALUE;
let maxY = Number.MIN_VALUE;
for (const point of this._points) {
let a = rotate(Math.PI / 180 * this._rotation, point.x, point.y);
minX = Math.min(minX, a.x);
minY = Math.min(minY, a.y);
maxX = Math.max(maxX, a.x);
maxY = Math.max(maxY, a.y);
}
this._boundingBox.x = minX;
this._boundingBox.y = minY;
this._boundingBox.width = maxX - minX;
this._boundingBox.height = maxY - minY;
this._isDirty = false;
}
return this._boundingBox;
}
public get points(): Vec2[] {
let points = this._points;
let len = points.length;
for (let i = 0; i < len; i++) {
let m = points[i];
this._realPoints[i] = m.rotate(Math.PI / 180 * this.rotation);
let a = this._realPoints[i];
a.x = a.x * this.scale + this.position.x;
a.y = a.y * this.scale + this.position.y;
}
return this._realPoints;
}
public set points(pts: Vec2[]) {
this._points = pts;
this._realPoints = new Array(pts.length);
for (let i = 0, len = pts.length; i < len; i++) {
this._realPoints[i] = v2(pts[i].x, pts[i].y);
}
this._isDirty = true;
}
}

View File

@ -1,372 +0,0 @@
/**
* @Author: Gongxh
* @Date: 2024-12-21
* @Description:
*/
import { Graphics, Intersection2D, rect, Rect } from "cc";
import { Circle } from "./Circle";
import { IShape, ShapeType } from "./IShape";
import { Polygon } from "./Polygon";
// 1|0
// ---
// 2|3
const enum Quadrant {
ONE = 0,
TWO,
THREE,
FOUR,
MORE, // 多个象限
}
const circleCircle = Intersection2D.circleCircle;
const polygonCircle = Intersection2D.polygonCircle;
const polygonPolygon = Intersection2D.polygonPolygon;
/** 两个形状是否碰撞 */
function isCollide(shape1: IShape, shape2: IShape): boolean {
if (shape1.shapeType === ShapeType.CIRCLE) {
if (shape2.shapeType === ShapeType.CIRCLE) {
return circleCircle(shape1.position, (shape1 as Circle).radius * shape1.scale, shape2.position, (shape2 as Circle).radius * shape2.scale);
} else if (shape2.shapeType === ShapeType.BOX || shape2.shapeType === ShapeType.POLYGON) {
return polygonCircle((shape2 as Polygon).points, shape1.position, (shape1 as Circle).radius * shape1.scale);
}
} else if (shape1.shapeType === ShapeType.BOX || shape1.shapeType === ShapeType.POLYGON) {
if (shape2.shapeType === ShapeType.CIRCLE) {
return polygonCircle((shape1 as Polygon).points, shape2.position, (shape2 as Circle).radius * shape2.scale);
} else if (shape2.shapeType === ShapeType.BOX || shape2.shapeType === ShapeType.POLYGON) {
return polygonPolygon((shape2 as Polygon).points, (shape1 as Polygon).points);
}
}
return false;
}
export const QTConfig = {
/** 每个节点(象限)所能包含物体的最大数量 */
MAX_SHAPES: 12,
/** 四叉树的最大深度 */
MAX_LEVELS: 5,
}
export class QuadTree {
/** @internal */
private _graphics: Graphics;
/** @internal */
private _shapes_map: Map<number, IShape[]>; // 根据类型存储形状对象
/** @internal */
private _trees: QuadTree[] = []; // 存储四个子节点
/** @internal */
private _level: number; // 树的深度
/** @internal */
private _bounds: Rect; // 树的外框
/** @internal */
private _ignore_shapes: IShape[] = []; // 不在树中的形状
/**
*
* @param rect
* @param level 0
* @param graphics cc中用于绘制树的绘制组件
*/
constructor(rect: Rect, level: number = 0, graphics: Graphics = undefined) {
this._shapes_map = new Map();
this._trees = [];
this._level = level || 0;
this._bounds = rect;
this._graphics = graphics;
}
/**
*
* @param shape
*
*
*/
public insert(shape: IShape): void {
// 如果该节点下存在子节点
if (this._trees.length > 0) {
let quadrant = this._getQuadrant(shape);
if (quadrant !== Quadrant.MORE) {
this._trees[quadrant].insert(shape);
return;
}
}
if (this._level == 0 && !this._isInner(shape, this._bounds)) {
// 插入跟节点并且形状不在根节点的框内,则把形状放入忽略列表中
this._ignore_shapes.push(shape);
} else {
// 存储在当前节点下
this._insert(shape);
// 如果当前节点存储的数量超过了 MAX_OBJECTS并且深度没超过 MAX_LEVELS则继续拆分
if (!this._trees.length && this._size() > QTConfig.MAX_SHAPES && this._level < QTConfig.MAX_LEVELS) {
this._split();
for (const shapes of this._shapes_map.values()) {
let length = shapes.length - 1;
for (let i = length; i >= 0; i--) {
let quadrant = this._getQuadrant(shapes[i]);
if (quadrant !== Quadrant.MORE) {
this._trees[quadrant].insert(shapes.splice(i, 1)[0]);
}
}
}
}
}
}
/** @internal */
private _insert(shape: IShape): void {
if (!this._shapes_map.has(shape.mask)) {
this._shapes_map.set(shape.mask, []);
}
this._shapes_map.get(shape.mask).push(shape);
}
/**
*
* ...
*/
public collide(shape: IShape, tag: number = -1): IShape[] {
let result: IShape[] = [];
if (this._trees.length > 0) {
let quadrant = this._getQuadrant(shape);
if (quadrant === Quadrant.MORE) {
let len = this._trees.length - 1;
for (let i = len; i >= 0; i--) {
result.push(...this._trees[i].collide(shape, tag));
}
} else {
result.push(...this._trees[quadrant].collide(shape, tag));
}
}
for (const key of this._shapes_map.keys()) {
if (!(tag & key)) {
continue;
}
let shapes = this._shapes_map.get(key);
for (const other_shape of shapes) {
if (other_shape.isValid && shape !== other_shape && isCollide(shape, other_shape)) {
result.push(other_shape);
}
}
}
return result;
}
/**
*
*/
public update(root?: QuadTree): void {
root = root || this;
let isRoot = (root === this);
isRoot && this.graphicsClear();
this._updateIgnoreShapes(root);
this._updateShapes(root);
// 递归刷新子象限
for (const tree of this._trees) {
tree.update(root);
}
this._removeChildTree();
this.graphicsTreeBound(root);
if (isRoot && this._graphics) {
this._graphics.stroke();
}
}
public clear(): void {
this._level = 0;
this._ignore_shapes.length = 0;
this._shapes_map.clear();
for (const tree of this._trees) {
tree.clear();
}
this._trees.length = 0;
}
/** 当前形状是否包含在象限内 @internal */
private _isInner(shape: IShape, bounds: Rect): boolean {
let rect = shape.getBoundingBox();
return (
rect.xMin * shape.scale + shape.position.x > bounds.xMin &&
rect.xMax * shape.scale + shape.position.x < bounds.xMax &&
rect.yMin * shape.scale + shape.position.y > bounds.yMin &&
rect.yMax * shape.scale + shape.position.y < bounds.yMax
);
}
/**
* :
* @param {Shape} shape
*
*
*
*
* @internal
*/
private _getQuadrant(shape: IShape): Quadrant {
let bounds = this._bounds;
let rect = shape.getBoundingBox();
let center = bounds.center;
let onTop = rect.yMin * shape.scale + shape.position.y > center.y;
let onBottom = rect.yMax * shape.scale + shape.position.y < center.y;
let onLeft = rect.xMax * shape.scale + shape.position.x < center.x;
let onRight = rect.xMin * shape.scale + shape.position.x > center.x;
if (onTop) {
if (onRight) {
return Quadrant.ONE;
} else if (onLeft) {
return Quadrant.TWO;
}
} else if (onBottom) {
if (onLeft) {
return Quadrant.THREE;
} else if (onRight) {
return Quadrant.FOUR;
}
}
return Quadrant.MORE; // 跨越多个象限
}
/**
*
* MAX_OBJECTS最大数量
*
*
* @internal
*/
private _split(): void {
let bounds = this._bounds;
let x = bounds.x;
let y = bounds.y;
let halfwidth = bounds.width * 0.5;
let halfheight = bounds.height * 0.5;
let nextLevel = this._level + 1;
this._trees.push(
new QuadTree(rect(bounds.center.x, bounds.center.y, halfwidth, halfheight), nextLevel, this._graphics),
new QuadTree(rect(x, bounds.center.y, halfwidth, halfheight), nextLevel, this._graphics),
new QuadTree(rect(x, y, halfwidth, halfheight), nextLevel, this._graphics),
new QuadTree(rect(bounds.center.x, y, halfwidth, halfheight), nextLevel, this._graphics)
);
}
/** 删除子树 @internal */
private _removeChildTree(): void {
if (this._trees.length > 0) {
if (this._totalSize() <= 0) {
this._trees.length = 0;
}
}
}
/** 更新忽略掉的形状 @internal */
private _updateIgnoreShapes(root: QuadTree): void {
let shapes = this._ignore_shapes;
let lastIndex = shapes.length - 1;
let index = 0;
while (index < lastIndex) {
let shape = shapes[index];
if (!shape.isValid) {
if (index !== lastIndex) {
shapes[index] = shapes[lastIndex];
}
shapes.pop();
lastIndex--;
continue;
}
if (this._isInner(shape, this._bounds)) {
if (index !== lastIndex) {
[shapes[index], shapes[lastIndex]] = [shapes[lastIndex], shapes[index]];
}
root.insert(shapes.pop());
} else {
index++;
}
}
}
/** 更新有效的形状 @internal */
private _updateShapes(root: QuadTree): void {
for (const shapes of this._shapes_map.values()) {
let lastIndex = shapes.length - 1;
let index = 0;
while (index <= lastIndex) {
let shape = shapes[index];
if (!shape.isValid) {
if (index !== lastIndex) {
shapes[index] = shapes[lastIndex];
}
shapes.pop();
lastIndex--;
continue;
}
if (!this._isInner(shape, this._bounds)) {
// 如果矩形不属于该象限,则将该矩形重新插入根节点
if (index !== lastIndex) {
[shapes[index], shapes[lastIndex]] = [shapes[lastIndex], shapes[index]];
}
root.insert(shapes.pop());
lastIndex--;
} else if (this._trees.length > 0) {
// 如果矩形属于该象限且该象限具有子象限,则将该矩形安插到子象限中
let quadrant = this._getQuadrant(shape);
if (quadrant !== Quadrant.MORE) {
if (index !== lastIndex) {
[shapes[index], shapes[lastIndex]] = [shapes[lastIndex], shapes[index]];
}
this._trees[quadrant].insert(shapes.pop());
lastIndex--;
} else {
index++;
}
} else {
index++;
}
}
}
}
/** 当前树以及子树中所有的形状数量 @internal */
private _totalSize(): number {
let size = this._size();
for (const tree of this._trees) {
size += tree._totalSize();
}
return size;
}
/** 当前树中所有的形状数量 @internal */
private _size(): number {
let size = 0;
for (const shapes of this._shapes_map.values()) {
size += shapes.length;
}
return size + this._ignore_shapes.length;
}
/** 画出当前树的边界 @internal */
private graphicsTreeBound(root: QuadTree): void {
if (!this._graphics) {
return;
}
if (this._trees.length > 0) {
this._graphics.moveTo(this._bounds.x, this._bounds.center.y);
this._graphics.lineTo(this._bounds.x + this._bounds.width, this._bounds.center.y);
this._graphics.moveTo(this._bounds.center.x, this._bounds.y);
this._graphics.lineTo(this._bounds.center.x, this._bounds.y + this._bounds.height);
}
if (this == root) {
this._graphics.moveTo(this._bounds.xMin, this._bounds.yMin);
this._graphics.lineTo(this._bounds.xMax, this._bounds.yMin);
this._graphics.lineTo(this._bounds.xMax, this._bounds.yMax);
this._graphics.lineTo(this._bounds.xMin, this._bounds.yMax);
this._graphics.lineTo(this._bounds.xMin, this._bounds.yMin);
}
}
/** 清除绘制 @internal */
private graphicsClear(): void {
this._graphics && this._graphics.clear();
}
}

View File

@ -1,75 +0,0 @@
/**
* @Author: Gongxh
* @Date: 2024-12-21
* @Description:
*/
import { Rect, Vec2 } from "cc";
import { IShape, ShapeType } from "./IShape";
export abstract class Shape implements IShape {
/**
* & -1
*/
private _mask: number = -1;
protected _scale: number;
/** 脏标记 用来重置包围盒 @internal */
protected _isDirty: boolean;
/** 包围盒 @internal */
protected _boundingBox: Rect;
/** 位置 @internal */
protected _position: Vec2;
/** 旋转角度 @internal */
protected _rotation: number;
/** 是否有效 下次更新时删除 @internal */
private _valid: boolean = true;
public abstract get shapeType(): ShapeType;
public get mask(): number { return this._mask; }
public get position(): Vec2 { return this._position; }
public get scale(): number { return this._scale; }
public get rotation(): number { return this._rotation; }
public get isValid(): boolean { return this._valid; }
constructor(mask: number) {
this._mask = mask;
this._scale = 1.0;
this._rotation = 0;
this._isDirty = true;
this._boundingBox = new Rect();
this._position = new Vec2();
}
public setPosition(x: number, y: number) {
this._position.x = x;
this._position.y = y;
}
public setRotation(angle: number) {
if (this._rotation !== angle) {
this._rotation = angle;
this._isDirty = true;
}
}
public setScale(value: number) {
if (this._scale !== value) {
this._scale = value;
this._isDirty = true;
}
}
/** 包围盒 子类重写 */
public abstract getBoundingBox(): Rect;
public destroy(): void {
this._valid = false;
}
}