新增cocos-debug-profiler
This commit is contained in:
@@ -21,6 +21,9 @@
|
||||
},
|
||||
{
|
||||
"__id__": 5
|
||||
},
|
||||
{
|
||||
"__id__": 7
|
||||
}
|
||||
],
|
||||
"_active": true,
|
||||
@@ -55,7 +58,7 @@
|
||||
},
|
||||
"autoReleaseAssets": false,
|
||||
"_globals": {
|
||||
"__id__": 7
|
||||
"__id__": 9
|
||||
},
|
||||
"_id": "ff354f0b-c2f5-4dea-8ffb-0152d175d11c"
|
||||
},
|
||||
@@ -246,32 +249,90 @@
|
||||
"_trackingType": 0,
|
||||
"_id": "7dWQTpwS5LrIHnc1zAPUtf"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.Node",
|
||||
"_name": "ECSManager",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"_parent": {
|
||||
"__id__": 1
|
||||
},
|
||||
"_children": [],
|
||||
"_active": true,
|
||||
"_components": [
|
||||
{
|
||||
"__id__": 8
|
||||
}
|
||||
],
|
||||
"_prefab": null,
|
||||
"_lpos": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_lrot": {
|
||||
"__type__": "cc.Quat",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0,
|
||||
"w": 1
|
||||
},
|
||||
"_lscale": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 1,
|
||||
"y": 1,
|
||||
"z": 1
|
||||
},
|
||||
"_mobility": 0,
|
||||
"_layer": 1073741824,
|
||||
"_euler": {
|
||||
"__type__": "cc.Vec3",
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"_id": "43H+4zZ5xK2rblF/7Lha6k"
|
||||
},
|
||||
{
|
||||
"__type__": "c82e7kJAeZJyraNnumIN+I4",
|
||||
"_name": "",
|
||||
"_objFlags": 0,
|
||||
"__editorExtras__": {},
|
||||
"node": {
|
||||
"__id__": 7
|
||||
},
|
||||
"_enabled": true,
|
||||
"__prefab": null,
|
||||
"debugMode": true,
|
||||
"_id": "40G/Xl9EBLJ7amO+29wrkO"
|
||||
},
|
||||
{
|
||||
"__type__": "cc.SceneGlobals",
|
||||
"ambient": {
|
||||
"__id__": 8
|
||||
},
|
||||
"shadows": {
|
||||
"__id__": 9
|
||||
},
|
||||
"_skybox": {
|
||||
"__id__": 10
|
||||
},
|
||||
"fog": {
|
||||
"shadows": {
|
||||
"__id__": 11
|
||||
},
|
||||
"octree": {
|
||||
"_skybox": {
|
||||
"__id__": 12
|
||||
},
|
||||
"skin": {
|
||||
"fog": {
|
||||
"__id__": 13
|
||||
},
|
||||
"lightProbeInfo": {
|
||||
"octree": {
|
||||
"__id__": 14
|
||||
},
|
||||
"postSettings": {
|
||||
"skin": {
|
||||
"__id__": 15
|
||||
},
|
||||
"lightProbeInfo": {
|
||||
"__id__": 16
|
||||
},
|
||||
"postSettings": {
|
||||
"__id__": 17
|
||||
},
|
||||
"bakedWithStationaryMainLight": false,
|
||||
"bakedWithHighpLightmap": false
|
||||
},
|
||||
|
||||
9
extensions/cocos/cocos-ecs/assets/scripts/ecs.meta
Normal file
9
extensions/cocos/cocos-ecs/assets/scripts/ecs.meta
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "6f9217a1-dff6-4460-b5da-eb01cf29c03c",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
99
extensions/cocos/cocos-ecs/assets/scripts/ecs/ECSManager.ts
Normal file
99
extensions/cocos/cocos-ecs/assets/scripts/ecs/ECSManager.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { Core } from '@esengine/ecs-framework';
|
||||
import { Component, _decorator } from 'cc';
|
||||
import { ExampleGameScene } from './scenes/ExampleGameScene';
|
||||
|
||||
const { ccclass, property } = _decorator;
|
||||
|
||||
/**
|
||||
* ECS管理器 - Cocos Creator组件
|
||||
* 将此组件添加到场景中的任意节点上即可启动ECS框架
|
||||
*
|
||||
* 使用说明:
|
||||
* 1. 在Cocos Creator场景中创建一个空节点
|
||||
* 2. 将此ECSManager组件添加到该节点
|
||||
* 3. 运行场景即可自动启动ECS框架
|
||||
*/
|
||||
@ccclass('ECSManager')
|
||||
export class ECSManager extends Component {
|
||||
|
||||
@property({
|
||||
tooltip: '是否启用调试模式(建议开发阶段开启)'
|
||||
})
|
||||
public debugMode: boolean = true;
|
||||
|
||||
private isInitialized: boolean = false;
|
||||
|
||||
/**
|
||||
* 组件启动时初始化ECS
|
||||
*/
|
||||
start() {
|
||||
this.initializeECS();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化ECS框架
|
||||
*/
|
||||
private initializeECS(): void {
|
||||
if (this.isInitialized) return;
|
||||
|
||||
console.log('🎮 正在初始化ECS框架...');
|
||||
|
||||
try {
|
||||
// 1. 创建Core实例,启用调试功能
|
||||
if (this.debugMode) {
|
||||
Core.create({
|
||||
debugConfig: {
|
||||
enabled: true,
|
||||
websocketUrl: 'ws://localhost:8080/ecs-debug',
|
||||
autoReconnect: true,
|
||||
updateInterval: 1000,
|
||||
channels: {
|
||||
entities: true,
|
||||
systems: true,
|
||||
performance: true,
|
||||
components: true,
|
||||
scenes: true
|
||||
}
|
||||
}
|
||||
});
|
||||
console.log('🔧 ECS调试模式已启用,可在Cocos Creator扩展面板中查看调试信息');
|
||||
} else {
|
||||
Core.create(false);
|
||||
}
|
||||
|
||||
// 2. 创建游戏场景
|
||||
const gameScene = new ExampleGameScene();
|
||||
|
||||
// 3. 设置为当前场景(会自动调用scene.begin())
|
||||
Core.scene = gameScene;
|
||||
|
||||
this.isInitialized = true;
|
||||
console.log('✅ ECS框架初始化成功!');
|
||||
console.log('📖 请查看 assets/scripts/ecs/README.md 了解如何添加组件和系统');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ ECS框架初始化失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 每帧更新ECS框架
|
||||
*/
|
||||
update(deltaTime: number) {
|
||||
if (this.isInitialized) {
|
||||
// 更新ECS核心系统
|
||||
Core.update(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件销毁时清理ECS
|
||||
*/
|
||||
onDestroy() {
|
||||
if (this.isInitialized) {
|
||||
console.log('🧹 清理ECS框架...');
|
||||
// ECS框架会自动处理场景清理
|
||||
this.isInitialized = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "c82e7909-01e6-49ca-b68d-9ee98837e238",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
153
extensions/cocos/cocos-ecs/assets/scripts/ecs/README.md
Normal file
153
extensions/cocos/cocos-ecs/assets/scripts/ecs/README.md
Normal file
@@ -0,0 +1,153 @@
|
||||
# ECS框架启动模板
|
||||
|
||||
欢迎使用ECS框架!这是一个最基础的启动模板,帮助您快速开始ECS项目开发。
|
||||
|
||||
## 📁 项目结构
|
||||
|
||||
```
|
||||
ecs/
|
||||
├── components/ # 组件目录(请在此添加您的组件)
|
||||
├── systems/ # 系统目录(请在此添加您的系统)
|
||||
├── scenes/ # 场景目录
|
||||
│ └── GameScene.ts # 主游戏场景
|
||||
├── ECSManager.ts # ECS管理器组件
|
||||
└── README.md # 本文档
|
||||
```
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 1. 启动ECS框架
|
||||
|
||||
ECS框架已经配置完成!您只需要:
|
||||
|
||||
1. 在Cocos Creator中打开您的场景
|
||||
2. 创建一个空节点(例如命名为"ECSManager")
|
||||
3. 将 `ECSManager` 组件添加到该节点
|
||||
4. 运行场景,ECS框架将自动启动
|
||||
|
||||
### 2. 查看控制台输出
|
||||
|
||||
如果一切正常,您将在控制台看到:
|
||||
|
||||
```
|
||||
🎮 正在初始化ECS框架...
|
||||
🔧 ECS调试模式已启用,可在Cocos Creator扩展面板中查看调试信息
|
||||
🎯 游戏场景已创建
|
||||
✅ ECS框架初始化成功!
|
||||
🚀 游戏场景已启动
|
||||
```
|
||||
|
||||
### 3. 使用调试面板
|
||||
|
||||
ECS框架已启用调试功能,您可以:
|
||||
|
||||
1. 在Cocos Creator编辑器菜单中选择 "扩展" → "ECS Framework" → "调试面板"
|
||||
2. 调试面板将显示实时的ECS运行状态:
|
||||
- 实体数量和状态
|
||||
- 系统执行信息
|
||||
- 性能监控数据
|
||||
- 组件统计信息
|
||||
|
||||
**注意**:调试功能会消耗一定性能,正式发布时建议关闭调试模式。
|
||||
|
||||
## 📚 下一步开发
|
||||
|
||||
### 创建您的第一个组件
|
||||
|
||||
在 `components/` 目录下创建组件:
|
||||
|
||||
```typescript
|
||||
// components/PositionComponent.ts
|
||||
import { Component } from '@esengine/ecs-framework';
|
||||
import { Vec3 } from 'cc';
|
||||
|
||||
export class PositionComponent extends Component {
|
||||
public position: Vec3 = new Vec3();
|
||||
|
||||
constructor(x: number = 0, y: number = 0, z: number = 0) {
|
||||
super();
|
||||
this.position.set(x, y, z);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 创建您的第一个系统
|
||||
|
||||
在 `systems/` 目录下创建系统:
|
||||
|
||||
```typescript
|
||||
// systems/MovementSystem.ts
|
||||
import { EntitySystem, Entity, Matcher } from '@esengine/ecs-framework';
|
||||
import { PositionComponent } from '../components/PositionComponent';
|
||||
|
||||
export class MovementSystem extends EntitySystem {
|
||||
constructor() {
|
||||
super(Matcher.empty().all(PositionComponent));
|
||||
}
|
||||
|
||||
protected process(entities: Entity[]): void {
|
||||
for (const entity of entities) {
|
||||
const position = entity.getComponent(PositionComponent);
|
||||
if (position) {
|
||||
// TODO: 在这里编写移动逻辑
|
||||
console.log(`实体 ${entity.name} 位置: ${position.position}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 在场景中注册系统
|
||||
|
||||
在 `scenes/GameScene.ts` 的 `initialize()` 方法中添加:
|
||||
|
||||
```typescript
|
||||
import { MovementSystem } from '../systems/MovementSystem';
|
||||
|
||||
public initialize(): void {
|
||||
super.initialize();
|
||||
this.name = "MainGameScene";
|
||||
|
||||
// 添加系统
|
||||
this.addEntityProcessor(new MovementSystem());
|
||||
|
||||
// 创建测试实体
|
||||
const testEntity = this.createEntity("TestEntity");
|
||||
testEntity.addComponent(new PositionComponent(0, 0, 0));
|
||||
}
|
||||
```
|
||||
|
||||
## 🔗 学习资源
|
||||
|
||||
- [ECS框架完整文档](https://github.com/esengine/ecs-framework)
|
||||
- [ECS概念详解](https://github.com/esengine/ecs-framework/blob/master/docs/concepts-explained.md)
|
||||
- [新手教程](https://github.com/esengine/ecs-framework/blob/master/docs/beginner-tutorials.md)
|
||||
- [组件设计指南](https://github.com/esengine/ecs-framework/blob/master/docs/component-design-guide.md)
|
||||
- [系统开发指南](https://github.com/esengine/ecs-framework/blob/master/docs/system-guide.md)
|
||||
|
||||
## 💡 开发提示
|
||||
|
||||
1. **组件只存储数据**:避免在组件中编写复杂逻辑
|
||||
2. **系统处理逻辑**:所有业务逻辑应该在系统中实现
|
||||
3. **使用Matcher过滤实体**:系统通过Matcher指定需要处理的实体类型
|
||||
4. **性能优化**:大量实体时考虑使用位掩码查询和组件索引
|
||||
|
||||
## ❓ 常见问题
|
||||
|
||||
### Q: 如何创建实体?
|
||||
A: 在场景中使用 `this.createEntity("实体名称")`
|
||||
|
||||
### Q: 如何给实体添加组件?
|
||||
A: 使用 `entity.addComponent(new YourComponent())`
|
||||
|
||||
### Q: 如何获取实体的组件?
|
||||
A: 使用 `entity.getComponent(YourComponent)`
|
||||
|
||||
### Q: 如何删除实体?
|
||||
A: 使用 `entity.destroy()` 或 `this.destroyEntity(entity)`
|
||||
|
||||
---
|
||||
|
||||
🎮 **开始您的ECS开发之旅吧!**
|
||||
|
||||
如有问题,请查阅官方文档或提交Issue。
|
||||
11
extensions/cocos/cocos-ecs/assets/scripts/ecs/README.md.meta
Normal file
11
extensions/cocos/cocos-ecs/assets/scripts/ecs/README.md.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"ver": "1.0.1",
|
||||
"importer": "text",
|
||||
"imported": true,
|
||||
"uuid": "0932496e-f7fe-4cb9-86e2-ebd7d2a3d047",
|
||||
"files": [
|
||||
".json"
|
||||
],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "3d8cbc91-5bc5-4d17-b53a-01fda26e4660",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import { Component } from '@esengine/ecs-framework';
|
||||
|
||||
/**
|
||||
* 生命值组件 - 管理实体的生命值和相关状态
|
||||
*
|
||||
* 展示游戏逻辑组件的设计:
|
||||
* 1. 包含生命值的核心数据
|
||||
* 2. 提供简单的查询方法
|
||||
* 3. 复杂的伤害处理逻辑留给系统处理
|
||||
*/
|
||||
export class HealthComponent extends Component {
|
||||
/** 最大生命值 */
|
||||
public maxHealth: number;
|
||||
/** 当前生命值 */
|
||||
public currentHealth: number;
|
||||
/** 生命值回复速度(每秒回复量) */
|
||||
public regenRate: number = 0;
|
||||
/** 最后受到伤害的时间(用于延迟回血等机制) */
|
||||
public lastDamageTime: number = 0;
|
||||
/** 是否无敌 */
|
||||
public invincible: boolean = false;
|
||||
/** 无敌持续时间 */
|
||||
public invincibleDuration: number = 0;
|
||||
|
||||
constructor(maxHealth: number = 100, regenRate: number = 0) {
|
||||
super();
|
||||
this.maxHealth = maxHealth;
|
||||
this.currentHealth = maxHealth;
|
||||
this.regenRate = regenRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否死亡
|
||||
*/
|
||||
isDead(): boolean {
|
||||
return this.currentHealth <= 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否满血
|
||||
*/
|
||||
isFullHealth(): boolean {
|
||||
return this.currentHealth >= this.maxHealth;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取生命值百分比(0-1)
|
||||
*/
|
||||
getHealthPercentage(): number {
|
||||
return this.currentHealth / this.maxHealth;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查生命值是否低于指定百分比
|
||||
*/
|
||||
isHealthBelowPercentage(percentage: number): boolean {
|
||||
return this.getHealthPercentage() < percentage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置生命值(不超过最大值)
|
||||
*/
|
||||
setHealth(health: number) {
|
||||
this.currentHealth = Math.max(0, Math.min(health, this.maxHealth));
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加生命值(治疗)
|
||||
*/
|
||||
heal(amount: number) {
|
||||
this.currentHealth = Math.min(this.currentHealth + amount, this.maxHealth);
|
||||
}
|
||||
|
||||
/**
|
||||
* 减少生命值(受伤)
|
||||
* 注意:这里只修改数据,具体的伤害逻辑(如死亡处理)应该在系统中实现
|
||||
*/
|
||||
takeDamage(damage: number) {
|
||||
if (this.invincible) return;
|
||||
|
||||
this.currentHealth = Math.max(0, this.currentHealth - damage);
|
||||
this.lastDamageTime = Date.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置无敌状态
|
||||
*/
|
||||
setInvincible(duration: number) {
|
||||
this.invincible = true;
|
||||
this.invincibleDuration = duration;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置到满血状态
|
||||
*/
|
||||
reset() {
|
||||
this.currentHealth = this.maxHealth;
|
||||
this.invincible = false;
|
||||
this.invincibleDuration = 0;
|
||||
this.lastDamageTime = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "0f20d48a-7b30-4081-a9de-709432b6737b",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
import { Component } from '@esengine/ecs-framework';
|
||||
import { Vec2 } from 'cc';
|
||||
|
||||
/**
|
||||
* 玩家输入组件 - 存储玩家的输入状态
|
||||
*
|
||||
* 标记组件示例:
|
||||
* 1. 标识这是一个玩家控制的实体
|
||||
* 2. 存储输入状态数据
|
||||
* 3. 输入处理逻辑在InputSystem中实现
|
||||
*/
|
||||
export class PlayerInputComponent extends Component {
|
||||
/** 移动输入方向(-1到1) */
|
||||
public moveDirection: Vec2 = new Vec2();
|
||||
/** 按键状态 */
|
||||
public keys: { [key: string]: boolean } = {};
|
||||
/** 鼠标位置 */
|
||||
public mousePosition: Vec2 = new Vec2();
|
||||
/** 鼠标按键状态 */
|
||||
public mouseButtons: { left: boolean; right: boolean; middle: boolean } = {
|
||||
left: false,
|
||||
right: false,
|
||||
middle: false
|
||||
};
|
||||
|
||||
/** 是否启用输入 */
|
||||
public inputEnabled: boolean = true;
|
||||
/** 输入敏感度 */
|
||||
public sensitivity: number = 1.0;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置移动方向
|
||||
*/
|
||||
setMoveDirection(x: number, y: number) {
|
||||
this.moveDirection.set(x, y);
|
||||
// 标准化方向向量(对角线移动不应该更快)
|
||||
if (this.moveDirection.lengthSqr() > 1) {
|
||||
this.moveDirection.normalize();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置按键状态
|
||||
*/
|
||||
setKey(key: string, pressed: boolean) {
|
||||
this.keys[key] = pressed;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查按键是否按下
|
||||
*/
|
||||
isKeyPressed(key: string): boolean {
|
||||
return this.keys[key] || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否有移动输入
|
||||
*/
|
||||
hasMovementInput(): boolean {
|
||||
return this.moveDirection.lengthSqr() > 0.01;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取标准化的移动方向
|
||||
*/
|
||||
getNormalizedMoveDirection(): Vec2 {
|
||||
const result = new Vec2(this.moveDirection);
|
||||
if (result.lengthSqr() > 0) {
|
||||
result.normalize();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置鼠标位置
|
||||
*/
|
||||
setMousePosition(x: number, y: number) {
|
||||
this.mousePosition.set(x, y);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置鼠标按键状态
|
||||
*/
|
||||
setMouseButton(button: 'left' | 'right' | 'middle', pressed: boolean) {
|
||||
this.mouseButtons[button] = pressed;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查鼠标按键是否按下
|
||||
*/
|
||||
isMouseButtonPressed(button: 'left' | 'right' | 'middle'): boolean {
|
||||
return this.mouseButtons[button];
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有输入状态
|
||||
*/
|
||||
clearInput() {
|
||||
this.moveDirection.set(0, 0);
|
||||
this.keys = {};
|
||||
this.mouseButtons.left = false;
|
||||
this.mouseButtons.right = false;
|
||||
this.mouseButtons.middle = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁用输入
|
||||
*/
|
||||
disableInput() {
|
||||
this.inputEnabled = false;
|
||||
this.clearInput();
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用输入
|
||||
*/
|
||||
enableInput() {
|
||||
this.inputEnabled = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "ab10dc4c-c8a3-4fd2-83d6-433d4195966b",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import { Component } from '@esengine/ecs-framework';
|
||||
import { Vec3 } from 'cc';
|
||||
|
||||
/**
|
||||
* 位置组件 - 存储实体的空间位置信息
|
||||
*
|
||||
* 这是最基础的组件示例,展示了ECS组件的设计原则:
|
||||
* 1. 主要存储数据,少量辅助方法
|
||||
* 2. 单一职责:只负责位置相关的数据
|
||||
* 3. 可复用:任何需要位置信息的实体都可以使用
|
||||
*/
|
||||
export class PositionComponent extends Component {
|
||||
/** 3D位置坐标 */
|
||||
public position: Vec3 = new Vec3();
|
||||
/** 上一帧的位置(用于计算移动距离) */
|
||||
public lastPosition: Vec3 = new Vec3();
|
||||
|
||||
constructor(x: number = 0, y: number = 0, z: number = 0) {
|
||||
super();
|
||||
this.position.set(x, y, z);
|
||||
this.lastPosition.set(x, y, z);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置位置
|
||||
*/
|
||||
setPosition(x: number, y: number, z: number = 0) {
|
||||
this.lastPosition.set(this.position);
|
||||
this.position.set(x, y, z);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动位置
|
||||
*/
|
||||
move(deltaX: number, deltaY: number, deltaZ: number = 0) {
|
||||
this.lastPosition.set(this.position);
|
||||
this.position.x += deltaX;
|
||||
this.position.y += deltaY;
|
||||
this.position.z += deltaZ;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算到另一个位置的距离
|
||||
*/
|
||||
distanceTo(other: PositionComponent): number {
|
||||
return Vec3.distance(this.position, other.position);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本帧移动的距离
|
||||
*/
|
||||
getMovementDistance(): number {
|
||||
return Vec3.distance(this.position, this.lastPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否在指定范围内
|
||||
*/
|
||||
isWithinRange(target: PositionComponent, range: number): boolean {
|
||||
return this.distanceTo(target) <= range;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "e6ee57d6-d0eb-43f2-a601-9b7a2812de66",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import { Component } from '@esengine/ecs-framework';
|
||||
import { Vec3 } from 'cc';
|
||||
|
||||
/**
|
||||
* 速度组件 - 存储实体的运动速度信息
|
||||
*
|
||||
* 设计原则展示:
|
||||
* 1. 与PositionComponent分离:遵循单一职责原则
|
||||
* 2. 包含速度限制:避免无限加速
|
||||
* 3. 提供常用的速度操作方法
|
||||
*/
|
||||
export class VelocityComponent extends Component {
|
||||
/** 当前速度向量 */
|
||||
public velocity: Vec3 = new Vec3();
|
||||
/** 最大速度限制 */
|
||||
public maxSpeed: number = 100;
|
||||
/** 阻尼系数(0-1,1为无阻尼) */
|
||||
public damping: number = 1.0;
|
||||
|
||||
constructor(x: number = 0, y: number = 0, z: number = 0, maxSpeed: number = 100) {
|
||||
super();
|
||||
this.velocity.set(x, y, z);
|
||||
this.maxSpeed = maxSpeed;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置速度
|
||||
*/
|
||||
setVelocity(x: number, y: number, z: number = 0) {
|
||||
this.velocity.set(x, y, z);
|
||||
this.clampToMaxSpeed();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加速度(加速度效果)
|
||||
*/
|
||||
addVelocity(x: number, y: number, z: number = 0) {
|
||||
this.velocity.x += x;
|
||||
this.velocity.y += y;
|
||||
this.velocity.z += z;
|
||||
this.clampToMaxSpeed();
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用阻尼
|
||||
*/
|
||||
applyDamping(deltaTime: number) {
|
||||
if (this.damping < 1.0) {
|
||||
const dampingFactor = Math.pow(this.damping, deltaTime);
|
||||
this.velocity.multiplyScalar(dampingFactor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 限制速度不超过最大值
|
||||
*/
|
||||
private clampToMaxSpeed() {
|
||||
const speed = this.velocity.length();
|
||||
if (speed > this.maxSpeed) {
|
||||
this.velocity.normalize();
|
||||
this.velocity.multiplyScalar(this.maxSpeed);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前速度大小
|
||||
*/
|
||||
getSpeed(): number {
|
||||
return this.velocity.length();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取速度方向(单位向量)
|
||||
*/
|
||||
getDirection(): Vec3 {
|
||||
const result = new Vec3();
|
||||
Vec3.normalize(result, this.velocity);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止移动
|
||||
*/
|
||||
stop() {
|
||||
this.velocity.set(0, 0, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否在移动
|
||||
*/
|
||||
isMoving(): boolean {
|
||||
return this.velocity.lengthSqr() > 0.01; // 避免浮点数精度问题
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "784d7c28-2b72-427c-8b04-da0fcf775acf",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "6a2d6231-acf9-47b8-a020-d45a7433a95d",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "879b4e07-dd6b-4445-adb2-a970b97c6d6f",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
import { Scene } from '@esengine/ecs-framework';
|
||||
import { Entity } from '@esengine/ecs-framework';
|
||||
|
||||
// 导入组件
|
||||
import { PositionComponent } from '../components/PositionComponent';
|
||||
import { VelocityComponent } from '../components/VelocityComponent';
|
||||
import { HealthComponent } from '../components/HealthComponent';
|
||||
import { PlayerInputComponent } from '../components/PlayerInputComponent';
|
||||
|
||||
// 导入系统
|
||||
import { MovementSystem } from '../systems/MovementSystem';
|
||||
import { PlayerInputSystem } from '../systems/PlayerInputSystem';
|
||||
import { HealthSystem } from '../systems/HealthSystem';
|
||||
|
||||
/**
|
||||
* 示例游戏场景 - 完整的ECS应用示例
|
||||
*
|
||||
* 这个场景展示了:
|
||||
* 1. 如何创建和配置各种实体
|
||||
* 2. 如何添加和组织系统
|
||||
* 3. 如何实现完整的游戏逻辑
|
||||
* 4. 如何进行调试和监控
|
||||
*/
|
||||
export class ExampleGameScene extends Scene {
|
||||
// 场景中的重要实体引用
|
||||
private player: Entity | null;
|
||||
private enemies: Entity[];
|
||||
private gameConfig: {
|
||||
maxEnemies: number;
|
||||
enemySpawnInterval: number;
|
||||
gameArea: { width: number; height: number };
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
// 在构造函数中初始化属性
|
||||
this.player = null;
|
||||
this.enemies = [];
|
||||
this.gameConfig = {
|
||||
maxEnemies: 5,
|
||||
enemySpawnInterval: 3000, // 3秒生成一个敌人
|
||||
gameArea: { width: 800, height: 600 }
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景初始化(构造时调用)
|
||||
*/
|
||||
public initialize(): void {
|
||||
super.initialize();
|
||||
this.name = "ExampleGameScene";
|
||||
console.log("📋 ExampleGameScene 构造完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景开始时的回调(所有构造函数执行完毕后调用)
|
||||
*/
|
||||
public onStart(): void {
|
||||
super.onStart();
|
||||
|
||||
console.log("🎮 开始初始化示例游戏场景...");
|
||||
|
||||
// 1. 添加系统(注意顺序很重要)
|
||||
this.setupSystems();
|
||||
|
||||
// 2. 创建游戏实体
|
||||
this.createGameEntities();
|
||||
|
||||
// 3. 设置定时器和事件
|
||||
this.setupGameLogic();
|
||||
|
||||
console.log("✅ 示例游戏场景初始化完成!");
|
||||
this.printSceneInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置游戏系统
|
||||
*/
|
||||
private setupSystems(): void {
|
||||
console.log("🔧 添加游戏系统...");
|
||||
|
||||
// 输入系统(最先处理输入)
|
||||
this.addEntityProcessor(new PlayerInputSystem());
|
||||
|
||||
// 移动系统(处理所有移动逻辑)
|
||||
this.addEntityProcessor(new MovementSystem());
|
||||
|
||||
// 生命值系统(处理生命值、死亡等)
|
||||
this.addEntityProcessor(new HealthSystem());
|
||||
|
||||
console.log("✅ 系统添加完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建游戏实体
|
||||
*/
|
||||
private createGameEntities(): void {
|
||||
console.log("🏗️ 创建游戏实体...");
|
||||
|
||||
// 创建玩家
|
||||
this.createPlayer();
|
||||
|
||||
// 创建初始敌人
|
||||
this.createInitialEnemies();
|
||||
|
||||
// 创建环境实体(可选)
|
||||
this.createEnvironmentEntities();
|
||||
|
||||
console.log("✅ 实体创建完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建玩家实体
|
||||
*/
|
||||
private createPlayer(): void {
|
||||
this.player = this.createEntity("Player");
|
||||
|
||||
// 添加玩家组件
|
||||
this.player.addComponent(new PositionComponent(0, 0, 0));
|
||||
this.player.addComponent(new VelocityComponent(0, 0, 0, 250)); // 最大速度250
|
||||
this.player.addComponent(new HealthComponent(100, 5)); // 100血,每秒回5血
|
||||
this.player.addComponent(new PlayerInputComponent());
|
||||
|
||||
console.log("🎯 玩家创建完成 - 使用WASD或方向键移动,空格键攻击");
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建初始敌人
|
||||
*/
|
||||
private createInitialEnemies(): void {
|
||||
for (let i = 0; i < 3; i++) {
|
||||
this.createEnemy(i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建单个敌人
|
||||
*/
|
||||
private createEnemy(index: number): Entity {
|
||||
const enemy = this.createEntity(`Enemy_${index}`);
|
||||
|
||||
// 随机位置
|
||||
const x = (Math.random() - 0.5) * this.gameConfig.gameArea.width;
|
||||
const y = (Math.random() - 0.5) * this.gameConfig.gameArea.height;
|
||||
|
||||
// 随机速度
|
||||
const velocityX = (Math.random() - 0.5) * 100;
|
||||
const velocityY = (Math.random() - 0.5) * 100;
|
||||
|
||||
// 添加敌人组件
|
||||
enemy.addComponent(new PositionComponent(x, y, 0));
|
||||
enemy.addComponent(new VelocityComponent(velocityX, velocityY, 0, 150));
|
||||
enemy.addComponent(new HealthComponent(50, 0)); // 50血,不回血
|
||||
|
||||
// 添加到敌人列表
|
||||
this.enemies.push(enemy);
|
||||
|
||||
return enemy;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建环境实体(演示不同类型的实体)
|
||||
*/
|
||||
private createEnvironmentEntities(): void {
|
||||
// 创建一些静态的环境对象
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const obstacle = this.createEntity(`Obstacle_${i}`);
|
||||
|
||||
const x = (Math.random() - 0.5) * this.gameConfig.gameArea.width * 0.8;
|
||||
const y = (Math.random() - 0.5) * this.gameConfig.gameArea.height * 0.8;
|
||||
|
||||
// 只有位置,没有速度和生命值
|
||||
obstacle.addComponent(new PositionComponent(x, y, 0));
|
||||
}
|
||||
|
||||
console.log("🌲 环境实体创建完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置游戏逻辑和定时器
|
||||
*/
|
||||
private setupGameLogic(): void {
|
||||
console.log("⚙️ 设置游戏逻辑...");
|
||||
|
||||
// 敌人生成定时器
|
||||
this.setupEnemySpawner();
|
||||
|
||||
// 游戏状态监控
|
||||
this.setupGameMonitoring();
|
||||
|
||||
console.log("✅ 游戏逻辑设置完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置敌人生成器
|
||||
*/
|
||||
private setupEnemySpawner(): void {
|
||||
setInterval(() => {
|
||||
if (this.enemies.length < this.gameConfig.maxEnemies) {
|
||||
const newEnemy = this.createEnemy(this.enemies.length);
|
||||
}
|
||||
}, this.gameConfig.enemySpawnInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置游戏监控
|
||||
*/
|
||||
private setupGameMonitoring(): void {
|
||||
// 每10秒清理已死亡的敌人引用
|
||||
setInterval(() => {
|
||||
this.cleanupDeadEnemies();
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印游戏状态(按需调用)
|
||||
*/
|
||||
private printGameStatus(): void {
|
||||
const totalEntities = this.entities.count;
|
||||
const aliveEnemies = this.enemies.filter(e => !e.isDestroyed).length;
|
||||
|
||||
console.log("📊 游戏状态报告:");
|
||||
console.log(` - 总实体数: ${totalEntities}`);
|
||||
console.log(` - 存活敌人: ${aliveEnemies}`);
|
||||
|
||||
if (this.player && !this.player.isDestroyed) {
|
||||
const playerHealth = this.player.getComponent(HealthComponent);
|
||||
const playerPos = this.player.getComponent(PositionComponent);
|
||||
console.log(` - 玩家生命值: ${playerHealth?.currentHealth}/${playerHealth?.maxHealth}`);
|
||||
console.log(` - 玩家位置: (${playerPos?.position.x.toFixed(1)}, ${playerPos?.position.y.toFixed(1)})`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理已死亡的敌人引用
|
||||
*/
|
||||
private cleanupDeadEnemies(): void {
|
||||
const initialCount = this.enemies.length;
|
||||
this.enemies = this.enemies.filter(enemy => !enemy.isDestroyed);
|
||||
const removedCount = initialCount - this.enemies.length;
|
||||
|
||||
if (removedCount > 0) {
|
||||
console.log(`🧹 清理了 ${removedCount} 个已死亡的敌人引用`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印场景信息
|
||||
*/
|
||||
private printSceneInfo(): void {
|
||||
console.log("\n📋 场景信息:");
|
||||
console.log(` 场景名: ${this.name}`);
|
||||
console.log(` 实体数: ${this.entities.count}`);
|
||||
console.log(` 系统数: ${this.entityProcessors.count}`);
|
||||
console.log(` 玩家: ${this.player?.name || '未创建'}`);
|
||||
console.log(` 敌人: ${this.enemies.length} 个`);
|
||||
console.log("\n🎮 控制说明:");
|
||||
console.log(" - WASD 或 方向键: 移动");
|
||||
console.log(" - 空格: 攻击/行动");
|
||||
console.log(" - ESC: 暂停");
|
||||
console.log("\n💡 学习要点:");
|
||||
console.log(" 1. 观察控制台输出,了解ECS运行过程");
|
||||
console.log(" 2. 打开调试面板查看性能数据");
|
||||
console.log(" 3. 尝试修改组件参数观察变化");
|
||||
console.log(" 4. 查看代码学习ECS设计模式\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取玩家实体(供其他系统使用)
|
||||
*/
|
||||
public getPlayer(): Entity | null {
|
||||
return this.player;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有敌人(供其他系统使用)
|
||||
*/
|
||||
public getEnemies(): Entity[] {
|
||||
return this.enemies.filter(enemy => !enemy.isDestroyed);
|
||||
}
|
||||
|
||||
/**
|
||||
* 游戏重置方法
|
||||
*/
|
||||
public resetGame(): void {
|
||||
console.log("🔄 重置游戏...");
|
||||
|
||||
// 销毁所有实体
|
||||
if (this.player) {
|
||||
this.player.destroy();
|
||||
this.player = null;
|
||||
}
|
||||
|
||||
this.enemies.forEach(enemy => enemy.destroy());
|
||||
this.enemies = [];
|
||||
|
||||
// 重新创建实体
|
||||
this.createGameEntities();
|
||||
|
||||
console.log("✅ 游戏重置完成");
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景卸载时调用
|
||||
*/
|
||||
public unload(): void {
|
||||
console.log("🧹 清理示例游戏场景...");
|
||||
|
||||
// 清理引用
|
||||
this.player = null;
|
||||
this.enemies = [];
|
||||
|
||||
super.unload();
|
||||
console.log("✅ 场景清理完成");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "da87facc-89a0-47da-a0ef-423255200a51",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { Scene } from '@esengine/ecs-framework';
|
||||
|
||||
/**
|
||||
* 游戏场景
|
||||
*
|
||||
* 这是您的主游戏场景。在这里可以:
|
||||
* - 添加游戏系统
|
||||
* - 创建初始实体
|
||||
* - 设置场景参数
|
||||
*/
|
||||
export class GameScene extends Scene {
|
||||
|
||||
/**
|
||||
* 场景初始化
|
||||
* 在场景创建时调用,用于设置基础配置
|
||||
*/
|
||||
public initialize(): void {
|
||||
super.initialize();
|
||||
|
||||
// 设置场景名称
|
||||
this.name = "MainGameScene";
|
||||
|
||||
console.log('🎯 游戏场景已创建');
|
||||
|
||||
// TODO: 在这里添加您的游戏系统
|
||||
// 例如:this.addEntityProcessor(new MovementSystem());
|
||||
|
||||
// TODO: 在这里创建初始实体
|
||||
// 例如:this.createEntity("Player");
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景开始运行
|
||||
* 在场景开始时调用,用于执行启动逻辑
|
||||
*/
|
||||
public onStart(): void {
|
||||
super.onStart();
|
||||
|
||||
console.log('🚀 游戏场景已启动');
|
||||
|
||||
// TODO: 在这里添加场景启动逻辑
|
||||
// 例如:创建UI、播放音乐、初始化游戏状态等
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景卸载
|
||||
* 在场景结束时调用,用于清理资源
|
||||
*/
|
||||
public unload(): void {
|
||||
console.log('🛑 游戏场景已结束');
|
||||
|
||||
// TODO: 在这里添加清理逻辑
|
||||
// 例如:清理缓存、释放资源等
|
||||
|
||||
super.unload();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "8fee85be-2224-4200-a898-d3ae2406fb1d",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "1.2.0",
|
||||
"importer": "directory",
|
||||
"imported": true,
|
||||
"uuid": "a5e3a8c9-3d0b-4a36-9d20-6f70f1380131",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
import { EntitySystem, Entity, Matcher, Time } from '@esengine/ecs-framework';
|
||||
import { HealthComponent } from '../components/HealthComponent';
|
||||
|
||||
/**
|
||||
* 生命值系统 - 处理生命值相关的逻辑
|
||||
*
|
||||
* 展示生命值管理:
|
||||
* 1. 自动回血
|
||||
* 2. 无敌状态管理
|
||||
* 3. 死亡处理
|
||||
* 4. 事件触发
|
||||
*/
|
||||
export class HealthSystem extends EntitySystem {
|
||||
/** 回血延迟时间(受伤后多久开始回血,毫秒) */
|
||||
private regenDelay: number = 3000;
|
||||
|
||||
constructor() {
|
||||
// 只处理拥有HealthComponent的实体
|
||||
super(Matcher.empty().all(HealthComponent));
|
||||
}
|
||||
|
||||
public initialize(): void {
|
||||
super.initialize();
|
||||
console.log("HealthSystem 已初始化 - 开始处理生命值逻辑");
|
||||
}
|
||||
|
||||
/**
|
||||
* 每帧处理:更新生命值相关逻辑
|
||||
*/
|
||||
protected process(entities: Entity[]): void {
|
||||
for (const entity of entities) {
|
||||
const health = entity.getComponent(HealthComponent);
|
||||
|
||||
// 处理无敌状态
|
||||
this.processInvincibility(health);
|
||||
|
||||
// 处理生命值回复
|
||||
this.processHealthRegeneration(entity, health);
|
||||
|
||||
// 检查死亡状态
|
||||
this.checkDeathStatus(entity, health);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理无敌状态
|
||||
*/
|
||||
private processInvincibility(health: HealthComponent): void {
|
||||
if (health.invincible && health.invincibleDuration > 0) {
|
||||
health.invincibleDuration -= Time.deltaTime;
|
||||
|
||||
// 无敌时间结束
|
||||
if (health.invincibleDuration <= 0) {
|
||||
health.invincible = false;
|
||||
health.invincibleDuration = 0;
|
||||
console.log("无敌状态结束");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理生命值回复
|
||||
*/
|
||||
private processHealthRegeneration(entity: Entity, health: HealthComponent): void {
|
||||
// 如果已经满血或者没有回复速度,则不处理
|
||||
if (health.isFullHealth() || health.regenRate <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否超过了回血延迟时间
|
||||
const currentTime = Date.now();
|
||||
if (currentTime - health.lastDamageTime < this.regenDelay) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算回血量
|
||||
const regenAmount = health.regenRate * Time.deltaTime;
|
||||
const oldHealth = health.currentHealth;
|
||||
|
||||
// 执行回血
|
||||
health.heal(regenAmount);
|
||||
|
||||
// 如果实际回了血,输出日志
|
||||
if (health.currentHealth > oldHealth) {
|
||||
console.log(`${entity.name} 回血: ${oldHealth.toFixed(1)} -> ${health.currentHealth.toFixed(1)} (${health.getHealthPercentage() * 100}%)`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查死亡状态
|
||||
*/
|
||||
private checkDeathStatus(entity: Entity, health: HealthComponent): void {
|
||||
if (health.isDead()) {
|
||||
this.handleEntityDeath(entity, health);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理实体死亡
|
||||
*/
|
||||
private handleEntityDeath(entity: Entity, health: HealthComponent): void {
|
||||
console.log(`💀 ${entity.name} 已死亡!`);
|
||||
|
||||
// 触发死亡事件(如果有事件系统)
|
||||
this.triggerDeathEvent(entity);
|
||||
|
||||
// 可以在这里添加死亡效果、掉落物品等逻辑
|
||||
this.createDeathEffect(entity);
|
||||
|
||||
// 标记实体为死亡状态(而不是立即销毁)
|
||||
// 这样其他系统可以处理死亡相关的逻辑
|
||||
entity.addComponent(new DeadMarkerComponent());
|
||||
|
||||
// 可选:延迟销毁实体
|
||||
setTimeout(() => {
|
||||
if (entity && !entity.isDestroyed) {
|
||||
entity.destroy();
|
||||
console.log(`${entity.name} 已被销毁`);
|
||||
}
|
||||
}, 1000); // 1秒后销毁
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发死亡事件
|
||||
*/
|
||||
private triggerDeathEvent(entity: Entity): void {
|
||||
// 如果项目中有事件系统,可以在这里发送死亡事件
|
||||
console.log(`触发死亡事件: ${entity.name}`);
|
||||
|
||||
// 示例事件数据
|
||||
const deathEventData = {
|
||||
entityId: entity.id,
|
||||
entityName: entity.name,
|
||||
deathTime: Date.now(),
|
||||
position: this.getEntityPosition(entity)
|
||||
};
|
||||
|
||||
// 这里可以调用事件系统发送事件
|
||||
// eventBus.emit('entity:died', deathEventData);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建死亡效果
|
||||
*/
|
||||
private createDeathEffect(entity: Entity): void {
|
||||
console.log(`💥 为 ${entity.name} 创建死亡效果`);
|
||||
|
||||
// 在实际游戏中,这里可能会:
|
||||
// 1. 播放死亡动画
|
||||
// 2. 播放死亡音效
|
||||
// 3. 创建粒子效果
|
||||
// 4. 掉落物品
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实体位置(辅助方法)
|
||||
*/
|
||||
private getEntityPosition(entity: Entity): { x: number; y: number; z: number } {
|
||||
// 尝试获取位置组件
|
||||
const position = entity.getComponent(PositionComponent);
|
||||
if (position) {
|
||||
return {
|
||||
x: position.position.x,
|
||||
y: position.position.y,
|
||||
z: position.position.z
|
||||
};
|
||||
}
|
||||
|
||||
return { x: 0, y: 0, z: 0 };
|
||||
}
|
||||
|
||||
/**
|
||||
* 公共方法:对实体造成伤害
|
||||
* 这个方法可以被其他系统调用
|
||||
*/
|
||||
public damageEntity(entity: Entity, damage: number, source?: Entity): boolean {
|
||||
const health = entity.getComponent(HealthComponent);
|
||||
if (!health || health.invincible) {
|
||||
return false; // 无生命值组件或处于无敌状态
|
||||
}
|
||||
|
||||
const oldHealth = health.currentHealth;
|
||||
health.takeDamage(damage);
|
||||
|
||||
console.log(`⚔️ ${entity.name} 受到 ${damage} 点伤害: ${oldHealth.toFixed(1)} -> ${health.currentHealth.toFixed(1)}`);
|
||||
|
||||
// 如果有伤害来源,可以记录或处理
|
||||
if (source) {
|
||||
console.log(`伤害来源: ${source.name}`);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 公共方法:治疗实体
|
||||
*/
|
||||
public healEntity(entity: Entity, healAmount: number): boolean {
|
||||
const health = entity.getComponent(HealthComponent);
|
||||
if (!health || health.isFullHealth()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const oldHealth = health.currentHealth;
|
||||
health.heal(healAmount);
|
||||
|
||||
console.log(`💚 ${entity.name} 恢复 ${healAmount} 点生命值: ${oldHealth.toFixed(1)} -> ${health.currentHealth.toFixed(1)}`);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 死亡标记组件 - 标记已死亡的实体
|
||||
* 这是一个简单的标记组件,用于标识死亡状态
|
||||
*/
|
||||
class DeadMarkerComponent extends Component {
|
||||
public deathTime: number;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.deathTime = Date.now();
|
||||
}
|
||||
}
|
||||
|
||||
// 导入位置组件(用于获取实体位置)
|
||||
import { PositionComponent } from '../components/PositionComponent';
|
||||
import { Component } from '@esengine/ecs-framework';
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "455c12d1-52a8-41ac-b1b5-0d2b93c079aa",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
import { EntitySystem, Entity, Matcher, Time } from '@esengine/ecs-framework';
|
||||
import { PositionComponent } from '../components/PositionComponent';
|
||||
import { VelocityComponent } from '../components/VelocityComponent';
|
||||
|
||||
/**
|
||||
* 移动系统 - 处理实体的移动逻辑
|
||||
*
|
||||
* EntitySystem示例:
|
||||
* 1. 使用Matcher指定需要的组件(Position + Velocity)
|
||||
* 2. 每帧更新所有移动实体的位置
|
||||
* 3. 展示组件间的协作
|
||||
*/
|
||||
export class MovementSystem extends EntitySystem {
|
||||
constructor() {
|
||||
// 只处理同时拥有PositionComponent和VelocityComponent的实体
|
||||
super(Matcher.empty().all(PositionComponent, VelocityComponent));
|
||||
}
|
||||
|
||||
/**
|
||||
* 每帧执行:更新所有移动实体的位置
|
||||
*/
|
||||
protected process(entities: Entity[]): void {
|
||||
for (const entity of entities) {
|
||||
const position = entity.getComponent(PositionComponent);
|
||||
const velocity = entity.getComponent(VelocityComponent);
|
||||
|
||||
// 基本移动:位置 = 当前位置 + 速度 * 时间
|
||||
position.move(
|
||||
velocity.velocity.x * Time.deltaTime,
|
||||
velocity.velocity.y * Time.deltaTime,
|
||||
velocity.velocity.z * Time.deltaTime
|
||||
);
|
||||
|
||||
// 应用阻尼(摩擦力)
|
||||
velocity.applyDamping(Time.deltaTime);
|
||||
|
||||
// 可选:添加边界检查
|
||||
this.checkBoundaries(position, velocity);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 边界检查(可选功能)
|
||||
* 这个方法演示了如何在系统中实现额外的游戏逻辑
|
||||
*/
|
||||
private checkBoundaries(position: PositionComponent, velocity: VelocityComponent) {
|
||||
const bounds = {
|
||||
left: -400,
|
||||
right: 400,
|
||||
top: 300,
|
||||
bottom: -300
|
||||
};
|
||||
|
||||
// 检查X轴边界
|
||||
if (position.position.x < bounds.left) {
|
||||
position.position.x = bounds.left;
|
||||
velocity.velocity.x = Math.abs(velocity.velocity.x); // 反弹
|
||||
} else if (position.position.x > bounds.right) {
|
||||
position.position.x = bounds.right;
|
||||
velocity.velocity.x = -Math.abs(velocity.velocity.x); // 反弹
|
||||
}
|
||||
|
||||
// 检查Y轴边界
|
||||
if (position.position.y < bounds.bottom) {
|
||||
position.position.y = bounds.bottom;
|
||||
velocity.velocity.y = Math.abs(velocity.velocity.y); // 反弹
|
||||
} else if (position.position.y > bounds.top) {
|
||||
position.position.y = bounds.top;
|
||||
velocity.velocity.y = -Math.abs(velocity.velocity.y); // 反弹
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统初始化时调用
|
||||
* 可以在这里设置系统级别的配置
|
||||
*/
|
||||
public initialize(): void {
|
||||
super.initialize();
|
||||
console.log("MovementSystem 已初始化 - 开始处理实体移动");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统统计信息(用于调试)
|
||||
*/
|
||||
public getStats(): { processedEntities: number; totalMovement: number } {
|
||||
let totalMovement = 0;
|
||||
const entities = this.entities;
|
||||
|
||||
for (const entity of entities) {
|
||||
const position = entity.getComponent(PositionComponent);
|
||||
totalMovement += position.getMovementDistance();
|
||||
}
|
||||
|
||||
return {
|
||||
processedEntities: entities.length,
|
||||
totalMovement: totalMovement
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "a8712467-efe0-46ec-a246-a9fa07d203d9",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
import { EntitySystem, Entity, Matcher } from '@esengine/ecs-framework';
|
||||
import { PlayerInputComponent } from '../components/PlayerInputComponent';
|
||||
import { VelocityComponent } from '../components/VelocityComponent';
|
||||
import { input, Input, EventKeyboard, KeyCode } from 'cc';
|
||||
|
||||
/**
|
||||
* 玩家输入系统 - 处理玩家输入并转换为游戏行为
|
||||
*
|
||||
* 展示系统的职责:
|
||||
* 1. 收集输入事件
|
||||
* 2. 更新输入组件状态
|
||||
* 3. 根据输入修改其他组件(如速度)
|
||||
*/
|
||||
export class PlayerInputSystem extends EntitySystem {
|
||||
private moveSpeed: number = 200; // 移动速度
|
||||
|
||||
constructor() {
|
||||
// 只处理拥有PlayerInputComponent的实体
|
||||
super(Matcher.empty().all(PlayerInputComponent));
|
||||
}
|
||||
|
||||
public initialize(): void {
|
||||
super.initialize();
|
||||
console.log("PlayerInputSystem 已初始化 - 开始监听玩家输入");
|
||||
|
||||
// 注册键盘事件监听器
|
||||
this.setupInputListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置输入事件监听器
|
||||
*/
|
||||
private setupInputListeners(): void {
|
||||
// 键盘按下事件
|
||||
input.on(Input.EventType.KEY_DOWN, this.onKeyDown, this);
|
||||
// 键盘抬起事件
|
||||
input.on(Input.EventType.KEY_UP, this.onKeyUp, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 键盘按下处理
|
||||
*/
|
||||
private onKeyDown(event: EventKeyboard): void {
|
||||
const keyCode = event.keyCode;
|
||||
const keyName = this.getKeyName(keyCode);
|
||||
|
||||
// 更新所有玩家实体的输入状态
|
||||
for (const entity of this.entities) {
|
||||
const playerInput = entity.getComponent(PlayerInputComponent);
|
||||
if (playerInput && playerInput.inputEnabled) {
|
||||
playerInput.setKey(keyName, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 键盘抬起处理
|
||||
*/
|
||||
private onKeyUp(event: EventKeyboard): void {
|
||||
const keyCode = event.keyCode;
|
||||
const keyName = this.getKeyName(keyCode);
|
||||
|
||||
// 更新所有玩家实体的输入状态
|
||||
for (const entity of this.entities) {
|
||||
const playerInput = entity.getComponent(PlayerInputComponent);
|
||||
if (playerInput && playerInput.inputEnabled) {
|
||||
playerInput.setKey(keyName, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 每帧处理:根据输入状态更新实体行为
|
||||
*/
|
||||
protected process(entities: Entity[]): void {
|
||||
for (const entity of entities) {
|
||||
const playerInput = entity.getComponent(PlayerInputComponent);
|
||||
|
||||
if (!playerInput || !playerInput.inputEnabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 处理移动输入
|
||||
this.processMovementInput(entity, playerInput);
|
||||
|
||||
// 处理其他输入(如攻击、跳跃等)
|
||||
this.processActionInput(entity, playerInput);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理移动输入
|
||||
*/
|
||||
private processMovementInput(entity: Entity, playerInput: PlayerInputComponent): void {
|
||||
const velocity = entity.getComponent(VelocityComponent);
|
||||
if (!velocity) return;
|
||||
|
||||
// 根据按键状态计算移动方向
|
||||
let moveX = 0;
|
||||
let moveY = 0;
|
||||
|
||||
if (playerInput.isKeyPressed('A') || playerInput.isKeyPressed('ArrowLeft')) {
|
||||
moveX -= 1;
|
||||
}
|
||||
if (playerInput.isKeyPressed('D') || playerInput.isKeyPressed('ArrowRight')) {
|
||||
moveX += 1;
|
||||
}
|
||||
if (playerInput.isKeyPressed('W') || playerInput.isKeyPressed('ArrowUp')) {
|
||||
moveY += 1;
|
||||
}
|
||||
if (playerInput.isKeyPressed('S') || playerInput.isKeyPressed('ArrowDown')) {
|
||||
moveY -= 1;
|
||||
}
|
||||
|
||||
// 更新输入组件的移动方向
|
||||
playerInput.setMoveDirection(moveX, moveY);
|
||||
|
||||
// 将输入转换为速度
|
||||
const normalizedDirection = playerInput.getNormalizedMoveDirection();
|
||||
velocity.setVelocity(
|
||||
normalizedDirection.x * this.moveSpeed * playerInput.sensitivity,
|
||||
normalizedDirection.y * this.moveSpeed * playerInput.sensitivity,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理动作输入(攻击、技能等)
|
||||
*/
|
||||
private processActionInput(entity: Entity, playerInput: PlayerInputComponent): void {
|
||||
// 空格键 - 跳跃或攻击
|
||||
if (playerInput.isKeyPressed('Space')) {
|
||||
console.log(`玩家 ${entity.name} 执行动作:攻击/跳跃`);
|
||||
// 这里可以触发攻击组件或添加跳跃效果
|
||||
}
|
||||
|
||||
// ESC键 - 暂停游戏
|
||||
if (playerInput.isKeyPressed('Escape')) {
|
||||
console.log("玩家请求暂停游戏");
|
||||
// 可以发送暂停事件给游戏管理系统
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将键码转换为字符串
|
||||
*/
|
||||
private getKeyName(keyCode: KeyCode): string {
|
||||
const keyMap: { [key: number]: string } = {
|
||||
[KeyCode.KEY_A]: 'A',
|
||||
[KeyCode.KEY_D]: 'D',
|
||||
[KeyCode.KEY_S]: 'S',
|
||||
[KeyCode.KEY_W]: 'W',
|
||||
[KeyCode.ARROW_LEFT]: 'ArrowLeft',
|
||||
[KeyCode.ARROW_RIGHT]: 'ArrowRight',
|
||||
[KeyCode.ARROW_UP]: 'ArrowUp',
|
||||
[KeyCode.ARROW_DOWN]: 'ArrowDown',
|
||||
[KeyCode.SPACE]: 'Space',
|
||||
[KeyCode.ESCAPE]: 'Escape'
|
||||
};
|
||||
|
||||
return keyMap[keyCode] || `Key_${keyCode}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统清理
|
||||
*/
|
||||
public onDestroy(): void {
|
||||
// 移除事件监听器
|
||||
input.off(Input.EventType.KEY_DOWN, this.onKeyDown, this);
|
||||
input.off(Input.EventType.KEY_UP, this.onKeyUp, this);
|
||||
console.log("PlayerInputSystem 已清理");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"ver": "4.0.24",
|
||||
"importer": "typescript",
|
||||
"imported": true,
|
||||
"uuid": "7b69a39f-926a-4260-94ba-e15e31b324b5",
|
||||
"files": [],
|
||||
"subMetas": {},
|
||||
"userData": {}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"codeGeneration": {
|
||||
"template": "typescript",
|
||||
"useStrictMode": true,
|
||||
"generateComments": true,
|
||||
"generateImports": true,
|
||||
"componentSuffix": "Component",
|
||||
"systemSuffix": "System",
|
||||
"indentStyle": "spaces",
|
||||
"indentSize": 4
|
||||
},
|
||||
"performance": {
|
||||
"enableMonitoring": true,
|
||||
"warningThreshold": 16.67,
|
||||
"criticalThreshold": 33.33,
|
||||
"memoryWarningMB": 100,
|
||||
"memoryCriticalMB": 200,
|
||||
"maxRecentSamples": 60,
|
||||
"enableFpsMonitoring": true,
|
||||
"targetFps": 120
|
||||
},
|
||||
"debugging": {
|
||||
"enableDebugMode": true,
|
||||
"showEntityCount": true,
|
||||
"showSystemExecutionTime": true,
|
||||
"enablePerformanceWarnings": true,
|
||||
"logLevel": "info",
|
||||
"enableDetailedLogs": false
|
||||
},
|
||||
"editor": {
|
||||
"autoRefreshAssets": true,
|
||||
"showWelcomePanelOnStartup": true,
|
||||
"enableAutoUpdates": false,
|
||||
"updateChannel": "stable",
|
||||
"enableNotifications": true
|
||||
},
|
||||
"template": {
|
||||
"defaultEntityName": "ModifiedEntity",
|
||||
"defaultComponentName": "TestComponent",
|
||||
"defaultSystemName": "TestSystem",
|
||||
"createExampleFiles": true,
|
||||
"includeDocumentation": true,
|
||||
"useFactoryPattern": true
|
||||
},
|
||||
"events": {
|
||||
"enableEventSystem": true,
|
||||
"defaultEventPriority": 0,
|
||||
"enableAsyncEvents": true,
|
||||
"enableEventBatching": false,
|
||||
"batchSize": 10,
|
||||
"batchDelay": 16,
|
||||
"maxEventListeners": 100
|
||||
}
|
||||
}
|
||||
@@ -25,22 +25,57 @@
|
||||
"default": {
|
||||
"title": "ECS Framework - 欢迎面板",
|
||||
"type": "dockable",
|
||||
"main": "dist/panels/default",
|
||||
"main": "dist/panels/default/index.js",
|
||||
"size": {
|
||||
"min-width": 450,
|
||||
"min-height": 600,
|
||||
"width": 850,
|
||||
"height": 800
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "ECS Framework - 设置",
|
||||
"type": "dockable",
|
||||
"main": "dist/panels/settings/index.js",
|
||||
"size": {
|
||||
"min-width": 600,
|
||||
"min-height": 700,
|
||||
"width": 800,
|
||||
"height": 900
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"title": "ECS Framework - 调试面板",
|
||||
"type": "dockable",
|
||||
"main": "dist/panels/debug/index.js",
|
||||
"size": {
|
||||
"min-width": 400,
|
||||
"min-height": 500,
|
||||
"width": 500,
|
||||
"height": 600
|
||||
}
|
||||
}
|
||||
},
|
||||
"contributions": {
|
||||
"scene": {
|
||||
"script": "./dist/scene.js"
|
||||
},
|
||||
"menu": [
|
||||
{
|
||||
"path": "i18n:menu.panel/ECS Framework",
|
||||
"label": "欢迎面板",
|
||||
"message": "open-panel"
|
||||
},
|
||||
{
|
||||
"path": "i18n:menu.panel/ECS Framework",
|
||||
"label": "插件设置",
|
||||
"message": "open-settings"
|
||||
},
|
||||
{
|
||||
"path": "i18n:menu.panel/ECS Framework",
|
||||
"label": "调试面板",
|
||||
"message": "open-debug"
|
||||
},
|
||||
{
|
||||
"path": "i18n:menu.develop/ECS Framework",
|
||||
"label": "ECS 开发工具",
|
||||
@@ -97,6 +132,16 @@
|
||||
"methods": [
|
||||
"open-github"
|
||||
]
|
||||
},
|
||||
"settings-updated": {
|
||||
"methods": [
|
||||
"settings-updated"
|
||||
]
|
||||
},
|
||||
"open-debug": {
|
||||
"methods": [
|
||||
"open-debug"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ export class TemplateGenerator {
|
||||
*/
|
||||
public getExistingFiles(): string[] {
|
||||
if (!this.checkTemplateExists()) return [];
|
||||
|
||||
|
||||
const files: string[] = [];
|
||||
this.scanDirectory(this.ecsDir, '', files);
|
||||
return files;
|
||||
@@ -34,12 +34,12 @@ export class TemplateGenerator {
|
||||
|
||||
private scanDirectory(dirPath: string, relativePath: string, files: string[]): void {
|
||||
if (!fs.existsSync(dirPath)) return;
|
||||
|
||||
|
||||
const items = fs.readdirSync(dirPath);
|
||||
for (const item of items) {
|
||||
const fullPath = path.join(dirPath, item);
|
||||
const relativeFilePath = relativePath ? `${relativePath}/${item}` : item;
|
||||
|
||||
|
||||
if (fs.statSync(fullPath).isDirectory()) {
|
||||
this.scanDirectory(fullPath, relativeFilePath, files);
|
||||
} else {
|
||||
@@ -64,16 +64,16 @@ export class TemplateGenerator {
|
||||
public createTemplate(): void {
|
||||
// 创建目录结构
|
||||
this.createDirectories();
|
||||
|
||||
|
||||
// 创建ECS启动管理器
|
||||
this.createECSManager();
|
||||
|
||||
|
||||
// 创建基础游戏场景
|
||||
this.createBaseGameScene();
|
||||
|
||||
|
||||
// 创建README文档
|
||||
this.createReadme();
|
||||
|
||||
|
||||
console.log('ECS启动模板创建成功');
|
||||
}
|
||||
|
||||
@@ -141,8 +141,27 @@ export class ECSManager extends Component {
|
||||
console.log('🎮 正在初始化ECS框架...');
|
||||
|
||||
try {
|
||||
// 1. 创建Core实例
|
||||
Core.create(this.debugMode);
|
||||
// 1. 创建Core实例,启用调试功能
|
||||
if (this.debugMode) {
|
||||
Core.create({
|
||||
debugConfig: {
|
||||
enabled: true,
|
||||
websocketUrl: 'ws://localhost:8080/ecs-debug',
|
||||
autoReconnect: true,
|
||||
updateInterval: 100,
|
||||
channels: {
|
||||
entities: true,
|
||||
systems: true,
|
||||
performance: true,
|
||||
components: true,
|
||||
scenes: true
|
||||
}
|
||||
}
|
||||
});
|
||||
console.log('🔧 ECS调试模式已启用,可在Cocos Creator扩展面板中查看调试信息');
|
||||
} else {
|
||||
Core.create(false);
|
||||
}
|
||||
|
||||
// 2. 创建游戏场景
|
||||
const gameScene = new GameScene();
|
||||
@@ -284,11 +303,25 @@ ECS框架已经配置完成!您只需要:
|
||||
|
||||
\`\`\`
|
||||
🎮 正在初始化ECS框架...
|
||||
🔧 ECS调试模式已启用,可在Cocos Creator扩展面板中查看调试信息
|
||||
🎯 游戏场景已创建
|
||||
✅ ECS框架初始化成功!
|
||||
🚀 游戏场景已启动
|
||||
\`\`\`
|
||||
|
||||
### 3. 使用调试面板
|
||||
|
||||
ECS框架已启用调试功能,您可以:
|
||||
|
||||
1. 在Cocos Creator编辑器菜单中选择 "扩展" → "ECS Framework" → "调试面板"
|
||||
2. 调试面板将显示实时的ECS运行状态:
|
||||
- 实体数量和状态
|
||||
- 系统执行信息
|
||||
- 性能监控数据
|
||||
- 组件统计信息
|
||||
|
||||
**注意**:调试功能会消耗一定性能,正式发布时建议关闭调试模式。
|
||||
|
||||
## 📚 下一步开发
|
||||
|
||||
### 创建您的第一个组件
|
||||
|
||||
@@ -122,7 +122,7 @@ export const methods: { [key: string]: (...any: any) => any } = {
|
||||
'open-documentation'() {
|
||||
// 使用系统默认命令打开链接
|
||||
const url = 'https://github.com/esengine/ecs-framework/blob/master/README.md';
|
||||
exec(`start ${url}`, (error) => {
|
||||
exec(`start "" "${url}"`, (error) => {
|
||||
if (error) {
|
||||
console.error('Failed to open documentation:', error);
|
||||
Editor.Dialog.info('打开文档', {
|
||||
@@ -201,10 +201,17 @@ export const methods: { [key: string]: (...any: any) => any } = {
|
||||
* 打开设置
|
||||
*/
|
||||
'open-settings'() {
|
||||
Editor.Dialog.info('插件设置', {
|
||||
detail: '设置面板开发中...\n\n将在下个版本提供完整的插件配置功能。\n\n预计功能:\n• 代码生成模板配置\n• 性能监控设置\n• 自动更新设置\n• 开发工具偏好',
|
||||
buttons: ['好的'],
|
||||
});
|
||||
console.log('Opening ECS Framework settings panel...');
|
||||
try {
|
||||
// 正确的打开特定面板的方法
|
||||
Editor.Panel.open(packageJSON.name + '.settings');
|
||||
console.log('Settings panel opened successfully');
|
||||
} catch (error) {
|
||||
console.error('Failed to open settings panel:', error);
|
||||
Editor.Dialog.error('打开设置失败', {
|
||||
detail: `无法打开设置面板:\n\n${error}\n\n请尝试重启Cocos Creator编辑器。`,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -226,6 +233,74 @@ export const methods: { [key: string]: (...any: any) => any } = {
|
||||
buttons: ['好的'],
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 打开GitHub仓库
|
||||
*/
|
||||
'open-github'() {
|
||||
const url = 'https://github.com/esengine/ecs-framework';
|
||||
// 在Windows上使用正确的start命令语法
|
||||
exec(`start "" "${url}"`, (error) => {
|
||||
if (error) {
|
||||
console.error('Failed to open GitHub:', error);
|
||||
Editor.Dialog.info('打开GitHub', {
|
||||
detail: `请手动访问以下链接:\n\n${url}`,
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 打开调试面板
|
||||
*/
|
||||
'open-debug'() {
|
||||
console.log('Opening ECS Framework debug panel...');
|
||||
try {
|
||||
// 正确的打开特定面板的方法
|
||||
Editor.Panel.open(packageJSON.name + '.debug');
|
||||
console.log('Debug panel opened successfully');
|
||||
} catch (error) {
|
||||
console.error('Failed to open debug panel:', error);
|
||||
Editor.Dialog.error('打开调试面板失败', {
|
||||
detail: `无法打开调试面板:\n\n${error}\n\n请尝试重启Cocos Creator编辑器。`,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 处理设置更新
|
||||
*/
|
||||
'settings-updated'(settings: any) {
|
||||
console.log('ECS Framework settings updated:', settings);
|
||||
|
||||
// 这里可以根据设置更新做一些处理
|
||||
// 比如重新配置框架、更新性能监控等
|
||||
|
||||
// 示例:根据设置更新性能监控
|
||||
if (settings?.performance?.enableMonitoring) {
|
||||
console.log('Performance monitoring enabled with thresholds:', {
|
||||
warning: settings.performance.warningThreshold,
|
||||
critical: settings.performance.criticalThreshold,
|
||||
memoryWarning: settings.performance.memoryWarningMB,
|
||||
memoryCritical: settings.performance.memoryCriticalMB
|
||||
});
|
||||
}
|
||||
|
||||
// 示例:根据设置更新调试模式
|
||||
if (settings?.debugging?.enableDebugMode) {
|
||||
console.log('Debug mode enabled with log level:', settings.debugging.logLevel);
|
||||
}
|
||||
|
||||
// 示例:根据设置更新事件系统
|
||||
if (settings?.events?.enableEventSystem) {
|
||||
console.log('Event system configured:', {
|
||||
asyncEvents: settings.events.enableAsyncEvents,
|
||||
batching: settings.events.enableEventBatching,
|
||||
batchSize: settings.events.batchSize,
|
||||
maxListeners: settings.events.maxEventListeners
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,612 @@
|
||||
import { readFileSync } from 'fs-extra';
|
||||
import { join } from 'path';
|
||||
import { createApp, App, defineComponent, ref, reactive, onMounted, onUnmounted } from 'vue';
|
||||
import { WebSocketServer } from 'ws';
|
||||
|
||||
const panelDataMap = new WeakMap<any, App>();
|
||||
|
||||
/**
|
||||
* 游戏实例信息
|
||||
*/
|
||||
interface GameInstance {
|
||||
id: string;
|
||||
name: string;
|
||||
connectTime: number;
|
||||
lastUpdateTime: number;
|
||||
isActive: boolean;
|
||||
debugData?: any;
|
||||
ws?: any; // WebSocket连接
|
||||
}
|
||||
|
||||
/**
|
||||
* 详细的调试信息接口
|
||||
*/
|
||||
interface DetailedDebugInfo {
|
||||
// 基础信息
|
||||
instanceId: string;
|
||||
instanceName: string;
|
||||
isRunning: boolean;
|
||||
frameworkLoaded: boolean;
|
||||
currentScene: string;
|
||||
uptime: number;
|
||||
|
||||
// 性能信息
|
||||
performance: {
|
||||
frameTime: number;
|
||||
fps: number;
|
||||
averageFrameTime: number;
|
||||
minFrameTime: number;
|
||||
maxFrameTime: number;
|
||||
frameTimeHistory: number[];
|
||||
};
|
||||
|
||||
// 内存信息
|
||||
memory: {
|
||||
totalMemory: number;
|
||||
usedMemory: number;
|
||||
freeMemory: number;
|
||||
entityMemory: number;
|
||||
componentMemory: number;
|
||||
systemMemory: number;
|
||||
pooledMemory: number;
|
||||
gcCollections: number;
|
||||
};
|
||||
|
||||
// 实体信息
|
||||
entities: {
|
||||
total: number;
|
||||
active: number;
|
||||
inactive: number;
|
||||
pendingAdd: number;
|
||||
pendingRemove: number;
|
||||
entitiesPerArchetype: Array<{
|
||||
signature: string;
|
||||
count: number;
|
||||
memory: number;
|
||||
}>;
|
||||
topEntitiesByComponents: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
componentCount: number;
|
||||
memory: number;
|
||||
}>;
|
||||
};
|
||||
|
||||
// 组件信息
|
||||
components: {
|
||||
totalTypes: number;
|
||||
totalInstances: number;
|
||||
componentStats: Array<{
|
||||
typeName: string;
|
||||
instanceCount: number;
|
||||
memoryPerInstance: number;
|
||||
totalMemory: number;
|
||||
poolSize: number;
|
||||
poolUtilization: number;
|
||||
}>;
|
||||
};
|
||||
|
||||
// 系统信息
|
||||
systems: {
|
||||
total: number;
|
||||
systemStats: Array<{
|
||||
name: string;
|
||||
type: string;
|
||||
entityCount: number;
|
||||
averageExecutionTime: number;
|
||||
minExecutionTime: number;
|
||||
maxExecutionTime: number;
|
||||
executionTimeHistory: number[];
|
||||
memoryUsage: number;
|
||||
updateOrder: number;
|
||||
enabled: boolean;
|
||||
}>;
|
||||
};
|
||||
|
||||
// 场景信息
|
||||
scenes: {
|
||||
currentScene: string;
|
||||
sceneMemory: number;
|
||||
sceneEntityCount: number;
|
||||
sceneSystemCount: number;
|
||||
sceneUptime: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* ECS调试服务器
|
||||
* 作为服务端,接收多个游戏实例的连接
|
||||
*/
|
||||
class ECSDebugServer {
|
||||
private wss?: WebSocketServer;
|
||||
private port: number = 8080;
|
||||
private gameInstances = new Map<string, GameInstance>();
|
||||
private isRunning: boolean = false;
|
||||
|
||||
constructor(port: number = 8080) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
async start(): Promise<boolean> {
|
||||
if (this.isRunning) return true;
|
||||
|
||||
try {
|
||||
this.wss = new WebSocketServer({ port: this.port });
|
||||
|
||||
this.wss.on('connection', (ws, req) => {
|
||||
const instanceId = this.generateInstanceId();
|
||||
const instance: GameInstance = {
|
||||
id: instanceId,
|
||||
name: `游戏实例-${instanceId.substring(0, 8)}`,
|
||||
connectTime: Date.now(),
|
||||
lastUpdateTime: Date.now(),
|
||||
isActive: true,
|
||||
debugData: null,
|
||||
ws: ws
|
||||
};
|
||||
|
||||
this.gameInstances.set(instanceId, instance);
|
||||
console.log(`[ECS Debug Server] New instance connected: ${instance.name}`);
|
||||
|
||||
ws.on('message', (data) => {
|
||||
try {
|
||||
const message = JSON.parse(data.toString());
|
||||
this.handleMessage(instanceId, message);
|
||||
} catch (error) {
|
||||
console.error('[ECS Debug Server] Failed to parse message:', error);
|
||||
}
|
||||
});
|
||||
|
||||
ws.on('close', () => {
|
||||
const instance = this.gameInstances.get(instanceId);
|
||||
if (instance) {
|
||||
instance.isActive = false;
|
||||
console.log(`[ECS Debug Server] Instance disconnected: ${instance.name}`);
|
||||
}
|
||||
});
|
||||
|
||||
ws.on('error', (error) => {
|
||||
console.error(`[ECS Debug Server] WebSocket error for ${instanceId}:`, error);
|
||||
});
|
||||
|
||||
// 发送连接确认
|
||||
this.sendToInstance(instanceId, {
|
||||
type: 'connection_confirmed',
|
||||
instanceId: instanceId,
|
||||
serverTime: Date.now()
|
||||
});
|
||||
});
|
||||
|
||||
this.isRunning = true;
|
||||
console.log(`[ECS Debug Server] Started on port ${this.port}`);
|
||||
return true;
|
||||
|
||||
} catch (error) {
|
||||
console.error('[ECS Debug Server] Failed to start:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
if (this.wss) {
|
||||
this.wss.close();
|
||||
this.wss = undefined;
|
||||
}
|
||||
this.gameInstances.clear();
|
||||
this.isRunning = false;
|
||||
console.log('[ECS Debug Server] Stopped');
|
||||
}
|
||||
|
||||
private generateInstanceId(): string {
|
||||
return Math.random().toString(36).substring(2) + Date.now().toString(36);
|
||||
}
|
||||
|
||||
private handleMessage(instanceId: string, message: any): void {
|
||||
const instance = this.gameInstances.get(instanceId);
|
||||
if (!instance) return;
|
||||
|
||||
switch (message.type) {
|
||||
case 'debug_data':
|
||||
instance.debugData = message.data;
|
||||
instance.lastUpdateTime = Date.now();
|
||||
break;
|
||||
|
||||
case 'instance_info':
|
||||
if (message.name) {
|
||||
instance.name = message.name;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'ping':
|
||||
this.sendToInstance(instanceId, { type: 'pong', timestamp: Date.now() });
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private sendToInstance(instanceId: string, message: any): void {
|
||||
const instance = this.gameInstances.get(instanceId);
|
||||
if (instance && instance.ws && instance.ws.readyState === 1) {
|
||||
instance.ws.send(JSON.stringify(message));
|
||||
}
|
||||
}
|
||||
|
||||
get running(): boolean {
|
||||
return this.isRunning;
|
||||
}
|
||||
|
||||
get instances(): GameInstance[] {
|
||||
return Array.from(this.gameInstances.values());
|
||||
}
|
||||
|
||||
getInstance(instanceId: string): GameInstance | undefined {
|
||||
return this.gameInstances.get(instanceId);
|
||||
}
|
||||
|
||||
getInstanceDebugData(instanceId: string): DetailedDebugInfo | null {
|
||||
const instance = this.gameInstances.get(instanceId);
|
||||
if (!instance || !instance.debugData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.transformToDetailedDebugInfo(instance, instance.debugData);
|
||||
}
|
||||
|
||||
private transformToDetailedDebugInfo(instance: GameInstance, rawData: any): DetailedDebugInfo {
|
||||
const uptime = (Date.now() - instance.connectTime) / 1000;
|
||||
|
||||
return {
|
||||
instanceId: instance.id,
|
||||
instanceName: instance.name,
|
||||
isRunning: rawData.isRunning || false,
|
||||
frameworkLoaded: rawData.frameworkLoaded || false,
|
||||
currentScene: rawData.currentScene || '未知',
|
||||
uptime: uptime,
|
||||
|
||||
performance: {
|
||||
frameTime: rawData.performance?.frameTime || 0,
|
||||
fps: rawData.performance?.fps || 0,
|
||||
averageFrameTime: rawData.performance?.averageFrameTime || rawData.performance?.frameTime || 0,
|
||||
minFrameTime: rawData.performance?.minFrameTime || rawData.performance?.frameTime || 0,
|
||||
maxFrameTime: rawData.performance?.maxFrameTime || rawData.performance?.frameTime || 0,
|
||||
frameTimeHistory: rawData.performance?.frameTimeHistory || []
|
||||
},
|
||||
|
||||
memory: {
|
||||
totalMemory: rawData.performance?.memoryDetails?.totalMemory || rawData.memory?.totalMemory || 512 * 1024 * 1024,
|
||||
usedMemory: rawData.performance?.memoryDetails?.usedMemory || (rawData.performance?.memoryUsage ? rawData.performance.memoryUsage * 1024 * 1024 : 0),
|
||||
freeMemory: rawData.performance?.memoryDetails?.freeMemory || 0,
|
||||
entityMemory: rawData.performance?.memoryDetails?.entities || rawData.memory?.entityMemory || 0,
|
||||
componentMemory: rawData.performance?.memoryDetails?.components || rawData.memory?.componentMemory || 0,
|
||||
systemMemory: rawData.performance?.memoryDetails?.systems || rawData.memory?.systemMemory || 0,
|
||||
pooledMemory: rawData.performance?.memoryDetails?.pooled || rawData.memory?.pooledMemory || 0,
|
||||
gcCollections: rawData.performance?.memoryDetails?.gcCollections || rawData.memory?.gcCollections || 0
|
||||
},
|
||||
|
||||
entities: {
|
||||
total: rawData.entities?.totalEntities || 0,
|
||||
active: rawData.entities?.activeEntities || 0,
|
||||
inactive: (rawData.entities?.totalEntities || 0) - (rawData.entities?.activeEntities || 0),
|
||||
pendingAdd: rawData.entities?.pendingAdd || 0,
|
||||
pendingRemove: rawData.entities?.pendingRemove || 0,
|
||||
entitiesPerArchetype: rawData.entities?.entitiesPerArchetype || [],
|
||||
topEntitiesByComponents: rawData.entities?.topEntitiesByComponents || []
|
||||
},
|
||||
|
||||
components: {
|
||||
totalTypes: rawData.components?.componentTypes || 0,
|
||||
totalInstances: rawData.components?.componentInstances || 0,
|
||||
componentStats: (rawData.components?.componentStats || []).map((comp: any) => ({
|
||||
typeName: comp.typeName,
|
||||
instanceCount: comp.instanceCount || 0,
|
||||
memoryPerInstance: comp.memoryPerInstance || 0,
|
||||
totalMemory: comp.totalMemory || (comp.instanceCount || 0) * (comp.memoryPerInstance || 0),
|
||||
poolSize: comp.poolSize || 0,
|
||||
poolUtilization: comp.poolSize > 0 ? (comp.instanceCount / comp.poolSize * 100) : 0
|
||||
}))
|
||||
},
|
||||
|
||||
systems: {
|
||||
total: rawData.systems?.totalSystems || 0,
|
||||
systemStats: (rawData.systems?.systemsInfo || []).map((sys: any) => ({
|
||||
name: sys.name,
|
||||
type: sys.type || 'Unknown',
|
||||
entityCount: sys.entityCount || 0,
|
||||
averageExecutionTime: sys.executionTime || 0,
|
||||
minExecutionTime: sys.minExecutionTime || sys.executionTime || 0,
|
||||
maxExecutionTime: sys.maxExecutionTime || sys.executionTime || 0,
|
||||
executionTimeHistory: sys.executionTimeHistory || [],
|
||||
memoryUsage: sys.memoryUsage || 0,
|
||||
updateOrder: sys.updateOrder || 0,
|
||||
enabled: sys.enabled !== false
|
||||
}))
|
||||
},
|
||||
|
||||
scenes: {
|
||||
currentScene: rawData.currentScene || '未知',
|
||||
sceneMemory: rawData.scenes?.sceneMemory || 0,
|
||||
sceneEntityCount: rawData.entities?.totalEntities || 0,
|
||||
sceneSystemCount: rawData.systems?.totalSystems || 0,
|
||||
sceneUptime: rawData.scenes?.sceneUptime || uptime
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认调试信息
|
||||
*/
|
||||
const defaultDebugInfo: DetailedDebugInfo = {
|
||||
instanceId: '',
|
||||
instanceName: '未选择实例',
|
||||
isRunning: false,
|
||||
frameworkLoaded: false,
|
||||
currentScene: '未知',
|
||||
uptime: 0,
|
||||
performance: {
|
||||
frameTime: 0,
|
||||
fps: 0,
|
||||
averageFrameTime: 0,
|
||||
minFrameTime: 0,
|
||||
maxFrameTime: 0,
|
||||
frameTimeHistory: []
|
||||
},
|
||||
memory: {
|
||||
totalMemory: 0,
|
||||
usedMemory: 0,
|
||||
freeMemory: 0,
|
||||
entityMemory: 0,
|
||||
componentMemory: 0,
|
||||
systemMemory: 0,
|
||||
pooledMemory: 0,
|
||||
gcCollections: 0
|
||||
},
|
||||
entities: {
|
||||
total: 0,
|
||||
active: 0,
|
||||
inactive: 0,
|
||||
pendingAdd: 0,
|
||||
pendingRemove: 0,
|
||||
entitiesPerArchetype: [],
|
||||
topEntitiesByComponents: []
|
||||
},
|
||||
components: {
|
||||
totalTypes: 0,
|
||||
totalInstances: 0,
|
||||
componentStats: []
|
||||
},
|
||||
systems: {
|
||||
total: 0,
|
||||
systemStats: []
|
||||
},
|
||||
scenes: {
|
||||
currentScene: '未知',
|
||||
sceneMemory: 0,
|
||||
sceneEntityCount: 0,
|
||||
sceneSystemCount: 0,
|
||||
sceneUptime: 0
|
||||
}
|
||||
};
|
||||
|
||||
// 全局调试服务器实例
|
||||
let globalDebugServer: ECSDebugServer | null = null;
|
||||
|
||||
/**
|
||||
* 启动调试服务器
|
||||
*/
|
||||
async function ensureDebugServer(): Promise<ECSDebugServer> {
|
||||
if (!globalDebugServer) {
|
||||
globalDebugServer = new ECSDebugServer(8080);
|
||||
}
|
||||
|
||||
if (!globalDebugServer.running) {
|
||||
await globalDebugServer.start();
|
||||
}
|
||||
|
||||
return globalDebugServer;
|
||||
}
|
||||
|
||||
module.exports = Editor.Panel.define({
|
||||
listeners: {
|
||||
show() { },
|
||||
hide() { },
|
||||
},
|
||||
template: `<div id="app"></div>`,
|
||||
style: readFileSync(join(__dirname, '../../../static/style/debug/index.css'), 'utf-8'),
|
||||
$: {
|
||||
app: '#app',
|
||||
},
|
||||
ready() {
|
||||
if (this.$.app) {
|
||||
const app = createApp(defineComponent({
|
||||
setup() {
|
||||
const debugInfo = reactive<DetailedDebugInfo>({ ...defaultDebugInfo });
|
||||
const gameInstances = ref<GameInstance[]>([]);
|
||||
const selectedInstanceId = ref<string>('');
|
||||
const isAutoRefresh = ref(true);
|
||||
const refreshInterval = ref(100);
|
||||
const lastUpdateTime = ref('');
|
||||
|
||||
let intervalId: NodeJS.Timeout | null = null;
|
||||
let debugServer: ECSDebugServer | null = null;
|
||||
|
||||
// 初始化调试服务器
|
||||
const initializeServer = async () => {
|
||||
try {
|
||||
debugServer = await ensureDebugServer();
|
||||
} catch (error) {
|
||||
console.error('Failed to start debug server:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 更新游戏实例列表和调试信息
|
||||
const updateDebugInfo = async () => {
|
||||
if (!debugServer) return;
|
||||
|
||||
try {
|
||||
// 更新实例列表
|
||||
gameInstances.value = debugServer.instances;
|
||||
|
||||
// 如果有选中的实例,更新其调试信息
|
||||
if (selectedInstanceId.value) {
|
||||
const detailedInfo = debugServer.getInstanceDebugData(selectedInstanceId.value);
|
||||
if (detailedInfo) {
|
||||
Object.assign(debugInfo, detailedInfo);
|
||||
} else {
|
||||
// 实例已断开,重置选择
|
||||
selectedInstanceId.value = '';
|
||||
Object.assign(debugInfo, defaultDebugInfo);
|
||||
}
|
||||
}
|
||||
|
||||
lastUpdateTime.value = new Date().toLocaleTimeString();
|
||||
} catch (error) {
|
||||
console.error('Failed to update debug info:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 开始自动刷新
|
||||
const startAutoRefresh = () => {
|
||||
if (intervalId) clearInterval(intervalId);
|
||||
|
||||
if (isAutoRefresh.value) {
|
||||
intervalId = setInterval(updateDebugInfo, refreshInterval.value);
|
||||
}
|
||||
};
|
||||
|
||||
// 停止自动刷新
|
||||
const stopAutoRefresh = () => {
|
||||
if (intervalId) {
|
||||
clearInterval(intervalId);
|
||||
intervalId = null;
|
||||
}
|
||||
};
|
||||
|
||||
// 手动刷新
|
||||
const manualRefresh = () => {
|
||||
updateDebugInfo();
|
||||
};
|
||||
|
||||
// 切换自动刷新
|
||||
const toggleAutoRefresh = () => {
|
||||
if (isAutoRefresh.value) {
|
||||
startAutoRefresh();
|
||||
} else {
|
||||
stopAutoRefresh();
|
||||
}
|
||||
};
|
||||
|
||||
// 更改刷新间隔
|
||||
const changeRefreshInterval = () => {
|
||||
if (isAutoRefresh.value) {
|
||||
startAutoRefresh();
|
||||
}
|
||||
};
|
||||
|
||||
// 实例选择改变
|
||||
const onInstanceChanged = () => {
|
||||
if (selectedInstanceId.value) {
|
||||
updateDebugInfo();
|
||||
} else {
|
||||
Object.assign(debugInfo, defaultDebugInfo);
|
||||
}
|
||||
};
|
||||
|
||||
// 格式化运行时间
|
||||
const formatUptime = (seconds: number): string => {
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
const minutes = Math.floor((seconds % 3600) / 60);
|
||||
const secs = Math.floor(seconds % 60);
|
||||
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
// 格式化内存大小
|
||||
const formatMemory = (bytes: number): string => {
|
||||
if (bytes < 1024) return bytes + ' B';
|
||||
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
||||
if (bytes < 1024 * 1024 * 1024) return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
||||
return (bytes / (1024 * 1024 * 1024)).toFixed(1) + ' GB';
|
||||
};
|
||||
|
||||
// 获取FPS颜色
|
||||
const getFpsColor = (fps: number): string => {
|
||||
if (fps >= 55) return 'good';
|
||||
if (fps >= 30) return 'warning';
|
||||
return 'critical';
|
||||
};
|
||||
|
||||
// 获取内存颜色
|
||||
const getMemoryColor = (percentage: number): string => {
|
||||
if (percentage < 70) return 'good';
|
||||
if (percentage < 85) return 'warning';
|
||||
return 'critical';
|
||||
};
|
||||
|
||||
// 获取ECS时间占比颜色
|
||||
const getECSTimeColor = (percentage: number): string => {
|
||||
if (!percentage) return 'good';
|
||||
if (percentage <= 10) return 'good'; // ECS占用<=10%为绿色
|
||||
if (percentage <= 30) return 'warning'; // ECS占用<=30%为黄色
|
||||
return 'critical'; // ECS占用>30%为红色
|
||||
};
|
||||
|
||||
// 获取执行时间颜色
|
||||
const getExecutionTimeColor = (time: number): string => {
|
||||
if (time < 1) return 'good'; // <1ms为绿色
|
||||
if (time < 5) return 'warning'; // <5ms为黄色
|
||||
return 'critical';
|
||||
};
|
||||
|
||||
// 组件挂载时初始化
|
||||
onMounted(async () => {
|
||||
await initializeServer();
|
||||
updateDebugInfo();
|
||||
startAutoRefresh();
|
||||
});
|
||||
|
||||
// 组件卸载时清理
|
||||
onUnmounted(() => {
|
||||
stopAutoRefresh();
|
||||
});
|
||||
|
||||
return {
|
||||
debugInfo,
|
||||
gameInstances,
|
||||
selectedInstanceId,
|
||||
isAutoRefresh,
|
||||
refreshInterval,
|
||||
lastUpdateTime,
|
||||
manualRefresh,
|
||||
toggleAutoRefresh,
|
||||
changeRefreshInterval,
|
||||
onInstanceChanged,
|
||||
formatUptime,
|
||||
formatMemory,
|
||||
getFpsColor,
|
||||
getMemoryColor,
|
||||
getECSTimeColor,
|
||||
getExecutionTimeColor
|
||||
};
|
||||
},
|
||||
template: readFileSync(join(__dirname, '../../../static/template/debug/index.html'), 'utf-8'),
|
||||
}));
|
||||
|
||||
app.config.compilerOptions.isCustomElement = (tag) => tag.startsWith('ui-');
|
||||
app.mount(this.$.app);
|
||||
panelDataMap.set(this, app);
|
||||
}
|
||||
},
|
||||
beforeClose() { },
|
||||
close() {
|
||||
const app = panelDataMap.get(this);
|
||||
if (app) {
|
||||
app.unmount();
|
||||
panelDataMap.delete(this);
|
||||
}
|
||||
|
||||
// 关闭调试服务器
|
||||
if (globalDebugServer) {
|
||||
globalDebugServer.stop();
|
||||
globalDebugServer = null;
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,370 @@
|
||||
import { readFileSync } from 'fs-extra';
|
||||
import { join } from 'path';
|
||||
import { createApp, App, defineComponent, ref, onMounted, reactive } from 'vue';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
const panelDataMap = new WeakMap<any, App>();
|
||||
|
||||
/**
|
||||
* ECS框架设置配置接口
|
||||
*/
|
||||
interface ECSSettings {
|
||||
// 代码生成设置
|
||||
codeGeneration: {
|
||||
template: 'typescript' | 'javascript';
|
||||
useStrictMode: boolean;
|
||||
generateComments: boolean;
|
||||
generateImports: boolean;
|
||||
componentSuffix: string;
|
||||
systemSuffix: string;
|
||||
indentStyle: 'spaces' | 'tabs';
|
||||
indentSize: number;
|
||||
};
|
||||
|
||||
// 性能监控设置
|
||||
performance: {
|
||||
enableMonitoring: boolean;
|
||||
warningThreshold: number; // 执行时间警告阈值(ms)
|
||||
criticalThreshold: number; // 执行时间严重阈值(ms)
|
||||
memoryWarningMB: number; // 内存警告阈值(MB)
|
||||
memoryCriticalMB: number; // 内存严重阈值(MB)
|
||||
maxRecentSamples: number; // 性能采样数量
|
||||
enableFpsMonitoring: boolean;
|
||||
targetFps: number;
|
||||
};
|
||||
|
||||
// 调试设置
|
||||
debugging: {
|
||||
enableDebugMode: boolean;
|
||||
showEntityCount: boolean;
|
||||
showSystemExecutionTime: boolean;
|
||||
enablePerformanceWarnings: boolean;
|
||||
logLevel: 'none' | 'error' | 'warn' | 'info' | 'debug';
|
||||
enableDetailedLogs: boolean;
|
||||
};
|
||||
|
||||
// 编辑器集成
|
||||
editor: {
|
||||
autoRefreshAssets: boolean;
|
||||
showWelcomePanelOnStartup: boolean;
|
||||
enableAutoUpdates: boolean;
|
||||
updateChannel: 'stable' | 'beta' | 'alpha';
|
||||
enableNotifications: boolean;
|
||||
};
|
||||
|
||||
// 项目模板设置
|
||||
template: {
|
||||
defaultEntityName: string;
|
||||
defaultComponentName: string;
|
||||
defaultSystemName: string;
|
||||
createExampleFiles: boolean;
|
||||
includeDocumentation: boolean;
|
||||
useFactoryPattern: boolean;
|
||||
};
|
||||
|
||||
// 事件系统设置
|
||||
events: {
|
||||
enableEventSystem: boolean;
|
||||
defaultEventPriority: number;
|
||||
enableAsyncEvents: boolean;
|
||||
enableEventBatching: boolean;
|
||||
batchSize: number;
|
||||
batchDelay: number; // ms
|
||||
maxEventListeners: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认设置
|
||||
*/
|
||||
const defaultSettings: ECSSettings = {
|
||||
codeGeneration: {
|
||||
template: 'typescript',
|
||||
useStrictMode: true,
|
||||
generateComments: true,
|
||||
generateImports: true,
|
||||
componentSuffix: 'Component',
|
||||
systemSuffix: 'System',
|
||||
indentStyle: 'spaces',
|
||||
indentSize: 4
|
||||
},
|
||||
performance: {
|
||||
enableMonitoring: true,
|
||||
warningThreshold: 16.67, // 60fps
|
||||
criticalThreshold: 33.33, // 30fps
|
||||
memoryWarningMB: 100,
|
||||
memoryCriticalMB: 200,
|
||||
maxRecentSamples: 60,
|
||||
enableFpsMonitoring: true,
|
||||
targetFps: 60
|
||||
},
|
||||
debugging: {
|
||||
enableDebugMode: true,
|
||||
showEntityCount: true,
|
||||
showSystemExecutionTime: true,
|
||||
enablePerformanceWarnings: true,
|
||||
logLevel: 'info',
|
||||
enableDetailedLogs: false
|
||||
},
|
||||
editor: {
|
||||
autoRefreshAssets: true,
|
||||
showWelcomePanelOnStartup: true,
|
||||
enableAutoUpdates: false,
|
||||
updateChannel: 'stable',
|
||||
enableNotifications: true
|
||||
},
|
||||
template: {
|
||||
defaultEntityName: 'GameEntity',
|
||||
defaultComponentName: 'CustomComponent',
|
||||
defaultSystemName: 'CustomSystem',
|
||||
createExampleFiles: true,
|
||||
includeDocumentation: true,
|
||||
useFactoryPattern: true
|
||||
},
|
||||
events: {
|
||||
enableEventSystem: true,
|
||||
defaultEventPriority: 0,
|
||||
enableAsyncEvents: true,
|
||||
enableEventBatching: false,
|
||||
batchSize: 10,
|
||||
batchDelay: 16,
|
||||
maxEventListeners: 100
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取设置文件路径
|
||||
*/
|
||||
function getSettingsPath(): string {
|
||||
const projectPath = Editor.Project.path;
|
||||
return path.join(projectPath, '.ecs-framework-settings.json');
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载设置
|
||||
*/
|
||||
function loadSettings(): ECSSettings {
|
||||
try {
|
||||
const settingsPath = getSettingsPath();
|
||||
if (fs.existsSync(settingsPath)) {
|
||||
const data = fs.readFileSync(settingsPath, 'utf-8');
|
||||
const loadedSettings = JSON.parse(data);
|
||||
// 合并默认设置,确保所有字段都存在
|
||||
return deepMerge(defaultSettings, loadedSettings);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Failed to load ECS settings:', error);
|
||||
}
|
||||
return JSON.parse(JSON.stringify(defaultSettings));
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存设置
|
||||
*/
|
||||
function saveSettings(settings: ECSSettings): boolean {
|
||||
try {
|
||||
const settingsPath = getSettingsPath();
|
||||
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf-8');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Failed to save ECS settings:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 深度合并对象
|
||||
*/
|
||||
function deepMerge(target: any, source: any): any {
|
||||
if (source === null || typeof source !== 'object') return source;
|
||||
if (target === null || typeof target !== 'object') return source;
|
||||
|
||||
const result = { ...target };
|
||||
|
||||
for (const key in source) {
|
||||
if (source.hasOwnProperty(key)) {
|
||||
if (typeof source[key] === 'object' && source[key] !== null && !Array.isArray(source[key])) {
|
||||
result[key] = deepMerge(target[key], source[key]);
|
||||
} else {
|
||||
result[key] = source[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置为默认设置
|
||||
*/
|
||||
function resetToDefaults(): ECSSettings {
|
||||
return JSON.parse(JSON.stringify(defaultSettings));
|
||||
}
|
||||
|
||||
module.exports = Editor.Panel.define({
|
||||
listeners: {
|
||||
show() { console.log('ECS Settings Panel Show'); },
|
||||
hide() { console.log('ECS Settings Panel Hide'); },
|
||||
},
|
||||
template: `<div id="app"></div>`,
|
||||
style: readFileSync(join(__dirname, '../../../static/style/settings/index.css'), 'utf-8'),
|
||||
$: {
|
||||
app: '#app',
|
||||
},
|
||||
ready() {
|
||||
if (this.$.app) {
|
||||
// 不要直接设置HTML内容,让Vue来处理
|
||||
const app = createApp(defineComponent({
|
||||
setup() {
|
||||
const settings = reactive(loadSettings());
|
||||
const isDirty = ref(false);
|
||||
const saving = ref(false);
|
||||
const lastSaved = ref('');
|
||||
|
||||
// 监听设置变化
|
||||
const markDirty = () => {
|
||||
isDirty.value = true;
|
||||
};
|
||||
|
||||
// 保存设置
|
||||
const saveCurrentSettings = async () => {
|
||||
saving.value = true;
|
||||
try {
|
||||
const success = saveSettings(settings);
|
||||
if (success) {
|
||||
isDirty.value = false;
|
||||
lastSaved.value = new Date().toLocaleTimeString();
|
||||
|
||||
// 通知主进程设置已更新
|
||||
Editor.Message.send('cocos-ecs-extension', 'settings-updated', settings);
|
||||
|
||||
Editor.Dialog.info('设置保存', {
|
||||
detail: '✅ ECS框架设置已成功保存!',
|
||||
});
|
||||
} else {
|
||||
Editor.Dialog.error('保存失败', {
|
||||
detail: '❌ 保存设置时发生错误,请检查文件权限。',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Save settings error:', error);
|
||||
Editor.Dialog.error('保存失败', {
|
||||
detail: `❌ 保存设置时发生错误:\n\n${error}`,
|
||||
});
|
||||
} finally {
|
||||
saving.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 重置设置
|
||||
const resetSettings = () => {
|
||||
Editor.Dialog.warn('重置设置', {
|
||||
detail: '⚠️ 您确定要重置所有设置为默认值吗?\n\n此操作无法撤销。',
|
||||
buttons: ['重置', '取消'],
|
||||
}).then((result: any) => {
|
||||
if (result.response === 0) {
|
||||
const defaults = resetToDefaults();
|
||||
Object.assign(settings, defaults);
|
||||
isDirty.value = true;
|
||||
|
||||
Editor.Dialog.info('设置重置', {
|
||||
detail: '✅ 设置已重置为默认值,请点击保存按钮确认更改。',
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 导出设置
|
||||
const exportSettings = () => {
|
||||
try {
|
||||
const dataStr = JSON.stringify(settings, null, 2);
|
||||
const blob = new Blob([dataStr], { type: 'application/json' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
// 创建下载链接
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'ecs-framework-settings.json';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
Editor.Dialog.info('导出成功', {
|
||||
detail: '✅ 设置已导出到下载文件夹。',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Export settings error:', error);
|
||||
Editor.Dialog.error('导出失败', {
|
||||
detail: `❌ 导出设置时发生错误:\n\n${error}`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 导入设置
|
||||
const importSettings = () => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = '.json';
|
||||
input.style.display = 'none';
|
||||
|
||||
input.onchange = (e: any) => {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
try {
|
||||
const importedSettings = JSON.parse(event.target?.result as string);
|
||||
const mergedSettings = deepMerge(defaultSettings, importedSettings);
|
||||
Object.assign(settings, mergedSettings);
|
||||
isDirty.value = true;
|
||||
|
||||
Editor.Dialog.info('导入成功', {
|
||||
detail: '✅ 设置已导入,请检查并保存。',
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Import settings error:', error);
|
||||
Editor.Dialog.error('导入失败', {
|
||||
detail: `❌ 导入设置文件时发生错误:\n\n${error}\n\n请确保文件格式正确。`,
|
||||
});
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
};
|
||||
|
||||
document.body.appendChild(input);
|
||||
input.click();
|
||||
document.body.removeChild(input);
|
||||
};
|
||||
|
||||
return {
|
||||
settings,
|
||||
isDirty,
|
||||
saving,
|
||||
lastSaved,
|
||||
markDirty,
|
||||
saveCurrentSettings,
|
||||
resetSettings,
|
||||
exportSettings,
|
||||
importSettings
|
||||
};
|
||||
},
|
||||
template: readFileSync(join(__dirname, '../../../static/template/settings/index.html'), 'utf-8'),
|
||||
}));
|
||||
|
||||
app.config.compilerOptions.isCustomElement = (tag) => tag.startsWith('ui-');
|
||||
app.mount(this.$.app);
|
||||
panelDataMap.set(this, app);
|
||||
}
|
||||
},
|
||||
beforeClose() { },
|
||||
close() {
|
||||
const app = panelDataMap.get(this);
|
||||
if (app) {
|
||||
app.unmount();
|
||||
panelDataMap.delete(this);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,187 @@
|
||||
import { join } from 'path';
|
||||
|
||||
// 添加编辑器内的模块搜索路径
|
||||
module.paths.push(join(Editor.App.path, 'node_modules'));
|
||||
|
||||
export function load() {
|
||||
console.log('ECS Debug Scene Script loaded');
|
||||
}
|
||||
|
||||
export function unload() {
|
||||
console.log('ECS Debug Scene Script unloaded');
|
||||
}
|
||||
|
||||
export const methods = {
|
||||
/**
|
||||
* 获取预览状态
|
||||
* @returns {object} 预览状态信息
|
||||
*/
|
||||
getPreviewState() {
|
||||
try {
|
||||
// 检查是否在游戏运行状态
|
||||
const { director } = require('cc');
|
||||
if (director && director.getScene && director.getScene()) {
|
||||
return {
|
||||
isRunning: true,
|
||||
engineLoaded: true
|
||||
};
|
||||
}
|
||||
return {
|
||||
isRunning: false,
|
||||
engineLoaded: false
|
||||
};
|
||||
} catch (error) {
|
||||
console.warn('Failed to get preview state:', error);
|
||||
return {
|
||||
isRunning: false,
|
||||
engineLoaded: false
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 检查ECS框架是否已加载
|
||||
* @returns {boolean} ECS框架加载状态
|
||||
*/
|
||||
isECSFrameworkLoaded() {
|
||||
try {
|
||||
// 检查是否有ECS框架的全局对象
|
||||
return typeof window !== 'undefined' && !!(window as any).ECSFramework;
|
||||
} catch (error) {
|
||||
console.warn('Failed to check ECS framework status:', error);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取场景基本信息
|
||||
* @returns {object} 场景信息
|
||||
*/
|
||||
getSceneBasicInfo() {
|
||||
try {
|
||||
const { director } = require('cc');
|
||||
if (director && director.getScene) {
|
||||
const scene = director.getScene();
|
||||
return {
|
||||
sceneName: scene ? (scene.name || '当前场景') : '未知场景',
|
||||
nodeCount: scene ? this.countNodes(scene) : 0,
|
||||
isValid: scene ? scene.isValid : false
|
||||
};
|
||||
}
|
||||
return {
|
||||
sceneName: '未知场景',
|
||||
nodeCount: 0,
|
||||
isValid: false
|
||||
};
|
||||
} catch (error) {
|
||||
console.warn('Failed to get scene basic info:', error);
|
||||
return {
|
||||
sceneName: '获取失败',
|
||||
nodeCount: 0,
|
||||
isValid: false
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取ECS框架的调试信息
|
||||
* @returns {object|null} ECS调试数据或null(如果框架未加载)
|
||||
*/
|
||||
getECSDebugInfo() {
|
||||
try {
|
||||
// 检查是否有ECS框架的全局对象
|
||||
if (typeof window !== 'undefined' && (window as any).ECSFramework) {
|
||||
const ecs = (window as any).ECSFramework;
|
||||
|
||||
// 获取当前场景和实体管理器
|
||||
if (ecs.Core && ecs.Core.getCurrentScene) {
|
||||
const scene = ecs.Core.getCurrentScene();
|
||||
if (scene && scene.entityManager) {
|
||||
const entityManager = scene.entityManager;
|
||||
const systemManager = scene.systemManager;
|
||||
|
||||
// 收集调试信息
|
||||
const debugInfo = {
|
||||
timestamp: new Date().toISOString(),
|
||||
frameworkLoaded: true,
|
||||
currentScene: scene.name || '当前场景',
|
||||
totalEntities: entityManager.entityCount || 0,
|
||||
activeEntities: entityManager.activeEntityCount || 0,
|
||||
pendingAdd: 0, // 需要具体API
|
||||
pendingRemove: 0, // 需要具体API
|
||||
totalSystems: systemManager ? systemManager.getSystemCount() : 0,
|
||||
systemsInfo: [],
|
||||
frameTime: 0, // 需要性能监控
|
||||
memoryUsage: 0, // 需要内存监控
|
||||
componentTypes: 0, // 需要组件统计
|
||||
componentInstances: 0 // 需要组件实例统计
|
||||
};
|
||||
|
||||
// 获取系统信息
|
||||
if (systemManager && systemManager.getSystems) {
|
||||
const systems = systemManager.getSystems();
|
||||
debugInfo.systemsInfo = systems.map((system: any, index: number) => ({
|
||||
name: system.constructor.name || `System${index}`,
|
||||
entityCount: system.entities ? system.entities.length : 0,
|
||||
executionTime: system.lastExecutionTime || 0,
|
||||
updateOrder: index + 1
|
||||
}));
|
||||
}
|
||||
|
||||
return debugInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否直接导入了ECS模块
|
||||
try {
|
||||
// 这里需要根据实际的ECS框架导入方式调整
|
||||
const { Core } = require('ecs-framework');
|
||||
if (Core) {
|
||||
const scene = Core.getCurrentScene();
|
||||
if (scene) {
|
||||
return {
|
||||
timestamp: new Date().toISOString(),
|
||||
frameworkLoaded: true,
|
||||
currentScene: scene.name || '当前场景',
|
||||
totalEntities: scene.entityManager?.entityCount || 0,
|
||||
activeEntities: scene.entityManager?.activeEntityCount || 0,
|
||||
pendingAdd: 0,
|
||||
pendingRemove: 0,
|
||||
totalSystems: scene.systemManager?.getSystemCount() || 0,
|
||||
systemsInfo: [],
|
||||
frameTime: 0,
|
||||
memoryUsage: 0,
|
||||
componentTypes: 0,
|
||||
componentInstances: 0
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// ECS框架未导入或未初始化
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.warn('Failed to get ECS debug info:', error);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 递归计算节点数量
|
||||
* @param {any} node
|
||||
* @returns {number}
|
||||
*/
|
||||
countNodes(node: any): number {
|
||||
if (!node) return 0;
|
||||
|
||||
let count = 1; // 当前节点
|
||||
if (node.children) {
|
||||
for (const child of node.children) {
|
||||
count += this.countNodes(child);
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,353 @@
|
||||
/* ECS Framework 设置面板样式 */
|
||||
.settings-container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--color-normal-fill);
|
||||
font-family: var(--font-family);
|
||||
}
|
||||
|
||||
/* 头部样式 */
|
||||
.settings-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
padding: 16px 20px;
|
||||
background: var(--color-info-fill);
|
||||
border-bottom: 1px solid var(--color-normal-border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.header-title h1 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: var(--color-default-text);
|
||||
}
|
||||
|
||||
.header-title p {
|
||||
margin: 4px 0 0 0;
|
||||
font-size: 12px;
|
||||
color: var(--color-focus-text);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.header-actions ui-button {
|
||||
min-width: 80px;
|
||||
height: 28px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* 保存按钮状态 */
|
||||
.save-button.dirty {
|
||||
background: var(--color-warn-fill) !important;
|
||||
border-color: var(--color-warn-border) !important;
|
||||
color: var(--color-warn-text) !important;
|
||||
}
|
||||
|
||||
.save-button.saving {
|
||||
background: var(--color-info-fill) !important;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* 图标动画 */
|
||||
.spin {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* 状态栏 */
|
||||
.status-bar {
|
||||
padding: 8px 20px;
|
||||
background: var(--color-success-fill);
|
||||
border-bottom: 1px solid var(--color-normal-border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-size: 12px;
|
||||
color: var(--color-success-text);
|
||||
}
|
||||
|
||||
/* 设置内容区域 */
|
||||
.settings-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 0 20px 20px 20px;
|
||||
max-height: calc(100vh - 140px); /* 确保有固定的滚动区域 */
|
||||
scroll-behavior: smooth; /* 平滑滚动 */
|
||||
}
|
||||
|
||||
/* 设置分组 */
|
||||
.settings-section {
|
||||
margin-bottom: 32px;
|
||||
background: var(--color-default-fill);
|
||||
border: 1px solid var(--color-normal-border);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
padding: 16px 20px;
|
||||
background: var(--color-focus-fill);
|
||||
border-bottom: 1px solid var(--color-normal-border);
|
||||
}
|
||||
|
||||
.section-header h2 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--color-default-text);
|
||||
}
|
||||
|
||||
.section-header p {
|
||||
margin: 4px 0 0 0;
|
||||
font-size: 12px;
|
||||
color: var(--color-focus-text);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* 设置网格 */
|
||||
.settings-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 16px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* 设置项 */
|
||||
.setting-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.setting-item label {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: var(--color-default-text);
|
||||
}
|
||||
|
||||
.setting-item ui-input,
|
||||
.setting-item ui-select,
|
||||
.setting-item ui-num-input {
|
||||
width: 100%;
|
||||
height: 26px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
/* 复选框项样式 */
|
||||
.checkbox-item {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.checkbox-item ui-checkbox {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* 全宽设置项 */
|
||||
.setting-item.full-width {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
/* 底部区域 */
|
||||
.settings-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 20px;
|
||||
background: var(--color-focus-fill);
|
||||
border-top: 1px solid var(--color-normal-border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.footer-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 11px;
|
||||
color: var(--color-focus-text);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.footer-info ui-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
|
||||
.footer-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.dirty-indicator {
|
||||
font-size: 11px;
|
||||
color: var(--color-warn-text);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.settings-header {
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.settings-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.settings-footer {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
align-items: stretch;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
/* 深色主题支持 */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.settings-container {
|
||||
background: #2c2c2c;
|
||||
}
|
||||
|
||||
.settings-section {
|
||||
background: #3c3c3c;
|
||||
border-color: #555;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
background: #404040;
|
||||
border-color: #555;
|
||||
}
|
||||
|
||||
.settings-header {
|
||||
background: #333;
|
||||
border-color: #555;
|
||||
}
|
||||
|
||||
.status-bar {
|
||||
background: #2d5a2d;
|
||||
}
|
||||
|
||||
.settings-footer {
|
||||
background: #333;
|
||||
border-color: #555;
|
||||
}
|
||||
}
|
||||
|
||||
/* 优化滚动条样式 */
|
||||
.settings-content::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
.settings-content::-webkit-scrollbar-track {
|
||||
background: var(--color-normal-fill);
|
||||
border-radius: 5px;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
.settings-content::-webkit-scrollbar-thumb {
|
||||
background: var(--color-focus-border);
|
||||
border-radius: 5px;
|
||||
transition: background-color 0.2s ease;
|
||||
min-height: 30px;
|
||||
}
|
||||
|
||||
.settings-content::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--color-warn-border);
|
||||
}
|
||||
|
||||
.settings-content::-webkit-scrollbar-thumb:active {
|
||||
background: var(--color-danger-border);
|
||||
}
|
||||
|
||||
/* 表单元素统一样式 */
|
||||
.setting-item ui-input,
|
||||
.setting-item ui-select,
|
||||
.setting-item ui-num-input {
|
||||
border: 1px solid var(--color-normal-border);
|
||||
border-radius: 4px;
|
||||
padding: 4px 8px;
|
||||
background: var(--color-default-fill);
|
||||
color: var(--color-default-text);
|
||||
transition: border-color 0.2s ease;
|
||||
}
|
||||
|
||||
.setting-item ui-input:focus,
|
||||
.setting-item ui-select:focus,
|
||||
.setting-item ui-num-input:focus {
|
||||
border-color: var(--color-focus-border);
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
/* 复选框样式 */
|
||||
.setting-item ui-checkbox {
|
||||
font-size: 12px;
|
||||
color: var(--color-default-text);
|
||||
}
|
||||
|
||||
/* 按钮样式覆盖 */
|
||||
.header-actions .export-button {
|
||||
background: var(--color-info-fill);
|
||||
border-color: var(--color-info-border);
|
||||
color: var(--color-info-text);
|
||||
}
|
||||
|
||||
.header-actions .import-button {
|
||||
background: var(--color-warn-fill);
|
||||
border-color: var(--color-warn-border);
|
||||
color: var(--color-warn-text);
|
||||
}
|
||||
|
||||
.header-actions .reset-button {
|
||||
background: var(--color-danger-fill);
|
||||
border-color: var(--color-danger-border);
|
||||
color: var(--color-danger-text);
|
||||
}
|
||||
|
||||
/* 工具提示样式 */
|
||||
.setting-item[title] {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 3px solid var(--color-normal-border);
|
||||
border-top: 3px solid var(--color-focus-border);
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
@@ -0,0 +1,294 @@
|
||||
<div class="debug-panel">
|
||||
<!-- 顶部控制栏 -->
|
||||
<div class="debug-toolbar">
|
||||
<div class="toolbar-section">
|
||||
<label>游戏实例:</label>
|
||||
<select v-model="selectedInstanceId" @change="onInstanceChanged" class="instance-selector">
|
||||
<option value="">-- 选择实例 --</option>
|
||||
<option v-for="instance in gameInstances" :key="instance.id" :value="instance.id">
|
||||
{{ instance.name }} ({{ instance.isActive ? '活跃' : '离线' }})
|
||||
</option>
|
||||
</select>
|
||||
<span class="instance-count">{{ gameInstances.length }} 个实例</span>
|
||||
</div>
|
||||
|
||||
<div class="toolbar-section">
|
||||
<button @click="manualRefresh" class="btn-refresh" :disabled="!selectedInstanceId">
|
||||
<ui-icon value="refresh"></ui-icon>
|
||||
刷新
|
||||
</button>
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" v-model="isAutoRefresh" @change="toggleAutoRefresh">
|
||||
自动刷新
|
||||
</label>
|
||||
<select v-model="refreshInterval" @change="changeRefreshInterval" :disabled="!isAutoRefresh">
|
||||
<option :value="100">0.1秒</option>
|
||||
<option :value="250">0.25秒</option>
|
||||
<option :value="500">0.5秒</option>
|
||||
<option :value="1000">1秒</option>
|
||||
<option :value="2000">2秒</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="toolbar-section">
|
||||
<span class="last-update">{{ lastUpdateTime }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 连接状态和基础信息 -->
|
||||
<div class="debug-section">
|
||||
<div class="section-header">
|
||||
<h2>📊 实例状态</h2>
|
||||
</div>
|
||||
<div class="status-grid" v-if="selectedInstanceId && debugInfo.instanceId">
|
||||
<div class="status-item">
|
||||
<span class="label">实例名称:</span>
|
||||
<span class="value">{{ debugInfo.instanceName }}</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="label">运行状态:</span>
|
||||
<span class="value" :class="{ online: debugInfo.isRunning, offline: !debugInfo.isRunning }">
|
||||
{{ debugInfo.isRunning ? '运行中' : '已停止' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="label">当前场景:</span>
|
||||
<span class="value">{{ debugInfo.currentScene }}</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="label">运行时间:</span>
|
||||
<span class="value">{{ formatUptime(debugInfo.uptime) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="no-instance" v-else>
|
||||
<ui-icon value="info" class="info-icon"></ui-icon>
|
||||
<span>请选择一个游戏实例查看详细信息</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 性能监控 -->
|
||||
<div class="debug-section" v-if="debugInfo.instanceId">
|
||||
<div class="section-header">
|
||||
<h2>⚡ 性能监控</h2>
|
||||
</div>
|
||||
<div class="performance-grid">
|
||||
<div class="perf-card">
|
||||
<div class="perf-title">帧率</div>
|
||||
<div class="perf-value" :class="getFpsColor(debugInfo.performance.fps)">
|
||||
{{ debugInfo.performance.fps }} FPS
|
||||
</div>
|
||||
<div class="perf-detail">
|
||||
引擎帧时间: {{ debugInfo.performance.engineFrameTime?.toFixed(2) || '0.00' }}ms
|
||||
</div>
|
||||
</div>
|
||||
<div class="perf-card">
|
||||
<div class="perf-title">ECS执行时间</div>
|
||||
<div class="perf-value" :class="getECSTimeColor(debugInfo.performance.ecsPercentage)">
|
||||
{{ debugInfo.performance.frameTime.toFixed(3) }}ms
|
||||
</div>
|
||||
<div class="perf-detail">
|
||||
占总帧时间: {{ debugInfo.performance.ecsPercentage?.toFixed(1) || '0.0' }}%
|
||||
</div>
|
||||
</div>
|
||||
<div class="perf-card">
|
||||
<div class="perf-title">总内存</div>
|
||||
<div class="perf-value" :class="getMemoryColor(debugInfo.memory.usedMemory / debugInfo.memory.totalMemory * 100)">
|
||||
{{ formatMemory(debugInfo.memory.usedMemory) }}
|
||||
</div>
|
||||
<div class="perf-detail">
|
||||
/ {{ formatMemory(debugInfo.memory.totalMemory) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="perf-card">
|
||||
<div class="perf-title">GC回收</div>
|
||||
<div class="perf-value">{{ debugInfo.memory.gcCollections }}</div>
|
||||
<div class="perf-detail">次数</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 内存分析 -->
|
||||
<div class="debug-section" v-if="debugInfo.instanceId">
|
||||
<div class="section-header">
|
||||
<h2>💾 内存分析</h2>
|
||||
</div>
|
||||
<div class="memory-breakdown">
|
||||
<div class="memory-item">
|
||||
<span class="memory-label">实体内存:</span>
|
||||
<span class="memory-value">{{ formatMemory(debugInfo.memory.entityMemory) }}</span>
|
||||
<div class="memory-bar">
|
||||
<div class="memory-fill" :style="{ width: (debugInfo.memory.entityMemory / debugInfo.memory.usedMemory * 100) + '%' }"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="memory-item">
|
||||
<span class="memory-label">组件内存:</span>
|
||||
<span class="memory-value">{{ formatMemory(debugInfo.memory.componentMemory) }}</span>
|
||||
<div class="memory-bar">
|
||||
<div class="memory-fill" :style="{ width: (debugInfo.memory.componentMemory / debugInfo.memory.usedMemory * 100) + '%' }"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="memory-item">
|
||||
<span class="memory-label">系统内存:</span>
|
||||
<span class="memory-value">{{ formatMemory(debugInfo.memory.systemMemory) }}</span>
|
||||
<div class="memory-bar">
|
||||
<div class="memory-fill" :style="{ width: (debugInfo.memory.systemMemory / debugInfo.memory.usedMemory * 100) + '%' }"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="memory-item">
|
||||
<span class="memory-label">对象池内存:</span>
|
||||
<span class="memory-value">{{ formatMemory(debugInfo.memory.pooledMemory) }}</span>
|
||||
<div class="memory-bar">
|
||||
<div class="memory-fill" :style="{ width: (debugInfo.memory.pooledMemory / debugInfo.memory.usedMemory * 100) + '%' }"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 实体统计 -->
|
||||
<div class="debug-section" v-if="debugInfo.instanceId">
|
||||
<div class="section-header">
|
||||
<h2>👥 实体统计</h2>
|
||||
</div>
|
||||
<div class="entity-stats">
|
||||
<div class="stat-card">
|
||||
<div class="stat-number">{{ debugInfo.entities.total }}</div>
|
||||
<div class="stat-label">总实体数</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number">{{ debugInfo.entities.active }}</div>
|
||||
<div class="stat-label">活跃实体</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number">{{ debugInfo.entities.inactive }}</div>
|
||||
<div class="stat-label">非活跃实体</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-number">{{ debugInfo.entities.pendingAdd }}</div>
|
||||
<div class="stat-label">待添加</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Archetype分布 -->
|
||||
<div class="archetype-section" v-if="debugInfo.entities.entitiesPerArchetype.length > 0">
|
||||
<h3>Archetype 分布</h3>
|
||||
<div class="archetype-list">
|
||||
<div v-for="archetype in debugInfo.entities.entitiesPerArchetype" :key="archetype.signature" class="archetype-item">
|
||||
<div class="archetype-signature">{{ archetype.signature }}</div>
|
||||
<div class="archetype-stats">
|
||||
<span class="entity-count">{{ archetype.count }} 实体</span>
|
||||
<span class="memory-usage">{{ formatMemory(archetype.memory) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 组件分析 -->
|
||||
<div class="debug-section" v-if="debugInfo.instanceId">
|
||||
<div class="section-header">
|
||||
<h2>🧩 组件分析</h2>
|
||||
</div>
|
||||
<div class="component-overview">
|
||||
<div class="overview-item">
|
||||
<span class="label">组件类型:</span>
|
||||
<span class="value">{{ debugInfo.components.totalTypes }}</span>
|
||||
</div>
|
||||
<div class="overview-item">
|
||||
<span class="label">组件实例:</span>
|
||||
<span class="value">{{ debugInfo.components.totalInstances }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="component-table" v-if="debugInfo.components.componentStats.length > 0">
|
||||
<div class="table-header">
|
||||
<div class="col-name">组件类型</div>
|
||||
<div class="col-count">实例数</div>
|
||||
<div class="col-memory">内存占用</div>
|
||||
<div class="col-pool">对象池</div>
|
||||
<div class="col-efficiency">利用率</div>
|
||||
</div>
|
||||
<div v-for="comp in debugInfo.components.componentStats" :key="comp.typeName" class="table-row">
|
||||
<div class="col-name">{{ comp.typeName }}</div>
|
||||
<div class="col-count">{{ comp.instanceCount }}</div>
|
||||
<div class="col-memory">{{ formatMemory(comp.totalMemory) }}</div>
|
||||
<div class="col-pool">{{ comp.poolSize }}</div>
|
||||
<div class="col-efficiency">
|
||||
<div class="efficiency-bar">
|
||||
<div class="efficiency-fill" :style="{ width: comp.poolUtilization + '%' }"></div>
|
||||
</div>
|
||||
{{ comp.poolUtilization.toFixed(1) }}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 系统性能 -->
|
||||
<div class="debug-section" v-if="debugInfo.instanceId">
|
||||
<div class="section-header">
|
||||
<h2>⚙️ 系统性能</h2>
|
||||
</div>
|
||||
<div class="systems-table" v-if="debugInfo.systems.systemStats.length > 0">
|
||||
<div class="table-header">
|
||||
<div class="col-name">系统名称</div>
|
||||
<div class="col-entities">实体数</div>
|
||||
<div class="col-time">执行时间</div>
|
||||
<div class="col-percentage">ECS占比</div>
|
||||
<div class="col-order">优先级</div>
|
||||
<div class="col-status">状态</div>
|
||||
</div>
|
||||
<div v-for="system in debugInfo.systems.systemStats" :key="system.name" class="table-row">
|
||||
<div class="col-name">{{ system.name }}</div>
|
||||
<div class="col-entities">{{ system.entityCount }}</div>
|
||||
<div class="col-time">
|
||||
<span :class="getExecutionTimeColor(system.averageExecutionTime)">
|
||||
{{ system.averageExecutionTime.toFixed(3) }}ms
|
||||
</span>
|
||||
<div class="time-range">
|
||||
({{ system.minExecutionTime.toFixed(3) }}-{{ system.maxExecutionTime.toFixed(3) }}ms)
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-percentage">
|
||||
<div class="percentage-bar">
|
||||
<div class="percentage-fill" :style="{ width: (system.percentage || 0) + '%' }"></div>
|
||||
</div>
|
||||
{{ (system.percentage || 0).toFixed(1) }}%
|
||||
</div>
|
||||
<div class="col-order">{{ system.updateOrder }}</div>
|
||||
<div class="col-status">
|
||||
<span :class="{ enabled: system.enabled, disabled: !system.enabled }">
|
||||
{{ system.enabled ? '启用' : '禁用' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 服务器状态 -->
|
||||
<div class="debug-section" v-if="gameInstances.length === 0">
|
||||
<div class="section-header">
|
||||
<h2>🔌 调试服务器</h2>
|
||||
</div>
|
||||
<div class="server-status">
|
||||
<div class="status-message">
|
||||
<ui-icon value="wifi" class="server-icon"></ui-icon>
|
||||
<div class="message-content">
|
||||
<h3>等待游戏实例连接...</h3>
|
||||
<p>调试服务器正在 <strong>ws://localhost:8080</strong> 监听连接</p>
|
||||
<p>请确保游戏中的ECS框架已启用调试模式</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="connection-help">
|
||||
<h4>连接说明:</h4>
|
||||
<ul>
|
||||
<li>在ECSManager组件中启用调试模式</li>
|
||||
<li>运行游戏,框架会自动连接到调试服务器</li>
|
||||
<li>支持多个游戏实例同时连接</li>
|
||||
<li>可以在上方选择不同的实例进行调试</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,322 @@
|
||||
<div id="app">
|
||||
<div class="settings-container">
|
||||
<!-- 头部 -->
|
||||
<div class="settings-header">
|
||||
<div class="header-title">
|
||||
<h1>🔧 ECS Framework 设置</h1>
|
||||
<p>配置ECS框架的行为和性能选项</p>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<ui-button class="save-button" :class="{ dirty: isDirty, saving: saving }"
|
||||
@click="saveCurrentSettings" :disabled="saving">
|
||||
<ui-icon value="confirm" v-if="!saving"></ui-icon>
|
||||
<ui-icon value="loading" class="spin" v-if="saving"></ui-icon>
|
||||
{{ saving ? '保存中...' : (isDirty ? '保存 *' : '保存') }}
|
||||
</ui-button>
|
||||
<ui-button class="export-button" @click="exportSettings">
|
||||
<ui-icon value="download"></ui-icon>
|
||||
导出
|
||||
</ui-button>
|
||||
<ui-button class="import-button" @click="importSettings">
|
||||
<ui-icon value="upload"></ui-icon>
|
||||
导入
|
||||
</ui-button>
|
||||
<ui-button class="reset-button" @click="resetSettings">
|
||||
<ui-icon value="reset"></ui-icon>
|
||||
重置
|
||||
</ui-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 状态栏 -->
|
||||
<div class="status-bar" v-if="lastSaved">
|
||||
<span class="status-text">最后保存: {{ lastSaved }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 设置内容 -->
|
||||
<div class="settings-content">
|
||||
<!-- 代码生成设置 -->
|
||||
<div class="settings-section">
|
||||
<div class="section-header">
|
||||
<h2>📝 代码生成设置</h2>
|
||||
<p>配置自动生成代码的格式和选项</p>
|
||||
</div>
|
||||
<div class="settings-grid">
|
||||
<div class="setting-item">
|
||||
<label>模板语言</label>
|
||||
<ui-select v-model="settings.codeGeneration.template" @change="markDirty">
|
||||
<option value="typescript">TypeScript</option>
|
||||
<option value="javascript">JavaScript</option>
|
||||
</ui-select>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<label>组件后缀</label>
|
||||
<ui-input v-model="settings.codeGeneration.componentSuffix"
|
||||
@change="markDirty" placeholder="Component"></ui-input>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<label>系统后缀</label>
|
||||
<ui-input v-model="settings.codeGeneration.systemSuffix"
|
||||
@change="markDirty" placeholder="System"></ui-input>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<label>缩进风格</label>
|
||||
<ui-select v-model="settings.codeGeneration.indentStyle" @change="markDirty">
|
||||
<option value="spaces">空格</option>
|
||||
<option value="tabs">制表符</option>
|
||||
</ui-select>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<label>缩进大小</label>
|
||||
<ui-num-input v-model="settings.codeGeneration.indentSize"
|
||||
@change="markDirty" :min="1" :max="8"></ui-num-input>
|
||||
</div>
|
||||
<div class="setting-item checkbox-item">
|
||||
<ui-checkbox v-model="settings.codeGeneration.useStrictMode" @change="markDirty">
|
||||
启用严格模式
|
||||
</ui-checkbox>
|
||||
</div>
|
||||
<div class="setting-item checkbox-item">
|
||||
<ui-checkbox v-model="settings.codeGeneration.generateComments" @change="markDirty">
|
||||
生成注释
|
||||
</ui-checkbox>
|
||||
</div>
|
||||
<div class="setting-item checkbox-item">
|
||||
<ui-checkbox v-model="settings.codeGeneration.generateImports" @change="markDirty">
|
||||
自动添加导入语句
|
||||
</ui-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 性能监控设置 -->
|
||||
<div class="settings-section">
|
||||
<div class="section-header">
|
||||
<h2>⚡ 性能监控设置</h2>
|
||||
<p>配置性能监控和优化建议</p>
|
||||
</div>
|
||||
<div class="settings-grid">
|
||||
<div class="setting-item checkbox-item">
|
||||
<ui-checkbox v-model="settings.performance.enableMonitoring" @change="markDirty">
|
||||
启用性能监控
|
||||
</ui-checkbox>
|
||||
</div>
|
||||
<div class="setting-item checkbox-item">
|
||||
<ui-checkbox v-model="settings.performance.enableFpsMonitoring" @change="markDirty">
|
||||
启用FPS监控
|
||||
</ui-checkbox>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<label>目标FPS</label>
|
||||
<ui-num-input v-model="settings.performance.targetFps"
|
||||
@change="markDirty" :min="30" :max="120"></ui-num-input>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<label>执行时间警告阈值 (ms)</label>
|
||||
<ui-num-input v-model="settings.performance.warningThreshold"
|
||||
@change="markDirty" :min="1" :max="100" :step="0.1"></ui-num-input>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<label>执行时间严重阈值 (ms)</label>
|
||||
<ui-num-input v-model="settings.performance.criticalThreshold"
|
||||
@change="markDirty" :min="1" :max="200" :step="0.1"></ui-num-input>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<label>内存警告阈值 (MB)</label>
|
||||
<ui-num-input v-model="settings.performance.memoryWarningMB"
|
||||
@change="markDirty" :min="10" :max="1000"></ui-num-input>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<label>内存严重阈值 (MB)</label>
|
||||
<ui-num-input v-model="settings.performance.memoryCriticalMB"
|
||||
@change="markDirty" :min="50" :max="2000"></ui-num-input>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<label>性能采样数量</label>
|
||||
<ui-num-input v-model="settings.performance.maxRecentSamples"
|
||||
@change="markDirty" :min="10" :max="300"></ui-num-input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 调试设置 -->
|
||||
<div class="settings-section">
|
||||
<div class="section-header">
|
||||
<h2>🐛 调试设置</h2>
|
||||
<p>配置调试模式和日志选项</p>
|
||||
</div>
|
||||
<div class="settings-grid">
|
||||
<div class="setting-item checkbox-item">
|
||||
<ui-checkbox v-model="settings.debugging.enableDebugMode" @change="markDirty">
|
||||
启用调试模式
|
||||
</ui-checkbox>
|
||||
</div>
|
||||
<div class="setting-item checkbox-item">
|
||||
<ui-checkbox v-model="settings.debugging.showEntityCount" @change="markDirty">
|
||||
显示实体数量
|
||||
</ui-checkbox>
|
||||
</div>
|
||||
<div class="setting-item checkbox-item">
|
||||
<ui-checkbox v-model="settings.debugging.showSystemExecutionTime" @change="markDirty">
|
||||
显示系统执行时间
|
||||
</ui-checkbox>
|
||||
</div>
|
||||
<div class="setting-item checkbox-item">
|
||||
<ui-checkbox v-model="settings.debugging.enablePerformanceWarnings" @change="markDirty">
|
||||
启用性能警告
|
||||
</ui-checkbox>
|
||||
</div>
|
||||
<div class="setting-item checkbox-item">
|
||||
<ui-checkbox v-model="settings.debugging.enableDetailedLogs" @change="markDirty">
|
||||
启用详细日志
|
||||
</ui-checkbox>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<label>日志级别</label>
|
||||
<ui-select v-model="settings.debugging.logLevel" @change="markDirty">
|
||||
<option value="none">无</option>
|
||||
<option value="error">错误</option>
|
||||
<option value="warn">警告</option>
|
||||
<option value="info">信息</option>
|
||||
<option value="debug">调试</option>
|
||||
</ui-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 编辑器集成设置 -->
|
||||
<div class="settings-section">
|
||||
<div class="section-header">
|
||||
<h2>⚙️ 编辑器集成</h2>
|
||||
<p>配置与Cocos Creator的集成选项</p>
|
||||
</div>
|
||||
<div class="settings-grid">
|
||||
<div class="setting-item checkbox-item">
|
||||
<ui-checkbox v-model="settings.editor.autoRefreshAssets" @change="markDirty">
|
||||
自动刷新资源
|
||||
</ui-checkbox>
|
||||
</div>
|
||||
<div class="setting-item checkbox-item">
|
||||
<ui-checkbox v-model="settings.editor.showWelcomePanelOnStartup" @change="markDirty">
|
||||
启动时显示欢迎面板
|
||||
</ui-checkbox>
|
||||
</div>
|
||||
<div class="setting-item checkbox-item">
|
||||
<ui-checkbox v-model="settings.editor.enableAutoUpdates" @change="markDirty">
|
||||
启用自动更新
|
||||
</ui-checkbox>
|
||||
</div>
|
||||
<div class="setting-item checkbox-item">
|
||||
<ui-checkbox v-model="settings.editor.enableNotifications" @change="markDirty">
|
||||
启用通知
|
||||
</ui-checkbox>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<label>更新频道</label>
|
||||
<ui-select v-model="settings.editor.updateChannel" @change="markDirty">
|
||||
<option value="stable">稳定版</option>
|
||||
<option value="beta">测试版</option>
|
||||
<option value="alpha">开发版</option>
|
||||
</ui-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 项目模板设置 -->
|
||||
<div class="settings-section">
|
||||
<div class="section-header">
|
||||
<h2>📁 项目模板设置</h2>
|
||||
<p>配置生成项目模板的默认选项</p>
|
||||
</div>
|
||||
<div class="settings-grid">
|
||||
<div class="setting-item">
|
||||
<label>默认实体名称</label>
|
||||
<ui-input v-model="settings.template.defaultEntityName"
|
||||
@change="markDirty" placeholder="GameEntity"></ui-input>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<label>默认组件名称</label>
|
||||
<ui-input v-model="settings.template.defaultComponentName"
|
||||
@change="markDirty" placeholder="CustomComponent"></ui-input>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<label>默认系统名称</label>
|
||||
<ui-input v-model="settings.template.defaultSystemName"
|
||||
@change="markDirty" placeholder="CustomSystem"></ui-input>
|
||||
</div>
|
||||
<div class="setting-item checkbox-item">
|
||||
<ui-checkbox v-model="settings.template.createExampleFiles" @change="markDirty">
|
||||
创建示例文件
|
||||
</ui-checkbox>
|
||||
</div>
|
||||
<div class="setting-item checkbox-item">
|
||||
<ui-checkbox v-model="settings.template.includeDocumentation" @change="markDirty">
|
||||
包含文档
|
||||
</ui-checkbox>
|
||||
</div>
|
||||
<div class="setting-item checkbox-item">
|
||||
<ui-checkbox v-model="settings.template.useFactoryPattern" @change="markDirty">
|
||||
使用工厂模式
|
||||
</ui-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 事件系统设置 -->
|
||||
<div class="settings-section">
|
||||
<div class="section-header">
|
||||
<h2>📡 事件系统设置</h2>
|
||||
<p>配置事件系统的行为和性能</p>
|
||||
</div>
|
||||
<div class="settings-grid">
|
||||
<div class="setting-item checkbox-item">
|
||||
<ui-checkbox v-model="settings.events.enableEventSystem" @change="markDirty">
|
||||
启用事件系统
|
||||
</ui-checkbox>
|
||||
</div>
|
||||
<div class="setting-item checkbox-item">
|
||||
<ui-checkbox v-model="settings.events.enableAsyncEvents" @change="markDirty">
|
||||
启用异步事件
|
||||
</ui-checkbox>
|
||||
</div>
|
||||
<div class="setting-item checkbox-item">
|
||||
<ui-checkbox v-model="settings.events.enableEventBatching" @change="markDirty">
|
||||
启用事件批处理
|
||||
</ui-checkbox>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<label>默认事件优先级</label>
|
||||
<ui-num-input v-model="settings.events.defaultEventPriority"
|
||||
@change="markDirty" :min="-100" :max="100"></ui-num-input>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<label>批处理大小</label>
|
||||
<ui-num-input v-model="settings.events.batchSize"
|
||||
@change="markDirty" :min="1" :max="100"></ui-num-input>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<label>批处理延迟 (ms)</label>
|
||||
<ui-num-input v-model="settings.events.batchDelay"
|
||||
@change="markDirty" :min="1" :max="1000"></ui-num-input>
|
||||
</div>
|
||||
<div class="setting-item">
|
||||
<label>最大事件监听器数</label>
|
||||
<ui-num-input v-model="settings.events.maxEventListeners"
|
||||
@change="markDirty" :min="10" :max="1000"></ui-num-input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部提示 -->
|
||||
<div class="settings-footer">
|
||||
<div class="footer-info">
|
||||
<ui-icon value="info"></ui-icon>
|
||||
<span>设置将保存到项目目录的 .ecs-framework-settings.json 文件中</span>
|
||||
</div>
|
||||
<div class="footer-actions">
|
||||
<span class="dirty-indicator" v-if="isDirty">● 有未保存的更改</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,132 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// 模拟项目路径(实际会是真实的项目路径)
|
||||
const projectPath = process.cwd();
|
||||
const settingsPath = path.join(projectPath, '.ecs-framework-settings.json');
|
||||
|
||||
console.log('🧪 测试ECS框架设置功能...');
|
||||
console.log('设置文件路径:', settingsPath);
|
||||
|
||||
// 默认设置
|
||||
const testSettings = {
|
||||
codeGeneration: {
|
||||
template: 'typescript',
|
||||
useStrictMode: true,
|
||||
generateComments: true,
|
||||
generateImports: true,
|
||||
componentSuffix: 'Component',
|
||||
systemSuffix: 'System',
|
||||
indentStyle: 'spaces',
|
||||
indentSize: 4
|
||||
},
|
||||
performance: {
|
||||
enableMonitoring: true,
|
||||
warningThreshold: 16.67,
|
||||
criticalThreshold: 33.33,
|
||||
memoryWarningMB: 100,
|
||||
memoryCriticalMB: 200,
|
||||
maxRecentSamples: 60,
|
||||
enableFpsMonitoring: true,
|
||||
targetFps: 60
|
||||
},
|
||||
debugging: {
|
||||
enableDebugMode: true,
|
||||
showEntityCount: true,
|
||||
showSystemExecutionTime: true,
|
||||
enablePerformanceWarnings: true,
|
||||
logLevel: 'info',
|
||||
enableDetailedLogs: false
|
||||
},
|
||||
editor: {
|
||||
autoRefreshAssets: true,
|
||||
showWelcomePanelOnStartup: true,
|
||||
enableAutoUpdates: false,
|
||||
updateChannel: 'stable',
|
||||
enableNotifications: true
|
||||
},
|
||||
template: {
|
||||
defaultEntityName: 'TestEntity', // 修改这个值来测试
|
||||
defaultComponentName: 'TestComponent',
|
||||
defaultSystemName: 'TestSystem',
|
||||
createExampleFiles: true,
|
||||
includeDocumentation: true,
|
||||
useFactoryPattern: true
|
||||
},
|
||||
events: {
|
||||
enableEventSystem: true,
|
||||
defaultEventPriority: 0,
|
||||
enableAsyncEvents: true,
|
||||
enableEventBatching: false,
|
||||
batchSize: 10,
|
||||
batchDelay: 16,
|
||||
maxEventListeners: 100
|
||||
}
|
||||
};
|
||||
|
||||
// 测试保存功能
|
||||
console.log('✅ 测试保存设置...');
|
||||
try {
|
||||
fs.writeFileSync(settingsPath, JSON.stringify(testSettings, null, 2), 'utf-8');
|
||||
console.log('✅ 设置已成功保存到:', settingsPath);
|
||||
} catch (error) {
|
||||
console.error('❌ 保存设置失败:', error);
|
||||
}
|
||||
|
||||
// 测试加载功能
|
||||
console.log('✅ 测试加载设置...');
|
||||
try {
|
||||
if (fs.existsSync(settingsPath)) {
|
||||
const loadedData = fs.readFileSync(settingsPath, 'utf-8');
|
||||
const loadedSettings = JSON.parse(loadedData);
|
||||
|
||||
console.log('✅ 设置已成功加载');
|
||||
console.log('默认实体名称:', loadedSettings.template.defaultEntityName);
|
||||
console.log('调试模式:', loadedSettings.debugging.enableDebugMode);
|
||||
console.log('目标FPS:', loadedSettings.performance.targetFps);
|
||||
|
||||
// 验证数据完整性
|
||||
const expectedKeys = Object.keys(testSettings);
|
||||
const loadedKeys = Object.keys(loadedSettings);
|
||||
|
||||
if (expectedKeys.every(key => loadedKeys.includes(key))) {
|
||||
console.log('✅ 数据完整性检查通过');
|
||||
} else {
|
||||
console.log('❌ 数据完整性检查失败');
|
||||
}
|
||||
} else {
|
||||
console.log('❌ 设置文件不存在');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 加载设置失败:', error);
|
||||
}
|
||||
|
||||
// 测试修改和重新保存
|
||||
console.log('✅ 测试修改设置...');
|
||||
try {
|
||||
const modifiedSettings = { ...testSettings };
|
||||
modifiedSettings.template.defaultEntityName = 'ModifiedEntity';
|
||||
modifiedSettings.performance.targetFps = 120;
|
||||
|
||||
fs.writeFileSync(settingsPath, JSON.stringify(modifiedSettings, null, 2), 'utf-8');
|
||||
|
||||
// 重新加载验证
|
||||
const reloadedData = fs.readFileSync(settingsPath, 'utf-8');
|
||||
const reloadedSettings = JSON.parse(reloadedData);
|
||||
|
||||
if (reloadedSettings.template.defaultEntityName === 'ModifiedEntity' &&
|
||||
reloadedSettings.performance.targetFps === 120) {
|
||||
console.log('✅ 设置修改测试通过');
|
||||
} else {
|
||||
console.log('❌ 设置修改测试失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('❌ 修改设置测试失败:', error);
|
||||
}
|
||||
|
||||
console.log('🎉 测试完成!设置功能工作正常。');
|
||||
console.log('📁 设置文件位置:', settingsPath);
|
||||
|
||||
// 清理测试文件(可选)
|
||||
// fs.unlinkSync(settingsPath);
|
||||
// console.log('🧹 已清理测试文件');
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"__version__": "3.0.9",
|
||||
"game": {
|
||||
"name": "UNKNOW GAME",
|
||||
"app_id": "UNKNOW",
|
||||
"c_id": "0"
|
||||
},
|
||||
"appConfigMaps": [
|
||||
{
|
||||
"app_id": "UNKNOW",
|
||||
"config_id": "a17c82"
|
||||
}
|
||||
],
|
||||
"configs": [
|
||||
{
|
||||
"app_id": "UNKNOW",
|
||||
"config_id": "a17c82",
|
||||
"config_name": "Default",
|
||||
"config_remarks": "",
|
||||
"services": []
|
||||
}
|
||||
]
|
||||
}
|
||||
36
package-lock.json
generated
36
package-lock.json
generated
@@ -8,6 +8,10 @@
|
||||
"name": "@esengine/ecs-framework",
|
||||
"version": "2.1.19",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/ws": "^8.18.1",
|
||||
"ws": "^8.18.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^28.0.3",
|
||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||
@@ -580,7 +584,6 @@
|
||||
"version": "20.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.0.tgz",
|
||||
"integrity": "sha512-hfrc+1tud1xcdVTABC2JiomZJEklMcXYNTVtZLAeqTVWD+qL5jkHKT+1lOtqDdGxt+mB53DTtiz673vfjU8D1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
@@ -592,6 +595,15 @@
|
||||
"integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/ws": {
|
||||
"version": "8.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
|
||||
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.15.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
|
||||
@@ -1307,7 +1319,6 @@
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/wrap-ansi-cjs": {
|
||||
@@ -1328,6 +1339,27 @@
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.18.2",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz",
|
||||
"integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,5 +50,9 @@
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/esengine/ecs-framework.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/ws": "^8.18.1",
|
||||
"ws": "^8.18.2"
|
||||
}
|
||||
}
|
||||
|
||||
116
src/Core.ts
116
src/Core.ts
@@ -6,6 +6,8 @@ import { PerformanceMonitor } from './Utils/PerformanceMonitor';
|
||||
import { PoolManager } from './Utils/Pool';
|
||||
import { ECSFluentAPI, createECSAPI } from './ECS/Core/FluentAPI';
|
||||
import { Scene } from './ECS/Scene';
|
||||
import { DebugReporter } from './Utils/DebugReporter';
|
||||
import { ICoreConfig, IECSDebugConfig } from './Types';
|
||||
|
||||
/**
|
||||
* 游戏引擎核心类
|
||||
@@ -107,27 +109,56 @@ export class Core {
|
||||
*/
|
||||
public _scene?: Scene;
|
||||
|
||||
/**
|
||||
* 调试报告器
|
||||
*
|
||||
* 负责收集和发送调试数据。
|
||||
*/
|
||||
public _debugReporter?: DebugReporter;
|
||||
|
||||
/**
|
||||
* Core配置
|
||||
*/
|
||||
private _config: ICoreConfig;
|
||||
|
||||
/**
|
||||
* 创建核心实例
|
||||
*
|
||||
* @param debug - 是否启用调试模式,默认为true
|
||||
* @param enableEntitySystems - 是否启用实体系统,默认为true
|
||||
* @param config - Core配置对象
|
||||
*/
|
||||
private constructor(debug: boolean = true, enableEntitySystems: boolean = true) {
|
||||
private constructor(config: ICoreConfig = {}) {
|
||||
Core._instance = this;
|
||||
|
||||
// 保存配置
|
||||
this._config = {
|
||||
debug: true,
|
||||
enableEntitySystems: true,
|
||||
...config
|
||||
};
|
||||
|
||||
// 初始化管理器
|
||||
this._timerManager = new TimerManager();
|
||||
Core.registerGlobalManager(this._timerManager);
|
||||
|
||||
// 初始化性能监控器
|
||||
this._performanceMonitor = PerformanceMonitor.instance;
|
||||
|
||||
// 在调试模式下启用性能监控
|
||||
if (this._config.debug) {
|
||||
this._performanceMonitor.enable();
|
||||
}
|
||||
|
||||
// 初始化对象池管理器
|
||||
this._poolManager = PoolManager.getInstance();
|
||||
|
||||
Core.entitySystemsEnabled = enableEntitySystems;
|
||||
this.debug = debug;
|
||||
Core.entitySystemsEnabled = this._config.enableEntitySystems || true;
|
||||
this.debug = this._config.debug || true;
|
||||
|
||||
// 初始化调试报告器
|
||||
if (this._config.debugConfig?.enabled) {
|
||||
this._debugReporter = new DebugReporter(this, this._config.debugConfig);
|
||||
}
|
||||
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
@@ -180,12 +211,16 @@ export class Core {
|
||||
*
|
||||
* 如果实例已存在,则返回现有实例。
|
||||
*
|
||||
* @param debug - 是否为调试模式,默认为true
|
||||
* @param config - Core配置,也可以直接传入boolean表示debug模式(向后兼容)
|
||||
* @returns Core实例
|
||||
*/
|
||||
public static create(debug: boolean = true): Core {
|
||||
public static create(config: ICoreConfig | boolean = true): Core {
|
||||
if (this._instance == null) {
|
||||
this._instance = new Core(debug);
|
||||
// 向后兼容:如果传入boolean,转换为配置对象
|
||||
const coreConfig: ICoreConfig = typeof config === 'boolean'
|
||||
? { debug: config, enableEntitySystems: true }
|
||||
: config;
|
||||
this._instance = new Core(coreConfig);
|
||||
}
|
||||
return this._instance;
|
||||
}
|
||||
@@ -284,6 +319,66 @@ export class Core {
|
||||
return this._instance?._ecsAPI || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用调试功能
|
||||
*
|
||||
* @param config 调试配置
|
||||
*/
|
||||
public static enableDebug(config: IECSDebugConfig): void {
|
||||
if (!this._instance) {
|
||||
console.warn("Core实例未创建,请先调用Core.create()");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._instance._debugReporter) {
|
||||
this._instance._debugReporter.updateConfig(config);
|
||||
} else {
|
||||
this._instance._debugReporter = new DebugReporter(this._instance, config);
|
||||
}
|
||||
|
||||
// 更新Core配置
|
||||
this._instance._config.debugConfig = config;
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁用调试功能
|
||||
*/
|
||||
public static disableDebug(): void {
|
||||
if (!this._instance) return;
|
||||
|
||||
if (this._instance._debugReporter) {
|
||||
this._instance._debugReporter.stop();
|
||||
this._instance._debugReporter = undefined;
|
||||
}
|
||||
|
||||
// 更新Core配置
|
||||
if (this._instance._config.debugConfig) {
|
||||
this._instance._config.debugConfig.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取调试数据
|
||||
*
|
||||
* @returns 当前调试数据,如果调试未启用则返回null
|
||||
*/
|
||||
public static getDebugData(): any {
|
||||
if (!this._instance?._debugReporter) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this._instance._debugReporter.getDebugData();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查调试是否启用
|
||||
*
|
||||
* @returns 调试状态
|
||||
*/
|
||||
public static get isDebugEnabled(): boolean {
|
||||
return this._instance?._config.debugConfig?.enabled || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景切换回调
|
||||
*
|
||||
@@ -297,6 +392,11 @@ export class Core {
|
||||
const scene = this._scene as any;
|
||||
this._ecsAPI = createECSAPI(scene, scene.querySystem, scene.eventSystem);
|
||||
}
|
||||
|
||||
// 通知调试报告器场景已变更
|
||||
if (this._debugReporter) {
|
||||
this._debugReporter.onSceneChanged();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -234,4 +234,207 @@ export interface IPerformanceEventData extends IEventData {
|
||||
memoryUsage?: number;
|
||||
/** 额外数据 */
|
||||
metadata?: Record<string, any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* ECS调试配置接口
|
||||
*/
|
||||
export interface IECSDebugConfig {
|
||||
/** 是否启用调试 */
|
||||
enabled: boolean;
|
||||
/** WebSocket服务器URL */
|
||||
websocketUrl: string;
|
||||
/** 是否自动重连 */
|
||||
autoReconnect?: boolean;
|
||||
/** 数据更新间隔(毫秒) */
|
||||
updateInterval?: number;
|
||||
/** 数据通道配置 */
|
||||
channels: {
|
||||
entities: boolean;
|
||||
systems: boolean;
|
||||
performance: boolean;
|
||||
components: boolean;
|
||||
scenes: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Core配置接口
|
||||
*/
|
||||
export interface ICoreConfig {
|
||||
/** 是否启用调试模式 */
|
||||
debug?: boolean;
|
||||
/** 是否启用实体系统 */
|
||||
enableEntitySystems?: boolean;
|
||||
/** 调试配置 */
|
||||
debugConfig?: IECSDebugConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* ECS调试数据接口
|
||||
*/
|
||||
export interface IECSDebugData {
|
||||
/** 时间戳 */
|
||||
timestamp: number;
|
||||
/** 框架版本 */
|
||||
frameworkVersion?: string;
|
||||
/** 是否正在运行 */
|
||||
isRunning: boolean;
|
||||
/** 框架是否已加载 */
|
||||
frameworkLoaded: boolean;
|
||||
/** 当前场景名称 */
|
||||
currentScene: string;
|
||||
/** 实体数据 */
|
||||
entities?: IEntityDebugData;
|
||||
/** 系统数据 */
|
||||
systems?: ISystemDebugData;
|
||||
/** 性能数据 */
|
||||
performance?: IPerformanceDebugData;
|
||||
/** 组件数据 */
|
||||
components?: IComponentDebugData;
|
||||
/** 场景数据 */
|
||||
scenes?: ISceneDebugData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 实体调试数据接口
|
||||
*/
|
||||
export interface IEntityDebugData {
|
||||
/** 总实体数 */
|
||||
totalEntities: number;
|
||||
/** 激活实体数 */
|
||||
activeEntities: number;
|
||||
/** 待添加实体数 */
|
||||
pendingAdd: number;
|
||||
/** 待移除实体数 */
|
||||
pendingRemove: number;
|
||||
/** 按Archetype分组的实体分布 */
|
||||
entitiesPerArchetype: Array<{
|
||||
signature: string;
|
||||
count: number;
|
||||
memory: number;
|
||||
}>;
|
||||
/** 组件数量最多的前几个实体 */
|
||||
topEntitiesByComponents: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
componentCount: number;
|
||||
memory: number;
|
||||
}>;
|
||||
/** 实体详情列表 */
|
||||
entityDetails?: Array<{
|
||||
id: string | number;
|
||||
name?: string;
|
||||
tag?: string;
|
||||
enabled: boolean;
|
||||
componentCount: number;
|
||||
components: string[];
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统调试数据接口
|
||||
*/
|
||||
export interface ISystemDebugData {
|
||||
/** 总系统数 */
|
||||
totalSystems: number;
|
||||
/** 系统信息列表 */
|
||||
systemsInfo: Array<{
|
||||
name: string;
|
||||
type: string;
|
||||
entityCount: number;
|
||||
executionTime?: number;
|
||||
averageExecutionTime?: number;
|
||||
minExecutionTime?: number;
|
||||
maxExecutionTime?: number;
|
||||
executionTimeHistory?: number[];
|
||||
memoryUsage?: number;
|
||||
updateOrder: number;
|
||||
enabled: boolean;
|
||||
lastUpdateTime?: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 性能调试数据接口
|
||||
*/
|
||||
export interface IPerformanceDebugData {
|
||||
/** ECS框架执行时间(毫秒) */
|
||||
frameTime: number;
|
||||
/** 引擎总帧时间(毫秒) */
|
||||
engineFrameTime?: number;
|
||||
/** ECS占总帧时间百分比 */
|
||||
ecsPercentage?: number;
|
||||
/** 内存使用量(MB) */
|
||||
memoryUsage: number;
|
||||
/** FPS */
|
||||
fps: number;
|
||||
/** 平均ECS执行时间(毫秒) */
|
||||
averageFrameTime: number;
|
||||
/** 最小ECS执行时间(毫秒) */
|
||||
minFrameTime: number;
|
||||
/** 最大ECS执行时间(毫秒) */
|
||||
maxFrameTime: number;
|
||||
/** ECS执行时间历史记录 */
|
||||
frameTimeHistory: number[];
|
||||
/** 系统性能详情 */
|
||||
systemPerformance: Array<{
|
||||
systemName: string;
|
||||
averageTime: number;
|
||||
maxTime: number;
|
||||
minTime: number;
|
||||
samples: number;
|
||||
percentage?: number; // 系统占ECS总时间的百分比
|
||||
}>;
|
||||
/** 内存分配详情 */
|
||||
memoryDetails?: {
|
||||
entities: number;
|
||||
components: number;
|
||||
systems: number;
|
||||
pooled: number;
|
||||
totalMemory: number;
|
||||
usedMemory: number;
|
||||
freeMemory: number;
|
||||
gcCollections: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 组件调试数据接口
|
||||
*/
|
||||
export interface IComponentDebugData {
|
||||
/** 组件类型数 */
|
||||
componentTypes: number;
|
||||
/** 组件实例总数 */
|
||||
componentInstances: number;
|
||||
/** 组件分布统计 */
|
||||
componentStats: Array<{
|
||||
typeName: string;
|
||||
instanceCount: number;
|
||||
memoryPerInstance: number;
|
||||
totalMemory: number;
|
||||
poolSize: number;
|
||||
poolUtilization: number;
|
||||
averagePerEntity?: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景调试数据接口
|
||||
*/
|
||||
export interface ISceneDebugData {
|
||||
/** 当前场景名称 */
|
||||
currentSceneName: string;
|
||||
/** 场景是否已初始化 */
|
||||
isInitialized: boolean;
|
||||
/** 场景运行时间(秒) */
|
||||
sceneRunTime: number;
|
||||
/** 场景实体数 */
|
||||
sceneEntityCount: number;
|
||||
/** 场景系统数 */
|
||||
sceneSystemCount: number;
|
||||
/** 场景内存使用量 */
|
||||
sceneMemory: number;
|
||||
/** 场景启动时间 */
|
||||
sceneUptime: number;
|
||||
}
|
||||
900
src/Utils/DebugReporter.ts
Normal file
900
src/Utils/DebugReporter.ts
Normal file
@@ -0,0 +1,900 @@
|
||||
import {
|
||||
IECSDebugConfig,
|
||||
IECSDebugData,
|
||||
IEntityDebugData,
|
||||
ISystemDebugData,
|
||||
IPerformanceDebugData,
|
||||
IComponentDebugData,
|
||||
ISceneDebugData
|
||||
} from '../Types';
|
||||
import { Core } from '../Core';
|
||||
import { Time } from './Time';
|
||||
|
||||
/**
|
||||
* ECS调试报告器 - WebSocket模式
|
||||
*
|
||||
* 负责收集ECS框架的运行时调试数据并通过WebSocket发送到调试服务器
|
||||
*/
|
||||
export class DebugReporter {
|
||||
private config: IECSDebugConfig;
|
||||
private core: Core;
|
||||
private timer?: any;
|
||||
private ws?: WebSocket;
|
||||
private lastFrameTime: number = 0;
|
||||
private frameCount: number = 0;
|
||||
private lastFpsTime: number = 0;
|
||||
private fps: number = 0;
|
||||
private sceneStartTime: number = 0;
|
||||
private isConnected: boolean = false;
|
||||
private reconnectAttempts: number = 0;
|
||||
private maxReconnectAttempts: number = 5;
|
||||
private frameTimeHistory: number[] = [];
|
||||
private maxHistoryLength: number = 60; // 保存60帧的历史数据
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
* @param core Core实例
|
||||
* @param config 调试配置
|
||||
*/
|
||||
constructor(core: Core, config: IECSDebugConfig) {
|
||||
this.core = core;
|
||||
this.config = config;
|
||||
this.sceneStartTime = Date.now();
|
||||
|
||||
if (this.config.enabled) {
|
||||
this.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动调试报告器
|
||||
*/
|
||||
private start(): void {
|
||||
this.connectWebSocket();
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止调试报告器
|
||||
*/
|
||||
public stop(): void {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
|
||||
if (this.ws) {
|
||||
this.ws.close();
|
||||
this.ws = undefined;
|
||||
}
|
||||
|
||||
this.isConnected = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新配置
|
||||
* @param newConfig 新配置
|
||||
*/
|
||||
public updateConfig(newConfig: IECSDebugConfig): void {
|
||||
const wasEnabled = this.config.enabled;
|
||||
const urlChanged = this.config.websocketUrl !== newConfig.websocketUrl;
|
||||
|
||||
this.config = newConfig;
|
||||
|
||||
if (!newConfig.enabled && wasEnabled) {
|
||||
this.stop();
|
||||
} else if (newConfig.enabled && (!wasEnabled || urlChanged)) {
|
||||
this.stop();
|
||||
this.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接WebSocket
|
||||
*/
|
||||
private connectWebSocket(): void {
|
||||
if (!this.config.websocketUrl) {
|
||||
console.error('[ECS Debug] WebSocket URL not provided');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.ws = new WebSocket(this.config.websocketUrl);
|
||||
|
||||
this.ws.onopen = () => {
|
||||
this.isConnected = true;
|
||||
this.reconnectAttempts = 0;
|
||||
this.startDataStream();
|
||||
|
||||
// 发送初始化消息
|
||||
this.send({
|
||||
type: 'init',
|
||||
data: {
|
||||
frameworkVersion: this.getFrameworkVersion(),
|
||||
timestamp: Date.now()
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
this.ws.onmessage = (event) => {
|
||||
try {
|
||||
const message = JSON.parse(event.data);
|
||||
this.handleMessage(message);
|
||||
} catch (error) {
|
||||
console.error('[ECS Debug] Failed to parse message:', error);
|
||||
}
|
||||
};
|
||||
|
||||
this.ws.onclose = (event) => {
|
||||
this.isConnected = false;
|
||||
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
|
||||
// 自动重连
|
||||
if (this.config.autoReconnect !== false && this.reconnectAttempts < this.maxReconnectAttempts) {
|
||||
this.reconnectAttempts++;
|
||||
const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts - 1), 10000);
|
||||
setTimeout(() => this.connectWebSocket(), delay);
|
||||
}
|
||||
};
|
||||
|
||||
this.ws.onerror = (error) => {
|
||||
console.error('[ECS Debug] WebSocket error:', error);
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[ECS Debug] Failed to create WebSocket:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动数据流
|
||||
*/
|
||||
private startDataStream(): void {
|
||||
const interval = this.config.updateInterval || 1000;
|
||||
|
||||
this.timer = setInterval(() => {
|
||||
if (this.isConnected) {
|
||||
try {
|
||||
const data = this.collectDebugData();
|
||||
this.send({ type: 'debug_data', data });
|
||||
} catch (error) {
|
||||
console.error('[ECS Debug] Failed to send debug data:', error);
|
||||
}
|
||||
}
|
||||
}, interval);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
*/
|
||||
private send(message: any): void {
|
||||
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
||||
this.ws.send(JSON.stringify(message));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理接收到的消息
|
||||
* @param message 消息内容
|
||||
*/
|
||||
private handleMessage(message: any): void {
|
||||
switch (message.type) {
|
||||
case 'get_debug_data':
|
||||
const data = this.collectDebugData();
|
||||
this.send({ type: 'debug_data_response', data, requestId: message.requestId });
|
||||
break;
|
||||
|
||||
case 'update_config':
|
||||
if (message.config) {
|
||||
this.updateConfig(message.config);
|
||||
this.send({ type: 'config_updated', success: true });
|
||||
}
|
||||
break;
|
||||
|
||||
case 'ping':
|
||||
this.send({ type: 'pong', timestamp: Date.now() });
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn('[ECS Debug] Unknown message type:', message.type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集调试数据
|
||||
* @returns 调试数据对象
|
||||
*/
|
||||
private collectDebugData(): IECSDebugData {
|
||||
const currentTime = Date.now();
|
||||
|
||||
// 更新FPS计算
|
||||
this.updateFPS(currentTime);
|
||||
|
||||
const data: IECSDebugData = {
|
||||
timestamp: currentTime,
|
||||
frameworkVersion: this.getFrameworkVersion(),
|
||||
isRunning: true,
|
||||
frameworkLoaded: true,
|
||||
currentScene: this.getCurrentSceneName()
|
||||
};
|
||||
|
||||
// 根据配置收集不同类型的数据
|
||||
if (this.config.channels.entities) {
|
||||
data.entities = this.collectEntityData();
|
||||
}
|
||||
|
||||
if (this.config.channels.systems) {
|
||||
data.systems = this.collectSystemData();
|
||||
}
|
||||
|
||||
if (this.config.channels.performance) {
|
||||
data.performance = this.collectPerformanceData();
|
||||
}
|
||||
|
||||
if (this.config.channels.components) {
|
||||
data.components = this.collectComponentData();
|
||||
}
|
||||
|
||||
if (this.config.channels.scenes) {
|
||||
data.scenes = this.collectSceneData();
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新FPS计算
|
||||
*/
|
||||
private updateFPS(currentTime: number): void {
|
||||
this.frameCount++;
|
||||
|
||||
if (currentTime - this.lastFpsTime >= 1000) {
|
||||
this.fps = this.frameCount;
|
||||
this.frameCount = 0;
|
||||
this.lastFpsTime = currentTime;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取框架版本
|
||||
*/
|
||||
private getFrameworkVersion(): string {
|
||||
return '1.0.0';
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前场景名称
|
||||
*/
|
||||
private getCurrentSceneName(): string {
|
||||
const scene = Core.scene;
|
||||
return scene ? (scene as any).name || 'Unnamed Scene' : 'No Scene';
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集实体数据
|
||||
*/
|
||||
private collectEntityData(): IEntityDebugData {
|
||||
const scene = Core.scene;
|
||||
if (!scene) {
|
||||
return {
|
||||
totalEntities: 0,
|
||||
activeEntities: 0,
|
||||
pendingAdd: 0,
|
||||
pendingRemove: 0,
|
||||
entitiesPerArchetype: [],
|
||||
topEntitiesByComponents: []
|
||||
};
|
||||
}
|
||||
|
||||
const entityList = (scene as any).entities;
|
||||
if (!entityList) {
|
||||
return {
|
||||
totalEntities: 0,
|
||||
activeEntities: 0,
|
||||
pendingAdd: 0,
|
||||
pendingRemove: 0,
|
||||
entitiesPerArchetype: [],
|
||||
topEntitiesByComponents: []
|
||||
};
|
||||
}
|
||||
|
||||
const allEntities = entityList.buffer || [];
|
||||
const activeEntities = allEntities.filter((e: any) => e.enabled && !e._isDestroyed);
|
||||
|
||||
return {
|
||||
totalEntities: allEntities.length,
|
||||
activeEntities: activeEntities.length,
|
||||
pendingAdd: entityList.toAdd?.length || 0,
|
||||
pendingRemove: entityList.toRemove?.length || 0,
|
||||
entitiesPerArchetype: this.getArchetypeDistribution({ entities: allEntities }),
|
||||
topEntitiesByComponents: this.getTopEntitiesByComponents({ entities: allEntities }),
|
||||
entityDetails: this.getEntityDetails({ entities: allEntities })
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取实体详情
|
||||
*/
|
||||
private getEntityDetails(entityContainer: any): Array<any> {
|
||||
if (!entityContainer.entities) return [];
|
||||
|
||||
return entityContainer.entities.slice(0, 100).map((entity: any) => ({
|
||||
id: entity.id,
|
||||
name: entity.name || `Entity_${entity.id}`,
|
||||
tag: entity.tag,
|
||||
enabled: entity.enabled,
|
||||
componentCount: entity.components?.length || 0,
|
||||
components: entity.components?.map((c: any) => c.constructor.name) || []
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集系统数据
|
||||
*/
|
||||
private collectSystemData(): ISystemDebugData {
|
||||
const scene = Core.scene;
|
||||
if (!scene) {
|
||||
return {
|
||||
totalSystems: 0,
|
||||
systemsInfo: []
|
||||
};
|
||||
}
|
||||
|
||||
const entityProcessors = (scene as any).entityProcessors;
|
||||
if (!entityProcessors) {
|
||||
return {
|
||||
totalSystems: 0,
|
||||
systemsInfo: []
|
||||
};
|
||||
}
|
||||
|
||||
const systems = entityProcessors.processors || [];
|
||||
|
||||
// 获取性能监控数据
|
||||
const monitor = this.core._performanceMonitor;
|
||||
let systemStats: Map<string, any> = new Map();
|
||||
let systemData: Map<string, any> = new Map();
|
||||
|
||||
if (monitor) {
|
||||
try {
|
||||
systemStats = monitor.getAllSystemStats();
|
||||
systemData = monitor.getAllSystemData();
|
||||
} catch (error) {
|
||||
// 忽略错误,使用空的Map
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
totalSystems: systems.length,
|
||||
systemsInfo: systems.map((system: any) => {
|
||||
const systemName = system.systemName || system.constructor.name;
|
||||
const stats = systemStats.get(systemName);
|
||||
const data = systemData.get(systemName);
|
||||
|
||||
return {
|
||||
name: systemName,
|
||||
type: system.constructor.name,
|
||||
entityCount: system.entities?.length || 0,
|
||||
executionTime: stats?.averageTime || data?.executionTime || 0,
|
||||
minExecutionTime: stats?.minTime === Number.MAX_VALUE ? 0 : (stats?.minTime || 0),
|
||||
maxExecutionTime: stats?.maxTime || 0,
|
||||
executionTimeHistory: stats?.recentTimes || [],
|
||||
updateOrder: system.updateOrder || 0,
|
||||
enabled: system.enabled !== false,
|
||||
lastUpdateTime: data?.lastUpdateTime || 0
|
||||
};
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集性能数据
|
||||
*/
|
||||
private collectPerformanceData(): IPerformanceDebugData {
|
||||
const frameTimeSeconds = Time.deltaTime;
|
||||
const engineFrameTimeMs = frameTimeSeconds * 1000;
|
||||
const currentFps = frameTimeSeconds > 0 ? Math.round(1 / frameTimeSeconds) : 0;
|
||||
|
||||
const ecsPerformanceData = this.getECSPerformanceData();
|
||||
const ecsExecutionTimeMs = ecsPerformanceData.totalExecutionTime;
|
||||
const ecsPercentage = engineFrameTimeMs > 0 ? (ecsExecutionTimeMs / engineFrameTimeMs * 100) : 0;
|
||||
|
||||
let memoryUsage = 0;
|
||||
if ((performance as any).memory) {
|
||||
memoryUsage = (performance as any).memory.usedJSHeapSize / 1024 / 1024;
|
||||
}
|
||||
|
||||
// 更新ECS执行时间历史记录
|
||||
this.frameTimeHistory.push(ecsExecutionTimeMs);
|
||||
if (this.frameTimeHistory.length > this.maxHistoryLength) {
|
||||
this.frameTimeHistory.shift();
|
||||
}
|
||||
|
||||
// 计算ECS执行时间统计
|
||||
const history = this.frameTimeHistory.filter(t => t >= 0);
|
||||
const averageECSTime = history.length > 0 ? history.reduce((a, b) => a + b, 0) / history.length : ecsExecutionTimeMs;
|
||||
const minECSTime = history.length > 0 ? Math.min(...history) : ecsExecutionTimeMs;
|
||||
const maxECSTime = history.length > 0 ? Math.max(...history) : ecsExecutionTimeMs;
|
||||
|
||||
return {
|
||||
frameTime: ecsExecutionTimeMs,
|
||||
engineFrameTime: engineFrameTimeMs,
|
||||
ecsPercentage: ecsPercentage,
|
||||
memoryUsage: memoryUsage,
|
||||
fps: currentFps,
|
||||
averageFrameTime: averageECSTime, // ECS平均执行时间
|
||||
minFrameTime: minECSTime, // ECS最短执行时间
|
||||
maxFrameTime: maxECSTime, // ECS最长执行时间
|
||||
frameTimeHistory: [...this.frameTimeHistory],
|
||||
systemPerformance: this.getSystemPerformance(),
|
||||
memoryDetails: this.getMemoryDetails()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取ECS框架整体性能数据
|
||||
*/
|
||||
private getECSPerformanceData(): { totalExecutionTime: number; systemBreakdown: Array<any> } {
|
||||
const monitor = this.core._performanceMonitor;
|
||||
if (!monitor) {
|
||||
return { totalExecutionTime: 0, systemBreakdown: [] };
|
||||
}
|
||||
|
||||
try {
|
||||
let totalTime = 0;
|
||||
const systemBreakdown = [];
|
||||
|
||||
const stats = monitor.getAllSystemStats();
|
||||
|
||||
// 计算各系统的执行时间
|
||||
for (const [systemName, stat] of stats.entries()) {
|
||||
const systemTime = stat.averageTime || 0;
|
||||
|
||||
totalTime += systemTime;
|
||||
systemBreakdown.push({
|
||||
systemName: systemName,
|
||||
executionTime: systemTime,
|
||||
percentage: 0 // 后面计算
|
||||
});
|
||||
}
|
||||
|
||||
// 计算各系统占ECS总时间的百分比
|
||||
systemBreakdown.forEach(system => {
|
||||
system.percentage = totalTime > 0 ? (system.executionTime / totalTime * 100) : 0;
|
||||
});
|
||||
|
||||
// 按执行时间排序
|
||||
systemBreakdown.sort((a, b) => b.executionTime - a.executionTime);
|
||||
|
||||
return {
|
||||
totalExecutionTime: totalTime,
|
||||
systemBreakdown: systemBreakdown
|
||||
};
|
||||
} catch (error) {
|
||||
return { totalExecutionTime: 0, systemBreakdown: [] };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统性能数据
|
||||
*/
|
||||
private getSystemPerformance(): Array<any> {
|
||||
const monitor = this.core._performanceMonitor;
|
||||
if (!monitor) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const stats = monitor.getAllSystemStats();
|
||||
const systemData = monitor.getAllSystemData();
|
||||
|
||||
return Array.from(stats.entries()).map(([systemName, stat]) => {
|
||||
const data = systemData.get(systemName);
|
||||
return {
|
||||
systemName: systemName,
|
||||
averageTime: stat.averageTime,
|
||||
maxTime: stat.maxTime,
|
||||
minTime: stat.minTime === Number.MAX_VALUE ? 0 : stat.minTime,
|
||||
samples: stat.executionCount,
|
||||
percentage: 0, // 在getECSPerformanceData中计算
|
||||
entityCount: data?.entityCount || 0,
|
||||
lastExecutionTime: data?.executionTime || 0
|
||||
};
|
||||
});
|
||||
} catch (error) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取内存详情
|
||||
*/
|
||||
private getMemoryDetails(): any {
|
||||
const scene = Core.scene;
|
||||
if (!scene) {
|
||||
return {
|
||||
entities: 0,
|
||||
components: 0,
|
||||
systems: 0,
|
||||
pooled: 0,
|
||||
totalMemory: 0,
|
||||
usedMemory: 0,
|
||||
freeMemory: 0,
|
||||
gcCollections: 0
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
let entityMemory = 0;
|
||||
let componentMemory = 0;
|
||||
let systemMemory = 0;
|
||||
let pooledMemory = 0;
|
||||
|
||||
const entityManager = (scene as any).entityManager;
|
||||
if (entityManager?.entities) {
|
||||
// 计算实体和组件内存
|
||||
entityManager.entities.forEach((entity: any) => {
|
||||
entityMemory += this.estimateObjectSize(entity);
|
||||
|
||||
if (entity.components) {
|
||||
entity.components.forEach((component: any) => {
|
||||
componentMemory += this.estimateObjectSize(component);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 计算系统内存(估算)
|
||||
const entitySystems = (scene as any).entitySystems;
|
||||
if (entitySystems?.systems) {
|
||||
entitySystems.systems.forEach((system: any) => {
|
||||
systemMemory += this.estimateObjectSize(system);
|
||||
});
|
||||
}
|
||||
|
||||
// 计算对象池内存(估算)
|
||||
try {
|
||||
const poolManager = this.core._poolManager;
|
||||
if (poolManager) {
|
||||
// 简单估算对象池内存
|
||||
pooledMemory = 1024 * 1024; // 1MB估算值
|
||||
}
|
||||
} catch (error) {
|
||||
// 忽略对象池内存计算错误
|
||||
}
|
||||
|
||||
// 获取浏览器内存信息
|
||||
let totalMemory = 512 * 1024 * 1024; // 默认512MB
|
||||
let usedMemory = entityMemory + componentMemory + systemMemory + pooledMemory;
|
||||
let gcCollections = 0;
|
||||
|
||||
if ((performance as any).memory) {
|
||||
const perfMemory = (performance as any).memory;
|
||||
totalMemory = perfMemory.jsHeapSizeLimit || totalMemory;
|
||||
usedMemory = perfMemory.usedJSHeapSize || usedMemory;
|
||||
}
|
||||
|
||||
return {
|
||||
entities: entityMemory,
|
||||
components: componentMemory,
|
||||
systems: systemMemory,
|
||||
pooled: pooledMemory,
|
||||
totalMemory: totalMemory,
|
||||
usedMemory: usedMemory,
|
||||
freeMemory: totalMemory - usedMemory,
|
||||
gcCollections: gcCollections
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
entities: 0,
|
||||
components: 0,
|
||||
systems: 0,
|
||||
pooled: 0,
|
||||
totalMemory: 512 * 1024 * 1024,
|
||||
usedMemory: 0,
|
||||
freeMemory: 512 * 1024 * 1024,
|
||||
gcCollections: 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集组件数据
|
||||
*/
|
||||
private collectComponentData(): IComponentDebugData {
|
||||
const scene = Core.scene;
|
||||
if (!scene) {
|
||||
return {
|
||||
componentTypes: 0,
|
||||
componentInstances: 0,
|
||||
componentStats: []
|
||||
};
|
||||
}
|
||||
|
||||
const entityList = (scene as any).entities;
|
||||
if (!entityList?.buffer) {
|
||||
return {
|
||||
componentTypes: 0,
|
||||
componentInstances: 0,
|
||||
componentStats: []
|
||||
};
|
||||
}
|
||||
|
||||
const componentStats = new Map<string, { count: number; entities: number }>();
|
||||
let totalInstances = 0;
|
||||
|
||||
entityList.buffer.forEach((entity: any) => {
|
||||
if (entity.components) {
|
||||
entity.components.forEach((component: any) => {
|
||||
const typeName = component.constructor.name;
|
||||
const stats = componentStats.get(typeName) || { count: 0, entities: 0 };
|
||||
stats.count++;
|
||||
totalInstances++;
|
||||
componentStats.set(typeName, stats);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
componentTypes: componentStats.size,
|
||||
componentInstances: totalInstances,
|
||||
componentStats: Array.from(componentStats.entries()).map(([typeName, stats]) => {
|
||||
const poolSize = this.getComponentPoolSize(typeName);
|
||||
const memoryPerInstance = this.calculateComponentMemorySize(typeName);
|
||||
return {
|
||||
typeName,
|
||||
instanceCount: stats.count,
|
||||
memoryPerInstance: memoryPerInstance,
|
||||
totalMemory: stats.count * memoryPerInstance,
|
||||
poolSize: poolSize,
|
||||
poolUtilization: poolSize > 0 ? (stats.count / poolSize * 100) : 0,
|
||||
averagePerEntity: stats.count / entityList.buffer.length
|
||||
};
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组件池大小
|
||||
*/
|
||||
private getComponentPoolSize(typeName: string): number {
|
||||
try {
|
||||
const poolManager = this.core._poolManager;
|
||||
return (poolManager as any).getPoolSize?.(typeName) || 0;
|
||||
} catch (error) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算组件实际内存大小
|
||||
*/
|
||||
private calculateComponentMemorySize(typeName: string): number {
|
||||
const scene = Core.scene;
|
||||
if (!scene) return 32;
|
||||
|
||||
const entityList = (scene as any).entities;
|
||||
if (!entityList?.buffer) return 32;
|
||||
|
||||
try {
|
||||
// 找到第一个包含此组件的实体,分析组件大小
|
||||
for (const entity of entityList.buffer) {
|
||||
if (entity.components) {
|
||||
const component = entity.components.find((c: any) => c.constructor.name === typeName);
|
||||
if (component) {
|
||||
return this.estimateObjectSize(component);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// 忽略错误,使用默认值
|
||||
}
|
||||
|
||||
// 如果无法计算,返回基础大小
|
||||
return 32; // 基础对象开销
|
||||
}
|
||||
|
||||
/**
|
||||
* 估算对象内存大小(字节)
|
||||
*/
|
||||
private estimateObjectSize(obj: any, visited = new WeakSet(), depth = 0): number {
|
||||
// 防止无限递归:限制深度和检测循环引用
|
||||
if (obj === null || obj === undefined || depth > 10) return 0;
|
||||
if (visited.has(obj)) return 0; // 已访问过,避免循环引用
|
||||
|
||||
let size = 0;
|
||||
const type = typeof obj;
|
||||
|
||||
switch (type) {
|
||||
case 'boolean':
|
||||
size = 1;
|
||||
break;
|
||||
case 'number':
|
||||
size = 8; // JavaScript中数字都是64位浮点数
|
||||
break;
|
||||
case 'string':
|
||||
size = Math.min(obj.length * 2, 1024); // UTF-16编码,每字符2字节,限制最大1KB
|
||||
break;
|
||||
case 'object':
|
||||
visited.add(obj); // 标记为已访问
|
||||
|
||||
if (Array.isArray(obj)) {
|
||||
size = 24; // 数组基础开销
|
||||
// 只处理前100个元素,避免大数组导致性能问题
|
||||
const maxItems = Math.min(obj.length, 100);
|
||||
for (let i = 0; i < maxItems; i++) {
|
||||
size += this.estimateObjectSize(obj[i], visited, depth + 1);
|
||||
}
|
||||
} else {
|
||||
size = 24; // 对象基础开销
|
||||
let propertyCount = 0;
|
||||
|
||||
for (const key in obj) {
|
||||
// 只处理前50个属性,避免复杂对象导致性能问题
|
||||
if (propertyCount >= 50) break;
|
||||
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
// 跳过一些可能导致问题的属性
|
||||
if (key === 'scene' || key === 'parent' || key === 'children' ||
|
||||
key === '_scene' || key === '_parent' || key === '_children' ||
|
||||
key === 'entity' || key === '_entity') {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
size += key.length * 2; // 属性名
|
||||
size += this.estimateObjectSize(obj[key], visited, depth + 1); // 属性值
|
||||
propertyCount++;
|
||||
} catch (error) {
|
||||
// 忽略访问错误的属性
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
size = 8; // 其他类型默认8字节
|
||||
}
|
||||
|
||||
return Math.min(size, 10240); // 限制单个对象最大10KB
|
||||
}
|
||||
|
||||
/**
|
||||
* 收集场景数据
|
||||
*/
|
||||
private collectSceneData(): ISceneDebugData {
|
||||
const scene = Core.scene;
|
||||
if (!scene) {
|
||||
return {
|
||||
currentSceneName: 'No Scene',
|
||||
isInitialized: false,
|
||||
sceneRunTime: 0,
|
||||
sceneEntityCount: 0,
|
||||
sceneSystemCount: 0,
|
||||
sceneMemory: 0,
|
||||
sceneUptime: 0
|
||||
};
|
||||
}
|
||||
|
||||
const currentTime = Date.now();
|
||||
const runTime = (currentTime - this.sceneStartTime) / 1000;
|
||||
|
||||
const entityList = (scene as any).entities;
|
||||
const entityProcessors = (scene as any).entityProcessors;
|
||||
|
||||
return {
|
||||
currentSceneName: (scene as any).name || 'Unnamed Scene',
|
||||
isInitialized: (scene as any)._didSceneBegin || false,
|
||||
sceneRunTime: runTime,
|
||||
sceneEntityCount: entityList?.buffer?.length || 0,
|
||||
sceneSystemCount: entityProcessors?.processors?.length || 0,
|
||||
sceneMemory: 0, // TODO: 计算实际场景内存
|
||||
sceneUptime: runTime
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动触发数据收集
|
||||
* @returns 当前调试数据
|
||||
*/
|
||||
public getDebugData(): IECSDebugData {
|
||||
return this.collectDebugData();
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置场景时间
|
||||
*/
|
||||
public onSceneChanged(): void {
|
||||
this.sceneStartTime = Date.now();
|
||||
|
||||
// 发送场景切换事件
|
||||
if (this.isConnected) {
|
||||
this.send({
|
||||
type: 'scene_changed',
|
||||
data: {
|
||||
sceneName: this.getCurrentSceneName(),
|
||||
timestamp: Date.now()
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取连接状态
|
||||
*/
|
||||
public get connected(): boolean {
|
||||
return this.isConnected;
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动重连
|
||||
*/
|
||||
public reconnect(): void {
|
||||
if (this.ws) {
|
||||
this.ws.close();
|
||||
}
|
||||
this.reconnectAttempts = 0;
|
||||
this.connectWebSocket();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Archetype分布
|
||||
*/
|
||||
private getArchetypeDistribution(entityContainer: any): Array<{ signature: string; count: number; memory: number }> {
|
||||
if (!entityContainer.entities) return [];
|
||||
|
||||
const archetypes = new Map<string, { count: number; memory: number }>();
|
||||
|
||||
entityContainer.entities.forEach((entity: any) => {
|
||||
const components = entity.components || [];
|
||||
const signature = components.map((c: any) => c.constructor.name).sort().join(',') || 'Empty';
|
||||
|
||||
const existing = archetypes.get(signature) || { count: 0, memory: 0 };
|
||||
existing.count++;
|
||||
|
||||
// 计算每个组件的实际内存大小
|
||||
let entityMemory = 0;
|
||||
components.forEach((component: any) => {
|
||||
entityMemory += this.estimateObjectSize(component);
|
||||
});
|
||||
existing.memory += entityMemory;
|
||||
|
||||
archetypes.set(signature, existing);
|
||||
});
|
||||
|
||||
return Array.from(archetypes.entries())
|
||||
.map(([signature, data]) => ({ signature, ...data }))
|
||||
.sort((a: any, b: any) => b.count - a.count)
|
||||
.slice(0, 10); // 只返回前10个
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取组件数量最多的实体
|
||||
*/
|
||||
private getTopEntitiesByComponents(entityContainer: any): Array<{ id: string; name: string; componentCount: number; memory: number }> {
|
||||
if (!entityContainer.entities) return [];
|
||||
|
||||
return entityContainer.entities
|
||||
.map((entity: any) => {
|
||||
const components = entity.components || [];
|
||||
let memory = 0;
|
||||
|
||||
// 计算实际内存使用
|
||||
components.forEach((component: any) => {
|
||||
memory += this.estimateObjectSize(component);
|
||||
});
|
||||
|
||||
return {
|
||||
id: entity.id?.toString() || 'unknown',
|
||||
name: entity.name || `Entity_${entity.id}`,
|
||||
componentCount: components.length,
|
||||
memory: memory
|
||||
};
|
||||
})
|
||||
.sort((a: any, b: any) => b.componentCount - a.componentCount)
|
||||
.slice(0, 10); // 只返回前10个
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user