UNPKG

javascript-entity-component-system

Version:
481 lines (398 loc) 12.7 kB
export type Component = { name : string state : { [key: string]: any } onAttach?: Function } export type Processor = { name : string required: string[] update(entity: Entity, components: Component[], processor: Processor): void } export type Entity = { name : string components: Component[] processors: Processor[] } /** * The Entity Component System class. */ export class EntityComponentSystem { /** * All registered components * @defaultValue [] */ components: Component[] /** * All registered processors * @defaultValue [] */ processors: Processor[] /** * All registered entities * @defaultValue [] */ entities: Entity[] constructor() { this.components = [] this.processors = [] this.entities = [] } /** * Gets all registered processors. * @returns All registered processors in an array. */ getProcessors(): Processor[] { return this.processors } /** * Gets all components processors. * @returns All registered components in an array. */ getComponents(): Component[] { return this.components } /** * Gets all registered entities. * @returns All registered entities in an array. */ getEntities(): Entity[] { return this.entities } /** * Gets a registered processor by name. * @param name - Name of the processor * @returns A processor or throws an error. */ getProcessor(name: string): Processor { const hasProcessor = this.hasProcessor(name) if (!hasProcessor) { throw new Error(`getProcessor(): processor "${name}" not found.`) } return this.processors.find((processor) => processor.name === name) as Processor } /** * Gets a registered component by name. * @param name - Name of the component * @returns A component or throws an error. */ getComponent(name: string): Component { const hasComponent = this.hasComponent(name) if (!hasComponent) { throw new Error(`getComponent(): component "${name}" not found.`) } return this.components.find((component) => component.name === name) as Component } /** * Gets a registered entity by name. * @param name -Name of the entity * @returns A entity or throws an error. */ getEntity(name: string): Entity { const hasEntity = this.hasEntity(name) if (!hasEntity) { throw new Error(`getEntity(): entity "${name}" not found.`) } return this.entities.find((entity) => entity.name === name) as Entity } /** * Gets all registered entities that match the given name. * @param name -Name of the entity * @returns An array of entities or an empty array. */ getEntitiesByName(name: string): Entity[] { const entities = [] const length = this.entities.length for (let i = 0; i < length; i++) { const currentEntity = this.entities[i] if (currentEntity.name === name) { entities.push(currentEntity) } } return entities } getEntityComponents(entity: Entity, components: string[]): Component[] { const foundComponents = [] const length = entity.components.length for (let currentComponent of components) { for (let i = 0; i < length; i++) { const currentEntityComponent = entity.components[i] if (currentEntityComponent.name === currentComponent) { foundComponents.push(currentEntityComponent) } } } return foundComponents } /** * Checks if processor is registered by name. * @param name - Name of the processor * @returns true if found or false if not. */ hasProcessor(name: string): boolean { let found = false for (let processor of this.processors) { if (processor.name === name) { found = true } } return found } /** * Checks if component is registered. * @param name - Name of the component * @returns true if found or false if not. */ hasComponent(name: string): boolean { let found = false for (let component of this.components) { if (component.name === name) { found = true } } return found } /** * Checks if entity is registered. * @param name - Name of the entity * @returns true if found or false if not. */ hasEntity(name: string): boolean { let found = false for (let entity of this.entities) { if (entity.name === name) { found = true } } return found } /** * Composes a entity with given components. * @param name - Name of the entity * @param components - An array of component names * @param processors - An array of processor names * @returns The composed entity or throws an error. */ createEntity(name: string, components: string[], processors: string[]): Entity { const entityName = name const allComponents = [] const allProcessors = [] components.forEach(componentName => { const foundComponent = this.getComponent(componentName) const copy = JSON.parse(JSON.stringify(foundComponent)) if (copy.onAttach) { foundComponent.onAttach() } allComponents.push(copy) }) processors.forEach(processorName => { const foundProcessor = this.getProcessor(processorName) allProcessors.push(foundProcessor) }) return { name: entityName, components: allComponents, processors: allProcessors } } /** * Checks if an entity has target component. * @param entity - entity object * @param component - Name of component * @returns true if entity has the component or false if not */ entityHasComponent(entity: Entity, component: string): boolean { const length = entity.components.length for (let i = 0; i < length; i++) { if (entity.components[i].name === component) { return true } } return false } /** * Checks if an entity has target processor. * @param entity - entity object * @param processor - Name of processor * @returns true if entity has the processor or false if not */ entityHasProcessor(entity: Entity, processor: string): boolean { const length = entity.processors.length for (let i = 0; i < length; i++) { if (entity.processors[i].name === processor) { return true } } return false } /** * Removes a component from an entity. * @param entity - entity object * @param component - Name of component * @returns Void if operation successful or throw an error. */ removeComponentFromEntity(entity: Entity, component: string): void { if (!this.entityHasComponent(entity, component)) { throw new Error(`removeComponentFromEntity(): component ${component} not found in entity ${entity.name}`) } let index = null const length = entity.components.length for (let i = 0; i < length; i++) { if (entity.components[i].name === component) { index = i break } } entity.components.splice(index, 1) } /** * Removes processor from an entity. * @param entity - entity object * @param processor - Name of processor * @returns Void if operation successful or throws an error. */ removeProcessorFromEntity(entity: Entity, processor: string): void { if (!this.entityHasProcessor(entity, processor)) { throw new Error(`removeProcessorFromEntity(): Processor ${processor} not found in entity ${entity.name}`) } let index = null const length = entity.processors.length for (let i = 0; i < length; i++) { if (entity.processors[i].name === processor) { index = i break } } entity.processors.splice(index, 1) } /** * Adds a component to an entity. * @param entity - entity object * @param component - Name of component * @returns Void if operation is successful or throws an error. */ addComponentToEntity(entity: Entity, component: string): void { if (this.entityHasComponent(entity, component)) { throw new Error(`addComponentToEntity(): Can't add component ${component} - this entity already has this component.`) } if (!this.hasComponent(component)) { throw new Error(`addComponentToEntity(): You can't add component ${component} to entity ${entity.name}, because the component is not registered.`) } entity.components.push(this.getComponent(component)) } /** * Adds a processor to an entity. * @param entity - entity object * @param processor - Name of processor * @returns Void if operation is successful or throws an error. */ addProcessorToEntity(entity: Entity, processor: string): void { if (this.entityHasProcessor(entity, processor)) { throw new Error(`addProcessorToEntity(): Can't add processor ${processor} - this entity already this processor.`) } if (!this.hasProcessor(processor)) { throw new Error(`addProcessorToEntity(): You can't add processor ${processor} to entity ${entity.name}, because the processor is not registerd.`) } entity.processors.push(this.getProcessor(processor)) } /** * Adds a entity to the system. * @param entity - entity object * @returns Void if successful */ addEntity(entity: Entity): void { this.entities.push(entity) } /** * Adds a component to the system. * @param component - component object * @returns Void if successful */ addComponent(component: Component): void { const passedComponent = component as Component if (passedComponent.onAttach) { passedComponent.onAttach() } this.components.push(passedComponent) } /** * Adds a processor to the system. * @param processor - processor object * @returns Void if successful */ addProcessor(processor: Processor): void { this.processors.push(processor) } /** * Removes an entity from the system. * @param entity - entity object * @returns Void if successful or throws an error. */ removeEntity(entity: Entity): void { const length = this.entities.length for (let i = 0; i < length; i++) { if (this.entities[i] === entity) { this.entities.splice(i, 1) return } } throw new Error(`removeEntity(): entity "${entity.name}" not found.`) } /** * Removes all entities from the system. * @returns void */ removeAllEntities(): void { this.entities = [] } /** * Gets all entities that have the target component registered. * @param componentName - Name of the component * @returns All entities in an array. */ private getEntitiesFromRequiredComponents(components: string[]): Entity[] { const entities = [] let entitiesAmount = this.entities.length for (let i = 0; i < entitiesAmount; i++) { const currentEntity = this.entities[i] let hasAllComponents = true for (let j = 0; j < components.length; j++) { const currentComponent = components[j] if (!this.entityHasComponent(currentEntity, currentComponent)) { hasAllComponents = false break } } if (hasAllComponents) { entities.push(currentEntity) } } return entities } /** * Runs all processors for it's corresponding components e.g. run the prcoessors update function. * @returns Void if successful */ private runProcessors(): void { this.processors.forEach(processor => { const entities = this.getEntitiesFromRequiredComponents(processor.required) const entityAmount = entities.length for (let i = 0; i < entityAmount; i++) { const currentEntity = entities[i] const hasProcessor = this.entityHasProcessor(currentEntity, processor.name) if (hasProcessor) { const components = this.getEntityComponents(currentEntity, processor.required) processor.update(currentEntity, components, processor) } } }) } /** * Runs all processors. This should be done per frame e.g. inside your gameloop. * @returns Void if successful */ update(): void { this.runProcessors() } }