Last active
April 4, 2020 02:56
-
-
Save sokol815/9199c3a074cec94209dd2278b46d23f1 to your computer and use it in GitHub Desktop.
Entity Component System in javascript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var App = App || {}; | |
if( !Acc ) { | |
console.warn('Acc is required for App.ECS to function'); | |
} | |
App.ECS = function( parent ) { | |
this.parent = parent; | |
this.systems = {}; | |
this.components = {}; | |
this.entities = []; | |
this.componentFamilies = {}; // keys of components with values being arrays of entities having those components. Updated any-time an entity adds or removes a component. | |
this.events = {}; | |
this.listeners = { | |
addComponent: {}, | |
removeComponent: {} | |
}; | |
this._entityComponentLookup = {}; | |
this._prefabs = {}; | |
this._eventQueue = []; | |
}; | |
App.ECS.prototype._eventNames = ['init','onKeyDown','onKeyUp','onClick','update']; | |
App.ECS.prototype._reservedWords = ['delta','components','base','name']; | |
App.ECS.prototype.FilterReserved = function( values ) { | |
var self = this; | |
return values.filter(function(val){ return self._reservedWords.indexOf( val ) < 0; }); | |
}; | |
App.ECS.prototype.IsReserved = function(name) { | |
if( this._reservedWords.indexOf( name ) > -1 ) { | |
console.warn(name + ' is a reserved ECS word. No Components, systems or properties can be named that.'); | |
} | |
}; | |
App.ECS.prototype.Init = function() { | |
var self = this; | |
document.onkeydown = function(e) { | |
self.TriggerEvent( 'onKeyDown', e ); | |
}; | |
document.onkeyup = function(e) { | |
self.TriggerEvent( 'onKeyUp', e ); | |
}; | |
document.onclick = function(e) { | |
self.TriggerEvent( 'onClick', e ); | |
}; | |
console.log('ECS init triggered'); | |
this.TriggerEvent('init'); | |
}; | |
App.ECS.prototype.Clean = function() { | |
this.entities = []; | |
this._eventQueue = []; | |
this._entityComponentLookup = {}; | |
this._defaultEntity.nextId = 0; | |
this._defaultComponent.nextId = 0; | |
}; | |
App.ECS.prototype.TriggerEvent = function( eventName, eventParams ) { | |
var eventList = this.events[eventName]; | |
if( eventList != undefined ) { | |
for( var i = 0; i < eventList.length; i++ ) { | |
var consumed = eventList[i].functions[eventName].call( eventList[i], this, eventParams ); | |
if( consumed ) { | |
return; | |
} | |
} | |
} | |
}; | |
App.ECS.prototype.CloneWithDefault = function( obj, def ) { | |
var res = Acc.Clone( def ); | |
var keys = Object.keys( obj ); | |
for( var i = 0; i < keys.length; i++ ) { | |
if( keys[i] == 'functions' && def['functions'] != undefined ) { | |
var objFuncKeys = Object.keys(obj['functions']); | |
var defFuncKeys = Object.keys(def['functions']); | |
for( var j = 0; j < objFuncKeys.length; j++ ) { | |
res['functions'][objFuncKeys[j]] = obj['functions'][objFuncKeys[j]]; | |
} | |
} else { | |
res[keys[i]] = obj[keys[i]]; | |
} | |
} | |
return res; | |
}; | |
App.ECS.prototype._defaultSystem = { | |
name: 'default', | |
functions: { | |
'init': function( ecs ) { | |
console.log(this.name + ' system initialized'); | |
} | |
} | |
}; | |
App.ECS.prototype.RegisterSystem = function( system ) { | |
if( this.systems[system.name] != undefined ) { | |
console.warn("System type " + system.name + " already exists."); | |
return; | |
} | |
if( this.IsReserved( system.name ) ) { | |
console.warn("Cannot name system '" + system.name + "' it is a reserved word." ); | |
return; | |
} | |
if( system.functions.init == undefined ) { | |
system.functions.init = this._defaultSystem.functions.init; | |
} | |
var sys = system;//this.CloneWithDefault( system, this._defaultSystem ); | |
var keys = Object.keys( sys.functions ); | |
for( var i = 0; i < keys.length; i++ ) { | |
if( this._eventNames.indexOf( keys[i] ) > -1 ) { | |
if( this.events[keys[i]] == undefined ) { | |
this.events[keys[i]] = []; | |
} | |
this.events[keys[i]].push(sys); | |
} | |
} | |
if( sys.listen ) { | |
for( type in sys.listen ) { | |
if( this.listeners[type] ) { | |
for( var i = 0; i < sys.listen[type].length; i++ ) { | |
var cur = sys.listen[type][i]; | |
if( this.listeners[type][cur.name] == undefined ) { | |
this.listeners[type][cur.name] = []; | |
} | |
this.listeners[type][cur.name].push({ | |
system: sys, | |
name: cur.name, | |
functionToExecute: cur.functionToExecute, | |
priority: cur.priority | |
}); | |
} | |
} | |
} | |
} | |
this.systems[sys.name] = sys; | |
}; | |
App.ECS.prototype._defaultComponent = { | |
name: 'default', | |
nextId: 0, | |
getNextId: function(){ | |
return this.nextId++; | |
}, | |
values: { | |
} | |
}; | |
App.ECS.prototype.RegisterComponent = function( component ) { | |
if( this.components[component.name] != undefined ) { | |
console.warn("Component type " + component.name + " already exists." ); | |
return; | |
} | |
if( this.IsReserved( component.name ) ) { | |
console.warn("Cannot name component '" + component.name + "' it is a reserved word." ); | |
return; | |
} | |
var comp = this.CloneWithDefault( component, this._defaultComponent ); | |
this.components[comp.name] = comp; | |
}; | |
App.ECS.prototype._defaultEntity = { | |
nextId: 0, | |
getNextId: function() { | |
return this.nextId++; | |
} | |
}; | |
App.ECS.prototype.CreateEntity = function( components ) { | |
if( components == undefined ) { | |
components = []; | |
} | |
var entity = { | |
type: 'entity', | |
id: this._defaultEntity.getNextId(), | |
components: {} | |
}; | |
this.entities.binaryInsert( entity, this._UniqueEntityCompare ); | |
for( var i = 0; i < components.length; i++ ) { | |
this.AddComponentToEntity( components[i], entity ); | |
} | |
return entity; | |
}; | |
App.ECS.prototype.RemoveEntity = function( entity ) { | |
for( var comp in entity.components ) { | |
this.RemoveComponentFromEntity( comp, entity ); | |
} | |
var index = this.entities.binaryIndexOf( entity, this._UniqueEntityCompare ); | |
if( index < 0 ) { | |
console.warn('entity not found... something is wrong', entity ); | |
} else { | |
this.entities.removeAtIndex( index ); | |
} | |
}; | |
App.ECS.prototype.EventTriggerAddComponent = function( compName, entity ) { | |
if( this.listeners.addComponent[compName] !== undefined ) { | |
var set = this.listeners.addComponent[compName]; | |
for( var i = 0; i < set.length; i++ ) { | |
set[i].system.functions[set[i].functionToExecute]( entity ); | |
} | |
} | |
}; | |
App.ECS.prototype.AddComponentToEntity = function( compName, entity ) { | |
var comp = this.components[compName]; | |
if( comp != undefined && entity.components[compName] == undefined ) { | |
entity.components[compName] = Acc.Clone( comp.values ); | |
this.CacheComponentAdded( compName, entity ); | |
} | |
this.EventTriggerAddComponent( compName, entity ); | |
}; | |
App.ECS.prototype.EventTriggerRemoveComponent = function( compName, entity ) { | |
if( this.listeners.removeComponent[compName] !== undefined ) { | |
var set = this.listeners.removeComponent[compName]; | |
for( var i = 0; i < set.length; i++ ) { | |
set[i].system.functions[set[i].functionToExecute]( entity ); | |
} | |
} | |
}; | |
App.ECS.prototype.RemoveComponentFromEntity = function( compName, entity ) { | |
var comp = this.components[compName]; | |
if( comp != undefined && entity.components[compName] != undefined ) { | |
delete entity.components[compName]; | |
this.CacheComponentRemoved( compName, entity ); | |
} | |
this.EventTriggerRemoveComponent( compName, entity ); | |
}; | |
App.ECS.prototype._UniqueEntityCompare = function( a, b ) { | |
if( a.id < b.id ) { return -1; } | |
if( a.id > b.id ) { return 1; } | |
return 0; | |
}; | |
App.ECS.prototype.CacheComponentAdded = function( compName, entity ) { | |
if( this._entityComponentLookup[compName] == undefined ) { | |
this._entityComponentLookup[compName] = []; | |
} | |
this._entityComponentLookup[compName].binaryInsert( entity, this._UniqueEntityCompare ); | |
}; | |
App.ECS.prototype.CacheComponentRemoved = function( compName, entity ) { | |
if( this._entityComponentLookup[compName] == undefined ) { | |
this._entityComponentLookup[compName] = []; | |
return; | |
} | |
var index = this._entityComponentLookup[compName].binaryIndexOf(entity, this._UniqueEntityCompare ); | |
if( index > -1 ) { | |
this._entityComponentLookup[compName].removeAtIndex(index); | |
} | |
}; | |
App.ECS.prototype.CreatePrefab = function( name, componentData ) { | |
var constructedPrefab = {name: name}; | |
componentData = Acc.Clone( componentData ); | |
if( componentData.type == 'entity' ) { | |
delete componentData.type; | |
} | |
if( this.IsReserved( name ) ) { | |
console.warn("Cannot name prefab '" + name + "' it is a reserved word." ); | |
return; | |
} | |
var componentNames = this.FilterReserved(Object.keys(componentData)); | |
for( var i = 0; i < componentNames.length; i++ ) { | |
this._AddComponentToPrefab( constructedPrefab, componentNames[i], componentData[componentNames[i]] ); | |
} | |
this._prefabs[name] = constructedPrefab; | |
console.log('prefab ' + name + ' registered.'); | |
return constructedPrefab; | |
}; | |
App.ECS.prototype._AddComponentToPrefab = function( prefab, componentName, componentData ) { | |
var comp = this.components[componentName]; | |
if( comp == undefined ) { | |
console.warn('Could not find Component by name of "'+componentName+'".'); | |
return; | |
} | |
prefab[componentName] = Acc.Clone( comp.values ); | |
if( Array.isArray( comp.values )){ | |
// --- if we have an array, it's because this is a list of entities | |
prefab[componentName] = []; | |
for( var j = 0; j < componentData.length; j++ ) { | |
// --- each of these is an entity reference. | |
var curChildPrefab = componentData[j]; | |
var childName = prefab.name+'.'+componentName+'.'+j; | |
var childPrefab = this.CreatePrefab( childName, curChildPrefab['components'] ); | |
prefab[componentName].push({ type: 'prefab', value: childName }); | |
} | |
} else { | |
var keys = Object.keys(componentData); | |
for( var j = 0; j < keys.length; j++ ) { | |
if( prefab[componentName][keys[j]] === undefined ) { | |
console.warn( 'Invalid property "'+keys[j]+'" on component "' + componentName + '" in the "' + prefab.name + '" prefab.'); | |
} else if( typeof componentData[keys[j]] == 'object' && comp.values[keys[j]] === null ) { | |
// --- this is intended to be a reference to an entity | |
var curChildPrefab = componentData[keys[j]]; | |
var childName = prefab.name+'.'+componentName+'.'+keys[j]; | |
var childPrefab = this.CreatePrefab( childName, curChildPrefab['components'] ); | |
prefab[componentName][keys[j]] = {type: 'prefab', value: childName }; | |
} else { | |
prefab[componentName][keys[j]] = componentData[keys[j]]; | |
} | |
} | |
} | |
}; | |
App.ECS.prototype.CreatePrefabs = function( prefabsObject ) { | |
var keys = Object.keys( prefabsObject ); | |
var children = {}; | |
for( var i = 0; i < keys.length; i++ ) { | |
var cur = prefabsObject[keys[i]]; | |
if( cur.base != undefined ) { | |
children[keys[i]] = cur; | |
} else { | |
this.CreatePrefab( keys[i], cur ); | |
} | |
} | |
this.SolvePrefabDependencies( children ); | |
}; | |
App.ECS.prototype.SolvePrefabDependencies = function( children ) { | |
var keys = Object.keys( children ); | |
for( var i = 0; i < keys.length; i++ ) { | |
var childPrefabData = children[keys[i]]; | |
if( this._prefabs[childPrefabData.base] == undefined ) { | |
console.warn("could not find base prefab named '" + childPrefabData.base + "'" ); | |
// --- do nothing else for this prefab. | |
} else { | |
var newChild = Acc.Clone( this._prefabs[childPrefabData.base] ); | |
newChild.name = keys[i]; | |
// --- at this point we have the prefab as it's parent defined it | |
var childKeys = this.FilterReserved( Object.keys( childPrefabData )); | |
for( var j = 0; j < childKeys.length; j++ ) { | |
if( newChild[childKeys[j]] == undefined ) { | |
// --- component newly added to prefab | |
this._AddComponentToPrefab( newChild, childKeys[j], childPrefabData[childKeys[j]] ); | |
} else { | |
// --- component exists on prefab | |
if( Array.isArray( newChild[childKeys[j]] ) ) { | |
// --- component is an array type values. | |
newChild[childKeys[j]] = []; | |
for( var k = 0; k < childPrefabData[childKeys[j]].length; k++ ) { | |
// --- each item on here should be a prefab | |
newChild[childKeys[j]].push( Acc.Clone( childPrefabData[childKeys[j]][k] )); | |
} | |
} else { | |
var compValues = Object.keys( newChild[childKeys[j]] ); | |
/// --- component has key: value pairs. each key: value pair may be an array, too. | |
for( var k = 0; k < compValues.length; k++ ) { | |
if( Array.isArray( newChild[childKeys[j]][compValues[k]])) { | |
newChild[childKeys[j]][compValues[k]] = []; | |
if( childPrefabData[childKeys[j]][compValues[k]] != undefined ) { | |
// --- list of entities | |
for( var l = 0; l < childPrefabData[childKeys[j]][compValues[k]].length; l++ ) { | |
newChild[childKeys[j]][compValues[k]].push(childPrefabData[childKeys[j]][compValues[k]][l]); | |
} | |
} | |
} else { | |
// --- just a value | |
if( childPrefabData[childKeys[j]][compValues[k]] != undefined ) { | |
newChild[childKeys[j]][compValues[k]] = childPrefabData[childKeys[j]][compValues[k]]; | |
} | |
} | |
} | |
} | |
} | |
} | |
this._prefabs[keys[i]] = newChild; | |
console.log('prefab ' + keys[i] + ' registered.'); | |
} | |
} | |
}; | |
App.ECS.prototype.Serialize = function( entity ) { | |
var output = JSON.stringify( entity ); | |
return output; | |
}; | |
App.ECS.prototype.Deserialize = function( entity ) { | |
if( typeof entity !== 'string' ) { | |
entity = JSON.stringify( entity ); | |
} | |
var output = JSON.parse( entity ); | |
// --- re-key all IDs | |
this.ReKey( output ); | |
this.RecursiveRegister( output ); | |
return output; | |
}; | |
App.ECS.prototype.RecursiveRegister = function( obj, objName ) { | |
if( Array.isArray( obj )) { | |
// --- we have an array of entities | |
for( var i = 0; i < obj.length; i++ ) { | |
// -- each entity | |
this.RegisterEntity( obj[i] ); | |
} | |
} else if( obj.type == 'entity' ) { | |
// --- single entity | |
this.RegisterEntity( obj ); | |
} else { | |
// --- component? | |
var component = this.components[objName]; | |
if( Array.isArray(component.values)) { | |
for( var i = 0; i < obj.length; i++ ) { | |
this.RegisterEntity( obj[i] ); | |
} | |
} else { | |
for( var property in component.values ) { | |
if( component.values[property] === null ) { | |
// --- direct entity reference | |
if( obj[property] !== null ) { | |
this.RegisterEntity( obj[property] ); | |
} | |
} else { | |
// --- just a component property. | |
} | |
} | |
} | |
} | |
}; | |
App.ECS.prototype.RegisterEntity = function( entity ) { | |
this.entities.binaryInsert( entity, this._UniqueEntityCompare ); | |
for( var componentName in entity.components ) { | |
this.CacheComponentAdded( componentName, entity ); | |
this.EventTriggerAddComponent( componentName, entity ); | |
this.ReKey( entity.components[componentName], componentName ); | |
} | |
}; | |
App.ECS.prototype.ReKey = function( obj, objName ) { | |
if( Array.isArray( obj )) { | |
// --- we have an array of entities | |
for( var i = 0; i < obj.length; i++ ) { | |
// -- each entity | |
this.ReKeyEntity( obj[i] ); | |
} | |
} else if( obj.type == 'entity' ) { | |
// --- single entity | |
this.ReKeyEntity( obj ); | |
} else { | |
// --- component? | |
var component = this.components[objName]; | |
if( Array.isArray(component.values)) { | |
for( var i = 0; i < obj.length; i++ ) { | |
this.ReKeyEntity( obj[i] ); | |
} | |
} else { | |
for( var property in component.values ) { | |
if( component.values[property] === null ) { | |
// --- direct entity reference | |
if( obj[property] !== null ) { | |
this.ReKeyEntity( obj[property] ); | |
} | |
} else { | |
// --- just a component property. | |
} | |
} | |
} | |
} | |
}; | |
App.ECS.prototype.ReKeyEntity = function( entity ) { | |
entity.id = this._defaultEntity.getNextId(); | |
for( var componentName in entity.components ) { | |
this.ReKey( entity.components[componentName], componentName ); | |
} | |
}; | |
App.ECS.prototype._reservedPrefabWords = ['type','value']; | |
App.ECS.prototype.CreateEntityFromPrefab = function( prefabName ) { | |
var self = this; | |
var prefab = this._prefabs[prefabName]; | |
if( prefab == undefined ) { | |
console.warn('Could not find prefab by name of "' + prefabName + '".') | |
return null; | |
} | |
var entity = this.CreateEntity(); | |
var keys = this.FilterReserved( Object.keys(prefab) ); | |
for( var i = 0; i < keys.length; i++ ) { | |
this.AddComponentToEntity( keys[i], entity ); | |
entity.components[keys[i]] = Acc.Clone(prefab[keys[i]]); | |
var curComp = entity.components[keys[i]]; | |
this.ResolvePrefabComponent( curComp ); | |
} | |
return entity; | |
}; | |
App.ECS.prototype.ResolvePrefabComponent = function( component, externalData ) { | |
if( Array.isArray( component )) { | |
for( var i = 0; i < component.length; i++ ) { | |
component[i] = this.ResolvePrefabEntity( component[i] ); | |
} | |
} else if( component.values === null ) { | |
// --- pointer to entity | |
console.warn('not entirely sure how components pointing to another entity should work.', component); | |
} else { | |
var dataSource = (externalData == undefined ? component : externalData ); | |
var dataKeys = Object.keys( dataSource ); | |
for( var j = 0; j < dataKeys.length; j++ ) { | |
if( Array.isArray( dataSource[dataKeys[j]] )) { // --- array of entities | |
component[dataKeys[j]] = this.ResolvePrefabArray( dataSource[dataKeys[j]] ); | |
} else if( typeof dataSource[dataKeys[j]] == 'object' ) { // --- pointer to single entity | |
component[dataKeys[j]] = this.ResolvePrefabEntity( dataSource[dataKeys[j]] ); | |
} else { // --- just regular key:value data | |
component[dataKeys[j]] = dataSource[dataKeys[j]]; | |
} | |
} | |
} | |
}; | |
App.ECS.prototype.ResolvePrefabArray = function( array ) { | |
// --- possibly a list of prefabs in here | |
for( var k = 0; k < array.length; k++ ) { | |
array[k] = this.ResolvePrefabEntity( array[k] ); | |
} | |
return array; | |
}; | |
App.ECS.prototype.ResolvePrefabEntity = function( prefab ) { | |
if( prefab == null ) { | |
return null; | |
} | |
switch( prefab.type ) { | |
case 'prefab': | |
var data = Acc.Clone( prefab ); | |
var entity = this.CreateEntityFromPrefab( prefab.value ); | |
delete data.value; | |
delete data.type; | |
var prefabComponentOverrides = Object.keys(data); | |
for( var l = 0; l < prefabComponentOverrides.length; l++ ) { | |
if( entity.components[prefabComponentOverrides[l]] != undefined ) { | |
// --- the component exists on this entity | |
this.ResolvePrefabComponent( entity.components[prefabComponentOverrides[l]], data[prefabComponentOverrides[l]] ); | |
} | |
} | |
return entity; | |
default: | |
console.warn('unknown element type on list of prefab entities "' + prefab.type + '".', prefab ); | |
return null; | |
} | |
}; | |
App.ECS.prototype.GetEntitiesWithComponents = function( componentNames ) { | |
if( !Array.isArray( componentNames )) { | |
componentNames = [componentNames]; | |
} | |
var list = []; | |
for( var i = 0; i < componentNames.length; i++ ) { | |
if( i == 0 ) { | |
// --- just add all components with this type to the list | |
var compCache = this._entityComponentLookup[componentNames[i]]; | |
if( compCache != undefined ) { | |
for( var j = 0; j < compCache.length; j++ ) { | |
list.push(compCache[j]); | |
} | |
} | |
} else { | |
for( var j = 0; j < list.length; j++ ) { | |
if( list[j].components[componentNames[j]] == undefined ) { | |
list.removeAtIndex(j); | |
j--; | |
} | |
} | |
// --- ensure that all components in our list have the given compenentName | |
} | |
} | |
return list; | |
}; | |
App.ECS.prototype.GetAllChildrenEntities = function( entity, componentFilters ) { | |
if( componentFilters == undefined ) { | |
componentFilters = []; | |
} else if ( !Array.isArray( componentFilters )) { | |
componentFilters= [componentFilters]; | |
} | |
var children = this.RecursiveChildrenEntities( entity ); | |
if( componentFilters != undefined && componentFilters.length > 0 ) { | |
children = children.filter(function(c){ | |
var keys = Object.keys( c.components ); | |
for( var i = 0; i < componentFilters.length; i++ ) { | |
if( keys.indexOf( componentFilters[i] ) < 0 ) { | |
return false; | |
} | |
} | |
return true; | |
}); | |
} | |
return children; | |
} | |
App.ECS.prototype.RecursiveChildrenEntities = function( entity ) { | |
var children = []; | |
var compKeys = Object.keys(entity.components); | |
for( var i = 0; i < compKeys.length; i++ ) { | |
if( Array.isArray(entity.components[compKeys[i]] )) { | |
// --- list of entities | |
for( var j = 0; j < entity.components[compKeys[i]].length; j++ ) { | |
children.push( entity.components[compKeys[i]][j] ); | |
children = children.concat(this.GetAllChildrenEntities(entity.components[compKeys[i]][j])); | |
} | |
} else if( entity.components[compKeys[i]] !== null && entity.components[compKeys[i]].components != undefined ) { | |
// --- component is a singleton entity reference | |
children.push( entity.components[compKeys[i]] ); | |
children = children.concat(this.GetAllChildrenEntities(entity.components[compKeys[i]])); | |
} else { | |
// look through each attribute on the component to find singleton or array entities | |
var curComp = entity.components[compKeys[i]]; | |
var keys = Object.keys(curComp); | |
for( var j = 0; j < keys.length; j++ ) { | |
if( Array.isArray( curComp[keys[j]] )) { | |
// --- list of entities | |
for( var k = 0; k < curComp[keys[j]].length; k++ ) { | |
children.push( curComp[keys[j]][k] ); | |
children = children.concat(this.GetAllChildrenEntities(curComp[keys[j]][k])); | |
} | |
} else if( curComp[keys[j]] !== null && curComp[keys[j]].components != undefined ) { | |
children.push( curComp[keys[j]] ); | |
children = children.concat( this.GetAllChildrenEntities( curComp[keys[j]])); | |
} else { | |
// --- not an entity... ignore. | |
} | |
} | |
} | |
} | |
return children; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Entity structure: | |
{ | |
id: 0, | |
components: {} | |
} | |
if a component has an array value attribute, it can contain an array of entities | |
if a component has a value attribute equal to null, it can reference one entity. | |
*/ | |
App.prototype.ECSData = { | |
Components: [ | |
{ | |
name: 'dungeon-branch', // --- name of component | |
values: { // --- fields on component | |
name: 'none', | |
baseDifficulty: 0, | |
levels: [], // --- array of entitites | |
completionRewards: [] | |
} | |
},{ | |
name: 'dungeon-level', | |
values: { | |
depth: 0, | |
difficultyModifier: 0, | |
encounters: [], | |
completionRewards: [] | |
} | |
},{ | |
name: 'control', | |
values: { | |
hasInput: false, | |
isActive: true, | |
bindings: [] | |
} | |
},{ | |
name: 'hp', | |
values: { | |
cur: 0, | |
max: 0, | |
regen: 0 | |
} | |
},{ | |
name: 'mana', | |
values: { | |
cur: 0, | |
max: 0, | |
regen: 0 | |
} | |
},{ | |
name: 'soul', // --- used for temporary actors (e.g. summoned) and the PC. | |
values: { | |
cur: 0, | |
decay: 0 | |
} | |
},{ | |
name: 'skill', | |
values: { | |
name: 'none', | |
effects: [] | |
} | |
},{ | |
name: 'experience', | |
values: { | |
total: 0, | |
levelFormula: '{0}*({0}-1))*(10)', | |
levelFormula0: 'level', // --- the higher learnRate is, the faster level will increment | |
maxLevel: 30, | |
learnModifier: 0, // each point is +25% or -10% learn rate. | |
type: '' | |
} | |
},{ | |
name: 'timestamp', | |
values: { | |
time: 0 | |
} | |
},{ | |
name: 'body', | |
values: { | |
parts: [], // pointers to entities | |
partialSlots: [] // pointers to entities with itemSlots and partialCounts | |
} | |
},{ | |
name: 'bodypart', | |
values: { | |
partType: '', | |
name: '', | |
size: 0 | |
} | |
},{ | |
name: 'itemslots', | |
values: [] | |
},{ | |
name: 'itemslot', | |
values: { | |
itemType: '', | |
item: null | |
} | |
},{ | |
name: 'backpack', | |
values: [] // pointers to entities | |
},{ | |
name: 'identity', | |
values: { | |
isProper: false, | |
gender: 'n', | |
plural: 's', | |
title: '', | |
article: 'a', | |
pronoun: 'it', | |
pluralPronoun: 'they', | |
name: 'none' | |
} | |
},{ | |
name: 'item', | |
values: { | |
count: 1, | |
maxStack: 1, | |
quality: 5, | |
state: 1 | |
} | |
},{ | |
name: 'attack', | |
values: { | |
type: 'kinetic', | |
damage: '', | |
bonus: '', | |
toHit: '', | |
timeCost: '', | |
repeat: 0 | |
} | |
},{ | |
name: 'skillset', | |
values: [] // pointers to entities | |
},{ | |
name: 'action', | |
values: { | |
type: '', | |
} | |
},{ | |
name: 'actionlist', | |
values: [], // pointers to entities | |
},{ | |
name: 'partialitemslot', | |
values: { | |
itemType: '', | |
capacity: 0 | |
} | |
},{ | |
name: 'partialitemslots', | |
values: [] | |
},{ | |
name: 'cumulativeStats', | |
values: { | |
coldResistance: 0, | |
fireResistance: 0, | |
negativeResistance: 0, | |
magicResistance: 0, | |
poisonResistance: false, | |
lightningResistance: false, | |
seeInvisible: false, | |
hp: 0, | |
hpRegen: 0, | |
mana: 0, | |
manaRegen: 0, | |
ac: 0, | |
sh: 0, | |
stealth: 0, | |
strength: 0, | |
dexterity: 0, | |
intelligence: 0, | |
meleeSlaying: 0, | |
rangedSlaying: 0, | |
meleeToHit: 0, | |
rangedToHit: 0, | |
spellEnhancement: 0, | |
encumberance: 0 | |
} | |
},{ | |
name: 'coldResistance', | |
values: { | |
value: 0 | |
} | |
},{ | |
name: 'fireResistance', | |
values: { | |
value: 0 | |
} | |
},{ | |
name: 'negativeResistance', | |
values: { | |
value: 0 | |
} | |
},{ | |
name: 'magicResistance', | |
values: { | |
value: 0 | |
} | |
},{ | |
name: 'poisonResistance', | |
values: { | |
value: 0 | |
} | |
},{ | |
name: 'lightningResistance', | |
values: { | |
value: 0 | |
} | |
},{ | |
name: 'seeInvisible', | |
values: { | |
value: false | |
} | |
},{ | |
name: 'ac', | |
values: { | |
value: 0 | |
} | |
},{ | |
name: 'sh', | |
values: { | |
value: 0 | |
} | |
},{ | |
name: 'stealth', | |
values: { | |
value: 0 | |
} | |
},{ | |
name: 'strength', | |
values: { | |
value: 0 | |
} | |
},{ | |
name: 'intelligence', | |
values: { | |
value: 0 | |
} | |
},{ | |
name: 'dexterity', | |
values: { | |
value: 0 | |
} | |
},{ | |
name: 'meleeSlaying', | |
values: { | |
value: 0 | |
} | |
},{ | |
name: 'rangedSlaying', | |
values: { | |
value: 0 | |
} | |
},{ | |
name: 'meleeToHit', | |
values: { | |
value: 0 | |
} | |
},{ | |
name: 'rangedToHit', | |
values: { | |
value: 0 | |
} | |
},{ | |
name: 'spellEnhancement', | |
values: { | |
value: 0 | |
} | |
},{ | |
name: 'encumberance', | |
values: { | |
value: 0 | |
} | |
},{ | |
name: 'damage', | |
values: { | |
value: '1d1', | |
type: 'normal' | |
} | |
},{ | |
name: 'updateDelta', | |
values: { | |
delta: 0 | |
} | |
} | |
], | |
Prefabs: { | |
actor: { | |
skillset: [], | |
timestamp: {}, | |
identity: { gender: 'male', article: 'a', pronoun: 'he', name: 'actor' }, | |
action: {}, | |
body: {}, | |
experience: {}, | |
hp: {}, | |
mana: {}, | |
cumulativeStats: {}, | |
coldResistance: {}, | |
fireResistance: {}, | |
negativeResistance: {}, | |
magicResistance: {}, | |
poisonResistance: {}, | |
lightningResistance: {}, | |
seeInvisible: {}, | |
ac: {}, | |
sh: {}, | |
stealth: {}, | |
strength: {}, | |
intelligence: {}, | |
dexterity: {}, | |
meleeSlaying: {}, | |
rangedSlaying: {}, | |
meleeToHit: {}, | |
rangedToHit: {}, | |
spellEnhancement: {}, | |
encumberance: {} | |
}, | |
humanoid: { | |
base: 'actor', // --- indicator that this prefab is based on the "actor" prefab. | |
body: { | |
parts: [ | |
{ type: 'prefab', value: 'head' }, | |
{ type: 'prefab', value: 'torso' }, | |
{ type: 'prefab', value: 'hand', bodypart: { name: 'left hand'}}, | |
{ type: 'prefab', value: 'hand', bodypart: { name: 'right hand'}}, | |
{ type: 'prefab', value: 'foot', bodypart: { name: 'left foot'}}, | |
{ type: 'prefab', value: 'foot', bodypart: { name: 'right foot'}}, | |
] | |
}, | |
backpack: {}, | |
itemslots: [], | |
}, | |
head: { | |
bodypart: { partType: 'head', name: 'head', size: 6 }, | |
itemslots: [{ | |
components: { | |
itemslot: { | |
itemType: 'helmet' | |
} | |
} | |
}] | |
}, | |
torso: { | |
bodypart: { partType: 'torso', name: 'torso', size: 40 }, | |
itemslots: [{ | |
components: { | |
itemslot: { | |
itemType: 'body-armour' | |
} | |
} | |
},{ | |
components: { | |
itemslot:{ | |
itemType: 'amulet' | |
} | |
} | |
},{ | |
components: { | |
itemslot: { | |
itemType: 'cloak' | |
} | |
} | |
}], | |
}, | |
hand: { | |
bodypart: { partType: 'hand', name: 'hand', size: 8 }, | |
partialitemslots: [{ | |
components: { | |
partialitemslot: { | |
itemType: 'gloves', | |
capacity: 0.5 | |
} | |
} | |
},{ | |
components: { | |
partialitemslot: { | |
itemType: 'hand', | |
capacity: 1 | |
} | |
} | |
}], | |
itemslots: [{ | |
components: { | |
itemslot: { | |
itemType: 'ring' | |
} | |
} | |
}] | |
}, | |
foot: { | |
bodypart: { partType: 'foot', name: 'foot', size: 14 }, | |
partialitemslots: [{ | |
components: { | |
partialitemslot: { | |
itemType: 'boots', | |
capacity: 0.5, | |
} | |
} | |
}], | |
}, | |
'skill-fighting': { | |
skill: { | |
name: 'fighting', | |
effects: [] | |
}, | |
experience: { | |
type: 'skill' | |
} | |
}, | |
'skill-fighting': { | |
skill: { | |
name: 'fighting', | |
effects: [] | |
}, | |
experience: { | |
} | |
} | |
} | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// -- clock is a system to run with the ECS above. | |
(function(){ | |
var self = { | |
name: 'clock', | |
requestedFPS: 30, | |
msBetweenUpdates: 0, | |
msLastUpdate: 0, | |
frameId: 0, | |
ecs: null, | |
updateRunning: false, | |
documentIsHidden: false, | |
documentIsHiddenTimestamp: 0, | |
listen: { | |
addComponent: [ | |
//{ name: 'sample', functionToExecute: 'processSamples', priority: 1 } | |
], | |
removeComponent: [ | |
//{ name: 'sample', functionToExecute: 'processSamples', priority: 1 } | |
] | |
}, | |
functions: { | |
init: function( ecs ) { | |
self.ecs = ecs; | |
console.log('init clock called'); | |
self.msBetweenUpdates = 1000 / self.requestedFPS; | |
window.requestAnimationFrame( self.functions.scheduleUpdate ); | |
var hidden, visibilityState, visibilityChange; | |
if (typeof document.hidden !== "undefined") { | |
hidden = "hidden", visibilityChange = "visibilitychange", visibilityState = "visibilityState"; | |
} else if (typeof document.msHidden !== "undefined") { | |
hidden = "msHidden", visibilityChange = "msvisibilitychange", visibilityState = "msVisibilityState"; | |
} | |
var document_hidden = document[hidden]; | |
document.addEventListener(visibilityChange, function() { | |
if(document_hidden != document[hidden]) { | |
if(document[hidden]) { | |
// Document hidden | |
self.documentIsHidden = true; | |
self.documentIsHiddenTimestamp = Date.now(); | |
console.log('document is now hidden'); | |
} else { | |
// Document shown | |
self.documentIsHidden = false; | |
var elapsedTime = (self.documentIsHiddenTimestamp == 0 ? 0 : Date.now() - self.documentIsHiddenTimestamp); | |
// --- do something with this elapsed time? | |
console.log('document is back after '+elapsedTime+'ms'); | |
var frames = ( elapsedTime / self.msBetweenUpdates ) >> 0; | |
console.log('catching up with ' + frames + ' missed frames.'); | |
if( frames > 0 ) { | |
self.frameId += frames; | |
self.functions.doUpdate( frames, ( self.msBetweenUpdates * frames ) / 1000 ); | |
} | |
self.documentIsHiddenTimestamp = 0; | |
} | |
document_hidden = document[hidden]; | |
} | |
}); | |
}, | |
setFPS: function( ecs, fps ) { | |
self.requestedFPS = fps; | |
self.msBetweenUpdates = 1000 / self.requestedFPS; | |
}, | |
scheduleUpdate: function( timeStamp ) { | |
var ecs = self.ecs; | |
var frames = (( timeStamp - self.msLastUpdate ) / self.msBetweenUpdates ) >> 0; | |
if( frames > 0 ) { | |
// --- update needs to occur | |
self.msLastUpdate += self.msBetweenUpdates * frames; | |
self.functions.doUpdate( frames, (self.msBetweenUpdates * frames) / 1000 ); | |
self.frameId += frames; | |
} | |
window.requestAnimationFrame( self.functions.scheduleUpdate ); | |
}, | |
doUpdate: function( frames, seconds ) { | |
if( self.updateRunning ) { return; } | |
var ecs = self.ecs; | |
self.updateRunning = true; | |
ecs.TriggerEvent('update', { frames: frames, seconds: seconds }); | |
self.updateRunning = false; | |
ecs.parent.view.Render( ecs.parent.game.world ); | |
} | |
} | |
}; | |
App.prototype._Systems.push( self ); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment