]> git.r.bdr.sh - rbdr/serpentity/commitdiff
Serpentity initial commit
authorBen Beltran <redacted>
Mon, 11 Aug 2014 06:35:17 +0000 (01:35 -0500)
committerBen Beltran <redacted>
Mon, 11 Aug 2014 06:35:17 +0000 (01:35 -0500)
.gitignore [new file with mode: 0644]
README.md [new file with mode: 0644]
bin/test [new file with mode: 0755]
lib/renderer/console.js [new file with mode: 0644]
lib/serpentity/component.js [new file with mode: 0644]
lib/serpentity/entity.js [new file with mode: 0644]
lib/serpentity/node.js [new file with mode: 0644]
lib/serpentity/node_collection.js [new file with mode: 0644]
lib/serpentity/serpentity.js [new file with mode: 0644]
lib/serpentity/system.js [new file with mode: 0644]
package.json [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..3c3629e
--- /dev/null
@@ -0,0 +1 @@
+node_modules
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..3878dd1
--- /dev/null
+++ b/README.md
@@ -0,0 +1,97 @@
+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
diff --git a/bin/test b/bin/test
new file mode 100755 (executable)
index 0000000..55e0aa2
--- /dev/null
+++ b/bin/test
@@ -0,0 +1,94 @@
+#!/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);
diff --git a/lib/renderer/console.js b/lib/renderer/console.js
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/lib/serpentity/component.js b/lib/serpentity/component.js
new file mode 100644 (file)
index 0000000..0204e30
--- /dev/null
@@ -0,0 +1,9 @@
+/*
+ * 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")({
+
+});
diff --git a/lib/serpentity/entity.js b/lib/serpentity/entity.js
new file mode 100644 (file)
index 0000000..cf2a623
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * 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;
+        }
+    }
+});
diff --git a/lib/serpentity/node.js b/lib/serpentity/node.js
new file mode 100644 (file)
index 0000000..00abde2
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * 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];
+                }
+            }
+        }
+    }
+});
diff --git a/lib/serpentity/node_collection.js b/lib/serpentity/node_collection.js
new file mode 100644 (file)
index 0000000..7bac90f
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * 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;
+        }
+    }
+});
diff --git a/lib/serpentity/serpentity.js b/lib/serpentity/serpentity.js
new file mode 100644 (file)
index 0000000..cdacd31
--- /dev/null
@@ -0,0 +1,235 @@
+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");
diff --git a/lib/serpentity/system.js b/lib/serpentity/system.js
new file mode 100644 (file)
index 0000000..c817be1
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * 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
+        }
+    }
+});
diff --git a/package.json b/package.json
new file mode 100644 (file)
index 0000000..785334f
--- /dev/null
@@ -0,0 +1,26 @@
+{
+  "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"
+}
+