mirror of
https://github.com/Gongxh0901/kunpolibrary
synced 2025-07-01 22:04:17 +00:00
模块拆分
This commit is contained in:
parent
4ccd8bde50
commit
c25c1554f3
18
CHANGELOG.md
18
CHANGELOG.md
@ -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
|
31
README.md
31
README.md
@ -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 编写,提供完整的类型定义文件。
|
||||
|
102
docs/Asset.md
102
docs/Asset.md
@ -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;
|
||||
```
|
||||
|
@ -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. 性能优化:
|
||||
- 使用黑板共享数据,避免重复计算
|
||||
- 合理使用记忆节点,减少重复执行
|
||||
- 控制行为树的深度,避免过于复杂
|
||||
|
||||
|
524
docs/EC.md
524
docs/EC.md
@ -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`可以方便创建、配置、导出实体,操作界面如下图:
|
||||
|
||||

|
||||
|
||||
#### *使用*
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
|
132
docs/QuadTree.md
132
docs/QuadTree.md
@ -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 |
@ -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",
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
@ -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}`;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 时间等待节点(无子节点)
|
||||
* 时间到后返回SUCCESS,否则返回RUNING
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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 或者 RUNING,那停止迭代,本Node向自己的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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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 { }
|
||||
}
|
@ -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;
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
};
|
@ -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);
|
||||
}
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
/**
|
||||
* @type {&} AND,按位与处理两个长度相同的二进制数,两个相应的二进位都为 1,该位的结果值才为 1,否则为 0
|
||||
* @type {|} OR,按位或处理两个长度相同的二进制数,两个相应的二进位中只要有一个为 1,该位的结果值为 1
|
||||
* @type {~} 取反,取反是一元运算符,对一个二进制数的每一位执行逻辑反操作。使数字 1 成为 0,0 成为 1
|
||||
* @type {^} 异或,按位异或运算,对等长二进制模式按位或二进制数的每一位执行逻辑异按位或操作。操作的结果是如果某位不同则该位为 1,否则该位为 0
|
||||
* @type {<<} 左移,把 << 左边的运算数的各二进位全部左移若干位,由 << 右边的数指定移动的位数,高位丢弃,低位补0; 将一个值左移一个位置相当于将其乘以2,移位两个位置相当于乘以4,依此类推。
|
||||
* @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;
|
||||
|
||||
/**
|
||||
* 2进制转10进制 (不支持小数和负数)
|
||||
* @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;
|
||||
}
|
||||
/**
|
||||
* 10进制转2进制 (不支持小数和负数)
|
||||
* @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
|
@ -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 标签除了表示Entity,还可以通过EntityManager获取指定标签的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} 如果计数为0或状态不存在,则返回true
|
||||
* @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();
|
||||
}
|
||||
}
|
@ -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})不存在`);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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";
|
||||
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user