the game
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add sfx

dunkirk.sh f681e6e8 e0161e21

verified
+270 -14
public/music/pixel-song-18.mp3

This is a binary file and will not be displayed.

public/music/pixel-song-19.mp3

This is a binary file and will not be displayed.

public/music/pixel-song-21.mp3

This is a binary file and will not be displayed.

public/music/pixel-song-3.mp3

This is a binary file and will not be displayed.

public/sfx/boom-1-bright-attack.mp3

This is a binary file and will not be displayed.

public/sfx/coin-1.wav

This is a binary file and will not be displayed.

public/sfx/coin-2.wav

This is a binary file and will not be displayed.

public/sfx/coin-3.wav

This is a binary file and will not be displayed.

public/sfx/coin-4.wav

This is a binary file and will not be displayed.

public/sfx/coin-5.wav

This is a binary file and will not be displayed.

public/sfx/large-underwater-explosion.mp3

This is a binary file and will not be displayed.

public/sfx/ouch.mp3

This is a binary file and will not be displayed.

public/sfx/pixel-explosion.mp3

This is a binary file and will not be displayed.

public/sfx/smb_powerup.wav

This is a binary file and will not be displayed.

public/sfx/windup.mp3

This is a binary file and will not be displayed.

+10
src/enemy.ts
··· 37 health -= amount; 38 console.log(`Enemy damaged: ${amount}, health: ${health}`); 39 40 // Flash red when hit 41 isHit = true; 42 this.color = k.rgb(255, 0, 0); ··· 62 63 // Enemy death 64 die(this: GameObj) { 65 // Add confetti effect only (no kaboom) 66 if (k.addConfetti) { 67 k.addConfetti(this.pos);
··· 37 health -= amount; 38 console.log(`Enemy damaged: ${amount}, health: ${health}`); 39 40 + // Play hit sound 41 + if (typeof (window as any).gameSound !== 'undefined') { 42 + (window as any).gameSound.playSfx('hit', { volume: 0.3, detune: 200 }); 43 + } 44 + 45 // Flash red when hit 46 isHit = true; 47 this.color = k.rgb(255, 0, 0); ··· 67 68 // Enemy death 69 die(this: GameObj) { 70 + // Play death sound 71 + if (typeof (window as any).gameSound !== 'undefined') { 72 + (window as any).gameSound.playSfx('death', { volume: 0.4, detune: -100 }); 73 + } 74 + 75 // Add confetti effect only (no kaboom) 76 if (k.addConfetti) { 77 k.addConfetti(this.pos);
+14
src/main.ts
··· 4 import player from "./player"; 5 import { makeEnemy } from "./enemy"; 6 import confettiPlugin from "./confetti"; 7 8 const k = kaplay({ plugins: [crew] }); 9 k.loadRoot("./"); // A good idea for Itch.io publishing later ··· 15 const confetti = confettiPlugin(k); 16 k.addConfetti = confetti.addConfetti; 17 18 // Game state 19 let gameActive = true; 20 let finalScore = 0; ··· 23 k.scene("main", () => { 24 // Reset game state 25 gameActive = true; 26 27 k.setGravity(1600); 28 ··· 123 // Only trigger once when crossing the threshold 124 if (!k.get("level-up-text").length) { 125 difficultyLevel += 1; 126 127 // Update difficulty in tracker 128 const tracker = k.get("game-score-tracker")[0];
··· 4 import player from "./player"; 5 import { makeEnemy } from "./enemy"; 6 import confettiPlugin from "./confetti"; 7 + import setupSoundSystem from "./sound"; 8 9 const k = kaplay({ plugins: [crew] }); 10 k.loadRoot("./"); // A good idea for Itch.io publishing later ··· 16 const confetti = confettiPlugin(k); 17 k.addConfetti = confetti.addConfetti; 18 19 + // Setup sound system 20 + const sound = setupSoundSystem(k); 21 + sound.preloadSounds(); 22 + 23 + // Make sound system globally available 24 + (window as any).gameSound = sound; 25 + 26 // Game state 27 let gameActive = true; 28 let finalScore = 0; ··· 31 k.scene("main", () => { 32 // Reset game state 33 gameActive = true; 34 + 35 + // Start background music 36 + sound.playRandomMusic(); 37 38 k.setGravity(1600); 39 ··· 134 // Only trigger once when crossing the threshold 135 if (!k.get("level-up-text").length) { 136 difficultyLevel += 1; 137 + 138 + // Play level up sound effect 139 + sound.playSfx("levelUp"); 140 141 // Update difficulty in tracker 142 const tracker = k.get("game-score-tracker")[0];
+67 -14
src/player.ts
··· 1 import type { KAPLAYCtx, Comp, GameObj } from "kaplay"; 2 import { Vec2 } from "kaplay"; 3 4 // Define player component type 5 interface PlayerComp extends Comp { 6 speed: number; ··· 67 return k.vec2(center.x + dx * ratio, center.y + dy * ratio); 68 }; 69 70 return { 71 id: "player", 72 require: ["body", "area", "pos"], ··· 82 83 health -= amount; 84 85 // Flash red when hit 86 isHit = true; 87 this.color = k.rgb(255, 0, 0); ··· 107 k.addKaboom(this.pos, { scale: 2 }); 108 k.shake(20); 109 110 // Emit death event for game over handling 111 this.trigger("death"); 112 } ··· 116 heal(this: GameObj, amount: number) { 117 // Add health but don't exceed max health 118 health = Math.min(health + amount, maxHealth); 119 - 120 // Flash green when healed 121 this.color = k.rgb(0, 255, 0); 122 - 123 // Reset color after a short time 124 k.wait(0.1, () => { 125 this.color = k.rgb(); 126 }); 127 - 128 // Update health bar 129 if (healthBar) { 130 const healthPercent = Math.max(0, health / maxHealth); ··· 201 this.onKeyPress(["space", "up", "w"], () => { 202 if (this.isGrounded()) { 203 this.jump(jumpForce); 204 } 205 }); 206 207 // Attack with X key - now ultimate move 208 this.onKeyPress("x", () => { 209 // Create visual effects for charging up 210 const chargeEffect = k.add([ 211 k.circle(50), ··· 220 k.tween( 221 50, 222 200, 223 - 1.5, 224 (v) => { 225 if (chargeEffect.exists()) { 226 chargeEffect.radius = v; ··· 250 }); 251 252 // After delay, trigger the ultimate explosion 253 - k.wait(2, () => { 254 if (chargeEffect.exists()) chargeEffect.destroy(); 255 if (warningText.exists()) warningText.destroy(); 256 ··· 277 ); 278 } 279 280 // Visual effects 281 k.addKaboom(this.pos, { 282 scale: 5, ··· 291 k.addKaboom(k.vec2(this.pos).add(offset), { 292 scale: 2 + Math.random() * 2, 293 }); 294 }); 295 } 296 ··· 329 // Damage all enemies with high damage 330 const enemies = k.get("enemy"); 331 let enemiesKilled = 0; 332 - 333 enemies.forEach((enemy) => { 334 const dist = k.vec2(enemy.pos).dist(this.pos); 335 if (dist < explosionRadius) { 336 // Count enemies killed 337 enemiesKilled++; 338 - 339 // Instant kill any enemy within the explosion radius 340 (enemy as any).damage(1000); // Extremely high damage to ensure death 341 ··· 344 k.addKaboom(enemy.pos, { 345 scale: 1 + Math.random(), 346 }); 347 }); 348 } 349 }); 350 - 351 // Calculate bonus score based on health and enemies killed 352 // Higher health = higher score multiplier 353 const healthPercent = health / maxHealth; 354 - const scoreBonus = Math.round(500 * healthPercent * (1 + enemiesKilled * 0.5)); 355 - 356 // Add score bonus 357 if (scoreBonus > 0) { 358 // Get score object ··· 364 const newScore = currentScore + scoreBonus; 365 // Update score display 366 scoreObj.text = `Score: ${newScore}`; 367 - 368 // Update the actual score variable in the game scene 369 // This is needed for the game over screen to show the correct score 370 const gameScores = k.get("game-score-tracker"); 371 if (gameScores.length > 0) { 372 gameScores[0].updateScore(newScore); 373 } 374 - 375 // Show bonus text 376 const bonusText = k.add([ 377 k.text(`+${scoreBonus} ULTIMATE BONUS!`, { size: 32 }), ··· 382 k.z(100), 383 k.opacity(1), 384 ]); 385 - 386 // Fade out and destroy the text 387 k.tween( 388 1, ··· 396 }, 397 k.easings.easeInQuad, 398 ); 399 - 400 k.wait(1.5, () => { 401 if (bonusText.exists()) bonusText.destroy(); 402 }); ··· 422 ); 423 424 console.log("Creating explosion at", clampedPos.x, clampedPos.y); 425 426 // Create visual explosion effect 427 k.addKaboom(clampedPos); ··· 478 if (isAttacking) return; 479 480 isAttacking = true; 481 482 if (sword) { 483 // Set sword to attacking state for collision detection
··· 1 import type { KAPLAYCtx, Comp, GameObj } from "kaplay"; 2 import { Vec2 } from "kaplay"; 3 4 + // Make sound system available to the player component 5 + declare global { 6 + interface Window { 7 + gameSound: any; 8 + } 9 + } 10 + 11 // Define player component type 12 interface PlayerComp extends Comp { 13 speed: number; ··· 74 return k.vec2(center.x + dx * ratio, center.y + dy * ratio); 75 }; 76 77 + // Helper function to play sound if available 78 + const playSound = (type: string, options = {}) => { 79 + if (window.gameSound) { 80 + window.gameSound.playSfx(type, options); 81 + } 82 + }; 83 + 84 return { 85 id: "player", 86 require: ["body", "area", "pos"], ··· 96 97 health -= amount; 98 99 + // Play hit sound 100 + playSound("hit", { volume: 0.4, detune: 300 }); 101 + 102 // Flash red when hit 103 isHit = true; 104 this.color = k.rgb(255, 0, 0); ··· 124 k.addKaboom(this.pos, { scale: 2 }); 125 k.shake(20); 126 127 + // Play death sound 128 + playSound("explosion", { volume: 1, detune: -300 }); 129 + 130 // Emit death event for game over handling 131 this.trigger("death"); 132 } ··· 136 heal(this: GameObj, amount: number) { 137 // Add health but don't exceed max health 138 health = Math.min(health + amount, maxHealth); 139 + 140 + // Play heal sound 141 + playSound("coin", { volume: 0.2, detune: 200 }); 142 + 143 // Flash green when healed 144 this.color = k.rgb(0, 255, 0); 145 + 146 // Reset color after a short time 147 k.wait(0.1, () => { 148 this.color = k.rgb(); 149 }); 150 + 151 // Update health bar 152 if (healthBar) { 153 const healthPercent = Math.max(0, health / maxHealth); ··· 224 this.onKeyPress(["space", "up", "w"], () => { 225 if (this.isGrounded()) { 226 this.jump(jumpForce); 227 + playSound("coin", { volume: 0.3, detune: 400 }); 228 } 229 }); 230 231 // Attack with X key - now ultimate move 232 this.onKeyPress("x", () => { 233 + // Play charging sound 234 + playSound("windup", { volume: 0.5, detune: 500 }); 235 + 236 // Create visual effects for charging up 237 const chargeEffect = k.add([ 238 k.circle(50), ··· 247 k.tween( 248 50, 249 200, 250 + 1.4, 251 (v) => { 252 if (chargeEffect.exists()) { 253 chargeEffect.radius = v; ··· 277 }); 278 279 // After delay, trigger the ultimate explosion 280 + k.wait(1.5, () => { 281 if (chargeEffect.exists()) chargeEffect.destroy(); 282 if (warningText.exists()) warningText.destroy(); 283 ··· 304 ); 305 } 306 307 + // Play massive explosion sound 308 + playSound("explosion", { volume: 1.0, detune: -600 }); 309 + 310 // Visual effects 311 k.addKaboom(this.pos, { 312 scale: 5, ··· 321 k.addKaboom(k.vec2(this.pos).add(offset), { 322 scale: 2 + Math.random() * 2, 323 }); 324 + 325 + // Play additional explosion sounds with slight delay 326 + playSound("explosion", { 327 + volume: 0.7, 328 + detune: -300 + Math.random() * 200, 329 + }); 330 }); 331 } 332 ··· 365 // Damage all enemies with high damage 366 const enemies = k.get("enemy"); 367 let enemiesKilled = 0; 368 + 369 enemies.forEach((enemy) => { 370 const dist = k.vec2(enemy.pos).dist(this.pos); 371 if (dist < explosionRadius) { 372 // Count enemies killed 373 enemiesKilled++; 374 + 375 // Instant kill any enemy within the explosion radius 376 (enemy as any).damage(1000); // Extremely high damage to ensure death 377 ··· 380 k.addKaboom(enemy.pos, { 381 scale: 1 + Math.random(), 382 }); 383 + 384 + // Play enemy death sound 385 + playSound("explosion", { 386 + volume: 0.5, 387 + detune: Math.random() * 400 - 200, 388 + }); 389 }); 390 } 391 }); 392 + 393 // Calculate bonus score based on health and enemies killed 394 // Higher health = higher score multiplier 395 const healthPercent = health / maxHealth; 396 + const scoreBonus = Math.round( 397 + 500 * healthPercent * (1 + enemiesKilled * 0.5), 398 + ); 399 + 400 // Add score bonus 401 if (scoreBonus > 0) { 402 // Get score object ··· 408 const newScore = currentScore + scoreBonus; 409 // Update score display 410 scoreObj.text = `Score: ${newScore}`; 411 + 412 // Update the actual score variable in the game scene 413 // This is needed for the game over screen to show the correct score 414 const gameScores = k.get("game-score-tracker"); 415 if (gameScores.length > 0) { 416 gameScores[0].updateScore(newScore); 417 } 418 + 419 + // Play bonus sound 420 + playSound("coin", { volume: 0.8 }); 421 + 422 // Show bonus text 423 const bonusText = k.add([ 424 k.text(`+${scoreBonus} ULTIMATE BONUS!`, { size: 32 }), ··· 429 k.z(100), 430 k.opacity(1), 431 ]); 432 + 433 // Fade out and destroy the text 434 k.tween( 435 1, ··· 443 }, 444 k.easings.easeInQuad, 445 ); 446 + 447 k.wait(1.5, () => { 448 if (bonusText.exists()) bonusText.destroy(); 449 }); ··· 469 ); 470 471 console.log("Creating explosion at", clampedPos.x, clampedPos.y); 472 + 473 + // Play explosion sound 474 + playSound("explosion", { volume: 0.6 }); 475 476 // Create visual explosion effect 477 k.addKaboom(clampedPos); ··· 528 if (isAttacking) return; 529 530 isAttacking = true; 531 + 532 + // Play sword swing sound 533 + playSound("explosion", { volume: 1, detune: 800 }); 534 535 if (sword) { 536 // Set sword to attacking state for collision detection
+179
src/sound.ts
···
··· 1 + import type { KAPLAYCtx } from "kaplay"; 2 + 3 + // Sound effects and music system 4 + export function setupSoundSystem(k: KAPLAYCtx) { 5 + // Available music tracks 6 + const musicTracks = [ 7 + "pixel-song-3.mp3", 8 + "pixel-song-18.mp3", 9 + "pixel-song-19.mp3", 10 + "pixel-song-21.mp3", 11 + ]; 12 + 13 + // Sound effects 14 + const soundEffects = { 15 + coin: [ 16 + "coin-1.wav", 17 + "coin-2.wav", 18 + "coin-3.wav", 19 + "coin-4.wav", 20 + "coin-5.wav", 21 + ], 22 + explosion: ["pixel-explosion.mp3"], 23 + jump: ["coin-1.wav"], 24 + hit: ["ouch.mp3"], 25 + heal: ["coin-3.wav"], 26 + death: ["large-underwater-explosion.mp3"], 27 + levelUp: ["smb_powerup.wav"], 28 + windup: ["windup.mp3"], 29 + }; 30 + 31 + // Keep track of last played music to avoid repeats 32 + let lastPlayedMusic = ""; 33 + let currentMusic: any = null; 34 + let musicVolume = 0.8; // Increased from 0.5 to 0.8 35 + let sfxVolume = 0.7; 36 + let musicEnabled = true; 37 + let sfxEnabled = true; 38 + 39 + // Preload all sounds 40 + function preloadSounds() { 41 + // Preload music 42 + musicTracks.forEach((track) => { 43 + k.loadSound(track, `music/${track}`); 44 + }); 45 + 46 + // Preload sound effects 47 + Object.values(soundEffects) 48 + .flat() 49 + .forEach((sfx) => { 50 + k.loadSound(sfx, `sfx/${sfx}`); 51 + }); 52 + } 53 + 54 + // Play a random music track (avoiding the last played one) 55 + function playRandomMusic() { 56 + if (!musicEnabled) return; 57 + 58 + // Stop current music if playing 59 + if (currentMusic) { 60 + currentMusic.stop(); 61 + } 62 + 63 + // Filter out the last played track to avoid repeats 64 + const availableTracks = musicTracks.filter( 65 + (track) => track !== lastPlayedMusic, 66 + ); 67 + 68 + // Select a random track from available tracks 69 + const randomIndex = Math.floor(Math.random() * availableTracks.length); 70 + const selectedTrack = availableTracks[randomIndex]; 71 + 72 + // Update last played track 73 + lastPlayedMusic = selectedTrack; 74 + 75 + // Play the selected track 76 + currentMusic = k.play(selectedTrack, { 77 + volume: musicVolume, 78 + loop: true, 79 + }); 80 + 81 + return currentMusic; 82 + } 83 + 84 + // Play a sound effect 85 + function playSfx(type: keyof typeof soundEffects, options: any = {}) { 86 + if (!sfxEnabled) return; 87 + 88 + const sounds = soundEffects[type]; 89 + if (!sounds || sounds.length === 0) return; 90 + 91 + // Select a random sound from the category 92 + const randomIndex = Math.floor(Math.random() * sounds.length); 93 + const selectedSound = sounds[randomIndex]; 94 + // Play the sound with options 95 + return k.play(selectedSound, { 96 + volume: options.volume ?? sfxVolume, 97 + ...options, 98 + }); 99 + } 100 + 101 + // Play a specific sound file directly 102 + function playSound(soundName: string, options: any = {}) { 103 + if (!sfxEnabled) return; 104 + 105 + return k.play(soundName, { 106 + volume: options.volume ?? sfxVolume, 107 + ...options, 108 + }); 109 + } 110 + 111 + // Toggle music on/off 112 + function toggleMusic() { 113 + musicEnabled = !musicEnabled; 114 + 115 + if (musicEnabled) { 116 + playRandomMusic(); 117 + } else if (currentMusic) { 118 + currentMusic.stop(); 119 + currentMusic = null; 120 + } 121 + 122 + return musicEnabled; 123 + } 124 + 125 + // Toggle sound effects on/off 126 + function toggleSfx() { 127 + sfxEnabled = !sfxEnabled; 128 + return sfxEnabled; 129 + } 130 + 131 + // Set music volume 132 + function setMusicVolume(volume: number) { 133 + musicVolume = Math.max(0, Math.min(1, volume)); 134 + if (currentMusic) { 135 + currentMusic.volume(musicVolume); 136 + } 137 + return musicVolume; 138 + } 139 + 140 + // Set sound effects volume 141 + function setSfxVolume(volume: number) { 142 + sfxVolume = Math.max(0, Math.min(1, volume)); 143 + return sfxVolume; 144 + } 145 + 146 + // Check if a sound is currently playing 147 + function isMusicPlaying() { 148 + return currentMusic !== null; 149 + } 150 + 151 + // Stop current music 152 + function stopMusic() { 153 + if (currentMusic) { 154 + currentMusic.stop(); 155 + currentMusic = null; 156 + } 157 + } 158 + 159 + // Get current music track name 160 + function getCurrentMusic() { 161 + return lastPlayedMusic; 162 + } 163 + 164 + return { 165 + preloadSounds, 166 + playRandomMusic, 167 + playSfx, 168 + playSound, 169 + toggleMusic, 170 + toggleSfx, 171 + setMusicVolume, 172 + setSfxVolume, 173 + isMusicPlaying, 174 + stopMusic, 175 + getCurrentMusic, 176 + }; 177 + } 178 + 179 + export default setupSoundSystem;