Unlimited undo; add spritename component instead of sprites on each entity; update canvas on game object

This commit is contained in:
Logan Hunt 2022-04-10 22:13:05 -06:00
parent a23ea2e014
commit 69b5f4448c
33 changed files with 95 additions and 62 deletions

View File

@ -24,6 +24,7 @@
canvas: { canvas: {
width: document.getElementById('game-canvas').width, width: document.getElementById('game-canvas').width,
height: document.getElementById('game-canvas').height, height: document.getElementById('game-canvas').height,
context: document.getElementById('game-canvas').getContext("2d"),
} }
}; };
</script> </script>

4
src/bootstrap.js vendored
View File

@ -14,6 +14,7 @@ game.bootstrap = (() => {
'src/components/position.js', 'src/components/momentum.js', 'src/components/gridPosition.js', 'src/components/position.js', 'src/components/momentum.js', 'src/components/gridPosition.js',
'src/components/appearence.js', 'src/components/controllable.js', 'src/components/pushable.js', 'src/components/appearence.js', 'src/components/controllable.js', 'src/components/pushable.js',
'src/components/loadPriority.js', 'src/components/stop.js', 'src/components/alive.js', 'src/components/loadPriority.js', 'src/components/stop.js', 'src/components/alive.js',
'src/components/sprite.js'
], ],
id: 'components' id: 'components'
}, },
@ -32,7 +33,8 @@ game.bootstrap = (() => {
{ src: ['src/systems/system.js'], id: 'system' }, { src: ['src/systems/system.js'], id: 'system' },
{ {
src: [ src: [
'src/systems/render.js', 'src/systems/grid.js', 'src/systems/physics.js', 'src/systems/keyboardInput.js', 'src/systems/collision.js', 'src/systems/render.js', 'src/systems/grid.js', 'src/systems/physics.js', 'src/systems/keyboardInput.js',
'src/systems/collision.js', 'src/systems/undo.js'
], ],
id: 'systems' }, id: 'systems' },
{ src: ['src/game.js'], id: 'game' }, { src: ['src/game.js'], id: 'game' },

1
src/components/sprite.js Normal file
View File

@ -0,0 +1 @@
game.components.Sprite = ({spriteName}) => game.Component('sprite', {spriteName});

View File

@ -1 +1 @@
game.components.Stop = ({stop}) => game.Component("stop", {stop}); game.components.Stop = () => game.Component("stop");

View File

@ -3,7 +3,7 @@ game.createBigBlue = () => {
bigBlue.addComponent(game.components.LoadPriority({priority: 1})); bigBlue.addComponent(game.components.LoadPriority({priority: 1}));
bigBlue.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100})); bigBlue.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100}));
bigBlue.addComponent(game.components.Alive()); bigBlue.addComponent(game.components.Alive());
bigBlue.sprite = game.sprites.bigBlue; bigBlue.addComponent(game.components.Sprite({spriteName: "bigBlue"}))
// TODO: Remove this // TODO: Remove this
bigBlue.addComponent(game.components.Controllable({controls: ['left', 'right', 'up', 'down']})); bigBlue.addComponent(game.components.Controllable({controls: ['left', 'right', 'up', 'down']}));

View File

@ -31,9 +31,7 @@ game.createBorderParticles = ({colors, maxAmount, minAmount, minLife, maxLife, m
} }
return particle; return particle;
}); });
particles.addComponent(game.components.LoadPriority({priority: 1})); game.sprites.borderParticle = game.graphics.Sprite({
particles.addComponent(game.components.Alive());
particles.sprite = game.graphics.Sprite({
drawFunction: (elapsedTime, {x, y, width, height}, context) => { drawFunction: (elapsedTime, {x, y, width, height}, context) => {
particleSpecs.map((spec) => spec.elapsed += elapsedTime); particleSpecs.map((spec) => spec.elapsed += elapsedTime);
particleSpecs = particleSpecs.filter((spec) => spec.lifetime > spec.elapsed); particleSpecs = particleSpecs.filter((spec) => spec.lifetime > spec.elapsed);
@ -51,5 +49,8 @@ game.createBorderParticles = ({colors, maxAmount, minAmount, minLife, maxLife, m
}); });
} }
}) })
particles.addComponent(game.components.LoadPriority({priority: 1}));
particles.addComponent(game.components.Alive());
particles.addComponent(game.components.Sprite({spriteName: "borderParticle"}))
return particles; return particles;
} }

View File

@ -3,6 +3,6 @@ game.createFlag = () => {
flag.addComponent(game.components.LoadPriority({priority: 2})); flag.addComponent(game.components.LoadPriority({priority: 2}));
flag.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100})); flag.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100}));
flag.addComponent(game.components.Alive()); flag.addComponent(game.components.Alive());
flag.sprite = game.sprites.flag; flag.addComponent(game.components.Sprite({spriteName: "flag"}))
return flag; return flag;
} }

View File

@ -3,6 +3,6 @@ game.createFloor = () => {
floor.addComponent(game.components.LoadPriority({priority: 5})); floor.addComponent(game.components.LoadPriority({priority: 5}));
floor.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100})); floor.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100}));
floor.addComponent(game.components.Alive()); floor.addComponent(game.components.Alive());
floor.sprite = game.sprites.floor; floor.addComponent(game.components.Sprite({spriteName: "floor"}))
return floor; return floor;
} }

View File

@ -3,6 +3,6 @@ game.createGrass = () => {
grass.addComponent(game.components.LoadPriority({priority: 6})); grass.addComponent(game.components.LoadPriority({priority: 6}));
grass.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100})); grass.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100}));
grass.addComponent(game.components.Alive()); grass.addComponent(game.components.Alive());
grass.sprite = game.sprites.grass; grass.addComponent(game.components.Sprite({spriteName: "grass"}))
return grass; return grass;
} }

View File

@ -4,6 +4,6 @@ game.createHedge = () => {
hedge.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100})); hedge.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100}));
hedge.addComponent(game.components.Stop({stop: true})); hedge.addComponent(game.components.Stop({stop: true}));
hedge.addComponent(game.components.Alive()); hedge.addComponent(game.components.Alive());
hedge.sprite = game.sprites.hedge; hedge.addComponent(game.components.Sprite({spriteName: "hedge"}))
return hedge; return hedge;
} }

View File

@ -1,10 +1,9 @@
game.createLava = () => { game.createLava = () => {
// TODO: Split this into two entities: water and lava
const lava = game.Entity(); const lava = game.Entity();
lava.addComponent(game.components.LoadPriority({priority: 5})); lava.addComponent(game.components.LoadPriority({priority: 5}));
lava.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100})); lava.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100}));
lava.addComponent(game.components.Alive()); lava.addComponent(game.components.Alive());
lava.sprite = game.sprites.lava; lava.addComponent(game.components.Sprite({spriteName: "lava"}))
return lava; return lava;
} }

View File

@ -3,7 +3,7 @@ game.createRock = () => {
rock.addComponent(game.components.LoadPriority({priority: 2})); rock.addComponent(game.components.LoadPriority({priority: 2}));
rock.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100})); rock.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100}));
rock.addComponent(game.components.Alive()); rock.addComponent(game.components.Alive());
rock.sprite = game.sprites.rock; rock.addComponent(game.components.Sprite({spriteName: "rock"}))
//TODO: Remove this //TODO: Remove this
rock.addComponent(game.components.Pushable()); rock.addComponent(game.components.Pushable());

View File

@ -2,8 +2,8 @@ game.createWall = () => {
const wall = game.Entity(); const wall = game.Entity();
wall.addComponent(game.components.LoadPriority({priority: 3})); wall.addComponent(game.components.LoadPriority({priority: 3}));
wall.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100})); wall.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100}));
wall.addComponent(game.components.Stop({stop: true})); wall.addComponent(game.components.Stop());
wall.addComponent(game.components.Alive()); wall.addComponent(game.components.Alive());
wall.sprite = game.sprites.wall; wall.addComponent(game.components.Sprite({spriteName: "wall"}))
return wall; return wall;
} }

View File

@ -3,12 +3,6 @@ game.createWater = () => {
water.addComponent(game.components.LoadPriority({priority: 5})); water.addComponent(game.components.LoadPriority({priority: 5}));
water.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100})); water.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100}));
water.addComponent(game.components.Alive()); water.addComponent(game.components.Alive());
water.sprite = game.graphics.Sprite({ water.addComponent(game.components.Sprite({spriteName: "water"}))
image: game.assets.water,
spriteHeight: 24,
spriteWidth: 24,
numFrames: 3,
timePerFrame: 100,
});
return water; return water;
} }

View File

@ -4,6 +4,6 @@ game.createWordBigBlue = () => {
wordBigBlue.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100})); wordBigBlue.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100}));
wordBigBlue.addComponent(game.components.Pushable({pushable: true})); wordBigBlue.addComponent(game.components.Pushable({pushable: true}));
wordBigBlue.addComponent(game.components.Alive()); wordBigBlue.addComponent(game.components.Alive());
wordBigBlue.sprite = game.sprites.wordBigBlue; wordBigBlue.addComponent(game.components.Sprite({spriteName: "wordBigBlue"}))
return wordBigBlue; return wordBigBlue;
} }

View File

@ -4,6 +4,6 @@ game.createWordFlag = () => {
wordFlag.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100})); wordFlag.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100}));
wordFlag.addComponent(game.components.Pushable({pushable: true})); wordFlag.addComponent(game.components.Pushable({pushable: true}));
wordFlag.addComponent(game.components.Alive()); wordFlag.addComponent(game.components.Alive());
wordFlag.sprite = game.sprites.wordFlag; wordFlag.addComponent(game.components.Sprite({spriteName: "wordFlag"}))
return wordFlag; return wordFlag;
} }

View File

@ -5,6 +5,6 @@ game.createWordIs = () => {
// wordIs.addComponent(game.components.Stop({stop: true})); // wordIs.addComponent(game.components.Stop({stop: true}));
wordIs.addComponent(game.components.Pushable()); wordIs.addComponent(game.components.Pushable());
wordIs.addComponent(game.components.Alive()); wordIs.addComponent(game.components.Alive());
wordIs.sprite = game.sprites.wordIs; wordIs.addComponent(game.components.Sprite({spriteName: "wordIs"}))
return wordIs; return wordIs;
} }

View File

@ -4,6 +4,6 @@ game.createWordKill = () => {
wordKill.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100})); wordKill.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100}));
wordKill.addComponent(game.components.Pushable({pushable: true})); wordKill.addComponent(game.components.Pushable({pushable: true}));
wordKill.addComponent(game.components.Alive()); wordKill.addComponent(game.components.Alive());
wordKill.sprite = game.sprites.wordKill; wordKill.addComponent(game.components.Sprite({spriteName: "wordKill"}))
return wordKill; return wordKill;
} }

View File

@ -5,6 +5,6 @@ game.createWordLava = () => {
wordLava.addComponent(game.components.Pushable({pushable: true})); wordLava.addComponent(game.components.Pushable({pushable: true}));
wordLava.addComponent(game.components.Alive()); wordLava.addComponent(game.components.Alive());
wordLava.sprite = game.sprites.wordLava; wordLava.addComponent(game.components.Sprite({spriteName: "wordLava"}))
return wordLava; return wordLava;
} }

View File

@ -4,6 +4,6 @@ game.createWordPush = () => {
wordPush.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100})); wordPush.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100}));
wordPush.addComponent(game.components.Pushable({pushable: true})); wordPush.addComponent(game.components.Pushable({pushable: true}));
wordPush.addComponent(game.components.Alive()); wordPush.addComponent(game.components.Alive());
wordPush.sprite = game.sprites.wordPush; wordPush.addComponent(game.components.Sprite({spriteName: "wordPush"}))
return wordPush; return wordPush;
} }

View File

@ -4,6 +4,6 @@ game.createWordRock = () => {
wordRock.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100})); wordRock.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100}));
wordRock.addComponent(game.components.Pushable({pushable: true})); wordRock.addComponent(game.components.Pushable({pushable: true}));
wordRock.addComponent(game.components.Alive()); wordRock.addComponent(game.components.Alive());
wordRock.sprite = game.sprites.wordRock; wordRock.addComponent(game.components.Sprite({spriteName: "wordRock"}))
return wordRock; return wordRock;
} }

View File

@ -4,6 +4,6 @@ game.createWordSink = () => {
wordSink.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100})); wordSink.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100}));
wordSink.addComponent(game.components.Pushable({pushable: true})); wordSink.addComponent(game.components.Pushable({pushable: true}));
wordSink.addComponent(game.components.Alive()); wordSink.addComponent(game.components.Alive());
wordSink.sprite = game.sprites.wordSink; wordSink.addComponent(game.components.Sprite({spriteName: "wordSink"}))
return wordSink; return wordSink;
} }

View File

@ -4,6 +4,6 @@ game.createWordStop = () => {
wordStop.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100})); wordStop.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100}));
wordStop.addComponent(game.components.Pushable({pushable: true})); wordStop.addComponent(game.components.Pushable({pushable: true}));
wordStop.addComponent(game.components.Alive()); wordStop.addComponent(game.components.Alive());
wordStop.sprite = game.sprites.wordStop; wordStop.addComponent(game.components.Sprite({spriteName: "wordStop"}))
return wordStop; return wordStop;
} }

View File

@ -4,6 +4,6 @@ game.createWordWall = () => {
wordWall.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100})); wordWall.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100}));
wordWall.addComponent(game.components.Pushable({pushable: true})); wordWall.addComponent(game.components.Pushable({pushable: true}));
wordWall.addComponent(game.components.Alive()); wordWall.addComponent(game.components.Alive());
wordWall.sprite = game.sprites.wordWall; wordWall.addComponent(game.components.Sprite({spriteName: "wordWall"}))
return wordWall; return wordWall;
} }

View File

@ -4,6 +4,6 @@ game.createWordWater = () => {
wordWater.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100})); wordWater.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100}));
wordWater.addComponent(game.components.Pushable({pushable: true})); wordWater.addComponent(game.components.Pushable({pushable: true}));
wordWater.addComponent(game.components.Alive()); wordWater.addComponent(game.components.Alive());
wordWater.sprite = game.sprites.wordWater; wordWater.addComponent(game.components.Sprite({spriteName: "wordWater"}))
return wordWater; return wordWater;
} }

View File

@ -4,6 +4,6 @@ game.createWordWin = () => {
wordWin.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100})); wordWin.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100}));
wordWin.addComponent(game.components.Pushable({pushable: true})); wordWin.addComponent(game.components.Pushable({pushable: true}));
wordWin.addComponent(game.components.Alive()); wordWin.addComponent(game.components.Alive());
wordWin.sprite = game.sprites.wordWin; wordWin.addComponent(game.components.Sprite({spriteName: "wordWin"}))
return wordWin; return wordWin;
} }

View File

@ -4,6 +4,6 @@ game.createWordYou = () => {
wordYou.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100})); wordYou.addComponent(game.components.Appearance({rot: 0, width: 100, height: 100}));
wordYou.addComponent(game.components.Pushable({pushable: true})); wordYou.addComponent(game.components.Pushable({pushable: true}));
wordYou.addComponent(game.components.Alive()); wordYou.addComponent(game.components.Alive());
wordYou.sprite = game.sprites.wordYou; wordYou.addComponent(game.components.Sprite({spriteName: "wordYou"}))
return wordYou; return wordYou;
} }

View File

@ -26,14 +26,14 @@ game.initialize = () => {
// Maintained by gridSystem as a side-effect // Maintained by gridSystem as a side-effect
game.entitiesGrid = Array(game.config.yDim).fill(null).map(() => Array(game.config.xDim).fill(null).map(() => new Map())); game.entitiesGrid = Array(game.config.yDim).fill(null).map(() => Array(game.config.xDim).fill(null).map(() => new Map()));
game.systemOrder = ["gridSystem", "collisionSystem", "physics", "keyboardInput", "render"]; game.systemOrder = ["gridSystem", "collisionSystem", "physics", "keyboardInput", "undo", "render"];
game.systems = { game.systems = { };
physics: game.system.Physics(game.entitiesGrid), game.systems.physics = game.system.Physics(),
gridSystem: game.system.Grid(game.entitiesGrid), game.systems.gridSystem = game.system.Grid(game.entitiesGrid);
collisionSystem: game.system.Collision(game.entitiesGrid), game.systems.collisionSystem = game.system.Collision(game.entitiesGrid);
keyboardInput: game.system.KeyboardInput(), game.systems.render = game.system.Render(game.graphics);
render: game.system.Render(game.graphics), game.systems.undo = game.system.Undo(game.entitiesGrid);
}; game.systems.keyboardInput = game.system.KeyboardInput(game.systems.undo);
lastTimeStamp = performance.now() lastTimeStamp = performance.now()
requestAnimationFrame(game.loop); requestAnimationFrame(game.loop);

View File

@ -52,4 +52,4 @@ game.graphics = (
return { clear, Sprite }; return { clear, Sprite };
} }
)(document.getElementById("game-canvas").getContext("2d")); )(game.canvas.context);

View File

@ -1,5 +1,6 @@
game.system.Collision = (entitiesGrid) => { game.system.Collision = (entitiesGrid) => {
const update = (elapsedTime, entities, changedIds) => { const update = (elapsedTime, entities, changedIds) => {
const thisChangedIds = new Set();
for (let entity of Object.keys(entities).map((id) => entities[id])) { for (let entity of Object.keys(entities).map((id) => entities[id])) {
if (entity.hasComponent("controllable") && entity.hasComponent("gridPosition") && entity.hasComponent("momentum")) { if (entity.hasComponent("controllable") && entity.hasComponent("gridPosition") && entity.hasComponent("momentum")) {
const momentum = unitize(entity.components.momentum); const momentum = unitize(entity.components.momentum);
@ -38,25 +39,25 @@ game.system.Collision = (entitiesGrid) => {
entity.removeComponent("momentum"); entity.removeComponent("momentum");
} else { } else {
entitiesToPush.map((e) => { entitiesToPush.map((e) => {
const particles = game.createBorderParticles({ // const particles = game.createBorderParticles({
colors: ["#16f7c9", "#0d6e5a", "#2fa18a", "#48cfb4", "#58877d", "#178054", "#2cdb92"], // colors: ["#16f7c9", "#0d6e5a", "#2fa18a", "#48cfb4", "#58877d", "#178054", "#2cdb92"],
maxSpeed: 0.20, // maxSpeed: 0.20,
minRadius: 1, // minRadius: 1,
maxRadius: 3, // maxRadius: 3,
minLife: 100, // minLife: 100,
maxLife: 300, // maxLife: 300,
minAmount: 20, // minAmount: 20,
maxAmount: 50, // maxAmount: 50,
}); // });
particles.addComponent(game.components.Position(e.components.position)); // particles.addComponent(game.components.Position(e.components.position));
particles.addComponent(game.components.Appearance({width: game.canvas.width / game.config.xDim, height: game.canvas.height / game.config.yDim})); // particles.addComponent(game.components.Appearance({width: game.canvas.width / game.config.xDim, height: game.canvas.height / game.config.yDim}));
game.entities[particles.id] = particles; // game.entities[particles.id] = particles;
e.addComponent(game.components.Momentum({...momentum})) e.addComponent(game.components.Momentum({...momentum}))
}); });
} }
} }
} }
return new Set(); return thisChangedIds;
}; };
return { update }; return { update };
}; };

View File

@ -1,4 +1,4 @@
game.system.KeyboardInput = () => { game.system.KeyboardInput = (undoSystem) => {
"use strict"; "use strict";
const keys = {}; const keys = {};
const keyPress = (event) => { const keyPress = (event) => {
@ -20,9 +20,11 @@ game.system.KeyboardInput = () => {
} else if (controls.includes('down') && keys['ArrowDown']) { } else if (controls.includes('down') && keys['ArrowDown']) {
entity.addComponent(game.components.Momentum({ dx: 0, dy: 1 })); entity.addComponent(game.components.Momentum({ dx: 0, dy: 1 }));
} }
} }
} }
if (keys['z']) {
undoSystem.undo(entities);
}
Object.keys(keys).map((key) => delete keys[key]); Object.keys(keys).map((key) => delete keys[key]);
return new Set(); return new Set();

View File

@ -10,8 +10,8 @@ game.system.Render = (graphics) => {
}); });
sortedEntities.forEach((entity) => { sortedEntities.forEach((entity) => {
if (entity.sprite && entity.hasComponent("position") && entity.hasComponent("appearance")) { if (entity.hasComponent("position") && entity.hasComponent("appearance") && entity.hasComponent("sprite")) {
entity.sprite.draw(elapsedTime, {...entity.components.position, ...entity.components.appearance}); game.sprites[entity.components.sprite.spriteName].draw(elapsedTime, {...entity.components.position, ...entity.components.appearance});
} }
}); });

32
src/systems/undo.js Normal file
View File

@ -0,0 +1,32 @@
game.system.Undo = (entitiesGrid) => {
const states = [];
const update = (elapsedTime, entities, changedIds) => {
if (changedIds.size) {
lastUndid = false;
const state = {};
for (let id in entities) {
if (entities[id].hasComponent("gridPosition")) {
state[id] = JSON.parse(JSON.stringify(entities[id].components));
}
}
states.push(state);
}
return new Set();
}
const undo = (entities) => {
states.map((state) => console.log(state[65].gridPosition));
let state = states.slice(0, -1).pop();
for (let id in state) {
for (let componentName in state[id]) {
entities[id].addComponent({name: componentName, ...state[id][componentName]});
}
}
if (states.length > 1) {
states.pop();
}
}
return { update, undo };
}