Skip to content

Repository files navigation

ecsy

NPM packageBuild SizeDev DependenciesBuild Status

ECSY (pronounced as "eck-see") is an highly experimental Entity Component System framework implemented in javascript, aiming to be lightweight, easy to use and with good performance.

For detailed information on the architecture and API please visit the documentation page

Features

  • Framework agnostic
  • Focused on providing a simple but yet efficient API
  • Designed to avoid garbage collection as possible
  • Systems, entities and components are scoped in a world instance
  • Multiple queries per system
  • Reactive support:
    • Support for reactive behaviour on systems (React to changes on entities and components)
    • System can query mutable or immutable components
  • Predictable:
    • Systems will run on the order they were registered or based on the priority defined when registering them
    • Reactive events will not generate a random callback when emited but queued and be processed in order
  • Modern Javascript: ES6, classes, modules,...
  • Pool for components and entities

Examples

Usage

Installing the package via npm:

npm install --save ecsy
<!DOCTYPE html><htmllang="en"><head><title>Hello!</title><metacharset="utf-8"><metahttp-equiv="X-UA-Compatible" content="IE=edge"><metaname="viewport" content="width=device-width, initial-scale=1"><style>html,body: {
margin:0;
padding:0;
}
</style><scripttype="module">import{World,System,TagComponent}from"https://ecsy.io/build/ecsy.module.js";constNUM_ELEMENTS=50;constSPEED_MULTIPLIER=0.3;constSHAPE_SIZE=50;constSHAPE_HALF_SIZE=SHAPE_SIZE/2;// Initialize canvasletcanvas=document.querySelector("canvas");letcanvasWidth=canvas.width=window.innerWidth;letcanvasHeight=canvas.height=window.innerHeight;letctx=canvas.getContext("2d");//----------------------// Components//----------------------// Velocity componentclassVelocity{constructor(){this.x=this.y=0;}}// Position componentclassPosition{constructor(){this.x=this.y=0;}}// Shape componentclassShape{constructor(){this.primitive='box';}}// Renderable componentclassRenderableextendsTagComponent{}//----------------------// Systems//----------------------// MovableSystemclassMovableSystemextendsSystem{// This method will get called on every frame by defaultexecute(delta,time){// Iterate through all the entities on the querythis.queries.moving.results.forEach(entity=>{varvelocity=entity.getComponent(Velocity);varposition=entity.getMutableComponent(Position);position.x+=velocity.x*delta;position.y+=velocity.y*delta;if(position.x>canvasWidth+SHAPE_HALF_SIZE)position.x=-SHAPE_HALF_SIZE;if(position.x<-SHAPE_HALF_SIZE)position.x=canvasWidth+SHAPE_HALF_SIZE;if(position.y>canvasHeight+SHAPE_HALF_SIZE)position.y=-SHAPE_HALF_SIZE;if(position.y<-SHAPE_HALF_SIZE)position.y=canvasHeight+SHAPE_HALF_SIZE;});}}// Define a query of entities that have "Velocity" and "Position" componentsMovableSystem.queries={moving: {components: [Velocity,Position]}}// RendererSystemclassRendererSystemextendsSystem{// This method will get called on every frame by defaultexecute(delta,time){ctx.fillStyle="#d4d4d4";ctx.fillRect(0,0,canvasWidth,canvasHeight);// Iterate through all the entities on the querythis.queries.renderables.results.forEach(entity=>{varshape=entity.getComponent(Shape);varposition=entity.getComponent(Position);if(shape.primitive==='box'){this.drawBox(position);}else{this.drawCircle(position);}});}drawCircle(position){ctx.beginPath();ctx.arc(position.x,position.y,SHAPE_HALF_SIZE,0,2*Math.PI,false);ctx.fillStyle="#39c495";ctx.fill();ctx.lineWidth=2;ctx.strokeStyle="#0b845b";ctx.stroke();}drawBox(position){ctx.beginPath();ctx.rect(position.x-SHAPE_HALF_SIZE,position.y-SHAPE_HALF_SIZE,SHAPE_SIZE,SHAPE_SIZE);ctx.fillStyle="#e2736e";ctx.fill();ctx.lineWidth=2;ctx.strokeStyle="#b74843";ctx.stroke();}}// Define a query of entities that have "Renderable" and "Shape" componentsRendererSystem.queries={renderables: {components: [Renderable,Shape]}}// Create world and register the systems on itvarworld=newWorld();world.registerSystem(MovableSystem).registerSystem(RendererSystem);// Some helper functions when creating the componentsfunctiongetRandomVelocity(){return{x: SPEED_MULTIPLIER*(2*Math.random()-1),y: SPEED_MULTIPLIER*(2*Math.random()-1)};}functiongetRandomPosition(){return{x: Math.random()*canvasWidth,y: Math.random()*canvasHeight};}functiongetRandomShape(){return{primitive: Math.random()>=0.5 ? 'circle' : 'box'};}for(leti=0;i<NUM_ELEMENTS;i++){world.createEntity().addComponent(Velocity,getRandomVelocity()).addComponent(Shape,getRandomShape()).addComponent(Position,getRandomPosition()).addComponent(Renderable)}// Run!functionrun(){// Compute delta and elapsed timevartime=performance.now();vardelta=time-lastTime;// Run all the systemsworld.execute(delta,time);lastTime=time;requestAnimationFrame(run);}varlastTime=performance.now();run();</script></head><body><canvaswidth="500" height="500"></canvas></body></html>

Try it on glitch

You can also include the hosted javascript directly on your HTML:

<!-- Using UMD (It will expose a global ECSY namespace) --><scriptsrc="https://ecsy.io/build/ecsy.js"></script><!-- Using ES6 modules --><scriptsrc="https://ecsy.io/build/ecsy.module.js"></script>

About

Highly experimental Entity Component System for javascript

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages