|
@@ -2360,7 +2360,7 @@ stretchyman.update = (canvas, delta, skeleton, state) => {
|
|
|
const [tank, tank2] = await Promise.all([
|
|
|
spine.getSpineWidget("tank").whenReady, spine.getSpineWidget("tank2").whenReady]);
|
|
|
|
|
|
- tank.beforeUpdateWorldTransforms = (skeleton, state) => {
|
|
|
+ tank.beforeUpdateWorldTransforms = (delta, skeleton, state) => {
|
|
|
if (!tank.onScreen) return;
|
|
|
tank.skeleton.scaleX = tank2.skeleton.scaleX;
|
|
|
tank.skeleton.scaleY = tank2.skeleton.scaleY;
|
|
@@ -2399,7 +2399,7 @@ stretchyman.update = (canvas, delta, skeleton, state) => {
|
|
|
const [tank, tank2] = await Promise.all([
|
|
|
spine.getSpineWidget("tank").whenReady, spine.getSpineWidget("tank2").whenReady]);
|
|
|
|
|
|
-tank.beforeUpdateWorldTransforms = (skeleton, state) => {
|
|
|
+tank.beforeUpdateWorldTransforms = (delta, skeleton, state) => {
|
|
|
if (!tank.onScreen) return;
|
|
|
tank.skeleton.scaleX = tank2.skeleton.scaleX;
|
|
|
tank.skeleton.scaleY = tank2.skeleton.scaleY;
|
|
@@ -3415,1278 +3415,6 @@ const darkPicker = document.getElementById("dark-picker");
|
|
|
///////
|
|
|
-->
|
|
|
|
|
|
- <!--
|
|
|
- /////////////////////
|
|
|
- // start section //
|
|
|
- /////////////////////
|
|
|
- -->
|
|
|
-
|
|
|
- <div class="section vertical-split">
|
|
|
-
|
|
|
- <div class="split-left">
|
|
|
- A login UI made using the chibi stickers and a button made using Spine.
|
|
|
- <p>
|
|
|
- The chibi sticker does the following:
|
|
|
- <ul>
|
|
|
- <li>It looks at the cursor when no input field is selected</li>
|
|
|
- <li>Look at the caret when username input field is selected</li>
|
|
|
- <li>Cover its eyes when password input field is selected</li>
|
|
|
- <li>React in two different ways depending on the password</li>
|
|
|
- </ul>
|
|
|
- </p>
|
|
|
-
|
|
|
- <p>
|
|
|
- The button does the following:
|
|
|
- <ul>
|
|
|
- <li>Starts some animation when cursor enters, leaves, stays, or click the button</li>
|
|
|
- <li>Appends a div containing the <code>LOGIN</code> text to a slot</li>
|
|
|
- <li>Submits the form on click</li>
|
|
|
- </ul>
|
|
|
- </p>
|
|
|
- </div>
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- <span id="ruler" style="visibility: hidden; white-space: nowrap; position: absolute"></span>
|
|
|
- <div style="background-color: white; width: 250px; padding: 30px; text-align: center;">
|
|
|
- <div style="display: flex; justify-content: center;">
|
|
|
- <div style="width: 150px; height:150px; border-radius: 5%; border: 1px solid rgb(113, 113, 113); background-color: rgb(211, 211, 211); margin-bottom: 30px;">
|
|
|
- <spine-widget
|
|
|
- identifier="spineboy-login"
|
|
|
- atlas="assets/pwd/chibi-stickers-pro-pwd-test.atlas"
|
|
|
- skeleton="assets/pwd/chibi-stickers.json"
|
|
|
- skin="spineboy"
|
|
|
- bounds-x="-177"
|
|
|
- bounds-y="238"
|
|
|
- bounds-width="364"
|
|
|
- bounds-height="412"
|
|
|
- animation="interactive/head/idle"
|
|
|
- clip
|
|
|
- isinteractive
|
|
|
- ></spine-widget>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="input-group" style="margin-bottom: 15px;"><select id="skinSelect"></select></div>
|
|
|
-
|
|
|
- <form id="loginForm">
|
|
|
- <div class="input-group" style="margin-bottom: 15px;">
|
|
|
- <input style="width: 100%; padding: 10px; box-sizing: border-box;" type="text" id="username" name="username" placeholder="Username" autocomplete="off" required>
|
|
|
- </div>
|
|
|
- <div class="input-group" style="margin-bottom: 15px;">
|
|
|
- <input style="width: 100%; padding: 10px; box-sizing: border-box;" type="password" id="password" name="password" placeholder="Password" required>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div style="margin-bottom: 10px; color: black">Password is <code>password</code></div>
|
|
|
-
|
|
|
- <div style="height: 75px; cursor: pointer;">
|
|
|
- <div id="button-text" style="font-size: xx-large; cursor: pointer; user-select: none; display: none;"> LOGIN </div>
|
|
|
- <spine-widget
|
|
|
- identifier="button-login"
|
|
|
- atlas="assets/pwd/button.atlas"
|
|
|
- skeleton="assets/pwd/button.json"
|
|
|
- animation="idle"
|
|
|
- isinteractive
|
|
|
- fit="fill"
|
|
|
- ></spine-widget>
|
|
|
- </div>
|
|
|
-
|
|
|
- </form>
|
|
|
- </div>
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- <script>
|
|
|
- const mouseX = Smooth(0, 200);
|
|
|
- const mouseY = Smooth(0, 200);
|
|
|
-
|
|
|
- (async () => {
|
|
|
-
|
|
|
- const form = document.getElementById('loginForm');
|
|
|
- const widgetButton = spine.getSpineWidget("button-login");
|
|
|
- await widgetButton.whenReady;
|
|
|
-
|
|
|
- widgetButton.skeleton.color.set(.85, .85, .85, 1);
|
|
|
- widgetButton.cursorEventCallback = (event, originalEvent) => {
|
|
|
- if (event === "enter") {
|
|
|
- widgetButton.state.setAnimation(0, "enhance-in", false);
|
|
|
- widgetButton.state.setAnimation(1, "shadow-in", false);
|
|
|
- widgetButton.state.setAnimation(2, "jump", true);
|
|
|
- };
|
|
|
- if (event === "leave") {
|
|
|
- widgetButton.state.setAnimation(0, "enhance-out", false).timeScale = 2;
|
|
|
- widgetButton.state.setAnimation(1, "shadow-out", false);
|
|
|
- widgetButton.state.setEmptyAnimation(2, 0).mixDuration = .25;
|
|
|
- };
|
|
|
- if (event === "down") {
|
|
|
- widgetButton.state.setAnimation(0, "enhance-in", false).timeScale = 2;
|
|
|
- originalEvent.preventDefault();
|
|
|
- if (form.reportValidity()) {
|
|
|
- if (passwordInput.value === "password") {
|
|
|
- widget.state.setAnimation(0, "interactive/pwd/hooray", 0);
|
|
|
- widget.state.addAnimation(0, "interactive/head/idle", true);
|
|
|
- } else {
|
|
|
- widget.state.setAnimation(0, "interactive/pwd/sad", 0);
|
|
|
- widget.state.addAnimation(0, "interactive/head/idle", true);
|
|
|
- }
|
|
|
- }
|
|
|
- };
|
|
|
- }
|
|
|
-
|
|
|
- const textSlot = widgetButton.skeleton.findSlot("CLICK ME");
|
|
|
- textSlot.setAttachment(null);
|
|
|
-
|
|
|
-
|
|
|
- const divText = document.getElementById("button-text");
|
|
|
-
|
|
|
- widgetButton.followSlot("CLICK ME", divText, { followScale: false });
|
|
|
-
|
|
|
- const widget = spine.getSpineWidget("spineboy-login");
|
|
|
- await widget.whenReady;
|
|
|
-
|
|
|
- // default settings
|
|
|
- widget.state.data.defaultMix = 0.15;
|
|
|
-
|
|
|
- // Skin selection
|
|
|
- const skinSelect = document.getElementById('skinSelect');
|
|
|
- widget.skeleton.data.skins.forEach(({ name }) => {
|
|
|
- if (name === "default") return;
|
|
|
- skinSelect.add(new Option(name, name, name === "spineboy"));
|
|
|
- });
|
|
|
- skinSelect.value = "spineboy";
|
|
|
- skinSelect.addEventListener('change', (e) => widget.skin = e.target.value);
|
|
|
-
|
|
|
- // click on spineboy
|
|
|
- widget.cursorEventCallback = (event) => {
|
|
|
- if (event === "down") {
|
|
|
- widget.state.setAnimation(0, "interactive/pwd/touch", false);
|
|
|
- widget.state.addAnimation(0, "interactive/head/idle", true);
|
|
|
- }
|
|
|
- if (event === "enter") widget.state.setAnimation(0, "emotes/wave", true);
|
|
|
- if (event === "leave") widget.state.setAnimation(0, "interactive/head/idle", false);
|
|
|
- }
|
|
|
-
|
|
|
- // Head follow logic
|
|
|
- let focusInput = false;
|
|
|
-
|
|
|
- // Password input field
|
|
|
- const passwordInput = document.getElementById('password');
|
|
|
- passwordInput.addEventListener('focus', () => {
|
|
|
- focusInput = true;
|
|
|
- resetBlendTracks();
|
|
|
- widget.state.setAnimation(0, "interactive/pwd/hide", 0);
|
|
|
- });
|
|
|
- passwordInput.addEventListener('blur', () => {
|
|
|
- focusInput = false;
|
|
|
- widget.state.setAnimation(0, "interactive/head/idle", 0);
|
|
|
- });
|
|
|
-
|
|
|
- // Username input field
|
|
|
- const usernameInput = document.getElementById('username');
|
|
|
- usernameInput.addEventListener('input', () => {
|
|
|
- setBlendTracks();
|
|
|
- });
|
|
|
- usernameInput.addEventListener('focus', () => {
|
|
|
- focusInput = true;
|
|
|
- setBlendTracks();
|
|
|
- });
|
|
|
- usernameInput.addEventListener('blur', () => {
|
|
|
- focusInput = false;
|
|
|
- setBlendTracks();
|
|
|
- });
|
|
|
-
|
|
|
- // Animation left/down blending
|
|
|
- const ALPHA_LEFT_RANGE = 4;
|
|
|
- const ALPHA_DOWN = .8;
|
|
|
- const ALPHA_DOWN_RANGE = 2;
|
|
|
- const moveVector = [0, 0];
|
|
|
- const leftTrack = widget.state.setAnimation(1, "interactive/head/left", true);
|
|
|
- const downTrack = widget.state.setAnimation(2, "interactive/head/down", true);
|
|
|
- leftTrack.mixBlend = spine.MixBlend.add;
|
|
|
- downTrack.mixBlend = spine.MixBlend.add;
|
|
|
-
|
|
|
- const setBlendTracks = () => {
|
|
|
- moveVector[0] = getAlphaLeft();
|
|
|
- moveVector[1] = ALPHA_DOWN;
|
|
|
- }
|
|
|
- const resetBlendTracks = () => {
|
|
|
- moveVector[0] = 0;
|
|
|
- moveVector[1] = 0;
|
|
|
- }
|
|
|
- resetBlendTracks();
|
|
|
-
|
|
|
- const getWidthOfText = (txt) => {
|
|
|
- const ruler = document.getElementById("ruler");
|
|
|
- ruler.innerHTML = txt;
|
|
|
- return ruler.offsetWidth;
|
|
|
- }
|
|
|
-
|
|
|
- const getAlphaLeft = () => {
|
|
|
- const { width } = usernameInput.getBoundingClientRect();
|
|
|
- const length = getWidthOfText(usernameInput.value);
|
|
|
- const alpha = Math.min(1, length / width) * 2
|
|
|
- return 1 - alpha;
|
|
|
- };
|
|
|
-
|
|
|
- widget.afterUpdateWorldTransforms = () => {
|
|
|
- if (focusInput) {
|
|
|
- blendTo(moveVector[0], moveVector[1]);
|
|
|
- } else {
|
|
|
- const { x, y, width, height} = widget.getHostElement().getBoundingClientRect();
|
|
|
- const l = -((widget.overlay.cursorCanvasX - (x + width)) / window.innerWidth) * ALPHA_LEFT_RANGE;
|
|
|
- const d = ((widget.overlay.cursorCanvasY - (y + height)) / window.innerHeight) * ALPHA_DOWN_RANGE;
|
|
|
- blendTo(l, d);
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- const blendTo = (x, y) => {
|
|
|
- leftTrack.alpha = mouseX(x);
|
|
|
- downTrack.alpha = mouseY(y);
|
|
|
- }
|
|
|
-
|
|
|
- })();
|
|
|
-
|
|
|
- </script>
|
|
|
-
|
|
|
-
|
|
|
- <div class="split-bottom">
|
|
|
- <pre><code id="code-display">
|
|
|
- <script>escapeHTMLandInject(`
|
|
|
-Look at the source code`
|
|
|
-);</script>
|
|
|
- </code></pre>
|
|
|
- </div>
|
|
|
-
|
|
|
- </div>
|
|
|
-
|
|
|
- <!--
|
|
|
- /////////////////////
|
|
|
- // end section //
|
|
|
- /////////////////////
|
|
|
- -->
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- <!--
|
|
|
- /////////////////////
|
|
|
- // start section //
|
|
|
- /////////////////////
|
|
|
- -->
|
|
|
-
|
|
|
-
|
|
|
- <div class="section vertical-split">
|
|
|
-
|
|
|
- <style>
|
|
|
-
|
|
|
- .phone {
|
|
|
- min-width: 288px;
|
|
|
- height: 80%;
|
|
|
- background: white;
|
|
|
- border-radius: 30px;
|
|
|
- box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
|
|
|
- overflow: hidden;
|
|
|
- border: 16px solid black;
|
|
|
- position: relative;
|
|
|
- display: flex;
|
|
|
- aspect-ratio: 1/2;
|
|
|
- color: black;
|
|
|
- }
|
|
|
-
|
|
|
- .screen-container {
|
|
|
- display: flex;
|
|
|
- width: 100%;
|
|
|
- transition: transform 0.25s linear;
|
|
|
- }
|
|
|
-
|
|
|
- .screen {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- flex-shrink: 0;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- }
|
|
|
-
|
|
|
- .top-section {
|
|
|
- flex: 1;
|
|
|
- background: #ddd;
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- align-items: center;
|
|
|
- font-size: 24px;
|
|
|
- font-weight: bold;
|
|
|
- border-bottom: 5px solid black;
|
|
|
- }
|
|
|
-
|
|
|
- .bottom-section {
|
|
|
- display: flex;
|
|
|
- flex-direction: row;
|
|
|
- /* height: 40%; */
|
|
|
- background: #fff;
|
|
|
- }
|
|
|
-
|
|
|
- .left-section, .right-section {
|
|
|
- flex: 1;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- justify-content: space-between;
|
|
|
- border-right: 5px solid black;
|
|
|
- }
|
|
|
-
|
|
|
- .right-section {
|
|
|
- border-right: none;
|
|
|
- }
|
|
|
-
|
|
|
- .left-section {
|
|
|
- display: grid;
|
|
|
- grid-template-columns: repeat(2, 1fr);
|
|
|
- gap: 5px;
|
|
|
- padding: 5px;
|
|
|
- width: 100%;
|
|
|
- max-width: 800px;
|
|
|
- }
|
|
|
-
|
|
|
- .item {
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- align-items: center;
|
|
|
- justify-content: space-evenly;
|
|
|
- background: #f0f0f0;
|
|
|
- padding: 1px;
|
|
|
- border-radius: 5px;
|
|
|
- }
|
|
|
-
|
|
|
- .item img {
|
|
|
- width: 100%;
|
|
|
- height: auto;
|
|
|
- max-width: 30px;
|
|
|
- }
|
|
|
-
|
|
|
- .controls {
|
|
|
- display: flex;
|
|
|
- gap: 5px;
|
|
|
- flex-direction: row-reverse;
|
|
|
- }
|
|
|
-
|
|
|
- .controls button {
|
|
|
- width: 20px;
|
|
|
- height: 20px;
|
|
|
- font-size: 12px;
|
|
|
- }
|
|
|
-
|
|
|
- .btn-small {
|
|
|
- font-size: 16px;
|
|
|
- border: none;
|
|
|
- background: #007bff;
|
|
|
- color: white;
|
|
|
- cursor: pointer;
|
|
|
- border-radius: 3px;
|
|
|
- margin: 1px 0;
|
|
|
- }
|
|
|
-
|
|
|
- .btn-small:active {
|
|
|
- background: #0056b3;
|
|
|
- }
|
|
|
-
|
|
|
- .list {
|
|
|
- flex: 1;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- background: #f0f0f0;
|
|
|
- }
|
|
|
-
|
|
|
- .list-item {
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
- align-items: center;
|
|
|
- flex: 1;
|
|
|
- font-size: 16px;
|
|
|
- font-weight: bold;
|
|
|
- padding: 5px;
|
|
|
- border-bottom: 3px solid black;
|
|
|
- }
|
|
|
-
|
|
|
- .buttons {
|
|
|
- display: flex;
|
|
|
- }
|
|
|
-
|
|
|
- .btn {
|
|
|
- flex: 1;
|
|
|
- padding: 10px;
|
|
|
- font-size: 16px;
|
|
|
- font-weight: bold;
|
|
|
- border: none;
|
|
|
- background: #dc3545;
|
|
|
- color: white;
|
|
|
- cursor: pointer;
|
|
|
- }
|
|
|
-
|
|
|
- .btn.next {
|
|
|
- background: #28a745;
|
|
|
- }
|
|
|
-
|
|
|
- .btn:active {
|
|
|
- opacity: 0.8;
|
|
|
- }
|
|
|
-
|
|
|
- .buttons-section-2 {
|
|
|
- height: 40%;
|
|
|
- }
|
|
|
-
|
|
|
- .food-piece-circle {
|
|
|
- width: 45px;
|
|
|
- height: 45px;
|
|
|
- border-radius: 50%;
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- align-items: center;
|
|
|
- border: 3px solid transparent;
|
|
|
- transition: border-color 0.3s ease-in-out;
|
|
|
- cursor: pointer;
|
|
|
- background: white;
|
|
|
- box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
|
|
|
- }
|
|
|
-
|
|
|
- .box-button:active {
|
|
|
- box-shadow: inset 0px 4px 6px rgba(0, 0, 0, 0.1);
|
|
|
- transform: translateY(2px);
|
|
|
- }
|
|
|
-
|
|
|
- .group-buttons {
|
|
|
- width: 100%;
|
|
|
- padding: 10px;
|
|
|
- box-sizing: border-box;
|
|
|
- }
|
|
|
- </style>
|
|
|
-
|
|
|
-
|
|
|
- <div style="max-width: 300px; text-align: center; margin: 10px;">
|
|
|
- <div class="phone">
|
|
|
- <spine-overlay overlay-id="phone" scrollable></spine-overlay>
|
|
|
-
|
|
|
-
|
|
|
- <div class="screen-container" id="screenContainer">
|
|
|
-
|
|
|
- <!-- SECTION 1 -->
|
|
|
- <div class="screen">
|
|
|
- <div class="top-section">
|
|
|
- <spine-widget
|
|
|
- identifier="list"
|
|
|
-
|
|
|
- overlay-id="phone"
|
|
|
- default-mix=".2"
|
|
|
- atlas="assets/food/food-app-pro.atlas"
|
|
|
- skeleton="assets/food/list-search.json"
|
|
|
- animation="animation"
|
|
|
-
|
|
|
- isinteractive
|
|
|
-
|
|
|
- ></spine-widget>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="bottom-section">
|
|
|
- <div class="left-section">
|
|
|
- <div id="carrotDiv" class="item">
|
|
|
- <img src="assets/food/carrot-body.png">
|
|
|
- <div class="controls">
|
|
|
- <button id="carrotPlus" class="btn-small">+</button>
|
|
|
- <button id="carrotMinus" class="btn-small">-</button>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div id="tomatoDiv" class="item">
|
|
|
- <img src="assets/food/tomato-body.png">
|
|
|
- <div class="controls">
|
|
|
- <button id="tomatoPlus" class="btn-small">+</button>
|
|
|
- <button id="tomatoMinus" class="btn-small">-</button>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div id="breadDiv" class="item">
|
|
|
- <img src="assets/food/bread.png">
|
|
|
- <div class="controls">
|
|
|
- <button id="breadPlus" class="btn-small">+</button>
|
|
|
- <button id="breadMinus" class="btn-small">-</button>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div id="mushroomDiv" class="item">
|
|
|
- <img src="assets/food/mushroom.png">
|
|
|
- <div class="controls">
|
|
|
- <button id="mushroomPlus" class="btn-small">+</button>
|
|
|
- <button id="mushroomMinus" class="btn-small">-</button>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div class="right-section">
|
|
|
- <div class="list">
|
|
|
- <div class="list-item"><span>Carrots</span> <span id="list-item-carrot">0</span></div>
|
|
|
- <div class="list-item"><span>Tomatoes</span> <span id="list-item-tomato">0</span></div>
|
|
|
- <div class="list-item"><span>Breads</span> <span id="list-item-bread">0</span></div>
|
|
|
- <div class="list-item"><span>Mushrooms</span> <span id="list-item-mushroom">0</span></div>
|
|
|
- </div>
|
|
|
- <div class="buttons">
|
|
|
- <button class="btn">Clear</button>
|
|
|
- <button class="btn next" onclick="nextScreen()">Next</button>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <!-- SECTION 1 -->
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- <!-- SECTION 2 -->
|
|
|
- <div class="screen">
|
|
|
- <div class="top-section">
|
|
|
-
|
|
|
- <spine-widget
|
|
|
- identifier="pan"
|
|
|
-
|
|
|
- overlay-id="phone"
|
|
|
- default-mix=".2"
|
|
|
- atlas="assets/food/food-app-pro.atlas"
|
|
|
- skeleton="assets/food/pan-cooking-pro.json"
|
|
|
- animation="animation"
|
|
|
-
|
|
|
- ></spine-widget>
|
|
|
-
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="buttons-section-2">
|
|
|
-
|
|
|
- <div class="group-buttons">
|
|
|
- <div style="text-align: center;">
|
|
|
- Click on the food to cook it
|
|
|
- </div>
|
|
|
- <div class="buttons">
|
|
|
- <button class="btn" style="padding: 5px;" onclick="prevScreen()">Prev</button>
|
|
|
- <button id="btn-next-2" class="btn" style="padding: 5px; background-color: gainsboro; transition: background-color 0.25s ease-in-out;" onclick="nextScreen()" disabled>Next</button>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div style="display: flex; flex-wrap: wrap; justify-content: center; gap: 10px">
|
|
|
- <div id="foodPieceDiv1" class="food-piece-circle">
|
|
|
- <img src="assets/food/food-piece-1.png" style="width: 70%; height: auto;">
|
|
|
- </div>
|
|
|
-
|
|
|
- <div id="foodPieceDiv2" class="food-piece-circle">
|
|
|
- <img src="assets/food/food-piece-2.png" style="width: 70%; height: auto;">
|
|
|
- </div>
|
|
|
-
|
|
|
- <div id="foodPieceDiv3" class="food-piece-circle">
|
|
|
- <img src="assets/food/food-piece-3.png" style="width: 70%; height: auto;">
|
|
|
- </div>
|
|
|
-
|
|
|
- <div id="foodPieceDiv4" class="food-piece-circle">
|
|
|
- <img src="assets/food/food-piece-4.png" style="width: 70%; height: auto;">
|
|
|
- </div>
|
|
|
-
|
|
|
- <div id="foodPieceDiv5" class="food-piece-circle">
|
|
|
- <img src="assets/food/food-piece-5.png" style="width: 70%; height: auto;">
|
|
|
- </div>
|
|
|
-
|
|
|
- <div id="foodPieceDiv6" class="food-piece-circle">
|
|
|
- <img src="assets/food/food-piece-6.png" style="width: 70%; height: auto;">
|
|
|
- </div>
|
|
|
-
|
|
|
- <div id="foodPieceDiv7" class="food-piece-circle">
|
|
|
- <img src="assets/food/food-piece-7.png" style="width: 70%; height: auto;">
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- </div>
|
|
|
-
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- SECTION 2 -->
|
|
|
-
|
|
|
- <!-- SECTION 3 -->
|
|
|
- <div class="screen">
|
|
|
- <div class="top-section">
|
|
|
-
|
|
|
- <spine-widget
|
|
|
- identifier="delivery"
|
|
|
-
|
|
|
- overlay-id="phone"
|
|
|
- default-mix=".2"
|
|
|
- atlas="assets/food/food-app-pro.atlas"
|
|
|
- skeleton="assets/food/meal-delivery-pro.json"
|
|
|
- animation="animation"
|
|
|
-
|
|
|
- ></spine-widget>
|
|
|
-
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="buttons-section-2">
|
|
|
- <div style="display: flex; flex-wrap: wrap; justify-content: center; gap: 10px">
|
|
|
- <div id="buttonDistance" class="food-piece-circle box-button" style="user-select: none;">
|
|
|
- <img src="assets/food/box.png" style="width: 70%; height: auto;">
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="group-buttons">
|
|
|
- <div style="text-align: center;">
|
|
|
- Help the box to reach the destination by clicking the button above!
|
|
|
- </div>
|
|
|
- <div class="buttons">
|
|
|
- <button class="btn" style="padding: 5px;" onclick="prevScreen()">Prev</button>
|
|
|
- <button id="btn-next-3" class="btn" style="padding: 5px; background-color: gainsboro; transition: background-color 0.25s ease-in-out;" onclick="nextScreen()" disabled>Next</button>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- </div>
|
|
|
-
|
|
|
- </div>
|
|
|
- <!-- SECTION 3 -->
|
|
|
-
|
|
|
- <!-- SECTION 4 -->
|
|
|
- <div class="screen">
|
|
|
- <div class="top-section">
|
|
|
-
|
|
|
- <spine-widget
|
|
|
- identifier="ready"
|
|
|
-
|
|
|
- overlay-id="phone"
|
|
|
- default-mix=".2"
|
|
|
- atlas="assets/food/food-app-pro.atlas"
|
|
|
- skeleton="assets/food/meal-ready-pro.json"
|
|
|
- animation="base"
|
|
|
-
|
|
|
- isinteractive
|
|
|
- ></spine-widget>
|
|
|
-
|
|
|
- </div>
|
|
|
-
|
|
|
- <div class="buttons-section-2">
|
|
|
- <div class="group-buttons">
|
|
|
- <div style="text-align: center;">
|
|
|
- Congratulation! You food has just been delivered!
|
|
|
- </div>
|
|
|
- <div class="buttons">
|
|
|
- <button class="btn" style="padding: 5px;" onclick="prevScreen()">Prev</button>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- </div>
|
|
|
-
|
|
|
- </div>
|
|
|
- <!-- SECTION 4 -->
|
|
|
-
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <script>
|
|
|
- let currentIndex = 0;
|
|
|
- function nextScreen() {
|
|
|
- const container = document.getElementById('screenContainer');
|
|
|
- const totalScreens = document.querySelectorAll('.screen').length;
|
|
|
- if (currentIndex < totalScreens - 1) {
|
|
|
- currentIndex++;
|
|
|
- container.style.transform = `translateX(-${currentIndex * 100}%)`;
|
|
|
- }
|
|
|
- }
|
|
|
- function prevScreen() {
|
|
|
- const container = document.getElementById('screenContainer');
|
|
|
- const totalScreens = document.querySelectorAll('.screen').length;
|
|
|
- if (currentIndex > 0) {
|
|
|
- currentIndex--;
|
|
|
- container.style.transform = `translateX(-${currentIndex * 100}%)`;
|
|
|
- }
|
|
|
- }
|
|
|
- </script>
|
|
|
-
|
|
|
- <script>
|
|
|
- (async () => {
|
|
|
-
|
|
|
- /* SECTION 1 */
|
|
|
- const widget1 = await spine.getSpineWidget("list").whenReady;
|
|
|
-
|
|
|
- const setInteractionSectionOne = (itemName, trackNumber) => {
|
|
|
- const divName = `${itemName}Div`;
|
|
|
- const buttonNamePlus = `${itemName}Plus`;
|
|
|
- const buttonNameMinus = `${itemName}Minus`;
|
|
|
- const listItemName = `list-item-${itemName}`;
|
|
|
- const itemDiv = document.getElementById(divName);
|
|
|
- const listItemDiv = document.getElementById(listItemName);
|
|
|
-
|
|
|
- itemDiv.addEventListener('mouseenter', () => {
|
|
|
- widget1.state.setAnimation(0, `focus-${itemName}`, true);
|
|
|
- widget1.state.setAnimation(trackNumber, `shake-${itemName}`, true);
|
|
|
- });
|
|
|
-
|
|
|
- const setDefaultState = () => {
|
|
|
-
|
|
|
- const currentEntry = widget1.state.getCurrent(trackNumber);
|
|
|
- if (!currentEntry) return;
|
|
|
-
|
|
|
- if (currentEntry.animation.name === `shake-${itemName}`) {
|
|
|
- widget1.state.setEmptyAnimation(trackNumber);
|
|
|
- } else if (currentEntry.next && currentEntry.next.animation && currentEntry.next.animation.name === `shake-${itemName}`) {
|
|
|
- widget1.state.clearNext(currentEntry);
|
|
|
- widget1.state.addEmptyAnimation(trackNumber);
|
|
|
- }
|
|
|
- widget1.state.setAnimation(0, "animation", true);
|
|
|
-
|
|
|
- }
|
|
|
- itemDiv.addEventListener('mouseleave', setDefaultState);
|
|
|
-
|
|
|
- const addItemAction = () => {
|
|
|
- widget1.state.setAnimation(trackNumber, `add-${itemName}`, false);
|
|
|
- widget1.state.addAnimation(trackNumber, `shake-${itemName}`, true);
|
|
|
- listItemDiv.textContent = parseInt(listItemDiv.textContent) + 1;
|
|
|
- }
|
|
|
-
|
|
|
- const itemSlot = widget1.skeleton.findSlot(`bubble-base-${itemName.charAt(0)}`);
|
|
|
- let onItem = false;
|
|
|
- widget1.addCursorSlotEventCallback(itemSlot, (slot, event) => {
|
|
|
-
|
|
|
- if (event === "enter") {
|
|
|
- widget1.state.setAnimation(0, `focus-${itemName}`, true);
|
|
|
- widget1.state.setAnimation(trackNumber, `shake-${itemName}`, true);
|
|
|
- }
|
|
|
-
|
|
|
- if (event === "leave") {
|
|
|
- onItem = false;
|
|
|
- const currentEntry = widget1.state.getCurrent(trackNumber);
|
|
|
- if (currentEntry.animation.name === `shake-${itemName}`) {
|
|
|
- widget1.state.setEmptyAnimation(trackNumber);
|
|
|
- } else if (currentEntry.next && currentEntry.next.animation && currentEntry.next.animation.name === `shake-${itemName}`) {
|
|
|
- widget1.state.clearNext(currentEntry);
|
|
|
- widget1.state.addEmptyAnimation(trackNumber);
|
|
|
- }
|
|
|
-
|
|
|
- const currentEntryZero = widget1.state.getCurrent(0);
|
|
|
- if (currentEntryZero.animation.name === `focus-${itemName}`) {
|
|
|
- widget1.state.setAnimation(0, "animation", true);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (event === "down") {
|
|
|
- addItemAction();
|
|
|
- }
|
|
|
-
|
|
|
- });
|
|
|
-
|
|
|
- const itemButtonPlus = document.getElementById(buttonNamePlus);
|
|
|
- itemButtonPlus.onclick = addItemAction;
|
|
|
-
|
|
|
- const itemButtonMinus = document.getElementById(buttonNameMinus);
|
|
|
- const removeItemAction = () => {
|
|
|
- const current = parseInt(listItemDiv.textContent);
|
|
|
- if (current > 0) listItemDiv.textContent = current - 1;
|
|
|
- }
|
|
|
- itemButtonMinus.onclick = removeItemAction;
|
|
|
-
|
|
|
- }
|
|
|
- setInteractionSectionOne("carrot", 1);
|
|
|
- setInteractionSectionOne("tomato", 2);
|
|
|
- setInteractionSectionOne("bread", 3);
|
|
|
- setInteractionSectionOne("mushroom", 4);
|
|
|
- /* SECTION 1 */
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- /* SECTION 2 */
|
|
|
- const btnNext2 = document.getElementById("btn-next-2");
|
|
|
-
|
|
|
- const widget2 = await spine.getSpineWidget("pan").whenReady;
|
|
|
- const foodPiece1 = widget2.skeleton.findSlot(`food-piece-1`);
|
|
|
- const foodPiece2 = widget2.skeleton.findSlot(`food-piece-2`);
|
|
|
- const foodPiece3 = widget2.skeleton.findSlot(`food-piece-3`);
|
|
|
- const foodPiece4 = widget2.skeleton.findSlot(`food-piece-4`);
|
|
|
- const foodPiece5 = widget2.skeleton.findSlot(`food-piece-5`);
|
|
|
- const foodPiece6 = widget2.skeleton.findSlot(`food-piece-6`);
|
|
|
- const foodPiece7 = widget2.skeleton.findSlot(`food-piece-7`);
|
|
|
-
|
|
|
- const foodPieces = [
|
|
|
- [foodPiece1, document.getElementById("foodPieceDiv1")],
|
|
|
- [foodPiece2, document.getElementById("foodPieceDiv2")],
|
|
|
- [foodPiece3, document.getElementById("foodPieceDiv3")],
|
|
|
- [foodPiece4, document.getElementById("foodPieceDiv4")],
|
|
|
- [foodPiece5, document.getElementById("foodPieceDiv5")],
|
|
|
- [foodPiece6, document.getElementById("foodPieceDiv6")],
|
|
|
- [foodPiece7, document.getElementById("foodPieceDiv7")],
|
|
|
- ];
|
|
|
-
|
|
|
- foodPieces.forEach(([foodPiece, itemDiv], index) => {
|
|
|
- foodPiece.color.set(1, 1, 1, 0);
|
|
|
- itemDiv.addEventListener('mousedown', () => {
|
|
|
-
|
|
|
- if (itemDiv.dataset.cooking) {
|
|
|
- itemDiv.style.borderColor = "transparent";
|
|
|
- delete itemDiv.dataset.cooking;
|
|
|
- const interval = setInterval(() => {
|
|
|
- let alpha = foodPiece.color.a;
|
|
|
- if (alpha <= 0) {
|
|
|
- clearInterval(interval);
|
|
|
- return;
|
|
|
- }
|
|
|
- foodPiece.color.set(1, 1, 1, alpha - 0.1);
|
|
|
- }, 10);
|
|
|
- } else {
|
|
|
- itemDiv.style.borderColor = "#4CAF50";
|
|
|
- itemDiv.dataset.cooking = true;
|
|
|
- const interval = setInterval(() => {
|
|
|
- let alpha = foodPiece.color.a;
|
|
|
- if (alpha >= 1) {
|
|
|
- clearInterval(interval);
|
|
|
- return;
|
|
|
- }
|
|
|
- foodPiece.color.set(1, 1, 1, alpha + 0.1);
|
|
|
- }, 10);
|
|
|
- }
|
|
|
-
|
|
|
- if (foodPieces.every(([,{ dataset }]) => dataset.cooking)) {
|
|
|
- btnNext2.style.backgroundColor = "#28a745";
|
|
|
- btnNext2.removeAttribute("disabled");
|
|
|
- } else {
|
|
|
- btnNext2.style.backgroundColor = "gainsboro";
|
|
|
- btnNext2.setAttribute("disabled", "");
|
|
|
- }
|
|
|
- });
|
|
|
- })
|
|
|
-
|
|
|
- /* SECTION 2 */
|
|
|
-
|
|
|
- /* SECTION 3 */
|
|
|
- const widget3 = await spine.getSpineWidget("delivery").whenReady;
|
|
|
- const btnNext3 = document.getElementById("btn-next-3");
|
|
|
- const box = widget3.skeleton.findSlot("box");
|
|
|
-
|
|
|
- let distance = -1300;
|
|
|
-
|
|
|
- const buttonDistance = document.getElementById("buttonDistance");
|
|
|
- buttonDistance.addEventListener("mousedown", () => {
|
|
|
- let toAdd = 200;
|
|
|
- const interval = setInterval(() => {
|
|
|
- if (toAdd <= 0) {
|
|
|
- clearInterval(interval);
|
|
|
- return;
|
|
|
- }
|
|
|
- toAdd -= 10;
|
|
|
- distance += 10;
|
|
|
- }, 10)
|
|
|
- })
|
|
|
-
|
|
|
- setInterval(() => {
|
|
|
- if (distance <= -1300) return;
|
|
|
- distance -= 5;
|
|
|
-
|
|
|
- if (distance > 2000) {
|
|
|
- btnNext3.style.backgroundColor = "#28a745";
|
|
|
- btnNext3.removeAttribute("disabled");
|
|
|
- } else {
|
|
|
- btnNext3.style.backgroundColor = "gainsboro";
|
|
|
- btnNext3.setAttribute("disabled", "");
|
|
|
- }
|
|
|
- }, 10)
|
|
|
-
|
|
|
- widget3.beforeUpdateWorldTransforms = () => {
|
|
|
- box.bone.x += distance;
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- if (foodPieces.every(([,{ dataset }]) => dataset.cooking)) {
|
|
|
- btnNext3.style.backgroundColor = "#28a745";
|
|
|
- btnNext3.removeAttribute("disabled");
|
|
|
- } else {
|
|
|
- btnNext3.style.backgroundColor = "gainsboro";
|
|
|
- btnNext3.setAttribute("disabled", "");
|
|
|
- }
|
|
|
-
|
|
|
- /* SECTION 3 */
|
|
|
-
|
|
|
-
|
|
|
- /* SECTION 4 */
|
|
|
- const widget4 = await spine.getSpineWidget("ready").whenReady;
|
|
|
-
|
|
|
- const slot4Bread = widget4.skeleton.findSlot("salad");
|
|
|
- widget4.addCursorSlotEventCallback(slot4Bread, (slot, event) => {
|
|
|
-
|
|
|
- if (event === "enter") {
|
|
|
- widget4.state.setAnimation(1, "bread-opening", false);
|
|
|
- widget4.state.addAnimation(1, "bread-open", true);
|
|
|
- }
|
|
|
-
|
|
|
- if (event === "leave") {
|
|
|
- widget4.state.setAnimation(1, "bread-closing", false);
|
|
|
- }
|
|
|
-
|
|
|
- });
|
|
|
-
|
|
|
- const slot4Bottle = widget4.skeleton.findSlot("bottle-base");
|
|
|
- widget4.addCursorSlotEventCallback(slot4Bottle, (slot, event) => {
|
|
|
-
|
|
|
- if (event === "enter") {
|
|
|
- widget4.state.setAnimation(2, "bottle-opening", false);
|
|
|
- widget4.state.addAnimation(2, "bottle-open", true);
|
|
|
- }
|
|
|
-
|
|
|
- if (event === "leave") {
|
|
|
- widget4.state.setAnimation(2, "bottle-closing", false);
|
|
|
- }
|
|
|
-
|
|
|
- });
|
|
|
-
|
|
|
- const slot4Fries = widget4.skeleton.findSlot("fries-case-back");
|
|
|
- widget4.addCursorSlotEventCallback(slot4Fries, (slot, event) => {
|
|
|
-
|
|
|
- if (event === "enter") {
|
|
|
- widget4.state.setAnimation(3, "fries", true);
|
|
|
- }
|
|
|
-
|
|
|
- if (event === "leave") {
|
|
|
- let track = widget4.state.setEmptyAnimation(3);
|
|
|
- track.mixDuration = 1;
|
|
|
- }
|
|
|
-
|
|
|
- });
|
|
|
-
|
|
|
- /* SECTION 4 */
|
|
|
- })();
|
|
|
- </script>
|
|
|
-
|
|
|
-
|
|
|
- <div class="split-bottom">
|
|
|
- <pre><code id="code-display">
|
|
|
- <script>escapeHTMLandInject(`
|
|
|
-Look at the source code`
|
|
|
-);</script>
|
|
|
- </code></pre>
|
|
|
- </div>
|
|
|
-
|
|
|
- </div>
|
|
|
-
|
|
|
-
|
|
|
- <!--
|
|
|
- /////////////////////
|
|
|
- // end section //
|
|
|
- /////////////////////
|
|
|
- -->
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- <!--
|
|
|
- /////////////////////
|
|
|
- // start section //
|
|
|
- /////////////////////
|
|
|
- -->
|
|
|
-
|
|
|
- <div class="section vertical-split" id="spneboy-game">
|
|
|
-
|
|
|
- <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.whenReady, windmill.whenReady]);
|
|
|
-
|
|
|
- spineboy.state.setAnimation(2, "aim", true);
|
|
|
-
|
|
|
- spineboy.skeleton.slots.forEach(slot => {
|
|
|
- if (slot.data.name === "gun") {
|
|
|
- spineboy.addCursorSlotEventCallback(slot, (slot,event) => {
|
|
|
- if (event === "down") {
|
|
|
- spineboy.state.setAnimation(1, "shoot", false);
|
|
|
- }
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- if (slot.data.name === "torso") {
|
|
|
- spineboy.addCursorSlotEventCallback(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.addCursorSlotEventCallback(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.addCursorSlotEventCallback(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;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- 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;
|
|
|
- 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>
|
|
|
-
|
|
|
-
|
|
|
- <div class="split-bottom">
|
|
|
- <pre><code id="code-display">
|
|
|
- <script>
|
|
|
- escapeHTMLandInject(`
|
|
|
-Look at the source code`)
|
|
|
- </script>
|
|
|
- </code></pre>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!--
|
|
|
- /////////////////////
|
|
|
- // end section //
|
|
|
- /////////////////////
|
|
|
- -->
|
|
|
-
|
|
|
<script>
|
|
|
spine.SpineWebComponentWidget.SHOW_FPS = true;
|
|
|
</script>
|