add byte beasties escape webtoy
BIN
src/assets/_root/webtoys/bbe/beasties/blinker_shadow_red.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
src/assets/_root/webtoys/bbe/beasties/bobbler_shadow_yellow.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
src/assets/_root/webtoys/bbe/beasties/chomper_shadow_blue.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
src/assets/_root/webtoys/bbe/beasties/creeper_shadow_red.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
src/assets/_root/webtoys/bbe/beasties/diver_shadow_blue.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
src/assets/_root/webtoys/bbe/beasties/gawker_shadow_yellow.png
Normal file
After Width: | Height: | Size: 8.4 KiB |
BIN
src/assets/_root/webtoys/bbe/beasties/grabber_shadow_red.png
Normal file
After Width: | Height: | Size: 7.8 KiB |
BIN
src/assets/_root/webtoys/bbe/beasties/puffer_shadow_green.png
Normal file
After Width: | Height: | Size: 7.9 KiB |
BIN
src/assets/_root/webtoys/bbe/beasties/reacher_shadow_yellow.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
src/assets/_root/webtoys/bbe/beasties/scanner_shadow_red.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
src/assets/_root/webtoys/bbe/beasties/seer_shadow_green.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
src/assets/_root/webtoys/bbe/beasties/shuffler_shadow_yellow.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
src/assets/_root/webtoys/bbe/beasties/snapper_shadow_blue.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
src/assets/_root/webtoys/bbe/beasties/spooker_shadow_green.png
Normal file
After Width: | Height: | Size: 7.4 KiB |
BIN
src/assets/_root/webtoys/bbe/beasties/thinker_shadow_green.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
src/assets/_root/webtoys/bbe/beasties/walker_shadow_blue.png
Normal file
After Width: | Height: | Size: 11 KiB |
74
src/assets/_root/webtoys/bbe/index.html
Normal file
@ -0,0 +1,74 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<head>
|
||||
<title>Byte Beasties: Escape!</title>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="content" id="content">
|
||||
<h1>Byte Beasties: Escape!</h1>
|
||||
<noscript>
|
||||
Sorry, this game requires JS to function.
|
||||
</noscript>
|
||||
|
||||
<div class="game" id="game">
|
||||
<div class="settings" id="settings">
|
||||
<div>
|
||||
<h2>Story</h2>
|
||||
|
||||
<p>
|
||||
The Byte Beasties are on the run from the GTP (General Thinking Program),
|
||||
an evil AI that wants to absorb them into its software.
|
||||
</p>
|
||||
<p>
|
||||
Help the Byte Beasties escape GTP's yellow absorption wave by
|
||||
hitting the green logic gates and avoiding the red NULL gates.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<form id="settingsForm" name="settings"" method="dialog"">
|
||||
<h2>Settings</h2>
|
||||
|
||||
<p>
|
||||
<label>Mode:
|
||||
<select name="mode" id="settingsMode">
|
||||
<option value="easy">Easy</option>
|
||||
<option value="medium">Medium</option>
|
||||
<option value="hard">Hard</option>
|
||||
<option value="endless">Endless</option>
|
||||
</select>
|
||||
</label>
|
||||
</p>
|
||||
|
||||
<p class="beastiePickerWrapper">
|
||||
Beastie:
|
||||
<div class="settingsBeastie" id="settingsBeastie"></div>
|
||||
</p>
|
||||
|
||||
<p class="settingsFooter">
|
||||
<button id="cancelSettings">Cancel</button>
|
||||
<button id="applySettings">Apply & Reset</button>
|
||||
</p>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Controls</h2>
|
||||
<ul class="controls">
|
||||
<li><strong>← OR →</strong>: Move the beastie</dd>
|
||||
<li><strong>P</strong>: Pause</li>
|
||||
<li><strong>R</strong> Restart game</li>
|
||||
<li><strong>S</strong>: Show Settings</dd>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
<!-- We load this library via "module" script-tag to guarantee ES6 minimum functionality -->
|
||||
|
||||
<script type="module" src="scripts/main.js"></script>
|
||||
</body>
|
||||
</html>
|
656
src/assets/_root/webtoys/bbe/scripts/bbe.js
Normal file
@ -0,0 +1,656 @@
|
||||
import { createLightning, getMousePos } from "./lightning.js";
|
||||
|
||||
const startGame = (canvas) => {
|
||||
const ctx = canvas.getContext("2d"),
|
||||
BEASTIE_IMAGES = [
|
||||
"blinker_shadow_red.png",
|
||||
"creeper_shadow_red.png",
|
||||
"grabber_shadow_red.png",
|
||||
"scanner_shadow_red.png",
|
||||
"bobbler_shadow_yellow.png",
|
||||
"gawker_shadow_yellow.png",
|
||||
"reacher_shadow_yellow.png",
|
||||
"shuffler_shadow_yellow.png",
|
||||
"puffer_shadow_green.png",
|
||||
"seer_shadow_green.png",
|
||||
"spooker_shadow_green.png",
|
||||
"thinker_shadow_green.png",
|
||||
"chomper_shadow_blue.png",
|
||||
"diver_shadow_blue.png",
|
||||
"snapper_shadow_blue.png",
|
||||
"walker_shadow_blue.png",
|
||||
],
|
||||
GATE = {
|
||||
HEIGHT: 20,
|
||||
MARGIN: 25,
|
||||
MAX_SPACE: 140,
|
||||
MIN_SPACE: 60,
|
||||
START: canvas.height - 200,
|
||||
WIDTH: 65,
|
||||
},
|
||||
game = {
|
||||
interval: 1000 / 90, // ms/f = 1000 ms/s / 90 f/s
|
||||
winScore: 15000,
|
||||
winScores: {
|
||||
easy: 15000,
|
||||
medium: 30000,
|
||||
hard: 50000,
|
||||
endless: 0,
|
||||
},
|
||||
blockerFrequency: 0.3,
|
||||
blockerFrequencies: {
|
||||
easy: 0.3,
|
||||
medium: 0.4,
|
||||
hard: 0.5,
|
||||
endless: 0.3,
|
||||
},
|
||||
hasBlockers: true,
|
||||
isOver: false,
|
||||
isPaused: false,
|
||||
showRestartModal: false,
|
||||
maxLead: 400,
|
||||
maxSpeed: -8,
|
||||
|
||||
speed: -1,
|
||||
speedBoost: {
|
||||
duration: 1,
|
||||
multiplier: 2,
|
||||
},
|
||||
},
|
||||
drag = 0.3,
|
||||
beastie = {
|
||||
width: 48,
|
||||
height: 48,
|
||||
x: canvas.width / 2 - 20,
|
||||
y: GATE.START + 100,
|
||||
|
||||
// velocity
|
||||
dx: 0,
|
||||
dy: 0,
|
||||
|
||||
// for tracking speed boosts
|
||||
boostEnd: 0,
|
||||
|
||||
score: 0,
|
||||
image: null,
|
||||
},
|
||||
player = {
|
||||
dx: 0,
|
||||
dy: 0,
|
||||
},
|
||||
enemy = {
|
||||
width: canvas.width,
|
||||
height: canvas.height,
|
||||
x: 0,
|
||||
y: canvas.height - 50,
|
||||
delaySeconds: 5,
|
||||
speed: -2,
|
||||
},
|
||||
random = (min, max) => Math.random() * (max - min) + min,
|
||||
images = BEASTIE_IMAGES.reduce((acc, name) => {
|
||||
const tempImg = new Image();
|
||||
tempImg.src = `/webtoys/bbe/beasties/${name}`;
|
||||
acc[name] = tempImg;
|
||||
return acc;
|
||||
}, {}),
|
||||
img = new Image(),
|
||||
hasCollision = (gate) =>
|
||||
// Collision check: AAB (Axis-Aligned Bounding Box)
|
||||
beastie.x < gate.x + GATE.WIDTH &&
|
||||
beastie.x > gate.x - beastie.width &&
|
||||
beastie.y < gate.y + GATE.HEIGHT &&
|
||||
beastie.y > gate.y - beastie.height,
|
||||
settingsBeastie = document.getElementById("settingsBeastie");
|
||||
|
||||
let // vertical space between gates
|
||||
minPlatformSpace = GATE.MIN_SPACE,
|
||||
maxPlatformSpace = GATE.MAX_SPACE,
|
||||
gates = [
|
||||
{
|
||||
x: canvas.width / 2 - GATE.WIDTH / 2,
|
||||
y: GATE.START,
|
||||
touched: false,
|
||||
blocker: false,
|
||||
},
|
||||
],
|
||||
y = GATE.START,
|
||||
keydown = false,
|
||||
runtime = 0,
|
||||
lastTimestamp;
|
||||
|
||||
img.onload = () => {
|
||||
ctx.drawImage(img, beastie.x, beastie.y);
|
||||
};
|
||||
// img.src = "/webtoys/bbe/beasties/blinker_shadow_red.png";
|
||||
|
||||
Object.keys(images).forEach((key, i) => {
|
||||
const input = document.createElement("input"),
|
||||
label = document.createElement("label"),
|
||||
bImage = images[key];
|
||||
label.className = "beastieImageInputWrapper";
|
||||
input.name = "beastie";
|
||||
input.type = "radio";
|
||||
// TODO: Add Cookie support
|
||||
input.checked = i === 0;
|
||||
input.value = key;
|
||||
label.appendChild(input);
|
||||
bImage.className = "beastieImage";
|
||||
label.appendChild(bImage);
|
||||
settingsBeastie.appendChild(label);
|
||||
});
|
||||
|
||||
const renderLightning = () => {
|
||||
var color = "hsla(60, 100%, 50%, .7)";
|
||||
|
||||
ctx.save();
|
||||
ctx.globalCompositeOperation = "lighter";
|
||||
|
||||
ctx.strokeStyle = color;
|
||||
ctx.shadowColor = color;
|
||||
|
||||
ctx.fillStyle = color;
|
||||
ctx.fillRect(enemy.x, enemy.y, enemy.width, canvas.height);
|
||||
ctx.fillStyle = "hsla(0, 0%, 10%, 0.2)";
|
||||
ctx.shadowBlur = 0;
|
||||
ctx.globalCompositeOperation = "source-over";
|
||||
ctx.fillRect(enemy.x, enemy.y, enemy.width, canvas.height);
|
||||
ctx.globalCompositeOperation = "lighter";
|
||||
ctx.shadowBlur = 15;
|
||||
|
||||
const lightning = createLightning(canvas, enemy);
|
||||
ctx.beginPath();
|
||||
for (let i = 0; i < lightning.length; i++) {
|
||||
ctx.lineTo(lightning[i].x, Math.max(lightning[i].y, enemy.y));
|
||||
}
|
||||
ctx.stroke();
|
||||
// requestAnimationFrame(renderLightning);
|
||||
ctx.restore();
|
||||
};
|
||||
|
||||
const restartGame = () => {
|
||||
const whichBeastie = document.querySelector(
|
||||
'input[name="beastie"]:checked'
|
||||
).value,
|
||||
settingsMode = document.getElementById("settingsMode");
|
||||
|
||||
if (settingsMode) {
|
||||
const { value } = settingsMode;
|
||||
game.winScore = game.winScores[value];
|
||||
game.blockerFrequency = game.blockerFrequencies[value];
|
||||
}
|
||||
|
||||
img.src = `/webtoys/bbe/beasties/${whichBeastie}`;
|
||||
|
||||
game.isPaused = false;
|
||||
game.isOver = false;
|
||||
game.showRestartModal = false;
|
||||
y = GATE.START;
|
||||
minPlatformSpace = GATE.MIN_SPACE;
|
||||
maxPlatformSpace = GATE.MAX_SPACE;
|
||||
// all the gates - starts in the bottom middle
|
||||
gates = [
|
||||
{
|
||||
x: canvas.width / 2 - GATE.WIDTH / 2,
|
||||
y: GATE.START,
|
||||
touched: false,
|
||||
blocker: false,
|
||||
},
|
||||
];
|
||||
beastie.dx = 0;
|
||||
beastie.dy = 0;
|
||||
beastie.boostEnd = 0;
|
||||
beastie.score = 0;
|
||||
beastie.x = canvas.width / 2 - 20;
|
||||
beastie.y = canvas.height - 50;
|
||||
|
||||
player.dx = 0;
|
||||
player.dy = 0;
|
||||
|
||||
enemy.y = canvas.height - 50;
|
||||
|
||||
runtime = 0;
|
||||
|
||||
while (y > 0) {
|
||||
// the next gate can be placed above the previous one with a space
|
||||
// somewhere between the min and max space
|
||||
y -= GATE.HEIGHT + random(minPlatformSpace, maxPlatformSpace);
|
||||
|
||||
gates.push({
|
||||
x: random(
|
||||
GATE.MARGIN,
|
||||
canvas.width - GATE.MARGIN - GATE.WIDTH
|
||||
),
|
||||
y,
|
||||
touched: false,
|
||||
});
|
||||
}
|
||||
},
|
||||
// game loop
|
||||
loop = (timestamp) => {
|
||||
requestAnimationFrame(loop);
|
||||
|
||||
// limit FPS
|
||||
if (timestamp - lastTimestamp <= game.interval) return;
|
||||
|
||||
if (!(game.isOver || game.isPaused))
|
||||
runtime += timestamp - lastTimestamp;
|
||||
lastTimestamp = timestamp;
|
||||
|
||||
if (game.isOver) {
|
||||
ctx.save();
|
||||
ctx.fillStyle = "yellow";
|
||||
ctx.fillRect(
|
||||
canvas.width / 4,
|
||||
canvas.height / 4,
|
||||
enemy.width / 2,
|
||||
canvas.height / 2
|
||||
);
|
||||
ctx.fillStyle = "black";
|
||||
ctx.font = "32px sans";
|
||||
ctx.fillText(
|
||||
beastie.y <= 0 ? "You Win!" : "Game Over",
|
||||
canvas.width / 3,
|
||||
canvas.height / 3,
|
||||
canvas.width / 3
|
||||
);
|
||||
ctx.fillText(
|
||||
`Score:
|
||||
${beastie.score}`,
|
||||
canvas.width / 3,
|
||||
(canvas.height / 3) * 2,
|
||||
canvas.width / 3
|
||||
);
|
||||
ctx.restore();
|
||||
return;
|
||||
}
|
||||
|
||||
if (game.showRestartModal) {
|
||||
ctx.save();
|
||||
ctx.fillStyle = "yellow";
|
||||
ctx.fillRect(
|
||||
canvas.width / 4,
|
||||
canvas.height / 4,
|
||||
enemy.width / 2,
|
||||
canvas.height / 2
|
||||
);
|
||||
ctx.fillStyle = "black";
|
||||
ctx.font = "48px sans";
|
||||
ctx.fillText(
|
||||
`Restart? (Y / N)`,
|
||||
canvas.width / 3,
|
||||
canvas.height / 3,
|
||||
canvas.width / 3
|
||||
);
|
||||
ctx.restore();
|
||||
return;
|
||||
}
|
||||
|
||||
if (game.isPaused) {
|
||||
ctx.save();
|
||||
ctx.fillStyle = "yellow";
|
||||
ctx.fillRect(
|
||||
canvas.width / 4,
|
||||
canvas.height / 3,
|
||||
canvas.width / 2,
|
||||
canvas.height / 3
|
||||
);
|
||||
ctx.fillStyle = "black";
|
||||
ctx.font = "40px sans";
|
||||
ctx.fillText(`Paused`, canvas.width / 3 - 5, canvas.height / 2);
|
||||
ctx.restore();
|
||||
return;
|
||||
}
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// if beastie reaches the middle of the screen, move the gates down
|
||||
// instead of beastie up to make it look like beastie is going up
|
||||
if (
|
||||
beastie.y < canvas.height - canvas.height / 3 &&
|
||||
beastie.dy < 0 &&
|
||||
(beastie.score <= game.winScore || !game.winScore)
|
||||
) {
|
||||
gates = gates.map(({ y, ...otherProps }) => ({
|
||||
...otherProps,
|
||||
y: y - beastie.dy,
|
||||
}));
|
||||
|
||||
// add more gates to the top of the screen as beastie moves up
|
||||
while (gates[gates.length - 1].y > 0) {
|
||||
gates.push({
|
||||
x: random(25, canvas.width - 25 - GATE.WIDTH),
|
||||
y:
|
||||
gates[gates.length - 1].y -
|
||||
(GATE.HEIGHT +
|
||||
random(minPlatformSpace, maxPlatformSpace)),
|
||||
touched: false,
|
||||
blocker:
|
||||
game.hasBlockers &&
|
||||
gates.filter(({ blocker }) => blocker).length /
|
||||
gates.length <
|
||||
game.blockerFrequency &&
|
||||
Math.random() <= game.blockerFrequency
|
||||
? true
|
||||
: false,
|
||||
});
|
||||
|
||||
// add a bit to the min/max gate space as the player goes up
|
||||
minPlatformSpace += 0.5;
|
||||
maxPlatformSpace += 0.5;
|
||||
|
||||
// cap max space
|
||||
maxPlatformSpace = Math.min(
|
||||
maxPlatformSpace,
|
||||
canvas.height / 2
|
||||
);
|
||||
}
|
||||
// move enemy backwards
|
||||
enemy.y -= beastie.dy;
|
||||
} else {
|
||||
if (beastie.y <= 0) beastie.dy = -10;
|
||||
beastie.y += beastie.dy;
|
||||
}
|
||||
|
||||
// apply drag when key not pressed
|
||||
if (!keydown) {
|
||||
if (player.dx < 0) {
|
||||
beastie.dx += drag;
|
||||
|
||||
// don't let dx go above 0
|
||||
if (beastie.dx > 0) {
|
||||
beastie.dx = 0;
|
||||
player.dx = 0;
|
||||
}
|
||||
} else if (player.dx > 0) {
|
||||
beastie.dx -= drag;
|
||||
|
||||
if (beastie.dx < 0) {
|
||||
beastie.dx = 0;
|
||||
player.dx = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
player.dy === 0 ||
|
||||
(player.dy < 0 && Date.now() > beastie.boostEnd)
|
||||
) {
|
||||
beastie.dy += drag;
|
||||
|
||||
// don't let dx go above 0
|
||||
if (beastie.dy > game.speed) {
|
||||
beastie.dy = game.speed;
|
||||
player.dy = game.speed;
|
||||
}
|
||||
} else if (player.dy > 0) {
|
||||
beastie.dy -= drag;
|
||||
|
||||
if (beastie.dy < 0) {
|
||||
beastie.dy = 0;
|
||||
player.dy = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
beastie.x += beastie.dx;
|
||||
|
||||
// make beastie wrap the screen
|
||||
if (beastie.x + beastie.width < 0) {
|
||||
beastie.x = canvas.width;
|
||||
} else if (beastie.x > canvas.width) {
|
||||
beastie.x = -beastie.width;
|
||||
}
|
||||
|
||||
ctx.fillStyle = "green";
|
||||
gates = gates.map((gate) => {
|
||||
// draw gates
|
||||
ctx.save();
|
||||
if (gate.blocker) ctx.fillStyle = "red";
|
||||
else ctx.fillStyle = gate.touched ? "lime" : "green";
|
||||
ctx.fillRect(gate.x, gate.y, GATE.WIDTH, GATE.HEIGHT);
|
||||
if (gate.blocker) {
|
||||
ctx.save();
|
||||
ctx.lineWidth = 4;
|
||||
ctx.strokeStyle = "black";
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(gate.x, gate.y);
|
||||
ctx.lineTo(gate.x + GATE.WIDTH, gate.y + GATE.HEIGHT);
|
||||
ctx.moveTo(gate.x + GATE.WIDTH, gate.y);
|
||||
ctx.lineTo(gate.x, gate.y + GATE.HEIGHT);
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
// make beastie stop if it collides with a blocking gate
|
||||
if (gate.blocker && hasCollision(gate)) {
|
||||
beastie.dy = game.speed * 0.5;
|
||||
gate.touched = true;
|
||||
}
|
||||
|
||||
// make beastie jump if it collides with a gate from above
|
||||
if (
|
||||
// beastie is falling
|
||||
// beastie.dy > 0 &&
|
||||
// beastie was previous above the gate
|
||||
// prevDoodleY + doodle.height <= gate.y &&
|
||||
// doodle collides with gate
|
||||
|
||||
!gate.blocker &&
|
||||
// !gate.touched &&
|
||||
hasCollision(gate)
|
||||
) {
|
||||
// reset beastie position so it's on top of the gate
|
||||
// beastie.y = gate.y - beastie.height;
|
||||
if (!gate.touched) {
|
||||
beastie.dy = Math.max(
|
||||
game.maxSpeed,
|
||||
game.speedBoost.multiplier *
|
||||
Math.min(beastie.dy, game.speed)
|
||||
);
|
||||
beastie.score += 100;
|
||||
}
|
||||
beastie.boostEnd =
|
||||
Date.now() + game.speedBoost.duration * 1000;
|
||||
gate.touched = true;
|
||||
}
|
||||
|
||||
return gate;
|
||||
});
|
||||
|
||||
// check on enemy
|
||||
if (!runtime) runtime = 0;
|
||||
|
||||
if (runtime > enemy.delaySeconds * 1000)
|
||||
enemy.y = enemy.y + enemy.speed;
|
||||
if (enemy.y < 1) {
|
||||
enemy.y = 0;
|
||||
game.isOver = true;
|
||||
} else if (enemy.y > beastie.y + game.maxLead)
|
||||
enemy.y = beastie.y + game.maxLead;
|
||||
|
||||
// draw beastie
|
||||
ctx.drawImage(
|
||||
img,
|
||||
beastie.x,
|
||||
beastie.y,
|
||||
beastie.width,
|
||||
beastie.height
|
||||
);
|
||||
|
||||
// +100 points for each offscreen blocker that wasn't touched
|
||||
beastie.score +=
|
||||
100 *
|
||||
gates.filter(
|
||||
(gate) =>
|
||||
gate.y > canvas.height && gate.blocker && !gate.touched
|
||||
).length;
|
||||
// remove any offscreen gates
|
||||
gates = gates.filter((gate) => gate.y <= canvas.height);
|
||||
|
||||
beastie.score += Math.floor(Math.abs(beastie.dy));
|
||||
ctx.fillStyle = "white";
|
||||
ctx.font = "12px sans";
|
||||
ctx.fillText(`Score: ${beastie.score}`, 10, 20);
|
||||
|
||||
renderLightning();
|
||||
};
|
||||
|
||||
// listen to keyboard events to move beastie
|
||||
document.addEventListener("keydown", (e) => {
|
||||
if (keydown) return;
|
||||
|
||||
if (e.code === "ArrowLeft") {
|
||||
keydown = true;
|
||||
player.dx = -1;
|
||||
beastie.dx = -3;
|
||||
} else if (e.code === "ArrowRight") {
|
||||
keydown = true;
|
||||
player.dx = 1;
|
||||
beastie.dx = 3;
|
||||
} else if (e.code === "KeyP") {
|
||||
keydown = true;
|
||||
game.isPaused = !game.isPaused;
|
||||
} else if (e.code === "KeyR") {
|
||||
keydown = true;
|
||||
if (game.isOver) {
|
||||
restartGame();
|
||||
return;
|
||||
}
|
||||
game.isPaused = true;
|
||||
game.showRestartModal = true;
|
||||
} else if (e.code === "KeyS") {
|
||||
keydown = true;
|
||||
game.isPaused = true;
|
||||
document.getElementById("settings").classList.add("showSettings");
|
||||
} else if (e.code === "KeyY") {
|
||||
keydown = true;
|
||||
if (game.showRestartModal) restartGame();
|
||||
} else if (e.code === "KeyN") {
|
||||
keydown = true;
|
||||
if (game.showRestartModal) {
|
||||
game.showRestartModal = false;
|
||||
game.isPaused = false;
|
||||
}
|
||||
|
||||
/*
|
||||
} else if (e.code === "ArrowDown") {
|
||||
keydown = true;
|
||||
player.dy = 1;
|
||||
beastie.dy = 3;
|
||||
} else if (e.code === "ArrowUp") {
|
||||
keydown = true;
|
||||
player.dy = -1;
|
||||
beastie.dy = -3;
|
||||
*/
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener("keyup", () => {
|
||||
keydown = false;
|
||||
});
|
||||
|
||||
canvas.addEventListener(
|
||||
"mousedown",
|
||||
(e) => {
|
||||
keydown = true;
|
||||
const result = getMousePos(canvas, e);
|
||||
if (result.x < canvas.width / 2) {
|
||||
player.dx = -1;
|
||||
beastie.dx = -3;
|
||||
} else {
|
||||
player.dx = 1;
|
||||
beastie.dx = 3;
|
||||
}
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
canvas.addEventListener(
|
||||
"mousemove",
|
||||
(e) => {
|
||||
if (!keydown) return;
|
||||
const result = getMousePos(canvas, e);
|
||||
if (result.x < beastie.x) {
|
||||
player.dx = -1;
|
||||
beastie.dx = -3;
|
||||
} else {
|
||||
player.dx = 1;
|
||||
beastie.dx = 3;
|
||||
}
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
canvas.addEventListener(
|
||||
"mouseup",
|
||||
() => {
|
||||
keydown = false;
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
canvas.addEventListener(
|
||||
"touchstart",
|
||||
(e) => {
|
||||
mousePos = getTouchPos(canvas, e);
|
||||
const touch = e.touches[0],
|
||||
mouseEvent = new MouseEvent("mousedown", {
|
||||
clientX: touch.clientX,
|
||||
clientY: touch.clientY,
|
||||
});
|
||||
canvas.dispatchEvent(mouseEvent);
|
||||
},
|
||||
false
|
||||
);
|
||||
canvas.addEventListener(
|
||||
"touchend",
|
||||
() => {
|
||||
var mouseEvent = new MouseEvent("mouseup", {});
|
||||
canvas.dispatchEvent(mouseEvent);
|
||||
},
|
||||
false
|
||||
);
|
||||
canvas.addEventListener(
|
||||
"touchmove",
|
||||
(e) => {
|
||||
const touch = e.touches[0];
|
||||
keydown = true;
|
||||
if (touch.clientX < beastie.x) {
|
||||
player.dx = -1;
|
||||
beastie.dx = -3;
|
||||
} else {
|
||||
player.dx = 1;
|
||||
beastie.dx = 3;
|
||||
}
|
||||
canvas.dispatchEvent(mouseEvent);
|
||||
},
|
||||
false
|
||||
);
|
||||
document.getElementById("cancelSettings").addEventListener("click", (e) => {
|
||||
// close modal
|
||||
document.getElementById("settings").classList.remove("showSettings");
|
||||
// leave game paused
|
||||
});
|
||||
document.getElementById("applySettings").addEventListener("click", (e) => {
|
||||
// close modal
|
||||
document.getElementById("settings").classList.remove("showSettings");
|
||||
// restart game
|
||||
restartGame();
|
||||
});
|
||||
document
|
||||
.getElementById("toggleSettings")
|
||||
?.addEventListener("click", (e) => {
|
||||
// pause game
|
||||
game.isPaused = true;
|
||||
|
||||
// show settings
|
||||
document
|
||||
.getElementById("settings")
|
||||
.classList.toggle("showSettings");
|
||||
});
|
||||
|
||||
// start the game
|
||||
restartGame();
|
||||
|
||||
requestAnimationFrame(loop);
|
||||
};
|
||||
|
||||
export { startGame };
|
45
src/assets/_root/webtoys/bbe/scripts/lightning.js
Normal file
@ -0,0 +1,45 @@
|
||||
export const createLightning = (canvas, enemy) => {
|
||||
const { y: size } = enemy;
|
||||
var center = { x: canvas.width / 2, y: canvas.height };
|
||||
var minSegmentHeight = 5;
|
||||
var roughness = 2;
|
||||
var maxDifference = size / 5;
|
||||
|
||||
let lightning = [];
|
||||
let segmentHeight = size / 3;
|
||||
lightning.push({
|
||||
x: center.x,
|
||||
y: center.y + 200,
|
||||
});
|
||||
lightning.push({
|
||||
x: Math.random() * (canvas.width - 100) + 50,
|
||||
y: Math.abs((Math.random() - 0.9) * 100),
|
||||
});
|
||||
let currDiff = maxDifference;
|
||||
while (segmentHeight > minSegmentHeight) {
|
||||
const newSegments = [];
|
||||
for (var i = 0; i < lightning.length - 1; i++) {
|
||||
const start = lightning[i],
|
||||
end = lightning[i + 1],
|
||||
midX = (start.x + end.x) / 2,
|
||||
newX = midX + (Math.random() * 2 - 1) * currDiff;
|
||||
newSegments.push(start, { x: newX, y: (start.y + end.y) / 2 });
|
||||
}
|
||||
|
||||
newSegments.push(lightning.pop());
|
||||
lightning = newSegments;
|
||||
|
||||
currDiff /= roughness;
|
||||
segmentHeight /= 2;
|
||||
}
|
||||
return lightning;
|
||||
};
|
||||
|
||||
// Get the position of the mouse relative to the canvas
|
||||
export const getMousePos = (canvasDom, mouseEvent) => {
|
||||
var rect = canvasDom.getBoundingClientRect();
|
||||
return {
|
||||
x: mouseEvent.clientX - rect.left,
|
||||
y: mouseEvent.clientY - rect.top,
|
||||
};
|
||||
};
|
32
src/assets/_root/webtoys/bbe/scripts/main.js
Normal file
@ -0,0 +1,32 @@
|
||||
// @license magnet:?xt=urn:btih:90dc5c0be029de84e523b9b3922520e79e0e6f08&dn=cc0.txt CC0-1.0
|
||||
/****************************************************************************
|
||||
* Planar Vagabond's Guide to the Multiverse (planarvagabond.com)
|
||||
*
|
||||
* Copyright 2023-2024 Eric Woodward
|
||||
* Source released under CC0 Public Domain License v1.0
|
||||
* https://www.planarvagabond.com/licenses/cc0/
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
****************************************************************************/
|
||||
|
||||
import { startGame } from "./bbe.js";
|
||||
|
||||
export default (() => {
|
||||
// we load this library as a module to guarantee baseline ES6 functionality
|
||||
|
||||
// Indicate JS is loaded
|
||||
document.documentElement.className =
|
||||
document.documentElement.className.replace("no-js", "js");
|
||||
|
||||
setTimeout(() => {
|
||||
const canvas = document.createElement("canvas"),
|
||||
contentDiv = document.getElementById("game");
|
||||
|
||||
canvas.setAttribute("width", "375");
|
||||
canvas.setAttribute("height", "667");
|
||||
canvas.setAttribute("id", "game");
|
||||
contentDiv.appendChild(canvas);
|
||||
|
||||
startGame(canvas);
|
||||
}, 1);
|
||||
})();
|
||||
// @license-end
|
82
src/assets/_root/webtoys/bbe/styles.css
Normal file
@ -0,0 +1,82 @@
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: #333;
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
canvas {
|
||||
border: 1px solid black;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.beastieImage {
|
||||
max-width: 2rem;
|
||||
}
|
||||
|
||||
.beastieImageInputWrapper {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
padding: .5rem;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 375px;
|
||||
}
|
||||
|
||||
.game {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.settings {
|
||||
background-color: rgba(33, 33, 33, .95);
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
padding: 0 1rem 1rem;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
transition: opacity ease 0.3s;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.settings.showSettings {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.settingsBeastie {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.settingsBeastie label {
|
||||
padding: .5rem;
|
||||
}
|
||||
|
||||
.settingsFooter {
|
||||
margin: 1rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.toggleSettings {
|
||||
background-color: rgba(33, 33, 33, .9);
|
||||
border: 1px solid rgba(33, 33, 33, .9);
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 5px;
|
||||
z-index: 2;
|
||||
}
|