UNPKG

vertecs

Version:

A typescript entity-component-system framework

333 lines (284 loc) 8.93 kB
import { v4 as uuidv4 } from "uuid"; import Component from "./Component"; import EcsManager from "./EcsManager"; import type { ComponentClass } from "./Component"; export interface EntityOptions { id?: string; name?: string; components?: Component[]; children?: Entity[]; ecsManager?: EcsManager; parent?: Entity; } /** * An entity is a general purpose object which contains components */ export default class Entity { #ecsManager?: EcsManager; readonly #id: string; readonly #components: Component[]; #parent?: Entity; readonly #children: Entity[]; #name?: string; #root: Entity; readonly #tags: string[]; public constructor(options?: EntityOptions) { this.#id = options?.id ?? uuidv4(); this.#children = []; this.#components = []; this.#ecsManager = options?.ecsManager; options?.parent?.addChild(this); this.#name = options?.name; this.#root = this; this.#tags = []; if (this.#ecsManager) { this.#ecsManager.addEntity(this); } options?.children?.forEach((child) => this.addChild(child)); // Add components one by one to trigger events options?.components?.forEach((component) => this.addComponent(component) ); } /** * Find an entity by it's id * @param ecsManager * @param id The entity id to find */ public static findById( ecsManager: EcsManager, id: string ): Entity | undefined { return ecsManager.entities.find((entity) => entity.id === id); } /** * Find an entity by a component * @param ecsManager * @param component The component class */ public static findByComponent( ecsManager: EcsManager, component: ComponentClass ): Entity | undefined { return ecsManager.entities.find((entity) => entity.getComponent(component) ); } /** * Find an entity by a component * @param ecsManager The ecs manager * @param component The component class */ public static findAllByComponent( ecsManager: EcsManager, component: ComponentClass ): Entity[] { return ecsManager.entities.filter((entity) => entity.getComponent(component) ); } /** * Find an entity by a tag * @param ecsManager * @param tag The tag */ public static findAllByTag(ecsManager: EcsManager, tag: string): Entity[] { return ecsManager.entities.filter((entity) => entity.tags.includes(tag) ); } /** * Return the first child found with the specified name * @param name The child name */ public findChildByName(name: string): Entity | undefined { for (let i = 0; i < this.children.length; i++) { const child = this.children[i]; if (child.name === name) { return child; } } for (let i = 0; i < this.children.length; i++) { const child = this.children[i]; const found = child.findChildByName(name); if (found) { return found; } } return undefined; } /** * Find the first entity in the entity hierarchy with the specified component * @param component */ public findWithComponent(component: ComponentClass): Entity | undefined { if (this.getComponent(component)) { return this; } return this.children.find((child) => child.findWithComponent(component) ); } /** * Return a component by its class * @param componentClass The component's class or subclass constructor */ public getComponent<T extends Component>( componentClass: ComponentClass<T> ): T | undefined { return this.components.find( (component) => component instanceof componentClass ) as T | undefined; } /** * Return the first component found in an entity hierarchy * @param componentConstructor The component's class or subclass constructor */ public findComponent<T extends Component>( componentConstructor: ComponentClass<T> ): T | undefined { return this.findWithComponent(componentConstructor)?.getComponent( componentConstructor ); } /** * Return all components present in the entity * @param filter */ public getComponents<T extends Component>( filter?: ComponentClass<T>[] ): (T | undefined)[] { if (!filter) { // Return all components when no filter is given return Array.from(this.#components.values()) as T[]; } return filter.map((filteredComponentClass) => this.getComponent(filteredComponentClass) ); } /** * Add a component to this entity * @param newComponent The component to add */ public addComponent(newComponent: Component) { if (!this.getComponent(newComponent.constructor as ComponentClass)) { newComponent.entity = this; this.components.push(newComponent); this.#ecsManager?.onComponentAddedToEntity(this, newComponent); newComponent.onAddedToEntity(this); this.#components.forEach((component) => { if (component !== newComponent) { component.onComponentAddedToAttachedEntity(component); } }); } } public addComponents(...newComponents: Component[]) { newComponents.forEach((component) => this.addComponent(component)); } /** * Add a child to this entity * @param entity The child */ public addChild(entity: Entity) { this.#children.push(entity); entity.parent = this; entity.root = this.root; if (!entity.ecsManager) { this.ecsManager?.addEntity(entity); } } /** * Add a tag to an entity * @param tag The tag to add */ public addTag(tag: string) { this.#tags.push(tag); } /** * Remove a components from this entity * @param componentClass The component's class to remove */ public removeComponent<T extends Component>( componentClass: ComponentClass<T> ): T | undefined { const component = this.getComponent(componentClass); if (component) { this.#components.splice(this.#components.indexOf(component), 1); this.#ecsManager?.onComponentRemovedFromEntity(this, component); component.onRemovedFromEntity(this); return component; } return undefined; } /** * Clone an entity's name, components, recursively */ public clone(id?: string): Entity { const clone = new Entity({ name: this.#name, components: Array.from(this.#components.values()).map((component) => component.clone() ), ecsManager: this.#ecsManager, id, }); this.children.forEach((child) => { clone.addChild(child.clone()); }); return clone; } /** * Destroy this entity, remove and destroy all added components */ public destroy(): void { this.children.forEach((child) => child.destroy()); for (let i = this.components.length - 1; i >= 0; i--) { this.removeComponent( this.components[i].constructor as ComponentClass ); } this.parent?.children.splice(this.parent.children.indexOf(this), 1); this.#ecsManager?.removeEntity(this); } public get ecsManager(): EcsManager | undefined { return this.#ecsManager; } public set ecsManager(value: EcsManager | undefined) { this.#ecsManager = value; } public get tags(): string[] { return this.#tags; } public get root(): Entity { return this.#root; } public set root(value: Entity) { this.#root = value; } public get components(): Component[] { return this.#components; } public get parent(): Entity | undefined { return this.#parent; } public set parent(entity: Entity | undefined) { this.components.forEach((component) => component.onEntityNewParent(entity) ); this.#parent = entity; this.#root = entity?.root ?? this; } public get name(): string | undefined { return this.#name; } public set name(value: string | undefined) { this.#name = value; } public get children(): Entity[] { return this.#children; } public get id(): string { return this.#id; } }