X-Git-Url: https://git.r.bdr.sh/rbdr/serpentity/blobdiff_plain/21653e1d2688779376b509e6c2e7086553d55010..509e372f9ccbc49344493fdb36b2afb17103f4da:/dist/serpentity.js diff --git a/dist/serpentity.js b/dist/serpentity.js index 22df2c7..1f9b250 100644 --- a/dist/serpentity.js +++ b/dist/serpentity.js @@ -1,6 +1,521 @@ -"undefined"!=typeof require&&require("neon"),Class("Serpentity")({prototype:{systems:null,nodeCollections:null,entities:null,init:function(e){var t;e=e||{},this.systems=[],this.entities=[],this.nodeCollections={};for(t in e)e.hasOwnProperty(t)&&(this[t]=e[t])},addSystem:function(e,t){var i,s;return this.systems.indexOf(e)>=0?!1:(e.priority=t,s=!1,i=0,this.systems.some(function(t,n){return i=n,t.priority>=e.priority?(s=!0,!0):void 0}),s||(i+=1),this.systems.splice(i,0,e),e.added(this),!0)},removeSystem:function(e){var t;return t=this.systems.indexOf(e),t>=0?(this.systems[t].removed(this),this.systems.splice(t,1),!0):!1},addEntity:function(e){var t;if(this.entities.indexOf(e)>=0)return!1;this.entities.push(e);for(t in this.nodeCollections)this.nodeCollections.hasOwnProperty(t)&&this.nodeCollections[t].add(e);return!0},removeEntity:function(e){var t;if(t=this.entities.indexOf(e),t>=0){for(property in this.nodeCollections)this.nodeCollections.hasOwnProperty(property)&&this.nodeCollections[property].remove(e);return this.entities.splice(t,1),!0}return!1},getNodes:function(e){var t;return this.nodeCollections.hasOwnProperty(e)?this.nodeCollections[e].nodes:(t=new Serpentity.NodeCollection({type:e}),this.nodeCollections[e]=t,this.entities.forEach(function(e){t.add(e)}),t.nodes)},update:function(e){this.systems.forEach(function(t){t.update(e)})}}}),"undefined"!=typeof require&&(require("./component.js"),require("./entity.js"),require("./node.js"),require("./node_collection.js"),require("./system.js")); -Class(Serpentity,"Entity")({prototype:{_components:null,_componentKeys:null,init:function(n){var t;this._componentKeys=[],this._components=[];for(t in n)n.hasOwnProperty(t)&&(this[t]=n[t])},add:function(n){return this._componentKeys.indexOf(n.constructor)>=0?!1:(this._componentKeys.push(n.constructor),this._components.push(n),!0)},hasComponent:function(n){return this._componentKeys.indexOf(n)>=0?!0:!1},getComponent:function(n){var t;return t=this._componentKeys.indexOf(n),t>=0?this._components[t]:void 0}}}); -Class(Serpentity,"Node")({matches:function(t){var s,r,n;n=this.types;for(s in this.types)if(this.types.hasOwnProperty(s)&&(r=!1,t.hasComponent(n[s])&&(r=!0),!r))return!1;return!0},prototype:{types:null,init:function(){var t;this.types={};for(t in this.constructor)this.constructor.hasOwnProperty(t)&&(this.types[t]=this.constructor[t])}}}); -Class(Serpentity,"NodeCollection")({prototype:{type:null,nodes:null,init:function(t){var n;t=t||{},this.nodes=[];for(n in t)t.hasOwnProperty(n)&&(this[n]=t[n])},add:function(t){var n,e,i;if(this.type.matches(t)&&!this._entityExists(t)){n=new this.type({}),n.entity=t,e=this.type.types;for(i in e)e.hasOwnProperty(i)&&(n[i]=t.getComponent(e[i]));return this.nodes.push(n),!0}return!1},remove:function(t){var n;return n=-1,this.nodes.forEach(function(e,i){e.entity===t&&(n=i)}),n>=0?(this.nodes.splice(n,1),!0):!1},_entityExists:function(t){var n;return n=!1,this.nodes.forEach(function(e){e.entity===t&&(n=!0)}),n}}}); -Class(Serpentity,"Component")({}); -Class(Serpentity,"System")({prototype:{added:function(){},removed:function(){},update:function(){}}}); \ No newline at end of file +if (typeof require !== "undefined") { + require("neon"); +} + +/* +Serpentity is a simple entity framework inspired by Ash. + +Usage: + + require('serpentity'); + +## Instantiating an engine + + var engine = Serpentity(); + +Add entities or systems, systems are added with a priority (the smaller +the number, the earlier it will be called): + + engine.addEntity(entityFactory()); + engine.addSystem(new GameSystem(), priority); + +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.addComponent(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, + entities : null, + _nodeCollections : null, + _nodeCollectionKeys : null, + + init : function init(config) { + var property; + + config = config || {}; + + this.systems = []; + this.entities = []; + this._nodeCollections = []; + this._nodeCollectionKeys = []; + + 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, priority) { + var lastIndex, found; + + if (this.systems.indexOf(system) >= 0) { + return false; + } + + system.priority = priority; + + found = false; + lastIndex = 0; + + this.systems.some(function findPriority(existingSystem, i) { + lastIndex = i; + if (existingSystem.priority >= system.priority) { + found = true; + return true; + } + }); + + if (!found) { + lastIndex += 1 + } + + this.systems.splice(lastIndex, 0, 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) { + if (this.entities.indexOf(entity) >= 0) { + return false; + } + this.entities.push(entity); + + this._nodeCollections.forEach(function (collection) { + collection.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) { + this._nodeCollections.forEach(function (collection) { + collection.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 position, nodeCollection; + + position = this._nodeCollectionKeys.indexOf(nodeType); + + if (position >= 0) { + return this._nodeCollections[position].nodes; + } + + nodeCollection = new Serpentity.NodeCollection({ + type : nodeType, + }); + + this._nodeCollectionKeys.push(nodeType); + this._nodeCollections.push(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); + }); + } + } +}); + +if (typeof require !== "undefined") { + require("./component.js"); + require("./entity.js"); + require("./node.js"); + require("./node_collection.js"); + require("./system.js"); +} + +/* + * The entity gives the entity framework its name. It exists only + * to hold components. + */ +Class(Serpentity, "Entity")({ + prototype : { + _components : null, + _componentKeys : null, + + init : function init(config) { + var property; + + this._componentKeys = []; + 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 + */ + addComponent : function addComponent(component) { + if (this._componentKeys.indexOf(component.constructor) >= 0) { + return false; + } + this._componentKeys.push(component.constructor); + this._components.push(component); + return true; + }, + + /* + * returns true if component is included, false otherwise + */ + hasComponent : function hasComponent(componentClass) { + if (this._componentKeys.indexOf(componentClass) >= 0) { + return true; + } + return false; + }, + + /* + * returns the component associated with that key + */ + getComponent : function getComponent(componentClass) { + var position; + position = this._componentKeys.indexOf(componentClass); + if (position >= 0) { + return this._components[position]; + } + } + } +}); + +/* + * 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]; + } + } + } + } +}); + +/* + * 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.getComponent(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; + } + } +}); + +/* + * 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")({ + prototype : { + init : function init(config) { + var property; + + config = config || {}; + + for (property in config) { + if (config.hasOwnProperty(property)) { + this[property] = config[property]; + } + } + } + } +}); + +/* + * 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 + } + } +});