Skip to content

Commit 2cd7c73

Browse files
EXE901r4pt0s
andauthored
feat: Add Main Menu, Character Selection, and Pause Menu (#262)
Co-authored-by: Wolfgang Kreminger <r4pt0s@protonmail.com> Fixes #260 Fixes #6
1 parent 31c963b commit 2cd7c73

12 files changed

Lines changed: 1535 additions & 110 deletions

File tree

index.html

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,272 @@
232232
<div class="menu-bar"></div>
233233
</div>
234234

235+
<!-- Main Menu Overlay -->
236+
<div id="main-menu" class="main-menu-overlay">
237+
<div class="main-menu-container">
238+
<div class="main-menu-content">
239+
<!-- Logo/Title -->
240+
<div class="main-menu-title">
241+
<h1>ZTM QUEST</h1>
242+
<p class="main-menu-subtitle">Adventure Awaits</p>
243+
</div>
244+
245+
<!-- Menu Buttons -->
246+
<div class="main-menu-buttons">
247+
<button id="start-new-game-btn" class="main-menu-btn primary">
248+
<span>New Game</span>
249+
</button>
250+
251+
<button id="continue-game-btn" class="main-menu-btn secondary" style="display: none;">
252+
<span>Continue</span>
253+
</button>
254+
255+
<button id="main-menu-achievements-btn" class="main-menu-btn secondary">
256+
<span>Achievements</span>
257+
</button>
258+
259+
<button id="main-menu-stats-btn" class="main-menu-btn secondary">
260+
<span>Stats</span>
261+
</button>
262+
263+
<button id="main-menu-settings-btn" class="main-menu-btn secondary">
264+
<span>Settings</span>
265+
</button>
266+
267+
<button id="main-menu-credits-btn" class="main-menu-btn secondary">
268+
<span>Credits</span>
269+
</button>
270+
</div>
271+
272+
<!-- Version -->
273+
<div class="main-menu-footer">
274+
<p>Press ENTER to start</p>
275+
</div>
276+
</div>
277+
</div>
278+
</div>
279+
280+
<!-- In-Game Pause Menu -->
281+
<div id="pause-menu" class="pause-menu-overlay" style="display: none;">
282+
<div class="pause-menu-container">
283+
<div class="pause-menu-content">
284+
<h2 class="pause-menu-title">PAUSED</h2>
285+
286+
<div class="pause-menu-buttons">
287+
<button id="resume-game-btn" class="pause-menu-btn primary">
288+
<span>Resume</span>
289+
</button>
290+
291+
<div class="pause-menu-settings">
292+
<div class="setting-row">
293+
<label>Music</label>
294+
<button id="toggle-sound-btn" class="toggle-btn">
295+
<span class="toggle-indicator"></span>
296+
</button>
297+
</div>
298+
</div>
299+
300+
<button id="pause-main-menu-btn" class="pause-menu-btn secondary">
301+
<span>Main Menu</span>
302+
</button>
303+
</div>
304+
305+
<div class="pause-menu-footer">
306+
<p>Press ESC to resume</p>
307+
</div>
308+
</div>
309+
</div>
310+
</div>
311+
312+
<!-- Settings Modal -->
313+
<div id="settings-modal" class="modal-overlay" style="display: none;">
314+
<div class="modal-container settings-modal">
315+
<div class="modal-header">
316+
<h2>⚙️ Settings</h2>
317+
<button class="close-modal-btn" data-modal="settings-modal">×</button>
318+
</div>
319+
320+
<div class="modal-content">
321+
322+
<!-- Music Volume -->
323+
<div class="setting-group">
324+
<label for="music-volume">Music</label>
325+
<div class="volume-control">
326+
<input type="range" id="music-volume" min="0" max="100" value="100" />
327+
<span id="music-volume-value">100%</span>
328+
</div>
329+
</div>
330+
331+
<!-- Fullscreen Toggle -->
332+
<div class="setting-group">
333+
<label>Fullscreen</label>
334+
<button id="toggle-fullscreen-btn" class="toggle-btn-modal">
335+
<span class="toggle-indicator-modal"></span>
336+
</button>
337+
</div>
338+
339+
<!-- Controls Guide -->
340+
<div class="setting-group">
341+
<button id="show-controls-btn" class="settings-action-btn">
342+
View Controls
343+
</button>
344+
</div>
345+
346+
<!-- Reset Save Data -->
347+
<div class="setting-group">
348+
<button id="reset-save-btn" class="settings-action-btn danger">
349+
Reset Game Progress
350+
</button>
351+
</div>
352+
353+
</div>
354+
355+
<div class="modal-footer">
356+
<button id="save-settings-btn" class="modal-save-btn">Save & Close</button>
357+
</div>
358+
</div>
359+
</div>
360+
361+
<!-- Achievements Modal -->
362+
<div id="achievements-modal" class="modal-overlay" style="display: none;">
363+
<div class="modal-container">
364+
<div class="modal-header">
365+
<h2>🏆 Achievements</h2>
366+
<button class="close-modal-btn" data-modal="achievements-modal">×</button>
367+
</div>
368+
369+
<div class="modal-content achievements-grid" id="achievements-list">
370+
<div class="no-achievements">
371+
<p>No achievements available yet!</p>
372+
<p class="subtext">Check back later for new achievements</p>
373+
</div>
374+
</div>
375+
</div>
376+
</div>
377+
378+
<!-- Stats Modal -->
379+
<div id="stats-modal" class="modal-overlay" style="display: none;">
380+
<div class="modal-container">
381+
<div class="modal-header">
382+
<h2>📊 Player Stats</h2>
383+
<button class="close-modal-btn" data-modal="stats-modal">×</button>
384+
</div>
385+
386+
<div class="modal-content stats-content">
387+
<div class="stat-card">
388+
<div class="stat-icon">⏱️</div>
389+
<div class="stat-info">
390+
<div class="stat-label">Time Played</div>
391+
<div class="stat-value" id="stat-time">00:00</div>
392+
</div>
393+
</div>
394+
395+
<div class="stat-card">
396+
<div class="stat-icon">🪙</div>
397+
<div class="stat-info">
398+
<div class="stat-label">Coins Collected</div>
399+
<div class="stat-value" id="stat-coins">0</div>
400+
</div>
401+
</div>
402+
403+
<div class="stat-card">
404+
<div class="stat-icon"></div>
405+
<div class="stat-info">
406+
<div class="stat-label">Current Energy</div>
407+
<div class="stat-value" id="stat-energy">100</div>
408+
</div>
409+
</div>
410+
411+
<div class="stat-card">
412+
<div class="stat-icon">🎯</div>
413+
<div class="stat-info">
414+
<div class="stat-label">Quests Completed</div>
415+
<div class="stat-value" id="stat-quests">0</div>
416+
</div>
417+
</div>
418+
419+
<div class="stat-card">
420+
<div class="stat-icon">🗺️</div>
421+
<div class="stat-info">
422+
<div class="stat-label">Maps Explored</div>
423+
<div class="stat-value" id="stat-maps">1</div>
424+
</div>
425+
</div>
426+
427+
<div class="stat-card">
428+
<div class="stat-icon">🏆</div>
429+
<div class="stat-info">
430+
<div class="stat-label">Achievements</div>
431+
<div class="stat-value" id="stat-achievements">0/0</div>
432+
</div>
433+
</div>
434+
</div>
435+
</div>
436+
</div>
437+
438+
<!-- Credits Modal -->
439+
<div id="credits-modal" class="modal-overlay" style="display: none;">
440+
<div class="modal-container">
441+
<div class="modal-header">
442+
<h2>✨ Credits</h2>
443+
<button class="close-modal-btn" data-modal="credits-modal">×</button>
444+
</div>
445+
446+
<div class="modal-content credits-content">
447+
<div class="credits-section">
448+
<h3>Zero To Mastery Community</h3>
449+
<p>A collaborative project by ZTM students and contributors</p>
450+
</div>
451+
452+
<div class="credits-section">
453+
<h3>Game Development</h3>
454+
<p>Built with Kaplay (Kaboom.js)</p>
455+
<p>Pixel art graphics and sound design</p>
456+
</div>
457+
458+
<div class="credits-section">
459+
<h3>Special Thanks</h3>
460+
<p>All open-source contributors</p>
461+
<p>Zero To Mastery Academy</p>
462+
<p>Hacktoberfest participants</p>
463+
</div>
464+
465+
<div class="credits-section">
466+
<a href="https://github.com/zero-to-mastery/ZTM-Quest" target="_blank" class="credits-link">
467+
View Full Credits on GitHub →
468+
</a>
469+
</div>
470+
</div>
471+
</div>
472+
</div>
473+
474+
<!-- Controls Modal Content Template -->
475+
<div id="controls-template" style="display: none;">
476+
<div class="controls-message">
477+
<h3>Controls</h3>
478+
<ul>
479+
<li>W/↑ - Move Up</li>
480+
<li>A/← - Move Left</li>
481+
<li>S/↓ - Move Down</li>
482+
<li>D/→ - Move Right</li>
483+
<li>ENTER - Interact</li>
484+
<li>ESC - Pause Menu</li>
485+
<li>M - Toggle Map</li>
486+
</ul>
487+
<button id="controls-close-btn">OK</button>
488+
</div>
489+
</div>
490+
491+
<!-- Reset Confirmation Template -->
492+
<div id="reset-confirm-template" style="display: none;">
493+
<div>
494+
<p>Are you sure you want to reset all game progress? This cannot be undone!</p>
495+
<button id="reset-cancel-btn">Cancel</button>
496+
<button id="reset-confirm-btn" class="danger">Reset</button>
497+
</div>
498+
</div>
499+
500+
235501
<div id="header" class="top">ZTM Quest</div>
236502
<div id="left-panel" class="left">
237503
<div>

src/main.js

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ import { orangeHouse } from './scenes/orange_house';
2626
import { redHouse } from './scenes/red_house';
2727
import { companyInterior } from './scenes/company_interior';
2828
import { loadingScreen } from './scenes/loadingScreen';
29+
import { mainMenuScene } from './scenes/mainMenuScene';
30+
import { setupPauseMenu } from './utils/pauseMenu';
31+
import { setupMenuModals } from './utils/menuModals';
2932

3033
import { Backpack } from './backpack';
3134

@@ -58,13 +61,16 @@ k.scene('company_interior', (enter_tag) =>
5861
k.scene('startScreen', gameStartScreen);
5962
k.scene('lose', loseScreen);
6063

61-
// Wait for assets to load, THEN show 30-second loading screen
62-
k.onLoad(() => {
63-
// Start with loading screen scene
64-
k.go('loadingScreen');
64+
// Initialize menu modals (settings, achievements, stats, credits)
65+
setupMenuModals();
66+
67+
// Load saved game state from localStorage (if available)
6568

66-
// After loading screen finishes (30s), it will automatically go to 'start'
67-
// The loadingScreen scene handles the transition
69+
// Wait for assets to load, THEN show main menu
70+
// In main.js
71+
k.onLoad(() => {
72+
setupPauseMenu();
73+
mainMenuScene();
6874
});
6975

7076
// To test different maps instead of going through each and every scene to get to yours,
@@ -83,13 +89,15 @@ k.onLoad(() => {
8389

8490
updateEnergyUI(getGameState().player.energy);
8591
updateCoinsUI();
92+
8693
setInterval(() => {
87-
const gameState = getGameState();
94+
const gameState = getGameState(); // This should be inside setInterval so that gameState variable is updated at every interval.
8895
if (gameState.player.energy) {
8996
gameState.player.energy -= 1;
9097
setGameState(gameState);
9198
updateEnergyUI(gameState.player.energy);
9299
} else if (Math.floor(k.time()) % 3 == 0) {
100+
// This ensures log appears atmost 2 times per minute.
93101
k.debug.log('I need some energy.');
94102
}
95103
}, 10000);

src/scenes/bootstrap.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import { addPlayerControls } from './../player.controls';
88
import { resetPausingVariables } from '../utils/resetPausingVariables';
99
import { Backpack } from '../backpack';
1010

11+
// Define handler outside function so we can remove it
12+
let pauseHandler = null;
13+
1114
export async function bootstrap(bootMapCb, mapArgs) {
1215
const gameState = getGameState();
1316
const player = makePlayer(gameState.player);
@@ -35,4 +38,26 @@ export async function bootstrap(bootMapCb, mapArgs) {
3538
addSceneSounds(sounds, k, map);
3639

3740
Backpack.backpackInteractions();
41+
42+
// Remove old pause handler if exists
43+
if (pauseHandler) {
44+
document.removeEventListener('keydown', pauseHandler);
45+
}
46+
47+
// Create new pause handler
48+
pauseHandler = (e) => {
49+
if (e.key === 'Escape') {
50+
const pauseMenu = document.getElementById('pause-menu');
51+
if (pauseMenu) {
52+
if (pauseMenu.style.display === 'none' || pauseMenu.style.display === '') {
53+
pauseMenu.style.display = 'flex';
54+
} else {
55+
pauseMenu.style.display = 'none';
56+
}
57+
}
58+
}
59+
};
60+
61+
// Add new pause handler
62+
document.addEventListener('keydown', pauseHandler);
3863
}

0 commit comments

Comments
 (0)