--- /dev/null
+node_modules
--- /dev/null
+Serpentity is a simple entity framework inspired by Ash.
+
+Usage:
+
+require('serpentity');
+
+## Instantiating an engine
+
+var engine = Serpentity();
+
+Add entities or systems:
+
+ engine.addEntity(entityFactory());
+ engine.addSystem(new GameSystem());
+
+Update all systems:
+
+ engine.update(dt);
+
+Remove entities or systems:
+
+ engine.removeEntity(entityReference);
+ engine.removeSystem(systemReference);
+
+## Creating Entities
+
+Entities are the basic object of Serpentity, and they do nothing.
+
+ var entity = new Serpentity.entity();
+
+All the behavior is added through components
+
+## Creating Components
+
+Components define data that we can add to an entity. This data will
+eventually be consumed by "Systems"
+
+ Class("PositionComponent").inherits(Serpentity.Component)({
+ prototype : {
+ x : 0,
+ y : 0
+ }
+ });
+
+You can add components to entities by using the add method:
+
+ entity.add(new PositionComponent());
+
+
+Systems can refer to entities by requesting nodes.
+
+## Working with Nodes
+
+Nodes are sets of components that you define, so your system can require
+entities that always follow the API defined in the node.
+
+ Class("MovementNode").inherits(Serpentity.Node)({
+ types : {
+ position : PositionComponent,
+ motion : MotionComponent
+ }
+ });
+
+You can then request an array of all the nodes representing entities
+that comply with that API
+
+ engine.getNodes(MovementNode);
+
+## Creating Systems
+
+Systems are called on every update, and they use components through nodes.
+
+Class("TestSystem").inherits(Serpentity.System)({
+ prototype : {
+ added : function added(engine){
+ this.nodeList = engine.getNodes(MovementNode);
+ },
+ removed : function removed(engine){
+ this.nodeList = undefined;
+ }
+ update : function update(dt){
+ this.nodeList.forEach(function (node) {
+ console.log("Current position is: " + node.position.x + "," + node.position.y);
+ });
+ }
+ }
+});
+
+## That's it
+
+Just run `engine.update(dt)` in your game loop :D
+
+## TO-DO
+
+* Removing components
+* Implement the ashteroids demo (Serpentoids)
+* Actually check performance
--- /dev/null
+#!/usr/bin/env node
+
+require("colors");
+require("serpentity");
+
+/////////////////
+// Load the stuff
+/////////////////
+console.log("\n## Loading".bold.black)
+console.log("Serpentity: " + (typeof Serpentity !== "undefined" ? "LOAD OK".green : "FAIL".red));
+console.log("Serpentity.Entity: " + (typeof Serpentity !== "undefined" && Serpentity.Entity ? "LOAD OK".green : "FAIL".red));
+console.log("Serpentity.Component: " + (typeof Serpentity !== "undefined" && Serpentity.Component ? "LOAD OK".green : "FAIL".red));
+console.log("Serpentity.System: " + (typeof Serpentity !== "undefined" && Serpentity.System ? "LOAD OK".green : "FAIL".red));
+console.log("Serpentity.Node: " + (typeof Serpentity !== "undefined" && Serpentity.Node ? "LOAD OK".green : "FAIL".red));
+console.log("Serpentity.NodeCollection: " + (typeof Serpentity !== "undefined" && Serpentity.NodeCollection ? "LOAD OK".green : "FAIL".red));
+
+//////////////////////
+// Create test classes
+//////////////////////
+console.log("\n## Creating Test Classes".bold.black);
+Class("TestSystem").inherits(Serpentity.System)({
+ prototype : {
+ added : function added(engine) {
+ this.testNodes = engine.getNodes(TestNode);
+ console.log("System added callback: " + "EXEC OK".green);
+ },
+
+ removed : function removed(engine) {
+ this.testNodes = null;
+ console.log("System removed callback: " + "EXEC OK".green);
+ },
+
+ update : function update(dt) {
+ this.testNodes.forEach(function (node) {
+ console.log("Running Node: " + (node.test.testMessage === "test" ? "SYSTEM OK".green : "FAIL".RED));
+ });
+ console.log("System update callback: " + "EXEC OK".green);
+ }
+ }
+});
+var testSystem = new TestSystem();
+console.log("TestSystem: " + "CREATE OK".green)
+
+
+Class("TestComponent").inherits(Serpentity.Component)({
+ prototype : {
+ testMessage : "test"
+ }
+});
+console.log("TestComponent: " + "CREATE OK".green)
+
+Class("TestNode").inherits(Serpentity.Node)({
+ types : {
+ test : TestComponent
+ }
+});
+console.log("TestNode: " + "CREATE OK".green)
+
+
+console.log("\n## Adding system to the engine".bold.black)
+
+var engine = new Serpentity();
+console.log("engine: " + "CREATE OK".green)
+
+engine.addSystem(testSystem);
+
+console.log("\n## Running update without any entities".bold.black)
+engine.update(10);
+
+console.log("\n## Adding system to the engine and updating".bold.black)
+var entity = new Serpentity.Entity();
+entity.add(new TestComponent());
+engine.addEntity(entity);
+engine.update(10);
+
+console.log("\n## Removing the system and readding".bold.black)
+engine.removeSystem(testSystem);
+engine.update(10);
+engine.addSystem(testSystem);
+engine.update(10);
+
+console.log("\n## Adding a second entity".bold.black)
+var entity = new Serpentity.Entity();
+entity.add(new TestComponent());
+engine.addEntity(entity);
+engine.update(10);
+
+console.log("\n## Removing entity".bold.black)
+engine.removeEntity(entity)
+engine.update(10);
+
+console.log("\n## Removing system".bold.black)
+engine.removeSystem(testSystem)
+engine.update(10);
--- /dev/null
+/*
+ * Components store data. Nothing to say here really, just
+ * inherit and add a prototype, or don't even inherit, see?
+ * It's just an empty class, so what I'm trying to say is your
+ * components can be any class whatsoever.
+ */
+Class(Serpentity, "Component")({
+
+});
--- /dev/null
+/*
+ * The entity gives the entity framework its name. It exists only
+ * to hold components.
+ */
+Class(Serpentity, "Entity")({
+ prototype : {
+ addedComponents : null,
+
+ init : function init(config) {
+ var property;
+
+ this.components = {};
+
+ for (property in config) {
+ if (config.hasOwnProperty(property)) {
+ this[property] = config[property];
+ }
+ }
+ },
+
+ /*
+ * Adds a component to the entity.
+ *
+ * returns true if added, false if already present
+ */
+ add : function add(component) {
+ if (this.components.hasOwnProperty(component.constructor)) {
+ return false;
+ }
+ this.components[component.constructor] = component;
+ return true;
+ },
+
+ /*
+ * returns true if component is included, false otherwise
+ */
+ hasComponent : function hasComponent(componentClass) {
+ if (this.components.hasOwnProperty(componentClass)) {
+ return true;
+ }
+ return false;
+ }
+ }
+});
--- /dev/null
+/*
+ * A node describes a set of components in order to describe entities
+ * that include them.
+ */
+Class(Serpentity, "Node")({
+
+ /*
+ * Returns true if the given entity matches the defined protocol,
+ * false otherwise
+ */
+ matches : function matches(entity) {
+ var property, matched, types;
+
+ types = this.types;
+
+ for (property in this.types) {
+
+ if (this.types.hasOwnProperty(property)) {
+ matched = false;
+
+ if (entity.hasComponent(types[property])) {
+ matched = true;
+ }
+
+ if (!matched) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ },
+
+ prototype : {
+
+ types : null,
+
+ init : function (config) {
+ var property;
+
+ this.types = {};
+
+ for (property in this.constructor) {
+ if (this.constructor.hasOwnProperty(property)) {
+ this.types[property] = this.constructor[property];
+ }
+ }
+ }
+ }
+});
--- /dev/null
+/*
+ * Node Collections contain nodes, in order to keep the lists of nodes
+ * that belong to each type.
+ *
+ * It has a type which is the class name of the node, and an array of
+ * instances of that class.
+ */
+Class(Serpentity, "NodeCollection")({
+ prototype : {
+ type : null,
+ nodes : null,
+
+ init : function init(config) {
+ var property;
+
+ config = config || {};
+
+ this.nodes = [];
+
+ for (property in config) {
+ if (config.hasOwnProperty(property)) {
+ this[property] = config[property];
+ }
+ }
+ },
+
+ /*
+ * Creates a node for an entity if it matches, and adds it to the
+ * node list.
+ *
+ * Returns true if added, false otherwise.
+ */
+ add : function add(entity) {
+ var node, types, property;
+
+ if (this.type.matches(entity) && !this._entityExists(entity)) {
+ node = new this.type({});
+
+ node.entity = entity;
+
+ types = this.type.types;
+
+ for (property in types) {
+ if (types.hasOwnProperty(property)) {
+ node[property] = entity.components[types[property]]
+ }
+ }
+
+ this.nodes.push(node);
+
+ return true;
+ }
+
+ return false;
+ },
+
+ /*
+ * Removes an entity by removing its related node from the list of nodes
+ *
+ * returns true if it was removed, false otherwise.
+ */
+ remove : function (entity) {
+ var found;
+ found = -1;
+ this.nodes.forEach(function (node, i) {
+ if (node.entity === entity) {
+ found = i;
+ }
+ });
+
+ if (found >= 0) {
+ this.nodes.splice(found, 1);
+ return true;
+ }
+
+ return false;
+ },
+
+ /*
+ * Checks whether we already have nodes for this entity.
+ */
+ _entityExists : function entityExists(entity) {
+ var found;
+ found = false;
+ this.nodes.forEach(function (node) {
+ if (node.entity === entity) {
+ found = true;
+ }
+ });
+
+ return found;
+ }
+ }
+});
--- /dev/null
+require("neon");
+
+/*
+Serpentity is a simple entity framework inspired by Ash.
+
+Usage:
+
+require('serpentity');
+
+## Instantiating an engine
+
+var engine = Serpentity();
+
+Add entities or systems:
+
+ engine.addEntity(entityFactory());
+ engine.addSystem(new GameSystem());
+
+Update all systems:
+
+ engine.update(dt);
+
+Remove entities or systems:
+
+ engine.removeEntity(entityReference);
+ engine.removeSystem(systemReference);
+
+## Creating Entities
+
+Entities are the basic object of Serpentity, and they do nothing.
+
+ var entity = new Serpentity.entity();
+
+All the behavior is added through components
+
+## Creating Components
+
+Components define data that we can add to an entity. This data will
+eventually be consumed by "Systems"
+
+ Class("PositionComponent").inherits(Serpentity.Component)({
+ prototype : {
+ x : 0,
+ y : 0
+ }
+ });
+
+You can add components to entities by using the add method:
+
+ entity.add(new PositionComponent());
+
+
+Systems can refer to entities by requesting nodes.
+
+## Working with Nodes
+
+Nodes are sets of components that you define, so your system can require
+entities that always follow the API defined in the node.
+
+ Class("MovementNode").inherits(Serpentity.Node)({
+ types : {
+ position : PositionComponent,
+ motion : MotionComponent
+ }
+ });
+
+You can then request an array of all the nodes representing entities
+that comply with that API
+
+ engine.getNodes(MovementNode);
+
+## Creating Systems
+
+Systems are called on every update, and they use components through nodes.
+
+Class("TestSystem").inherits(Serpentity.System)({
+ prototype : {
+ added : function added(engine){
+ this.nodeList = engine.getNodes(MovementNode);
+ },
+ removed : function removed(engine){
+ this.nodeList = undefined;
+ }
+ update : function update(dt){
+ this.nodeList.forEach(function (node) {
+ console.log("Current position is: " + node.position.x + "," + node.position.y);
+ });
+ }
+ }
+});
+
+## That's it
+
+Just run `engine.update(dt)` in your game loop :D
+
+*/
+Class("Serpentity")({
+ prototype : {
+ systems : null,
+ nodeCollections : null,
+ entities : null,
+
+ init : function init(config) {
+ var property;
+
+ config = config || {};
+
+ this.systems = [];
+ this.entities = [];
+ this.nodeCollections = {};
+
+ for (property in config) {
+ if (config.hasOwnProperty(property)) {
+ this[property] = config[property];
+ }
+ }
+ },
+
+ /*
+ * Adds a system to the engine, so its update method will be called
+ * with the others. Triggers added hook.
+ *
+ * returns true if added succesfully, false if already added
+ */
+ addSystem : function addSystem(system) {
+ if (this.systems.indexOf(system) >= 0) {
+ return false;
+ }
+ this.systems.push(system);
+ system.added(this);
+ return true;
+ },
+
+ /*
+ * Removes a system from the engine, so its update method will no
+ * longer will be called. Triggers the removed hook.
+ *
+ * returns true if removed succesfully, false if already added
+ */
+ removeSystem : function removeSystem(system) {
+ var position;
+
+ position = this.systems.indexOf(system);
+ if (position >= 0) {
+ this.systems[position].removed(this);
+ this.systems.splice(position, 1);
+ return true;
+ }
+
+ return false;
+ },
+
+ /*
+ * Adds an entity to the engine, adds to existing node collections
+ *
+ * returns true if added, false if already there
+ */
+ addEntity : function addEntity(entity) {
+ var property;
+
+ if (this.entities.indexOf(entity) >= 0) {
+ return false;
+ }
+ this.entities.push(entity);
+
+ for (property in this.nodeCollections) {
+ if (this.nodeCollections.hasOwnProperty(property)) {
+ this.nodeCollections[property].add(entity);
+ }
+ }
+ return true;
+ },
+
+ /*
+ * Removes entity from system, removing from all node collections
+ *
+ * returns true if removed, false if not present
+ */
+ removeEntity : function removeEntity(entity) {
+ var position;
+
+ position = this.entities.indexOf(entity);
+ if (position >= 0) {
+ for (property in this.nodeCollections) {
+ if (this.nodeCollections.hasOwnProperty(property)) {
+ this.nodeCollections[property].remove(entity);
+ }
+ }
+
+ this.entities.splice(position, 1);
+ return true;
+ }
+
+ return false;
+ },
+
+ /*
+ * Given a Node Class, retrieves a list of all the nodes for each
+ * applicable entity.
+ */
+ getNodes : function getNodes(nodeType) {
+ var nodeCollection;
+
+ if (this.nodeCollections.hasOwnProperty(nodeType)) {
+ return this.nodeCollections[nodeType].nodes;
+ }
+
+ nodeCollection = new Serpentity.NodeCollection({
+ type : nodeType,
+ });
+ this.nodeCollections[nodeType] = nodeCollection;
+
+ this.entities.forEach(function (entity) {
+ nodeCollection.add(entity);
+ });
+
+ return nodeCollection.nodes;
+ },
+
+ /*
+ * Calls update for every loaded system.
+ */
+ update : function update(dt) {
+ this.systems.forEach(function (system) {
+ system.update(dt);
+ });
+ }
+ }
+});
+
+require("./component.js");
+require("./entity.js");
+require("./node.js");
+require("./node_collection.js");
+require("./system.js");
--- /dev/null
+/*
+ * Systems contain most of the logic, and work with nodes in order to
+ * act and change their values.
+ *
+ * You usually want to inherit from this class and override the
+ * three methods.
+ */
+Class(Serpentity, "System")({
+ prototype : {
+
+ /*
+ * This will be run when the system is added to the engine
+ */
+ added : function added(engine) {
+ // Override
+ },
+
+ /*
+ * This will be run when the system is removed from the engine
+ */
+ removed : function removed(engine) {
+ // Override
+ },
+
+ /*
+ * This will run every time the engine's update method is called
+ */
+ update : function update(dt) {
+ // Override
+ }
+ }
+});
--- /dev/null
+{
+ "name" : "serpentity",
+ "description" : "A simple entity frameowrk inspired by ash",
+ "version" : "0.0.1",
+ "contributors" : [
+ {
+ "name" : "Ben Beltran",
+ "email" : "ben@nsovocal.com",
+ "url" : "http://nsovocal.com"
+ }
+ ],
+ "repository" : {
+ "type" : "git",
+ "url" : "https://github.com/benbeltran/serpentity.git"
+ },
+ "dependencies" : {
+ "neon" : "2.0.x",
+ "tellurium" : "2.0.x"
+ },
+ "devDependencies" : {
+ "colors" : "0.6.2"
+ },
+ "engines" : { "node" : ">= 0.10.0" },
+ "main" : "./lib/serpentity/serpentity.js"
+}
+