mirror of
https://github.com/EsotericSoftware/spine-runtimes.git
synced 2026-02-04 14:24:53 +08:00
Add interactive widget example.
This commit is contained in:
parent
2a6b424a1c
commit
d4aeb9a608
@ -2103,8 +2103,7 @@ stretchyman.update = (canvas, delta, skeleton, state) => {
|
||||
(async () => {
|
||||
const tank = spine.getSpineWidget("tank");
|
||||
const tank2 = spine.getSpineWidget("tank2");
|
||||
await tank.loadingPromise;
|
||||
await tank2.loadingPromise;
|
||||
await Promise.all([tank.loadingPromise, tank2.loadingPromise]);
|
||||
|
||||
tank.beforeUpdateWorldTransforms = (skeleton, state) => {
|
||||
if (!tank.onScreen) return;
|
||||
@ -2145,8 +2144,7 @@ stretchyman.update = (canvas, delta, skeleton, state) => {
|
||||
(async () => {
|
||||
const tank = spine.getSpineWidget("tank");
|
||||
const tank2 = spine.getSpineWidget("tank2");
|
||||
await tank.loadingPromise;
|
||||
await tank2.loadingPromise;
|
||||
await Promise.all([tank.loadingPromise, tank2.loadingPromise]);
|
||||
|
||||
// since we want the tank to overflow the div, we set fit to none
|
||||
// then we "sync" the tank scale to the one of the tank above
|
||||
@ -3559,6 +3557,331 @@ TODO`
|
||||
-->
|
||||
|
||||
|
||||
<!--
|
||||
/////////////////////
|
||||
// start section //
|
||||
/////////////////////
|
||||
-->
|
||||
|
||||
<div class="section vertical-split">
|
||||
|
||||
<div class="split-top split">
|
||||
<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-widget
|
||||
identifier="windmill-game"
|
||||
atlas="assets/windmill-ess.atlas"
|
||||
skeleton="assets/windmill-ess.json"
|
||||
animation="animation"
|
||||
isinteractive
|
||||
></spine-widget>
|
||||
<spine-widget
|
||||
identifier="spineboy-game"
|
||||
atlas="assets/spineboy-pma.atlas"
|
||||
skeleton="assets/spineboy-pro.json"
|
||||
animation="hoverboard"
|
||||
fit="none"
|
||||
></spine-widget>
|
||||
</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 style="display: flex; flex-direction: column; border: 3px solid black; border-radius: 10px; box-shadow: 5px 5px 15px grey; padding: 10px; ">
|
||||
Use WASD to move around!
|
||||
</div>
|
||||
|
||||
<div style="display: flex; flex-direction: column; border: 3px solid black; border-radius: 10px; box-shadow: 5px 5px 15px grey; padding: 10px; ">
|
||||
<div id="killed" style="flex: 30%; font-weight: bold; text-transform: uppercase; border-bottom: 1px solid white; text-align: center;"></div>
|
||||
<div style="flex: 70%;"">Save the flowers from the white pest by shooting them</div>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; flex-direction: column; border: 3px solid black; border-radius: 10px; box-shadow: 5px 5px 15px grey; padding: 10px; ">
|
||||
<div id="ammo" style="flex: 30%; font-weight: bold; text-transform: uppercase; border-bottom: 1px solid white; text-align: center;"></div>
|
||||
<div>Go to the red colored rooster of bush when ammo is low</div>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; flex-direction: column; border: 3px solid black; border-radius: 10px; box-shadow: 5px 5px 15px grey; padding: 10px; ">
|
||||
<div id="level" style="flex: 30%; font-weight: bold; text-transform: uppercase; border-bottom: 1px solid white; text-align: center;"></div>
|
||||
<div>Reach level 10 to win the game</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
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.getSpineWidget("spineboy-game");
|
||||
const windmill = spine.getSpineWidget("windmill-game");
|
||||
await Promise.all([spineboy.loadingPromise, windmill.loadingPromise]);
|
||||
|
||||
spineboy.state.setAnimation(2, "aim", true);
|
||||
|
||||
spineboy.skeleton.slots.forEach(slot => {
|
||||
if (slot.data.name === "gun") {
|
||||
spineboy.addCursorSlotEventCallbacks(slot, (slot,event) => {
|
||||
if (event === "down") {
|
||||
spineboy.state.setAnimation(1, "shoot", false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (slot.data.name === "torso") {
|
||||
spineboy.addCursorSlotEventCallbacks(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.addCursorSlotEventCallbacks(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.addCursorSlotEventCallbacks(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()) {
|
||||
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 = 2.5;
|
||||
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;
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
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 = () => {
|
||||
let posX = 0;
|
||||
let posY = 0;
|
||||
|
||||
// Move based on pressed keys
|
||||
const inc = MOVE_SPEED * 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;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
|
||||
<div class="split-bottom">
|
||||
<pre><code id="code-display">
|
||||
<script>
|
||||
escapeHTMLandInject(`
|
||||
<spine-widget
|
||||
atlas="assets/spineboy-pma.atlas"
|
||||
skeleton="assets/spineboy-pro.skel"
|
||||
animation="walk"
|
||||
></spine-widget>`)
|
||||
</script>
|
||||
</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
/////////////////////
|
||||
// end section //
|
||||
/////////////////////
|
||||
-->
|
||||
|
||||
<script>
|
||||
spine.SpineWebComponentWidget.SHOW_FPS = true;
|
||||
</script>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user