the game

feat: add invisible walls

dunkirk.sh 72a98fab c09b4d3e

verified
+77 -43
public/sprites/bean.png

This is a binary file and will not be displayed.

+31 -31
src/confetti.ts
··· 3 // Extend the KAPLAYCtx type to include our addConfetti method 4 declare module "kaplay" { 5 interface KAPLAYCtx { 6 - addConfetti?: (pos: { x: number, y: number }) => GameObj[]; 7 } 8 } 9 10 // Function to create a confetti effect at a position 11 - export function addConfetti(k: KAPLAYCtx, pos: { x: number, y: number }) { 12 // Number of confetti particles 13 const PARTICLE_COUNT = 50; 14 - 15 // Confetti colors 16 const COLORS = [ 17 - [255, 0, 0], // Red 18 - [0, 255, 0], // Green 19 - [0, 0, 255], // Blue 20 - [255, 255, 0], // Yellow 21 - [255, 0, 255], // Magenta 22 - [0, 255, 255], // Cyan 23 - [255, 165, 0], // Orange 24 - [128, 0, 128], // Purple 25 - ]; 26 - 27 // Create particles 28 const particles: GameObj[] = []; 29 - 30 for (let i = 0; i < PARTICLE_COUNT; i++) { 31 // Random color 32 const color = COLORS[Math.floor(Math.random() * COLORS.length)]; 33 - 34 // Random size 35 const size = Math.random() * 8 + 2; // 2-10 pixels 36 - 37 // Random shape (circle or rect) 38 const isCircle = Math.random() > 0.5; 39 - 40 // Random velocity 41 const angle = Math.random() * Math.PI * 2; 42 const speed = Math.random() * 400 + 100; // 100-500 pixels per second 43 const vx = Math.cos(angle) * speed; 44 const vy = Math.sin(angle) * speed - 200; // Initial upward boost 45 - 46 // Random rotation speed 47 const rotSpeed = Math.random() * 10 - 5; // -5 to 5 radians per second 48 - 49 // Create particle 50 const particle = k.add([ 51 isCircle ? k.circle(size / 2) : k.rect(size, size / 2), 52 k.pos(pos.x, pos.y), 53 - k.color(...color), 54 k.anchor("center"), 55 k.rotate(Math.random() * 360), // Random initial rotation 56 k.opacity(1), ··· 65 fadeStart: 0.7, // When to start fading (0.7 = 70% of lifespan) 66 }, 67 ]); 68 - 69 // Update function for the particle 70 particle.onUpdate(() => { 71 // Update position based on velocity 72 particle.pos.x += particle.vx * k.dt(); 73 particle.pos.y += particle.vy * k.dt(); 74 - 75 // Apply gravity 76 particle.vy += particle.gravity * k.dt(); 77 - 78 // Apply rotation 79 particle.angle += particle.rotSpeed * k.dt() * 60; 80 - 81 // Update lifespan 82 particle.lifespan -= k.dt(); 83 - 84 // Fade out 85 if (particle.lifespan < particle.fadeStart) { 86 particle.opacity = Math.max(0, particle.lifespan / particle.fadeStart); 87 } 88 - 89 // Destroy when lifespan is over 90 if (particle.lifespan <= 0) { 91 particle.destroy(); 92 } 93 }); 94 - 95 particles.push(particle); 96 } 97 - 98 return particles; 99 } 100 ··· 102 export function confettiPlugin(k: KAPLAYCtx) { 103 return { 104 // Add the confetti function to the game context 105 - addConfetti(pos: { x: number, y: number }) { 106 return addConfetti(k, pos); 107 - } 108 }; 109 } 110 111 - export default confettiPlugin;
··· 3 // Extend the KAPLAYCtx type to include our addConfetti method 4 declare module "kaplay" { 5 interface KAPLAYCtx { 6 + addConfetti?: (pos: { x: number; y: number }) => GameObj[]; 7 } 8 } 9 10 // Function to create a confetti effect at a position 11 + export function addConfetti(k: KAPLAYCtx, pos: { x: number; y: number }) { 12 // Number of confetti particles 13 const PARTICLE_COUNT = 50; 14 + 15 // Confetti colors 16 const COLORS = [ 17 + [255, 0, 0], // Red 18 + [0, 255, 0], // Green 19 + [0, 0, 255], // Blue 20 + [255, 255, 0], // Yellow 21 + [255, 0, 255], // Magenta 22 + [0, 255, 255], // Cyan 23 + [255, 165, 0], // Orange 24 + [128, 0, 128], // Purple 25 + ] as const; 26 + 27 // Create particles 28 const particles: GameObj[] = []; 29 + 30 for (let i = 0; i < PARTICLE_COUNT; i++) { 31 // Random color 32 const color = COLORS[Math.floor(Math.random() * COLORS.length)]; 33 + 34 // Random size 35 const size = Math.random() * 8 + 2; // 2-10 pixels 36 + 37 // Random shape (circle or rect) 38 const isCircle = Math.random() > 0.5; 39 + 40 // Random velocity 41 const angle = Math.random() * Math.PI * 2; 42 const speed = Math.random() * 400 + 100; // 100-500 pixels per second 43 const vx = Math.cos(angle) * speed; 44 const vy = Math.sin(angle) * speed - 200; // Initial upward boost 45 + 46 // Random rotation speed 47 const rotSpeed = Math.random() * 10 - 5; // -5 to 5 radians per second 48 + 49 // Create particle 50 const particle = k.add([ 51 isCircle ? k.circle(size / 2) : k.rect(size, size / 2), 52 k.pos(pos.x, pos.y), 53 + k.color(color[0], color[1], color[2]), 54 k.anchor("center"), 55 k.rotate(Math.random() * 360), // Random initial rotation 56 k.opacity(1), ··· 65 fadeStart: 0.7, // When to start fading (0.7 = 70% of lifespan) 66 }, 67 ]); 68 + 69 // Update function for the particle 70 particle.onUpdate(() => { 71 // Update position based on velocity 72 particle.pos.x += particle.vx * k.dt(); 73 particle.pos.y += particle.vy * k.dt(); 74 + 75 // Apply gravity 76 particle.vy += particle.gravity * k.dt(); 77 + 78 // Apply rotation 79 particle.angle += particle.rotSpeed * k.dt() * 60; 80 + 81 // Update lifespan 82 particle.lifespan -= k.dt(); 83 + 84 // Fade out 85 if (particle.lifespan < particle.fadeStart) { 86 particle.opacity = Math.max(0, particle.lifespan / particle.fadeStart); 87 } 88 + 89 // Destroy when lifespan is over 90 if (particle.lifespan <= 0) { 91 particle.destroy(); 92 } 93 }); 94 + 95 particles.push(particle); 96 } 97 + 98 return particles; 99 } 100 ··· 102 export function confettiPlugin(k: KAPLAYCtx) { 103 return { 104 // Add the confetti function to the game context 105 + addConfetti(pos: { x: number; y: number }) { 106 return addConfetti(k, pos); 107 + }, 108 }; 109 } 110 111 + export default confettiPlugin;
+46 -12
src/main.ts
··· 2 import kaplay from "kaplay"; 3 4 import player from "./player"; 5 - import { makeEnemy, type EnemyComp } from "./enemy"; 6 - import confettiPlugin, { addConfetti } from "./confetti"; 7 8 const k = kaplay({ plugins: [crew] }); 9 k.loadRoot("./"); // A good idea for Itch.io publishing later ··· 27 k.color(127, 200, 255), 28 ]); 29 30 // Create player object with components 31 const playerObj = k.add([ 32 k.pos(120, 500), ··· 54 gameTime += 1; // Increment game time by 1 second 55 56 // Every 30 seconds, increase difficulty 57 - if (score != 0 && score % (50 * difficultyLevel) === 0) { 58 difficultyLevel += 1; 59 60 // Increase max enemies (cap at 15) ··· 120 121 switch (side) { 122 case 0: // top 123 - x = Math.random() * k.width(); 124 - y = -50; 125 break; 126 case 1: // right 127 - x = k.width() + 50; 128 - y = Math.random() * k.height(); 129 break; 130 case 2: // bottom 131 - x = Math.random() * k.width(); 132 - y = k.height() + 50; 133 break; 134 case 3: // left 135 - x = -50; 136 - y = Math.random() * k.height(); 137 break; 138 } 139 ··· 146 enemies = enemies.filter((e) => e !== newEnemy); 147 148 // Increase score when enemy is destroyed 149 - score += 10 + Math.pow(difficultyLevel, 0.75); 150 151 // Update score display 152 scoreText.text = `Score: ${score}`;
··· 2 import kaplay from "kaplay"; 3 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 ··· 27 k.color(127, 200, 255), 28 ]); 29 30 + // Create walls around the edge of the map 31 + // Left wall 32 + const leftWall = k.add([ 33 + k.rect(20, k.height()), 34 + k.pos(-20, 0), 35 + k.outline(4), 36 + k.area(), 37 + k.body({ isStatic: true }), 38 + k.color(127, 200, 255), 39 + k.opacity(0.5), 40 + ]); 41 + 42 + // Right wall 43 + const rightWall = k.add([ 44 + k.rect(20, k.height()), 45 + k.pos(k.width(), 0), 46 + k.outline(4), 47 + k.area(), 48 + k.body({ isStatic: true }), 49 + k.color(127, 200, 255), 50 + k.opacity(0.5), 51 + ]); 52 + 53 + // Top wall 54 + const topWall = k.add([ 55 + k.rect(k.width(), 20), 56 + k.pos(0, -20), 57 + k.outline(4), 58 + k.area(), 59 + k.body({ isStatic: true }), 60 + k.color(127, 200, 255), 61 + k.opacity(0.5), 62 + ]); 63 + 64 // Create player object with components 65 const playerObj = k.add([ 66 k.pos(120, 500), ··· 88 gameTime += 1; // Increment game time by 1 second 89 90 // Every 30 seconds, increase difficulty 91 + if (score != 0 && score % (50 + 5 * difficultyLevel) === 0) { 92 difficultyLevel += 1; 93 94 // Increase max enemies (cap at 15) ··· 154 155 switch (side) { 156 case 0: // top 157 + x = Math.random() * (k.width() - 40) + 20; // Avoid spawning behind side walls 158 + y = 10; // Just inside the top wall 159 break; 160 case 1: // right 161 + x = k.width() - 10; // Just inside the right wall 162 + y = Math.random() * (k.height() - 48 - 20) + 20; // Avoid spawning behind top wall or inside ground 163 break; 164 case 2: // bottom 165 + x = Math.random() * (k.width() - 40) + 20; // Avoid spawning behind side walls 166 + y = k.height() - 58; // Just above the ground (ground is at height-48 with height 48) 167 break; 168 case 3: // left 169 + x = 10; // Just inside the left wall 170 + y = Math.random() * (k.height() - 48 - 20) + 20; // Avoid spawning behind top wall or inside ground 171 break; 172 } 173 ··· 180 enemies = enemies.filter((e) => e !== newEnemy); 181 182 // Increase score when enemy is destroyed 183 + score += Math.round(10 + Math.pow(difficultyLevel, 0.75)); 184 185 // Update score display 186 scoreText.text = `Score: ${score}`;