2025-06-10 13:12:14 +08:00
|
|
|
|
# 实体基础指南
|
2025-06-08 10:20:51 +08:00
|
|
|
|
|
2025-06-10 13:12:14 +08:00
|
|
|
|
本指南介绍实体(Entity)的基本概念和基础使用方法。
|
|
|
|
|
|
|
|
|
|
|
|
> 📖 **需要高级实体管理?** 请参考 [EntityManager 指南](entity-manager-example.md) 了解高性能查询和批量操作
|
2025-06-08 10:20:51 +08:00
|
|
|
|
|
|
|
|
|
|
## 实体概述
|
|
|
|
|
|
|
|
|
|
|
|
实体(Entity)是 ECS 架构中的核心概念之一,它作为组件的容器存在。实体本身不包含游戏逻辑,所有功能都通过添加不同的组件来实现。
|
|
|
|
|
|
|
|
|
|
|
|
### 实体的特点
|
|
|
|
|
|
|
|
|
|
|
|
- **轻量级容器**:实体只是组件的载体,不包含具体的游戏逻辑
|
|
|
|
|
|
- **唯一标识**:每个实体都有唯一的ID和名称
|
|
|
|
|
|
- **层次结构**:支持父子关系,可以构建复杂的实体层次
|
|
|
|
|
|
- **高性能查询**:基于位掩码的组件查询系统
|
|
|
|
|
|
- **生命周期管理**:完整的创建、更新、销毁流程
|
|
|
|
|
|
|
|
|
|
|
|
## 创建实体
|
|
|
|
|
|
|
|
|
|
|
|
### 基本创建方式
|
|
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
import { Scene } from '@esengine/ecs-framework';
|
|
|
|
|
|
|
|
|
|
|
|
// 通过场景创建实体
|
|
|
|
|
|
const scene = new Scene();
|
|
|
|
|
|
const entity = scene.createEntity("Player");
|
|
|
|
|
|
|
|
|
|
|
|
console.log(entity.name); // "Player"
|
|
|
|
|
|
console.log(entity.id); // 唯一的数字ID
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2025-06-08 21:50:50 +08:00
|
|
|
|
### 批量创建实体(推荐)
|
|
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
import { Scene } from '@esengine/ecs-framework';
|
|
|
|
|
|
|
|
|
|
|
|
const scene = new Scene();
|
|
|
|
|
|
|
|
|
|
|
|
// 批量创建1000个实体 - 高性能
|
|
|
|
|
|
const entities = scene.createEntities(1000, "Enemy");
|
|
|
|
|
|
|
|
|
|
|
|
// 批量配置
|
|
|
|
|
|
entities.forEach((entity, index) => {
|
|
|
|
|
|
entity.tag = 2; // 敌人标签
|
|
|
|
|
|
// 添加组件...
|
|
|
|
|
|
});
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2025-06-08 10:20:51 +08:00
|
|
|
|
### 使用流式API创建
|
|
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
import { Core } from '@esengine/ecs-framework';
|
|
|
|
|
|
|
|
|
|
|
|
// 使用ECS流式API
|
|
|
|
|
|
const entity = Core.ecsAPI
|
|
|
|
|
|
?.entity("Enemy")
|
|
|
|
|
|
.withComponent(new PositionComponent(100, 200))
|
|
|
|
|
|
.withComponent(new HealthComponent(50))
|
|
|
|
|
|
.withTag(2)
|
|
|
|
|
|
.build();
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 实体属性
|
|
|
|
|
|
|
|
|
|
|
|
### 基本属性
|
|
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
// 实体名称 - 用于调试和标识
|
|
|
|
|
|
entity.name = "Player";
|
|
|
|
|
|
|
|
|
|
|
|
// 实体ID - 只读,场景内唯一
|
|
|
|
|
|
console.log(entity.id); // 例如: 1
|
|
|
|
|
|
|
|
|
|
|
|
// 标签 - 用于分类和快速查询
|
|
|
|
|
|
entity.tag = 1; // 玩家标签
|
|
|
|
|
|
entity.tag = 2; // 敌人标签
|
|
|
|
|
|
|
|
|
|
|
|
// 更新顺序 - 控制实体在系统中的处理优先级
|
|
|
|
|
|
entity.updateOrder = 0; // 数值越小优先级越高
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 状态控制
|
|
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
// 启用状态 - 控制实体是否参与更新和处理
|
|
|
|
|
|
entity.enabled = true; // 启用实体
|
|
|
|
|
|
entity.enabled = false; // 禁用实体
|
|
|
|
|
|
|
|
|
|
|
|
// 激活状态 - 控制实体及其子实体的活跃状态
|
|
|
|
|
|
entity.active = true; // 激活实体
|
|
|
|
|
|
entity.active = false; // 停用实体
|
|
|
|
|
|
|
|
|
|
|
|
// 检查层次结构中的激活状态
|
|
|
|
|
|
if (entity.activeInHierarchy) {
|
|
|
|
|
|
// 实体在整个层次结构中都是激活的
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查销毁状态
|
|
|
|
|
|
if (entity.isDestroyed) {
|
|
|
|
|
|
// 实体已被销毁
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 更新间隔
|
|
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
// 控制实体更新频率
|
|
|
|
|
|
entity.updateInterval = 1; // 每帧更新
|
|
|
|
|
|
entity.updateInterval = 2; // 每2帧更新一次
|
|
|
|
|
|
entity.updateInterval = 5; // 每5帧更新一次
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 组件管理
|
|
|
|
|
|
|
|
|
|
|
|
### 添加组件
|
|
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
// 创建并添加组件
|
|
|
|
|
|
const healthComponent = entity.addComponent(new HealthComponent(100));
|
|
|
|
|
|
|
|
|
|
|
|
// 使用工厂方法创建组件
|
|
|
|
|
|
const positionComponent = entity.createComponent(PositionComponent, 100, 200);
|
|
|
|
|
|
|
|
|
|
|
|
// 批量添加组件
|
|
|
|
|
|
const components = entity.addComponents([
|
|
|
|
|
|
new PositionComponent(0, 0),
|
|
|
|
|
|
new VelocityComponent(50, 0),
|
|
|
|
|
|
new HealthComponent(100)
|
|
|
|
|
|
]);
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 获取组件
|
|
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
// 获取单个组件
|
|
|
|
|
|
const health = entity.getComponent(HealthComponent);
|
|
|
|
|
|
if (health) {
|
|
|
|
|
|
console.log(`当前生命值: ${health.currentHealth}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取或创建组件(如果不存在则创建)
|
|
|
|
|
|
const position = entity.getOrCreateComponent(PositionComponent, 0, 0);
|
|
|
|
|
|
|
|
|
|
|
|
// 获取多个同类型组件(如果组件可以重复添加)
|
|
|
|
|
|
const allHealthComponents = entity.getComponents(HealthComponent);
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 检查组件
|
|
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
// 检查是否拥有指定组件
|
|
|
|
|
|
if (entity.hasComponent(HealthComponent)) {
|
|
|
|
|
|
// 实体拥有生命值组件
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查组件掩码(高性能)
|
|
|
|
|
|
const mask = entity.componentMask;
|
2025-06-10 13:12:14 +08:00
|
|
|
|
console.log(`组件掩码: ${mask.toString(2)}`); // 二进制表示
|
2025-06-08 10:20:51 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 移除组件
|
|
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
// 移除指定组件实例
|
|
|
|
|
|
const healthComponent = entity.getComponent(HealthComponent);
|
|
|
|
|
|
if (healthComponent) {
|
|
|
|
|
|
entity.removeComponent(healthComponent);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 按类型移除组件
|
|
|
|
|
|
const removedHealth = entity.removeComponentByType(HealthComponent);
|
|
|
|
|
|
|
|
|
|
|
|
// 批量移除组件
|
|
|
|
|
|
const removedComponents = entity.removeComponentsByTypes([
|
|
|
|
|
|
HealthComponent,
|
|
|
|
|
|
VelocityComponent
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
// 移除所有组件
|
|
|
|
|
|
entity.removeAllComponents();
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 层次结构管理
|
|
|
|
|
|
|
|
|
|
|
|
### 父子关系
|
|
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
// 创建父子实体
|
|
|
|
|
|
const player = scene.createEntity("Player");
|
|
|
|
|
|
const weapon = scene.createEntity("Weapon");
|
|
|
|
|
|
const shield = scene.createEntity("Shield");
|
|
|
|
|
|
|
|
|
|
|
|
// 添加子实体
|
|
|
|
|
|
player.addChild(weapon);
|
|
|
|
|
|
player.addChild(shield);
|
|
|
|
|
|
|
|
|
|
|
|
// 获取父实体
|
|
|
|
|
|
console.log(weapon.parent === player); // true
|
|
|
|
|
|
|
|
|
|
|
|
// 获取所有子实体
|
|
|
|
|
|
const children = player.children;
|
|
|
|
|
|
console.log(children.length); // 2
|
|
|
|
|
|
|
|
|
|
|
|
// 获取子实体数量
|
|
|
|
|
|
console.log(player.childCount); // 2
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 查找子实体
|
|
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
// 按名称查找子实体
|
|
|
|
|
|
const weapon = player.findChild("Weapon");
|
|
|
|
|
|
|
|
|
|
|
|
// 递归查找子实体
|
|
|
|
|
|
const deepChild = player.findChild("DeepChild", true);
|
|
|
|
|
|
|
|
|
|
|
|
// 按标签查找子实体
|
|
|
|
|
|
const enemies = player.findChildrenByTag(2); // 查找所有敌人标签的子实体
|
|
|
|
|
|
|
|
|
|
|
|
// 递归按标签查找
|
|
|
|
|
|
const allEnemies = player.findChildrenByTag(2, true);
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 层次结构操作
|
|
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
// 移除子实体
|
|
|
|
|
|
const removed = player.removeChild(weapon);
|
|
|
|
|
|
|
|
|
|
|
|
// 移除所有子实体
|
|
|
|
|
|
player.removeAllChildren();
|
|
|
|
|
|
|
|
|
|
|
|
// 获取根实体
|
|
|
|
|
|
const root = weapon.getRoot();
|
|
|
|
|
|
|
|
|
|
|
|
// 检查祖先关系
|
|
|
|
|
|
if (player.isAncestorOf(weapon)) {
|
|
|
|
|
|
// player 是 weapon 的祖先
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查后代关系
|
|
|
|
|
|
if (weapon.isDescendantOf(player)) {
|
|
|
|
|
|
// weapon 是 player 的后代
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取实体在层次结构中的深度
|
|
|
|
|
|
const depth = weapon.getDepth(); // 从根实体开始计算的深度
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 遍历子实体
|
|
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
// 遍历直接子实体
|
|
|
|
|
|
player.forEachChild((child, index) => {
|
|
|
|
|
|
console.log(`子实体 ${index}: ${child.name}`);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 递归遍历所有子实体
|
|
|
|
|
|
player.forEachChild((child, index) => {
|
|
|
|
|
|
console.log(`子实体 ${index}: ${child.name} (深度: ${child.getDepth()})`);
|
|
|
|
|
|
}, true);
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 实体生命周期
|
|
|
|
|
|
|
|
|
|
|
|
### 更新循环
|
|
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
// 手动更新实体(通常由场景自动调用)
|
|
|
|
|
|
entity.update();
|
|
|
|
|
|
|
|
|
|
|
|
// 实体会自动调用所有组件的update方法
|
|
|
|
|
|
class MyComponent extends Component {
|
|
|
|
|
|
public update(): void {
|
|
|
|
|
|
// 组件更新逻辑
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 销毁实体
|
|
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
// 销毁实体
|
|
|
|
|
|
entity.destroy();
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否已销毁
|
|
|
|
|
|
if (entity.isDestroyed) {
|
|
|
|
|
|
console.log("实体已被销毁");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 销毁实体时会自动:
|
|
|
|
|
|
// 1. 移除所有组件
|
|
|
|
|
|
// 2. 从父实体中移除
|
|
|
|
|
|
// 3. 销毁所有子实体
|
|
|
|
|
|
// 4. 从场景中移除
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2025-06-10 13:12:14 +08:00
|
|
|
|
# 高级特性请参考其他指南
|
2025-06-08 10:20:51 +08:00
|
|
|
|
|
2025-08-11 09:31:44 +08:00
|
|
|
|
> **更多功能:**
|
2025-06-10 13:12:14 +08:00
|
|
|
|
> - **高性能查询和批量操作** → [EntityManager 指南](entity-manager-example.md)
|
|
|
|
|
|
> - **性能优化技术** → [性能优化指南](performance-optimization.md)
|
|
|
|
|
|
> - **组件索引和缓存** → [技术概念详解](concepts-explained.md)
|
2025-06-08 10:20:51 +08:00
|
|
|
|
|
2025-06-10 13:12:14 +08:00
|
|
|
|
## 基础最佳实践
|
2025-06-08 10:20:51 +08:00
|
|
|
|
|
|
|
|
|
|
### 1. 合理使用标签
|
|
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
// 定义标签常量
|
2025-06-10 13:12:14 +08:00
|
|
|
|
const EntityTags = {
|
2025-06-08 10:20:51 +08:00
|
|
|
|
PLAYER: 1,
|
|
|
|
|
|
ENEMY: 2,
|
|
|
|
|
|
PROJECTILE: 3,
|
|
|
|
|
|
PICKUP: 4
|
|
|
|
|
|
} as const;
|
|
|
|
|
|
|
|
|
|
|
|
// 使用标签进行分类
|
2025-06-10 13:12:14 +08:00
|
|
|
|
player.tag = EntityTags.PLAYER;
|
|
|
|
|
|
enemy.tag = EntityTags.ENEMY;
|
2025-06-08 10:20:51 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2025-06-10 13:12:14 +08:00
|
|
|
|
### 2. 正确的销毁处理
|
2025-06-08 10:20:51 +08:00
|
|
|
|
|
|
|
|
|
|
```typescript
|
2025-06-10 13:12:14 +08:00
|
|
|
|
// 确保正确销毁实体
|
|
|
|
|
|
if (!entity.isDestroyed) {
|
|
|
|
|
|
entity.destroy(); // 自动移除组件和层次关系
|
|
|
|
|
|
}
|
2025-06-08 10:20:51 +08:00
|
|
|
|
|
2025-06-10 13:12:14 +08:00
|
|
|
|
// 检查实体状态
|
|
|
|
|
|
if (entity.isDestroyed) {
|
|
|
|
|
|
return; // 避免操作已销毁的实体
|
|
|
|
|
|
}
|
2025-06-08 10:20:51 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2025-06-10 13:12:14 +08:00
|
|
|
|
### 3. 组件生命周期
|
2025-06-08 10:20:51 +08:00
|
|
|
|
|
|
|
|
|
|
```typescript
|
2025-06-10 13:12:14 +08:00
|
|
|
|
// 正确添加组件
|
|
|
|
|
|
const health = entity.addComponent(new HealthComponent(100));
|
2025-06-08 10:20:51 +08:00
|
|
|
|
|
2025-06-10 13:12:14 +08:00
|
|
|
|
// 安全获取组件
|
|
|
|
|
|
const healthComp = entity.getComponent(HealthComponent);
|
|
|
|
|
|
if (healthComp && healthComp.currentHealth <= 0) {
|
|
|
|
|
|
entity.destroy();
|
2025-06-08 10:20:51 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## 常见问题
|
|
|
|
|
|
|
2025-06-10 13:12:14 +08:00
|
|
|
|
### Q: 实体如何实现位置、旋转等变换?
|
2025-06-08 10:20:51 +08:00
|
|
|
|
|
2025-06-10 13:12:14 +08:00
|
|
|
|
A: 通过添加相应的组件:
|
2025-06-08 10:20:51 +08:00
|
|
|
|
|
|
|
|
|
|
```typescript
|
|
|
|
|
|
class TransformComponent extends Component {
|
|
|
|
|
|
public position = { x: 0, y: 0 };
|
|
|
|
|
|
public rotation = 0;
|
|
|
|
|
|
public scale = { x: 1, y: 1 };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-10 13:12:14 +08:00
|
|
|
|
entity.addComponent(new TransformComponent());
|
2025-06-08 10:20:51 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2025-06-10 13:12:14 +08:00
|
|
|
|
### Q: 实体可以在场景间移动吗?
|
2025-06-08 10:20:51 +08:00
|
|
|
|
|
2025-06-10 13:12:14 +08:00
|
|
|
|
A: 不可以。实体与场景绑定,需要在新场景中重新创建。
|