/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Alien invader var Alien = Container.expand(function () { var self = Container.call(this); var alien = self.attachAsset('alien', { anchorX: 0.5, anchorY: 0.5 }); self.width = alien.width; self.height = alien.height; self.row = 0; self.col = 0; self.direction = 1; // 1: right, -1: left self.speed = 2; self.shootCooldown = 0; self.alive = true; // Alien shoots self.shoot = function () { var bullet = new AlienBullet(); bullet.x = self.x; bullet.y = self.y + self.height / 2 + bullet.height / 2; alienBullets.push(bullet); game.addChild(bullet); LK.getSound('alienShoot').play(); }; return self; }); // Alien bullet var AlienBullet = Container.expand(function () { var self = Container.call(this); var bullet = self.attachAsset('alienBullet', { anchorX: 0.5, anchorY: 0.5 }); self.width = bullet.width; self.height = bullet.height; self.speed = 18; self.update = function () { self.y += self.speed; }; return self; }); // Player spaceship var Player = 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 = 0; self.lives = 3; self.invincible = false; self.invincibleTimer = 0; self.poweredUp = false; self.powerupTimer = 0; // Shoot a bullet self.shoot = function () { if (!self.canShoot) return; var bullet = new PlayerBullet(); bullet.x = self.x; bullet.y = self.y - self.height / 2 - bullet.height / 2; playerBullets.push(bullet); game.addChild(bullet); LK.getSound('shoot').play(); self.canShoot = false; self.shootCooldown = 18; // 18 ticks ~0.3s }; // Power-up effect self.activatePowerup = function () { self.poweredUp = true; self.powerupTimer = 360; // 6 seconds // Visual feedback: flash ship tween(self, { alpha: 0.5 }, { duration: 120, easing: tween.easeIn, onFinish: function onFinish() { tween(self, { alpha: 1 }, { duration: 120, easing: tween.easeOut }); } }); }; // Invincibility effect self.setInvincible = function (duration) { self.invincible = true; self.invincibleTimer = duration; // Visual feedback: flicker }; 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.width = bullet.width; self.height = bullet.height; self.speed = -18; self.update = function () { self.y += self.speed; }; return self; }); // Power-up var Powerup = Container.expand(function () { var self = Container.call(this); var p = self.attachAsset('powerup', { anchorX: 0.5, anchorY: 0.5 }); self.width = p.width; self.height = p.height; self.speed = 10; self.type = 'rapid'; // Only one type for MVP self.update = function () { self.y += self.speed; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000010 }); /**** * Game Code ****/ // Music // Sound effects // Power-up // Alien bullet // Player bullet // Alien invader // Spaceship (player) // Game constants var GAME_WIDTH = 2048; var GAME_HEIGHT = 2732; var PLAYER_Y = GAME_HEIGHT - 220; var ALIEN_ROWS = 4; var ALIEN_COLS = 8; var ALIEN_X_MARGIN = 120; var ALIEN_Y_MARGIN = 120; var ALIEN_SPACING_X = 180; var ALIEN_SPACING_Y = 160; var ALIEN_START_Y = 320; var ALIEN_MOVE_DOWN = 60; var ALIEN_BASE_SPEED = 2; var ALIEN_SPEEDUP_PER_WAVE = 0.5; var ALIEN_SHOOT_CHANCE = 0.008; // Per alien per tick var POWERUP_CHANCE = 0.015; // Per tick, if no powerup present // Game state var player; var aliens = []; var playerBullets = []; var alienBullets = []; var powerups = []; var wave = 1; var score = 0; var lives = 3; var gameOver = false; var youWin = false; var moveDir = 1; // 1: right, -1: left var moveDownPending = false; var alienSpeed = ALIEN_BASE_SPEED; var dragNode = null; var lastTouchX = 0; var lastTouchY = 0; // GUI var scoreTxt = new Text2('0', { size: 120, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Heart icons for lives var heartIcons = []; var heartSpacing = 120; for (var i = 0; i < 3; i++) { var heart = LK.getAsset('powerup', { anchorX: 0.5, anchorY: 0.5, x: 120 + i * heartSpacing, y: 100, scaleX: 0.7, scaleY: 0.7 }); LK.gui.top.addChild(heart); heartIcons.push(heart); } var livesTxt = new Text2('', { size: 100, fill: 0xFF4444 }); livesTxt.anchor.set(0.5, 0); LK.gui.topRight.addChild(livesTxt); var waveTxt = new Text2('Wave 1', { size: 80, fill: 0x00FFCC }); waveTxt.anchor.set(0.5, 0); LK.gui.top.addChild(waveTxt); // Helper: update GUI function updateGUI() { scoreTxt.setText(score); // Update heart icons visibility for (var i = 0; i < heartIcons.length; i++) { heartIcons[i].visible = i < lives; } var hearts = ''; for (var i = 0; i < lives; i++) hearts += '♥'; livesTxt.setText(hearts); waveTxt.setText('Wave ' + wave); } // Helper: spawn aliens in grid function spawnAliens() { aliens = []; var totalWidth = (ALIEN_COLS - 1) * ALIEN_SPACING_X; var startX = (GAME_WIDTH - totalWidth) / 2; for (var row = 0; row < ALIEN_ROWS; row++) { for (var col = 0; col < ALIEN_COLS; col++) { var alien = new Alien(); alien.x = startX + col * ALIEN_SPACING_X; alien.y = ALIEN_START_Y + row * ALIEN_SPACING_Y; alien.row = row; alien.col = col; alien.direction = moveDir; alien.speed = alienSpeed; aliens.push(alien); game.addChild(alien); } } } // Helper: start new wave function startWave() { alienSpeed = ALIEN_BASE_SPEED + (wave - 1) * ALIEN_SPEEDUP_PER_WAVE; spawnAliens(); moveDir = 1; moveDownPending = false; waveTxt.setText('Wave ' + wave); } // Helper: reset game function resetGame() { // Remove all objects for (var i = 0; i < aliens.length; i++) aliens[i].destroy(); for (var i = 0; i < playerBullets.length; i++) playerBullets[i].destroy(); for (var i = 0; i < alienBullets.length; i++) alienBullets[i].destroy(); for (var i = 0; i < powerups.length; i++) powerups[i].destroy(); aliens = []; playerBullets = []; alienBullets = []; powerups = []; wave = 1; score = 0; lives = 3; gameOver = false; youWin = false; updateGUI(); // Reset player if (player) player.destroy(); player = new Player(); player.x = GAME_WIDTH / 2; player.y = PLAYER_Y; game.addChild(player); startWave(); } // Helper: show game over function triggerGameOver() { if (gameOver) return; gameOver = true; LK.getSound('loseLife').play(); LK.effects.flashScreen(0xff0000, 1200); LK.showGameOver(); } // Helper: show win function triggerYouWin() { if (youWin) return; youWin = true; LK.effects.flashScreen(0x00ff00, 1200); LK.showYouWin(); } // Helper: spawn powerup function spawnPowerup() { var p = new Powerup(); p.x = 200 + Math.random() * (GAME_WIDTH - 400); p.y = 200; powerups.push(p); game.addChild(p); } // --- Start Screen Implementation --- var startScreenOverlay = new Container(); var startBg = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, x: GAME_WIDTH / 2, y: GAME_HEIGHT / 2, scaleX: 16, scaleY: 22, tint: 0x000010 }); startScreenOverlay.addChild(startBg); var titleTxt = new Text2('SPACE INVADERS', { size: 220, fill: 0xFFFF00 }); titleTxt.anchor.set(0.5, 0.5); titleTxt.x = GAME_WIDTH / 2; titleTxt.y = GAME_HEIGHT / 2 - 320; startScreenOverlay.addChild(titleTxt); // Decorative player ship (centered, above button) var decorPlayer = LK.getAsset('playerShip', { anchorX: 0.5, anchorY: 0.5, x: GAME_WIDTH / 2, y: GAME_HEIGHT / 2 - 80, scaleX: 1.2, scaleY: 1.2 }); startScreenOverlay.addChild(decorPlayer); // Decorative powerups (left and right of player ship) var decorPowerupLeft = LK.getAsset('powerup', { anchorX: 0.5, anchorY: 0.5, x: GAME_WIDTH / 2 - 220, y: GAME_HEIGHT / 2 - 80, scaleX: 0.9, scaleY: 0.9 }); var decorPowerupRight = LK.getAsset('powerup', { anchorX: 0.5, anchorY: 0.5, x: GAME_WIDTH / 2 + 220, y: GAME_HEIGHT / 2 - 80, scaleX: 0.9, scaleY: 0.9 }); startScreenOverlay.addChild(decorPowerupLeft); startScreenOverlay.addChild(decorPowerupRight); // Start button var startBtn = new Text2('Start', { size: 140, fill: 0x00FFCC, background: 0x222222, padding: 60, radius: 60 }); startBtn.anchor.set(0.5, 0.5); startBtn.x = GAME_WIDTH / 2; startBtn.y = GAME_HEIGHT / 2 + 120; startScreenOverlay.addChild(startBtn); // Start button press handler startBtn.down = function (x, y, obj) { if (!gameStarted) { gameStarted = true; startScreenOverlay.visible = false; resetGame(); LK.playMusic('arcadebg', { fade: { start: 0, end: 1, duration: 1200 } }); } }; // Decorative aliens removed from start screen var creditsTxt = new Text2('by FRVR', { size: 80, fill: 0x00FFCC }); creditsTxt.anchor.set(0.5, 0.5); creditsTxt.x = GAME_WIDTH / 2; creditsTxt.y = GAME_HEIGHT / 2 + 320; startScreenOverlay.addChild(creditsTxt); // No aliens on start screen! game.addChild(startScreenOverlay); // Ensure no aliens are deleted from the game on the start screen for (var i = 0; i < aliens.length; i++) { if (aliens[i]) { aliens[i].destroy(); } } aliens = []; var gameStarted = false; // Only allow tap to start when on start screen game.down = function (x, y, obj) { if (gameOver || youWin) return; // Check if touch is on player ship if (player && y > player.y - player.height / 2 && y < player.y + player.height / 2 && x > player.x - player.width / 2 && x < player.x + player.width / 2) { draggingPlayer = true; dragOffsetX = x - player.x; } else { // Tap elsewhere: do nothing (auto-shoot enabled) } }; // --- End Start Screen Implementation --- // Drag-to-move and tap-to-shoot controls var draggingPlayer = false; var dragOffsetX = 0; game.down = function (x, y, obj) { if (gameOver || youWin) return; // Check if touch is on player ship if (player && y > player.y - player.height / 2 && y < player.y + player.height / 2 && x > player.x - player.width / 2 && x < player.x + player.width / 2) { draggingPlayer = true; dragOffsetX = x - player.x; } else { // Tap elsewhere: do nothing (auto-shoot enabled) } }; game.move = function (x, y, obj) { if (draggingPlayer && !gameOver && !youWin) { // Clamp player within screen bounds var minX = player.width / 2; var maxX = GAME_WIDTH - player.width / 2; var newX = Math.max(minX, Math.min(x - dragOffsetX, maxX)); player.x = newX; } }; game.up = function (x, y, obj) { draggingPlayer = false; }; // Main game loop game.update = function () { if (gameOver || youWin) return; // Player auto-shooting if (player && !player.canShoot) { player.shootCooldown--; if (player.shootCooldown <= 0) player.canShoot = true; } if (player && player.canShoot) { player.shoot(); } // Powerup timer if (player && player.poweredUp) { player.powerupTimer--; if (player.powerupTimer <= 0) { player.poweredUp = false; } } // Invincibility timer if (player && player.invincible) { player.invincibleTimer--; // Flicker effect player.alpha = LK.ticks % 10 < 5 ? 0.4 : 1; if (player.invincibleTimer <= 0) { player.invincible = false; player.alpha = 1; } } // Move aliens var leftmost = GAME_WIDTH, rightmost = 0, bottommost = 0; for (var i = 0; i < aliens.length; i++) { var a = aliens[i]; if (!a.alive) continue; a.x += moveDir * alienSpeed; if (a.x - a.width / 2 < leftmost) leftmost = a.x - a.width / 2; if (a.x + a.width / 2 > rightmost) rightmost = a.x + a.width / 2; if (a.y + a.height / 2 > bottommost) bottommost = a.y + a.height / 2; } // Change direction if at edge if (!moveDownPending && (leftmost < ALIEN_X_MARGIN || rightmost > GAME_WIDTH - ALIEN_X_MARGIN)) { moveDir *= -1; moveDownPending = true; } if (moveDownPending) { for (var i = 0; i < aliens.length; i++) { var a = aliens[i]; if (!a.alive) continue; a.y += ALIEN_MOVE_DOWN; } moveDownPending = false; } // Aliens shoot randomly, but only bottom-most alive in each column can shoot var bottomAliens = {}; for (var i = 0; i < aliens.length; i++) { var a = aliens[i]; if (!a.alive) continue; // Track the bottom-most alive alien in each column if (!bottomAliens[a.col] || a.y > bottomAliens[a.col].y) { bottomAliens[a.col] = a; } } // Only allow a few (e.g. 2) random bottom aliens to shoot per tick var shootableAliens = []; for (var col in bottomAliens) { shootableAliens.push(bottomAliens[col]); } var maxShooters = 2; for (var s = 0; s < maxShooters && shootableAliens.length > 0; s++) { var idx = Math.floor(Math.random() * shootableAliens.length); var a = shootableAliens[idx]; if (Math.random() < ALIEN_SHOOT_CHANCE + 0.001 * wave) { a.shoot(); } shootableAliens.splice(idx, 1); } // 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 < -b.height) { b.destroy(); playerBullets.splice(i, 1); continue; } // Check collision with aliens for (var j = 0; j < aliens.length; j++) { var a = aliens[j]; if (!a.alive) continue; if (b.intersects(a)) { a.alive = false; a.visible = false; b.destroy(); playerBullets.splice(i, 1); LK.getSound('alienExplode').play(); score += 10; updateGUI(); break; } } } // Update alien bullets for (var i = alienBullets.length - 1; i >= 0; i--) { var b = alienBullets[i]; b.update(); // Remove if off screen if (b.y > GAME_HEIGHT + b.height) { b.destroy(); alienBullets.splice(i, 1); continue; } // Check collision with player if (player && !player.invincible && b.intersects(player)) { b.destroy(); alienBullets.splice(i, 1); // Lose life lives--; updateGUI(); LK.getSound('hit').play(); if (lives <= 0) { triggerGameOver(); return; } else { player.setInvincible(90); // 1.5s invincibility } } } // Update powerups for (var i = powerups.length - 1; i >= 0; i--) { var p = powerups[i]; p.update(); if (p.y > GAME_HEIGHT + p.height) { p.destroy(); powerups.splice(i, 1); continue; } if (p.intersects(player)) { LK.getSound('powerup').play(); // Only restore up to 3 hearts if (lives < 3) { lives = 3; updateGUI(); } p.destroy(); powerups.splice(i, 1); } } // Powerup spawn if (powerups.length === 0 && Math.random() < POWERUP_CHANCE) { spawnPowerup(); } // Check if aliens reach player for (var i = 0; i < aliens.length; i++) { var a = aliens[i]; if (!a.alive) continue; if (player && a.y + a.height / 2 >= player.y - player.height / 2) { triggerGameOver(); return; } } // Next wave if all aliens dead var anyAlive = false; for (var i = 0; i < aliens.length; i++) { if (aliens[i].alive) { anyAlive = true; break; } } if (!anyAlive) { wave++; if (wave > 10) { triggerYouWin(); return; } startWave(); } }; // Clean up on game over or reset game.on('destroy', function () { LK.stopMusic(); });
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Alien invader
var Alien = Container.expand(function () {
var self = Container.call(this);
var alien = self.attachAsset('alien', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = alien.width;
self.height = alien.height;
self.row = 0;
self.col = 0;
self.direction = 1; // 1: right, -1: left
self.speed = 2;
self.shootCooldown = 0;
self.alive = true;
// Alien shoots
self.shoot = function () {
var bullet = new AlienBullet();
bullet.x = self.x;
bullet.y = self.y + self.height / 2 + bullet.height / 2;
alienBullets.push(bullet);
game.addChild(bullet);
LK.getSound('alienShoot').play();
};
return self;
});
// Alien bullet
var AlienBullet = Container.expand(function () {
var self = Container.call(this);
var bullet = self.attachAsset('alienBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = bullet.width;
self.height = bullet.height;
self.speed = 18;
self.update = function () {
self.y += self.speed;
};
return self;
});
// Player spaceship
var Player = 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 = 0;
self.lives = 3;
self.invincible = false;
self.invincibleTimer = 0;
self.poweredUp = false;
self.powerupTimer = 0;
// Shoot a bullet
self.shoot = function () {
if (!self.canShoot) return;
var bullet = new PlayerBullet();
bullet.x = self.x;
bullet.y = self.y - self.height / 2 - bullet.height / 2;
playerBullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
self.canShoot = false;
self.shootCooldown = 18; // 18 ticks ~0.3s
};
// Power-up effect
self.activatePowerup = function () {
self.poweredUp = true;
self.powerupTimer = 360; // 6 seconds
// Visual feedback: flash ship
tween(self, {
alpha: 0.5
}, {
duration: 120,
easing: tween.easeIn,
onFinish: function onFinish() {
tween(self, {
alpha: 1
}, {
duration: 120,
easing: tween.easeOut
});
}
});
};
// Invincibility effect
self.setInvincible = function (duration) {
self.invincible = true;
self.invincibleTimer = duration;
// Visual feedback: flicker
};
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.width = bullet.width;
self.height = bullet.height;
self.speed = -18;
self.update = function () {
self.y += self.speed;
};
return self;
});
// Power-up
var Powerup = Container.expand(function () {
var self = Container.call(this);
var p = self.attachAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = p.width;
self.height = p.height;
self.speed = 10;
self.type = 'rapid'; // Only one type for MVP
self.update = function () {
self.y += self.speed;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000010
});
/****
* Game Code
****/
// Music
// Sound effects
// Power-up
// Alien bullet
// Player bullet
// Alien invader
// Spaceship (player)
// Game constants
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var PLAYER_Y = GAME_HEIGHT - 220;
var ALIEN_ROWS = 4;
var ALIEN_COLS = 8;
var ALIEN_X_MARGIN = 120;
var ALIEN_Y_MARGIN = 120;
var ALIEN_SPACING_X = 180;
var ALIEN_SPACING_Y = 160;
var ALIEN_START_Y = 320;
var ALIEN_MOVE_DOWN = 60;
var ALIEN_BASE_SPEED = 2;
var ALIEN_SPEEDUP_PER_WAVE = 0.5;
var ALIEN_SHOOT_CHANCE = 0.008; // Per alien per tick
var POWERUP_CHANCE = 0.015; // Per tick, if no powerup present
// Game state
var player;
var aliens = [];
var playerBullets = [];
var alienBullets = [];
var powerups = [];
var wave = 1;
var score = 0;
var lives = 3;
var gameOver = false;
var youWin = false;
var moveDir = 1; // 1: right, -1: left
var moveDownPending = false;
var alienSpeed = ALIEN_BASE_SPEED;
var dragNode = null;
var lastTouchX = 0;
var lastTouchY = 0;
// GUI
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Heart icons for lives
var heartIcons = [];
var heartSpacing = 120;
for (var i = 0; i < 3; i++) {
var heart = LK.getAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5,
x: 120 + i * heartSpacing,
y: 100,
scaleX: 0.7,
scaleY: 0.7
});
LK.gui.top.addChild(heart);
heartIcons.push(heart);
}
var livesTxt = new Text2('', {
size: 100,
fill: 0xFF4444
});
livesTxt.anchor.set(0.5, 0);
LK.gui.topRight.addChild(livesTxt);
var waveTxt = new Text2('Wave 1', {
size: 80,
fill: 0x00FFCC
});
waveTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(waveTxt);
// Helper: update GUI
function updateGUI() {
scoreTxt.setText(score);
// Update heart icons visibility
for (var i = 0; i < heartIcons.length; i++) {
heartIcons[i].visible = i < lives;
}
var hearts = '';
for (var i = 0; i < lives; i++) hearts += '♥';
livesTxt.setText(hearts);
waveTxt.setText('Wave ' + wave);
}
// Helper: spawn aliens in grid
function spawnAliens() {
aliens = [];
var totalWidth = (ALIEN_COLS - 1) * ALIEN_SPACING_X;
var startX = (GAME_WIDTH - totalWidth) / 2;
for (var row = 0; row < ALIEN_ROWS; row++) {
for (var col = 0; col < ALIEN_COLS; col++) {
var alien = new Alien();
alien.x = startX + col * ALIEN_SPACING_X;
alien.y = ALIEN_START_Y + row * ALIEN_SPACING_Y;
alien.row = row;
alien.col = col;
alien.direction = moveDir;
alien.speed = alienSpeed;
aliens.push(alien);
game.addChild(alien);
}
}
}
// Helper: start new wave
function startWave() {
alienSpeed = ALIEN_BASE_SPEED + (wave - 1) * ALIEN_SPEEDUP_PER_WAVE;
spawnAliens();
moveDir = 1;
moveDownPending = false;
waveTxt.setText('Wave ' + wave);
}
// Helper: reset game
function resetGame() {
// Remove all objects
for (var i = 0; i < aliens.length; i++) aliens[i].destroy();
for (var i = 0; i < playerBullets.length; i++) playerBullets[i].destroy();
for (var i = 0; i < alienBullets.length; i++) alienBullets[i].destroy();
for (var i = 0; i < powerups.length; i++) powerups[i].destroy();
aliens = [];
playerBullets = [];
alienBullets = [];
powerups = [];
wave = 1;
score = 0;
lives = 3;
gameOver = false;
youWin = false;
updateGUI();
// Reset player
if (player) player.destroy();
player = new Player();
player.x = GAME_WIDTH / 2;
player.y = PLAYER_Y;
game.addChild(player);
startWave();
}
// Helper: show game over
function triggerGameOver() {
if (gameOver) return;
gameOver = true;
LK.getSound('loseLife').play();
LK.effects.flashScreen(0xff0000, 1200);
LK.showGameOver();
}
// Helper: show win
function triggerYouWin() {
if (youWin) return;
youWin = true;
LK.effects.flashScreen(0x00ff00, 1200);
LK.showYouWin();
}
// Helper: spawn powerup
function spawnPowerup() {
var p = new Powerup();
p.x = 200 + Math.random() * (GAME_WIDTH - 400);
p.y = 200;
powerups.push(p);
game.addChild(p);
}
// --- Start Screen Implementation ---
var startScreenOverlay = new Container();
var startBg = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2,
y: GAME_HEIGHT / 2,
scaleX: 16,
scaleY: 22,
tint: 0x000010
});
startScreenOverlay.addChild(startBg);
var titleTxt = new Text2('SPACE INVADERS', {
size: 220,
fill: 0xFFFF00
});
titleTxt.anchor.set(0.5, 0.5);
titleTxt.x = GAME_WIDTH / 2;
titleTxt.y = GAME_HEIGHT / 2 - 320;
startScreenOverlay.addChild(titleTxt);
// Decorative player ship (centered, above button)
var decorPlayer = LK.getAsset('playerShip', {
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2,
y: GAME_HEIGHT / 2 - 80,
scaleX: 1.2,
scaleY: 1.2
});
startScreenOverlay.addChild(decorPlayer);
// Decorative powerups (left and right of player ship)
var decorPowerupLeft = LK.getAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2 - 220,
y: GAME_HEIGHT / 2 - 80,
scaleX: 0.9,
scaleY: 0.9
});
var decorPowerupRight = LK.getAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2 + 220,
y: GAME_HEIGHT / 2 - 80,
scaleX: 0.9,
scaleY: 0.9
});
startScreenOverlay.addChild(decorPowerupLeft);
startScreenOverlay.addChild(decorPowerupRight);
// Start button
var startBtn = new Text2('Start', {
size: 140,
fill: 0x00FFCC,
background: 0x222222,
padding: 60,
radius: 60
});
startBtn.anchor.set(0.5, 0.5);
startBtn.x = GAME_WIDTH / 2;
startBtn.y = GAME_HEIGHT / 2 + 120;
startScreenOverlay.addChild(startBtn);
// Start button press handler
startBtn.down = function (x, y, obj) {
if (!gameStarted) {
gameStarted = true;
startScreenOverlay.visible = false;
resetGame();
LK.playMusic('arcadebg', {
fade: {
start: 0,
end: 1,
duration: 1200
}
});
}
};
// Decorative aliens removed from start screen
var creditsTxt = new Text2('by FRVR', {
size: 80,
fill: 0x00FFCC
});
creditsTxt.anchor.set(0.5, 0.5);
creditsTxt.x = GAME_WIDTH / 2;
creditsTxt.y = GAME_HEIGHT / 2 + 320;
startScreenOverlay.addChild(creditsTxt);
// No aliens on start screen!
game.addChild(startScreenOverlay);
// Ensure no aliens are deleted from the game on the start screen
for (var i = 0; i < aliens.length; i++) {
if (aliens[i]) {
aliens[i].destroy();
}
}
aliens = [];
var gameStarted = false;
// Only allow tap to start when on start screen
game.down = function (x, y, obj) {
if (gameOver || youWin) return;
// Check if touch is on player ship
if (player && y > player.y - player.height / 2 && y < player.y + player.height / 2 && x > player.x - player.width / 2 && x < player.x + player.width / 2) {
draggingPlayer = true;
dragOffsetX = x - player.x;
} else {
// Tap elsewhere: do nothing (auto-shoot enabled)
}
};
// --- End Start Screen Implementation ---
// Drag-to-move and tap-to-shoot controls
var draggingPlayer = false;
var dragOffsetX = 0;
game.down = function (x, y, obj) {
if (gameOver || youWin) return;
// Check if touch is on player ship
if (player && y > player.y - player.height / 2 && y < player.y + player.height / 2 && x > player.x - player.width / 2 && x < player.x + player.width / 2) {
draggingPlayer = true;
dragOffsetX = x - player.x;
} else {
// Tap elsewhere: do nothing (auto-shoot enabled)
}
};
game.move = function (x, y, obj) {
if (draggingPlayer && !gameOver && !youWin) {
// Clamp player within screen bounds
var minX = player.width / 2;
var maxX = GAME_WIDTH - player.width / 2;
var newX = Math.max(minX, Math.min(x - dragOffsetX, maxX));
player.x = newX;
}
};
game.up = function (x, y, obj) {
draggingPlayer = false;
};
// Main game loop
game.update = function () {
if (gameOver || youWin) return;
// Player auto-shooting
if (player && !player.canShoot) {
player.shootCooldown--;
if (player.shootCooldown <= 0) player.canShoot = true;
}
if (player && player.canShoot) {
player.shoot();
}
// Powerup timer
if (player && player.poweredUp) {
player.powerupTimer--;
if (player.powerupTimer <= 0) {
player.poweredUp = false;
}
}
// Invincibility timer
if (player && player.invincible) {
player.invincibleTimer--;
// Flicker effect
player.alpha = LK.ticks % 10 < 5 ? 0.4 : 1;
if (player.invincibleTimer <= 0) {
player.invincible = false;
player.alpha = 1;
}
}
// Move aliens
var leftmost = GAME_WIDTH,
rightmost = 0,
bottommost = 0;
for (var i = 0; i < aliens.length; i++) {
var a = aliens[i];
if (!a.alive) continue;
a.x += moveDir * alienSpeed;
if (a.x - a.width / 2 < leftmost) leftmost = a.x - a.width / 2;
if (a.x + a.width / 2 > rightmost) rightmost = a.x + a.width / 2;
if (a.y + a.height / 2 > bottommost) bottommost = a.y + a.height / 2;
}
// Change direction if at edge
if (!moveDownPending && (leftmost < ALIEN_X_MARGIN || rightmost > GAME_WIDTH - ALIEN_X_MARGIN)) {
moveDir *= -1;
moveDownPending = true;
}
if (moveDownPending) {
for (var i = 0; i < aliens.length; i++) {
var a = aliens[i];
if (!a.alive) continue;
a.y += ALIEN_MOVE_DOWN;
}
moveDownPending = false;
}
// Aliens shoot randomly, but only bottom-most alive in each column can shoot
var bottomAliens = {};
for (var i = 0; i < aliens.length; i++) {
var a = aliens[i];
if (!a.alive) continue;
// Track the bottom-most alive alien in each column
if (!bottomAliens[a.col] || a.y > bottomAliens[a.col].y) {
bottomAliens[a.col] = a;
}
}
// Only allow a few (e.g. 2) random bottom aliens to shoot per tick
var shootableAliens = [];
for (var col in bottomAliens) {
shootableAliens.push(bottomAliens[col]);
}
var maxShooters = 2;
for (var s = 0; s < maxShooters && shootableAliens.length > 0; s++) {
var idx = Math.floor(Math.random() * shootableAliens.length);
var a = shootableAliens[idx];
if (Math.random() < ALIEN_SHOOT_CHANCE + 0.001 * wave) {
a.shoot();
}
shootableAliens.splice(idx, 1);
}
// 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 < -b.height) {
b.destroy();
playerBullets.splice(i, 1);
continue;
}
// Check collision with aliens
for (var j = 0; j < aliens.length; j++) {
var a = aliens[j];
if (!a.alive) continue;
if (b.intersects(a)) {
a.alive = false;
a.visible = false;
b.destroy();
playerBullets.splice(i, 1);
LK.getSound('alienExplode').play();
score += 10;
updateGUI();
break;
}
}
}
// Update alien bullets
for (var i = alienBullets.length - 1; i >= 0; i--) {
var b = alienBullets[i];
b.update();
// Remove if off screen
if (b.y > GAME_HEIGHT + b.height) {
b.destroy();
alienBullets.splice(i, 1);
continue;
}
// Check collision with player
if (player && !player.invincible && b.intersects(player)) {
b.destroy();
alienBullets.splice(i, 1);
// Lose life
lives--;
updateGUI();
LK.getSound('hit').play();
if (lives <= 0) {
triggerGameOver();
return;
} else {
player.setInvincible(90); // 1.5s invincibility
}
}
}
// Update powerups
for (var i = powerups.length - 1; i >= 0; i--) {
var p = powerups[i];
p.update();
if (p.y > GAME_HEIGHT + p.height) {
p.destroy();
powerups.splice(i, 1);
continue;
}
if (p.intersects(player)) {
LK.getSound('powerup').play();
// Only restore up to 3 hearts
if (lives < 3) {
lives = 3;
updateGUI();
}
p.destroy();
powerups.splice(i, 1);
}
}
// Powerup spawn
if (powerups.length === 0 && Math.random() < POWERUP_CHANCE) {
spawnPowerup();
}
// Check if aliens reach player
for (var i = 0; i < aliens.length; i++) {
var a = aliens[i];
if (!a.alive) continue;
if (player && a.y + a.height / 2 >= player.y - player.height / 2) {
triggerGameOver();
return;
}
}
// Next wave if all aliens dead
var anyAlive = false;
for (var i = 0; i < aliens.length; i++) {
if (aliens[i].alive) {
anyAlive = true;
break;
}
}
if (!anyAlive) {
wave++;
if (wave > 10) {
triggerYouWin();
return;
}
startWave();
}
};
// Clean up on game over or reset
game.on('destroy', function () {
LK.stopMusic();
});