feat(nds): add audio in all menus
This commit is contained in:
@@ -1,58 +1,14 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const { onRender } = useScreen();
|
const { onRender } = useScreen();
|
||||||
|
const { assets } = useAssets();
|
||||||
|
|
||||||
const app = useAppStore();
|
const app = useAppStore();
|
||||||
const store = useHomeStore();
|
const store = useHomeStore();
|
||||||
const { assets } = useAssets();
|
const tickClock = useClockTick();
|
||||||
|
|
||||||
const CENTER_X = 63;
|
const CENTER_X = 63;
|
||||||
const CENTER_Y = 95;
|
const CENTER_Y = 95;
|
||||||
|
|
||||||
function drawLine(
|
|
||||||
ctx: CanvasRenderingContext2D,
|
|
||||||
x0: number,
|
|
||||||
y0: number,
|
|
||||||
x1: number,
|
|
||||||
y1: number,
|
|
||||||
width: number,
|
|
||||||
) {
|
|
||||||
const dx = Math.abs(x1 - x0);
|
|
||||||
const dy = Math.abs(y1 - y0);
|
|
||||||
const sx = x0 < x1 ? 1 : -1;
|
|
||||||
const sy = y0 < y1 ? 1 : -1;
|
|
||||||
let err = dx - dy;
|
|
||||||
|
|
||||||
const drawThickPixel = (x: number, y: number) => {
|
|
||||||
const isVertical = dy > dx;
|
|
||||||
|
|
||||||
if (width === 1) {
|
|
||||||
ctx.fillRect(x, y, 1, 1);
|
|
||||||
} else if (isVertical) {
|
|
||||||
const offset = Math.floor((width - 1) / 2);
|
|
||||||
ctx.fillRect(x - offset, y, width, 1);
|
|
||||||
} else {
|
|
||||||
const offset = Math.floor((width - 1) / 2);
|
|
||||||
ctx.fillRect(x, y - offset, 1, width);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
drawThickPixel(x0, y0);
|
|
||||||
|
|
||||||
if (x0 === x1 && y0 === y1) break;
|
|
||||||
|
|
||||||
const e2 = 2 * err;
|
|
||||||
if (e2 > -dy) {
|
|
||||||
err -= dy;
|
|
||||||
x0 += sx;
|
|
||||||
}
|
|
||||||
if (e2 < dx) {
|
|
||||||
err += dx;
|
|
||||||
y0 += sy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onRender((ctx) => {
|
onRender((ctx) => {
|
||||||
ctx.globalAlpha = store.isIntro
|
ctx.globalAlpha = store.isIntro
|
||||||
? store.intro.topScreenOpacity
|
? store.intro.topScreenOpacity
|
||||||
@@ -66,7 +22,9 @@ onRender((ctx) => {
|
|||||||
: store.isOutro && store.outro.animateTop
|
: store.isOutro && store.outro.animateTop
|
||||||
? store.outro.stage2Opacity
|
? store.outro.stage2Opacity
|
||||||
: 1;
|
: 1;
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
tickClock(now);
|
||||||
|
|
||||||
const renderHand = (
|
const renderHand = (
|
||||||
value: number,
|
value: number,
|
||||||
|
|||||||
@@ -58,6 +58,8 @@ const BIG_CIRCLE_DURATION = 0.09;
|
|||||||
const POST_DURATION = 0.07;
|
const POST_DURATION = 0.07;
|
||||||
|
|
||||||
const startButtonAnimation = (type: ButtonType) => {
|
const startButtonAnimation = (type: ButtonType) => {
|
||||||
|
assets.audio.pkmnButton.play();
|
||||||
|
|
||||||
const anim: ButtonAnimation = {
|
const anim: ButtonAnimation = {
|
||||||
type,
|
type,
|
||||||
position: BUTTONS[type].position,
|
position: BUTTONS[type].position,
|
||||||
|
|||||||
@@ -53,13 +53,20 @@ useKeyDown(({ key }) => {
|
|||||||
|
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case "NDS_UP":
|
case "NDS_UP":
|
||||||
|
if (selectedOption !== "yes") {
|
||||||
selectedOption = "yes";
|
selectedOption = "yes";
|
||||||
|
assets.audio.pkmnSelector.play();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case "NDS_DOWN":
|
case "NDS_DOWN":
|
||||||
|
if (selectedOption !== "no") {
|
||||||
selectedOption = "no";
|
selectedOption = "no";
|
||||||
|
assets.audio.pkmnSelector.play();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case "NDS_A":
|
case "NDS_A":
|
||||||
case "NDS_START":
|
case "NDS_START":
|
||||||
|
assets.audio.pkmnSelector.play();
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (selectedOption === "yes") store.visitProject();
|
if (selectedOption === "yes") store.visitProject();
|
||||||
store.showConfirmationPopup = false;
|
store.showConfirmationPopup = false;
|
||||||
@@ -90,12 +97,14 @@ onClick((x, y) => {
|
|||||||
const ACTIVATION_DELAY = 50;
|
const ACTIVATION_DELAY = 50;
|
||||||
|
|
||||||
if (rectContains([198, 105, 50, 14], [x, y])) {
|
if (rectContains([198, 105, 50, 14], [x, y])) {
|
||||||
|
assets.audio.pkmnSelector.play();
|
||||||
selectedOption = "yes";
|
selectedOption = "yes";
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
store.visitProject();
|
store.visitProject();
|
||||||
store.showConfirmationPopup = false;
|
store.showConfirmationPopup = false;
|
||||||
}, ACTIVATION_DELAY);
|
}, ACTIVATION_DELAY);
|
||||||
} else if (rectContains([198, 121, 50, 14], [x, y])) {
|
} else if (rectContains([198, 121, 50, 14], [x, y])) {
|
||||||
|
assets.audio.pkmnSelector.play();
|
||||||
selectedOption = "no";
|
selectedOption = "no";
|
||||||
setTimeout(() => (store.showConfirmationPopup = false), ACTIVATION_DELAY);
|
setTimeout(() => (store.showConfirmationPopup = false), ACTIVATION_DELAY);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ const handleReset = () => {
|
|||||||
|
|
||||||
const handleVisitAll = () => {
|
const handleVisitAll = () => {
|
||||||
if (isAnimating.value) return;
|
if (isAnimating.value) return;
|
||||||
|
assets.audio.menuOpen.play();
|
||||||
achievementsScreen.animateFadeToBlackIntro();
|
achievementsScreen.animateFadeToBlackIntro();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -71,11 +71,17 @@ const { select, selected, pressed, selectorPosition } = useButtonNavigation({
|
|||||||
onActivate: (buttonName) => {
|
onActivate: (buttonName) => {
|
||||||
if (isSubMenu(buttonName)) {
|
if (isSubMenu(buttonName)) {
|
||||||
store.openSubMenu(buttonName);
|
store.openSubMenu(buttonName);
|
||||||
|
} else if (buttonName === "touchScreen") {
|
||||||
|
store.openSubMenu("touchScreenTapTap");
|
||||||
} else {
|
} else {
|
||||||
|
if (!store.menuExpanded) {
|
||||||
|
assets.audio.settingsMenuOpen.play();
|
||||||
|
} else {
|
||||||
|
assets.audio.tinyClick.play(0.8);
|
||||||
|
}
|
||||||
if (buttonName === "options") select("optionsLanguage");
|
if (buttonName === "options") select("optionsLanguage");
|
||||||
if (buttonName === "clock") select("clockAchievements");
|
if (buttonName === "clock") select("clockAchievements");
|
||||||
if (buttonName === "user") select("userUserName");
|
if (buttonName === "user") select("userUserName");
|
||||||
if (buttonName === "touchScreen") store.openSubMenu("touchScreenTapTap");
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
navigation: {
|
navigation: {
|
||||||
@@ -156,6 +162,21 @@ const { select, selected, pressed, selectorPosition } = useButtonNavigation({
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
onNavigate: (buttonName) => {
|
||||||
|
if (isMainMenu(buttonName)) {
|
||||||
|
if (store.menuExpanded) {
|
||||||
|
assets.audio.settingsMenuClose.play();
|
||||||
|
} else {
|
||||||
|
assets.audio.tinyClick.play(0.8);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!store.menuExpanded) {
|
||||||
|
assets.audio.settingsMenuOpen.play();
|
||||||
|
} else {
|
||||||
|
assets.audio.tinyClick.play(0.8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
disabled: computed(
|
disabled: computed(
|
||||||
() =>
|
() =>
|
||||||
store.currentSubMenu !== null ||
|
store.currentSubMenu !== null ||
|
||||||
@@ -256,12 +277,17 @@ const handleActivateA = () => {
|
|||||||
|
|
||||||
if (isSubMenu(selected.value)) {
|
if (isSubMenu(selected.value)) {
|
||||||
store.openSubMenu(selected.value);
|
store.openSubMenu(selected.value);
|
||||||
|
} else if (selected.value === "touchScreen") {
|
||||||
|
store.openSubMenu("touchScreenTapTap");
|
||||||
} else {
|
} else {
|
||||||
|
if (!store.menuExpanded) {
|
||||||
|
assets.audio.settingsMenuOpen.play();
|
||||||
|
} else {
|
||||||
|
assets.audio.tinyClick.play(0.8);
|
||||||
|
}
|
||||||
if (selected.value === "options") select("optionsLanguage");
|
if (selected.value === "options") select("optionsLanguage");
|
||||||
if (selected.value === "clock") select("clockAchievements");
|
if (selected.value === "clock") select("clockAchievements");
|
||||||
if (selected.value === "user") select("userUserName");
|
if (selected.value === "user") select("userUserName");
|
||||||
if (selected.value === "touchScreen")
|
|
||||||
store.openSubMenu("touchScreenTapTap");
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -270,6 +296,7 @@ const handleActivateB = () => {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (isSubmenuSelected.value) {
|
if (isSubmenuSelected.value) {
|
||||||
|
assets.audio.settingsMenuClose.play();
|
||||||
select(getParentMenu(selected.value));
|
select(getParentMenu(selected.value));
|
||||||
} else {
|
} else {
|
||||||
store.animateOutro();
|
store.animateOutro();
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const handleActivateB = () => {
|
|||||||
onClosed: async (choice) => {
|
onClosed: async (choice) => {
|
||||||
if (choice === "A") {
|
if (choice === "A") {
|
||||||
await animateOutro();
|
await animateOutro();
|
||||||
store.closeSubMenu();
|
store.closeSubMenu(true);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
keepButtonsDown: (choice) => choice === "A",
|
keepButtonsDown: (choice) => choice === "A",
|
||||||
@@ -460,6 +460,8 @@ const slide = (rowDir: number, colDir: number) => {
|
|||||||
);
|
);
|
||||||
if (!changed) return;
|
if (!changed) return;
|
||||||
|
|
||||||
|
assets.audio.type.play(0.35);
|
||||||
|
|
||||||
if (board.some((r) => r.some((c) => c >= 512))) {
|
if (board.some((r) => r.some((c) => c >= 512))) {
|
||||||
achievements.unlock("2048_score_512");
|
achievements.unlock("2048_score_512");
|
||||||
}
|
}
|
||||||
@@ -534,11 +536,20 @@ const slide = (rowDir: number, colDir: number) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mergePairs.length > 0) {
|
||||||
|
const highestMerge = Math.max(...mergePairs.map((p) => p.mergedValue));
|
||||||
|
const boardMax = Math.max(0, ...beforeTiles.map((t) => t.value));
|
||||||
|
if (highestMerge > boardMax) {
|
||||||
|
assets.audio.duplicate.play(0.35);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const spawned = spawnTile();
|
const spawned = spawnTile();
|
||||||
saveState();
|
saveState();
|
||||||
|
|
||||||
if (isDead()) {
|
if (isDead()) {
|
||||||
buildTilesFromBoard();
|
buildTilesFromBoard();
|
||||||
|
assets.audio.invalid.play();
|
||||||
showRestartModal();
|
showRestartModal();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ const handleActivateA = () => {
|
|||||||
),
|
),
|
||||||
onClosed: async () => {
|
onClosed: async () => {
|
||||||
await animateOutro();
|
await animateOutro();
|
||||||
store.closeSubMenu();
|
store.closeSubMenu(true);
|
||||||
},
|
},
|
||||||
keepButtonsDown: true,
|
keepButtonsDown: true,
|
||||||
timeout: 2000,
|
timeout: 2000,
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ const handleActivateA = () => {
|
|||||||
: $t("settings.options.renderingMode.confirmation2d"),
|
: $t("settings.options.renderingMode.confirmation2d"),
|
||||||
onClosed: async () => {
|
onClosed: async () => {
|
||||||
await animateOutro();
|
await animateOutro();
|
||||||
store.closeSubMenu();
|
store.closeSubMenu(true);
|
||||||
},
|
},
|
||||||
keepButtonsDown: true,
|
keepButtonsDown: true,
|
||||||
timeout: 2000,
|
timeout: 2000,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ const app = useAppStore();
|
|||||||
const store = useSettingsStore();
|
const store = useSettingsStore();
|
||||||
const achievements = useAchievementsStore();
|
const achievements = useAchievementsStore();
|
||||||
const confirmationModal = useConfirmationModal();
|
const confirmationModal = useConfirmationModal();
|
||||||
|
const { assets } = useAssets();
|
||||||
|
|
||||||
const { onRender, onClick } = useScreen();
|
const { onRender, onClick } = useScreen();
|
||||||
|
|
||||||
@@ -161,7 +162,7 @@ const handleActivateB = () => {
|
|||||||
onClosed: async (choice) => {
|
onClosed: async (choice) => {
|
||||||
if (choice === "A") {
|
if (choice === "A") {
|
||||||
await animateOutro();
|
await animateOutro();
|
||||||
store.closeSubMenu();
|
store.closeSubMenu(true);
|
||||||
} else {
|
} else {
|
||||||
state.value = "playing";
|
state.value = "playing";
|
||||||
}
|
}
|
||||||
@@ -190,6 +191,7 @@ const handleActivateA = async () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else if (state.value === "waiting") {
|
} else if (state.value === "waiting") {
|
||||||
|
assets.audio.menuConfirmed.play();
|
||||||
await gsap.to(animation, {
|
await gsap.to(animation, {
|
||||||
areaOpacity: 0,
|
areaOpacity: 0,
|
||||||
duration: AREA_FADE_DURATION,
|
duration: AREA_FADE_DURATION,
|
||||||
@@ -264,7 +266,7 @@ const showDeathScreen = () => {
|
|||||||
resetGame();
|
resetGame();
|
||||||
} else {
|
} else {
|
||||||
await animateOutro();
|
await animateOutro();
|
||||||
store.closeSubMenu();
|
store.closeSubMenu(true);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
keepButtonsDown: (choice) => choice === "B",
|
keepButtonsDown: (choice) => choice === "B",
|
||||||
@@ -286,6 +288,7 @@ onClick((mx, my) => {
|
|||||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||||
|
|
||||||
if (distance <= circle.radius) {
|
if (distance <= circle.radius) {
|
||||||
|
assets.audio.type.play(0.35);
|
||||||
rings.push({
|
rings.push({
|
||||||
x: circle.x,
|
x: circle.x,
|
||||||
y: circle.y,
|
y: circle.y,
|
||||||
@@ -336,7 +339,10 @@ onRender((ctx, deltaTime) => {
|
|||||||
lives--;
|
lives--;
|
||||||
if (lives <= 0) {
|
if (lives <= 0) {
|
||||||
state.value = "ended";
|
state.value = "ended";
|
||||||
|
assets.audio.invalid.play();
|
||||||
showDeathScreen();
|
showDeathScreen();
|
||||||
|
} else {
|
||||||
|
assets.audio.eraser.play();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ const handleActivateA = () => {
|
|||||||
text,
|
text,
|
||||||
onClosed: async () => {
|
onClosed: async () => {
|
||||||
await animateOutro();
|
await animateOutro();
|
||||||
store.closeSubMenu();
|
store.closeSubMenu(true);
|
||||||
},
|
},
|
||||||
keepButtonsDown: true,
|
keepButtonsDown: true,
|
||||||
timeout: 2000,
|
timeout: 2000,
|
||||||
|
|||||||
@@ -156,16 +156,16 @@ useKeyDown(({ key }) => {
|
|||||||
|
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case "NDS_UP":
|
case "NDS_UP":
|
||||||
if (selectedRow > 0) select(selectedCol, selectedRow - 1);
|
if (selectedRow > 0) { assets.audio.tinyClick.play(0.8); select(selectedCol, selectedRow - 1); }
|
||||||
break;
|
break;
|
||||||
case "NDS_RIGHT":
|
case "NDS_RIGHT":
|
||||||
if (selectedCol < GRID_SIZE - 1) select(selectedCol + 1, selectedRow);
|
if (selectedCol < GRID_SIZE - 1) { assets.audio.tinyClick.play(0.8); select(selectedCol + 1, selectedRow); }
|
||||||
break;
|
break;
|
||||||
case "NDS_DOWN":
|
case "NDS_DOWN":
|
||||||
if (selectedRow < GRID_SIZE - 1) select(selectedCol, selectedRow + 1);
|
if (selectedRow < GRID_SIZE - 1) { assets.audio.tinyClick.play(0.8); select(selectedCol, selectedRow + 1); }
|
||||||
break;
|
break;
|
||||||
case "NDS_LEFT":
|
case "NDS_LEFT":
|
||||||
if (selectedCol > 0) select(selectedCol - 1, selectedRow);
|
if (selectedCol > 0) { assets.audio.tinyClick.play(0.8); select(selectedCol - 1, selectedRow); }
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -186,6 +186,7 @@ onClick((x, y) => {
|
|||||||
rectContains([0, 0, GRID_SIZE - 1, GRID_SIZE - 1], [col, row]) &&
|
rectContains([0, 0, GRID_SIZE - 1, GRID_SIZE - 1], [col, row]) &&
|
||||||
rectContains([0, 0, CELL_SIZE + 1, CELL_SIZE + 1], [cellLocalX, cellLocalY])
|
rectContains([0, 0, CELL_SIZE + 1, CELL_SIZE + 1], [cellLocalX, cellLocalY])
|
||||||
) {
|
) {
|
||||||
|
assets.audio.tinyClick.play(0.8);
|
||||||
select(col, row);
|
select(col, row);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -288,7 +289,7 @@ const handleActivateA = () => {
|
|||||||
text: $t("settings.user.color.confirmation"),
|
text: $t("settings.user.color.confirmation"),
|
||||||
onClosed: async () => {
|
onClosed: async () => {
|
||||||
await animateOutro();
|
await animateOutro();
|
||||||
store.closeSubMenu();
|
store.closeSubMenu(true);
|
||||||
},
|
},
|
||||||
keepButtonsDown: true,
|
keepButtonsDown: true,
|
||||||
timeout: 2000,
|
timeout: 2000,
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ const handleActivateB = async () => {
|
|||||||
onClosed: async (choice) => {
|
onClosed: async (choice) => {
|
||||||
if (choice === "A") {
|
if (choice === "A") {
|
||||||
await animateOutro();
|
await animateOutro();
|
||||||
store.closeSubMenu();
|
store.closeSubMenu(true);
|
||||||
} else {
|
} else {
|
||||||
state.value = "alive";
|
state.value = "alive";
|
||||||
}
|
}
|
||||||
@@ -160,6 +160,7 @@ const handleActivateA = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case "waiting": {
|
case "waiting": {
|
||||||
|
atlas.audio.menuConfirmed.play();
|
||||||
spawn();
|
spawn();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -207,6 +208,8 @@ const eat = () => {
|
|||||||
food.copy(randomFoodPos());
|
food.copy(randomFoodPos());
|
||||||
score += 1;
|
score += 1;
|
||||||
|
|
||||||
|
atlas.audio.duplicate.play(0.35);
|
||||||
|
|
||||||
if (score === 40) {
|
if (score === 40) {
|
||||||
achievements.unlock("snake_score_25");
|
achievements.unlock("snake_score_25");
|
||||||
}
|
}
|
||||||
@@ -214,6 +217,7 @@ const eat = () => {
|
|||||||
|
|
||||||
const die = () => {
|
const die = () => {
|
||||||
state.value = "dead";
|
state.value = "dead";
|
||||||
|
atlas.audio.invalid.play();
|
||||||
};
|
};
|
||||||
|
|
||||||
const spawn = () => {
|
const spawn = () => {
|
||||||
@@ -351,6 +355,7 @@ useKeyDown(({ key }) => {
|
|||||||
|
|
||||||
if (newDirection.clone().dot(direction) === 0) {
|
if (newDirection.clone().dot(direction) === 0) {
|
||||||
nextDirection.copy(newDirection);
|
nextDirection.copy(newDirection);
|
||||||
|
atlas.audio.type.play(0.35);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,64 +1,21 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const { onRender } = useScreen();
|
const { onRender } = useScreen();
|
||||||
|
const { assets } = useAssets();
|
||||||
|
|
||||||
const app = useAppStore();
|
const app = useAppStore();
|
||||||
const store = useSettingsStore();
|
const store = useSettingsStore();
|
||||||
const { assets } = useAssets();
|
const tickClock = useClockTick();
|
||||||
|
|
||||||
const CENTER_X = 63;
|
const CENTER_X = 63;
|
||||||
const CENTER_Y = 95;
|
const CENTER_Y = 95;
|
||||||
|
|
||||||
function drawLine(
|
|
||||||
ctx: CanvasRenderingContext2D,
|
|
||||||
x0: number,
|
|
||||||
y0: number,
|
|
||||||
x1: number,
|
|
||||||
y1: number,
|
|
||||||
width: number,
|
|
||||||
) {
|
|
||||||
const dx = Math.abs(x1 - x0);
|
|
||||||
const dy = Math.abs(y1 - y0);
|
|
||||||
const sx = x0 < x1 ? 1 : -1;
|
|
||||||
const sy = y0 < y1 ? 1 : -1;
|
|
||||||
let err = dx - dy;
|
|
||||||
|
|
||||||
const drawThickPixel = (x: number, y: number) => {
|
|
||||||
const isVertical = dy > dx;
|
|
||||||
|
|
||||||
if (width === 1) {
|
|
||||||
ctx.fillRect(x, y, 1, 1);
|
|
||||||
} else if (isVertical) {
|
|
||||||
const offset = Math.floor((width - 1) / 2);
|
|
||||||
ctx.fillRect(x - offset, y, width, 1);
|
|
||||||
} else {
|
|
||||||
const offset = Math.floor((width - 1) / 2);
|
|
||||||
ctx.fillRect(x, y - offset, 1, width);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
drawThickPixel(x0, y0);
|
|
||||||
|
|
||||||
if (x0 === x1 && y0 === y1) break;
|
|
||||||
|
|
||||||
const e2 = 2 * err;
|
|
||||||
if (e2 > -dy) {
|
|
||||||
err -= dy;
|
|
||||||
x0 += sx;
|
|
||||||
}
|
|
||||||
if (e2 < dx) {
|
|
||||||
err += dx;
|
|
||||||
y0 += sy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onRender((ctx) => {
|
onRender((ctx) => {
|
||||||
ctx.translate(0, -16 + store.notificationYOffset / 3);
|
ctx.translate(0, -16 + store.notificationYOffset / 3);
|
||||||
|
|
||||||
assets.images.home.topScreen.clock.draw(ctx, 13, 45);
|
assets.images.home.topScreen.clock.draw(ctx, 13, 45);
|
||||||
|
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
tickClock(now);
|
||||||
|
|
||||||
const renderHand = (
|
const renderHand = (
|
||||||
value: number,
|
value: number,
|
||||||
|
|||||||
@@ -7,14 +7,18 @@ type Rect = [number, number, number, number];
|
|||||||
let atlasImage: HTMLImageElement | null = null;
|
let atlasImage: HTMLImageElement | null = null;
|
||||||
const modelCache = new Map<string, THREE.Group>();
|
const modelCache = new Map<string, THREE.Group>();
|
||||||
|
|
||||||
const createAudio = (path: string) => ({
|
const createAudio = (path: string) => {
|
||||||
play: () => {
|
const source = import.meta.client ? new Audio(path) : null;
|
||||||
if (!import.meta.client) return;
|
return {
|
||||||
const audio = new Audio(path);
|
play: (volume = 1) => {
|
||||||
|
if (!source) return;
|
||||||
|
const audio = source.cloneNode() as HTMLAudioElement;
|
||||||
|
audio.volume = volume;
|
||||||
audio.addEventListener("ended", () => audio.remove(), { once: true });
|
audio.addEventListener("ended", () => audio.remove(), { once: true });
|
||||||
audio.play().catch(() => {});
|
audio.play().catch(() => {});
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const loaded = ref(0);
|
const loaded = ref(0);
|
||||||
const total = ref({{TOTAL}});
|
const total = ref({{TOTAL}});
|
||||||
@@ -104,6 +108,7 @@ type ModelTree = {
|
|||||||
|
|
||||||
export type AudioEntry = ReturnType<typeof createAudio>;
|
export type AudioEntry = ReturnType<typeof createAudio>;
|
||||||
|
|
||||||
|
|
||||||
type AudioTree = {
|
type AudioTree = {
|
||||||
[key: string]: AudioEntry | AudioTree;
|
[key: string]: AudioEntry | AudioTree;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ export const useButtonNavigation = <T extends Record<string, Rect>>({
|
|||||||
initialButton,
|
initialButton,
|
||||||
canClickButton,
|
canClickButton,
|
||||||
onActivate,
|
onActivate,
|
||||||
|
onNavigate,
|
||||||
navigation,
|
navigation,
|
||||||
disabled,
|
disabled,
|
||||||
selectorAnimation,
|
selectorAnimation,
|
||||||
@@ -13,6 +14,7 @@ export const useButtonNavigation = <T extends Record<string, Rect>>({
|
|||||||
initialButton: keyof T;
|
initialButton: keyof T;
|
||||||
canClickButton?: (buttonName: keyof T) => boolean;
|
canClickButton?: (buttonName: keyof T) => boolean;
|
||||||
onActivate?: (buttonName: keyof T) => void;
|
onActivate?: (buttonName: keyof T) => void;
|
||||||
|
onNavigate?: (buttonName: keyof T) => void;
|
||||||
navigation: Record<
|
navigation: Record<
|
||||||
keyof T,
|
keyof T,
|
||||||
{
|
{
|
||||||
@@ -231,6 +233,12 @@ export const useButtonNavigation = <T extends Record<string, Rect>>({
|
|||||||
if (selectedButton.value === buttonName) {
|
if (selectedButton.value === buttonName) {
|
||||||
onActivate?.(buttonName);
|
onActivate?.(buttonName);
|
||||||
} else {
|
} else {
|
||||||
|
if (onNavigate) {
|
||||||
|
onNavigate(buttonName);
|
||||||
|
} else {
|
||||||
|
const { assets } = useAssets();
|
||||||
|
assets.audio.tinyClick.play(0.8);
|
||||||
|
}
|
||||||
const path = findPath(graph, selectedButton.value, buttonName);
|
const path = findPath(graph, selectedButton.value, buttonName);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -356,6 +364,12 @@ export const useButtonNavigation = <T extends Record<string, Rect>>({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (targetButton) {
|
if (targetButton) {
|
||||||
|
if (onNavigate) {
|
||||||
|
onNavigate(targetButton);
|
||||||
|
} else {
|
||||||
|
const { assets } = useAssets();
|
||||||
|
assets.audio.tinyClick.play(0.8);
|
||||||
|
}
|
||||||
const path = findPath(graph, selectedButton.value, targetButton);
|
const path = findPath(graph, selectedButton.value, targetButton);
|
||||||
animateToButton(targetButton, path);
|
animateToButton(targetButton, path);
|
||||||
}
|
}
|
||||||
|
|||||||
12
app/composables/useClockTick.ts
Normal file
12
app/composables/useClockTick.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
let lastSecond = -1;
|
||||||
|
|
||||||
|
export const useClockTick = () => {
|
||||||
|
const { assets } = useAssets();
|
||||||
|
|
||||||
|
return (now: Date) => {
|
||||||
|
const s = now.getSeconds();
|
||||||
|
if (s === lastSecond) return;
|
||||||
|
lastSecond = s;
|
||||||
|
assets.audio.clockTick.play(s === 0 ? 1 : 0.7);
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -63,6 +63,9 @@ export const useAchievementsStore = defineStore("achievements", () => {
|
|||||||
|
|
||||||
storage.value.unlocked.push(name);
|
storage.value.unlocked.push(name);
|
||||||
|
|
||||||
|
const { assets } = useAssets();
|
||||||
|
assets.audio.messageReceived.play(0.5);
|
||||||
|
|
||||||
if (storage.value.unlocked.length === ACHIEVEMENTS.length) {
|
if (storage.value.unlocked.length === ACHIEVEMENTS.length) {
|
||||||
confetti.spawn();
|
confetti.spawn();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -137,6 +137,9 @@ export const useAchievementsScreen = defineStore("achievementsScreen", {
|
|||||||
this.isIntro = false;
|
this.isIntro = false;
|
||||||
this.isOutro = true;
|
this.isOutro = true;
|
||||||
|
|
||||||
|
const { assets } = useAssets();
|
||||||
|
assets.audio.menuConfirmed.play();
|
||||||
|
|
||||||
gsap
|
gsap
|
||||||
.timeline()
|
.timeline()
|
||||||
.fromTo(
|
.fromTo(
|
||||||
|
|||||||
@@ -45,6 +45,18 @@ export const useConfirmationModal = defineStore("confirmationModal", {
|
|||||||
this.isClosing = false;
|
this.isClosing = false;
|
||||||
this.isOpen = true;
|
this.isOpen = true;
|
||||||
|
|
||||||
|
const { assets } = useAssets();
|
||||||
|
if (
|
||||||
|
options.aLabel !== undefined ||
|
||||||
|
options.bLabel !== undefined ||
|
||||||
|
options.onActivateA !== undefined ||
|
||||||
|
options.onActivateB !== undefined
|
||||||
|
) {
|
||||||
|
assets.audio.menuOpen.play();
|
||||||
|
} else {
|
||||||
|
assets.audio.menuConfirmed.play();
|
||||||
|
}
|
||||||
|
|
||||||
gsap
|
gsap
|
||||||
.timeline()
|
.timeline()
|
||||||
// standard buttons down
|
// standard buttons down
|
||||||
@@ -83,6 +95,20 @@ export const useConfirmationModal = defineStore("confirmationModal", {
|
|||||||
|
|
||||||
this.isClosing = true;
|
this.isClosing = true;
|
||||||
|
|
||||||
|
if (
|
||||||
|
this.aLabel !== null ||
|
||||||
|
this.bLabel !== null ||
|
||||||
|
this.onActivateA !== null ||
|
||||||
|
this.onActivateB !== null
|
||||||
|
) {
|
||||||
|
const { assets } = useAssets();
|
||||||
|
if (choice === "A") {
|
||||||
|
assets.audio.menuConfirmed.play();
|
||||||
|
} else {
|
||||||
|
assets.audio.menuError.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const keepButtonsDown =
|
const keepButtonsDown =
|
||||||
typeof this.keepButtonsDown === "function"
|
typeof this.keepButtonsDown === "function"
|
||||||
? this.keepButtonsDown(choice)
|
? this.keepButtonsDown(choice)
|
||||||
|
|||||||
@@ -77,6 +77,9 @@ export const useContactStore = defineStore("contact", {
|
|||||||
pushNotification(content: string) {
|
pushNotification(content: string) {
|
||||||
this.notifications.push(content);
|
this.notifications.push(content);
|
||||||
|
|
||||||
|
const { assets } = useAssets();
|
||||||
|
assets.audio.messageSent.play();
|
||||||
|
|
||||||
gsap.fromTo(
|
gsap.fromTo(
|
||||||
this,
|
this,
|
||||||
{ notificationsYOffset: 20 },
|
{ notificationsYOffset: 20 },
|
||||||
@@ -92,6 +95,9 @@ export const useContactStore = defineStore("contact", {
|
|||||||
animateOutro() {
|
animateOutro() {
|
||||||
this.isOutro = true;
|
this.isOutro = true;
|
||||||
|
|
||||||
|
const { assets } = useAssets();
|
||||||
|
assets.audio.menuConfirmed.play();
|
||||||
|
|
||||||
const timeline = gsap.timeline({
|
const timeline = gsap.timeline({
|
||||||
onComplete: () => {
|
onComplete: () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
|||||||
@@ -113,6 +113,9 @@ export const useCreditsStore = defineStore("credits", {
|
|||||||
this.isIntro = false;
|
this.isIntro = false;
|
||||||
this.isOutro = true;
|
this.isOutro = true;
|
||||||
|
|
||||||
|
const { assets } = useAssets();
|
||||||
|
assets.audio.menuConfirmed.play();
|
||||||
|
|
||||||
gsap
|
gsap
|
||||||
.timeline()
|
.timeline()
|
||||||
.fromTo(
|
.fromTo(
|
||||||
|
|||||||
@@ -102,6 +102,9 @@ export const useHomeStore = defineStore("home", {
|
|||||||
},
|
},
|
||||||
|
|
||||||
animateOutro(to: AppScreen) {
|
animateOutro(to: AppScreen) {
|
||||||
|
const { assets } = useAssets();
|
||||||
|
assets.audio.menuOpen.play();
|
||||||
|
|
||||||
if (to === "achievements") {
|
if (to === "achievements") {
|
||||||
const achievementsScreen = useAchievementsScreen();
|
const achievementsScreen = useAchievementsScreen();
|
||||||
achievementsScreen.animateFadeToBlackIntro();
|
achievementsScreen.animateFadeToBlackIntro();
|
||||||
|
|||||||
@@ -32,6 +32,15 @@ export const useIntroStore = defineStore("intro", {
|
|||||||
this.isIntro = false;
|
this.isIntro = false;
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
.call(
|
||||||
|
() => {
|
||||||
|
const now = new Date();
|
||||||
|
const isBirthday = now.getMonth() === 3 && now.getDate() === 25;
|
||||||
|
(isBirthday ? assets.audio.birthdayStartup : assets.audio.startUp).play();
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
delay,
|
||||||
|
)
|
||||||
.to(
|
.to(
|
||||||
this.intro,
|
this.intro,
|
||||||
{
|
{
|
||||||
@@ -59,6 +68,9 @@ export const useIntroStore = defineStore("intro", {
|
|||||||
animateOutro() {
|
animateOutro() {
|
||||||
this.isOutro = true;
|
this.isOutro = true;
|
||||||
|
|
||||||
|
const { assets } = useAssets();
|
||||||
|
assets.audio.tinyClick.play(0.8);
|
||||||
|
|
||||||
gsap
|
gsap
|
||||||
.timeline()
|
.timeline()
|
||||||
.to(this.outro, {
|
.to(this.outro, {
|
||||||
|
|||||||
@@ -68,6 +68,9 @@ export const useSettingsStore = defineStore("settings", {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async openSubMenu(submenu: SettingsSubMenu) {
|
async openSubMenu(submenu: SettingsSubMenu) {
|
||||||
|
const { assets } = useAssets();
|
||||||
|
assets.audio.menuOpen.play();
|
||||||
|
|
||||||
await gsap
|
await gsap
|
||||||
.timeline()
|
.timeline()
|
||||||
.to(this.submenuTransition, {
|
.to(this.submenuTransition, {
|
||||||
@@ -99,7 +102,12 @@ export const useSettingsStore = defineStore("settings", {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async closeSubMenu() {
|
async closeSubMenu(silent = false) {
|
||||||
|
if (!silent) {
|
||||||
|
const { assets } = useAssets();
|
||||||
|
assets.audio.menuError.play();
|
||||||
|
}
|
||||||
|
|
||||||
await gsap
|
await gsap
|
||||||
.timeline()
|
.timeline()
|
||||||
.to(this, {
|
.to(this, {
|
||||||
@@ -148,6 +156,8 @@ export const useSettingsStore = defineStore("settings", {
|
|||||||
animateIntro() {
|
animateIntro() {
|
||||||
this.isIntro = true;
|
this.isIntro = true;
|
||||||
|
|
||||||
|
const { assets } = useAssets();
|
||||||
|
|
||||||
gsap
|
gsap
|
||||||
.timeline()
|
.timeline()
|
||||||
// bars
|
// bars
|
||||||
@@ -178,6 +188,7 @@ export const useSettingsStore = defineStore("settings", {
|
|||||||
{ 1: 0, duration: 0.1, ease: "none" },
|
{ 1: 0, duration: 0.1, ease: "none" },
|
||||||
0.2,
|
0.2,
|
||||||
)
|
)
|
||||||
|
.call(() => assets.audio.settingsMenuIntro.play(), undefined, 0.2)
|
||||||
.fromTo(
|
.fromTo(
|
||||||
this.menuOffsets,
|
this.menuOffsets,
|
||||||
{ 2: -48 },
|
{ 2: -48 },
|
||||||
@@ -198,8 +209,11 @@ export const useSettingsStore = defineStore("settings", {
|
|||||||
animateOutro() {
|
animateOutro() {
|
||||||
this.isOutro = true;
|
this.isOutro = true;
|
||||||
|
|
||||||
|
const { assets } = useAssets();
|
||||||
|
|
||||||
gsap
|
gsap
|
||||||
.timeline()
|
.timeline()
|
||||||
|
.call(() => assets.audio.settingsMenuOutro.play())
|
||||||
// title notification
|
// title notification
|
||||||
.fromTo(
|
.fromTo(
|
||||||
this,
|
this,
|
||||||
|
|||||||
@@ -1,3 +1,48 @@
|
|||||||
|
export const drawLine = (
|
||||||
|
ctx: CanvasRenderingContext2D,
|
||||||
|
x0: number,
|
||||||
|
y0: number,
|
||||||
|
x1: number,
|
||||||
|
y1: number,
|
||||||
|
width: number,
|
||||||
|
) => {
|
||||||
|
const dx = Math.abs(x1 - x0);
|
||||||
|
const dy = Math.abs(y1 - y0);
|
||||||
|
const sx = x0 < x1 ? 1 : -1;
|
||||||
|
const sy = y0 < y1 ? 1 : -1;
|
||||||
|
let err = dx - dy;
|
||||||
|
|
||||||
|
const drawThickPixel = (x: number, y: number) => {
|
||||||
|
const isVertical = dy > dx;
|
||||||
|
|
||||||
|
if (width === 1) {
|
||||||
|
ctx.fillRect(x, y, 1, 1);
|
||||||
|
} else if (isVertical) {
|
||||||
|
const offset = Math.floor((width - 1) / 2);
|
||||||
|
ctx.fillRect(x - offset, y, width, 1);
|
||||||
|
} else {
|
||||||
|
const offset = Math.floor((width - 1) / 2);
|
||||||
|
ctx.fillRect(x, y - offset, 1, width);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
drawThickPixel(x0, y0);
|
||||||
|
|
||||||
|
if (x0 === x1 && y0 === y1) break;
|
||||||
|
|
||||||
|
const e2 = 2 * err;
|
||||||
|
if (e2 > -dy) {
|
||||||
|
err -= dy;
|
||||||
|
x0 += sx;
|
||||||
|
}
|
||||||
|
if (e2 < dx) {
|
||||||
|
err += dx;
|
||||||
|
y0 += sy;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const fillTextCentered = (
|
export const fillTextCentered = (
|
||||||
ctx: CanvasRenderingContext2D,
|
ctx: CanvasRenderingContext2D,
|
||||||
text: string,
|
text: string,
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ export default defineNuxtModule({
|
|||||||
const rootDir = nuxt.options.rootDir;
|
const rootDir = nuxt.options.rootDir;
|
||||||
const imagesDir = join(rootDir, "app/assets/nds/images");
|
const imagesDir = join(rootDir, "app/assets/nds/images");
|
||||||
const modelsDir = join(rootDir, "public/nds/models");
|
const modelsDir = join(rootDir, "public/nds/models");
|
||||||
const audioDir = join(rootDir, "public/nds/audios");
|
const audioDir = join(rootDir, "public/nds/audio");
|
||||||
const templateFile = join(rootDir, "app/composables/useAssets.ts.in");
|
const templateFile = join(rootDir, "app/composables/useAssets.ts.in");
|
||||||
const outputFile = join(rootDir, "app/composables/useAssets.ts");
|
const outputFile = join(rootDir, "app/composables/useAssets.ts");
|
||||||
const atlasOutputPath = join(rootDir, "public/nds/atlas.webp");
|
const atlasOutputPath = join(rootDir, "public/nds/atlas.webp");
|
||||||
@@ -234,7 +234,7 @@ ${sp} }`;
|
|||||||
node = node[key] as AudioTree;
|
node = node[key] as AudioTree;
|
||||||
}
|
}
|
||||||
|
|
||||||
node[toCamelCase(fileName)] = `/nds/audios/${relative(audioDir, path)}`;
|
node[toCamelCase(fileName)] = `/nds/audio/${relative(audioDir, path)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return tree;
|
return tree;
|
||||||
|
|||||||
BIN
public/nds/audio/birthday-startup.mp3
Normal file
BIN
public/nds/audio/birthday-startup.mp3
Normal file
Binary file not shown.
BIN
public/nds/audio/clock-tick.mp3
Normal file
BIN
public/nds/audio/clock-tick.mp3
Normal file
Binary file not shown.
BIN
public/nds/audio/duplicate.mp3
Normal file
BIN
public/nds/audio/duplicate.mp3
Normal file
Binary file not shown.
BIN
public/nds/audio/eraser.mp3
Normal file
BIN
public/nds/audio/eraser.mp3
Normal file
Binary file not shown.
BIN
public/nds/audio/invalid.mp3
Normal file
BIN
public/nds/audio/invalid.mp3
Normal file
Binary file not shown.
BIN
public/nds/audio/menu-confirmed.mp3
Normal file
BIN
public/nds/audio/menu-confirmed.mp3
Normal file
Binary file not shown.
BIN
public/nds/audio/menu-error.mp3
Normal file
BIN
public/nds/audio/menu-error.mp3
Normal file
Binary file not shown.
BIN
public/nds/audio/menu-open.mp3
Normal file
BIN
public/nds/audio/menu-open.mp3
Normal file
Binary file not shown.
BIN
public/nds/audio/message-received.mp3
Normal file
BIN
public/nds/audio/message-received.mp3
Normal file
Binary file not shown.
BIN
public/nds/audio/message-sent.mp3
Normal file
BIN
public/nds/audio/message-sent.mp3
Normal file
Binary file not shown.
BIN
public/nds/audio/pkmn-button.mp3
Normal file
BIN
public/nds/audio/pkmn-button.mp3
Normal file
Binary file not shown.
BIN
public/nds/audio/pkmn-selector.mp3
Normal file
BIN
public/nds/audio/pkmn-selector.mp3
Normal file
Binary file not shown.
BIN
public/nds/audio/settings-menu-close.mp3
Normal file
BIN
public/nds/audio/settings-menu-close.mp3
Normal file
Binary file not shown.
BIN
public/nds/audio/settings-menu-intro.mp3
Normal file
BIN
public/nds/audio/settings-menu-intro.mp3
Normal file
Binary file not shown.
BIN
public/nds/audio/settings-menu-open.mp3
Normal file
BIN
public/nds/audio/settings-menu-open.mp3
Normal file
Binary file not shown.
BIN
public/nds/audio/settings-menu-outro.mp3
Normal file
BIN
public/nds/audio/settings-menu-outro.mp3
Normal file
Binary file not shown.
BIN
public/nds/audio/start-up.mp3
Normal file
BIN
public/nds/audio/start-up.mp3
Normal file
Binary file not shown.
BIN
public/nds/audio/tiny-click.mp3
Normal file
BIN
public/nds/audio/tiny-click.mp3
Normal file
Binary file not shown.
BIN
public/nds/audio/type.mp3
Normal file
BIN
public/nds/audio/type.mp3
Normal file
Binary file not shown.
Reference in New Issue
Block a user