0.1.21 • Published 8 months ago

@little-island/ecs v0.1.21

Weekly downloads
-
License
ISC
Repository
github
Last release
8 months ago

Entity Component System

Add this into any JavaScript game your building to allow a simple yet seamless way to handle multiple objects at once.

How it works

The most likely reason you would need this is when having many objects in your game that are either clones or modified replicants of each other. To add an object to the program, first you must create the "Assembly" that those objects will be based on. Then once you have that base assembly, you can create as many cloned or custom instances of it as you want. The updating is all handled through the "Systems" provided. You can also create custom Components to add to the entities to allow for individual and specialized functionality.

System (): class

This class essentially manages all the entities you create within it. It allows to store and run as many entities as you want. Store this when declared in a variable, then run its "update" method in whatever game loop you already have in your program

Below are the methods intended for your program's usage.

  • assemble (name: string, entityClass?: typeof Entity, options?: Assembly): void This stores, adds, and initializes an entity based on the name of an already registered assembly. The assemblt must be stored in the system before calling this.
  • createAssembly (name: string, buildMethod?: (e : Entity) => void): void This stores the assembly method.
  • get (name: string): Entity Retrieves stored entity based on its name.
  • setActive (entity: Entity, value: boolean): void Tell the entity to be active or inactive base on its name. If an entity is inactive, it will not be updated until active again.
  • update (deltaTime: number, elapsedTime: number): void This updates every active entity in this system. If an entity is marked as "dead", it will add it to a list. By the end of that run of the method, it will have removed every entity in that "dead" list from this system.

StandaloneSystem () extends System: class

This class does the same as the regular system accept it has its own "requestAnimationFrame" loop built in.

Below are the methods intended for your program's usage.

  • loop (): void Call this to start the frame-based system updating. This already contains the delta and elapsed time as well.

Component (parent: Entity, options: ComponentOptions): class

This class allows you to add custom functionality to your entities and it meant to be extending by the user. The base class on its own only contains the skeleton code for a component.

Below are the methods intended for your program's usage.

  • constructor (parent: Entity, options: ComponentOptions) Sets the entity in which this isntance of this component is paired too (the system handles this). The options are so you can enter custom data into the component on its creation.
  • destroy (): void Does whatever the user puts inside when this component's entity is "killed/dead".
  • findEntity (name: string): Entity Retrieves stored entity in this component's entity's system based on its name.
  • getComponent (name: string): Component Retrieves a stored component in its entity.
  • initComponent (): void Does whatever the user puts inside when this component is created.
  • initEntity (): void Does whatever the user puts inside when this component's entity is created.
  • isActive (): boolean Returns if this component is active or not.
  • onUpdate (deltaTime: number, elapsedTime: number): void Does whatever the user puts inside when the component is updated.
  • setActive (value: boolean): void Set if this component is active or not.
  • System get (): System Retrieves the system that this component's entity belongs to.

Entity (system: System, options?: EntityOptions): class

This class allows is the object that is created from the assembly you choose. You can add components to it to give it custom functionality. The base class on its own only contains the skeleton code for an entity.

Below are the methods intended for your program's usage.

  • constructor (system: System, options?: EntityOptions) Sets the system in which this isntance of this entity is paired too (the system handles this). The options are so you can enter custom data into the entity on its creation.
  • addComponent (componentClass: typeof Component, options?: ComponentOptions): void Stores and initializes a component in this entity based on its class and data inserted.
  • destroy (): void Calls all of its component's "destroy" methods so the entity can do what is neccessary to the program before being deleted.
  • findEntity (name: string): Entity Retrieves stored entity in this entity's system based on its name.
  • getComponent (name: string): Component Retrieves a stored component in this entity.
  • initEntity (): void Calls all of its component's "initEntity" methods when this entity is created or added to it's system.
  • kill (): void Marks this entity to be deleted before the end of its system's update loop.
  • setActive (value: boolean): void Set if this entity is active or not.
  • setName (name: string): void Set the name of this entity.

Get Started

There are a couple ways to use it.

With the "System" class

import * as ECS from '@little-island/ecs'
import * as THREE from 'three'

const TIME = {
    delta   : 0,
    elapsed : 0,
}

// define three.js dependents
const CLOCK = new THREE.Clock()
const SCENE = new THREE.Scene()

const CAMERA = new THREE.PerspectiveCamera(60, 1, 0.01, 1000)
CAMERA.position.y -= 3
CAMERA.lookAt(0, 0, 0)

const RENDERER = new THREE.WebGLRenderer()
RENDERER.setPixelRatio(window.devicePixelRatio)

document.body.appendChild(RENDERER.domElement)

// define components
class CubeMesh extends ECS.Component 
{
    constructor (parent, options) 
    {
        super(parent, options)

        const _GEOMETRY = new THREE.BoxGeometry( 1, 1, 1 )
        const _MATERIAL = new THREE.MeshNormalMaterial()

        this.Mesh = new THREE.Mesh(_GEOMETRY, _MATERIAL)

        if (options.x) 
        {
            this.Mesh.position.x = options.x
        }

    }

    initComponent () 
    {
        Scene.add(this.Mesh)
    }

    onUpdate (d) 
    {
        this.Mesh.rotation.x += d
        this.Mesh.rotation.z += d
    }

}

class CubeMaterial extends ECS.Component 
{
    initComponent () 
    {
        const _MESH = this.getComponent( 'CubeMesh' ).Mesh
        
        this.Material = new THREE.ShaderMaterial({
            uniforms: {
                colorA: {value: new THREE.Color(Math.random() * 0xffffff)},
                colorB: {value: new THREE.Color(Math.random() * 0xffffff)}
            },

            vertexShader: `
                varying vec3 vUv; 

                void main() {
                    vUv = position; 
        
                    vec4 modelViewPosition = modelViewMatrix * vec4(position, 1.0);
                    gl_Position = projectionMatrix * modelViewPosition; 
                }
            `,
            fragmentShader: `
                uniform vec3 colorA; 
                uniform vec3 colorB; 
                varying vec3 vUv;
      
                void main() {
                    gl_FragColor = vec4(mix(colorA, colorB, vUv.z), 1.0);
                }
            `,
        } )

        _MESH.material = this.Material
    }
}

// create and assemble entities
const SYSTEM = new ECS.System()

SYSTEM.createAssembly('Cube1', (e) => 
{
    e.addComponent(CubeMesh, {x: -0.8})
})

SYSTEM.createAssembly('Cube2', (e) => 
{
    e.addComponent(CubeMesh, {x: 0.8})
    e.addComponent(CubeMaterial)
})

SYSTEM.assemble('Cube1')
SYSTEM.assemble('Cube2')
 
// create and start loop
const loop = () => 
{
    requestAnimationFrame(() => 
    {
        TIME.delta   = CLOCK.getDelta()
        TIME.elapsed = CLOCK.getElapsedTime()

        SYSTEM.update(TIME.delta, TIME.elapsed)
        RENDERER.render(SCENE, CAMERA)

        loop()
    })
}

loop()

With "StandaloneSystem" class

import * as ECS from '@little-island/ecs'
import * as THREE from 'three'

// define three.js dependents
const SCENE = new THREE.Scene()

const CAMERA = new THREE.PerspectiveCamera(60, 1, 0.01, 1000)
CAMERA.position.y -= 3
CAMERA.lookAt(0, 0, 0)

const RENDERER = new THREE.WebGLRenderer()
RENDERER.setPixelRatio(window.devicePixelRatio)

document.body.appendChild(RENDERER.domElement)

// define components
class CubeMesh extends ECS.Component 
{
    constructor (parent, options) 
    {
        super(parent, options)

        const _GEOMETRY = new THREE.BoxGeometry( 1, 1, 1 )
        const _MATERIAL = new THREE.MeshNormalMaterial()

        this.Mesh = new THREE.Mesh(_GEOMETRY, _MATERIAL)

        if (options.x) 
        {
            this.Mesh.position.x = options.x
        }

    }

    initComponent () 
    {
        Scene.add(this.Mesh)
    }

    onUpdate (d) 
    {
        this.Mesh.rotation.x += d
        this.Mesh.rotation.z += d
    }

}

class CubeMaterial extends ECS.Component 
{
    initComponent () 
    {
        const _MESH = this.getComponent( 'CubeMesh' ).Mesh
        
        this.Material = new THREE.ShaderMaterial({
            uniforms: {
                colorA: {value: new THREE.Color(Math.random() * 0xffffff)},
                colorB: {value: new THREE.Color(Math.random() * 0xffffff)}
            },

            vertexShader: `
                varying vec3 vUv; 

                void main() {
                    vUv = position; 
        
                    vec4 modelViewPosition = modelViewMatrix * vec4(position, 1.0);
                    gl_Position = projectionMatrix * modelViewPosition; 
                }
            `,
            fragmentShader: `
                uniform vec3 colorA; 
                uniform vec3 colorB; 
                varying vec3 vUv;
      
                void main() {
                    gl_FragColor = vec4(mix(colorA, colorB, vUv.z), 1.0);
                }
            `,
        } )

        _MESH.material = this.Material
    }
}

// create and assemble entities
const SYSTEM = new ECS.StandaloneSystem()

SYSTEM.createAssembly('Cube1', (e) => 
{
    e.addComponent(CubeMesh, {x: -0.8})
})

SYSTEM.createAssembly('Cube2', (e) => 
{
    e.addComponent(CubeMesh, {x: 0.8})
    e.addComponent(CubeMaterial)
})

SYSTEM.assemble('Cube1')
SYSTEM.assemble('Cube2')
 
// start loop
SYSTEM.loop()
0.1.20

8 months ago

0.1.21

8 months ago

0.1.19

1 year ago

0.1.18

1 year ago

0.1.17

1 year ago

0.1.16

1 year ago

0.1.15

1 year ago

0.1.14

1 year ago

0.1.13

1 year ago

0.1.12

1 year ago

0.1.11

1 year ago

0.1.10

1 year ago

0.1.9

1 year ago

0.1.8

1 year ago

0.1.7

1 year ago

0.1.6

1 year ago

0.1.5

1 year ago

0.1.4

1 year ago

0.1.3

1 year ago

0.1.2

1 year ago