/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0
});
/****
* Classes
****/
// Boss ship class
var BossShip = Container.expand(function () {
var self = Container.call(this);
// Attach boss asset (use enemyShip but much larger, and red tint)
var boss = self.attachAsset('enemyShip', {
width: 480,
height: 480,
color: 0xff0000,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5
});
self.radius = 240;
self.isAlive = true;
self.health = 40;
self.maxHealth = 40;
self.lastY = undefined;
// Boss always at top center, stationary
self.x = GAME_WIDTH / 2;
self.y = 340;
// Boss update
self.update = function () {
// Boss remains stationary at top center
// Unique Skill 1: Radial bullet spread every 120 ticks
if (LK.ticks % 120 === 0) {
var numBullets = 14;
for (var i = 0; i < numBullets; i++) {
var angle = 2 * Math.PI / numBullets * i;
var bullet = new EnemyBullet();
bullet.x = self.x;
bullet.y = self.y + 220;
// Give bullet a custom direction
bullet.customVX = Math.cos(angle) * 13;
bullet.customVY = Math.sin(angle) * 13;
bullet.update = function () {
this.x += this.customVX;
this.y += this.customVY;
};
enemyBullets.push(bullet);
game.addChild(bullet);
}
}
// Unique Skill 2: Laser attack every 300 ticks (fires a fast straight bullet down the center)
if (LK.ticks % 300 === 0) {
var laser = new EnemyBullet();
laser.x = self.x;
laser.y = self.y + 260;
laser.speed = 38;
laser.radius = 60;
// Make it visually larger
var asset = laser.children[0];
if (asset) {
asset.width = 90;
asset.height = 340;
}
enemyBullets.push(laser);
game.addChild(laser);
}
// Unique Skill 3: Area attack (big slow bullet straight down) every 180 ticks
if (LK.ticks % 180 === 0) {
var area = new EnemyBullet();
area.x = self.x;
area.y = self.y + 240;
area.speed = 10;
area.radius = 120;
var asset = area.children[0];
if (asset) {
asset.width = 220;
asset.height = 120;
}
enemyBullets.push(area);
game.addChild(area);
}
};
// Flash on hit
self.flash = function () {
tween(boss, {
tint: 0xffffff
}, {
duration: 80,
onFinish: function onFinish() {
tween(boss, {
tint: 0xff0000
}, {
duration: 120
});
}
});
};
return self;
});
// Enemy bullet class
var EnemyBullet = Container.expand(function () {
var self = Container.call(this);
// Attach bullet asset (red, 24x48)
var bullet = self.attachAsset('enemyBullet', {
width: 24,
height: 48,
color: 0xff4444,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 14;
self.radius = 12;
self.update = function () {
self.y += self.speed;
};
return self;
});
// Enemy ship class
var EnemyShip = Container.expand(function () {
var self = Container.call(this);
// Attach enemy asset (ellipse, magenta, 220x220)
var enemy = self.attachAsset('enemyShip', {
width: 220,
height: 220,
color: 0xff33cc,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5
});
self.radius = 110;
self.speed = 4 + Math.random() * 2; // Vary speed
self.dirX = (Math.random() - 0.5) * 2; // Slight horizontal drift
// For movement patterns
self.waveOffset = Math.random() * Math.PI * 2;
// For collision
self.isAlive = true;
// Update per frame
self.update = function () {
// Move down, with a sine wave pattern
self.y += self.speed;
self.x += Math.sin(LK.ticks / 30 + self.waveOffset) * 2 + self.dirX;
// Prevent enemy from moving outside the horizontal screen area
var minX = 60;
var maxX = 2048 - 60;
if (self.x < minX) {
self.x = minX;
self.dirX = Math.abs(self.dirX); // bounce right
}
if (self.x > maxX) {
self.x = maxX;
self.dirX = -Math.abs(self.dirX); // bounce left
}
};
// Flash on hit
self.flash = function () {
tween(enemy, {
tint: 0xffffff
}, {
duration: 80,
onFinish: function onFinish() {
tween(enemy, {
tint: 0xff33cc
}, {
duration: 120
});
}
});
};
return self;
});
// Player bullet class
var PlayerBullet = Container.expand(function () {
var self = Container.call(this);
// Attach bullet asset (yellow, 30x60)
var bullet = self.attachAsset('playerBullet', {
width: 30,
height: 60,
color: 0xffe066,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
self.speed = -22;
self.radius = 15;
self.update = function () {
self.y += self.speed;
};
return self;
});
// Player spaceship class
var PlayerShip = Container.expand(function () {
var self = Container.call(this);
// Attach ship asset (box, blue, 220x220)
var ship = self.attachAsset('playerShip', {
width: 220,
height: 220,
color: 0x3399ff,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
// Ship properties
self.radius = 110; // for collision
self.isAlive = true;
// Ship flash effect on hit
self.flash = function () {
tween(ship, {
tint: 0xff3333
}, {
duration: 100,
onFinish: function onFinish() {
tween(ship, {
tint: 0x3399ff
}, {
duration: 200
});
}
});
};
return self;
});
// Powerup class
var PowerUp = Container.expand(function () {
var self = Container.call(this);
// Attach powerup asset (green ellipse, 120x120)
var pu = self.attachAsset('powerUp', {
width: 120,
height: 120,
color: 0x44ff88,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5
});
self.radius = 60;
self.speed = 6;
self.update = function () {
self.y += self.speed;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000010
});
/****
* Game Code
****/
// --- Start Screen Overlay ---
var startScreenOverlay = new Container();
// Large main title: "Space Wars"
var mainTitle = new Text2('Space Wars', {
size: 220,
fill: 0xFFD700,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
mainTitle.anchor.set(0.5, 0);
mainTitle.x = GAME_WIDTH / 2;
mainTitle.y = 120;
// Subtitle: "Galactic Showdown"
var subTitle = new Text2('Galactic Showdown', {
size: 110,
fill: 0x99e6ff,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
subTitle.anchor.set(0.5, 0);
subTitle.x = GAME_WIDTH / 2;
subTitle.y = mainTitle.y + mainTitle.height + 20;
// --- SHOP MENU UI ---
// Shop state
var shopOpen = false;
var shopOverlay = new Container();
shopOverlay.visible = false;
var shopTitle = new Text2('Costume Shop', {
size: 120,
fill: 0xFFD700,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
shopTitle.anchor.set(0.5, 0);
shopTitle.x = GAME_WIDTH / 2;
shopTitle.y = 120;
shopOverlay.addChild(shopTitle);
// Define costumes (id, name, price, asset)
var costumes = [{
id: "default",
name: "Classic",
price: 0,
asset: "playerShip"
}, {
id: "red",
name: "Red Comet",
price: 200,
asset: "enemyShip"
}, {
id: "gold",
name: "Gold Star",
price: 500,
asset: "playerBullet"
}];
// Persistent unlocks
if (!storage.unlockedCostumes) storage.unlockedCostumes = {
"default": true
};
if (!storage.selectedCostume) storage.selectedCostume = "default";
// Shop navigation state
var shopIndex = 0;
function updateShopUI() {
// Remove old preview if any
if (shopOverlay.costumePreview) {
shopOverlay.removeChild(shopOverlay.costumePreview);
shopOverlay.costumePreview = null;
}
var c = costumes[shopIndex];
var preview = LK.getAsset(c.asset, {
width: 320,
height: 320,
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2,
y: 520
});
shopOverlay.addChild(preview);
shopOverlay.costumePreview = preview;
// Update name/price
shopNameTxt.setText(c.name);
if (c.price === 0) {
shopPriceTxt.setText("Unlocked");
} else if (storage.unlockedCostumes[c.id]) {
shopPriceTxt.setText("Unlocked");
} else {
shopPriceTxt.setText("Price: " + c.price + " pts");
}
// Show select/unlock button
if (storage.unlockedCostumes[c.id]) {
shopActionBtnText.setText(storage.selectedCostume === c.id ? "Selected" : "Select");
} else {
shopActionBtnText.setText("Unlock");
}
// Hide select if already selected
shopActionBtn.alpha = storage.selectedCostume === c.id && storage.unlockedCostumes[c.id] ? 0.5 : 1;
}
// Costume name
var shopNameTxt = new Text2('', {
size: 80,
fill: 0xffffff,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
shopNameTxt.anchor.set(0.5, 0);
shopNameTxt.x = GAME_WIDTH / 2;
shopNameTxt.y = 900;
shopOverlay.addChild(shopNameTxt);
// Costume price
var shopPriceTxt = new Text2('', {
size: 60,
fill: 0xFFD700,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
shopPriceTxt.anchor.set(0.5, 0);
shopPriceTxt.x = GAME_WIDTH / 2;
shopPriceTxt.y = 1020;
shopOverlay.addChild(shopPriceTxt);
// Action button (Unlock/Select)
var shopActionBtn = LK.getAsset('playerBullet', {
width: 420,
height: 110,
color: 0x00ccff,
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2,
y: 1200
});
var shopActionBtnText = new Text2('', {
size: 70,
fill: 0xffffff,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
shopActionBtnText.anchor.set(0.5, 0.5);
shopActionBtnText.x = shopActionBtn.x;
shopActionBtnText.y = shopActionBtn.y;
shopOverlay.addChild(shopActionBtn);
shopOverlay.addChild(shopActionBtnText);
// Left/right nav buttons
var shopLeftBtn = LK.getAsset('enemyBullet', {
width: 100,
height: 100,
color: 0xFFD700,
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2 - 300,
y: 520
});
var shopRightBtn = LK.getAsset('enemyBullet', {
width: 100,
height: 100,
color: 0xFFD700,
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2 + 300,
y: 520
});
shopOverlay.addChild(shopLeftBtn);
shopOverlay.addChild(shopRightBtn);
// Close button
var shopCloseBtn = LK.getAsset('playerBullet', {
width: 180,
height: 80,
color: 0x888888,
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2,
y: 1450
});
var shopCloseBtnText = new Text2('Close', {
size: 50,
fill: 0xffffff,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
shopCloseBtnText.anchor.set(0.5, 0.5);
shopCloseBtnText.x = shopCloseBtn.x;
shopCloseBtnText.y = shopCloseBtn.y;
shopOverlay.addChild(shopCloseBtn);
shopOverlay.addChild(shopCloseBtnText);
// Shop button on start screen
var shopBtn = LK.getAsset('playerBullet', {
width: 320,
height: 100,
color: 0xFFD700,
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2,
y: GAME_HEIGHT / 2 + 320
});
var shopBtnText = new Text2('SHOP', {
size: 70,
fill: 0x000000,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
shopBtnText.anchor.set(0.5, 0.5);
shopBtnText.x = shopBtn.x;
shopBtnText.y = shopBtn.y;
// Shop button handler
shopBtn.down = function (x, y, obj) {
if (shopOpen) return;
shopOpen = true;
shopOverlay.visible = true;
LK.gui.center.addChild(shopOverlay);
updateShopUI();
// Hide start overlay
if (startScreenOverlay.parent) startScreenOverlay.visible = false;
};
// Shop close handler
shopCloseBtn.down = function (x, y, obj) {
shopOpen = false;
shopOverlay.visible = false;
if (shopOverlay.parent) LK.gui.center.removeChild(shopOverlay);
if (startScreenOverlay && !gameStarted) startScreenOverlay.visible = true;
};
// Shop left/right nav
shopLeftBtn.down = function (x, y, obj) {
shopIndex = (shopIndex + costumes.length - 1) % costumes.length;
updateShopUI();
};
shopRightBtn.down = function (x, y, obj) {
shopIndex = (shopIndex + 1) % costumes.length;
updateShopUI();
};
// Shop action (unlock/select)
shopActionBtn.down = function (x, y, obj) {
var c = costumes[shopIndex];
if (storage.unlockedCostumes[c.id]) {
// Select
storage.selectedCostume = c.id;
updateShopUI();
} else {
// Unlock if enough score
var score = LK.getScore();
if (score >= c.price) {
LK.setScore(score - c.price);
scoreTxt.setText(LK.getScore());
storage.unlockedCostumes[c.id] = true;
storage.selectedCostume = c.id;
updateShopUI();
} else {
// Not enough points, flash price red
shopPriceTxt.setText("Need " + (c.price - score) + " more!");
tween(shopPriceTxt, {
fill: 0xff0000
}, {
duration: 200,
onFinish: function onFinish() {
shopPriceTxt.setText("Price: " + c.price + " pts");
}
});
}
}
};
// Add to overlay
startScreenOverlay.addChild(mainTitle);
startScreenOverlay.addChild(subTitle);
startScreenOverlay.addChild(shopBtn);
startScreenOverlay.addChild(shopBtnText);
// Play button (large, centered)
var playBtn = LK.getAsset('playerBullet', {
width: 520,
height: 140,
color: 0x00ccff,
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2,
y: GAME_HEIGHT / 2 + 120
});
var playBtnText = new Text2('PLAY', {
size: 100,
fill: 0xffffff,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
playBtnText.anchor.set(0.5, 0.5);
playBtnText.x = playBtn.x;
playBtnText.y = playBtn.y;
startScreenOverlay.addChild(playBtn);
startScreenOverlay.addChild(playBtnText);
// Animate main title in
mainTitle.alpha = 0;
tween(mainTitle, {
alpha: 1
}, {
duration: 700,
easing: tween.cubicOut
});
// Animate subtitle in (slightly delayed)
subTitle.alpha = 0;
tween(subTitle, {
alpha: 1
}, {
duration: 700,
delay: 200,
easing: tween.cubicOut
});
// Animate play button in
playBtn.alpha = 0;
playBtnText.alpha = 0;
tween(playBtn, {
alpha: 1
}, {
duration: 700,
easing: tween.cubicOut
});
tween(playBtnText, {
alpha: 1
}, {
duration: 900,
easing: tween.cubicOut
});
// Add to overlay
startScreenOverlay.addChild(mainTitle);
startScreenOverlay.addChild(subTitle);
startScreenOverlay.addChild(playBtn);
startScreenOverlay.addChild(playBtnText);
LK.gui.center.addChild(startScreenOverlay);
// Block gameplay until Play is pressed
var gameStarted = false;
function hideStartScreen() {
if (startScreenOverlay.parent) {
LK.gui.center.removeChild(startScreenOverlay);
}
gameStarted = true;
}
// Play button interaction
playBtn.down = function (x, y, obj) {
if (gameStarted) return;
// Animate out
tween(startScreenOverlay, {
alpha: 0
}, {
duration: 400,
easing: tween.cubicIn,
onFinish: function onFinish() {
// Start background music only after Play is clicked
LK.playMusic('sapce');
hideStartScreen();
}
});
};
playBtn.move = function (x, y, obj) {};
playBtn.up = function (x, y, obj) {};
// Block all gameplay input until started
var origGameMove = game.move;
var origGameDown = game.down;
var origGameUp = game.up;
game.move = function (x, y, obj) {
if (!gameStarted) return;
if (origGameMove) origGameMove(x, y, obj);
};
game.down = function (x, y, obj) {
if (!gameStarted) return;
if (origGameDown) origGameDown(x, y, obj);
};
game.up = function (x, y, obj) {
if (!gameStarted) return;
if (origGameUp) origGameUp(x, y, obj);
};
// --- Volume Control UI on Pause ---
var volumeSlider = null;
var volumeLabel = null;
var lastPauseState = false;
// Helper to create volume slider UI
function showVolumeSlider() {
if (!LK.isPaused) return;
if (volumeSlider) return; // Already shown
// Create label
volumeLabel = new Text2('Music Volume', {
size: 60,
fill: 0xFFFFFF
});
volumeLabel.anchor.set(0.5, 0.5);
volumeLabel.x = GAME_WIDTH / 2;
volumeLabel.y = GAME_HEIGHT / 2 - 120;
LK.gui.center.addChild(volumeLabel);
// Create slider background
var sliderBg = LK.getAsset('playerBullet', {
width: 500,
height: 32,
color: 0x222222,
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2,
y: GAME_HEIGHT / 2
});
LK.gui.center.addChild(sliderBg);
// Create slider knob
volumeSlider = LK.getAsset('enemyBullet', {
width: 60,
height: 60,
color: 0xFFD700,
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2,
y: GAME_HEIGHT / 2
});
LK.gui.center.addChild(volumeSlider);
// Set initial knob position based on current music volume
var currentVolume = LK.getMusicVolume ? LK.getMusicVolume() : 1;
var minX = GAME_WIDTH / 2 - 220;
var maxX = GAME_WIDTH / 2 + 220;
volumeSlider.x = minX + (maxX - minX) * currentVolume;
// Drag logic
var dragging = false;
volumeSlider.down = function (x, y, obj) {
dragging = true;
};
volumeSlider.up = function (x, y, obj) {
dragging = false;
};
// Move handler for slider
volumeSlider.move = function (x, y, obj) {
if (!dragging) return;
var minX = GAME_WIDTH / 2 - 220;
var maxX = GAME_WIDTH / 2 + 220;
var px = Math.max(minX, Math.min(maxX, x));
volumeSlider.x = px;
// Calculate volume (0..1)
var vol = (px - minX) / (maxX - minX);
if (vol < 0) vol = 0;
if (vol > 1) vol = 1;
LK.setMusicVolume ? LK.setMusicVolume(vol) : null;
};
// Attach move/up to gui.center for mobile, but only if game is paused
LK.gui.center.move = function (x, y, obj) {
if (LK.isPaused && dragging) {
volumeSlider.move(x, y, obj);
}
};
LK.gui.center.up = function (x, y, obj) {
if (LK.isPaused) {
dragging = false;
}
};
}
// Helper to remove volume slider UI
function hideVolumeSlider() {
if (volumeSlider) {
LK.gui.center.removeChild(volumeSlider);
volumeSlider = null;
}
if (volumeLabel) {
LK.gui.center.removeChild(volumeLabel);
volumeLabel = null;
}
// Remove slider background if present
var children = LK.gui.center.children;
for (var i = children.length - 1; i >= 0; i--) {
var c = children[i];
if (c && c.width === 500 && c.height === 32 && c.color === 0x222222) {
LK.gui.center.removeChild(c);
}
}
// Remove move/up handlers
LK.gui.center.move = null;
LK.gui.center.up = null;
}
// Listen for pause/resume to show/hide slider
LK.on('pause', function () {
showVolumeSlider();
});
LK.on('resume', function () {
hideVolumeSlider();
});
var backgroundImg = LK.getAsset('background', {
width: GAME_WIDTH,
height: GAME_HEIGHT,
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.addChildAt(backgroundImg, 0); // Always at the back layer
// Play sapce music at game start
// (Music will be started after Play is clicked, see playBtn.down handler)
// LK.playMusic('sapce');
// Health bar asset (simple red bar, 600x40)
// Game constants
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var PLAYER_START_X = GAME_WIDTH / 2;
var PLAYER_START_Y = GAME_HEIGHT - 350;
// Level system variables
var currentLevel = 1;
var enemiesPerLevel = 6; // base number of enemies for level 1
var enemiesToSpawn = enemiesPerLevel;
var enemiesKilledThisLevel = 0;
var enemyScoreBase = 10; // base score for level 1
var enemyScoreThisLevel = enemyScoreBase;
// Game state
var player;
var enemies = [];
var playerBullets = [];
var enemyBullets = [];
var powerUps = [];
var dragNode = null;
var lastScore = 0;
var scoreTxt;
var powerUpActive = false;
var powerUpTimer = 0;
var enemySpawnTimer = 0;
var enemyFireTimer = 0;
var powerUpSpawnTimer = 0;
var gameOver = false;
// Boss state
var bossActive = false;
var bossShip = null;
var bossHealthBar = null;
// Score display
scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Import storage plugin for persistent high score
// High score display (top right, avoid top right 100x100)
var highScore = storage.highScore || 0;
var highScoreTxt = new Text2('High: ' + highScore, {
size: 60,
fill: 0xFFD700
});
highScoreTxt.anchor.set(1, 0);
highScoreTxt.x = GAME_WIDTH - 120;
highScoreTxt.y = 0;
LK.gui.top.addChild(highScoreTxt);
// Health system
var playerHealth = 5;
// Heart icon assets (use 5 separate heart images for live updating)
var heartIcons = [];
var heartAssetNames = ['heart1', 'heart2', 'heart3', 'heart4', 'heart5'];
for (var i = 0; i < 5; i++) {
var heart = LK.getAsset(heartAssetNames[i], {
width: 60,
height: 60,
anchorX: 0.5,
anchorY: 0.5,
x: 200 + i * 70,
y: 30
});
LK.gui.top.addChild(heart);
heartIcons.push(heart);
}
// Helper to update heart icons based on playerHealth
function updateHearts() {
for (var i = 0; i < heartIcons.length; i++) {
heartIcons[i].alpha = i < playerHealth ? 1 : 0.2;
}
}
updateHearts();
// Level display (top left, avoid top left 100x100)
var levelTxt = new Text2('Level 1', {
size: 40,
fill: 0xFFD700
});
levelTxt.anchor.set(0, 0);
levelTxt.x = 180;
levelTxt.y = 100;
LK.gui.top.addChild(levelTxt);
// Enemies left display (top left, avoid top left 100x100)
var enemiesLeftTxt = new Text2('Enemies: 0', {
size: 40,
fill: 0xFF8888
});
enemiesLeftTxt.anchor.set(0, 0);
enemiesLeftTxt.x = 180;
enemiesLeftTxt.y = 60;
LK.gui.top.addChild(enemiesLeftTxt);
// Initialize player
player = new PlayerShip();
// Apply selected costume if not default
if (storage.selectedCostume && storage.selectedCostume !== "default") {
var costume = null;
for (var i = 0; i < costumes.length; i++) {
if (costumes[i].id === storage.selectedCostume) costume = costumes[i];
}
if (costume) {
// Remove old asset and attach new
if (player.children.length > 0) player.removeChild(player.children[0]);
var costumeAsset = player.attachAsset(costume.asset, {
width: 220,
height: 220,
anchorX: 0.5,
anchorY: 0.5
});
}
}
player.x = PLAYER_START_X;
player.y = PLAYER_START_Y;
game.addChild(player);
// Touch/move controls
function handleMove(x, y, obj) {
if (dragNode && player.isAlive) {
// Clamp to game area, avoid top 100px (menu)
var px = Math.max(60, Math.min(GAME_WIDTH - 60, x));
var py = Math.max(200, Math.min(GAME_HEIGHT - 60, y));
dragNode.x = px;
dragNode.y = py;
}
}
game.move = handleMove;
game.down = function (x, y, obj) {
if (player.isAlive) {
dragNode = player;
handleMove(x, y, obj);
}
};
game.up = function (x, y, obj) {
dragNode = null;
};
// Helper: collision check (circle vs circle)
function circlesIntersect(a, b) {
var dx = a.x - b.x;
var dy = a.y - b.y;
var dist = Math.sqrt(dx * dx + dy * dy);
return dist < a.radius + b.radius;
}
// Helper: spawn enemy
function spawnEnemy() {
var enemy = new EnemyShip();
// Spawn in random horizontal position, avoid edges
enemy.x = 120 + Math.random() * (GAME_WIDTH - 240);
enemy.y = -100;
enemies.push(enemy);
game.addChild(enemy);
}
// Helper: spawn enemy bullet
function spawnEnemyBullet(enemy) {
var bullet = new EnemyBullet();
bullet.x = enemy.x;
bullet.y = enemy.y + 60;
enemyBullets.push(bullet);
game.addChild(bullet);
}
// Helper: spawn player bullet
function spawnPlayerBullet() {
var bullet = new PlayerBullet();
bullet.x = player.x;
bullet.y = player.y - 80;
playerBullets.push(bullet);
game.addChild(bullet);
}
// Helper: spawn powerup
function spawnPowerUp() {
var pu = new PowerUp();
pu.x = 120 + Math.random() * (GAME_WIDTH - 240);
pu.y = -60;
powerUps.push(pu);
game.addChild(pu);
}
// Powerup effect: double shot
function activatePowerUp() {
powerUpActive = true;
powerUpTimer = LK.ticks + 360; // 6 seconds at 60fps
// Flash player ship green
tween(player, {
tint: 0x44ff88
}, {
duration: 200,
onFinish: function onFinish() {
tween(player, {
tint: 0x3399ff
}, {
duration: 200
});
}
});
}
// Main game update
game.update = function () {
if (!gameStarted) {
return;
}
if (!player.isAlive) {
return;
}
// --- Player auto-fire ---
if (LK.ticks % (powerUpActive ? 7 : 14) === 0) {
if (powerUpActive) {
// Double shot: two bullets
var b1 = new PlayerBullet();
b1.x = player.x - 36;
b1.y = player.y - 80;
playerBullets.push(b1);
game.addChild(b1);
var b2 = new PlayerBullet();
b2.x = player.x + 36;
b2.y = player.y - 80;
playerBullets.push(b2);
game.addChild(b2);
} else {
spawnPlayerBullet();
}
}
// --- Enemy spawn ---
if (!bossActive && enemySpawnTimer <= LK.ticks && enemiesToSpawn > 0) {
spawnEnemy();
enemiesToSpawn--;
// Escalate spawn rate
var minDelay = 24,
maxDelay = 60;
var delay = Math.max(minDelay, maxDelay - Math.floor(LK.getScore() / 10) * 4);
enemySpawnTimer = LK.ticks + delay;
// Update enemies left UI
enemiesLeftTxt.setText('Enemies: ' + (enemiesToSpawn + enemies.length));
}
// --- Enemy fire ---
if (!bossActive && enemyFireTimer <= LK.ticks && enemies.length > 0) {
// Pick random enemy to fire
var idx = Math.floor(Math.random() * enemies.length);
if (enemies[idx]) {
spawnEnemyBullet(enemies[idx]);
}
// Reduce fire rate: increase min and max fire delay for better balance
var minFire = 36,
// was 18
maxFire = 100; // was 50
var fireDelay = Math.max(minFire, maxFire - Math.floor(LK.getScore() / 10) * 3);
enemyFireTimer = LK.ticks + fireDelay;
}
// --- Powerup spawn ---
if (powerUpSpawnTimer <= LK.ticks) {
if (Math.random() < 0.18) {
// ~18% chance
spawnPowerUp();
}
powerUpSpawnTimer = LK.ticks + 300 + Math.floor(Math.random() * 200);
}
// --- Powerup timer ---
if (powerUpActive && LK.ticks > powerUpTimer) {
powerUpActive = false;
}
// --- 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 (e.isAlive && circlesIntersect(b, e)) {
e.isAlive = false;
b.destroy();
playerBullets.splice(i, 1);
// --- Quick fire burst effect for enemy hit ---
var fireBurst = LK.getAsset('enemyBullet', {
width: 120,
height: 120,
color: 0xffa200,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5,
x: e.x,
y: e.y
});
fireBurst.alpha = 0.85;
fireBurst.scaleX = 1.0;
fireBurst.scaleY = 1.0;
game.addChild(fireBurst);
tween(fireBurst, {
tint: 0xfff200,
scaleX: 2.2,
scaleY: 2.2,
alpha: 0
}, {
duration: 120,
easing: tween.cubicOut,
onFinish: function onFinish() {
fireBurst.destroy();
}
});
e.destroy();
enemies.splice(j, 1);
// Score (scaled by level)
LK.setScore(LK.getScore() + enemyScoreThisLevel);
scoreTxt.setText(LK.getScore());
if (LK.getScore() > highScore) {
highScore = LK.getScore();
highScoreTxt.setText('High: ' + highScore);
storage.highScore = highScore;
}
// Track kills for level progression
enemiesKilledThisLevel++;
enemiesLeftTxt.setText('Enemies: ' + (enemiesToSpawn + enemies.length));
// If all enemies for this level are killed, immediately start a new level (endless progression)
if (enemiesKilledThisLevel >= enemiesPerLevel) {
// Boss fight every 5 levels
if ((currentLevel + 1) % 5 === 0) {
// Spawn boss
bossActive = true;
bossShip = new BossShip();
// BossShip class now sets its own x/y to top center and is stationary
game.addChild(bossShip);
// Add boss health bar
if (bossHealthBar) {
LK.gui.top.removeChild(bossHealthBar);
}
bossHealthBar = new Text2('Boss: ' + bossShip.health + '/' + bossShip.maxHealth, {
size: 90,
fill: 0xFF2222
});
bossHealthBar.anchor.set(0.5, 0);
bossHealthBar.x = GAME_WIDTH / 2;
bossHealthBar.y = 80;
LK.gui.top.addChild(bossHealthBar);
// Play boss music
LK.playMusic('boss');
// UI
levelTxt.setText('Boss Level ' + (currentLevel + 1));
enemiesLeftTxt.setText('Boss Fight!');
LK.effects.flashScreen(0xffcc00, 600);
} else {
// Next level! Endless progression
currentLevel++;
enemiesPerLevel = Math.floor(enemiesPerLevel * 1.25) + 1; // escalate number of enemies
enemiesToSpawn = enemiesPerLevel;
enemiesKilledThisLevel = 0;
enemyScoreThisLevel = enemyScoreBase * Math.pow(2, currentLevel - 1);
// Update UI
levelTxt.setText('Level ' + currentLevel);
enemiesLeftTxt.setText('Enemies: ' + (enemiesToSpawn + enemies.length));
// Optionally: flash screen or give feedback
LK.effects.flashScreen(0x00ffcc, 400);
}
}
break;
}
}
}
// --- Boss fight logic ---
if (bossActive && bossShip && bossShip.isAlive) {
bossShip.update();
// Boss health bar always at top center and visible only when boss is present
if (!bossHealthBar) {
bossHealthBar = new Text2('Boss: ' + bossShip.health + '/' + bossShip.maxHealth, {
size: 90,
fill: 0xFF2222
});
bossHealthBar.anchor.set(0.5, 0);
bossHealthBar.x = GAME_WIDTH / 2;
bossHealthBar.y = 80;
LK.gui.top.addChild(bossHealthBar);
} else {
bossHealthBar.x = GAME_WIDTH / 2;
bossHealthBar.y = 80;
bossHealthBar.setText('Boss: ' + bossShip.health + '/' + bossShip.maxHealth);
}
// Check player bullet collision with boss
for (var i = playerBullets.length - 1; i >= 0; i--) {
var b = playerBullets[i];
if (bossShip.isAlive && circlesIntersect(bossShip, b)) {
bossShip.health--;
bossShip.flash();
b.destroy();
playerBullets.splice(i, 1);
if (bossShip.health <= 0) {
bossShip.isAlive = false;
bossActive = false;
// Remove boss health bar
if (bossHealthBar) {
LK.gui.top.removeChild(bossHealthBar);
bossHealthBar = null;
}
// Resume sapce music after boss fight
LK.playMusic('sapce');
// Score for boss
LK.setScore(LK.getScore() + 100 * currentLevel);
scoreTxt.setText(LK.getScore());
if (LK.getScore() > highScore) {
highScore = LK.getScore();
highScoreTxt.setText('High: ' + highScore);
storage.highScore = highScore;
}
// Next level
currentLevel++;
enemiesPerLevel = Math.floor(enemiesPerLevel * 1.25) + 1;
enemiesToSpawn = enemiesPerLevel;
enemiesKilledThisLevel = 0;
enemyScoreThisLevel = enemyScoreBase * Math.pow(2, currentLevel - 1);
levelTxt.setText('Level ' + currentLevel);
enemiesLeftTxt.setText('Enemies: ' + enemiesPerLevel);
LK.effects.flashScreen(0x00ffcc, 600);
// Remove boss
bossShip.destroy();
bossShip = null;
}
break;
}
}
// Boss collision with player
if (player.isAlive && bossShip && bossShip.isAlive && circlesIntersect(bossShip, player)) {
bossShip.isAlive = false;
bossActive = false;
if (bossHealthBar) {
LK.gui.top.removeChild(bossHealthBar);
bossHealthBar = null;
}
// Resume sapce music after boss fight
LK.playMusic('sapce');
player.flash();
playerHealth--;
updateHearts();
LK.effects.flashScreen(0xff0000, 400);
if (playerHealth <= 0) {
player.isAlive = false;
tween(player, {
alpha: 0.2
}, {
duration: 400
});
LK.setTimeout(function () {
LK.showGameOver();
}, 900);
}
bossShip.destroy();
bossShip = null;
}
}
// --- Update enemy bullets ---
for (var i = enemyBullets.length - 1; i >= 0; i--) {
var eb = enemyBullets[i];
eb.update();
if (eb.y > GAME_HEIGHT + 80) {
eb.destroy();
enemyBullets.splice(i, 1);
continue;
}
// Check collision with player
if (player.isAlive && circlesIntersect(eb, player)) {
eb.destroy();
enemyBullets.splice(i, 1);
player.flash();
playerHealth--;
updateHearts();
LK.effects.flashScreen(0xff0000, 400);
if (playerHealth <= 0) {
// Game over
player.isAlive = false;
tween(player, {
alpha: 0.2
}, {
duration: 400
});
LK.setTimeout(function () {
LK.showGameOver();
}, 900);
break;
}
}
}
// --- Update enemies ---
for (var i = enemies.length - 1; i >= 0; i--) {
var e = enemies[i];
// Track lastY for crossing detection
if (typeof e.lastY === "undefined") {
e.lastY = e.y;
}
e.update();
// If enemy crosses into player's area (y > PLAYER_START_Y - 110, i.e. bottom of player ship)
if (e.lastY <= PLAYER_START_Y - 110 && e.y > PLAYER_START_Y - 110) {
// Only trigger if enemy is alive
if (e.isAlive && player.isAlive) {
e.isAlive = false;
// --- Fiery, intense explosion effect for enemy ship ---
var explosion = LK.getAsset('enemyShip', {
width: 260,
height: 260,
color: 0xfff200,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5,
x: e.x,
y: e.y
});
explosion.alpha = 0.95;
explosion.scaleX = 1.0;
explosion.scaleY = 1.0;
game.addChild(explosion);
tween(explosion, {
tint: 0xff6600,
scaleX: 2.7,
scaleY: 2.7,
alpha: 0.7
}, {
duration: 120,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(explosion, {
tint: 0xffffcc,
scaleX: 3.5,
scaleY: 3.5,
alpha: 0
}, {
duration: 220,
easing: tween.cubicIn,
onFinish: function onFinish() {
explosion.destroy();
}
});
}
});
tween(e, {}, {
duration: 120,
onFinish: function onFinish() {
e.destroy();
}
});
enemies.splice(i, 1);
playerHealth--;
updateHearts();
LK.effects.flashScreen(0xff0000, 400);
// Level progression if this was the last enemy for the level
enemiesLeftTxt.setText('Enemies: ' + (enemiesToSpawn + enemies.length));
if (enemiesKilledThisLevel + 1 >= enemiesPerLevel && enemiesToSpawn === 0 && enemies.length === 0) {
// Next level!
currentLevel++;
enemiesPerLevel = Math.floor(enemiesPerLevel * 1.25) + 1;
enemiesToSpawn = enemiesPerLevel;
enemiesKilledThisLevel = 0;
enemyScoreThisLevel = enemyScoreBase * Math.pow(2, currentLevel - 1);
// Update UI
levelTxt.setText('Level ' + currentLevel);
enemiesLeftTxt.setText('Enemies: ' + (enemiesToSpawn + enemies.length));
LK.effects.flashScreen(0x00ffcc, 400);
} else {
// Only increment kills if not progressing level
enemiesKilledThisLevel++;
}
if (playerHealth <= 0) {
// Game over
player.isAlive = false;
tween(player, {
alpha: 0.2
}, {
duration: 400
});
LK.setTimeout(function () {
LK.showGameOver();
}, 900);
break;
}
continue;
}
}
if (e.y > GAME_HEIGHT + 100) {
e.destroy();
enemies.splice(i, 1);
continue;
}
// Check collision with player
if (player.isAlive && e.isAlive && circlesIntersect(e, player)) {
e.isAlive = false;
e.flash();
player.flash();
// --- Fiery, intense explosion effect for enemy ship ---
var explosion = LK.getAsset('enemyShip', {
width: 260,
height: 260,
color: 0xfff200,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5,
x: e.x,
y: e.y
});
explosion.alpha = 0.95;
explosion.scaleX = 1.0;
explosion.scaleY = 1.0;
game.addChild(explosion);
tween(explosion, {
tint: 0xff6600,
scaleX: 2.7,
scaleY: 2.7,
alpha: 0.7
}, {
duration: 120,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(explosion, {
tint: 0xffffcc,
scaleX: 3.5,
scaleY: 3.5,
alpha: 0
}, {
duration: 220,
easing: tween.cubicIn,
onFinish: function onFinish() {
explosion.destroy();
}
});
}
});
tween(e, {}, {
duration: 120,
onFinish: function onFinish() {
e.destroy();
}
});
enemies.splice(i, 1);
playerHealth--;
updateHearts();
LK.effects.flashScreen(0xff0000, 400);
if (playerHealth <= 0) {
// Game over
player.isAlive = false;
tween(player, {
alpha: 0.2
}, {
duration: 400
});
LK.setTimeout(function () {
LK.showGameOver();
}, 900);
break;
}
}
// Update lastY for next frame
e.lastY = e.y;
}
// --- Update powerups ---
for (var i = powerUps.length - 1; i >= 0; i--) {
var pu = powerUps[i];
pu.update();
if (pu.y > GAME_HEIGHT + 80) {
pu.destroy();
powerUps.splice(i, 1);
continue;
}
// Check collision with player
if (player.isAlive && circlesIntersect(pu, player)) {
pu.destroy();
powerUps.splice(i, 1);
activatePowerUp();
}
}
};
// Set initial score
LK.setScore(0);
scoreTxt.setText('0');
if (LK.getScore() > highScore) {
highScore = LK.getScore();
}
highScoreTxt.setText('High: ' + highScore);
// Reset level system
currentLevel = 1;
enemiesPerLevel = 6;
enemiesToSpawn = enemiesPerLevel;
enemiesKilledThisLevel = 0;
enemyScoreThisLevel = enemyScoreBase;
levelTxt.setText('Level 1');
enemiesLeftTxt.setText('Enemies: ' + enemiesPerLevel);
playerHealth = 5;
updateHearts();
// Reset boss state
bossActive = false;
if (bossShip) {
bossShip.destroy();
bossShip = null;
}
if (bossHealthBar) {
LK.gui.top.removeChild(bossHealthBar);
bossHealthBar = null;
}
// Re-apply selected costume to player
if (player && storage.selectedCostume && storage.selectedCostume !== "default") {
var costume = null;
for (var i = 0; i < costumes.length; i++) {
if (costumes[i].id === storage.selectedCostume) costume = costumes[i];
}
if (costume) {
if (player.children.length > 0) player.removeChild(player.children[0]);
var costumeAsset = player.attachAsset(costume.asset, {
width: 220,
height: 220,
anchorX: 0.5,
anchorY: 0.5
});
}
} /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0
});
/****
* Classes
****/
// Boss ship class
var BossShip = Container.expand(function () {
var self = Container.call(this);
// Attach boss asset (use enemyShip but much larger, and red tint)
var boss = self.attachAsset('enemyShip', {
width: 480,
height: 480,
color: 0xff0000,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5
});
self.radius = 240;
self.isAlive = true;
self.health = 40;
self.maxHealth = 40;
self.lastY = undefined;
// Boss always at top center, stationary
self.x = GAME_WIDTH / 2;
self.y = 340;
// Boss update
self.update = function () {
// Boss remains stationary at top center
// Unique Skill 1: Radial bullet spread every 120 ticks
if (LK.ticks % 120 === 0) {
var numBullets = 14;
for (var i = 0; i < numBullets; i++) {
var angle = 2 * Math.PI / numBullets * i;
var bullet = new EnemyBullet();
bullet.x = self.x;
bullet.y = self.y + 220;
// Give bullet a custom direction
bullet.customVX = Math.cos(angle) * 13;
bullet.customVY = Math.sin(angle) * 13;
bullet.update = function () {
this.x += this.customVX;
this.y += this.customVY;
};
enemyBullets.push(bullet);
game.addChild(bullet);
}
}
// Unique Skill 2: Laser attack every 300 ticks (fires a fast straight bullet down the center)
if (LK.ticks % 300 === 0) {
var laser = new EnemyBullet();
laser.x = self.x;
laser.y = self.y + 260;
laser.speed = 38;
laser.radius = 60;
// Make it visually larger
var asset = laser.children[0];
if (asset) {
asset.width = 90;
asset.height = 340;
}
enemyBullets.push(laser);
game.addChild(laser);
}
// Unique Skill 3: Area attack (big slow bullet straight down) every 180 ticks
if (LK.ticks % 180 === 0) {
var area = new EnemyBullet();
area.x = self.x;
area.y = self.y + 240;
area.speed = 10;
area.radius = 120;
var asset = area.children[0];
if (asset) {
asset.width = 220;
asset.height = 120;
}
enemyBullets.push(area);
game.addChild(area);
}
};
// Flash on hit
self.flash = function () {
tween(boss, {
tint: 0xffffff
}, {
duration: 80,
onFinish: function onFinish() {
tween(boss, {
tint: 0xff0000
}, {
duration: 120
});
}
});
};
return self;
});
// Enemy bullet class
var EnemyBullet = Container.expand(function () {
var self = Container.call(this);
// Attach bullet asset (red, 24x48)
var bullet = self.attachAsset('enemyBullet', {
width: 24,
height: 48,
color: 0xff4444,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 14;
self.radius = 12;
self.update = function () {
self.y += self.speed;
};
return self;
});
// Enemy ship class
var EnemyShip = Container.expand(function () {
var self = Container.call(this);
// Attach enemy asset (ellipse, magenta, 220x220)
var enemy = self.attachAsset('enemyShip', {
width: 220,
height: 220,
color: 0xff33cc,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5
});
self.radius = 110;
self.speed = 4 + Math.random() * 2; // Vary speed
self.dirX = (Math.random() - 0.5) * 2; // Slight horizontal drift
// For movement patterns
self.waveOffset = Math.random() * Math.PI * 2;
// For collision
self.isAlive = true;
// Update per frame
self.update = function () {
// Move down, with a sine wave pattern
self.y += self.speed;
self.x += Math.sin(LK.ticks / 30 + self.waveOffset) * 2 + self.dirX;
// Prevent enemy from moving outside the horizontal screen area
var minX = 60;
var maxX = 2048 - 60;
if (self.x < minX) {
self.x = minX;
self.dirX = Math.abs(self.dirX); // bounce right
}
if (self.x > maxX) {
self.x = maxX;
self.dirX = -Math.abs(self.dirX); // bounce left
}
};
// Flash on hit
self.flash = function () {
tween(enemy, {
tint: 0xffffff
}, {
duration: 80,
onFinish: function onFinish() {
tween(enemy, {
tint: 0xff33cc
}, {
duration: 120
});
}
});
};
return self;
});
// Player bullet class
var PlayerBullet = Container.expand(function () {
var self = Container.call(this);
// Attach bullet asset (yellow, 30x60)
var bullet = self.attachAsset('playerBullet', {
width: 30,
height: 60,
color: 0xffe066,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
self.speed = -22;
self.radius = 15;
self.update = function () {
self.y += self.speed;
};
return self;
});
// Player spaceship class
var PlayerShip = Container.expand(function () {
var self = Container.call(this);
// Attach ship asset (box, blue, 220x220)
var ship = self.attachAsset('playerShip', {
width: 220,
height: 220,
color: 0x3399ff,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
// Ship properties
self.radius = 110; // for collision
self.isAlive = true;
// Ship flash effect on hit
self.flash = function () {
tween(ship, {
tint: 0xff3333
}, {
duration: 100,
onFinish: function onFinish() {
tween(ship, {
tint: 0x3399ff
}, {
duration: 200
});
}
});
};
return self;
});
// Powerup class
var PowerUp = Container.expand(function () {
var self = Container.call(this);
// Attach powerup asset (green ellipse, 120x120)
var pu = self.attachAsset('powerUp', {
width: 120,
height: 120,
color: 0x44ff88,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5
});
self.radius = 60;
self.speed = 6;
self.update = function () {
self.y += self.speed;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000010
});
/****
* Game Code
****/
// --- Start Screen Overlay ---
var startScreenOverlay = new Container();
// Large main title: "Space Wars"
var mainTitle = new Text2('Space Wars', {
size: 220,
fill: 0xFFD700,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
mainTitle.anchor.set(0.5, 0);
mainTitle.x = GAME_WIDTH / 2;
mainTitle.y = 120;
// Subtitle: "Galactic Showdown"
var subTitle = new Text2('Galactic Showdown', {
size: 110,
fill: 0x99e6ff,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
subTitle.anchor.set(0.5, 0);
subTitle.x = GAME_WIDTH / 2;
subTitle.y = mainTitle.y + mainTitle.height + 20;
// --- SHOP MENU UI ---
// Shop state
var shopOpen = false;
var shopOverlay = new Container();
shopOverlay.visible = false;
var shopTitle = new Text2('Costume Shop', {
size: 120,
fill: 0xFFD700,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
shopTitle.anchor.set(0.5, 0);
shopTitle.x = GAME_WIDTH / 2;
shopTitle.y = 120;
shopOverlay.addChild(shopTitle);
// Define costumes (id, name, price, asset)
var costumes = [{
id: "default",
name: "Classic",
price: 0,
asset: "playerShip"
}, {
id: "red",
name: "Red Comet",
price: 200,
asset: "enemyShip"
}, {
id: "gold",
name: "Gold Star",
price: 500,
asset: "playerBullet"
}];
// Persistent unlocks
if (!storage.unlockedCostumes) storage.unlockedCostumes = {
"default": true
};
if (!storage.selectedCostume) storage.selectedCostume = "default";
// Shop navigation state
var shopIndex = 0;
function updateShopUI() {
// Remove old preview if any
if (shopOverlay.costumePreview) {
shopOverlay.removeChild(shopOverlay.costumePreview);
shopOverlay.costumePreview = null;
}
var c = costumes[shopIndex];
var preview = LK.getAsset(c.asset, {
width: 320,
height: 320,
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2,
y: 520
});
shopOverlay.addChild(preview);
shopOverlay.costumePreview = preview;
// Update name/price
shopNameTxt.setText(c.name);
if (c.price === 0) {
shopPriceTxt.setText("Unlocked");
} else if (storage.unlockedCostumes[c.id]) {
shopPriceTxt.setText("Unlocked");
} else {
shopPriceTxt.setText("Price: " + c.price + " pts");
}
// Show select/unlock button
if (storage.unlockedCostumes[c.id]) {
shopActionBtnText.setText(storage.selectedCostume === c.id ? "Selected" : "Select");
} else {
shopActionBtnText.setText("Unlock");
}
// Hide select if already selected
shopActionBtn.alpha = storage.selectedCostume === c.id && storage.unlockedCostumes[c.id] ? 0.5 : 1;
}
// Costume name
var shopNameTxt = new Text2('', {
size: 80,
fill: 0xffffff,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
shopNameTxt.anchor.set(0.5, 0);
shopNameTxt.x = GAME_WIDTH / 2;
shopNameTxt.y = 900;
shopOverlay.addChild(shopNameTxt);
// Costume price
var shopPriceTxt = new Text2('', {
size: 60,
fill: 0xFFD700,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
shopPriceTxt.anchor.set(0.5, 0);
shopPriceTxt.x = GAME_WIDTH / 2;
shopPriceTxt.y = 1020;
shopOverlay.addChild(shopPriceTxt);
// Action button (Unlock/Select)
var shopActionBtn = LK.getAsset('playerBullet', {
width: 420,
height: 110,
color: 0x00ccff,
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2,
y: 1200
});
var shopActionBtnText = new Text2('', {
size: 70,
fill: 0xffffff,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
shopActionBtnText.anchor.set(0.5, 0.5);
shopActionBtnText.x = shopActionBtn.x;
shopActionBtnText.y = shopActionBtn.y;
shopOverlay.addChild(shopActionBtn);
shopOverlay.addChild(shopActionBtnText);
// Left/right nav buttons
var shopLeftBtn = LK.getAsset('enemyBullet', {
width: 100,
height: 100,
color: 0xFFD700,
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2 - 300,
y: 520
});
var shopRightBtn = LK.getAsset('enemyBullet', {
width: 100,
height: 100,
color: 0xFFD700,
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2 + 300,
y: 520
});
shopOverlay.addChild(shopLeftBtn);
shopOverlay.addChild(shopRightBtn);
// Close button
var shopCloseBtn = LK.getAsset('playerBullet', {
width: 180,
height: 80,
color: 0x888888,
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2,
y: 1450
});
var shopCloseBtnText = new Text2('Close', {
size: 50,
fill: 0xffffff,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
shopCloseBtnText.anchor.set(0.5, 0.5);
shopCloseBtnText.x = shopCloseBtn.x;
shopCloseBtnText.y = shopCloseBtn.y;
shopOverlay.addChild(shopCloseBtn);
shopOverlay.addChild(shopCloseBtnText);
// Shop button on start screen
var shopBtn = LK.getAsset('playerBullet', {
width: 320,
height: 100,
color: 0xFFD700,
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2,
y: GAME_HEIGHT / 2 + 320
});
var shopBtnText = new Text2('SHOP', {
size: 70,
fill: 0x000000,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
shopBtnText.anchor.set(0.5, 0.5);
shopBtnText.x = shopBtn.x;
shopBtnText.y = shopBtn.y;
// Shop button handler
shopBtn.down = function (x, y, obj) {
if (shopOpen) return;
shopOpen = true;
shopOverlay.visible = true;
LK.gui.center.addChild(shopOverlay);
updateShopUI();
// Hide start overlay
if (startScreenOverlay.parent) startScreenOverlay.visible = false;
};
// Shop close handler
shopCloseBtn.down = function (x, y, obj) {
shopOpen = false;
shopOverlay.visible = false;
if (shopOverlay.parent) LK.gui.center.removeChild(shopOverlay);
if (startScreenOverlay && !gameStarted) startScreenOverlay.visible = true;
};
// Shop left/right nav
shopLeftBtn.down = function (x, y, obj) {
shopIndex = (shopIndex + costumes.length - 1) % costumes.length;
updateShopUI();
};
shopRightBtn.down = function (x, y, obj) {
shopIndex = (shopIndex + 1) % costumes.length;
updateShopUI();
};
// Shop action (unlock/select)
shopActionBtn.down = function (x, y, obj) {
var c = costumes[shopIndex];
if (storage.unlockedCostumes[c.id]) {
// Select
storage.selectedCostume = c.id;
updateShopUI();
} else {
// Unlock if enough score
var score = LK.getScore();
if (score >= c.price) {
LK.setScore(score - c.price);
scoreTxt.setText(LK.getScore());
storage.unlockedCostumes[c.id] = true;
storage.selectedCostume = c.id;
updateShopUI();
} else {
// Not enough points, flash price red
shopPriceTxt.setText("Need " + (c.price - score) + " more!");
tween(shopPriceTxt, {
fill: 0xff0000
}, {
duration: 200,
onFinish: function onFinish() {
shopPriceTxt.setText("Price: " + c.price + " pts");
}
});
}
}
};
// Add to overlay
startScreenOverlay.addChild(mainTitle);
startScreenOverlay.addChild(subTitle);
startScreenOverlay.addChild(shopBtn);
startScreenOverlay.addChild(shopBtnText);
// Play button (large, centered)
var playBtn = LK.getAsset('playerBullet', {
width: 520,
height: 140,
color: 0x00ccff,
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2,
y: GAME_HEIGHT / 2 + 120
});
var playBtnText = new Text2('PLAY', {
size: 100,
fill: 0xffffff,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
playBtnText.anchor.set(0.5, 0.5);
playBtnText.x = playBtn.x;
playBtnText.y = playBtn.y;
startScreenOverlay.addChild(playBtn);
startScreenOverlay.addChild(playBtnText);
// Animate main title in
mainTitle.alpha = 0;
tween(mainTitle, {
alpha: 1
}, {
duration: 700,
easing: tween.cubicOut
});
// Animate subtitle in (slightly delayed)
subTitle.alpha = 0;
tween(subTitle, {
alpha: 1
}, {
duration: 700,
delay: 200,
easing: tween.cubicOut
});
// Animate play button in
playBtn.alpha = 0;
playBtnText.alpha = 0;
tween(playBtn, {
alpha: 1
}, {
duration: 700,
easing: tween.cubicOut
});
tween(playBtnText, {
alpha: 1
}, {
duration: 900,
easing: tween.cubicOut
});
// Add to overlay
startScreenOverlay.addChild(mainTitle);
startScreenOverlay.addChild(subTitle);
startScreenOverlay.addChild(playBtn);
startScreenOverlay.addChild(playBtnText);
LK.gui.center.addChild(startScreenOverlay);
// Block gameplay until Play is pressed
var gameStarted = false;
function hideStartScreen() {
if (startScreenOverlay.parent) {
LK.gui.center.removeChild(startScreenOverlay);
}
gameStarted = true;
}
// Play button interaction
playBtn.down = function (x, y, obj) {
if (gameStarted) return;
// Animate out
tween(startScreenOverlay, {
alpha: 0
}, {
duration: 400,
easing: tween.cubicIn,
onFinish: function onFinish() {
// Start background music only after Play is clicked
LK.playMusic('sapce');
hideStartScreen();
}
});
};
playBtn.move = function (x, y, obj) {};
playBtn.up = function (x, y, obj) {};
// Block all gameplay input until started
var origGameMove = game.move;
var origGameDown = game.down;
var origGameUp = game.up;
game.move = function (x, y, obj) {
if (!gameStarted) return;
if (origGameMove) origGameMove(x, y, obj);
};
game.down = function (x, y, obj) {
if (!gameStarted) return;
if (origGameDown) origGameDown(x, y, obj);
};
game.up = function (x, y, obj) {
if (!gameStarted) return;
if (origGameUp) origGameUp(x, y, obj);
};
// --- Volume Control UI on Pause ---
var volumeSlider = null;
var volumeLabel = null;
var lastPauseState = false;
// Helper to create volume slider UI
function showVolumeSlider() {
if (!LK.isPaused) return;
if (volumeSlider) return; // Already shown
// Create label
volumeLabel = new Text2('Music Volume', {
size: 60,
fill: 0xFFFFFF
});
volumeLabel.anchor.set(0.5, 0.5);
volumeLabel.x = GAME_WIDTH / 2;
volumeLabel.y = GAME_HEIGHT / 2 - 120;
LK.gui.center.addChild(volumeLabel);
// Create slider background
var sliderBg = LK.getAsset('playerBullet', {
width: 500,
height: 32,
color: 0x222222,
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2,
y: GAME_HEIGHT / 2
});
LK.gui.center.addChild(sliderBg);
// Create slider knob
volumeSlider = LK.getAsset('enemyBullet', {
width: 60,
height: 60,
color: 0xFFD700,
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2,
y: GAME_HEIGHT / 2
});
LK.gui.center.addChild(volumeSlider);
// Set initial knob position based on current music volume
var currentVolume = LK.getMusicVolume ? LK.getMusicVolume() : 1;
var minX = GAME_WIDTH / 2 - 220;
var maxX = GAME_WIDTH / 2 + 220;
volumeSlider.x = minX + (maxX - minX) * currentVolume;
// Drag logic
var dragging = false;
volumeSlider.down = function (x, y, obj) {
dragging = true;
};
volumeSlider.up = function (x, y, obj) {
dragging = false;
};
// Move handler for slider
volumeSlider.move = function (x, y, obj) {
if (!dragging) return;
var minX = GAME_WIDTH / 2 - 220;
var maxX = GAME_WIDTH / 2 + 220;
var px = Math.max(minX, Math.min(maxX, x));
volumeSlider.x = px;
// Calculate volume (0..1)
var vol = (px - minX) / (maxX - minX);
if (vol < 0) vol = 0;
if (vol > 1) vol = 1;
LK.setMusicVolume ? LK.setMusicVolume(vol) : null;
};
// Attach move/up to gui.center for mobile, but only if game is paused
LK.gui.center.move = function (x, y, obj) {
if (LK.isPaused && dragging) {
volumeSlider.move(x, y, obj);
}
};
LK.gui.center.up = function (x, y, obj) {
if (LK.isPaused) {
dragging = false;
}
};
}
// Helper to remove volume slider UI
function hideVolumeSlider() {
if (volumeSlider) {
LK.gui.center.removeChild(volumeSlider);
volumeSlider = null;
}
if (volumeLabel) {
LK.gui.center.removeChild(volumeLabel);
volumeLabel = null;
}
// Remove slider background if present
var children = LK.gui.center.children;
for (var i = children.length - 1; i >= 0; i--) {
var c = children[i];
if (c && c.width === 500 && c.height === 32 && c.color === 0x222222) {
LK.gui.center.removeChild(c);
}
}
// Remove move/up handlers
LK.gui.center.move = null;
LK.gui.center.up = null;
}
// Listen for pause/resume to show/hide slider
LK.on('pause', function () {
showVolumeSlider();
});
LK.on('resume', function () {
hideVolumeSlider();
});
var backgroundImg = LK.getAsset('background', {
width: GAME_WIDTH,
height: GAME_HEIGHT,
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.addChildAt(backgroundImg, 0); // Always at the back layer
// Play sapce music at game start
// (Music will be started after Play is clicked, see playBtn.down handler)
// LK.playMusic('sapce');
// Health bar asset (simple red bar, 600x40)
// Game constants
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var PLAYER_START_X = GAME_WIDTH / 2;
var PLAYER_START_Y = GAME_HEIGHT - 350;
// Level system variables
var currentLevel = 1;
var enemiesPerLevel = 6; // base number of enemies for level 1
var enemiesToSpawn = enemiesPerLevel;
var enemiesKilledThisLevel = 0;
var enemyScoreBase = 10; // base score for level 1
var enemyScoreThisLevel = enemyScoreBase;
// Game state
var player;
var enemies = [];
var playerBullets = [];
var enemyBullets = [];
var powerUps = [];
var dragNode = null;
var lastScore = 0;
var scoreTxt;
var powerUpActive = false;
var powerUpTimer = 0;
var enemySpawnTimer = 0;
var enemyFireTimer = 0;
var powerUpSpawnTimer = 0;
var gameOver = false;
// Boss state
var bossActive = false;
var bossShip = null;
var bossHealthBar = null;
// Score display
scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Import storage plugin for persistent high score
// High score display (top right, avoid top right 100x100)
var highScore = storage.highScore || 0;
var highScoreTxt = new Text2('High: ' + highScore, {
size: 60,
fill: 0xFFD700
});
highScoreTxt.anchor.set(1, 0);
highScoreTxt.x = GAME_WIDTH - 120;
highScoreTxt.y = 0;
LK.gui.top.addChild(highScoreTxt);
// Health system
var playerHealth = 5;
// Heart icon assets (use 5 separate heart images for live updating)
var heartIcons = [];
var heartAssetNames = ['heart1', 'heart2', 'heart3', 'heart4', 'heart5'];
for (var i = 0; i < 5; i++) {
var heart = LK.getAsset(heartAssetNames[i], {
width: 60,
height: 60,
anchorX: 0.5,
anchorY: 0.5,
x: 200 + i * 70,
y: 30
});
LK.gui.top.addChild(heart);
heartIcons.push(heart);
}
// Helper to update heart icons based on playerHealth
function updateHearts() {
for (var i = 0; i < heartIcons.length; i++) {
heartIcons[i].alpha = i < playerHealth ? 1 : 0.2;
}
}
updateHearts();
// Level display (top left, avoid top left 100x100)
var levelTxt = new Text2('Level 1', {
size: 40,
fill: 0xFFD700
});
levelTxt.anchor.set(0, 0);
levelTxt.x = 180;
levelTxt.y = 100;
LK.gui.top.addChild(levelTxt);
// Enemies left display (top left, avoid top left 100x100)
var enemiesLeftTxt = new Text2('Enemies: 0', {
size: 40,
fill: 0xFF8888
});
enemiesLeftTxt.anchor.set(0, 0);
enemiesLeftTxt.x = 180;
enemiesLeftTxt.y = 60;
LK.gui.top.addChild(enemiesLeftTxt);
// Initialize player
player = new PlayerShip();
// Apply selected costume if not default
if (storage.selectedCostume && storage.selectedCostume !== "default") {
var costume = null;
for (var i = 0; i < costumes.length; i++) {
if (costumes[i].id === storage.selectedCostume) costume = costumes[i];
}
if (costume) {
// Remove old asset and attach new
if (player.children.length > 0) player.removeChild(player.children[0]);
var costumeAsset = player.attachAsset(costume.asset, {
width: 220,
height: 220,
anchorX: 0.5,
anchorY: 0.5
});
}
}
player.x = PLAYER_START_X;
player.y = PLAYER_START_Y;
game.addChild(player);
// Touch/move controls
function handleMove(x, y, obj) {
if (dragNode && player.isAlive) {
// Clamp to game area, avoid top 100px (menu)
var px = Math.max(60, Math.min(GAME_WIDTH - 60, x));
var py = Math.max(200, Math.min(GAME_HEIGHT - 60, y));
dragNode.x = px;
dragNode.y = py;
}
}
game.move = handleMove;
game.down = function (x, y, obj) {
if (player.isAlive) {
dragNode = player;
handleMove(x, y, obj);
}
};
game.up = function (x, y, obj) {
dragNode = null;
};
// Helper: collision check (circle vs circle)
function circlesIntersect(a, b) {
var dx = a.x - b.x;
var dy = a.y - b.y;
var dist = Math.sqrt(dx * dx + dy * dy);
return dist < a.radius + b.radius;
}
// Helper: spawn enemy
function spawnEnemy() {
var enemy = new EnemyShip();
// Spawn in random horizontal position, avoid edges
enemy.x = 120 + Math.random() * (GAME_WIDTH - 240);
enemy.y = -100;
enemies.push(enemy);
game.addChild(enemy);
}
// Helper: spawn enemy bullet
function spawnEnemyBullet(enemy) {
var bullet = new EnemyBullet();
bullet.x = enemy.x;
bullet.y = enemy.y + 60;
enemyBullets.push(bullet);
game.addChild(bullet);
}
// Helper: spawn player bullet
function spawnPlayerBullet() {
var bullet = new PlayerBullet();
bullet.x = player.x;
bullet.y = player.y - 80;
playerBullets.push(bullet);
game.addChild(bullet);
}
// Helper: spawn powerup
function spawnPowerUp() {
var pu = new PowerUp();
pu.x = 120 + Math.random() * (GAME_WIDTH - 240);
pu.y = -60;
powerUps.push(pu);
game.addChild(pu);
}
// Powerup effect: double shot
function activatePowerUp() {
powerUpActive = true;
powerUpTimer = LK.ticks + 360; // 6 seconds at 60fps
// Flash player ship green
tween(player, {
tint: 0x44ff88
}, {
duration: 200,
onFinish: function onFinish() {
tween(player, {
tint: 0x3399ff
}, {
duration: 200
});
}
});
}
// Main game update
game.update = function () {
if (!gameStarted) {
return;
}
if (!player.isAlive) {
return;
}
// --- Player auto-fire ---
if (LK.ticks % (powerUpActive ? 7 : 14) === 0) {
if (powerUpActive) {
// Double shot: two bullets
var b1 = new PlayerBullet();
b1.x = player.x - 36;
b1.y = player.y - 80;
playerBullets.push(b1);
game.addChild(b1);
var b2 = new PlayerBullet();
b2.x = player.x + 36;
b2.y = player.y - 80;
playerBullets.push(b2);
game.addChild(b2);
} else {
spawnPlayerBullet();
}
}
// --- Enemy spawn ---
if (!bossActive && enemySpawnTimer <= LK.ticks && enemiesToSpawn > 0) {
spawnEnemy();
enemiesToSpawn--;
// Escalate spawn rate
var minDelay = 24,
maxDelay = 60;
var delay = Math.max(minDelay, maxDelay - Math.floor(LK.getScore() / 10) * 4);
enemySpawnTimer = LK.ticks + delay;
// Update enemies left UI
enemiesLeftTxt.setText('Enemies: ' + (enemiesToSpawn + enemies.length));
}
// --- Enemy fire ---
if (!bossActive && enemyFireTimer <= LK.ticks && enemies.length > 0) {
// Pick random enemy to fire
var idx = Math.floor(Math.random() * enemies.length);
if (enemies[idx]) {
spawnEnemyBullet(enemies[idx]);
}
// Reduce fire rate: increase min and max fire delay for better balance
var minFire = 36,
// was 18
maxFire = 100; // was 50
var fireDelay = Math.max(minFire, maxFire - Math.floor(LK.getScore() / 10) * 3);
enemyFireTimer = LK.ticks + fireDelay;
}
// --- Powerup spawn ---
if (powerUpSpawnTimer <= LK.ticks) {
if (Math.random() < 0.18) {
// ~18% chance
spawnPowerUp();
}
powerUpSpawnTimer = LK.ticks + 300 + Math.floor(Math.random() * 200);
}
// --- Powerup timer ---
if (powerUpActive && LK.ticks > powerUpTimer) {
powerUpActive = false;
}
// --- 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 (e.isAlive && circlesIntersect(b, e)) {
e.isAlive = false;
b.destroy();
playerBullets.splice(i, 1);
// --- Quick fire burst effect for enemy hit ---
var fireBurst = LK.getAsset('enemyBullet', {
width: 120,
height: 120,
color: 0xffa200,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5,
x: e.x,
y: e.y
});
fireBurst.alpha = 0.85;
fireBurst.scaleX = 1.0;
fireBurst.scaleY = 1.0;
game.addChild(fireBurst);
tween(fireBurst, {
tint: 0xfff200,
scaleX: 2.2,
scaleY: 2.2,
alpha: 0
}, {
duration: 120,
easing: tween.cubicOut,
onFinish: function onFinish() {
fireBurst.destroy();
}
});
e.destroy();
enemies.splice(j, 1);
// Score (scaled by level)
LK.setScore(LK.getScore() + enemyScoreThisLevel);
scoreTxt.setText(LK.getScore());
if (LK.getScore() > highScore) {
highScore = LK.getScore();
highScoreTxt.setText('High: ' + highScore);
storage.highScore = highScore;
}
// Track kills for level progression
enemiesKilledThisLevel++;
enemiesLeftTxt.setText('Enemies: ' + (enemiesToSpawn + enemies.length));
// If all enemies for this level are killed, immediately start a new level (endless progression)
if (enemiesKilledThisLevel >= enemiesPerLevel) {
// Boss fight every 5 levels
if ((currentLevel + 1) % 5 === 0) {
// Spawn boss
bossActive = true;
bossShip = new BossShip();
// BossShip class now sets its own x/y to top center and is stationary
game.addChild(bossShip);
// Add boss health bar
if (bossHealthBar) {
LK.gui.top.removeChild(bossHealthBar);
}
bossHealthBar = new Text2('Boss: ' + bossShip.health + '/' + bossShip.maxHealth, {
size: 90,
fill: 0xFF2222
});
bossHealthBar.anchor.set(0.5, 0);
bossHealthBar.x = GAME_WIDTH / 2;
bossHealthBar.y = 80;
LK.gui.top.addChild(bossHealthBar);
// Play boss music
LK.playMusic('boss');
// UI
levelTxt.setText('Boss Level ' + (currentLevel + 1));
enemiesLeftTxt.setText('Boss Fight!');
LK.effects.flashScreen(0xffcc00, 600);
} else {
// Next level! Endless progression
currentLevel++;
enemiesPerLevel = Math.floor(enemiesPerLevel * 1.25) + 1; // escalate number of enemies
enemiesToSpawn = enemiesPerLevel;
enemiesKilledThisLevel = 0;
enemyScoreThisLevel = enemyScoreBase * Math.pow(2, currentLevel - 1);
// Update UI
levelTxt.setText('Level ' + currentLevel);
enemiesLeftTxt.setText('Enemies: ' + (enemiesToSpawn + enemies.length));
// Optionally: flash screen or give feedback
LK.effects.flashScreen(0x00ffcc, 400);
}
}
break;
}
}
}
// --- Boss fight logic ---
if (bossActive && bossShip && bossShip.isAlive) {
bossShip.update();
// Boss health bar always at top center and visible only when boss is present
if (!bossHealthBar) {
bossHealthBar = new Text2('Boss: ' + bossShip.health + '/' + bossShip.maxHealth, {
size: 90,
fill: 0xFF2222
});
bossHealthBar.anchor.set(0.5, 0);
bossHealthBar.x = GAME_WIDTH / 2;
bossHealthBar.y = 80;
LK.gui.top.addChild(bossHealthBar);
} else {
bossHealthBar.x = GAME_WIDTH / 2;
bossHealthBar.y = 80;
bossHealthBar.setText('Boss: ' + bossShip.health + '/' + bossShip.maxHealth);
}
// Check player bullet collision with boss
for (var i = playerBullets.length - 1; i >= 0; i--) {
var b = playerBullets[i];
if (bossShip.isAlive && circlesIntersect(bossShip, b)) {
bossShip.health--;
bossShip.flash();
b.destroy();
playerBullets.splice(i, 1);
if (bossShip.health <= 0) {
bossShip.isAlive = false;
bossActive = false;
// Remove boss health bar
if (bossHealthBar) {
LK.gui.top.removeChild(bossHealthBar);
bossHealthBar = null;
}
// Resume sapce music after boss fight
LK.playMusic('sapce');
// Score for boss
LK.setScore(LK.getScore() + 100 * currentLevel);
scoreTxt.setText(LK.getScore());
if (LK.getScore() > highScore) {
highScore = LK.getScore();
highScoreTxt.setText('High: ' + highScore);
storage.highScore = highScore;
}
// Next level
currentLevel++;
enemiesPerLevel = Math.floor(enemiesPerLevel * 1.25) + 1;
enemiesToSpawn = enemiesPerLevel;
enemiesKilledThisLevel = 0;
enemyScoreThisLevel = enemyScoreBase * Math.pow(2, currentLevel - 1);
levelTxt.setText('Level ' + currentLevel);
enemiesLeftTxt.setText('Enemies: ' + enemiesPerLevel);
LK.effects.flashScreen(0x00ffcc, 600);
// Remove boss
bossShip.destroy();
bossShip = null;
}
break;
}
}
// Boss collision with player
if (player.isAlive && bossShip && bossShip.isAlive && circlesIntersect(bossShip, player)) {
bossShip.isAlive = false;
bossActive = false;
if (bossHealthBar) {
LK.gui.top.removeChild(bossHealthBar);
bossHealthBar = null;
}
// Resume sapce music after boss fight
LK.playMusic('sapce');
player.flash();
playerHealth--;
updateHearts();
LK.effects.flashScreen(0xff0000, 400);
if (playerHealth <= 0) {
player.isAlive = false;
tween(player, {
alpha: 0.2
}, {
duration: 400
});
LK.setTimeout(function () {
LK.showGameOver();
}, 900);
}
bossShip.destroy();
bossShip = null;
}
}
// --- Update enemy bullets ---
for (var i = enemyBullets.length - 1; i >= 0; i--) {
var eb = enemyBullets[i];
eb.update();
if (eb.y > GAME_HEIGHT + 80) {
eb.destroy();
enemyBullets.splice(i, 1);
continue;
}
// Check collision with player
if (player.isAlive && circlesIntersect(eb, player)) {
eb.destroy();
enemyBullets.splice(i, 1);
player.flash();
playerHealth--;
updateHearts();
LK.effects.flashScreen(0xff0000, 400);
if (playerHealth <= 0) {
// Game over
player.isAlive = false;
tween(player, {
alpha: 0.2
}, {
duration: 400
});
LK.setTimeout(function () {
LK.showGameOver();
}, 900);
break;
}
}
}
// --- Update enemies ---
for (var i = enemies.length - 1; i >= 0; i--) {
var e = enemies[i];
// Track lastY for crossing detection
if (typeof e.lastY === "undefined") {
e.lastY = e.y;
}
e.update();
// If enemy crosses into player's area (y > PLAYER_START_Y - 110, i.e. bottom of player ship)
if (e.lastY <= PLAYER_START_Y - 110 && e.y > PLAYER_START_Y - 110) {
// Only trigger if enemy is alive
if (e.isAlive && player.isAlive) {
e.isAlive = false;
// --- Fiery, intense explosion effect for enemy ship ---
var explosion = LK.getAsset('enemyShip', {
width: 260,
height: 260,
color: 0xfff200,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5,
x: e.x,
y: e.y
});
explosion.alpha = 0.95;
explosion.scaleX = 1.0;
explosion.scaleY = 1.0;
game.addChild(explosion);
tween(explosion, {
tint: 0xff6600,
scaleX: 2.7,
scaleY: 2.7,
alpha: 0.7
}, {
duration: 120,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(explosion, {
tint: 0xffffcc,
scaleX: 3.5,
scaleY: 3.5,
alpha: 0
}, {
duration: 220,
easing: tween.cubicIn,
onFinish: function onFinish() {
explosion.destroy();
}
});
}
});
tween(e, {}, {
duration: 120,
onFinish: function onFinish() {
e.destroy();
}
});
enemies.splice(i, 1);
playerHealth--;
updateHearts();
LK.effects.flashScreen(0xff0000, 400);
// Level progression if this was the last enemy for the level
enemiesLeftTxt.setText('Enemies: ' + (enemiesToSpawn + enemies.length));
if (enemiesKilledThisLevel + 1 >= enemiesPerLevel && enemiesToSpawn === 0 && enemies.length === 0) {
// Next level!
currentLevel++;
enemiesPerLevel = Math.floor(enemiesPerLevel * 1.25) + 1;
enemiesToSpawn = enemiesPerLevel;
enemiesKilledThisLevel = 0;
enemyScoreThisLevel = enemyScoreBase * Math.pow(2, currentLevel - 1);
// Update UI
levelTxt.setText('Level ' + currentLevel);
enemiesLeftTxt.setText('Enemies: ' + (enemiesToSpawn + enemies.length));
LK.effects.flashScreen(0x00ffcc, 400);
} else {
// Only increment kills if not progressing level
enemiesKilledThisLevel++;
}
if (playerHealth <= 0) {
// Game over
player.isAlive = false;
tween(player, {
alpha: 0.2
}, {
duration: 400
});
LK.setTimeout(function () {
LK.showGameOver();
}, 900);
break;
}
continue;
}
}
if (e.y > GAME_HEIGHT + 100) {
e.destroy();
enemies.splice(i, 1);
continue;
}
// Check collision with player
if (player.isAlive && e.isAlive && circlesIntersect(e, player)) {
e.isAlive = false;
e.flash();
player.flash();
// --- Fiery, intense explosion effect for enemy ship ---
var explosion = LK.getAsset('enemyShip', {
width: 260,
height: 260,
color: 0xfff200,
shape: 'ellipse',
anchorX: 0.5,
anchorY: 0.5,
x: e.x,
y: e.y
});
explosion.alpha = 0.95;
explosion.scaleX = 1.0;
explosion.scaleY = 1.0;
game.addChild(explosion);
tween(explosion, {
tint: 0xff6600,
scaleX: 2.7,
scaleY: 2.7,
alpha: 0.7
}, {
duration: 120,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(explosion, {
tint: 0xffffcc,
scaleX: 3.5,
scaleY: 3.5,
alpha: 0
}, {
duration: 220,
easing: tween.cubicIn,
onFinish: function onFinish() {
explosion.destroy();
}
});
}
});
tween(e, {}, {
duration: 120,
onFinish: function onFinish() {
e.destroy();
}
});
enemies.splice(i, 1);
playerHealth--;
updateHearts();
LK.effects.flashScreen(0xff0000, 400);
if (playerHealth <= 0) {
// Game over
player.isAlive = false;
tween(player, {
alpha: 0.2
}, {
duration: 400
});
LK.setTimeout(function () {
LK.showGameOver();
}, 900);
break;
}
}
// Update lastY for next frame
e.lastY = e.y;
}
// --- Update powerups ---
for (var i = powerUps.length - 1; i >= 0; i--) {
var pu = powerUps[i];
pu.update();
if (pu.y > GAME_HEIGHT + 80) {
pu.destroy();
powerUps.splice(i, 1);
continue;
}
// Check collision with player
if (player.isAlive && circlesIntersect(pu, player)) {
pu.destroy();
powerUps.splice(i, 1);
activatePowerUp();
}
}
};
// Set initial score
LK.setScore(0);
scoreTxt.setText('0');
if (LK.getScore() > highScore) {
highScore = LK.getScore();
}
highScoreTxt.setText('High: ' + highScore);
// Reset level system
currentLevel = 1;
enemiesPerLevel = 6;
enemiesToSpawn = enemiesPerLevel;
enemiesKilledThisLevel = 0;
enemyScoreThisLevel = enemyScoreBase;
levelTxt.setText('Level 1');
enemiesLeftTxt.setText('Enemies: ' + enemiesPerLevel);
playerHealth = 5;
updateHearts();
// Reset boss state
bossActive = false;
if (bossShip) {
bossShip.destroy();
bossShip = null;
}
if (bossHealthBar) {
LK.gui.top.removeChild(bossHealthBar);
bossHealthBar = null;
}
// Re-apply selected costume to player
if (player && storage.selectedCostume && storage.selectedCostume !== "default") {
var costume = null;
for (var i = 0; i < costumes.length; i++) {
if (costumes[i].id === storage.selectedCostume) costume = costumes[i];
}
if (costume) {
if (player.children.length > 0) player.removeChild(player.children[0]);
var costumeAsset = player.attachAsset(costume.asset, {
width: 220,
height: 220,
anchorX: 0.5,
anchorY: 0.5
});
}
}