<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<div id="board"></div>
<button id="gravityButtonRemove">Remove gravity</button>
<button id="gravityButtonAdd">Add gravity</button>
<br>
<button id="redColorRemove">Remove red color</button>
<button id="redColorAdd">Add red color</button>
</body>
</html>
const canvas = document.createElement("canvas")
canvas.width = canvas.height = 500
canvas.style.backgroundColor = "black"
const ctx = canvas.getContext("2d")
document.getElementById("board").appendChild(canvas)
// buttons
const gravityButtonRemove = document.getElementById("gravityButtonRemove")
const gravityButtonAdd = document.getElementById("gravityButtonAdd")
const redColorRemove = document.getElementById("redColorRemove")
const redColorAdd = document.getElementById("redColorAdd")
gravityButtonRemove.addEventListener("click", () => {
ECS.removeComponentFromEntity(Box, "gravity")
})
gravityButtonAdd.addEventListener("click", () => {
ECS.addComponentToEntity(Box, gravityComponent)
// console.log(Box)
})
redColorRemove.addEventListener("click", () => {
ECS.removeComponentFromEntity(Box, "redColor")
console.log(Box)
})
redColorAdd.addEventListener("click", () => {
ECS.addComponentToEntity(Box, redColorComponent)
console.log(Box)
})
class EntityComponentSystem {
constructor() {
this.components = []
this.processors = []
this.entities = []
}
getProcessorNames() {
return this.processors.map(processor => processor.name)
}
hasProcessor(processorName) {
return this.processors.find(processor => processor.name === processorName)
}
hasComponent(componentName) {
return this.components.find(component => component.name === componentName)
}
hasEntity(entityName) {
return this.entities.find(entity => entity.name === entityName)
}
createEntity(name, components) {
let entity = {name, components}
components.forEach(component => {
const componentObject = this.components.find(ecsComponent => component === ecsComponent.name)
entity = Object.assign(entity, componentObject.state)
})
return entity
}
entityHasComponent(entity, componentName) {
return entity.components.includes(componentName)
}
removeComponentFromEntity(entity, componentName) {
const indexOfComponent = entity.components.indexOf(componentName)
if (indexOfComponent === -1) {
throw new Error("Component not found on entity")
} else {
entity.components = entity.components.filter(component => component !== componentName)
const component = ECS.getComponent(componentName)
const keys = Object.keys(component.state)
keys.forEach(key => {
delete entity[key]
})
}
}
addComponentToEntity(entity, component) {
console.log(ECS.entityHasComponent(entity, component.name))
if (ECS.entityHasComponent(entity, component.name)) {
return
}
entity = Object.assign(entity, component.state ? component.state : {})
entity.components.push(component.name)
}
addEntity(entity) {
this.entities.push(entity)
}
addComponent(component) {
this.components.push(component)
}
addProcessor(processor) {
this.processors.push(processor)
}
getEntitiesFromComponent(componentName) {
return this.entities.filter(entity => {
return entity.components.includes(componentName)
})
}
getComponent(componentName) {
return this.components.find(component => componentName === component.name)
}
runProcessors() {
this.processors.forEach(processor => {
const entities = this.getEntitiesFromComponent(processor.component)
const component = this.getComponent(processor.component)
processor.update(component, entities)
})
}
runCustomLogicOnEntities() {
this.entities.forEach(entity => {
if (entity.update) {
entity.update()
}
})
}
update() {
this.runProcessors()
this.runCustomLogicOnEntities()
}
}
function gameloop() {
ECS.update()
requestAnimationFrame(gameloop)
}
requestAnimationFrame(gameloop)
const ECS = new EntityComponentSystem()
// Components
const positionComponent = {
name: "position",
state: {
x: 0,
y: 0
}
}
const sizeComponent = {
name: "size",
state: {
size: 10
}
}
const renderingComponent = {
name: "render"
}
const gravityComponent = {
name: "gravity",
state: {
velocity: 0
}
}
const edgeHandlingComponent = {
name: "edgeHandler"
}
const redColorComponent = {
name: "redColor",
state: {
color: "red"
}
}
// register Components
ECS.addComponent(positionComponent)
ECS.addComponent(sizeComponent)
ECS.addComponent(renderingComponent)
ECS.addComponent(gravityComponent)
ECS.addComponent(edgeHandlingComponent)
ECS.addComponent(redColorComponent)
// Processors
const renderingProcessor = {
name: "RenderProcessor",
component: "position",
update(component, entities) {
ctx.clearRect(0, 0, canvas.height, canvas.width)
entities.forEach(entity => {
// console.log(entity)
const {x, y, size} = entity
ctx.beginPath()
ctx.fillStyle = entity.color ? entity.color : "white"
ctx.fillRect(x, y, size, size)
ctx.closePath()
})
}
}
const gravityProcessor = {
name: "GravityProcessor",
component: "gravity",
update(component, entities) {
entities.forEach(entity => {
entity.velocity += 0.02
entity.y += entity.velocity
})
}
}
const edgeHandlingProcessor = {
name: "EdgeHandleProcessor",
component: "edgeHandler",
update(component, entities) {
entities.forEach(entity => {
const {x, y, size} = entity
if (x + size > canvas.width) {
entity.x = canvas.width - size
}
if (x < 0) {
entity.x = 0
}
if (y < 0) {
entity.y = 0
}
if (y + size >= canvas.height) {
entity.velocity = -entity.velocity
}
})
}
}
// register Processors
ECS.addProcessor(renderingProcessor)
ECS.addProcessor(gravityProcessor)
ECS.addProcessor(edgeHandlingProcessor)
// Entities
const Box = ECS.createEntity("Box", ["position", "size", "render", "gravity", "edgeHandler", "redColor"])
Box.x = Box.y = 100
Box.size = 50
// Add Entities
ECS.addEntity(Box)
// ECS.removeComponentFromEntity(Box, "gravity")
Output
This bin was created anonymously and its free preview time has expired (learn why). — Get a free unrestricted account
Dismiss xKeyboard Shortcuts
Shortcut | Action |
---|---|
ctrl + [num] | Toggle nth panel |
ctrl + 0 | Close focused panel |
ctrl + enter | Re-render output. If console visible: run JS in console |
Ctrl + l | Clear the console |
ctrl + / | Toggle comment on selected lines |
ctrl + ] | Indents selected lines |
ctrl + [ | Unindents selected lines |
tab | Code complete & Emmet expand |
ctrl + shift + L | Beautify code in active panel |
ctrl + s | Save & lock current Bin from further changes |
ctrl + shift + s | Open the share options |
ctrl + y | Archive Bin |
Complete list of JS Bin shortcuts |
JS Bin URLs
URL | Action |
---|---|
/ | Show the full rendered output. This content will update in real time as it's updated from the /edit url. |
/edit | Edit the current bin |
/watch | Follow a Code Casting session |
/embed | Create an embeddable version of the bin |
/latest | Load the very latest bin (/latest goes in place of the revision) |
/[username]/last | View the last edited bin for this user |
/[username]/last/edit | Edit the last edited bin for this user |
/[username]/last/watch | Follow the Code Casting session for the latest bin for this user |
/quiet | Remove analytics and edit button from rendered output |
.js | Load only the JavaScript for a bin |
.css | Load only the CSS for a bin |
Except for username prefixed urls, the url may start with http://jsbin.com/abc and the url fragments can be added to the url to view it differently. |