feat(settings/options/2048): display score in top bar, and implement intro and outro animation
This commit is contained in:
@@ -12,8 +12,11 @@ const handleActivateB = () => {
|
|||||||
confirmationModal.open({
|
confirmationModal.open({
|
||||||
text: $t("settings.options.2048.quitConfirmation"),
|
text: $t("settings.options.2048.quitConfirmation"),
|
||||||
onConfirm: () => {},
|
onConfirm: () => {},
|
||||||
onClosed: (choice) => {
|
onClosed: async (choice) => {
|
||||||
if (choice === "confirm") store.closeSubMenu();
|
if (choice === "confirm") {
|
||||||
|
await animateOutro();
|
||||||
|
store.closeSubMenu();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -62,9 +65,17 @@ const BOARD_X = 64;
|
|||||||
const BOARD_Y = 32;
|
const BOARD_Y = 32;
|
||||||
const BOARD_SIZE = 4;
|
const BOARD_SIZE = 4;
|
||||||
|
|
||||||
const SCORE_X = 195;
|
const SLIDE_OFFSET = 96;
|
||||||
const SCORE_Y = 36;
|
const SLIDE_DURATION = 0.25;
|
||||||
const HIGH_SCORE_Y = 68;
|
const SCORE_OFFSET = -20;
|
||||||
|
const SCORE_DURATION = 0.15;
|
||||||
|
|
||||||
|
const intro = reactive({
|
||||||
|
frameOffsetY: SLIDE_OFFSET,
|
||||||
|
frameOpacity: 0,
|
||||||
|
scoreOffsetY: SCORE_OFFSET,
|
||||||
|
tilesVisible: false,
|
||||||
|
});
|
||||||
|
|
||||||
const emptyBoard = () =>
|
const emptyBoard = () =>
|
||||||
Array.from({ length: BOARD_SIZE }, () =>
|
Array.from({ length: BOARD_SIZE }, () =>
|
||||||
@@ -125,12 +136,55 @@ const animateSpawnAll = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const animateIntro = async () => {
|
||||||
|
buildTilesFromBoard();
|
||||||
|
|
||||||
|
await gsap
|
||||||
|
.timeline()
|
||||||
|
.to(intro, { frameOffsetY: 0, duration: SLIDE_DURATION, ease: "none" }, 0)
|
||||||
|
.to(intro, { frameOpacity: 1, duration: SLIDE_DURATION, ease: "none" }, 0)
|
||||||
|
.call(
|
||||||
|
() => {
|
||||||
|
intro.tilesVisible = true;
|
||||||
|
animateSpawnAll();
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
SLIDE_DURATION,
|
||||||
|
)
|
||||||
|
.to(
|
||||||
|
intro,
|
||||||
|
{ scoreOffsetY: 0, duration: SCORE_DURATION, ease: "none" },
|
||||||
|
SLIDE_DURATION,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const animateOutro = async () => {
|
||||||
|
await gsap
|
||||||
|
.timeline()
|
||||||
|
.to(
|
||||||
|
intro,
|
||||||
|
{ frameOffsetY: SLIDE_OFFSET, duration: SLIDE_DURATION, ease: "none" },
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.to(intro, { frameOpacity: 0, duration: SLIDE_DURATION, ease: "none" }, 0)
|
||||||
|
.to(
|
||||||
|
intro,
|
||||||
|
{ scoreOffsetY: SCORE_OFFSET, duration: SCORE_DURATION, ease: "none" },
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
animateIntro();
|
||||||
|
});
|
||||||
|
|
||||||
onRender((ctx) => {
|
onRender((ctx) => {
|
||||||
assets.images.home.topScreen.background.draw(ctx, 0, 0);
|
assets.images.home.topScreen.background.draw(ctx, 0, 0);
|
||||||
ctx.textBaseline = "top";
|
ctx.textBaseline = "top";
|
||||||
|
|
||||||
ctx.save();
|
ctx.save();
|
||||||
ctx.translate(BOARD_X, BOARD_Y);
|
ctx.globalAlpha = intro.frameOpacity;
|
||||||
|
ctx.translate(BOARD_X, BOARD_Y + intro.frameOffsetY);
|
||||||
|
|
||||||
assets.images.settings.bottomScreen.options._2048.frame.draw(ctx, -3, -3);
|
assets.images.settings.bottomScreen.options._2048.frame.draw(ctx, -3, -3);
|
||||||
|
|
||||||
@@ -149,6 +203,11 @@ onRender((ctx) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!intro.tilesVisible) {
|
||||||
|
ctx.restore();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (const tile of tiles) {
|
for (const tile of tiles) {
|
||||||
const color = TILE_COLORS[tile.value] ?? LAST_TILE_COLOR;
|
const color = TILE_COLORS[tile.value] ?? LAST_TILE_COLOR;
|
||||||
|
|
||||||
@@ -174,27 +233,20 @@ onRender((ctx) => {
|
|||||||
ctx.restore();
|
ctx.restore();
|
||||||
}
|
}
|
||||||
ctx.restore();
|
ctx.restore();
|
||||||
|
|
||||||
ctx.font = "7px NDS7";
|
|
||||||
ctx.fillStyle = "#010101";
|
|
||||||
|
|
||||||
// score
|
|
||||||
ctx.fillText(`${$t("settings.options.2048.score")}:`, SCORE_X, SCORE_Y);
|
|
||||||
ctx.fillText(score.toString(), SCORE_X, SCORE_Y + 16);
|
|
||||||
|
|
||||||
// high score
|
|
||||||
ctx.fillText(
|
|
||||||
`${$t("settings.options.2048.highScore")}:`,
|
|
||||||
SCORE_X,
|
|
||||||
HIGH_SCORE_Y,
|
|
||||||
);
|
|
||||||
ctx.fillText(
|
|
||||||
savedState.value.highScore.toString(),
|
|
||||||
SCORE_X,
|
|
||||||
HIGH_SCORE_Y + 16,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onRender((ctx) => {
|
||||||
|
ctx.translate(0, intro.scoreOffsetY);
|
||||||
|
drawButton(ctx, `${$t("settings.options.2048.score")}: ${score}`, 10, 2, 118);
|
||||||
|
drawButton(
|
||||||
|
ctx,
|
||||||
|
`${$t("settings.options.2048.highScore")}: ${savedState.value.highScore}`,
|
||||||
|
138,
|
||||||
|
2,
|
||||||
|
108,
|
||||||
|
);
|
||||||
|
}, 110);
|
||||||
|
|
||||||
const getCell = (row: number, col: number) => board[row]![col]!;
|
const getCell = (row: number, col: number) => board[row]![col]!;
|
||||||
const setCell = (row: number, col: number, val: number) => {
|
const setCell = (row: number, col: number, val: number) => {
|
||||||
board[row]![col] = val;
|
board[row]![col] = val;
|
||||||
@@ -297,10 +349,9 @@ const isDead = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (board.every((r) => r.every((c) => c === 0))) {
|
if (board.every((r) => r.every((c) => c === 0))) {
|
||||||
resetBoard();
|
spawnTile();
|
||||||
} else {
|
spawnTile();
|
||||||
buildTilesFromBoard();
|
saveState();
|
||||||
animateSpawnAll();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const slide = (rowDir: number, colDir: number) => {
|
const slide = (rowDir: number, colDir: number) => {
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 188 B After Width: | Height: | Size: 134 B |
Reference in New Issue
Block a user