对query/entity进行安全类型扩展
This commit is contained in:
404
packages/core/src/ECS/Core/Query/TypedQuery.ts
Normal file
404
packages/core/src/ECS/Core/Query/TypedQuery.ts
Normal file
@@ -0,0 +1,404 @@
|
|||||||
|
/**
|
||||||
|
* 类型安全的Query查询系统
|
||||||
|
*
|
||||||
|
* 提供完整的TypeScript类型推断,在编译时确保类型安全
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Entity } from '../../Entity';
|
||||||
|
import type { ComponentConstructor, ComponentInstance, ComponentTypeMap } from '../../../Types/TypeHelpers';
|
||||||
|
import { Matcher, type QueryCondition } from '../../Utils/Matcher';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 类型安全的查询结果
|
||||||
|
*
|
||||||
|
* 根据查询条件自动推断实体必定拥有的组件类型
|
||||||
|
*/
|
||||||
|
export class TypedQueryResult<TAll extends readonly ComponentConstructor[]> {
|
||||||
|
private _entities: readonly Entity[];
|
||||||
|
private _componentTypes: TAll;
|
||||||
|
|
||||||
|
constructor(entities: readonly Entity[], componentTypes: TAll) {
|
||||||
|
this._entities = entities;
|
||||||
|
this._componentTypes = componentTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取实体列表
|
||||||
|
*/
|
||||||
|
get entities(): readonly Entity[] {
|
||||||
|
return this._entities;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实体数量
|
||||||
|
*/
|
||||||
|
get length(): number {
|
||||||
|
return this._entities.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 遍历所有实体
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* query.forEach((entity) => {
|
||||||
|
* // entity.getComponent返回类型自动推断
|
||||||
|
* const pos = entity.getComponent(Position); // Position类型
|
||||||
|
* const vel = entity.getComponent(Velocity); // Velocity类型
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
forEach(callback: (entity: Entity, index: number) => void): void {
|
||||||
|
this._entities.forEach(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 映射转换实体
|
||||||
|
*/
|
||||||
|
map<R>(callback: (entity: Entity, index: number) => R): R[] {
|
||||||
|
return this._entities.map(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 过滤实体
|
||||||
|
*/
|
||||||
|
filter(predicate: (entity: Entity, index: number) => boolean): TypedQueryResult<TAll> {
|
||||||
|
return new TypedQueryResult(this._entities.filter(predicate), this._componentTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查找第一个匹配的实体
|
||||||
|
*/
|
||||||
|
find(predicate: (entity: Entity, index: number) => boolean): Entity | undefined {
|
||||||
|
return this._entities.find(predicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否存在匹配的实体
|
||||||
|
*/
|
||||||
|
some(predicate: (entity: Entity, index: number) => boolean): boolean {
|
||||||
|
return this._entities.some(predicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否所有实体都匹配
|
||||||
|
*/
|
||||||
|
every(predicate: (entity: Entity, index: number) => boolean): boolean {
|
||||||
|
return this._entities.every(predicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定索引的实体
|
||||||
|
*/
|
||||||
|
get(index: number): Entity | undefined {
|
||||||
|
return this._entities[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取第一个实体
|
||||||
|
*/
|
||||||
|
get first(): Entity | undefined {
|
||||||
|
return this._entities[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取最后一个实体
|
||||||
|
*/
|
||||||
|
get last(): Entity | undefined {
|
||||||
|
return this._entities[this._entities.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查查询结果是否为空
|
||||||
|
*/
|
||||||
|
get isEmpty(): boolean {
|
||||||
|
return this._entities.length === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换为数组
|
||||||
|
*/
|
||||||
|
toArray(): Entity[] {
|
||||||
|
return [...this._entities];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取组件类型信息(用于调试)
|
||||||
|
*/
|
||||||
|
getComponentTypes(): readonly ComponentConstructor[] {
|
||||||
|
return this._componentTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 迭代器支持
|
||||||
|
*/
|
||||||
|
[Symbol.iterator](): Iterator<Entity> {
|
||||||
|
return this._entities[Symbol.iterator]();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 类型安全的查询构建器
|
||||||
|
*
|
||||||
|
* 支持链式调用,自动推断查询结果的类型
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* // 基础查询
|
||||||
|
* const query = new TypedQueryBuilder()
|
||||||
|
* .withAll(Position, Velocity)
|
||||||
|
* .build();
|
||||||
|
*
|
||||||
|
* // 复杂查询
|
||||||
|
* const complexQuery = new TypedQueryBuilder()
|
||||||
|
* .withAll(Transform, Renderer)
|
||||||
|
* .withAny(BoxCollider, CircleCollider)
|
||||||
|
* .withNone(Disabled)
|
||||||
|
* .withTag(EntityTags.Enemy)
|
||||||
|
* .build();
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export class TypedQueryBuilder<
|
||||||
|
TAll extends readonly ComponentConstructor[] = [],
|
||||||
|
TAny extends readonly ComponentConstructor[] = [],
|
||||||
|
TNone extends readonly ComponentConstructor[] = []
|
||||||
|
> {
|
||||||
|
private _all: TAll;
|
||||||
|
private _any: TAny;
|
||||||
|
private _none: TNone;
|
||||||
|
private _tag?: number;
|
||||||
|
private _name?: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
all?: TAll,
|
||||||
|
any?: TAny,
|
||||||
|
none?: TNone,
|
||||||
|
tag?: number,
|
||||||
|
name?: string
|
||||||
|
) {
|
||||||
|
this._all = (all || []) as TAll;
|
||||||
|
this._any = (any || []) as TAny;
|
||||||
|
this._none = (none || []) as TNone;
|
||||||
|
this._tag = tag;
|
||||||
|
this._name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 要求实体拥有所有指定的组件
|
||||||
|
*
|
||||||
|
* @param types 组件类型
|
||||||
|
* @returns 新的查询构建器,类型参数更新
|
||||||
|
*/
|
||||||
|
withAll<TNewAll extends readonly ComponentConstructor[]>(
|
||||||
|
...types: TNewAll
|
||||||
|
): TypedQueryBuilder<
|
||||||
|
readonly [...TAll, ...TNewAll],
|
||||||
|
TAny,
|
||||||
|
TNone
|
||||||
|
> {
|
||||||
|
return new TypedQueryBuilder(
|
||||||
|
[...this._all, ...types] as readonly [...TAll, ...TNewAll],
|
||||||
|
this._any,
|
||||||
|
this._none,
|
||||||
|
this._tag,
|
||||||
|
this._name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 要求实体至少拥有一个指定的组件
|
||||||
|
*
|
||||||
|
* @param types 组件类型
|
||||||
|
* @returns 新的查询构建器
|
||||||
|
*/
|
||||||
|
withAny<TNewAny extends readonly ComponentConstructor[]>(
|
||||||
|
...types: TNewAny
|
||||||
|
): TypedQueryBuilder<
|
||||||
|
TAll,
|
||||||
|
readonly [...TAny, ...TNewAny],
|
||||||
|
TNone
|
||||||
|
> {
|
||||||
|
return new TypedQueryBuilder(
|
||||||
|
this._all,
|
||||||
|
[...this._any, ...types] as readonly [...TAny, ...TNewAny],
|
||||||
|
this._none,
|
||||||
|
this._tag,
|
||||||
|
this._name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排除拥有指定组件的实体
|
||||||
|
*
|
||||||
|
* @param types 组件类型
|
||||||
|
* @returns 新的查询构建器
|
||||||
|
*/
|
||||||
|
withNone<TNewNone extends readonly ComponentConstructor[]>(
|
||||||
|
...types: TNewNone
|
||||||
|
): TypedQueryBuilder<
|
||||||
|
TAll,
|
||||||
|
TAny,
|
||||||
|
readonly [...TNone, ...TNewNone]
|
||||||
|
> {
|
||||||
|
return new TypedQueryBuilder(
|
||||||
|
this._all,
|
||||||
|
this._any,
|
||||||
|
[...this._none, ...types] as readonly [...TNone, ...TNewNone],
|
||||||
|
this._tag,
|
||||||
|
this._name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按标签过滤实体
|
||||||
|
*
|
||||||
|
* @param tag 标签值
|
||||||
|
* @returns 新的查询构建器
|
||||||
|
*/
|
||||||
|
withTag(tag: number): TypedQueryBuilder<TAll, TAny, TNone> {
|
||||||
|
return new TypedQueryBuilder(
|
||||||
|
this._all,
|
||||||
|
this._any,
|
||||||
|
this._none,
|
||||||
|
tag,
|
||||||
|
this._name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按名称过滤实体
|
||||||
|
*
|
||||||
|
* @param name 实体名称
|
||||||
|
* @returns 新的查询构建器
|
||||||
|
*/
|
||||||
|
withName(name: string): TypedQueryBuilder<TAll, TAny, TNone> {
|
||||||
|
return new TypedQueryBuilder(
|
||||||
|
this._all,
|
||||||
|
this._any,
|
||||||
|
this._none,
|
||||||
|
this._tag,
|
||||||
|
name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建Matcher对象
|
||||||
|
*
|
||||||
|
* @returns Matcher实例,用于传统查询API
|
||||||
|
*/
|
||||||
|
buildMatcher(): Matcher {
|
||||||
|
let matcher = Matcher.complex();
|
||||||
|
|
||||||
|
if (this._all.length > 0) {
|
||||||
|
matcher = matcher.all(...(this._all as unknown as ComponentConstructor[]));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._any.length > 0) {
|
||||||
|
matcher = matcher.any(...(this._any as unknown as ComponentConstructor[]));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._none.length > 0) {
|
||||||
|
matcher = matcher.none(...(this._none as unknown as ComponentConstructor[]));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._tag !== undefined) {
|
||||||
|
matcher = matcher.withTag(this._tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._name !== undefined) {
|
||||||
|
matcher = matcher.withName(this._name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return matcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取查询条件
|
||||||
|
*
|
||||||
|
* @returns 查询条件对象
|
||||||
|
*/
|
||||||
|
getCondition(): QueryCondition {
|
||||||
|
return {
|
||||||
|
all: [...this._all] as ComponentConstructor[],
|
||||||
|
any: [...this._any] as ComponentConstructor[],
|
||||||
|
none: [...this._none] as ComponentConstructor[],
|
||||||
|
tag: this._tag,
|
||||||
|
name: this._name
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取required组件类型(用于类型推断)
|
||||||
|
*/
|
||||||
|
getRequiredTypes(): TAll {
|
||||||
|
return this._all;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 克隆查询构建器
|
||||||
|
*/
|
||||||
|
clone(): TypedQueryBuilder<TAll, TAny, TNone> {
|
||||||
|
return new TypedQueryBuilder(
|
||||||
|
[...this._all] as unknown as TAll,
|
||||||
|
[...this._any] as unknown as TAny,
|
||||||
|
[...this._none] as unknown as TNone,
|
||||||
|
this._tag,
|
||||||
|
this._name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建类型安全的查询构建器
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* const query = createQuery()
|
||||||
|
* .withAll(Position, Velocity)
|
||||||
|
* .withNone(Disabled);
|
||||||
|
*
|
||||||
|
* // 在System或Scene中使用
|
||||||
|
* const entities = scene.query(query);
|
||||||
|
* entities.forEach(entity => {
|
||||||
|
* const pos = entity.getComponent(Position); // 自动推断为Position
|
||||||
|
* const vel = entity.getComponent(Velocity); // 自动推断为Velocity
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function createQuery(): TypedQueryBuilder<[], [], []> {
|
||||||
|
return new TypedQueryBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建单组件查询的便捷方法
|
||||||
|
*
|
||||||
|
* @param componentType 组件类型
|
||||||
|
* @returns 查询构建器
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* const healthEntities = queryFor(HealthComponent);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function queryFor<T extends ComponentConstructor>(
|
||||||
|
componentType: T
|
||||||
|
): TypedQueryBuilder<readonly [T], [], []> {
|
||||||
|
return new TypedQueryBuilder([componentType] as readonly [T]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建多组件查询的便捷方法
|
||||||
|
*
|
||||||
|
* @param types 组件类型数组
|
||||||
|
* @returns 查询构建器
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* const movableEntities = queryForAll(Position, Velocity);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function queryForAll<T extends readonly ComponentConstructor[]>(
|
||||||
|
...types: T
|
||||||
|
): TypedQueryBuilder<T, [], []> {
|
||||||
|
return new TypedQueryBuilder(types);
|
||||||
|
}
|
||||||
@@ -347,9 +347,15 @@ export class Entity {
|
|||||||
/**
|
/**
|
||||||
* 创建并添加组件
|
* 创建并添加组件
|
||||||
*
|
*
|
||||||
* @param componentType - 组件类型
|
* @param componentType - 组件类型构造函数
|
||||||
* @param args - 组件构造函数参数
|
* @param args - 组件构造函数参数
|
||||||
* @returns 创建的组件实例
|
* @returns 创建的组件实例
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* const position = entity.createComponent(Position, 100, 200);
|
||||||
|
* const health = entity.createComponent(Health, 100);
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
public createComponent<T extends Component>(
|
public createComponent<T extends Component>(
|
||||||
componentType: ComponentType<T>,
|
componentType: ComponentType<T>,
|
||||||
@@ -390,7 +396,13 @@ export class Entity {
|
|||||||
*
|
*
|
||||||
* @param component - 要添加的组件实例
|
* @param component - 要添加的组件实例
|
||||||
* @returns 添加的组件实例
|
* @returns 添加的组件实例
|
||||||
* @throws {Error} 如果组件类型已存在
|
* @throws {Error} 如果实体已存在该类型的组件
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* const position = new Position(100, 200);
|
||||||
|
* entity.addComponent(position);
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
public addComponent<T extends Component>(component: T): T {
|
public addComponent<T extends Component>(component: T): T {
|
||||||
const componentType = component.constructor as ComponentType<T>;
|
const componentType = component.constructor as ComponentType<T>;
|
||||||
@@ -429,8 +441,17 @@ export class Entity {
|
|||||||
/**
|
/**
|
||||||
* 获取指定类型的组件
|
* 获取指定类型的组件
|
||||||
*
|
*
|
||||||
* @param type - 组件类型
|
* @param type - 组件类型构造函数
|
||||||
* @returns 组件实例或null
|
* @returns 组件实例,如果不存在则返回null
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* const position = entity.getComponent(Position);
|
||||||
|
* if (position) {
|
||||||
|
* position.x += 10;
|
||||||
|
* position.y += 20;
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
public getComponent<T extends Component>(type: ComponentType<T>): T | null {
|
public getComponent<T extends Component>(type: ComponentType<T>): T | null {
|
||||||
// 快速检查:位掩码
|
// 快速检查:位掩码
|
||||||
@@ -442,7 +463,7 @@ export class Entity {
|
|||||||
if (this.scene?.componentStorageManager) {
|
if (this.scene?.componentStorageManager) {
|
||||||
const component = this.scene.componentStorageManager.getComponent(this.id, type);
|
const component = this.scene.componentStorageManager.getComponent(this.id, type);
|
||||||
if (component) {
|
if (component) {
|
||||||
return component;
|
return component as T;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -454,10 +475,18 @@ export class Entity {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查实体是否有指定类型的组件
|
* 检查实体是否拥有指定类型的组件
|
||||||
*
|
*
|
||||||
* @param type - 组件类型
|
* @param type - 组件类型构造函数
|
||||||
* @returns 如果有该组件则返回true
|
* @returns 如果实体拥有该组件返回true,否则返回false
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* if (entity.hasComponent(Position)) {
|
||||||
|
* const position = entity.getComponent(Position)!;
|
||||||
|
* position.x += 10;
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
public hasComponent<T extends Component>(type: ComponentType<T>): boolean {
|
public hasComponent<T extends Component>(type: ComponentType<T>): boolean {
|
||||||
if (!ComponentRegistry.isRegistered(type)) {
|
if (!ComponentRegistry.isRegistered(type)) {
|
||||||
@@ -471,9 +500,18 @@ export class Entity {
|
|||||||
/**
|
/**
|
||||||
* 获取或创建指定类型的组件
|
* 获取或创建指定类型的组件
|
||||||
*
|
*
|
||||||
* @param type - 组件类型
|
* 如果组件已存在则返回现有组件,否则创建新组件并添加到实体
|
||||||
* @param args - 组件构造函数参数(仅在创建时使用)
|
*
|
||||||
|
* @param type - 组件类型构造函数
|
||||||
|
* @param args - 组件构造函数参数(仅在创建新组件时使用)
|
||||||
* @returns 组件实例
|
* @returns 组件实例
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* // 确保实体拥有Position组件
|
||||||
|
* const position = entity.getOrCreateComponent(Position, 0, 0);
|
||||||
|
* position.x = 100;
|
||||||
|
* ```
|
||||||
*/
|
*/
|
||||||
public getOrCreateComponent<T extends Component>(
|
public getOrCreateComponent<T extends Component>(
|
||||||
type: ComponentType<T>,
|
type: ComponentType<T>,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { TypeSafeEventSystem } from './Core/EventSystem';
|
|||||||
import { EventBus } from './Core/EventBus';
|
import { EventBus } from './Core/EventBus';
|
||||||
import { IScene, ISceneConfig } from './IScene';
|
import { IScene, ISceneConfig } from './IScene';
|
||||||
import { getComponentInstanceTypeName, getSystemInstanceTypeName } from './Decorators';
|
import { getComponentInstanceTypeName, getSystemInstanceTypeName } from './Decorators';
|
||||||
|
import { TypedQueryBuilder } from './Core/Query/TypedQuery';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 游戏场景默认实现类
|
* 游戏场景默认实现类
|
||||||
@@ -319,6 +320,70 @@ export class Scene implements IScene {
|
|||||||
return this.findEntitiesByTag(tag);
|
return this.findEntitiesByTag(tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询拥有所有指定组件的实体
|
||||||
|
*
|
||||||
|
* @param componentTypes - 组件类型数组
|
||||||
|
* @returns 查询结果
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* const result = scene.queryAll(Position, Velocity);
|
||||||
|
* for (const entity of result.entities) {
|
||||||
|
* const pos = entity.getComponent(Position);
|
||||||
|
* const vel = entity.getComponent(Velocity);
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
public queryAll(...componentTypes: any[]): { entities: readonly Entity[] } {
|
||||||
|
return this.querySystem.queryAll(...componentTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询拥有任意一个指定组件的实体
|
||||||
|
*
|
||||||
|
* @param componentTypes - 组件类型数组
|
||||||
|
* @returns 查询结果
|
||||||
|
*/
|
||||||
|
public queryAny(...componentTypes: any[]): { entities: readonly Entity[] } {
|
||||||
|
return this.querySystem.queryAny(...componentTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询不包含指定组件的实体
|
||||||
|
*
|
||||||
|
* @param componentTypes - 组件类型数组
|
||||||
|
* @returns 查询结果
|
||||||
|
*/
|
||||||
|
public queryNone(...componentTypes: any[]): { entities: readonly Entity[] } {
|
||||||
|
return this.querySystem.queryNone(...componentTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建类型安全的查询构建器
|
||||||
|
*
|
||||||
|
* @returns 查询构建器,支持链式调用
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* // 使用查询构建器
|
||||||
|
* const matcher = scene.query()
|
||||||
|
* .withAll(Position, Velocity)
|
||||||
|
* .withNone(Disabled)
|
||||||
|
* .buildMatcher();
|
||||||
|
*
|
||||||
|
* // 在System中使用
|
||||||
|
* class MovementSystem extends EntitySystem {
|
||||||
|
* constructor() {
|
||||||
|
* super(matcher);
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
public query(): TypedQueryBuilder {
|
||||||
|
return new TypedQueryBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 在场景中添加一个EntitySystem处理器
|
* 在场景中添加一个EntitySystem处理器
|
||||||
* @param processor 处理器
|
* @param processor 处理器
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import type { QuerySystem } from '../Core/QuerySystem';
|
|||||||
import { getSystemInstanceTypeName } from '../Decorators';
|
import { getSystemInstanceTypeName } from '../Decorators';
|
||||||
import { createLogger } from '../../Utils/Logger';
|
import { createLogger } from '../../Utils/Logger';
|
||||||
import type { EventListenerConfig, TypeSafeEventSystem, EventHandler } from '../Core/EventSystem';
|
import type { EventListenerConfig, TypeSafeEventSystem, EventHandler } from '../Core/EventSystem';
|
||||||
|
import type { ComponentConstructor, ComponentInstance } from '../../Types/TypeHelpers';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 事件监听器记录
|
* 事件监听器记录
|
||||||
@@ -25,11 +26,16 @@ interface EventListenerRecord {
|
|||||||
* 用于处理一组符合特定条件的实体。系统是ECS架构中的逻辑处理单元,
|
* 用于处理一组符合特定条件的实体。系统是ECS架构中的逻辑处理单元,
|
||||||
* 负责对拥有特定组件组合的实体执行业务逻辑。
|
* 负责对拥有特定组件组合的实体执行业务逻辑。
|
||||||
*
|
*
|
||||||
|
* 支持泛型参数以提供类型安全的组件访问:
|
||||||
|
*
|
||||||
|
* @template TComponents - 系统需要的组件类型数组
|
||||||
|
*
|
||||||
* @example
|
* @example
|
||||||
* ```typescript
|
* ```typescript
|
||||||
|
* // 传统方式
|
||||||
* class MovementSystem extends EntitySystem {
|
* class MovementSystem extends EntitySystem {
|
||||||
* constructor() {
|
* constructor() {
|
||||||
* super(Transform, Velocity);
|
* super(Matcher.of(Transform, Velocity));
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
* protected process(entities: readonly Entity[]): void {
|
* protected process(entities: readonly Entity[]): void {
|
||||||
@@ -40,9 +46,26 @@ interface EventListenerRecord {
|
|||||||
* }
|
* }
|
||||||
* }
|
* }
|
||||||
* }
|
* }
|
||||||
|
*
|
||||||
|
* // 类型安全方式
|
||||||
|
* class MovementSystem extends EntitySystem<[typeof Transform, typeof Velocity]> {
|
||||||
|
* constructor() {
|
||||||
|
* super(Matcher.of(Transform, Velocity));
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* protected process(entities: readonly Entity[]): void {
|
||||||
|
* for (const entity of entities) {
|
||||||
|
* // 类型安全的组件访问
|
||||||
|
* const [transform, velocity] = this.getComponents(entity);
|
||||||
|
* transform.position.add(velocity.value);
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export abstract class EntitySystem implements ISystemBase {
|
export abstract class EntitySystem<
|
||||||
|
TComponents extends readonly ComponentConstructor[] = []
|
||||||
|
> implements ISystemBase {
|
||||||
private _updateOrder: number;
|
private _updateOrder: number;
|
||||||
private _enabled: boolean;
|
private _enabled: boolean;
|
||||||
private _performanceMonitor: PerformanceMonitor;
|
private _performanceMonitor: PerformanceMonitor;
|
||||||
@@ -793,4 +816,235 @@ export abstract class EntitySystem implements ISystemBase {
|
|||||||
protected onDestroy(): void {
|
protected onDestroy(): void {
|
||||||
// 子类可以重写此方法进行清理操作
|
// 子类可以重写此方法进行清理操作
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 类型安全的辅助方法
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 类型安全地获取单个组件
|
||||||
|
*
|
||||||
|
* 相比Entity.getComponent,此方法保证返回非空值,
|
||||||
|
* 如果组件不存在会抛出错误而不是返回null
|
||||||
|
*
|
||||||
|
* @param entity 实体
|
||||||
|
* @param componentType 组件类型
|
||||||
|
* @returns 组件实例(保证非空)
|
||||||
|
* @throws 如果组件不存在则抛出错误
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* protected process(entities: readonly Entity[]): void {
|
||||||
|
* for (const entity of entities) {
|
||||||
|
* const transform = this.requireComponent(entity, Transform);
|
||||||
|
* // transform 保证非空,类型为 Transform
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
protected requireComponent<T extends ComponentConstructor>(
|
||||||
|
entity: Entity,
|
||||||
|
componentType: T
|
||||||
|
): ComponentInstance<T> {
|
||||||
|
const component = entity.getComponent(componentType as any);
|
||||||
|
if (!component) {
|
||||||
|
throw new Error(
|
||||||
|
`Component ${componentType.name} not found on entity ${entity.name} in ${this.systemName}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return component as ComponentInstance<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量获取实体的所有必需组件
|
||||||
|
*
|
||||||
|
* 根据泛型参数TComponents推断返回类型,
|
||||||
|
* 返回一个元组,包含所有组件实例
|
||||||
|
*
|
||||||
|
* @param entity 实体
|
||||||
|
* @param components 组件类型数组
|
||||||
|
* @returns 组件实例元组
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* class MySystem extends EntitySystem<[typeof Position, typeof Velocity]> {
|
||||||
|
* protected process(entities: readonly Entity[]): void {
|
||||||
|
* for (const entity of entities) {
|
||||||
|
* const [pos, vel] = this.getComponents(entity, Position, Velocity);
|
||||||
|
* // pos: Position, vel: Velocity (自动类型推断)
|
||||||
|
* pos.x += vel.x;
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
protected getComponents<T extends readonly ComponentConstructor[]>(
|
||||||
|
entity: Entity,
|
||||||
|
...components: T
|
||||||
|
): { [K in keyof T]: ComponentInstance<T[K]> } {
|
||||||
|
return components.map((type) =>
|
||||||
|
this.requireComponent(entity, type)
|
||||||
|
) as any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 遍历实体并处理每个实体
|
||||||
|
*
|
||||||
|
* 提供更简洁的语法糖,避免手动遍历
|
||||||
|
*
|
||||||
|
* @param entities 实体列表
|
||||||
|
* @param processor 处理函数
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* protected process(entities: readonly Entity[]): void {
|
||||||
|
* this.forEach(entities, (entity) => {
|
||||||
|
* const transform = this.requireComponent(entity, Transform);
|
||||||
|
* transform.position.y -= 9.8 * Time.deltaTime;
|
||||||
|
* });
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
protected forEach(
|
||||||
|
entities: readonly Entity[],
|
||||||
|
processor: (entity: Entity, index: number) => void
|
||||||
|
): void {
|
||||||
|
for (let i = 0; i < entities.length; i++) {
|
||||||
|
processor(entities[i], i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 过滤实体
|
||||||
|
*
|
||||||
|
* @param entities 实体列表
|
||||||
|
* @param predicate 过滤条件
|
||||||
|
* @returns 过滤后的实体数组
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* protected process(entities: readonly Entity[]): void {
|
||||||
|
* const activeEntities = this.filterEntities(entities, (entity) => {
|
||||||
|
* const health = this.requireComponent(entity, Health);
|
||||||
|
* return health.value > 0;
|
||||||
|
* });
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
protected filterEntities(
|
||||||
|
entities: readonly Entity[],
|
||||||
|
predicate: (entity: Entity, index: number) => boolean
|
||||||
|
): Entity[] {
|
||||||
|
return Array.from(entities).filter(predicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 映射实体到另一种类型
|
||||||
|
*
|
||||||
|
* @param entities 实体列表
|
||||||
|
* @param mapper 映射函数
|
||||||
|
* @returns 映射后的结果数组
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* protected process(entities: readonly Entity[]): void {
|
||||||
|
* const positions = this.mapEntities(entities, (entity) => {
|
||||||
|
* const transform = this.requireComponent(entity, Transform);
|
||||||
|
* return transform.position;
|
||||||
|
* });
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
protected mapEntities<R>(
|
||||||
|
entities: readonly Entity[],
|
||||||
|
mapper: (entity: Entity, index: number) => R
|
||||||
|
): R[] {
|
||||||
|
return Array.from(entities).map(mapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查找第一个满足条件的实体
|
||||||
|
*
|
||||||
|
* @param entities 实体列表
|
||||||
|
* @param predicate 查找条件
|
||||||
|
* @returns 第一个满足条件的实体,或undefined
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* protected process(entities: readonly Entity[]): void {
|
||||||
|
* const player = this.findEntity(entities, (entity) =>
|
||||||
|
* entity.hasComponent(PlayerTag)
|
||||||
|
* );
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
protected findEntity(
|
||||||
|
entities: readonly Entity[],
|
||||||
|
predicate: (entity: Entity, index: number) => boolean
|
||||||
|
): Entity | undefined {
|
||||||
|
for (let i = 0; i < entities.length; i++) {
|
||||||
|
if (predicate(entities[i], i)) {
|
||||||
|
return entities[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否存在满足条件的实体
|
||||||
|
*
|
||||||
|
* @param entities 实体列表
|
||||||
|
* @param predicate 检查条件
|
||||||
|
* @returns 是否存在满足条件的实体
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* protected process(entities: readonly Entity[]): void {
|
||||||
|
* const hasLowHealth = this.someEntity(entities, (entity) => {
|
||||||
|
* const health = this.requireComponent(entity, Health);
|
||||||
|
* return health.value < 20;
|
||||||
|
* });
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
protected someEntity(
|
||||||
|
entities: readonly Entity[],
|
||||||
|
predicate: (entity: Entity, index: number) => boolean
|
||||||
|
): boolean {
|
||||||
|
for (let i = 0; i < entities.length; i++) {
|
||||||
|
if (predicate(entities[i], i)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否所有实体都满足条件
|
||||||
|
*
|
||||||
|
* @param entities 实体列表
|
||||||
|
* @param predicate 检查条件
|
||||||
|
* @returns 是否所有实体都满足条件
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* protected process(entities: readonly Entity[]): void {
|
||||||
|
* const allHealthy = this.everyEntity(entities, (entity) => {
|
||||||
|
* const health = this.requireComponent(entity, Health);
|
||||||
|
* return health.value > 50;
|
||||||
|
* });
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
protected everyEntity(
|
||||||
|
entities: readonly Entity[],
|
||||||
|
predicate: (entity: Entity, index: number) => boolean
|
||||||
|
): boolean {
|
||||||
|
for (let i = 0; i < entities.length; i++) {
|
||||||
|
if (!predicate(entities[i], i)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
307
packages/core/src/ECS/TypedEntity.ts
Normal file
307
packages/core/src/ECS/TypedEntity.ts
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
/**
|
||||||
|
* Entity类型安全工具函数
|
||||||
|
*
|
||||||
|
* 提供类型安全的组件操作工具函数,无需修改Entity类
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Entity } from './Entity';
|
||||||
|
import type { Component } from './Component';
|
||||||
|
import type { ComponentType } from './Core/ComponentStorage';
|
||||||
|
import type { ComponentConstructor, ComponentInstance } from '../Types/TypeHelpers';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取组件,如果不存在则抛出错误
|
||||||
|
*
|
||||||
|
* @param entity - 实体实例
|
||||||
|
* @param componentType - 组件类型构造函数
|
||||||
|
* @returns 组件实例(保证非空)
|
||||||
|
* @throws {Error} 如果组件不存在
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* const position = requireComponent(entity, Position);
|
||||||
|
* position.x += 10;
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function requireComponent<T extends ComponentConstructor>(
|
||||||
|
entity: Entity,
|
||||||
|
componentType: T
|
||||||
|
): ComponentInstance<T> {
|
||||||
|
const component = entity.getComponent(componentType as unknown as ComponentType<ComponentInstance<T>>);
|
||||||
|
if (!component) {
|
||||||
|
throw new Error(
|
||||||
|
`Component ${componentType.name} not found on entity ${entity.name} (id: ${entity.id})`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 尝试获取组件
|
||||||
|
*
|
||||||
|
* @param entity - 实体实例
|
||||||
|
* @param componentType - 组件类型构造函数
|
||||||
|
* @returns 组件实例或undefined
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* const health = tryGetComponent(entity, Health);
|
||||||
|
* if (health) {
|
||||||
|
* health.value -= 10;
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function tryGetComponent<T extends ComponentConstructor>(
|
||||||
|
entity: Entity,
|
||||||
|
componentType: T
|
||||||
|
): ComponentInstance<T> | undefined {
|
||||||
|
const component = entity.getComponent(componentType as unknown as ComponentType<ComponentInstance<T>>);
|
||||||
|
return component !== null ? component : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量获取组件
|
||||||
|
*
|
||||||
|
* @param entity - 实体实例
|
||||||
|
* @param types - 组件类型构造函数数组
|
||||||
|
* @returns 组件实例元组,每个元素可能为null
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* const [pos, vel, health] = getComponents(entity, Position, Velocity, Health);
|
||||||
|
* if (pos && vel && health) {
|
||||||
|
* pos.x += vel.dx;
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function getComponents<T extends readonly ComponentConstructor[]>(
|
||||||
|
entity: Entity,
|
||||||
|
...types: T
|
||||||
|
): { [K in keyof T]: ComponentInstance<T[K]> | null } {
|
||||||
|
return types.map((type) =>
|
||||||
|
entity.getComponent(type as unknown as ComponentType<ComponentInstance<typeof type>>)
|
||||||
|
) as { [K in keyof T]: ComponentInstance<T[K]> | null };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查实体是否拥有所有指定的组件
|
||||||
|
*
|
||||||
|
* @param entity - 实体实例
|
||||||
|
* @param types - 组件类型构造函数数组
|
||||||
|
* @returns 如果拥有所有组件返回true,否则返回false
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* if (hasComponents(entity, Position, Velocity)) {
|
||||||
|
* const pos = entity.getComponent(Position)!;
|
||||||
|
* const vel = entity.getComponent(Velocity)!;
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function hasComponents(entity: Entity, ...types: ComponentConstructor[]): boolean {
|
||||||
|
return types.every((type) => entity.hasComponent(type as unknown as ComponentType));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查实体是否拥有至少一个指定的组件
|
||||||
|
*
|
||||||
|
* @param entity - 实体实例
|
||||||
|
* @param types - 组件类型构造函数数组
|
||||||
|
* @returns 如果拥有任意一个组件返回true,否则返回false
|
||||||
|
*/
|
||||||
|
export function hasAnyComponent(entity: Entity, ...types: ComponentConstructor[]): boolean {
|
||||||
|
return types.some((type) => entity.hasComponent(type as unknown as ComponentType));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加组件并立即配置
|
||||||
|
*
|
||||||
|
* @param entity - 实体实例
|
||||||
|
* @param component - 组件实例
|
||||||
|
* @param configure - 配置回调函数
|
||||||
|
* @returns 实体实例(支持链式调用)
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* addAndConfigure(entity, new Health(), health => {
|
||||||
|
* health.maxValue = 100;
|
||||||
|
* health.value = 50;
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function addAndConfigure<T extends Component>(
|
||||||
|
entity: Entity,
|
||||||
|
component: T,
|
||||||
|
configure: (component: T) => void
|
||||||
|
): Entity {
|
||||||
|
entity.addComponent(component);
|
||||||
|
configure(component);
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取或添加组件
|
||||||
|
*
|
||||||
|
* 如果组件已存在则返回现有组件,否则通过工厂函数创建并添加
|
||||||
|
*
|
||||||
|
* @param entity - 实体实例
|
||||||
|
* @param componentType - 组件类型构造函数
|
||||||
|
* @param factory - 组件工厂函数(仅在组件不存在时调用)
|
||||||
|
* @returns 组件实例
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* const health = getOrAddComponent(entity, Health, () => new Health(100));
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function getOrAddComponent<T extends ComponentConstructor>(
|
||||||
|
entity: Entity,
|
||||||
|
componentType: T,
|
||||||
|
factory: () => ComponentInstance<T>
|
||||||
|
): ComponentInstance<T> {
|
||||||
|
let component = entity.getComponent(componentType as unknown as ComponentType<ComponentInstance<T>>);
|
||||||
|
if (!component) {
|
||||||
|
component = factory();
|
||||||
|
entity.addComponent(component);
|
||||||
|
}
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新组件的部分字段
|
||||||
|
*
|
||||||
|
* @param entity - 实体实例
|
||||||
|
* @param componentType - 组件类型构造函数
|
||||||
|
* @param data - 要更新的部分数据
|
||||||
|
* @returns 如果更新成功返回true,组件不存在返回false
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* updateComponent(entity, Position, { x: 100 });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function updateComponent<T extends ComponentConstructor>(
|
||||||
|
entity: Entity,
|
||||||
|
componentType: T,
|
||||||
|
data: Partial<ComponentInstance<T>>
|
||||||
|
): boolean {
|
||||||
|
const component = entity.getComponent(componentType as unknown as ComponentType<ComponentInstance<T>>);
|
||||||
|
if (!component) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(component, data);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 类型安全的实体构建器
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```typescript
|
||||||
|
* const player = buildEntity(scene.createEntity("Player"))
|
||||||
|
* .with(new Position(100, 100))
|
||||||
|
* .with(new Velocity(0, 0))
|
||||||
|
* .withTag(1)
|
||||||
|
* .build();
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export class TypedEntityBuilder {
|
||||||
|
private _entity: Entity;
|
||||||
|
|
||||||
|
constructor(entity: Entity) {
|
||||||
|
this._entity = entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加组件
|
||||||
|
*/
|
||||||
|
with<T extends Component>(component: T): this {
|
||||||
|
this._entity.addComponent(component);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加并配置组件
|
||||||
|
*/
|
||||||
|
withConfigured<T extends Component>(
|
||||||
|
component: T,
|
||||||
|
configure: (component: T) => void
|
||||||
|
): this {
|
||||||
|
this._entity.addComponent(component);
|
||||||
|
configure(component);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置标签
|
||||||
|
*/
|
||||||
|
withTag(tag: number): this {
|
||||||
|
this._entity.tag = tag;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置名称
|
||||||
|
*/
|
||||||
|
withName(name: string): this {
|
||||||
|
this._entity.name = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置激活状态
|
||||||
|
*/
|
||||||
|
withActive(active: boolean): this {
|
||||||
|
this._entity.active = active;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置启用状态
|
||||||
|
*/
|
||||||
|
withEnabled(enabled: boolean): this {
|
||||||
|
this._entity.enabled = enabled;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置更新顺序
|
||||||
|
*/
|
||||||
|
withUpdateOrder(order: number): this {
|
||||||
|
this._entity.updateOrder = order;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加子实体
|
||||||
|
*/
|
||||||
|
withChild(child: Entity): this {
|
||||||
|
this._entity.addChild(child);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 完成构建
|
||||||
|
*/
|
||||||
|
build(): Entity {
|
||||||
|
return this._entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取正在构建的实体
|
||||||
|
*/
|
||||||
|
get entity(): Entity {
|
||||||
|
return this._entity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建类型安全的实体构建器
|
||||||
|
*
|
||||||
|
* @param entity - 要包装的实体
|
||||||
|
* @returns 实体构建器
|
||||||
|
*/
|
||||||
|
export function buildEntity(entity: Entity): TypedEntityBuilder {
|
||||||
|
return new TypedEntityBuilder(entity);
|
||||||
|
}
|
||||||
295
packages/core/src/Types/TypeHelpers.ts
Normal file
295
packages/core/src/Types/TypeHelpers.ts
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
/**
|
||||||
|
* TypeScript类型工具集
|
||||||
|
*
|
||||||
|
* 提供高级类型推断和类型安全的工具类型
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { IComponent } from './index';
|
||||||
|
import { Component } from '../ECS/Component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组件类型提取器
|
||||||
|
* 从组件构造函数中提取实例类型
|
||||||
|
*/
|
||||||
|
export type ComponentInstance<T> = T extends new (...args: any[]) => infer R ? R : never;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组件构造函数类型
|
||||||
|
*
|
||||||
|
* 与 ComponentType 保持一致,避免类型转换
|
||||||
|
*/
|
||||||
|
export type ComponentConstructor<T extends IComponent = IComponent> = new (...args: any[]) => T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组件类型的通用约束
|
||||||
|
*
|
||||||
|
* 用于确保类型参数是有效的组件构造函数
|
||||||
|
*/
|
||||||
|
export type AnyComponentConstructor = ComponentConstructor<any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 多组件类型提取
|
||||||
|
* 从组件构造函数数组中提取所有实例类型的联合
|
||||||
|
*/
|
||||||
|
export type ExtractComponents<T extends readonly ComponentConstructor[]> = {
|
||||||
|
[K in keyof T]: ComponentInstance<T[K]>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组件类型映射
|
||||||
|
* 将组件构造函数数组转换为实例类型的元组
|
||||||
|
*/
|
||||||
|
export type ComponentTypeMap<T extends readonly ComponentConstructor[]> = {
|
||||||
|
[K in keyof T]: T[K] extends ComponentConstructor<infer C> ? C : never;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实体with组件的类型
|
||||||
|
* 表示一个实体确定拥有某些组件
|
||||||
|
*/
|
||||||
|
export interface EntityWithComponents<T extends readonly ComponentConstructor[]> {
|
||||||
|
readonly id: number;
|
||||||
|
readonly name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 类型安全的组件获取
|
||||||
|
* 确保返回非空的组件实例
|
||||||
|
*/
|
||||||
|
getComponent<C extends ComponentConstructor>(componentType: C): ComponentInstance<C>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否拥有组件
|
||||||
|
*/
|
||||||
|
hasComponent<C extends ComponentConstructor>(componentType: C): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有组件
|
||||||
|
*/
|
||||||
|
readonly components: ComponentTypeMap<T>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询结果类型
|
||||||
|
* 根据查询条件推断实体必定拥有的组件
|
||||||
|
*/
|
||||||
|
export type QueryResult<
|
||||||
|
All extends readonly ComponentConstructor[] = [],
|
||||||
|
Any extends readonly ComponentConstructor[] = [],
|
||||||
|
None extends readonly ComponentConstructor[] = []
|
||||||
|
> = {
|
||||||
|
/**
|
||||||
|
* 实体列表,确保拥有All中的所有组件
|
||||||
|
*/
|
||||||
|
readonly entities: ReadonlyArray<EntityWithComponents<All>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实体数量
|
||||||
|
*/
|
||||||
|
readonly length: number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 遍历实体
|
||||||
|
*/
|
||||||
|
forEach(callback: (entity: EntityWithComponents<All>, index: number) => void): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 映射转换
|
||||||
|
*/
|
||||||
|
map<R>(callback: (entity: EntityWithComponents<All>, index: number) => R): R[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 过滤实体
|
||||||
|
*/
|
||||||
|
filter(predicate: (entity: EntityWithComponents<All>, index: number) => boolean): QueryResult<All, Any, None>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* System处理的实体类型
|
||||||
|
* 根据Matcher推断System处理的实体类型
|
||||||
|
*/
|
||||||
|
export type SystemEntityType<M> = M extends {
|
||||||
|
getCondition(): {
|
||||||
|
all: infer All extends readonly ComponentConstructor[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
? EntityWithComponents<All>
|
||||||
|
: never;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组件字段类型提取
|
||||||
|
* 提取组件中所有可序列化的字段
|
||||||
|
*/
|
||||||
|
export type SerializableFields<T> = {
|
||||||
|
[K in keyof T]: T[K] extends Function ? never : K;
|
||||||
|
}[keyof T];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 只读组件类型
|
||||||
|
* 将组件的所有字段转为只读
|
||||||
|
*/
|
||||||
|
export type ReadonlyComponent<T extends IComponent> = {
|
||||||
|
readonly [K in keyof T]: T[K];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 部分组件类型
|
||||||
|
* 用于组件更新操作
|
||||||
|
*/
|
||||||
|
export type PartialComponent<T extends IComponent> = {
|
||||||
|
[K in SerializableFields<T>]?: T[K];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组件类型约束
|
||||||
|
* 确保类型参数是有效的组件
|
||||||
|
*/
|
||||||
|
export type ValidComponent<T> = T extends Component ? T : never;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组件数组约束
|
||||||
|
* 确保数组中的所有元素都是组件构造函数
|
||||||
|
*/
|
||||||
|
export type ValidComponentArray<T extends readonly any[]> = T extends readonly ComponentConstructor[]
|
||||||
|
? T
|
||||||
|
: never;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件处理器类型
|
||||||
|
* 提供类型安全的事件处理
|
||||||
|
*/
|
||||||
|
export type TypedEventHandler<T> = (data: T) => void | Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统生命周期钩子类型
|
||||||
|
*/
|
||||||
|
export interface SystemLifecycleHooks<T extends readonly ComponentConstructor[]> {
|
||||||
|
/**
|
||||||
|
* 实体添加到系统时调用
|
||||||
|
*/
|
||||||
|
onAdded?: (entity: EntityWithComponents<T>) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实体从系统移除时调用
|
||||||
|
*/
|
||||||
|
onRemoved?: (entity: EntityWithComponents<T>) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统初始化时调用
|
||||||
|
*/
|
||||||
|
onInitialize?: () => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统销毁时调用
|
||||||
|
*/
|
||||||
|
onDestroy?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fluent API构建器类型
|
||||||
|
*/
|
||||||
|
export interface TypeSafeBuilder<T> {
|
||||||
|
/**
|
||||||
|
* 完成构建
|
||||||
|
*/
|
||||||
|
build(): T;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组件池类型
|
||||||
|
*/
|
||||||
|
export interface ComponentPool<T extends IComponent> {
|
||||||
|
/**
|
||||||
|
* 从池中获取组件实例
|
||||||
|
*/
|
||||||
|
obtain(): T;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 归还组件到池中
|
||||||
|
*/
|
||||||
|
free(component: T): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空池
|
||||||
|
*/
|
||||||
|
clear(): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 池中可用对象数量
|
||||||
|
*/
|
||||||
|
readonly available: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实体查询条件类型
|
||||||
|
*/
|
||||||
|
export interface TypedQueryCondition<
|
||||||
|
All extends readonly ComponentConstructor[] = [],
|
||||||
|
Any extends readonly ComponentConstructor[] = [],
|
||||||
|
None extends readonly ComponentConstructor[] = []
|
||||||
|
> {
|
||||||
|
all: All;
|
||||||
|
any: Any;
|
||||||
|
none: None;
|
||||||
|
tag?: number;
|
||||||
|
name?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组件类型守卫
|
||||||
|
*/
|
||||||
|
export function isComponentType<T extends IComponent>(
|
||||||
|
value: any
|
||||||
|
): value is ComponentConstructor<T> {
|
||||||
|
return typeof value === 'function' && value.prototype instanceof Component;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 类型安全的组件数组守卫
|
||||||
|
*/
|
||||||
|
export function isComponentArray(
|
||||||
|
value: any[]
|
||||||
|
): value is ComponentConstructor[] {
|
||||||
|
return value.every(isComponentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提取组件类型名称(编译时)
|
||||||
|
*/
|
||||||
|
export type ComponentTypeName<T extends ComponentConstructor> = T extends {
|
||||||
|
prototype: { constructor: { name: infer N } };
|
||||||
|
}
|
||||||
|
? N
|
||||||
|
: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 多组件类型名称联合
|
||||||
|
*/
|
||||||
|
export type ComponentTypeNames<T extends readonly ComponentConstructor[]> = {
|
||||||
|
[K in keyof T]: ComponentTypeName<T[K]>;
|
||||||
|
}[number];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 深度只读类型
|
||||||
|
*/
|
||||||
|
export type DeepReadonly<T> = {
|
||||||
|
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 深度可选类型
|
||||||
|
*/
|
||||||
|
export type DeepPartial<T> = {
|
||||||
|
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排除方法的类型
|
||||||
|
*/
|
||||||
|
export type DataOnly<T> = {
|
||||||
|
[K in keyof T as T[K] extends Function ? never : K]: T[K];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 可序列化的组件数据
|
||||||
|
*/
|
||||||
|
export type SerializableComponent<T extends IComponent> = DeepPartial<DataOnly<T>>;
|
||||||
@@ -2,6 +2,9 @@
|
|||||||
* 框架核心类型定义
|
* 框架核心类型定义
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// 导出TypeScript类型增强工具
|
||||||
|
export * from './TypeHelpers';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 组件接口
|
* 组件接口
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ export type { ILogger, LoggerConfig } from './Utils/Logger';
|
|||||||
// ECS核心组件
|
// ECS核心组件
|
||||||
export * from './ECS';
|
export * from './ECS';
|
||||||
|
|
||||||
|
// TypeScript类型增强API
|
||||||
|
export * from './ECS/TypedEntity';
|
||||||
|
export * from './ECS/Systems/TypedEntitySystem';
|
||||||
|
export * from './ECS/Core/Query/TypedQuery';
|
||||||
|
|
||||||
// 事件系统
|
// 事件系统
|
||||||
export { ECSEventType, EventPriority, EVENT_TYPES, EventTypeValidator } from './ECS/CoreEvents';
|
export { ECSEventType, EventPriority, EVENT_TYPES, EventTypeValidator } from './ECS/CoreEvents';
|
||||||
|
|
||||||
|
|||||||
145
packages/core/tests/SceneQuery.test.ts
Normal file
145
packages/core/tests/SceneQuery.test.ts
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
/**
|
||||||
|
* Scene查询方法测试
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Component } from '../src/ECS/Component';
|
||||||
|
import { Entity } from '../src/ECS/Entity';
|
||||||
|
import { Scene } from '../src/ECS/Scene';
|
||||||
|
import { Core } from '../src/Core';
|
||||||
|
import { ECSComponent } from '../src/ECS/Decorators';
|
||||||
|
import { EntitySystem } from '../src/ECS/Systems/EntitySystem';
|
||||||
|
|
||||||
|
@ECSComponent('Position')
|
||||||
|
class Position extends Component {
|
||||||
|
constructor(public x: number = 0, public y: number = 0) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ECSComponent('Velocity')
|
||||||
|
class Velocity extends Component {
|
||||||
|
constructor(public dx: number = 0, public dy: number = 0) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ECSComponent('Disabled')
|
||||||
|
class Disabled extends Component {}
|
||||||
|
|
||||||
|
describe('Scene查询方法', () => {
|
||||||
|
let scene: Scene;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
Core.create({ debug: false, enableEntitySystems: true });
|
||||||
|
scene = new Scene();
|
||||||
|
scene.initialize();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
scene.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('基础查询方法', () => {
|
||||||
|
test('queryAll 查询拥有所有组件的实体', () => {
|
||||||
|
const e1 = scene.createEntity('E1');
|
||||||
|
e1.addComponent(new Position(10, 20));
|
||||||
|
e1.addComponent(new Velocity(1, 2));
|
||||||
|
|
||||||
|
const e2 = scene.createEntity('E2');
|
||||||
|
e2.addComponent(new Position(30, 40));
|
||||||
|
|
||||||
|
const result = scene.queryAll(Position, Velocity);
|
||||||
|
|
||||||
|
expect(result.entities).toHaveLength(1);
|
||||||
|
expect(result.entities[0]).toBe(e1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('queryAny 查询拥有任意组件的实体', () => {
|
||||||
|
const e1 = scene.createEntity('E1');
|
||||||
|
e1.addComponent(new Position(10, 20));
|
||||||
|
|
||||||
|
const e2 = scene.createEntity('E2');
|
||||||
|
e2.addComponent(new Velocity(1, 2));
|
||||||
|
|
||||||
|
const e3 = scene.createEntity('E3');
|
||||||
|
e3.addComponent(new Disabled());
|
||||||
|
|
||||||
|
const result = scene.queryAny(Position, Velocity);
|
||||||
|
|
||||||
|
expect(result.entities).toHaveLength(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('queryNone 查询不包含指定组件的实体', () => {
|
||||||
|
const e1 = scene.createEntity('E1');
|
||||||
|
e1.addComponent(new Position(10, 20));
|
||||||
|
|
||||||
|
const e2 = scene.createEntity('E2');
|
||||||
|
e2.addComponent(new Position(30, 40));
|
||||||
|
e2.addComponent(new Disabled());
|
||||||
|
|
||||||
|
const result = scene.queryNone(Disabled);
|
||||||
|
|
||||||
|
expect(result.entities).toHaveLength(1);
|
||||||
|
expect(result.entities[0]).toBe(e1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('TypedQueryBuilder', () => {
|
||||||
|
test('scene.query() 创建类型安全的查询构建器', () => {
|
||||||
|
const e1 = scene.createEntity('E1');
|
||||||
|
e1.addComponent(new Position(10, 20));
|
||||||
|
e1.addComponent(new Velocity(1, 2));
|
||||||
|
|
||||||
|
const e2 = scene.createEntity('E2');
|
||||||
|
e2.addComponent(new Position(30, 40));
|
||||||
|
e2.addComponent(new Velocity(3, 4));
|
||||||
|
e2.addComponent(new Disabled());
|
||||||
|
|
||||||
|
// 构建查询
|
||||||
|
const query = scene.query()
|
||||||
|
.withAll(Position, Velocity)
|
||||||
|
.withNone(Disabled);
|
||||||
|
|
||||||
|
const matcher = query.buildMatcher();
|
||||||
|
|
||||||
|
// 创建System使用这个matcher
|
||||||
|
class TestSystem extends EntitySystem {
|
||||||
|
public processedCount = 0;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(matcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override process(entities: readonly Entity[]): void {
|
||||||
|
this.processedCount = entities.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const system = new TestSystem();
|
||||||
|
scene.addSystem(system);
|
||||||
|
scene.update();
|
||||||
|
|
||||||
|
// 应该只处理e1(e2被Disabled排除)
|
||||||
|
expect(system.processedCount).toBe(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('TypedQueryBuilder 支持复杂查询', () => {
|
||||||
|
const e1 = scene.createEntity('E1');
|
||||||
|
e1.addComponent(new Position(10, 20));
|
||||||
|
e1.tag = 100;
|
||||||
|
|
||||||
|
const e2 = scene.createEntity('E2');
|
||||||
|
e2.addComponent(new Position(30, 40));
|
||||||
|
e2.tag = 200;
|
||||||
|
|
||||||
|
const query = scene.query()
|
||||||
|
.withAll(Position)
|
||||||
|
.withTag(100);
|
||||||
|
|
||||||
|
const condition = query.getCondition();
|
||||||
|
|
||||||
|
expect(condition.all).toContain(Position as any);
|
||||||
|
expect(condition.tag).toBe(100);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
205
packages/core/tests/TypeInference.test.ts
Normal file
205
packages/core/tests/TypeInference.test.ts
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
/**
|
||||||
|
* TypeScript类型推断测试
|
||||||
|
*
|
||||||
|
* 验证组件类型自动推断功能
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Component } from '../src/ECS/Component';
|
||||||
|
import { Entity } from '../src/ECS/Entity';
|
||||||
|
import { Scene } from '../src/ECS/Scene';
|
||||||
|
import { Core } from '../src/Core';
|
||||||
|
import { ECSComponent } from '../src/ECS/Decorators';
|
||||||
|
import { requireComponent, tryGetComponent, getComponents } from '../src/ECS/TypedEntity';
|
||||||
|
|
||||||
|
// 测试组件
|
||||||
|
@ECSComponent('Position')
|
||||||
|
class Position extends Component {
|
||||||
|
constructor(public x: number = 0, public y: number = 0) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ECSComponent('Velocity')
|
||||||
|
class Velocity extends Component {
|
||||||
|
constructor(public dx: number = 0, public dy: number = 0) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ECSComponent('Health')
|
||||||
|
class Health extends Component {
|
||||||
|
constructor(public value: number = 100, public maxValue: number = 100) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('TypeScript类型推断', () => {
|
||||||
|
let scene: Scene;
|
||||||
|
let entity: Entity;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
Core.create({ debug: false, enableEntitySystems: true });
|
||||||
|
scene = new Scene();
|
||||||
|
scene.initialize();
|
||||||
|
entity = scene.createEntity('TestEntity');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
scene.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Entity.getComponent 类型推断', () => {
|
||||||
|
test('getComponent 应该自动推断正确的返回类型', () => {
|
||||||
|
entity.addComponent(new Position(100, 200));
|
||||||
|
|
||||||
|
// 类型推断为 Position | null
|
||||||
|
const position = entity.getComponent(Position);
|
||||||
|
|
||||||
|
// TypeScript应该知道position可能为null
|
||||||
|
expect(position).not.toBeNull();
|
||||||
|
|
||||||
|
// 在null检查后,TypeScript应该知道position是Position类型
|
||||||
|
if (position) {
|
||||||
|
expect(position.x).toBe(100);
|
||||||
|
expect(position.y).toBe(200);
|
||||||
|
|
||||||
|
// 这些操作应该有完整的类型提示
|
||||||
|
position.x += 10;
|
||||||
|
position.y += 20;
|
||||||
|
|
||||||
|
expect(position.x).toBe(110);
|
||||||
|
expect(position.y).toBe(220);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getComponent 返回null时类型安全', () => {
|
||||||
|
// 实体没有Velocity组件
|
||||||
|
const velocity = entity.getComponent(Velocity);
|
||||||
|
|
||||||
|
// 应该返回null
|
||||||
|
expect(velocity).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('多个不同类型组件的类型推断', () => {
|
||||||
|
entity.addComponent(new Position(10, 20));
|
||||||
|
entity.addComponent(new Velocity(1, 2));
|
||||||
|
entity.addComponent(new Health(100));
|
||||||
|
|
||||||
|
const pos = entity.getComponent(Position);
|
||||||
|
const vel = entity.getComponent(Velocity);
|
||||||
|
const health = entity.getComponent(Health);
|
||||||
|
|
||||||
|
// 所有组件都应该被正确推断
|
||||||
|
if (pos && vel && health) {
|
||||||
|
// Position类型的字段
|
||||||
|
pos.x = 50;
|
||||||
|
pos.y = 60;
|
||||||
|
|
||||||
|
// Velocity类型的字段
|
||||||
|
vel.dx = 5;
|
||||||
|
vel.dy = 10;
|
||||||
|
|
||||||
|
// Health类型的字段
|
||||||
|
health.value = 80;
|
||||||
|
health.maxValue = 150;
|
||||||
|
|
||||||
|
expect(pos.x).toBe(50);
|
||||||
|
expect(vel.dx).toBe(5);
|
||||||
|
expect(health.value).toBe(80);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Entity.createComponent 类型推断', () => {
|
||||||
|
test('createComponent 应该自动推断返回类型', () => {
|
||||||
|
// 应该推断为Position类型(非null)
|
||||||
|
const position = entity.createComponent(Position, 100, 200);
|
||||||
|
|
||||||
|
expect(position).toBeInstanceOf(Position);
|
||||||
|
expect(position.x).toBe(100);
|
||||||
|
expect(position.y).toBe(200);
|
||||||
|
|
||||||
|
// 应该有完整的类型提示
|
||||||
|
position.x = 300;
|
||||||
|
expect(position.x).toBe(300);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Entity.hasComponent 类型守卫', () => {
|
||||||
|
test('hasComponent 可以用作类型守卫', () => {
|
||||||
|
entity.addComponent(new Position(10, 20));
|
||||||
|
|
||||||
|
if (entity.hasComponent(Position)) {
|
||||||
|
// 在这个作用域内,我们知道组件存在
|
||||||
|
const pos = entity.getComponent(Position)!;
|
||||||
|
pos.x = 100;
|
||||||
|
expect(pos.x).toBe(100);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Entity.getOrCreateComponent 类型推断', () => {
|
||||||
|
test('getOrCreateComponent 应该自动推断返回类型', () => {
|
||||||
|
// 第一次调用:创建新组件
|
||||||
|
const position1 = entity.getOrCreateComponent(Position, 50, 60);
|
||||||
|
expect(position1.x).toBe(50);
|
||||||
|
expect(position1.y).toBe(60);
|
||||||
|
|
||||||
|
// 第二次调用:返回已存在的组件
|
||||||
|
const position2 = entity.getOrCreateComponent(Position, 100, 200);
|
||||||
|
|
||||||
|
// 应该是同一个组件
|
||||||
|
expect(position2).toBe(position1);
|
||||||
|
expect(position2.x).toBe(50); // 值未改变
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('TypedEntity工具函数类型推断', () => {
|
||||||
|
test('requireComponent 返回非空类型', () => {
|
||||||
|
entity.addComponent(new Position(100, 200));
|
||||||
|
|
||||||
|
// requireComponent 返回非null类型
|
||||||
|
const position = requireComponent(entity, Position);
|
||||||
|
|
||||||
|
// 不需要null检查
|
||||||
|
expect(position.x).toBe(100);
|
||||||
|
position.x = 300;
|
||||||
|
expect(position.x).toBe(300);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('tryGetComponent 返回可选类型', () => {
|
||||||
|
entity.addComponent(new Position(50, 50));
|
||||||
|
|
||||||
|
const position = tryGetComponent(entity, Position);
|
||||||
|
|
||||||
|
// 应该返回组件
|
||||||
|
expect(position).toBeDefined();
|
||||||
|
if (position) {
|
||||||
|
expect(position.x).toBe(50);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 不存在的组件返回undefined
|
||||||
|
const velocity = tryGetComponent(entity, Velocity);
|
||||||
|
expect(velocity).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('getComponents 批量获取组件', () => {
|
||||||
|
entity.addComponent(new Position(10, 20));
|
||||||
|
entity.addComponent(new Velocity(1, 2));
|
||||||
|
entity.addComponent(new Health(100));
|
||||||
|
|
||||||
|
const [pos, vel, health] = getComponents(entity, Position, Velocity, Health);
|
||||||
|
|
||||||
|
// 应该推断为数组类型
|
||||||
|
expect(pos).not.toBeNull();
|
||||||
|
expect(vel).not.toBeNull();
|
||||||
|
expect(health).not.toBeNull();
|
||||||
|
|
||||||
|
if (pos && vel && health) {
|
||||||
|
expect(pos.x).toBe(10);
|
||||||
|
expect(vel.dx).toBe(1);
|
||||||
|
expect(health.value).toBe(100);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user