/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ // Enemy Bullet var EnemyBullet = Container.expand(function () { var self = Container.call(this); var bullet = self.attachAsset('enemyBullet', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 18; self.update = function () { self.y += self.speed; }; return self; }); // Enemy Ship var EnemyShip = Container.expand(function () { var self = Container.call(this); var ship = self.attachAsset('enemyShip', { anchorX: 0.5, anchorY: 0.5 }); self.width = ship.width; self.height = ship.height; self.speed = 4 + Math.random() * 2; self.shootCooldown = 60 + Math.floor(Math.random() * 60); self.shootTimer = Math.floor(Math.random() * self.shootCooldown); self.hp = 1; self.update = function () { self.y += self.speed; if (self.shootTimer > 0) { self.shootTimer--; } }; return self; }); // Player Bullet var PlayerBullet = Container.expand(function () { var self = Container.call(this); var bullet = self.attachAsset('playerBullet', { anchorX: 0.5, anchorY: 0.5 }); self.speed = -32; self.update = function () { self.y += self.speed; }; return self; }); // Player Ship var PlayerShip = Container.expand(function () { var self = Container.call(this); var ship = self.attachAsset('playerShip', { anchorX: 0.5, anchorY: 0.5 }); self.width = ship.width; self.height = ship.height; self.canShoot = true; self.shootCooldown = 24; // frames between shots (reduced fire rate) self.shootTimer = 0; // self.lives = 1; // For MVP, 1 hit = game over self.poweredUp = false; self.powerupTimer = 0; // Power-up effect self.setPowerup = function (duration) { self.poweredUp = true; self.powerupTimer = duration; ship.tint = 0x00ff99; }; self.clearPowerup = function () { self.poweredUp = false; self.powerupTimer = 0; ship.tint = 0xffffff; }; self.update = function () { if (self.shootTimer > 0) self.shootTimer--; if (self.poweredUp) { self.powerupTimer--; if (self.powerupTimer <= 0) { self.clearPowerup(); } } }; return self; }); // Powerup var Powerup = Container.expand(function () { var self = Container.call(this); var p = self.attachAsset('powerup', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 8; self.update = function () { self.y += self.speed; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000010 }); /**** * Game Code ****/ // Add starfield background image var background = LK.getAsset('starfield', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, scaleX: 2048 / 1024, // assuming starfield asset is 1024x1366, scale to fit scaleY: 2732 / 1366 }); game.addChild(background); // Tap-to-start overlay var tapToStartTxt = new Text2('Tap to Start', { size: 180, fill: 0xffffff, font: "'GillSans-Bold',Impact,'Arial Black',Tahoma" }); tapToStartTxt.anchor.set(0.5, 0.5); tapToStartTxt.x = 2048 / 2; tapToStartTxt.y = 2732 / 2; LK.gui.center.addChild(tapToStartTxt); var gameStarted = false; var savedUpdate = null; // Pause game logic until tap savedUpdate = game.update; game.update = function () { if (!gameStarted) return; savedUpdate(); }; // Pause music until start var musicStarted = false; // Block all input except tap to start var origMove = game.move; var origDown = game.down; var origUp = game.up; game.move = function () { if (gameStarted && origMove) origMove.apply(this, arguments); }; game.down = function (x, y, obj) { if (!gameStarted) { gameStarted = true; LK.gui.center.removeChild(tapToStartTxt); if (!musicStarted) { LK.playMusic('galacticMusic'); musicStarted = true; } // Restore input handlers if (origDown) origDown.apply(this, arguments); return; } if (origDown) origDown.apply(this, arguments); }; game.up = function () { if (gameStarted && origUp) origUp.apply(this, arguments); }; // Score display var scoreTxt = new Text2('0', { size: 120, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Highscore display var highscore = storage.highscore || 0; var highscoreTxt = new Text2("HI " + highscore, { size: 80, fill: 0xFFD700 }); highscoreTxt.anchor.set(1, 0); LK.gui.topRight.addChild(highscoreTxt); // Powerup countdown display (lower right) var powerupCountdownTxt = new Text2('', { size: 80, fill: 0x00ff99 }); powerupCountdownTxt.anchor.set(1, 1); // bottom right LK.gui.bottomRight.addChild(powerupCountdownTxt); // Lives display (top left, but offset to avoid menu) var livesTxt = new Text2('♥♥♥', { size: 100, fill: 0xff4444 }); livesTxt.anchor.set(0, 0); livesTxt.x = 110; // leave 100px for menu livesTxt.y = 0; LK.gui.top.addChild(livesTxt); // Game variables var player; var playerBullets = []; var enemies = []; var enemyBullets = []; var powerups = []; var dragNode = null; var lastPlayerHit = false; var spawnTimer = 0; var powerupSpawnTimer = 0; // Enemy shooting control var enemyShootingEnabled = false; var enemyShootInterval = 120; // initial interval (frames between shots, high so they don't shoot at first) var lastEnemyShootInterval = 120; // Helper: Clamp value function clamp(val, min, max) { return Math.max(min, Math.min(max, val)); } // Initialize player player = new PlayerShip(); player.lives = 3; player.x = 2048 / 2; player.y = 2732 - 220; game.addChild(player); // Move handler (drag player ship) function handleMove(x, y, obj) { if (dragNode === player) { // Clamp player within screen bounds var halfW = player.width / 2; var halfH = player.height / 2; player.x = clamp(x, halfW, 2048 - halfW); player.y = clamp(y, 2048, 2732 - halfH); // Only allow movement in lower 700px } } game.move = handleMove; game.down = function (x, y, obj) { // Only start drag if touch is on player var local = player.toLocal(game.toGlobal({ x: x, y: y })); if (local.x >= -player.width / 2 && local.x <= player.width / 2 && local.y >= -player.height / 2 && local.y <= player.height / 2) { dragNode = player; handleMove(x, y, obj); } }; game.up = function (x, y, obj) { dragNode = null; }; // Fire player bullet (auto-fire, no mouse button needed) function firePlayerBullet() { if (player.shootTimer === 0) { var bullet = new PlayerBullet(); bullet.x = player.x; bullet.y = player.y - player.height / 2 - 10; playerBullets.push(bullet); game.addChild(bullet); LK.getSound('shoot').play(); player.shootTimer = player.poweredUp ? 6 : player.shootCooldown; // Power-up: triple shot if (player.poweredUp) { for (var i = -1; i <= 1; i += 2) { var b = new PlayerBullet(); b.x = player.x + i * 50; b.y = player.y - player.height / 2 - 10; b.rotation = i * 0.18; b.update = function (angle) { return function () { this.y += this.speed * Math.cos(angle); this.x += this.speed * Math.sin(angle); }; }(i * 0.18); playerBullets.push(b); game.addChild(b); } } } } // Spawn enemy function spawnEnemy() { var enemy = new EnemyShip(); enemy.x = 180 + Math.random() * (2048 - 360); enemy.y = -enemy.height / 2 - 10; // Set initial shootCooldown based on current global interval enemy.shootCooldown = enemyShootingEnabled ? enemyShootInterval + Math.floor(Math.random() * 30) : 120 + Math.floor(Math.random() * 60); enemy.shootTimer = Math.floor(Math.random() * enemy.shootCooldown); enemies.push(enemy); game.addChild(enemy); } // Spawn powerup function spawnPowerup() { var p = new Powerup(); p.x = 180 + Math.random() * (2048 - 360); p.y = -p.height / 2 - 10; powerups.push(p); game.addChild(p); } // Main update loop game.update = function () { // Player update player.update(); // Update lives display var livesStr = ''; for (var i = 0; i < player.lives; i++) livesStr += '♥'; livesTxt.setText(livesStr); // Update powerup countdown text if (player.poweredUp && player.powerupTimer > 0) { // Show seconds left, rounded up var seconds = Math.ceil(player.powerupTimer / 60); powerupCountdownTxt.setText(seconds + "s"); } else { powerupCountdownTxt.setText(''); } // Player auto-fire if (LK.ticks % 2 === 0) { firePlayerBullet(); } // Update player bullets for (var i = playerBullets.length - 1; i >= 0; i--) { var b = playerBullets[i]; b.update(); // Remove if off screen if (b.y < -80) { b.destroy(); playerBullets.splice(i, 1); continue; } // Check collision with enemies for (var j = enemies.length - 1; j >= 0; j--) { var e = enemies[j]; if (b.intersects(e)) { e.hp--; if (e.hp <= 0) { // Explosion effect LK.effects.flashObject(e, 0xffffff, 200); // Add explosion sprite at enemy position var explosion = LK.getAsset('explosion', { anchorX: 0.5, anchorY: 0.5, x: e.x, y: e.y }); game.addChild(explosion); // Animate explosion fade out and destroy after 400ms tween(explosion, { alpha: 0 }, { duration: 400, onFinish: function onFinish() { explosion.destroy(); } }); LK.getSound('explosion').play(); LK.setScore(LK.getScore() + 10); scoreTxt.setText(LK.getScore()); // Highscore logic if (LK.getScore() > highscore) { highscore = LK.getScore(); storage.highscore = highscore; highscoreTxt.setText("HI " + highscore); } e.destroy(); enemies.splice(j, 1); } b.destroy(); playerBullets.splice(i, 1); break; } } } // Update enemies var playerWasHitThisFrame = false; // Track if player was hit this frame for (var i = enemies.length - 1; i >= 0; i--) { var e = enemies[i]; e.update(); // Remove if off screen if (e.y > 2732 + e.height) { e.destroy(); enemies.splice(i, 1); continue; } // Enemy shooting logic // Enable enemy shooting after player reaches 200 points if (!enemyShootingEnabled && LK.getScore() >= 200) { enemyShootingEnabled = true; } // Gradually increase fire rate after enemy shooting is enabled if (enemyShootingEnabled) { // Decrease interval every 10 seconds, down to a minimum var minInterval = 24; // fastest fire rate (lower = faster) var maxInterval = 120; // slowest fire rate (at start) // Use ticks since enabling shooting to ramp up fire rate if (typeof enemyShootingStartTick === "undefined") { enemyShootingStartTick = LK.ticks; } var elapsed = LK.ticks - enemyShootingStartTick; var interval = Math.max(minInterval, maxInterval - Math.floor(elapsed / 600) * 8); enemyShootInterval = interval; // If interval changed, update all enemies' shootCooldown if (enemyShootInterval !== lastEnemyShootInterval) { for (var j = 0; j < enemies.length; j++) { enemies[j].shootCooldown = enemyShootInterval + Math.floor(Math.random() * 30); } lastEnemyShootInterval = enemyShootInterval; } } // Enemy shooting if (enemyShootingEnabled && e.shootTimer === 0) { var eb = new EnemyBullet(); eb.x = e.x; eb.y = e.y + e.height / 2 + 10; enemyBullets.push(eb); game.addChild(eb); LK.getSound('enemyShoot').play(); e.shootTimer = e.shootCooldown; } // Check collision with player if (e.intersects(player)) { if (!e.lastHitPlayer) { if (!lastPlayerHit && !playerWasHitThisFrame) { LK.effects.flashScreen(0xff0000, 800); LK.getSound('explosion').play(); player.lives--; playerWasHitThisFrame = true; if (player.lives <= 0) { player.lives = 0; livesTxt.setText(''); LK.showGameOver(); } else { // Reset player position and give brief invulnerability player.x = 2048 / 2; player.y = 2732 - 220; } lastPlayerHit = true; } e.lastHitPlayer = true; } } else { e.lastHitPlayer = false; } } // Update enemy bullets for (var i = enemyBullets.length - 1; i >= 0; i--) { var eb = enemyBullets[i]; eb.update(); if (eb.y > 2732 + 60) { eb.destroy(); enemyBullets.splice(i, 1); continue; } // Check collision with player if (eb.intersects(player)) { if (!eb.lastHitPlayer) { if (!lastPlayerHit && !playerWasHitThisFrame) { LK.effects.flashScreen(0xff0000, 800); LK.getSound('explosion').play(); player.lives--; playerWasHitThisFrame = true; if (player.lives <= 0) { player.lives = 0; livesTxt.setText(''); LK.showGameOver(); } else { // Reset player position and give brief invulnerability player.x = 2048 / 2; player.y = 2732 - 220; } lastPlayerHit = true; } eb.lastHitPlayer = true; } } else { eb.lastHitPlayer = false; } } // Update powerups for (var i = powerups.length - 1; i >= 0; i--) { var p = powerups[i]; p.update(); if (p.y > 2732 + 60) { p.destroy(); powerups.splice(i, 1); continue; } // Check collision with player if (p.intersects(player)) { player.setPowerup(600); // 10 seconds at 60fps LK.getSound('powerup').play(); p.destroy(); powerups.splice(i, 1); } } // Enemy spawn logic spawnTimer--; if (spawnTimer <= 0) { spawnEnemy(); // After 600 points, spawn an extra enemy each spawn cycle if (LK.getScore() >= 600) { spawnEnemy(); } spawnTimer = 36 + Math.floor(Math.random() * 24); // spawn every 1s approx } // Powerup spawn logic powerupSpawnTimer--; if (powerupSpawnTimer <= 0) { if (Math.random() < 0.18) { spawnPowerup(); } powerupSpawnTimer = 360 + Math.floor(Math.random() * 240); // every 6-10s } // Reset lastPlayerHit if player is alive if (!player.destroyed) { lastPlayerHit = false; } }; // Reset game state on game over LK.on('gameover', function () { // Clean up all objects for (var i = 0; i < playerBullets.length; i++) playerBullets[i].destroy(); for (var i = 0; i < enemies.length; i++) enemies[i].destroy(); for (var i = 0; i < enemyBullets.length; i++) enemyBullets[i].destroy(); for (var i = 0; i < powerups.length; i++) powerups[i].destroy(); playerBullets = []; enemies = []; enemyBullets = []; powerups = []; lastPlayerHit = false; // Reset player lives if (player) player.lives = 3; // Update lives display var livesStr = ''; for (var i = 0; i < 3; i++) livesStr += '♥'; livesTxt.setText(livesStr); // Music will be handled by LK // Update highscore display in case storage was externally changed highscore = storage.highscore || 0; highscoreTxt.setText("HI " + highscore); }); // Reset game state on win (not used in MVP, but for future) LK.on('youwin', function () { for (var i = 0; i < playerBullets.length; i++) playerBullets[i].destroy(); for (var i = 0; i < enemies.length; i++) enemies[i].destroy(); for (var i = 0; i < enemyBullets.length; i++) enemyBullets[i].destroy(); for (var i = 0; i < powerups.length; i++) powerups[i].destroy(); playerBullets = []; enemies = []; enemyBullets = []; powerups = []; lastPlayerHit = false; // Reset player lives if (player) player.lives = 3; // Update lives display var livesStr = ''; for (var i = 0; i < 3; i++) livesStr += '♥'; livesTxt.setText(livesStr); // Update highscore display in case storage was externally changed highscore = storage.highscore || 0; highscoreTxt.setText("HI " + highscore); });
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Enemy Bullet
var EnemyBullet = Container.expand(function () {
var self = Container.call(this);
var bullet = self.attachAsset('enemyBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 18;
self.update = function () {
self.y += self.speed;
};
return self;
});
// Enemy Ship
var EnemyShip = Container.expand(function () {
var self = Container.call(this);
var ship = self.attachAsset('enemyShip', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = ship.width;
self.height = ship.height;
self.speed = 4 + Math.random() * 2;
self.shootCooldown = 60 + Math.floor(Math.random() * 60);
self.shootTimer = Math.floor(Math.random() * self.shootCooldown);
self.hp = 1;
self.update = function () {
self.y += self.speed;
if (self.shootTimer > 0) {
self.shootTimer--;
}
};
return self;
});
// Player Bullet
var PlayerBullet = Container.expand(function () {
var self = Container.call(this);
var bullet = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = -32;
self.update = function () {
self.y += self.speed;
};
return self;
});
// Player Ship
var PlayerShip = Container.expand(function () {
var self = Container.call(this);
var ship = self.attachAsset('playerShip', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = ship.width;
self.height = ship.height;
self.canShoot = true;
self.shootCooldown = 24; // frames between shots (reduced fire rate)
self.shootTimer = 0;
// self.lives = 1; // For MVP, 1 hit = game over
self.poweredUp = false;
self.powerupTimer = 0;
// Power-up effect
self.setPowerup = function (duration) {
self.poweredUp = true;
self.powerupTimer = duration;
ship.tint = 0x00ff99;
};
self.clearPowerup = function () {
self.poweredUp = false;
self.powerupTimer = 0;
ship.tint = 0xffffff;
};
self.update = function () {
if (self.shootTimer > 0) self.shootTimer--;
if (self.poweredUp) {
self.powerupTimer--;
if (self.powerupTimer <= 0) {
self.clearPowerup();
}
}
};
return self;
});
// Powerup
var Powerup = Container.expand(function () {
var self = Container.call(this);
var p = self.attachAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8;
self.update = function () {
self.y += self.speed;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000010
});
/****
* Game Code
****/
// Add starfield background image
var background = LK.getAsset('starfield', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: 2048 / 1024,
// assuming starfield asset is 1024x1366, scale to fit
scaleY: 2732 / 1366
});
game.addChild(background);
// Tap-to-start overlay
var tapToStartTxt = new Text2('Tap to Start', {
size: 180,
fill: 0xffffff,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
tapToStartTxt.anchor.set(0.5, 0.5);
tapToStartTxt.x = 2048 / 2;
tapToStartTxt.y = 2732 / 2;
LK.gui.center.addChild(tapToStartTxt);
var gameStarted = false;
var savedUpdate = null;
// Pause game logic until tap
savedUpdate = game.update;
game.update = function () {
if (!gameStarted) return;
savedUpdate();
};
// Pause music until start
var musicStarted = false;
// Block all input except tap to start
var origMove = game.move;
var origDown = game.down;
var origUp = game.up;
game.move = function () {
if (gameStarted && origMove) origMove.apply(this, arguments);
};
game.down = function (x, y, obj) {
if (!gameStarted) {
gameStarted = true;
LK.gui.center.removeChild(tapToStartTxt);
if (!musicStarted) {
LK.playMusic('galacticMusic');
musicStarted = true;
}
// Restore input handlers
if (origDown) origDown.apply(this, arguments);
return;
}
if (origDown) origDown.apply(this, arguments);
};
game.up = function () {
if (gameStarted && origUp) origUp.apply(this, arguments);
};
// Score display
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Highscore display
var highscore = storage.highscore || 0;
var highscoreTxt = new Text2("HI " + highscore, {
size: 80,
fill: 0xFFD700
});
highscoreTxt.anchor.set(1, 0);
LK.gui.topRight.addChild(highscoreTxt);
// Powerup countdown display (lower right)
var powerupCountdownTxt = new Text2('', {
size: 80,
fill: 0x00ff99
});
powerupCountdownTxt.anchor.set(1, 1); // bottom right
LK.gui.bottomRight.addChild(powerupCountdownTxt);
// Lives display (top left, but offset to avoid menu)
var livesTxt = new Text2('♥♥♥', {
size: 100,
fill: 0xff4444
});
livesTxt.anchor.set(0, 0);
livesTxt.x = 110; // leave 100px for menu
livesTxt.y = 0;
LK.gui.top.addChild(livesTxt);
// Game variables
var player;
var playerBullets = [];
var enemies = [];
var enemyBullets = [];
var powerups = [];
var dragNode = null;
var lastPlayerHit = false;
var spawnTimer = 0;
var powerupSpawnTimer = 0;
// Enemy shooting control
var enemyShootingEnabled = false;
var enemyShootInterval = 120; // initial interval (frames between shots, high so they don't shoot at first)
var lastEnemyShootInterval = 120;
// Helper: Clamp value
function clamp(val, min, max) {
return Math.max(min, Math.min(max, val));
}
// Initialize player
player = new PlayerShip();
player.lives = 3;
player.x = 2048 / 2;
player.y = 2732 - 220;
game.addChild(player);
// Move handler (drag player ship)
function handleMove(x, y, obj) {
if (dragNode === player) {
// Clamp player within screen bounds
var halfW = player.width / 2;
var halfH = player.height / 2;
player.x = clamp(x, halfW, 2048 - halfW);
player.y = clamp(y, 2048, 2732 - halfH); // Only allow movement in lower 700px
}
}
game.move = handleMove;
game.down = function (x, y, obj) {
// Only start drag if touch is on player
var local = player.toLocal(game.toGlobal({
x: x,
y: y
}));
if (local.x >= -player.width / 2 && local.x <= player.width / 2 && local.y >= -player.height / 2 && local.y <= player.height / 2) {
dragNode = player;
handleMove(x, y, obj);
}
};
game.up = function (x, y, obj) {
dragNode = null;
};
// Fire player bullet (auto-fire, no mouse button needed)
function firePlayerBullet() {
if (player.shootTimer === 0) {
var bullet = new PlayerBullet();
bullet.x = player.x;
bullet.y = player.y - player.height / 2 - 10;
playerBullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
player.shootTimer = player.poweredUp ? 6 : player.shootCooldown;
// Power-up: triple shot
if (player.poweredUp) {
for (var i = -1; i <= 1; i += 2) {
var b = new PlayerBullet();
b.x = player.x + i * 50;
b.y = player.y - player.height / 2 - 10;
b.rotation = i * 0.18;
b.update = function (angle) {
return function () {
this.y += this.speed * Math.cos(angle);
this.x += this.speed * Math.sin(angle);
};
}(i * 0.18);
playerBullets.push(b);
game.addChild(b);
}
}
}
}
// Spawn enemy
function spawnEnemy() {
var enemy = new EnemyShip();
enemy.x = 180 + Math.random() * (2048 - 360);
enemy.y = -enemy.height / 2 - 10;
// Set initial shootCooldown based on current global interval
enemy.shootCooldown = enemyShootingEnabled ? enemyShootInterval + Math.floor(Math.random() * 30) : 120 + Math.floor(Math.random() * 60);
enemy.shootTimer = Math.floor(Math.random() * enemy.shootCooldown);
enemies.push(enemy);
game.addChild(enemy);
}
// Spawn powerup
function spawnPowerup() {
var p = new Powerup();
p.x = 180 + Math.random() * (2048 - 360);
p.y = -p.height / 2 - 10;
powerups.push(p);
game.addChild(p);
}
// Main update loop
game.update = function () {
// Player update
player.update();
// Update lives display
var livesStr = '';
for (var i = 0; i < player.lives; i++) livesStr += '♥';
livesTxt.setText(livesStr);
// Update powerup countdown text
if (player.poweredUp && player.powerupTimer > 0) {
// Show seconds left, rounded up
var seconds = Math.ceil(player.powerupTimer / 60);
powerupCountdownTxt.setText(seconds + "s");
} else {
powerupCountdownTxt.setText('');
}
// Player auto-fire
if (LK.ticks % 2 === 0) {
firePlayerBullet();
}
// Update player bullets
for (var i = playerBullets.length - 1; i >= 0; i--) {
var b = playerBullets[i];
b.update();
// Remove if off screen
if (b.y < -80) {
b.destroy();
playerBullets.splice(i, 1);
continue;
}
// Check collision with enemies
for (var j = enemies.length - 1; j >= 0; j--) {
var e = enemies[j];
if (b.intersects(e)) {
e.hp--;
if (e.hp <= 0) {
// Explosion effect
LK.effects.flashObject(e, 0xffffff, 200);
// Add explosion sprite at enemy position
var explosion = LK.getAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
x: e.x,
y: e.y
});
game.addChild(explosion);
// Animate explosion fade out and destroy after 400ms
tween(explosion, {
alpha: 0
}, {
duration: 400,
onFinish: function onFinish() {
explosion.destroy();
}
});
LK.getSound('explosion').play();
LK.setScore(LK.getScore() + 10);
scoreTxt.setText(LK.getScore());
// Highscore logic
if (LK.getScore() > highscore) {
highscore = LK.getScore();
storage.highscore = highscore;
highscoreTxt.setText("HI " + highscore);
}
e.destroy();
enemies.splice(j, 1);
}
b.destroy();
playerBullets.splice(i, 1);
break;
}
}
}
// Update enemies
var playerWasHitThisFrame = false; // Track if player was hit this frame
for (var i = enemies.length - 1; i >= 0; i--) {
var e = enemies[i];
e.update();
// Remove if off screen
if (e.y > 2732 + e.height) {
e.destroy();
enemies.splice(i, 1);
continue;
}
// Enemy shooting logic
// Enable enemy shooting after player reaches 200 points
if (!enemyShootingEnabled && LK.getScore() >= 200) {
enemyShootingEnabled = true;
}
// Gradually increase fire rate after enemy shooting is enabled
if (enemyShootingEnabled) {
// Decrease interval every 10 seconds, down to a minimum
var minInterval = 24; // fastest fire rate (lower = faster)
var maxInterval = 120; // slowest fire rate (at start)
// Use ticks since enabling shooting to ramp up fire rate
if (typeof enemyShootingStartTick === "undefined") {
enemyShootingStartTick = LK.ticks;
}
var elapsed = LK.ticks - enemyShootingStartTick;
var interval = Math.max(minInterval, maxInterval - Math.floor(elapsed / 600) * 8);
enemyShootInterval = interval;
// If interval changed, update all enemies' shootCooldown
if (enemyShootInterval !== lastEnemyShootInterval) {
for (var j = 0; j < enemies.length; j++) {
enemies[j].shootCooldown = enemyShootInterval + Math.floor(Math.random() * 30);
}
lastEnemyShootInterval = enemyShootInterval;
}
}
// Enemy shooting
if (enemyShootingEnabled && e.shootTimer === 0) {
var eb = new EnemyBullet();
eb.x = e.x;
eb.y = e.y + e.height / 2 + 10;
enemyBullets.push(eb);
game.addChild(eb);
LK.getSound('enemyShoot').play();
e.shootTimer = e.shootCooldown;
}
// Check collision with player
if (e.intersects(player)) {
if (!e.lastHitPlayer) {
if (!lastPlayerHit && !playerWasHitThisFrame) {
LK.effects.flashScreen(0xff0000, 800);
LK.getSound('explosion').play();
player.lives--;
playerWasHitThisFrame = true;
if (player.lives <= 0) {
player.lives = 0;
livesTxt.setText('');
LK.showGameOver();
} else {
// Reset player position and give brief invulnerability
player.x = 2048 / 2;
player.y = 2732 - 220;
}
lastPlayerHit = true;
}
e.lastHitPlayer = true;
}
} else {
e.lastHitPlayer = false;
}
}
// Update enemy bullets
for (var i = enemyBullets.length - 1; i >= 0; i--) {
var eb = enemyBullets[i];
eb.update();
if (eb.y > 2732 + 60) {
eb.destroy();
enemyBullets.splice(i, 1);
continue;
}
// Check collision with player
if (eb.intersects(player)) {
if (!eb.lastHitPlayer) {
if (!lastPlayerHit && !playerWasHitThisFrame) {
LK.effects.flashScreen(0xff0000, 800);
LK.getSound('explosion').play();
player.lives--;
playerWasHitThisFrame = true;
if (player.lives <= 0) {
player.lives = 0;
livesTxt.setText('');
LK.showGameOver();
} else {
// Reset player position and give brief invulnerability
player.x = 2048 / 2;
player.y = 2732 - 220;
}
lastPlayerHit = true;
}
eb.lastHitPlayer = true;
}
} else {
eb.lastHitPlayer = false;
}
}
// Update powerups
for (var i = powerups.length - 1; i >= 0; i--) {
var p = powerups[i];
p.update();
if (p.y > 2732 + 60) {
p.destroy();
powerups.splice(i, 1);
continue;
}
// Check collision with player
if (p.intersects(player)) {
player.setPowerup(600); // 10 seconds at 60fps
LK.getSound('powerup').play();
p.destroy();
powerups.splice(i, 1);
}
}
// Enemy spawn logic
spawnTimer--;
if (spawnTimer <= 0) {
spawnEnemy();
// After 600 points, spawn an extra enemy each spawn cycle
if (LK.getScore() >= 600) {
spawnEnemy();
}
spawnTimer = 36 + Math.floor(Math.random() * 24); // spawn every 1s approx
}
// Powerup spawn logic
powerupSpawnTimer--;
if (powerupSpawnTimer <= 0) {
if (Math.random() < 0.18) {
spawnPowerup();
}
powerupSpawnTimer = 360 + Math.floor(Math.random() * 240); // every 6-10s
}
// Reset lastPlayerHit if player is alive
if (!player.destroyed) {
lastPlayerHit = false;
}
};
// Reset game state on game over
LK.on('gameover', function () {
// Clean up all objects
for (var i = 0; i < playerBullets.length; i++) playerBullets[i].destroy();
for (var i = 0; i < enemies.length; i++) enemies[i].destroy();
for (var i = 0; i < enemyBullets.length; i++) enemyBullets[i].destroy();
for (var i = 0; i < powerups.length; i++) powerups[i].destroy();
playerBullets = [];
enemies = [];
enemyBullets = [];
powerups = [];
lastPlayerHit = false;
// Reset player lives
if (player) player.lives = 3;
// Update lives display
var livesStr = '';
for (var i = 0; i < 3; i++) livesStr += '♥';
livesTxt.setText(livesStr);
// Music will be handled by LK
// Update highscore display in case storage was externally changed
highscore = storage.highscore || 0;
highscoreTxt.setText("HI " + highscore);
});
// Reset game state on win (not used in MVP, but for future)
LK.on('youwin', function () {
for (var i = 0; i < playerBullets.length; i++) playerBullets[i].destroy();
for (var i = 0; i < enemies.length; i++) enemies[i].destroy();
for (var i = 0; i < enemyBullets.length; i++) enemyBullets[i].destroy();
for (var i = 0; i < powerups.length; i++) powerups[i].destroy();
playerBullets = [];
enemies = [];
enemyBullets = [];
powerups = [];
lastPlayerHit = false;
// Reset player lives
if (player) player.lives = 3;
// Update lives display
var livesStr = '';
for (var i = 0; i < 3; i++) livesStr += '♥';
livesTxt.setText(livesStr);
// Update highscore display in case storage was externally changed
highscore = storage.highscore || 0;
highscoreTxt.setText("HI " + highscore);
});
spaceship seen from above. In-Game asset. 2d. High contrast. No shadows
spaceship seen from up going down. In-Game asset. 2d. High contrast. No shadows
color blue
red laser beam. In-Game asset. 2d. High contrast. No shadows
color green
explosion. In-Game asset. 2d. High contrast. No shadows
starfield banner
make box yellow