Created
October 2, 2023 20:02
-
-
Save GoodNovember/2953e36893b9e20d772d6279b47b96f0 to your computer and use it in GitHub Desktop.
an Entity Component System written in JavaScript
This file contains hidden or 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
// ENTITIES | |
let counter = 0 | |
let entities = new Set() | |
/** | |
* Creates a new entity and returns its id | |
* @returns {number} id of the new entity | |
*/ | |
export function makeEntity(){ | |
let id = counter++ | |
if(id > Number.MAX_SAFE_INTEGER){ | |
throw new Error('Too many entities!') | |
} | |
entities.add(id) | |
return id | |
} | |
/** | |
* Destroys an entity | |
* @param {number} entity id of the entity to destroy | |
* @returns {void} | |
* @throws if the entity does not exist | |
**/ | |
export function destroyEntity(entity){ | |
ensureEntityExists(entity) | |
// remove all components from the entity | |
const names = getComponentNamesForEntity(entity) | |
for(let name of names){ | |
removeComponent(entity, name) | |
} | |
// remove the entity | |
entities.delete(entity) | |
} | |
export function clear(){ | |
counter = 0 | |
entities.clear() | |
components.clear() | |
} | |
/** | |
* Checks if an entity exists | |
* @param {number} id id of the entity to check | |
* @returns {boolean} true if the entity exists, false otherwise | |
*/ | |
export function entityExists(id){ | |
return entities.has(id) | |
} | |
/** | |
* Checks if an entity does not exist, | |
* error if it does not exist | |
* @param {number} id id of the entity to check | |
* @throws if the entity does not exist | |
*/ | |
function ensureEntityExists(id){ | |
if(entityExists(id) === false){ | |
throw new Error(`Entity ${id} does not exist`) | |
} | |
} | |
/** | |
* Gets all entities | |
* @returns {Set} set of all entities | |
*/ | |
export function getEntities(){ | |
return entities | |
} | |
/** | |
* Creates an entity from an object, | |
* where the keys are the component names | |
* @param {object} ingredients object of components | |
* @returns {number} id of the new entity | |
*/ | |
export function makeEntityFromObject(ingredients){ | |
const keys = Object.keys(ingredients) | |
const entity = makeEntity() | |
for(let key of keys){ | |
addComponent(entity, key, ingredients[key]) | |
} | |
return entity | |
} | |
// COMPONENTS | |
let components = new Map() | |
/** | |
* Ensure a component was registered. | |
* If it was not, register it and create a new map for it | |
* @param {string} name | |
*/ | |
function ensureComponentWasRegistered(name){ | |
if(!components.has(name)){ | |
components.set(name, new Map()) | |
} | |
} | |
/** | |
* Adds a component to an entity | |
* @param {number} entity the entity to add the component to | |
* @param {string} name name of the component | |
* @param {*} component the component to add | |
*/ | |
export function addComponent(entity, name, component){ | |
ensureEntityExists(entity) | |
ensureComponentWasRegistered(name) | |
components.get(name).set(entity, component) | |
} | |
/** | |
* Removes a component from an entity | |
* @param {number} entity the entity to remove the component from | |
* @param {string} name the name of the component to remove | |
*/ | |
export function removeComponent(entity, name){ | |
ensureEntityExists(entity) | |
ensureComponentWasRegistered(name) | |
components.get(name).delete(entity) | |
} | |
/** | |
* Gets a component from an entity | |
* @param {number} entity the entity to get the component from | |
* @param {string} name the name of the component to get | |
* @returns {*} the component of the entity | |
*/ | |
export function getComponent(entity, name){ | |
ensureEntityExists(entity) | |
ensureComponentWasRegistered(name) | |
return components.get(name).get(entity) | |
} | |
/** | |
* Checks if an entity has a component | |
* @param {number} entity the entity to check | |
* @param {string} name the name of the component to check | |
* @returns {boolean} true if the entity has the component, false otherwise | |
* @throws if the entity does not exist | |
*/ | |
export function hasComponent(entity, name){ | |
ensureEntityExists(entity) | |
ensureComponentWasRegistered(name) | |
return components.get(name).has(entity) | |
} | |
/** | |
* Gets all entities with a component | |
* @param {string} name the name of the component | |
* @returns {Set} set of all entities with the component | |
*/ | |
export function getEntitiesWithComponent(name){ | |
ensureComponentWasRegistered(name) | |
return components.get(name).keys() | |
} | |
/** | |
* Gets all entities with a set of components | |
* @param {[string]} names the names of the components | |
* @returns {Set} set of all entities with the components | |
*/ | |
export function getEntitiesWithComponents(names){ | |
let entities = new Set() | |
for(let name of names){ | |
ensureComponentWasRegistered(name) | |
for(let entity of components.get(name).keys()){ | |
entities.add(entity) | |
} | |
} | |
return entities | |
} | |
/** | |
* Gets an array of all component names for an entity | |
* @param {number} entity the entity to get the components of | |
* @returns {[string]} array of component names | |
*/ | |
export function getComponentNamesForEntity(entity){ | |
ensureEntityExists(entity) | |
let names = [] | |
for(let [name, map] of components){ | |
if(map.has(entity)){ | |
names.push(name) | |
} | |
} | |
return names | |
} | |
/** | |
* Sets a component of an entity | |
* @param {number} entity the entity to set the component of | |
* @param {string} name the name of the component to set | |
* @param {*} component the component to set | |
*/ | |
export function setComponent(entity, name, component){ | |
ensureEntityExists(entity) | |
ensureComponentWasRegistered(name) | |
components.get(name).set(entity, component) | |
} | |
/** | |
* Checks if an entity has a component | |
* @param {number} entity the entity to check | |
* @param {string} name the name of the component to check | |
* @returns {boolean} true if the entity has the component, false otherwise | |
*/ | |
export function entityHasComponent(entity, name){ | |
ensureEntityExists(entity) | |
ensureComponentWasRegistered(name) | |
return components.get(name).has(entity) | |
} | |
/** | |
* Checks if an entity has all components in a list | |
* @param {number} entity the entity to check | |
* @param {[string]} names the names of the components to check | |
* @returns {boolean} true if the entity has all the components, false otherwise | |
*/ | |
export function entityHasComponents(entity, names){ | |
ensureEntityExists(entity) | |
for(let name of names){ | |
if(!entityHasComponent(entity, name)){ | |
return false | |
} | |
} | |
return true | |
} | |
/** | |
* Checks if an entity does not have a component | |
* @param {number} entity the entity to check | |
* @param {string} name the name of the component to check | |
* @returns {boolean} true if the entity does not have the component, false otherwise | |
*/ | |
export function entityHasNoComponent(entity, name){ | |
return !entityHasComponent(entity, name) | |
} | |
/** | |
* Executes a callback for each entity with a given component | |
* @param {string} name the name of the component | |
* @param {function} callback the callback to call for each entity with the component | |
*/ | |
export function eachEntityWithComponent(name, callback){ | |
ensureComponentWasRegistered(name) | |
let entities = getEntitiesWithComponent(name) | |
for(let entity of entities){ | |
callback(entity) | |
} | |
} | |
/** | |
* Executes a callback for each entity with a given set of components | |
* @param {[string]} names the names of the components | |
* @param {function} callback the callback to call for each entity with the components | |
*/ | |
export function eachEntityWithComponents(names, callback){ | |
const entities = getEntitiesWithComponents(names) | |
for(let entity of entities){ | |
callback(entity) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment