From: Ben Beltran Date: Mon, 11 Aug 2014 06:35:17 +0000 (-0500) Subject: Serpentity initial commit X-Git-Tag: v0.1.3~11 X-Git-Url: https://git.r.bdr.sh/rbdr/serpentity/commitdiff_plain/85861d6720c30adc4afd1e041fd7e27fb596dde7 Serpentity initial commit --- 85861d6720c30adc4afd1e041fd7e27fb596dde7 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/README.md b/README.md new file mode 100644 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 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 index 0000000..e69de29 diff --git a/lib/serpentity/component.js b/lib/serpentity/component.js new file mode 100644 index 0000000..0204e30 --- /dev/null +++ b/lib/serpentity/component.js @@ -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 index 0000000..cf2a623 --- /dev/null +++ b/lib/serpentity/entity.js @@ -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 index 0000000..00abde2 --- /dev/null +++ b/lib/serpentity/node.js @@ -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 index 0000000..7bac90f --- /dev/null +++ b/lib/serpentity/node_collection.js @@ -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 index 0000000..cdacd31 --- /dev/null +++ b/lib/serpentity/serpentity.js @@ -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 index 0000000..c817be1 --- /dev/null +++ b/lib/serpentity/system.js @@ -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 index 0000000..785334f --- /dev/null +++ b/package.json @@ -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" +} +