feat(settings/user/snake): display score in top bar, and implement intro and outro
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import * as THREE from "three";
|
import * as THREE from "three";
|
||||||
import { useIntervalFn, useLocalStorage } from "@vueuse/core";
|
import { useIntervalFn, useLocalStorage } from "@vueuse/core";
|
||||||
|
import gsap from "gsap";
|
||||||
|
|
||||||
const store = useSettingsStore();
|
const store = useSettingsStore();
|
||||||
const achievements = useAchievementsStore();
|
const achievements = useAchievementsStore();
|
||||||
@@ -12,15 +13,84 @@ const assets = atlas.images.settings.bottomScreen;
|
|||||||
|
|
||||||
const highScore = useLocalStorage("snake_high_score", 0);
|
const highScore = useLocalStorage("snake_high_score", 0);
|
||||||
|
|
||||||
const handleCancel = () => {
|
const BOARD_SLIDE_OFFSET = 96;
|
||||||
|
const BOARD_SLIDE_DURATION = 0.25;
|
||||||
|
const TEXT_FADE_DURATION = 0.15;
|
||||||
|
const SCORE_OFFSET = -20;
|
||||||
|
const SCORE_DURATION = 0.15;
|
||||||
|
|
||||||
|
const intro = reactive({
|
||||||
|
boardOffsetY: BOARD_SLIDE_OFFSET,
|
||||||
|
boardOpacity: 0,
|
||||||
|
textOpacity: 0,
|
||||||
|
scoreOffsetY: SCORE_OFFSET,
|
||||||
|
});
|
||||||
|
|
||||||
|
const animateIntro = async () => {
|
||||||
|
await gsap
|
||||||
|
.timeline()
|
||||||
|
.to(
|
||||||
|
intro,
|
||||||
|
{ boardOffsetY: 0, duration: BOARD_SLIDE_DURATION, ease: "none" },
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.to(
|
||||||
|
intro,
|
||||||
|
{ boardOpacity: 1, duration: BOARD_SLIDE_DURATION, ease: "none" },
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.to(
|
||||||
|
intro,
|
||||||
|
{ textOpacity: 1, duration: TEXT_FADE_DURATION, ease: "none" },
|
||||||
|
BOARD_SLIDE_DURATION,
|
||||||
|
)
|
||||||
|
.to(
|
||||||
|
intro,
|
||||||
|
{ scoreOffsetY: 0, duration: SCORE_DURATION, ease: "none" },
|
||||||
|
BOARD_SLIDE_DURATION + TEXT_FADE_DURATION,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const animateOutro = async () => {
|
||||||
|
await gsap
|
||||||
|
.timeline()
|
||||||
|
.to(
|
||||||
|
intro,
|
||||||
|
{
|
||||||
|
boardOffsetY: BOARD_SLIDE_OFFSET,
|
||||||
|
duration: BOARD_SLIDE_DURATION,
|
||||||
|
ease: "none",
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.to(
|
||||||
|
intro,
|
||||||
|
{ boardOpacity: 0, duration: BOARD_SLIDE_DURATION, ease: "none" },
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.to(
|
||||||
|
intro,
|
||||||
|
{ scoreOffsetY: SCORE_OFFSET, duration: SCORE_DURATION, ease: "none" },
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
animateIntro();
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleCancel = async () => {
|
||||||
switch (state.value) {
|
switch (state.value) {
|
||||||
case "alive": {
|
case "alive": {
|
||||||
state.value = "pause";
|
state.value = "pause";
|
||||||
confirmationModal.open({
|
confirmationModal.open({
|
||||||
text: $t("settings.user.snake.quitConfirmation"),
|
text: $t("settings.user.snake.quitConfirmation"),
|
||||||
onConfirm: () => {},
|
onConfirm: () => {},
|
||||||
onClosed: (choice) => {
|
onClosed: async (choice) => {
|
||||||
if (choice === "confirm") store.closeSubMenu();
|
if (choice === "confirm") {
|
||||||
|
await animateOutro();
|
||||||
|
store.closeSubMenu();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
onCancel: () => {
|
onCancel: () => {
|
||||||
state.value = "alive";
|
state.value = "alive";
|
||||||
@@ -31,6 +101,7 @@ const handleCancel = () => {
|
|||||||
|
|
||||||
case "dead":
|
case "dead":
|
||||||
case "waiting": {
|
case "waiting": {
|
||||||
|
await animateOutro();
|
||||||
store.closeSubMenu();
|
store.closeSubMenu();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -65,9 +136,9 @@ const handleConfirm = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const BOARD_X = 15;
|
const BOARD_X = 15;
|
||||||
const BOARD_Y = 48;
|
const BOARD_Y = 33;
|
||||||
const BOARD_WIDTH = 13;
|
const BOARD_WIDTH = 13;
|
||||||
const BOARD_HEIGHT = 7;
|
const BOARD_HEIGHT = 8;
|
||||||
const CELL_SIZE = 15;
|
const CELL_SIZE = 15;
|
||||||
const CELL_PADDING = 1;
|
const CELL_PADDING = 1;
|
||||||
|
|
||||||
@@ -154,34 +225,22 @@ const cellToBoardPos = (pos: THREE.Vector2) => ({
|
|||||||
onRender((ctx) => {
|
onRender((ctx) => {
|
||||||
assets.background.draw(ctx, 0, 0);
|
assets.background.draw(ctx, 0, 0);
|
||||||
|
|
||||||
// score
|
ctx.save();
|
||||||
assets.user.snakeScore.draw(ctx, 27, 32);
|
ctx.globalAlpha = intro.boardOpacity;
|
||||||
|
ctx.translate(0, intro.boardOffsetY);
|
||||||
|
|
||||||
ctx.textBaseline = "top";
|
ctx.textBaseline = "top";
|
||||||
ctx.font = "10px NDS10";
|
ctx.font = "10px NDS10";
|
||||||
ctx.fillStyle = "#282828";
|
|
||||||
fillTextHCentered(
|
|
||||||
ctx,
|
|
||||||
$t("settings.user.snake.score", { score }),
|
|
||||||
27,
|
|
||||||
36,
|
|
||||||
108,
|
|
||||||
);
|
|
||||||
fillTextHCentered(
|
|
||||||
ctx,
|
|
||||||
$t("settings.user.snake.best", { best: highScore.value }),
|
|
||||||
135,
|
|
||||||
36,
|
|
||||||
108,
|
|
||||||
);
|
|
||||||
|
|
||||||
// board
|
// board
|
||||||
assets.user.snakeBoard.draw(ctx, 15, 48);
|
assets.user.snakeBoard.draw(ctx, BOARD_X, BOARD_Y);
|
||||||
|
|
||||||
if (state.value === "waiting") {
|
if (state.value === "waiting") {
|
||||||
|
ctx.globalAlpha = intro.textOpacity;
|
||||||
ctx.fillStyle = "#fbfbfb";
|
ctx.fillStyle = "#fbfbfb";
|
||||||
const text = `\n\n\n ${$t("settings.user.snake.startPrompt")}`;
|
const text = $t("settings.user.snake.startPrompt");
|
||||||
let x = 15;
|
let x = 15;
|
||||||
let y = 52;
|
let y = 37;
|
||||||
for (let i = 0; i < text.length; i += 1, x += 16) {
|
for (let i = 0; i < text.length; i += 1, x += 16) {
|
||||||
while (text[i] === "\n") {
|
while (text[i] === "\n") {
|
||||||
x = 15;
|
x = 15;
|
||||||
@@ -216,8 +275,22 @@ onRender((ctx) => {
|
|||||||
cellToBoardPos(position).y,
|
cellToBoardPos(position).y,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onRender((ctx) => {
|
||||||
|
ctx.translate(0, intro.scoreOffsetY);
|
||||||
|
drawButton(ctx, $t("settings.user.snake.score", { score }), 10, 2, 118);
|
||||||
|
drawButton(
|
||||||
|
ctx,
|
||||||
|
$t("settings.user.snake.best", { best: highScore.value }),
|
||||||
|
138,
|
||||||
|
2,
|
||||||
|
108,
|
||||||
|
);
|
||||||
|
}, 110);
|
||||||
|
|
||||||
useKeyDown((key) => {
|
useKeyDown((key) => {
|
||||||
if (state.value !== "alive") return;
|
if (state.value !== "alive") return;
|
||||||
|
|
||||||
|
|||||||
@@ -133,7 +133,7 @@
|
|||||||
"description": "Play a quick game of Snake!",
|
"description": "Play a quick game of Snake!",
|
||||||
"score": "Score: {score}",
|
"score": "Score: {score}",
|
||||||
"best": "Best: {best}",
|
"best": "Best: {best}",
|
||||||
"startPrompt": "Press icon_a to start.",
|
"startPrompt": "\n\n\n Press icon_a to\n start",
|
||||||
"quitConfirmation": "Quit the game?",
|
"quitConfirmation": "Quit the game?",
|
||||||
"restartConfirmation": "Restart the game?"
|
"restartConfirmation": "Restart the game?"
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 120 B After Width: | Height: | Size: 164 B |
Reference in New Issue
Block a user