【发布时间】:2021-09-26 11:14:18
【问题描述】:
我正在尝试在 typescript 中设计一个实体组件系统。我的目标是创建一个可变参数泛型抽象 System 类,它将组件类作为泛型参数。
这个想法是派生的System 类可以用它们使用的组件来声明。然后,他们可以使用此信息(组件类对象)从所有实体列表中提取他们操作的实体子集。该子集是具有所需组件的实体集。
对于上下文,我首先包含了组件和实体代码:
interface IComponent {
owner: Entity | null;
}
type ComponentClass<C extends IComponent> = new (args: unknown[]) => C;
abstract class Entity {
public readonly name: string;
/** The pixijs scene node (pixijs display object). */
private _sceneNode: unknown;
protected _components: IComponent[] = [];
constructor(name: string) {
this.name = name;
}
public get components(): IComponent[] {
return this._components;
}
public addComponent(component: IComponent): void {
this._components.push(component);
component.owner = this;
}
public getComponent<C extends IComponent>(componentClass: ComponentClass<C>): C {
for (const component of this._components) {
if (component instanceof componentClass) {
return component as C;
}
}
throw new Error(`component '${componentClass.name}' missing from entity ${this.constructor.name}`);
}
public removeComponent<C extends IComponent>(componentClass: ComponentClass<C>): void {
const removeList: number[] = [];
this._components.forEach((component, index) => {
if (component instanceof componentClass) {
removeList.push(index);
}
});
removeList.forEach(index => {
this._components.splice(index, 1);
});
}
public hasComponent<C extends IComponent>(componentClass: ComponentClass<C>): boolean {
return this._components.some(component => {
return component instanceof componentClass;
});
}
}
interface ISystem {
onSpawnEntity(entity: Entity): void;
onDestroyEntity(entity: Entity): void;
pullEntities(entities: Entity[]): void;
update(dt_s: number) : void;
}
我试图使它成为一个可变参数泛型,可以采用任意数量的组件
类,每个类都扩展IComponent。目前,它只需要 2 个,这证明了我想要实现的目标:
abstract class System<C0 extends IComponent, C1 extends IComponent> implements ISystem {
protected _targets: [ComponentClass<C0>, ComponentClass<C1>];
protected _subjects: Entity[] = [];
constructor(targets: [ComponentClass<C0>, ComponentClass<C1>]) {
this._targets = targets;
}
public pullEntities(entities: Entity[]): void {
entities.forEach(entity => {
if(this.isEntitySubject(entity)) {
this._subjects.push(entity);
}
});
}
public onSpawnEntity(entity: Entity): void {
if(this.isEntitySubject(entity)) {
this._subjects.push(entity);
}
}
public onDestroyEntity(entity: Entity): void {
if(this.isEntitySubject(entity)) {
}
}
public update(dt_s: number) : void {
this._subjects.forEach(entity => {
const c0 = entity.getComponent(this._targets[0]);
const c1 = entity.getComponent(this._targets[1]);
this.updateEntity(c0, c1);
})
}
private isEntitySubject(entity: Entity): boolean {
return entity.hasComponent(this._targets[0]) &&
entity.hasComponent(this._targets[1]);
}
// the idea is that this is the only function systems will have to implement themselves,
// ideally want the args to be a variadic array of the component instances which the
// system uses.
protected updateEntity(c0: C0, c1: C1) {}
}
abstract class World
{
protected _systems: ISystem[] = [];
protected _entities: Entity[] = [];
public feedEntities(): void {
this._systems.forEach(system => {
system.pullEntities(this._entities);
});
}
public updateSystems(): void {
this._systems.forEach(system => {
system.update(20);
});
}
}
我还包含了示例用法以提供更多上下文:
////////////////////////////////////////////////////////////////////////////////
// EXAMPLE USAGE
////////////////////////////////////////////////////////////////////////////////
class PhysicsComponent implements IComponent {
public owner: Entity | null;
public x: number;
public y: number;
public mass: number;
}
class CollisionComponent implements IComponent {
public owner: Entity | null;
public bounds: {x: number, y: number, width: number, height: number};
}
// creating a new system, defining the component classes it takes.
class PhysicsSystem extends System<PhysicsComponent, CollisionComponent> {
protected updateEntity(physics: PhysicsComponent, collision: CollisionComponent) {
physics.x += 1;
physics.y += 1;
console.log(`entity: ${physics.owner.name} has position: {x: ${physics.x}, y: ${physics.y}}`);
}
}
class Person extends Entity {
constructor(name: string) {
super(name);
const physics = new PhysicsComponent();
physics.x = 20;
physics.y = 40;
this.addComponent(physics);
const collision = new CollisionComponent();
collision.bounds = {
x: 0, y: 0, width: 100, height: 100
};
this.addComponent(collision);
}
}
class GameWorld extends World {
constructor() {
super();
this._systems.push(new PhysicsSystem([PhysicsComponent, CollisionComponent]));
const e0 = new Person("jim");
const e1 = new Person("steve");
const e2 = new Person("sally");
this._entities.push(e0, e1, e2);
this.feedEntities();
}
}
const world = new GameWorld();
setInterval(() => {
world.updateSystems();
}, 300);
我正在努力实现的目标是否可能?
【问题讨论】:
标签: typescript generics amazon-ecs entity-component-system