feat: add ultimate ability and game over

dunkirk.sh 2cca584c 72a98fab

verified
Changed files
+388 -158
src
+237 -150
src/main.ts
···
const confetti = confettiPlugin(k);
k.addConfetti = confetti.addConfetti;
-
k.setGravity(1600);
+
// Game state
+
let gameActive = true;
+
let finalScore = 0;
-
// Create ground
-
const ground = k.add([
-
k.rect(k.width(), 48),
-
k.pos(0, k.height() - 48),
-
k.outline(4),
-
k.area(),
-
k.body({ isStatic: true }),
-
k.color(127, 200, 255),
-
]);
+
// Define game scenes
+
k.scene("main", () => {
+
// Reset game state
+
gameActive = true;
-
// Create walls around the edge of the map
-
// Left wall
-
const leftWall = k.add([
-
k.rect(20, k.height()),
-
k.pos(-20, 0),
-
k.outline(4),
-
k.area(),
-
k.body({ isStatic: true }),
-
k.color(127, 200, 255),
-
k.opacity(0.5),
-
]);
+
k.setGravity(1600);
-
// Right wall
-
const rightWall = k.add([
-
k.rect(20, k.height()),
-
k.pos(k.width(), 0),
-
k.outline(4),
-
k.area(),
-
k.body({ isStatic: true }),
-
k.color(127, 200, 255),
-
k.opacity(0.5),
-
]);
+
// Create ground
+
const ground = k.add([
+
k.rect(k.width(), 48),
+
k.pos(0, k.height() - 48),
+
k.outline(4),
+
k.area(),
+
k.body({ isStatic: true }),
+
k.color(127, 200, 255),
+
]);
-
// Top wall
-
const topWall = k.add([
-
k.rect(k.width(), 20),
-
k.pos(0, -20),
-
k.outline(4),
-
k.area(),
-
k.body({ isStatic: true }),
-
k.color(127, 200, 255),
-
k.opacity(0.5),
-
]);
+
// Create walls around the edge of the map
+
// Left wall
+
const leftWall = k.add([
+
k.rect(20, k.height()),
+
k.pos(-20, 0),
+
k.outline(4),
+
k.area(),
+
k.body({ isStatic: true }),
+
k.color(127, 200, 255),
+
k.opacity(0.5),
+
]);
-
// Create player object with components
-
const playerObj = k.add([
-
k.pos(120, 500),
-
k.sprite("glady-o"),
-
k.body(),
-
k.area(),
-
player(k),
-
"player", // Add tag for collision detection
-
]);
+
// Right wall
+
const rightWall = k.add([
+
k.rect(20, k.height()),
+
k.pos(k.width(), 0),
+
k.outline(4),
+
k.area(),
+
k.body({ isStatic: true }),
+
k.color(127, 200, 255),
+
k.opacity(0.5),
+
]);
-
// Enemy spawning variables
-
let enemies: any[] = [];
-
let initialMaxEnemies = 5;
-
let maxEnemies = initialMaxEnemies;
-
let initialSpawnInterval = 3; // seconds
-
let spawnInterval = initialSpawnInterval;
-
let gameTime = 0; // Track game time in seconds
-
let difficultyLevel = 1;
-
let score = 0;
+
// Top wall
+
const topWall = k.add([
+
k.rect(k.width(), 20),
+
k.pos(0, -20),
+
k.outline(4),
+
k.area(),
+
k.body({ isStatic: true }),
+
k.color(127, 200, 255),
+
k.opacity(0.5),
+
]);
-
const scoreText = k.add([k.text(`Score: ${score}`), k.pos(16, 16)]);
+
// Create player object with components
+
const playerObj = k.add([
+
k.pos(120, 500),
+
k.sprite("glady-o"),
+
k.body(),
+
k.area(),
+
player(k),
+
"player", // Add tag for collision detection
+
]);
+
+
// Enemy spawning variables
+
let enemies: any[] = [];
+
let initialMaxEnemies = 5;
+
let maxEnemies = initialMaxEnemies;
+
let initialSpawnInterval = 3; // seconds
+
let spawnInterval = initialSpawnInterval;
+
let gameTime = 0; // Track game time in seconds
+
let difficultyLevel = 1;
+
let score = 0;
+
+
const scoreText = k.add([k.text(`Score: ${score}`), k.pos(16, 16), "score"]);
+
+
// Difficulty scaling
+
function updateDifficulty() {
+
if (!gameActive) return;
+
+
gameTime += 1; // Increment game time by 1 second
+
+
// Every 30 seconds, increase difficulty
+
if (score != 0 && score % (50 + 5 * difficultyLevel) === 0) {
+
difficultyLevel += 1;
+
+
// Increase max enemies (cap at 15)
+
maxEnemies = Math.min(initialMaxEnemies + difficultyLevel * 3, 15);
+
+
// Decrease spawn interval (minimum 0.5 seconds)
+
spawnInterval = Math.max(
+
initialSpawnInterval - difficultyLevel * 0.3,
+
0.5,
+
);
-
// Difficulty scaling
-
function updateDifficulty() {
-
gameTime += 1; // Increment game time by 1 second
+
console.log(
+
`Difficulty increased to level ${difficultyLevel}. Max enemies: ${maxEnemies}, Spawn interval: ${spawnInterval}s`,
+
);
-
// Every 30 seconds, increase difficulty
-
if (score != 0 && score % (50 + 5 * difficultyLevel) === 0) {
-
difficultyLevel += 1;
+
// Cancel previous spawn loop and start a new one with updated interval
+
k.cancel();
+
k.loop(spawnInterval, spawnEnemy);
+
+
// Visual feedback for difficulty increase
+
const screenCenter = k.vec2(k.width() / 2, k.height() / 2);
+
if (k.addConfetti) {
+
k.addConfetti(screenCenter);
+
}
+
+
// Add difficulty level text
+
const levelText = k.add([
+
k.text(`Difficulty Level ${difficultyLevel}!`, { size: 32 }),
+
k.pos(screenCenter),
+
k.anchor("center"),
+
k.color(255, 255, 255),
+
k.outline(2, k.rgb(0, 0, 0)),
+
k.z(100),
+
k.opacity(1),
+
]);
+
+
// Fade out and destroy the text
+
k.tween(
+
1,
+
0,
+
2,
+
(v) => {
+
levelText.opacity = v;
+
},
+
k.easings.easeInQuad,
+
);
+
+
k.wait(2, () => {
+
levelText.destroy();
+
});
+
}
+
}
-
// Increase max enemies (cap at 15)
-
maxEnemies = Math.min(initialMaxEnemies + difficultyLevel * 3, 15);
+
// Start difficulty scaling
+
k.loop(1, updateDifficulty);
-
// Decrease spawn interval (minimum 0.5 seconds)
-
spawnInterval = Math.max(initialSpawnInterval - difficultyLevel * 0.3, 0.5);
+
// Spawn an enemy at a random position
+
function spawnEnemy() {
+
if (!gameActive) return;
-
console.log(
-
`Difficulty increased to level ${difficultyLevel}. Max enemies: ${maxEnemies}, Spawn interval: ${spawnInterval}s`,
-
);
+
// Don't spawn if we already have max enemies
+
if (enemies.length >= maxEnemies) return;
-
// Cancel previous spawn loop and start a new one with updated interval
-
k.cancel();
-
k.loop(spawnInterval, spawnEnemy);
+
// Random position at the edges of the screen
+
const side = Math.floor(Math.random() * 4); // 0: top, 1: right, 2: bottom, 3: left
+
let x = 0,
+
y = 0;
-
// Visual feedback for difficulty increase
-
const screenCenter = k.vec2(k.width() / 2, k.height() / 2);
-
if (k.addConfetti) {
-
k.addConfetti(screenCenter);
+
switch (side) {
+
case 0: // top
+
x = Math.random() * (k.width() - 40) + 20; // Avoid spawning behind side walls
+
y = 10; // Just inside the top wall
+
break;
+
case 1: // right
+
x = k.width() - 10; // Just inside the right wall
+
y = Math.random() * (k.height() - 48 - 20) + 20; // Avoid spawning behind top wall or inside ground
+
break;
+
case 2: // bottom
+
x = Math.random() * (k.width() - 40) + 20; // Avoid spawning behind side walls
+
y = k.height() - 58; // Just above the ground (ground is at height-48 with height 48)
+
break;
+
case 3: // left
+
x = 10; // Just inside the left wall
+
y = Math.random() * (k.height() - 48 - 20) + 20; // Avoid spawning behind top wall or inside ground
+
break;
}
-
// Add difficulty level text
-
const levelText = k.add([
-
k.text(`Difficulty Level ${difficultyLevel}!`, { size: 32 }),
-
k.pos(screenCenter),
-
k.anchor("center"),
-
k.color(255, 255, 255),
-
k.outline(2, k.rgb(0, 0, 0)),
-
k.z(100),
-
k.opacity(1),
-
]);
+
// Create enemy using the makeEnemy function
+
const newEnemy = makeEnemy(k, playerObj, x, y);
+
enemies.push(newEnemy);
+
+
// Remove from array when destroyed
+
newEnemy.on("destroy", () => {
+
enemies = enemies.filter((e) => e !== newEnemy);
-
// Fade out and destroy the text
-
k.tween(
-
1,
-
0,
-
2,
-
(v) => {
-
levelText.opacity = v;
-
},
-
k.easings.easeInQuad,
-
);
+
// Increase score when enemy is destroyed
+
score += Math.round(10 + Math.pow(difficultyLevel, 0.75));
-
k.wait(2, () => {
-
levelText.destroy();
+
// Update score display
+
scoreText.text = `Score: ${score}`;
+
+
if (Math.random() < 0.5) spawnEnemy();
});
}
-
}
-
// Start difficulty scaling
-
k.loop(1, updateDifficulty);
+
// Start spawning enemies
+
k.loop(spawnInterval, spawnEnemy);
-
// Spawn an enemy at a random position
-
function spawnEnemy() {
-
// Don't spawn if we already have max enemies
-
if (enemies.length >= maxEnemies) return;
+
// Game loop
+
k.onUpdate(() => {
+
// Update enemy list (remove destroyed enemies)
+
enemies = enemies.filter((enemy) => enemy.exists());
+
});
+
+
// Listen for game over event
+
playerObj.on("death", () => {
+
gameActive = false;
+
finalScore = score;
+
+
// Stop enemy spawning
+
k.cancel("spawnEnemy");
-
// Random position at the edges of the screen
-
const side = Math.floor(Math.random() * 4); // 0: top, 1: right, 2: bottom, 3: left
-
let x = 0,
-
y = 0;
+
// Wait a moment before showing game over screen
+
k.wait(2, () => {
+
k.go("gameOver", finalScore);
+
});
+
});
+
});
-
switch (side) {
-
case 0: // top
-
x = Math.random() * (k.width() - 40) + 20; // Avoid spawning behind side walls
-
y = 10; // Just inside the top wall
-
break;
-
case 1: // right
-
x = k.width() - 10; // Just inside the right wall
-
y = Math.random() * (k.height() - 48 - 20) + 20; // Avoid spawning behind top wall or inside ground
-
break;
-
case 2: // bottom
-
x = Math.random() * (k.width() - 40) + 20; // Avoid spawning behind side walls
-
y = k.height() - 58; // Just above the ground (ground is at height-48 with height 48)
-
break;
-
case 3: // left
-
x = 10; // Just inside the left wall
-
y = Math.random() * (k.height() - 48 - 20) + 20; // Avoid spawning behind top wall or inside ground
-
break;
-
}
+
// Game over scene
+
k.scene("gameOver", (score: number) => {
+
// Background
+
k.add([k.rect(k.width(), k.height()), k.color(0, 0, 0), k.opacity(0.7)]);
-
// Create enemy using the makeEnemy function
-
const newEnemy = makeEnemy(k, playerObj, x, y);
-
enemies.push(newEnemy);
+
// Game over text
+
k.add([
+
k.text("GAME OVER", { size: 64 }),
+
k.pos(k.width() / 2, k.height() / 3),
+
k.anchor("center"),
+
k.color(255, 50, 50),
+
]);
-
// Remove from array when destroyed
-
newEnemy.on("destroy", () => {
-
enemies = enemies.filter((e) => e !== newEnemy);
+
// Score display
+
k.add([
+
k.text(`Final Score: ${score}`, { size: 36 }),
+
k.pos(k.width() / 2, k.height() / 2),
+
k.anchor("center"),
+
k.color(255, 255, 255),
+
]);
-
// Increase score when enemy is destroyed
-
score += Math.round(10 + Math.pow(difficultyLevel, 0.75));
+
// Restart button
+
const restartBtn = k.add([
+
k.rect(200, 60),
+
k.pos(k.width() / 2, (k.height() * 2) / 3),
+
k.anchor("center"),
+
k.color(50, 150, 50),
+
k.area(),
+
"restart-btn",
+
]);
-
// Update score display
-
scoreText.text = `Score: ${score}`;
+
// Restart text
+
k.add([
+
k.text("RESTART", { size: 24 }),
+
k.pos(k.width() / 2, (k.height() * 2) / 3),
+
k.anchor("center"),
+
k.color(255, 255, 255),
+
]);
-
if (Math.random() < 0.5) spawnEnemy();
+
// Restart on button click
+
restartBtn.onClick(() => {
+
k.go("main");
});
-
}
-
// Start spawning enemies
-
k.loop(spawnInterval, spawnEnemy);
+
// Restart on key press
+
k.onKeyPress("r", () => {
+
k.go("main");
+
});
-
// Game loop
-
k.onUpdate(() => {
-
// Update enemy list (remove destroyed enemies)
-
enemies = enemies.filter((enemy) => enemy.exists());
+
// Restart on enter key
+
k.onKeyPress("enter", () => {
+
k.go("main");
+
});
});
-
console.log(typeof k);
+
// Start the game
+
k.go("main");
+151 -8
src/player.ts
···
// Check if player is dead
if (health <= 0) {
-
// Game over logic here
-
k.addKaboom(this.pos);
+
// Game over logic
+
health = 0; // Ensure health doesn't go negative
+
+
// Create dramatic death effect
+
k.addKaboom(this.pos, { scale: 2 });
k.shake(20);
+
+
// Emit death event for game over handling
+
this.trigger("death");
}
},
···
}
});
-
// Attack with X key
+
// Attack with X key - now ultimate move
this.onKeyPress("x", () => {
-
this.attack();
+
// Create visual effects for charging up
+
const chargeEffect = k.add([
+
k.circle(50),
+
k.pos(this.pos),
+
k.color(255, 0, 0),
+
k.opacity(0.5),
+
k.anchor("center"),
+
k.z(0.8),
+
]);
+
+
// Grow the charge effect
+
k.tween(
+
50,
+
200,
+
1.5,
+
(v) => {
+
if (chargeEffect.exists()) {
+
chargeEffect.radius = v;
+
chargeEffect.opacity = 0.5 + Math.sin(k.time() * 10) * 0.2;
+
}
+
},
+
k.easings.easeInQuad,
+
);
+
+
// Add warning text
+
const warningText = k.add([
+
k.text("ULTIMATE CHARGING...", { size: 24 }),
+
k.pos(k.width() / 2, 100),
+
k.color(255, 50, 50),
+
k.anchor("center"),
+
k.z(100),
+
]);
+
+
// Flash the warning text
+
k.loop(0.2, () => {
+
if (warningText.exists()) {
+
warningText.color =
+
warningText.color === k.rgb(255, 50, 50)
+
? k.rgb(255, 100, 100)
+
: k.rgb(255, 50, 50);
+
}
+
});
+
+
// After delay, trigger the ultimate explosion
+
k.wait(2, () => {
+
if (chargeEffect.exists()) chargeEffect.destroy();
+
if (warningText.exists()) warningText.destroy();
+
+
// Create massive explosion
+
const explosionRadius = 500; // Much larger than normal explosions
+
+
// Animate sword for dramatic effect
+
if (sword) {
+
// Make sword glow red
+
sword.color = k.rgb(255, 0, 0);
+
+
// Dramatic sword spin
+
k.tween(
+
0,
+
720,
+
1,
+
(v) => {
+
if (sword) {
+
sword.angle = v;
+
sword.scaleTo(1 + Math.sin(k.time() * 10) * 0.3);
+
}
+
},
+
k.easings.easeInOutQuad,
+
);
+
}
+
+
// Visual effects
+
k.addKaboom(this.pos, {
+
scale: 5,
+
});
+
+
// Add multiple explosion effects for dramatic impact
+
for (let i = 0; i < 8; i++) {
+
const angle = Math.PI * 2 * (i / 8);
+
const offset = k.vec2(Math.cos(angle) * 100, Math.sin(angle) * 100);
+
+
k.wait(i * 0.1, () => {
+
k.addKaboom(k.vec2(this.pos).add(offset), {
+
scale: 2 + Math.random() * 2,
+
});
+
});
+
}
+
+
// Heavy screen shake
+
k.shake(40);
+
+
// Create explosion area for damage
+
const explosion = k.add([
+
k.circle(explosionRadius),
+
k.pos(this.pos),
+
k.color(255, 50, 50),
+
k.area(),
+
k.anchor("center"),
+
k.opacity(0.6),
+
"ultimate-explosion",
+
]);
+
+
// Fade out explosion
+
k.tween(
+
0.6,
+
0,
+
1.5,
+
(v) => {
+
if (explosion.exists()) {
+
explosion.opacity = v;
+
}
+
},
+
k.easings.easeOutQuad,
+
);
+
+
// Destroy explosion after animation
+
k.wait(1.5, () => {
+
if (explosion.exists()) explosion.destroy();
+
});
+
+
// Damage all enemies with high damage
+
const enemies = k.get("enemy");
+
enemies.forEach((enemy) => {
+
const dist = k.vec2(enemy.pos).dist(this.pos);
+
if (dist < explosionRadius) {
+
// Instant kill any enemy within the explosion radius
+
(enemy as any).damage(1000); // Extremely high damage to ensure death
+
+
// Add additional explosion effect at enemy position
+
k.wait(Math.random() * 0.3, () => {
+
k.addKaboom(enemy.pos, {
+
scale: 1 + Math.random(),
+
});
+
});
+
}
+
});
+
+
// Kill the player (sacrifice)
+
this.damage(health); // Use current health to ensure death
+
});
});
// Attack, kaboom and shake on click
···
// Apply scale
if (arrowPoints[i].scale) {
-
arrowPoints[i].scale.x = size / 3; // Divide by default size (3)
-
arrowPoints[i].scale.y = size / 3;
+
arrowPoints[i].scaleTo(size / 3); // Divide by default size (3)
}
}
···
// Make arrow head larger
if (arrowHead.scale) {
-
arrowHead.scale.x = 3;
-
arrowHead.scale.y = 3;
+
arrowHead.scaleTo(3);
}
}
},