Skip to content

Commit

Permalink
Replace new Eco() with Eco.create(); Add Eco.container() to allow ext…
Browse files Browse the repository at this point in the history
…ending entities
  • Loading branch information
lajohnston committed Mar 2, 2018
1 parent 8314008 commit bbbe09f
Show file tree
Hide file tree
Showing 9 changed files with 117 additions and 47 deletions.
43 changes: 30 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,26 @@ game logic decoupled from the game framework and libraries of your choice.
As an alternative to defining hierarchical types for in-game entities
(such as "Player", "Enemy", "FlyingEnemy", "FlyingEnemyWithGun" ...), the ECS
pattern has you split the aspects into components (position, movement, canFly,
hasGun) and mix and match them. This allows you to assemble many variations
hasGun) then mix and match them. This allows you to assemble many variations
using the same component parts, and even allows components to be added or
removed to change entity behaviour at runtime.

* Components - data structures (position, movement, appearance etc.)
* Entities - in-game objects that have components
* Systems - logic that updates entities based on their components
* Entities - containers that hold components; represent in-game objects
* Systems - logic that updates entities based on their component set

## Basic usage

```javascript
// Create an instance with a list of component names
const eco = new Eco(["position", "movement"]);
// Create an Eco instance, specifying a list of component names
const eco = Eco.create(["position", "movement"]);

// Create an entity. Add components using dot notation
const entity = eco.entity();
entity.position = { x: 100, y: 200 };
entity.movement = { x: 1, y: 0, speed: 2 };

/**
* Create an iterator for entities with position && movement components.
* The iterator listens for changes and only updates when necessary
*/
// Create an iterator for entities with position && movement components.
const moveable = eco.iterator(["position", "movement"]);

// Your update function in the main loop
Expand All @@ -48,6 +45,7 @@ function update(delta) {

```javascript
// Create an empty entity
const eco = Eco.create(["position"]);
const entity = eco.entity();

// Add or replace components using standard dot notation
Expand All @@ -65,6 +63,24 @@ entity.has("position"); // false
entity.removeAll();
```

You can add custom methods to the Entity class by extending it.

```javascript
// Create and edit a custom Eco dependency container
const myEco = Eco.container();

// Extend the Entity class and add your own methods
myEco.Entity = class extends myEco.Entity {
getFoo() {
return this.foo;
}
};

// Create an eco instance then use it as normal
const eco = myEco.create(["foo"]);
const entity = eco.entity(); // will be an instance of your entity class
```

## Iterators

Iterators iterate over a subset of entities, similar to using Array.filter and
Expand Down Expand Up @@ -108,11 +124,12 @@ eco.onChange = (entity, componentName, newValue, oldValue) => {
// this will be called whenever a component value is set
};

// The following examples will trigger the callback
entity.position = {}; // oldValue = undefined; newValue = {}
entity.position = undefined; // oldValue = {}; newValue = undefined
// Setting a component value will trigger the callback
entity.foo = "bar"; // oldValue = undefined; newValue = "bar"
entity.foo = undefined; // oldValue = "bar"; newValue = undefined

// ...but editing component properties will not
// Eco does not perform deep change detection, so the following won't trigger
// the callback
entity.position.x = 100; // will not trigger eco.onChange
entity.position = entity.position; // you'd have to do this to manually trigger
```
Expand Down
4 changes: 2 additions & 2 deletions dist/eco.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions spec/benchmark/entities.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ suite("Adding entities", function() {
},
{
setup: function() {
this.eco = new Eco();
this.eco = Eco.create();
},
teardown: function() {
this.eco = undefined;
Expand Down Expand Up @@ -41,7 +41,7 @@ suite("Adding and removing components", () => {
},
{
setup: function() {
const eco = new Eco(["foo"]);
const eco = Eco.create(["foo"]);
this.ecoEntity = eco.entity();
},
teardown: function() {
Expand Down
2 changes: 1 addition & 1 deletion spec/benchmark/iterators.spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* global Eco, suite, benchmark */

function createEcoIterator(entityCount, components, filter) {
const eco = new Eco(["foo", "bar", "baz"]);
const eco = Eco.create(["foo", "bar", "baz"]);

for (let i = 0; i < entityCount; i++) {
const ecoEntity = eco.entity();
Expand Down
11 changes: 9 additions & 2 deletions spec/e2e/creatingEcoInstances.spec.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
describe("Eco", () => {
it("should expose an Eco variable on the global window object", () => {
it("should expose an Eco variable with a create function", () => {
const Eco = window.Eco;

expect(Eco).toBeDefined();
expect(new Eco()).toBeDefined();
expect(Eco.create).toBeDefined();
});

it("should expose an Eco variable with a container function", () => {
const Eco = window.Eco;

expect(Eco).toBeDefined();
expect(Eco.container).toBeDefined();
});
});
38 changes: 25 additions & 13 deletions spec/e2e/entities.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const Eco = window.Eco;

describe("Entities", () => {
it("should hold component data", () => {
const eco = new Eco(["foo", "bar"]);
const eco = Eco.create(["foo", "bar"]);

const entityA = eco.entity();
entityA.foo = "fooA";
Expand All @@ -20,7 +20,7 @@ describe("Entities", () => {
});

it("should state whether they have a component or not", () => {
const eco = new Eco(["foo", "bar"]);
const eco = Eco.create(["foo", "bar"]);

const entity = eco.entity();
entity.foo = "foo";
Expand All @@ -30,7 +30,7 @@ describe("Entities", () => {
});

it("should allow the removal of all its components", () => {
const eco = new Eco(["foo", "bar"]);
const eco = Eco.create(["foo", "bar"]);

const entity = eco.entity();
entity.foo = "foo";
Expand All @@ -42,7 +42,7 @@ describe("Entities", () => {
});

it("should return an object of its components", () => {
const eco = new Eco(["foo", "bar"]);
const eco = Eco.create(["foo", "bar"]);

const entity = eco.entity();
entity.foo = "foo";
Expand All @@ -56,15 +56,27 @@ describe("Entities", () => {
});
});

describe("eco.all", () => {
it("should provide an array of all entities", () => {
const eco = new Eco();
const entities = [eco.entity(), eco.entity(), eco.entity()];
const result = eco.all;
it("should provide an array of all entities", () => {
const eco = Eco.create();
const entities = [eco.entity(), eco.entity(), eco.entity()];
const result = eco.all;

expect(Array.isArray(result)).toBe(true);
expect(result.length).toBe(entities.length);
expect(result).toEqual(entities);
});
expect(Array.isArray(result)).toBe(true);
expect(result.length).toBe(entities.length);
expect(result).toEqual(entities);
});

it("should allow entities to be extended", () => {
const customEco = Eco.container();
customEco.Entity = class extends customEco.Entity {
getFoo() {
return this.foo;
}
};

const eco = customEco.create();
const entity = eco.entity();
entity.foo = "bar";
expect(entity.getFoo()).toBe("bar");
});
});
2 changes: 1 addition & 1 deletion spec/e2e/events.spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
function createEco() {
return new window.Eco(["foo"]);
return window.Eco.create(["foo"]);
}

describe("eco.onChange", () => {
Expand Down
2 changes: 1 addition & 1 deletion spec/e2e/iterators.spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const Eco = window.Eco;

function createEco() {
return new Eco(["foo", "bar", "baz"]);
return Eco.create(["foo", "bar", "baz"]);
}

describe("Iterators", () => {
Expand Down
58 changes: 46 additions & 12 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,54 @@ import Eco from "./eco";
import EntityCollection from "./entityCollection";
import Iterator from "./iterator";

function createEntityCollection() {
return new EntityCollection();
}
/**
* Creates and returns an Eco dependency container object
*
* @returns {Object} dependency container
*/
function createContainer() {
const container = {
Entity: AbstractEntity,
Iterator,
EntityCollection,
Eco,

function createIterator(entities, components, filterFunc) {
return new Iterator(entities, components, filterFunc);
}
// Eco instance
create(components = []) {
// Extend entity class so each Eco instance can define its own components
const Entity = class extends container.Entity {};
components.forEach(name => Entity.defineComponent(name));

return new container.Eco(
Entity,
container.entityCollection(),
container.createIterator
);
},

function createEco(components = []) {
// Extend entity class so each Eco instance can define its own components
const Entity = class extends AbstractEntity {};
components.forEach(name => Entity.defineComponent(name));
entityCollection() {
return new container.EntityCollection();
},

createIterator(entities, components, filterFunc) {
return new container.Iterator(entities, components, filterFunc);
}
};

return container;
}

return new Eco(Entity, createEntityCollection(), createIterator);
/**
* Returns a new Eco instance using a default container
*
* @param {string[]} components component names
* @returns {Eco} eco instance
*/
function create(components) {
return createContainer().create(components);
}

window.Eco = createEco;
window.Eco = {
container: createContainer,
create
};

0 comments on commit bbbe09f

Please sign in to comment.