feat(nds): add audio in all menus

This commit is contained in:
2026-02-25 14:52:48 +01:00
parent 4af6de5329
commit 858082e151
45 changed files with 240 additions and 117 deletions

View File

@@ -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,

View File

@@ -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,

View File

@@ -53,13 +53,20 @@ useKeyDown(({ key }) => {
switch (key) { switch (key) {
case "NDS_UP": case "NDS_UP":
selectedOption = "yes"; if (selectedOption !== "yes") {
selectedOption = "yes";
assets.audio.pkmnSelector.play();
}
break; break;
case "NDS_DOWN": case "NDS_DOWN":
selectedOption = "no"; if (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);
} }

View File

@@ -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();
}; };

View File

@@ -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();

View File

@@ -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;
} }

View File

@@ -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,

View File

@@ -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,

View File

@@ -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;
} }

View File

@@ -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,

View File

@@ -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,

View File

@@ -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);
} }
}); });

View File

@@ -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,

View File

@@ -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;
}; };

View File

@@ -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);
} }

View 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);
};
};

View File

@@ -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 {

View File

@@ -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(

View File

@@ -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)

View File

@@ -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(() => {

View File

@@ -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(

View File

@@ -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();

View File

@@ -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, {

View File

@@ -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,

View File

@@ -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,

View File

@@ -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;

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
public/nds/audio/eraser.mp3 Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
public/nds/audio/type.mp3 Normal file

Binary file not shown.