332 lines
14 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Webcomponent GUI</title>
<style>
body {
margin: 0;
padding: 0;
font-family: Arial, sans-serif;
font-size: 16px;
background-color: rgb(24, 149, 89);
}
.instruction {
display: flex; flex-direction: column; border: 3px solid black; border-radius: 10px; box-shadow: 5px 5px 15px grey; padding: 10px; background-color: white;
}
.instruction .title {
flex: 30%; font-weight: bold; text-transform: uppercase; border-bottom: 1px solid white; text-align: center;
}
</style>
</head>
<body>
<div style="display: flex; justify-content: center; align-items: center; padding: 10px;">
<div class="split-top split" style="max-width: 800px; width: 100%;">
<div class="split-left" style="padding: 0;">
<div id="container-game" style="display: flex; flex-direction: column; height: 400px; position: relative;">
<div
id="win-panel"
style="top: 0; left: 0; width: 100%; height: 100%; position: absolute; display: none; align-items: center; justify-content: center; background-color: #000000aa; z-index: 1; color: white; font-size: xx-large;">
YOU LOSE!
</div>
<div class="bottom" style="flex: 80%; background-color: rgb(24, 149, 89); display: flex; align-items: center; justify-content: center;">
<spine-skeleton
identifier="windmill-game"
atlas="/assets/windmill-pma.atlas"
skeleton="/assets/windmill-ess.json"
animation="animation"
interactive
></spine-skeleton>
<spine-skeleton
identifier="spineboy-game"
atlas="/assets/spineboy-pma.atlas"
skeleton="/assets/spineboy-pro.json"
animation="hoverboard"
fit="none"
></spine-skeleton>
</div>
</div>
</div>
<div class="split-right">
<div class="top" style="flex: 20%; flex-direction: column; gap: 10px; display: flex; align-items: center; justify-content: center; align-items: stretch; height: 100%;">
<div class="instruction">
Use WASD to move around!
</div>
<div class="instruction">
<div id="killed" class="title"></div>
<div style="flex: 70%;"">Save the flowers from the white pest by shooting them</div>
</div>
<div class="instruction">
<div id="ammo" class="title"></div>
<div>Go to the red colored rooster of bush when ammo is low</div>
</div>
<div class="instruction">
<div id="level" class="title"></div>
<div>Reach level 10 to win the game</div>
</div>
</div>
</div>
</div>
</div>
<script type="module">
import * as spine from '../dist/esm/spine-webcomponents.mjs';
const winPanel = document.getElementById("win-panel");
const killedSpan = document.getElementById("killed");
const ammoSpan = document.getElementById("ammo");
const levelSpan = document.getElementById("level");
const containerGame = document.getElementById("container-game");
(async () => {
const spineboy = spine.getSkeleton("spineboy-game");
const windmill = spine.getSkeleton("windmill-game");
await Promise.all([spineboy.whenReady, windmill.whenReady]);
spineboy.state.setAnimation(2, "aim", true);
spineboy.skeleton.slots.forEach(slot => {
if (slot.data.name === "gun") {
spineboy.addPointerSlotEventCallback(slot, (slot,event) => {
if (event === "down") {
spineboy.state.setAnimation(1, "shoot", false);
}
});
}
if (slot.data.name === "torso") {
spineboy.addPointerSlotEventCallback(slot, (slot,event) => {
if (event === "down") {
spineboy.state.setAnimation(0, "jump", false).mixDuration = 0.2;
spineboy.state.addAnimation(0, "walk", true).mixDuration = 0.2;
}
});
}
if (slot.data.name === "head") {
spineboy.addPointerSlotEventCallback(slot, (slot,event) => {
if (event === "down") {
spineboy.state.setAnimation(1, "run", true).mixDuration = 0.2;
} else {
if (event === "up") {
spineboy.state.setEmptyAnimation(1, 1);
spineboy.state.clearTrack(1);
}
}
});
}
});
const tempVector = new spine.Vector2();
const crosshairSlot = spineboy.skeleton.findSlot("crosshair");
crosshairSlot.color.a = 0;
const crosshairBone = crosshairSlot.bone;
let points = 0;
let ammo = 5;
let killed = 0;
let level = 1;
ammoSpan.innerText = `Ammo: ${ammo}`;
killedSpan.innerText = `Saved: ${killed}`;
levelSpan.innerText = `Level: ${level}`;
const flowers = [];
const ammoLocations = [];
windmill.skeleton.slots.forEach(slot => {
if (slot.data.name === "rooster" || slot.data.name === "bush1") {
ammoLocations.push(slot)
}
if (!Number.isNaN(parseInt(slot.data.name.replace("flower", ""))) || slot.data.name === "flower") {
slot.color.set(1, 0, 0, 1);
flowers.push(slot);
windmill.addPointerSlotEventCallback(slot, (slot, event) => {
if (ammo === 0) return;
if (event !== "down") return;
if (slot.color.g === 0) return;
spineboy.state.setAnimation(1, "shoot", false);
ammo--;
points++;
killed++;
if (points === 10) {
level++
points = 0
maxTime -= 100
}
ammoSpan.innerText = `Ammo: ${ammo}`;
killedSpan.innerText = `Saved: ${killed}`;
tempVector.x = slot.bone.x;
tempVector.y = slot.bone.y;
slot.bone.parentToWorld(tempVector);
crosshairBone.x = (tempVector.x + (windmill.worldX - spineboy.worldX) - spineboy.skeleton.x) / spineboy.skeleton.scaleX;
crosshairBone.y = (tempVector.y + (windmill.worldY - spineboy.worldY) - spineboy.skeleton.y) / spineboy.skeleton.scaleY;
slot.color.set(1, 0, 0, 1);
});
}
});
let time = 0;
let maxTime = 1000;
let ammoLocationActive = false;
let gameVectorTemp = new spine.Vector3();
let interval = setInterval(() => {
if (!ammoLocationActive && ammo <= 2) {
ammoLocationActive = true;
spineboy.overlay.worldToScreen(gameVectorTemp, spineboy.worldX + spineboy.skeleton.x, spineboy.worldY + spineboy.skeleton.y);
const { x, width } = containerGame.getBoundingClientRect();
const containerGameMiddle = x + width / 2;
const left = gameVectorTemp.x < containerGameMiddle;
const ammoLocation = ammoLocations[left ? 1 : 0];
(ammoLocation.darkColor ||= new spine.Color()).set(1, 0, 0, 1);
levelSpan.innerText = `Level: ${level} / 10`;
};
if (time >= maxTime) {
time = 0;
const flower = random(flowers);
flower.color.set(1, 1, 1, 1);
}
if (checkLoseCondition()) {
endGame(false, interval);
return;
}
if (checkWinCondition()) {
levelSpan.innerText = `Level: 10 / 10`;
endGame(true, interval);
return;
}
time += 100;
}, 100);
const checkWinCondition = () => level === 10;
const checkLoseCondition = () => flowers.every((flowerSlot) => flowerSlot.color.g !== 0);
const endGame = (win, interval) => {
clearInterval(interval);
winPanel.style.display = "flex";
winPanel.innerText = win ? "YOU WIN!" : "YOU LOSE!"
document.removeEventListener('keydown', handleKeyDown);
document.removeEventListener('keyup', handleKeyUp);
keys.w = false;
keys.a = false;
keys.s = false;
keys.d = false;
spineboy.state.setAnimation(1, win ? "jump" : "death", win);
}
const random = array => array[Math.floor(Math.random() * array.length)];
const MOVE_SPEED = 350;
const keys = {
w: false,
a: false,
s: false,
d: false
};
function handleKeyDown(e) {
const key = e.key.toLowerCase();
if (key in keys) {
keys[key] = true;
}
}
function handleKeyUp(e) {
const key = e.key.toLowerCase();
if (key in keys) keys[key] = false;
}
spineboy.skeleton.x += 0
spineboy.skeleton.y -= 330
let direction = 1;
spineboy.beforeUpdateWorldTransforms = (delta) => {
let posX = 0;
let posY = 0;
// Move based on pressed keys
const inc = (MOVE_SPEED * delta) * windmill.skeleton.scaleX;
if (keys.w) posY -= inc;
if (keys.a) posX -= inc;
if (keys.s) posY += inc;
if (keys.d) posX += inc;
// Update visual position
spineboy.skeleton.x += posX;
spineboy.skeleton.y -= posY;
direction = posX < 0 ? direction = -1 : posX > 0 ? 1 : direction;
spineboy.skeleton.scaleX = .25 * windmill.skeleton.scaleX * direction;
spineboy.skeleton.scaleY = .25 * windmill.skeleton.scaleY;
const spineboyPosition = {
x: spineboy.worldX + spineboy.skeleton.x - spineboy.bounds.width / 2 * spineboy.skeleton.scaleX * direction,
y: spineboy.worldY + spineboy.skeleton.y ,
width: spineboy.bounds.width * spineboy.skeleton.scaleX * direction,
height: spineboy.bounds.height * spineboy.skeleton.scaleY,
}
ammoLocations.forEach(element => {
const width = element.attachment.region.width * windmill.skeleton.scaleX;
const height = element.attachment.region.height * windmill.skeleton.scaleY;
const x = windmill.worldX + element.bone.worldX - width / 2;
const y = windmill.worldY + element.bone.worldY - height / 2;
if (isIntersecting(spineboyPosition, { x, y, width, height })) {
if (element.darkColor) {
ammo = 5;
element.darkColor = null;
ammoLocationActive = false;
ammoSpan.innerText = `Ammo: ${ammo}`;
}
}
});
}
document.addEventListener('keydown', handleKeyDown);
document.addEventListener('keyup', handleKeyUp);
})();
function isIntersecting(rect1, rect2) {
return (
rect1.x < rect2.x + rect2.width &&
rect1.x + rect1.width > rect2.x &&
rect1.y < rect2.y + rect2.height &&
rect1.y + rect1.height > rect2.y
);
}
</script>
</body>
</html>