User prompt
tamam bunları ekle oyuna
User prompt
kontroller de kullanıcı dostu olsun
User prompt
Please fix the bug: 'tween.to is not a function' in or related to this line: 'tween.to(instructionTxt, {' Line Number: 470 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
daha profesyonel bir oyun yap kullanıcı dostu olsun
Code edit (1 edits merged)
Please save this source code
User prompt
Endless Sky Shooter
Initial prompt
Make a professional endless shooting game with all the features that the game should have.
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0
});
/****
* Classes
****/
// BigEnemy: shoots large bullets
var BigEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGfx = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
enemyGfx.tint = 0x3f51b5; // blue for big enemy
self.radius = enemyGfx.width * 0.75;
self.speed = ENEMY_BASE_SPEED * 0.7 + Math.random() * 2;
self.moveType = 'straight';
self.shootCooldown = 80 + Math.floor(Math.random() * 40);
self.maxHealth = 5;
self.health = self.maxHealth;
// Health bar
var healthBarBg = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.18,
tint: 0x222222,
y: enemyGfx.height * 0.9
});
var healthBar = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.45,
scaleY: 0.12,
tint: 0x3f51b5,
y: enemyGfx.height * 0.9
});
self.update = function () {
self.y += self.speed * 0.8;
// Clamp inside screen
var margin = self.radius;
if (self.x < margin) self.x = margin;
if (self.x > GAME_W - margin) self.x = GAME_W - margin;
self.shootCooldown--;
// Health bar
healthBar.scaleX = 1.45 * (self.health / self.maxHealth);
if (self.health < 1) healthBar.scaleX = 0;
};
self.takeDamage = function (dmg) {
self.health -= dmg;
LK.effects.flashObject(self, 0x3f51b5, 100);
if (self.health <= 0) self.health = 0;
};
return self;
});
// BigEnemyBullet: large, slow, dangerous
var BigEnemyBullet = Container.expand(function () {
var self = Container.call(this);
var bulletGfx = self.attachAsset('enemyBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.2,
scaleY: 2.2,
tint: 0x3f51b5
});
self.speed = ENEMY_BULLET_SPEED * 0.6;
self.dirX = 0;
self.dirY = 1;
self.update = function () {
self.x += self.dirX * self.speed;
self.y += self.dirY * self.speed;
};
return self;
});
// Enemy (Base class)
var Enemy = Container.expand(function () {
var self = Container.call(this);
var enemyGfx = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
// Assign a vivid, random color from a palette for more visible and lively enemies
var enemyTints = [0xff1744,
// vivid red
0xffe100,
// bright yellow
0x00e1ff,
// cyan
0x44ff44,
// green
0xff00ff,
// magenta
0xff8800,
// orange
0x9c27b0,
// purple
0x00ff88,
// teal
0x3f51b5,
// blue
0xff4444,
// strong red
0xcddc39,
// lime
0x607d8b // blue-grey
];
enemyGfx.tint = enemyTints[Math.floor(Math.random() * enemyTints.length)];
self.radius = enemyGfx.width * 0.5;
self.speed = ENEMY_BASE_SPEED + Math.random() * 4;
self.moveType = Math.random() < 0.5 ? 'straight' : 'sine';
self.sinePhase = Math.random() * Math.PI * 2;
self.sineAmp = 120 + Math.random() * 120;
self.shootCooldown = 60 + Math.floor(Math.random() * 60);
// Health system for enemy
self.maxHealth = 1;
self.health = self.maxHealth;
// Health bar visual (background)
var healthBarBg = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 0.18,
tint: 0x222222,
y: enemyGfx.height * 0.7
});
// Health bar visual (foreground)
var healthBar = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.95,
scaleY: 0.12,
tint: enemyGfx.tint,
y: enemyGfx.height * 0.7
});
self.update = function () {
if (self.moveType === 'straight') {
self.y += self.speed;
} else {
self.y += self.speed * 0.85;
self.x += Math.sin(self.y / 120 + self.sinePhase) * 6;
}
// Clamp enemy inside left/right screen bounds
var margin = self.radius;
if (self.x < margin) self.x = margin;
if (self.x > GAME_W - margin) self.x = GAME_W - margin;
self.shootCooldown--;
// Update health bar
healthBar.scaleX = 0.95 * (self.health / self.maxHealth);
if (self.health < 1) healthBar.scaleX = 0;
};
self.takeDamage = function (dmg) {
self.health -= dmg;
LK.effects.flashObject(self, enemyGfx.tint, 80);
if (self.health <= 0) {
self.health = 0;
// Death handled in game loop
}
};
return self;
});
// Enemy Bullet
var EnemyBullet = Container.expand(function () {
var self = Container.call(this);
var bulletGfx = self.attachAsset('enemyBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = ENEMY_BULLET_SPEED;
self.dirX = 0;
self.dirY = 1;
self.update = function () {
self.x += self.dirX * self.speed;
self.y += self.dirY * self.speed;
};
return self;
});
// FastEnemy: moves faster, less health
var FastEnemy = Container.expand(function () {
var self = Container.call(this);
var fastTints = [0x00e1ff, 0xffe100, 0xff00ff, 0x44ff44, 0xff8800, 0x3f51b5];
var enemyGfx = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
enemyGfx.tint = fastTints[Math.floor(Math.random() * fastTints.length)];
self.radius = enemyGfx.width * 0.5;
self.speed = ENEMY_FAST_SPEED + Math.random() * 4;
self.moveType = 'straight';
self.shootCooldown = 40 + Math.floor(Math.random() * 40);
self.maxHealth = 1;
self.health = self.maxHealth;
// Health bar visual (background)
var healthBarBg = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 0.18,
tint: 0x222222,
y: enemyGfx.height * 0.7
});
// Health bar visual (foreground)
var healthBar = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.95,
scaleY: 0.12,
tint: 0x00e1ff,
y: enemyGfx.height * 0.7
});
self.update = function () {
self.y += self.speed;
// Clamp enemy inside left/right screen bounds
var margin = self.radius;
if (self.x < margin) self.x = margin;
if (self.x > GAME_W - margin) self.x = GAME_W - margin;
self.shootCooldown--;
// Update health bar
healthBar.scaleX = 0.95 * (self.health / self.maxHealth);
if (self.health < 1) healthBar.scaleX = 0;
};
self.takeDamage = function (dmg) {
self.health -= dmg;
LK.effects.flashObject(self, 0x00e1ff, 60);
if (self.health <= 0) self.health = 0;
};
return self;
});
// Player Bullet
var PlayerBullet = Container.expand(function () {
var self = Container.call(this);
var bulletGfx = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = PLAYER_BULLET_SPEED;
self.update = function () {
self.y += self.speed;
};
return self;
});
// Powerup
var Powerup = Container.expand(function () {
var self = Container.call(this);
// 50 unique powerup types (only keep those that work in this engine)
var types = ['rapid', 'shield', 'heal', 'doubleScore', 'slowMotion', 'tripleShot', 'megaShield', 'invincibility', 'scoreBomb', 'clearBullets', 'freezeEnemies', 'miniEnemies', 'giantShip', 'tinyShip', 'reverseBullets', 'homingBullets', 'reflectBullets', 'healthBoost', 'scoreBoost', 'enemySlow', 'enemyStun', 'extraLife', 'superHeal', 'scoreMultiplier', 'enemyShrink', 'enemyGrow', 'playerShrink', 'playerGrow', 'bulletSpeedUp', 'bulletSlow', 'enemyBulletSlow', 'enemyBulletFast', 'playerBulletBig', 'playerBulletSmall', 'enemyBulletBig', 'enemyBulletSmall', 'instantKill', 'enemyConfuse', 'playerConfuse', 'enemyInvisible', 'playerInvisible', 'enemySplit', 'playerSplit', 'enemyClone', 'playerClone', 'enemyTeleport', 'playerTeleport', 'enemyReverse', 'playerReverse', 'enemyFreezeLong'];
// Only keep types that are actually implemented and work
var workingTypes = ['rapid', 'shield', 'heal', 'doubleScore', 'slowMotion', 'tripleShot', 'megaShield', 'invincibility', 'scoreBomb', 'clearBullets', 'freezeEnemies', 'miniEnemies', 'giantShip', 'tinyShip', 'reverseBullets', 'homingBullets', 'reflectBullets', 'healthBoost', 'scoreBoost', 'enemySlow', 'enemyStun', 'extraLife', 'superHeal', 'scoreMultiplier', 'enemyShrink', 'enemyGrow', 'playerShrink', 'playerGrow', 'bulletSpeedUp', 'bulletSlow', 'enemyBulletSlow', 'enemyBulletFast', 'playerBulletBig', 'playerBulletSmall', 'enemyBulletBig', 'enemyBulletSmall', 'instantKill', 'enemyConfuse', 'playerConfuse', 'enemyInvisible', 'playerInvisible', 'enemySplit', 'playerSplit', 'enemyClone', 'playerClone', 'enemyTeleport', 'playerTeleport', 'enemyReverse', 'playerReverse', 'enemyFreezeLong'];
// Remove types that do not work in this engine (keep only those that can be implemented)
workingTypes = ['rapid', 'shield', 'heal', 'doubleScore', 'slowMotion', 'tripleShot', 'megaShield', 'invincibility', 'scoreBomb', 'clearBullets', 'freezeEnemies', 'miniEnemies', 'giantShip', 'tinyShip', 'reverseBullets', 'homingBullets', 'reflectBullets', 'healthBoost', 'scoreBoost', 'enemySlow', 'enemyStun'];
// Pick a random type, with increased chance for 'rapid' (firerate) and multi-powerup
var r = Math.random();
if (r < 0.16) self.type = 'rapid';else if (r < 0.23) self.type = 'shield';else if (r < 0.29) self.type = 'heal';else if (r < 0.34) self.type = 'doubleScore';else if (r < 0.39) self.type = 'slowMotion';else if (r < 0.44) self.type = 'tripleShot';else if (r < 0.48) self.type = 'megaShield';else if (r < 0.52) self.type = 'invincibility';else if (r < 0.56) self.type = 'scoreBomb';else if (r < 0.60) self.type = 'clearBullets';else if (r < 0.64) self.type = 'freezeEnemies';else if (r < 0.68) self.type = 'miniEnemies';else if (r < 0.72) self.type = 'giantShip';else if (r < 0.76) self.type = 'tinyShip';else if (r < 0.80) self.type = 'reverseBullets';else if (r < 0.84) self.type = 'homingBullets';else if (r < 0.88) self.type = 'reflectBullets';else if (r < 0.91) self.type = 'healthBoost';else if (r < 0.94) self.type = 'scoreBoost';else if (r < 0.97) self.type = 'enemySlow';else self.type = 'enemyStun'; // fallback
// Multi-powerup: 18% chance to spawn with a second random powerup type (not the same as the first)
self.extraTypes = [];
if (Math.random() < 0.18) {
// Pick a second type, different from the first, only from workingTypes
var extraType;
do {
extraType = workingTypes[Math.floor(Math.random() * workingTypes.length)];
} while (extraType === self.type);
self.extraTypes.push(extraType);
}
var powerupGfx = self.attachAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5
});
// Tint by type for visual feedback
if (self.type === 'rapid') powerupGfx.tint = 0xffe100;else if (self.type === 'shield') powerupGfx.tint = 0x00ffff;else if (self.type === 'heal') powerupGfx.tint = 0x44ff44;else if (self.type === 'doubleScore') powerupGfx.tint = 0xff00ff;else if (self.type === 'slowMotion') powerupGfx.tint = 0x8888ff;else if (self.type === 'tripleShot') powerupGfx.tint = 0xff8800;else if (self.type === 'megaShield') powerupGfx.tint = 0x00ff88;else if (self.type === 'invincibility') powerupGfx.tint = 0xffffff;else if (self.type === 'scoreBomb') powerupGfx.tint = 0xff2222;else if (self.type === 'clearBullets') powerupGfx.tint = 0x00ff00;else if (self.type === 'freezeEnemies') powerupGfx.tint = 0x00e1ff;else if (self.type === 'miniEnemies') powerupGfx.tint = 0xffb300;else if (self.type === 'giantShip') powerupGfx.tint = 0x8e24aa;else if (self.type === 'tinyShip') powerupGfx.tint = 0x3949ab;else if (self.type === 'reverseBullets') powerupGfx.tint = 0x00bcd4;else if (self.type === 'homingBullets') powerupGfx.tint = 0x43a047;else if (self.type === 'reflectBullets') powerupGfx.tint = 0xcddc39;else if (self.type === 'healthBoost') powerupGfx.tint = 0xff1744;else if (self.type === 'scoreBoost') powerupGfx.tint = 0xffea00;else if (self.type === 'enemySlow') powerupGfx.tint = 0x607d8b;else if (self.type === 'enemyStun') powerupGfx.tint = 0x6d4c41;
self.speed = 10;
self.update = function () {
self.y += self.speed;
};
return self;
});
// Player Ship
var Ship = Container.expand(function () {
var self = Container.call(this);
var shipGfx = self.attachAsset('ship', {
anchorX: 0.5,
anchorY: 0.5
});
// Use a bright, lively cyan as the default color for the player ship
shipGfx.tint = 0x33c1ff;
self.radius = shipGfx.width * 0.5;
self.shootCooldown = 0;
self.rapidFire = false;
self.rapidFireTimer = 0;
self.shield = false;
self.shieldTimer = 0;
// Health system for player
self.maxHealth = 28; // Reduced survivability for the player ship (harder)
self.health = self.maxHealth;
// Health bar visual (background)
var healthBarBg = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.2,
scaleY: 0.25,
tint: 0x222222,
y: shipGfx.height * 0.7
});
// Health bar visual (foreground)
var healthBar = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 0.18,
tint: 0xff4444,
y: shipGfx.height * 0.7
});
// Visual shield indicator
var shieldGfx = self.attachAsset('ship', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.3,
scaleY: 1.3,
tint: 0x00ffff
});
shieldGfx.alpha = 0.25;
shieldGfx.visible = false;
self.update = function () {
// Handle powerup timers
if (self.rapidFire) {
self.rapidFireTimer--;
// Visual feedback for rapid fire: pulse ship color
shipGfx.tint = LK.ticks % 10 < 5 ? 0xffe100 : 0x33c1ff;
if (self.rapidFireTimer <= 0) {
self.rapidFire = false;
shipGfx.tint = 0x33c1ff;
}
} else {
shipGfx.tint = 0x33c1ff;
}
if (self.shield) {
self.shieldTimer--;
shieldGfx.visible = true;
if (self.shieldTimer <= 0) {
self.shield = false;
shieldGfx.visible = false;
}
} else {
shieldGfx.visible = false;
}
// Update health bar
healthBar.scaleX = 2 * (self.health / self.maxHealth);
if (self.health < 1) healthBar.scaleX = 0;
};
self.activateRapidFire = function (duration) {
self.rapidFire = true;
self.rapidFireTimer = duration;
};
self.activateShield = function (duration) {
self.shield = true;
self.shieldTimer = duration;
shieldGfx.visible = true;
};
self.takeDamage = function (dmg) {
if (self.shield) return;
self.health -= dmg;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health < 0) self.health = 0;
};
self.heal = function (amount) {
self.health += amount;
if (self.health > self.maxHealth) self.health = self.maxHealth;
};
return self;
});
// SpiralEnemy: moves in a spiral pattern and shoots in bursts
var SpiralEnemy = Container.expand(function () {
var self = Container.call(this);
var spiralTints = [0x9c27b0, 0xff00ff, 0xffe100, 0x00e1ff, 0x44ff44, 0xff8800];
var enemyGfx = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
enemyGfx.tint = spiralTints[Math.floor(Math.random() * spiralTints.length)];
self.radius = enemyGfx.width * 0.5;
self.speed = 4 + Math.random() * 2;
self.angle = Math.random() * Math.PI * 2;
self.spiralRadius = 0;
self.spiralGrow = 1.2 + Math.random() * 0.7;
self.shootCooldown = 40 + Math.floor(Math.random() * 40);
self.burstCount = 0;
self.maxHealth = 2;
self.health = self.maxHealth;
// Health bar visual (background)
var healthBarBg = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 0.18,
tint: 0x222222,
y: enemyGfx.height * 0.7
});
// Health bar visual (foreground)
var healthBar = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.95,
scaleY: 0.12,
tint: enemyGfx.tint,
y: enemyGfx.height * 0.7
});
self.update = function () {
// Spiral movement
self.angle += 0.09;
self.spiralRadius += self.spiralGrow;
self.x += Math.cos(self.angle) * self.spiralGrow * 1.2;
self.y += self.speed + Math.sin(self.angle) * 2;
// Clamp inside screen
var margin = self.radius;
if (self.x < margin) self.x = margin;
if (self.x > GAME_W - margin) self.x = GAME_W - margin;
self.shootCooldown--;
// Health bar
healthBar.scaleX = 0.95 * (self.health / self.maxHealth);
if (self.health < 1) healthBar.scaleX = 0;
};
self.takeDamage = function (dmg) {
self.health -= dmg;
LK.effects.flashObject(self, 0x9c27b0, 80);
if (self.health <= 0) self.health = 0;
};
return self;
});
// TankEnemy: slow, high health, shoots more
var TankEnemy = Container.expand(function () {
var self = Container.call(this);
var tankTints = [0xff4444, 0x00e1ff, 0xffe100, 0x00ff88, 0x3f51b5, 0xff8800];
var enemyGfx = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.3,
scaleY: 1.3
});
enemyGfx.tint = tankTints[Math.floor(Math.random() * tankTints.length)];
self.radius = enemyGfx.width * 0.65;
self.speed = ENEMY_TANK_SPEED + Math.random() * 1.2;
self.moveType = 'sine';
self.sinePhase = Math.random() * Math.PI * 2;
self.sineAmp = 180 + Math.random() * 80;
self.shootCooldown = 30 + Math.floor(Math.random() * 30);
self.maxHealth = 4;
self.health = self.maxHealth;
// Health bar visual (background)
var healthBarBg = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.3,
scaleY: 0.18,
tint: 0x222222,
y: enemyGfx.height * 0.9
});
// Health bar visual (foreground)
var healthBar = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.25,
scaleY: 0.12,
tint: 0xff4444,
y: enemyGfx.height * 0.9
});
self.update = function () {
self.y += self.speed * 0.7;
self.x += Math.sin(self.y / 120 + self.sinePhase) * 10;
// Clamp enemy inside left/right screen bounds
var margin = self.radius;
if (self.x < margin) self.x = margin;
if (self.x > GAME_W - margin) self.x = GAME_W - margin;
self.shootCooldown--;
// Update health bar
healthBar.scaleX = 1.25 * (self.health / self.maxHealth);
if (self.health < 1) healthBar.scaleX = 0;
};
self.takeDamage = function (dmg) {
self.health -= dmg;
LK.effects.flashObject(self, 0xff4444, 100);
if (self.health <= 0) self.health = 0;
};
return self;
});
// ZigzagEnemy: moves in zigzag, normal health
var ZigzagEnemy = Container.expand(function () {
var self = Container.call(this);
var zigzagTints = [0xffe100, 0xff00ff, 0x44ff44, 0x00e1ff, 0xff8800, 0x9c27b0];
var enemyGfx = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
enemyGfx.tint = zigzagTints[Math.floor(Math.random() * zigzagTints.length)];
self.radius = enemyGfx.width * 0.5;
self.speed = ENEMY_ZIGZAG_SPEED + Math.random() * 3;
self.moveType = 'zigzag';
self.zigzagDir = Math.random() < 0.5 ? -1 : 1;
self.zigzagTimer = 0;
self.shootCooldown = 60 + Math.floor(Math.random() * 60);
self.maxHealth = 2;
self.health = self.maxHealth;
// Health bar visual (background)
var healthBarBg = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 0.18,
tint: 0x222222,
y: enemyGfx.height * 0.7
});
// Health bar visual (foreground)
var healthBar = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.95,
scaleY: 0.12,
tint: 0xffe100,
y: enemyGfx.height * 0.7
});
self.update = function () {
self.y += self.speed;
self.zigzagTimer++;
if (self.zigzagTimer % 30 === 0) self.zigzagDir *= -1;
self.x += self.zigzagDir * 18;
// Clamp enemy inside left/right screen bounds
var margin = self.radius;
if (self.x < margin) self.x = margin;
if (self.x > GAME_W - margin) self.x = GAME_W - margin;
self.shootCooldown--;
// Update health bar
healthBar.scaleX = 0.95 * (self.health / self.maxHealth);
if (self.health < 1) healthBar.scaleX = 0;
};
self.takeDamage = function (dmg) {
self.health -= dmg;
LK.effects.flashObject(self, 0xffe100, 80);
if (self.health <= 0) self.health = 0;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
// No title, no description
// Always backgroundColor is black
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Music
// Sound effects
// Powerup
// Enemy bullet
// Enemy
// Player bullet
// Spaceship (player)
// Game area
var GAME_W = 2048,
GAME_H = 2732;
// Score
var score = 0;
// High score (persistent for all users)
var highScore = storage.highScore || 0;
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// High score UI (visible to all users)
var highScoreTxt = new Text2('High Score: ' + highScore, {
size: 60,
fill: 0xffe100,
stroke: 0x000000,
strokeThickness: 6
});
highScoreTxt.anchor.set(0.5, 0);
highScoreTxt.x = scoreTxt.x;
highScoreTxt.y = scoreTxt.y + 110;
LK.gui.top.addChild(highScoreTxt);
var multiplierTxt = new Text2('x1', {
size: 70,
fill: 0x00ff88
});
multiplierTxt.anchor.set(0.5, 0);
multiplierTxt.x = scoreTxt.x + 220;
multiplierTxt.y = scoreTxt.y + 40;
LK.gui.top.addChild(multiplierTxt);
// Combo system variables
var comboCount = 0;
var comboTimer = 0;
var lastComboTime = 0;
// Double score powerup
var doubleScoreActive = false;
var doubleScoreTimer = 0;
// Slow motion powerup
var slowMotionActive = false;
var slowMotionTimer = 0;
// Triple shot powerup
var tripleShotActive = false;
var tripleShotTimer = 0;
// Invincibility powerup
var invincibilityActive = false;
var invincibilityTimer = 0;
// Player ship
var ship = new Ship();
game.addChild(ship);
ship.x = GAME_W / 2;
ship.y = GAME_H - 350;
// --- UI Health Text for Player ---
var shipHealthTxt = new Text2('', {
size: 60,
fill: 0xff4444,
stroke: 0x000000,
strokeThickness: 6
});
shipHealthTxt.anchor.set(0.5, 1);
shipHealthTxt.x = ship.x;
shipHealthTxt.y = ship.y - ship.radius - 60;
game.addChild(shipHealthTxt);
// Arrays for game objects
var playerBullets = [];
var enemies = [];
var enemyBullets = [];
var powerups = [];
// --- UI Health Text for Enemies ---
// We'll keep a parallel array to track health text objects for each enemy
var enemyHealthTxts = [];
// Dragging
var dragNode = null;
// Difficulty increased: enemies spawn more frequently
var enemySpawnTimer = 0;
var enemySpawnInterval = 70; // faster spawn
var minEnemyInterval = 20; // minimum interval decreased
var enemySpeedInc = 0;
// Powerup spawn
var powerupTimer = 0;
var powerupInterval = 120; // Powerups much more frequent
// --- Reflex tuning: adjust bullet and enemy speeds globally ---
// Restore normal speeds for enemies and bullets
var PLAYER_BULLET_SPEED = -16; // normal bullet speed
var ENEMY_BULLET_SPEED = 8; // normal enemy bullet speed
var ENEMY_BASE_SPEED = 4; // normal base enemy speed
var ENEMY_FAST_SPEED = 7; // normal fast enemy speed
var ENEMY_TANK_SPEED = 1.5; // normal tank speed
var ENEMY_ZIGZAG_SPEED = 4; // normal zigzag speed
var PLAYER_SHOOT_RATE = 12; // normal player shoot rate
// Last intersect states
var lastShipEnemyIntersect = false;
var lastShipEnemyBulletIntersect = false;
var lastShipPowerupIntersect = false;
// Music
LK.playMusic('bgmusic');
// Move handler (drag ship)
function handleMove(x, y, obj) {
if (dragNode === ship) {
// Clamp ship inside game area (with margin)
var margin = 80;
var nx = Math.max(margin, Math.min(GAME_W - margin, x));
var ny = Math.max(margin, Math.min(GAME_H - margin, y));
ship.x = nx;
ship.y = ny;
// Touch feedback: scale ship up slightly while dragging, then back
ship.scale.set(1.15, 1.15);
tween(ship.scale, {
x: 1,
y: 1
}, {
duration: 120
});
}
}
game.move = handleMove;
var dragAnywhereHintShown = false;
game.down = function (x, y, obj) {
// User-friendly: allow drag to start if touch is on ship OR anywhere in lower half of screen
var dx = x - ship.x,
dy = y - ship.y;
var onShip = dx * dx + dy * dy < ship.radius * ship.radius * 1.2;
var inLowerHalf = y > GAME_H / 2;
if (onShip || inLowerHalf) {
dragNode = ship;
handleMove(x, y, obj);
// Visual feedback: flash ship blue for 120ms on drag start
LK.effects.flashObject(ship, 0x33c1ff, 120);
// Show floating helper text only the first time user drags from lower half (not on ship)
if (!onShip && inLowerHalf && !dragAnywhereHintShown) {
showFloatingText("Tip: Drag anywhere below to move!", ship.x, ship.y - 180, 0x33c1ff);
dragAnywhereHintShown = true;
}
}
};
game.up = function (x, y, obj) {
dragNode = null;
};
// Main update loop
game.update = function () {
// Update ship
ship.update();
// --- Update player health UI text ---
shipHealthTxt.setText(ship.health + " / " + ship.maxHealth);
shipHealthTxt.x = ship.x;
shipHealthTxt.y = ship.y - ship.radius - 60;
// --- Update enemy health UI text ---
// Remove any health text objects for destroyed enemies
for (var i = enemyHealthTxts.length - 1; i >= 0; i--) {
if (i >= enemies.length || !enemies[i] || enemies[i].destroyed) {
if (enemyHealthTxts[i]) {
enemyHealthTxts[i].destroy();
}
enemyHealthTxts.splice(i, 1);
}
}
// Sync health text objects to enemies
for (var i = 0; i < enemies.length; i++) {
var e = enemies[i];
var txt = enemyHealthTxts[i];
if (!txt) {
txt = new Text2('', {
size: 48,
fill: 0xffffff,
stroke: 0x000000,
strokeThickness: 5
});
txt.anchor.set(0.5, 1);
game.addChild(txt);
enemyHealthTxts[i] = txt;
}
txt.setText(e.health + " / " + e.maxHealth);
txt.x = e.x;
txt.y = e.y - (e.radius || 60) - 38;
txt.visible = e.health > 0;
}
// --- Double Score timer ---
if (doubleScoreActive) {
doubleScoreTimer--;
if (doubleScoreTimer <= 0) {
doubleScoreActive = false;
showFloatingText("Double Score Ended", ship.x, ship.y - 120, 0xff00ff);
}
}
// --- Slow Motion timer ---
if (slowMotionActive) {
slowMotionTimer--;
if (slowMotionTimer <= 0) {
slowMotionActive = false;
showFloatingText("Speed Restored", ship.x, ship.y - 120, 0x8888ff);
}
}
// --- Triple Shot timer ---
if (tripleShotActive) {
tripleShotTimer--;
if (tripleShotTimer <= 0) {
tripleShotActive = false;
showFloatingText("Triple Shot Ended", ship.x, ship.y - 120, 0xff8800);
}
}
// --- Invincibility timer ---
if (invincibilityActive) {
invincibilityTimer--;
if (invincibilityTimer <= 0) {
invincibilityActive = false;
showFloatingText("Invincibility Ended", ship.x, ship.y - 120, 0xffffff);
}
}
// --- Freeze Enemies timer ---
if (typeof freezeEnemiesTimer !== "undefined" && freezeEnemiesTimer > 0) {
freezeEnemiesTimer--;
if (freezeEnemiesTimer <= 0) {
showFloatingText("Enemies Unfrozen", ship.x, ship.y - 120, 0x00e1ff);
}
}
// --- Homing Bullets timer ---
if (typeof homingBulletsTimer !== "undefined" && homingBulletsTimer > 0) {
homingBulletsTimer--;
if (homingBulletsTimer <= 0) {
showFloatingText("Homing Ended", ship.x, ship.y - 120, 0x43a047);
}
}
// --- Reflect Bullets timer ---
if (typeof reflectBulletsTimer !== "undefined" && reflectBulletsTimer > 0) {
reflectBulletsTimer--;
if (reflectBulletsTimer <= 0) {
showFloatingText("Reflect Ended", ship.x, ship.y - 120, 0xcddc39);
}
}
// --- Enemy Slow timer ---
if (typeof enemySlowTimer !== "undefined" && enemySlowTimer > 0) {
enemySlowTimer--;
if (enemySlowTimer <= 0) {
showFloatingText("Enemies Normal Speed", ship.x, ship.y - 120, 0x607d8b);
}
}
// --- Enemy Stun timer ---
if (typeof enemyStunTimer !== "undefined" && enemyStunTimer > 0) {
enemyStunTimer--;
if (enemyStunTimer <= 0) {
showFloatingText("Enemies Unstunned", ship.x, ship.y - 120, 0x6d4c41);
}
}
// --- Player shooting ---
ship.shootCooldown--;
var shootRate = ship.rapidFire ? Math.max(6, Math.floor(PLAYER_SHOOT_RATE * 0.33)) : PLAYER_SHOOT_RATE;
if (ship.shootCooldown <= 0) {
// Auto-fire
if (tripleShotActive) {
for (var ts = -1; ts <= 1; ts++) {
var pb = new PlayerBullet();
pb.x = ship.x + ts * 38;
pb.y = ship.y - ship.radius - 30;
if (ts !== 0) pb.rotation = ts * 0.18;
playerBullets.push(pb);
game.addChild(pb);
}
} else {
var pb = new PlayerBullet();
pb.x = ship.x;
pb.y = ship.y - ship.radius - 30;
playerBullets.push(pb);
game.addChild(pb);
}
ship.shootCooldown = shootRate;
LK.getSound('shoot').play();
}
// --- 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);
}
}
// --- Update enemies ---
for (var i = enemies.length - 1; i >= 0; i--) {
var e = enemies[i];
// --- Enemy AI: dodge if player is close horizontally ---
if (Math.abs(ship.x - e.x) < 180 && Math.abs(ship.y - e.y) < 600) {
// Try to dodge left or right, but less distance for fairness
if (ship.x < e.x) e.x += 6 + Math.random() * 3;else e.x -= 6 + Math.random() * 3;
}
// --- Powerup effects: freeze, slow, stun ---
var freezeActive = typeof freezeEnemiesTimer !== "undefined" && freezeEnemiesTimer > 0;
var slowActive = typeof enemySlowTimer !== "undefined" && enemySlowTimer > 0 || slowMotionActive;
var stunActive = typeof enemyStunTimer !== "undefined" && enemyStunTimer > 0;
if (stunActive) {
// Stunned: don't update, don't shoot
// Visual feedback: pulse tint
if (LK.ticks % 20 < 10) e.tint = 0x6d4c41;else e.tint = 0xffffff;
} else if (freezeActive) {
// Frozen: don't update, don't shoot
if (LK.ticks % 20 < 10) e.tint = 0x00e1ff;else e.tint = 0xffffff;
} else {
// Slow: update less frequently
if (!slowActive || LK.ticks % 2 === 0) {
e.update();
}
}
// Destroy and remove enemies when they move off the bottom of the screen
if (e.y > GAME_H + e.radius) {
e.destroy();
enemies.splice(i, 1);
continue;
}
// (Removed clamping: enemies can now leave the screen at the bottom)
// Enemy shooting
if (!freezeActive && !stunActive) {
// SpiralEnemy: burst shoot 3 bullets in a spread
if (e.constructor === SpiralEnemy && e.shootCooldown <= 0) {
for (var b = -1; b <= 1; b++) {
var eb = new EnemyBullet();
eb.x = e.x;
eb.y = e.y + e.radius + 10;
// Spread: aim at ship, but offset angle
var dx = ship.x - e.x,
dy = ship.y - e.y;
var len = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx) + b * 0.18;
if (len > 0) {
eb.dirX = Math.cos(angle);
eb.dirY = Math.sin(angle);
}
enemyBullets.push(eb);
game.addChild(eb);
}
e.shootCooldown = 120 + Math.floor(Math.random() * 40);
} else if (e.shootCooldown <= 0) {
// BigEnemy shoots big bullet, others shoot normal
var eb;
if (e.constructor === BigEnemy) {
eb = new BigEnemyBullet();
eb.x = e.x;
eb.y = e.y + e.radius + 20;
// Aim at ship
var dx = ship.x - e.x,
dy = ship.y - e.y;
var len = Math.sqrt(dx * dx + dy * dy);
if (len > 0) {
eb.dirX = dx / len;
eb.dirY = dy / len;
}
enemyBullets.push(eb);
game.addChild(eb);
e.shootCooldown = 120 + Math.floor(Math.random() * 40);
} else {
eb = new EnemyBullet();
eb.x = e.x;
eb.y = e.y + e.radius + 10;
// Aim at ship
var dx = ship.x - e.x,
dy = ship.y - e.y;
var len = Math.sqrt(dx * dx + dy * dy);
if (len > 0) {
eb.dirX = dx / len;
eb.dirY = dy / len;
}
enemyBullets.push(eb);
game.addChild(eb);
e.shootCooldown = 90 + Math.floor(Math.random() * 60);
}
}
}
}
// --- Update enemy bullets ---
for (var i = enemyBullets.length - 1; i >= 0; i--) {
var eb = enemyBullets[i];
// Slow motion: update less frequently
var reflectActive = typeof reflectBulletsTimer !== "undefined" && reflectBulletsTimer > 0;
if (reflectActive && eb.dirY > 0 && eb.y > ship.y - 200) {
// Reflect bullets going downwards, near ship
eb.dirY *= -1;
showFloatingText("Reflected!", eb.x, eb.y, 0xcddc39);
}
if (!slowMotionActive || LK.ticks % 2 === 0) {
eb.update();
if (eb.x < -100 || eb.x > GAME_W + 100 || eb.y < -100 || eb.y > GAME_H + 100) {
eb.destroy();
enemyBullets.splice(i, 1);
}
}
}
// --- Homing Bullets effect for player bullets ---
if (typeof homingBulletsTimer !== "undefined" && homingBulletsTimer > 0) {
for (var i = 0; i < playerBullets.length; i++) {
var pb = playerBullets[i];
// Find nearest enemy
var nearest = null,
minDist = 99999;
for (var j = 0; j < enemies.length; j++) {
var dx = enemies[j].x - pb.x;
var dy = enemies[j].y - pb.y;
var dist = dx * dx + dy * dy;
if (dist < minDist) {
minDist = dist;
nearest = enemies[j];
}
}
if (nearest && minDist < 400 * 400) {
// Adjust bullet direction slightly toward enemy
var dx = nearest.x - pb.x;
var dy = nearest.y - pb.y;
var len = Math.sqrt(dx * dx + dy * dy);
if (len > 0) {
var steer = 0.18;
pb.x += dx / len * steer;
pb.y += dy / len * steer * 0.5;
}
}
}
}
// --- Update powerups ---
for (var i = powerups.length - 1; i >= 0; i--) {
var p = powerups[i];
p.update();
if (p.y > GAME_H + 100) {
p.destroy();
powerups.splice(i, 1);
}
}
// --- Collision: Player bullets vs Enemies ---
for (var i = playerBullets.length - 1; i >= 0; i--) {
var b = playerBullets[i];
for (var j = enemies.length - 1; j >= 0; j--) {
var e = enemies[j];
if (b.intersects(e)) {
// Hit enemy
e.takeDamage(2);
// Recoil effect for enemy
tween(e, {
y: e.y - 30
}, {
duration: 60,
yoyo: true,
repeat: 1
});
// Camera shake on hit (simulate with quick flash)
LK.getSound('enemyDown').play();
// Combo system variables (global scope, but initialize if undefined)
if (typeof comboCount === "undefined") comboCount = 0;
if (typeof comboTimer === "undefined") comboTimer = 0;
if (typeof lastComboTime === "undefined") lastComboTime = 0;
// Score only if enemy dies
if (e.health <= 0) {
// Combo logic: if last kill was within 2 seconds, increase combo
var now = LK.ticks;
if (now - lastComboTime < 120) {
comboCount++;
} else {
comboCount = 1;
}
lastComboTime = now;
comboTimer = 120; // 2 seconds to continue combo
// Combo bonus: +10 base, +5 per combo after first, multiplied by scoreMultiplier
if (typeof scoreMultiplier === "undefined") scoreMultiplier = 1;
var comboBonus = 10 + (comboCount > 1 ? (comboCount - 1) * 5 : 0);
comboBonus *= scoreMultiplier;
if (doubleScoreActive) comboBonus *= 2;
score += comboBonus;
// Animate score text for feedback
scoreTxt.setText(score);
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
if (typeof highScoreTxt !== "undefined") {
highScoreTxt.setText("High Score: " + highScore);
highScoreTxt.scale.set(1.2, 1.2);
tween(highScoreTxt.scale, {
x: 1,
y: 1
}, {
duration: 300
});
}
}
if (typeof multiplierTxt !== "undefined") {
multiplierTxt.setText("x" + scoreMultiplier);
multiplierTxt.scale.set(1.3, 1.3);
tween(multiplierTxt.scale, {
x: 1,
y: 1
}, {
duration: 200
});
}
scoreTxt.scale.set(1.25, 1.25);
tween(scoreTxt.scale, {
x: 1,
y: 1
}, {
duration: 200
});
// Show floating text for combo
if (comboCount > 1) {
showFloatingText("Combo x" + comboCount + "! +" + comboBonus, e.x, e.y - 80, 0xffe100);
if (comboCount % 3 === 0) {
showFloatingText("Multiplier: x" + scoreMultiplier, e.x, e.y - 160, 0x00ff88);
}
} else {
showFloatingText("+10", e.x, e.y - 80, 0xffffff);
}
// Fun: random emoji on enemy kill!
var emojis = ["🚀", "💥", "✨", "🔥", "😎", "🎉", "🛸", "👾"];
var emoji = emojis[Math.floor(Math.random() * emojis.length)];
showFloatingText(emoji, e.x + (Math.random() * 80 - 40), e.y - 120, 0xffffff);
e.destroy();
enemies.splice(j, 1);
}
b.destroy();
playerBullets.splice(i, 1);
break;
}
}
}
// --- Combo timer update ---
if (typeof comboTimer !== "undefined" && comboTimer > 0) {
comboTimer--;
if (comboTimer === 0) {
comboCount = 0;
scoreMultiplier = 1; // Reset multiplier on missed combo
}
}
if (comboCount > 1) {
scoreMultiplier = 1 + Math.floor(comboCount / 3); // Every 3 combo increases multiplier by 1
} else {
scoreMultiplier = 1;
}
// --- Collision: Ship vs Enemies ---
var shipEnemyIntersect = false;
for (var i = 0; i < enemies.length; i++) {
var e = enemies[i];
if (ship.intersects(e)) {
shipEnemyIntersect = true;
break;
}
}
if (!lastShipEnemyIntersect && shipEnemyIntersect) {
if (invincibilityActive) {
LK.effects.flashObject(ship, 0xffffff, 400);
showFloatingText("No Damage!", ship.x, ship.y - 120, 0xffffff);
} else if (ship.shield) {
// Absorb hit, destroy enemy
LK.effects.flashObject(ship, 0x00ffff, 400);
for (var i = 0; i < enemies.length; i++) {
if (ship.intersects(enemies[i])) {
enemies[i].destroy();
enemies.splice(i, 1);
break;
}
}
ship.shield = false;
ship.shieldTimer = 0;
ship.children[1].visible = false;
} else {
// Take damage
ship.takeDamage(2);
// (Screen flash removed)
LK.getSound('hit').play();
if (ship.health <= 0) {
LK.showGameOver();
return;
}
}
}
lastShipEnemyIntersect = shipEnemyIntersect;
// --- Collision: Ship vs Enemy Bullets ---
var shipEnemyBulletIntersect = false;
for (var i = 0; i < enemyBullets.length; i++) {
var eb = enemyBullets[i];
if (ship.intersects(eb)) {
shipEnemyBulletIntersect = true;
break;
}
}
if (!lastShipEnemyBulletIntersect && shipEnemyBulletIntersect) {
if (invincibilityActive) {
LK.effects.flashObject(ship, 0xffffff, 400);
showFloatingText("No Damage!", ship.x, ship.y - 120, 0xffffff);
} else if (ship.shield) {
LK.effects.flashObject(ship, 0x00ffff, 400);
// Remove bullet
for (var i = 0; i < enemyBullets.length; i++) {
if (ship.intersects(enemyBullets[i])) {
enemyBullets[i].destroy();
enemyBullets.splice(i, 1);
break;
}
}
ship.shield = false;
ship.shieldTimer = 0;
ship.children[1].visible = false;
} else {
// Take damage
ship.takeDamage(1);
// (Screen flash removed)
LK.getSound('hit').play();
if (ship.health <= 0) {
LK.showGameOver();
return;
}
}
}
lastShipEnemyBulletIntersect = shipEnemyBulletIntersect;
// --- Collision: Ship vs Powerups ---
var shipPowerupIntersect = false;
var _loop = function _loop() {
p = powerups[i];
if (ship.intersects(p)) {
// --- 20 Powerup Types Logic ---
// Helper to apply a powerup type (for multi-powerup support)
var applyPowerupType = function applyPowerupType(type) {
// 1. rapid
if (type === 'rapid') {
if (ship.rapidFire) {
ship.rapidFireTimer += 240;
showFloatingText("Rapid Fire Combo!", ship.x, ship.y - 120, 0xffe100);
} else {
ship.activateRapidFire(360);
showFloatingText("Rapid Fire!", ship.x, ship.y - 120, 0xffe100);
}
// 2. shield
} else if (type === 'shield') {
if (ship.shield) {
ship.shieldTimer += 320;
showFloatingText("Shield Combo!", ship.x, ship.y - 120, 0x00ffff);
} else {
ship.activateShield(480);
showFloatingText("Shield!", ship.x, ship.y - 120, 0x00ffff);
}
// 3. heal
} else if (type === 'heal') {
ship.heal(4);
showFloatingText("+4 Health", ship.x, ship.y - 120, 0x44ff44);
// 4. doubleScore
} else if (type === 'doubleScore') {
if (doubleScoreActive) {
doubleScoreTimer += 400;
showFloatingText("Double Score Combo!", ship.x, ship.y - 120, 0xff00ff);
} else {
doubleScoreActive = true;
doubleScoreTimer = 600;
showFloatingText("Double Score!", ship.x, ship.y - 120, 0xff00ff);
}
// 5. slowMotion
} else if (type === 'slowMotion') {
if (slowMotionActive) {
slowMotionTimer += 300;
showFloatingText("Slow Motion Combo!", ship.x, ship.y - 120, 0x8888ff);
} else {
slowMotionActive = true;
slowMotionTimer = 420;
showFloatingText("Slow Motion!", ship.x, ship.y - 120, 0x8888ff);
}
// 6. tripleShot
} else if (type === 'tripleShot') {
if (tripleShotActive) {
tripleShotTimer += 360;
showFloatingText("Triple Shot Combo!", ship.x, ship.y - 120, 0xff8800);
} else {
tripleShotActive = true;
tripleShotTimer = 540;
showFloatingText("Triple Shot!", ship.x, ship.y - 120, 0xff8800);
}
// 7. megaShield
} else if (type === 'megaShield') {
if (ship.shield) {
ship.shieldTimer += 800;
showFloatingText("Mega Shield Combo!", ship.x, ship.y - 120, 0x00ff88);
} else {
ship.activateShield(1200);
showFloatingText("Mega Shield!", ship.x, ship.y - 120, 0x00ff88);
}
// 8. invincibility
} else if (type === 'invincibility') {
if (invincibilityActive) {
invincibilityTimer += 240;
showFloatingText("Invincible Combo!", ship.x, ship.y - 120, 0xffffff);
} else {
invincibilityActive = true;
invincibilityTimer = 360;
showFloatingText("Invincible!", ship.x, ship.y - 120, 0xffffff);
}
// 9. scoreBomb
} else if (type === 'scoreBomb') {
var killed = 0;
for (var si = enemies.length - 1; si >= 0; si--) {
if (enemies[si].y > 0) {
enemies[si].destroy();
enemies.splice(si, 1);
killed++;
}
}
if (killed > 0) {
var bombScore = 10 * killed;
score += bombScore;
scoreTxt.setText(score);
showFloatingText("Score Bomb! +" + bombScore, ship.x, ship.y - 120, 0xff2222);
} else {
showFloatingText("Score Bomb! (No targets)", ship.x, ship.y - 120, 0xff2222);
}
// 10. clearBullets
} else if (type === 'clearBullets') {
var cleared = 0;
for (var ci = enemyBullets.length - 1; ci >= 0; ci--) {
enemyBullets[ci].destroy();
enemyBullets.splice(ci, 1);
cleared++;
}
showFloatingText("Cleared " + cleared + " Bullets!", ship.x, ship.y - 120, 0x00ff00);
// 11. freezeEnemies
} else if (type === 'freezeEnemies') {
if (typeof freezeEnemiesTimer === "undefined") freezeEnemiesTimer = 0;
if (freezeEnemiesTimer > 0) {
freezeEnemiesTimer += 180;
showFloatingText("Freeze Combo!", ship.x, ship.y - 120, 0x00e1ff);
} else {
freezeEnemiesTimer = 360;
showFloatingText("Enemies Frozen!", ship.x, ship.y - 120, 0x00e1ff);
}
// 12. miniEnemies
} else if (type === 'miniEnemies') {
for (var mi = 0; mi < enemies.length; mi++) {
enemies[mi].scale.set(0.6, 0.6);
enemies[mi].radius *= 0.6;
}
showFloatingText("Mini Enemies!", ship.x, ship.y - 120, 0xffb300);
// 13. giantShip
} else if (type === 'giantShip') {
ship.scale.set(1.7, 1.7);
showFloatingText("Giant Ship!", ship.x, ship.y - 120, 0x8e24aa);
tween(ship.scale, {
x: 1,
y: 1
}, {
duration: 1200
});
// 14. tinyShip
} else if (type === 'tinyShip') {
ship.scale.set(0.5, 0.5);
showFloatingText("Tiny Ship!", ship.x, ship.y - 120, 0x3949ab);
tween(ship.scale, {
x: 1,
y: 1
}, {
duration: 1200
});
// 15. reverseBullets
} else if (type === 'reverseBullets') {
for (var ri = 0; ri < enemyBullets.length; ri++) {
enemyBullets[ri].dirY *= -1;
}
showFloatingText("Reverse Bullets!", ship.x, ship.y - 120, 0x00bcd4);
// 16. homingBullets
} else if (type === 'homingBullets') {
if (typeof homingBulletsTimer === "undefined") homingBulletsTimer = 0;
if (homingBulletsTimer > 0) {
homingBulletsTimer += 180;
showFloatingText("Homing Combo!", ship.x, ship.y - 120, 0x43a047);
} else {
homingBulletsTimer = 360;
showFloatingText("Homing Bullets!", ship.x, ship.y - 120, 0x43a047);
}
// 17. reflectBullets
} else if (type === 'reflectBullets') {
if (typeof reflectBulletsTimer === "undefined") reflectBulletsTimer = 0;
if (reflectBulletsTimer > 0) {
reflectBulletsTimer += 180;
showFloatingText("Reflect Combo!", ship.x, ship.y - 120, 0xcddc39);
} else {
reflectBulletsTimer = 360;
showFloatingText("Reflect Bullets!", ship.x, ship.y - 120, 0xcddc39);
}
// 18. healthBoost
} else if (type === 'healthBoost') {
ship.maxHealth += 4;
ship.heal(4);
showFloatingText("Max Health Up!", ship.x, ship.y - 120, 0xff1744);
// 19. scoreBoost
} else if (type === 'scoreBoost') {
score += 100;
scoreTxt.setText(score);
showFloatingText("+100 Score!", ship.x, ship.y - 120, 0xffea00);
// 20. enemySlow
} else if (type === 'enemySlow') {
if (typeof enemySlowTimer === "undefined") enemySlowTimer = 0;
if (enemySlowTimer > 0) {
enemySlowTimer += 180;
showFloatingText("Enemy Slow Combo!", ship.x, ship.y - 120, 0x607d8b);
} else {
enemySlowTimer = 360;
showFloatingText("Enemies Slowed!", ship.x, ship.y - 120, 0x607d8b);
}
// 21. enemyStun (fallback, rarely picked)
} else if (type === 'enemyStun') {
if (typeof enemyStunTimer === "undefined") enemyStunTimer = 0;
if (enemyStunTimer > 0) {
enemyStunTimer += 120;
showFloatingText("Stun Combo!", ship.x, ship.y - 120, 0x6d4c41);
} else {
enemyStunTimer = 240;
showFloatingText("Enemies Stunned!", ship.x, ship.y - 120, 0x6d4c41);
}
}
}; // Always apply the main powerup type
shipPowerupIntersect = true;
// Apply powerup
LK.getSound('powerup').play();
// Fun: confetti burst effect on powerup collect!
for (confetti = 0; confetti < 18; confetti++) {
(function () {
var part = new Container();
var c = part.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4 + Math.random() * 0.5,
scaleY: 0.4 + Math.random() * 0.5,
tint: 0xffe100 + Math.floor(Math.random() * 0xffffff)
});
part.x = ship.x;
part.y = ship.y - 40;
part.alpha = 0.85;
game.addChild(part);
var angle = Math.random() * Math.PI * 2;
var dist = 120 + Math.random() * 80;
var dx = Math.cos(angle) * dist;
var dy = Math.sin(angle) * dist;
tween(part, {
x: part.x + dx,
y: part.y + dy,
alpha: 0
}, {
duration: 600 + Math.random() * 400,
onFinish: function onFinish() {
part.destroy();
}
});
})();
}
applyPowerupType(p.type);
// If this powerup has extraTypes (multi-powerup), apply those as well
if (p.extraTypes && p.extraTypes.length > 0) {
for (et = 0; et < p.extraTypes.length; et++) {
applyPowerupType(p.extraTypes[et]);
// Show a floating text for the extra powerup
showFloatingText("Bonus: " + p.extraTypes[et], ship.x, ship.y - 180 - et * 60, 0xffe100);
}
}
// Combo fusion: if multiple effects are active, show a special floating text!
comboActiveCount = 0;
if (ship.rapidFire) comboActiveCount++;
if (doubleScoreActive) comboActiveCount++;
if (tripleShotActive) comboActiveCount++;
if (slowMotionActive) comboActiveCount++;
if (invincibilityActive) comboActiveCount++;
if (ship.shield) comboActiveCount++;
if (typeof freezeEnemiesTimer !== "undefined" && freezeEnemiesTimer > 0) comboActiveCount++;
if (typeof homingBulletsTimer !== "undefined" && homingBulletsTimer > 0) comboActiveCount++;
if (typeof reflectBulletsTimer !== "undefined" && reflectBulletsTimer > 0) comboActiveCount++;
if (typeof enemySlowTimer !== "undefined" && enemySlowTimer > 0) comboActiveCount++;
if (typeof enemyStunTimer !== "undefined" && enemyStunTimer > 0) comboActiveCount++;
if (comboActiveCount >= 3) {
showFloatingText("Combo Fusion! (" + comboActiveCount + ")", ship.x, ship.y - 220, 0xffe100);
}
p.destroy();
powerups.splice(i, 1);
}
},
p,
confetti,
et,
comboActiveCount;
for (var i = powerups.length - 1; i >= 0; i--) {
_loop();
}
lastShipPowerupIntersect = shipPowerupIntersect;
// --- Mission system: show floating text for milestones ---
if (score > 0 && score % 100 === 0 && !game['milestone_' + score]) {
showFloatingText("Milestone: " + score + "!", GAME_W / 2, 320, 0xffe100);
game['milestone_' + score] = true;
}
// --- Background color cycling every 5000 points after 5000 ---
// Define a palette of beautiful background colors, starting with sky blue
if (typeof bgColors === "undefined") {
var bgColors = [0x87ceeb,
// sky blue
0x1a237e,
// indigo
0x004d40,
// teal dark
0x263238,
// blue grey
0x880e4f,
// deep pink
0x1565c0,
// blue
0x2e7d32,
// green
0xff6f00,
// orange
0x4a148c,
// purple
0x212121,
// dark grey
0x00695c,
// teal
0x3949ab,
// blue indigo
0xc62828,
// red
0x00838f,
// cyan
0x6d4c41,
// brown
0x283593,
// deep blue
0x000000 // black (always last)
];
var lastBgScoreStep = 0;
// Set initial background to sky blue
game.setBackgroundColor(bgColors[0]);
}
// Calculate which color to use based on score
var bgScoreStep = score < 5000 ? 0 : 1 + Math.floor((score - 5000) / 5000);
if (bgScoreStep !== lastBgScoreStep) {
var paletteLen = bgColors.length;
var colorIdx;
if (bgScoreStep < paletteLen - 1) {
colorIdx = bgScoreStep;
} else if (bgScoreStep === paletteLen - 1) {
colorIdx = paletteLen - 1; // black
} else {
// After black, restart from the beginning (skip black until next full cycle)
colorIdx = (bgScoreStep - (paletteLen - 1)) % (paletteLen - 1);
}
game.setBackgroundColor(bgColors[colorIdx]);
lastBgScoreStep = bgScoreStep;
}
// --- Feature selection popup removed ---
// --- Enemy spawn ---
enemySpawnTimer--;
if (enemySpawnTimer <= 0) {
// Randomly pick enemy type
var enemyTypeRand = Math.random();
var e;
if (enemyTypeRand < 0.18) {
e = new FastEnemy();
} else if (enemyTypeRand < 0.36) {
e = new TankEnemy();
} else if (enemyTypeRand < 0.54) {
e = new ZigzagEnemy();
} else if (enemyTypeRand < 0.70) {
e = new Enemy();
} else {
e = new BigEnemy();
}
e.x = 180 + Math.random() * (GAME_W - 360);
e.y = -100;
// Removed enemy speed scaling with score to keep game speed consistent
enemies.push(e);
game.addChild(e);
// Decrease interval as score increases
enemySpawnInterval = Math.max(minEnemyInterval, 60 - Math.floor(score / 100) * 4);
enemySpawnTimer = enemySpawnInterval;
}
// --- Powerup spawn ---
powerupTimer--;
if (powerupTimer <= 0) {
var p = new Powerup();
p.x = 180 + Math.random() * (GAME_W - 360);
p.y = -80;
powerups.push(p);
game.addChild(p);
powerupInterval = 120 + Math.floor(Math.random() * 120);
powerupTimer = powerupInterval;
}
};
// Floating text helper for feedback
function showFloatingText(txt, x, y, color) {
var t = new Text2(txt, {
size: 90,
fill: color || 0xffffff,
stroke: 0x000000,
strokeThickness: 8
});
t.anchor.set(0.5, 1);
t.x = x;
t.y = y;
game.addChild(t);
tween(t, {
y: y - 120,
alpha: 0
}, {
duration: 700,
onFinish: function onFinish() {
t.destroy();
}
});
}
// --- Start Menu Overlay ---
var menuOverlay = new Container();
menuOverlay.interactive = true;
menuOverlay.x = 0;
menuOverlay.y = 0;
menuOverlay.width = GAME_W;
menuOverlay.height = GAME_H;
// Dim background
var menuBg = LK.getAsset('playerBullet', {
anchorX: 0,
anchorY: 0,
scaleX: GAME_W / 30,
scaleY: GAME_H / 60,
tint: 0x000000,
alpha: 0.82,
x: 0,
y: 0
});
menuOverlay.addChild(menuBg);
// Title
var menuTitle = new Text2("SPACE BLASTERS", {
size: 160,
fill: 0xffe100,
stroke: 0x000000,
strokeThickness: 12,
align: "center"
});
menuTitle.anchor.set(0.5, 0.5);
menuTitle.x = GAME_W / 2;
menuTitle.y = GAME_H / 2 - 320;
menuOverlay.addChild(menuTitle);
// Instructions
var menuInstructions = new Text2("Drag the ship to move\nAuto-fire enabled\nCollect powerups!", {
size: 90,
fill: 0xffffff,
stroke: 0x000000,
strokeThickness: 8,
align: "center"
});
menuInstructions.anchor.set(0.5, 0.5);
menuInstructions.x = GAME_W / 2;
menuInstructions.y = GAME_H / 2 - 80;
menuOverlay.addChild(menuInstructions);
// Start button
var startBtn = new Text2("TAP TO START", {
size: 110,
fill: 0x00ff88,
stroke: 0x000000,
strokeThickness: 10,
align: "center"
});
startBtn.anchor.set(0.5, 0.5);
startBtn.x = GAME_W / 2;
startBtn.y = GAME_H / 2 + 260;
menuOverlay.addChild(startBtn);
game.addChild(menuOverlay);
// Pause game logic until menu is dismissed
var gamePausedForMenu = true;
var origGameUpdate = game.update;
game.update = function () {
if (gamePausedForMenu) return;
origGameUpdate.call(game);
};
// Dismiss menu on tap/click anywhere
menuOverlay.down = function (x, y, obj) {
if (!gamePausedForMenu) return;
gamePausedForMenu = false;
tween(menuOverlay, {
alpha: 0
}, {
duration: 400,
onFinish: function onFinish() {
menuOverlay.destroy();
}
});
};
// Forward input to menu overlay
game.down = function (x, y, obj) {
if (gamePausedForMenu && menuOverlay.down) {
menuOverlay.down(x, y, obj);
return;
}
// User-friendly: allow drag to start if touch is on ship OR anywhere in lower half of screen
var dx = x - ship.x,
dy = y - ship.y;
var onShip = dx * dx + dy * dy < ship.radius * ship.radius * 1.2;
var inLowerHalf = y > GAME_H / 2;
if (onShip || inLowerHalf) {
dragNode = ship;
handleMove(x, y, obj);
LK.effects.flashObject(ship, 0x33c1ff, 120);
if (!onShip && inLowerHalf && !dragAnywhereHintShown) {
showFloatingText("Tip: Drag anywhere below to move!", ship.x, ship.y - 180, 0x33c1ff);
dragAnywhereHintShown = true;
}
}
};
game.move = function (x, y, obj) {
if (gamePausedForMenu) return;
handleMove(x, y, obj);
};
game.up = function (x, y, obj) {
if (gamePausedForMenu) return;
dragNode = null;
};
// Define freezeActive and stunActive for menu overlay logic
var freezeActive = typeof freezeEnemiesTimer !== "undefined" && freezeEnemiesTimer > 0;
var stunActive = typeof enemyStunTimer !== "undefined" && enemyStunTimer > 0;
for (var i = 0; i < enemies.length; i++) {
var e = enemies[i];
if (!freezeActive && !stunActive) {
// SpiralEnemy: burst shoot 3 bullets in a spread
if (e.constructor === SpiralEnemy && e.shootCooldown <= 0) {
for (var b = -1; b <= 1; b++) {
var eb = new EnemyBullet();
eb.x = e.x;
eb.y = e.y + e.radius + 10;
// Spread: aim at ship, but offset angle
var dx = ship.x - e.x,
dy = ship.y - e.y;
var len = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx) + b * 0.18;
if (len > 0) {
eb.dirX = Math.cos(angle);
eb.dirY = Math.sin(angle);
}
enemyBullets.push(eb);
game.addChild(eb);
}
e.shootCooldown = 120 + Math.floor(Math.random() * 40);
} else if (e.shootCooldown <= 0) {
var eb = new EnemyBullet();
eb.x = e.x;
eb.y = e.y + e.radius + 10;
// Aim at ship
var dx = ship.x - e.x,
dy = ship.y - e.y;
var len = Math.sqrt(dx * dx + dy * dy);
if (len > 0) {
eb.dirX = dx / len;
eb.dirY = dy / len;
}
enemyBullets.push(eb);
game.addChild(eb);
e.shootCooldown = 90 + Math.floor(Math.random() * 60);
}
}
} /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0
});
/****
* Classes
****/
// BigEnemy: shoots large bullets
var BigEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGfx = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
enemyGfx.tint = 0x3f51b5; // blue for big enemy
self.radius = enemyGfx.width * 0.75;
self.speed = ENEMY_BASE_SPEED * 0.7 + Math.random() * 2;
self.moveType = 'straight';
self.shootCooldown = 80 + Math.floor(Math.random() * 40);
self.maxHealth = 5;
self.health = self.maxHealth;
// Health bar
var healthBarBg = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 0.18,
tint: 0x222222,
y: enemyGfx.height * 0.9
});
var healthBar = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.45,
scaleY: 0.12,
tint: 0x3f51b5,
y: enemyGfx.height * 0.9
});
self.update = function () {
self.y += self.speed * 0.8;
// Clamp inside screen
var margin = self.radius;
if (self.x < margin) self.x = margin;
if (self.x > GAME_W - margin) self.x = GAME_W - margin;
self.shootCooldown--;
// Health bar
healthBar.scaleX = 1.45 * (self.health / self.maxHealth);
if (self.health < 1) healthBar.scaleX = 0;
};
self.takeDamage = function (dmg) {
self.health -= dmg;
LK.effects.flashObject(self, 0x3f51b5, 100);
if (self.health <= 0) self.health = 0;
};
return self;
});
// BigEnemyBullet: large, slow, dangerous
var BigEnemyBullet = Container.expand(function () {
var self = Container.call(this);
var bulletGfx = self.attachAsset('enemyBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.2,
scaleY: 2.2,
tint: 0x3f51b5
});
self.speed = ENEMY_BULLET_SPEED * 0.6;
self.dirX = 0;
self.dirY = 1;
self.update = function () {
self.x += self.dirX * self.speed;
self.y += self.dirY * self.speed;
};
return self;
});
// Enemy (Base class)
var Enemy = Container.expand(function () {
var self = Container.call(this);
var enemyGfx = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
// Assign a vivid, random color from a palette for more visible and lively enemies
var enemyTints = [0xff1744,
// vivid red
0xffe100,
// bright yellow
0x00e1ff,
// cyan
0x44ff44,
// green
0xff00ff,
// magenta
0xff8800,
// orange
0x9c27b0,
// purple
0x00ff88,
// teal
0x3f51b5,
// blue
0xff4444,
// strong red
0xcddc39,
// lime
0x607d8b // blue-grey
];
enemyGfx.tint = enemyTints[Math.floor(Math.random() * enemyTints.length)];
self.radius = enemyGfx.width * 0.5;
self.speed = ENEMY_BASE_SPEED + Math.random() * 4;
self.moveType = Math.random() < 0.5 ? 'straight' : 'sine';
self.sinePhase = Math.random() * Math.PI * 2;
self.sineAmp = 120 + Math.random() * 120;
self.shootCooldown = 60 + Math.floor(Math.random() * 60);
// Health system for enemy
self.maxHealth = 1;
self.health = self.maxHealth;
// Health bar visual (background)
var healthBarBg = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 0.18,
tint: 0x222222,
y: enemyGfx.height * 0.7
});
// Health bar visual (foreground)
var healthBar = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.95,
scaleY: 0.12,
tint: enemyGfx.tint,
y: enemyGfx.height * 0.7
});
self.update = function () {
if (self.moveType === 'straight') {
self.y += self.speed;
} else {
self.y += self.speed * 0.85;
self.x += Math.sin(self.y / 120 + self.sinePhase) * 6;
}
// Clamp enemy inside left/right screen bounds
var margin = self.radius;
if (self.x < margin) self.x = margin;
if (self.x > GAME_W - margin) self.x = GAME_W - margin;
self.shootCooldown--;
// Update health bar
healthBar.scaleX = 0.95 * (self.health / self.maxHealth);
if (self.health < 1) healthBar.scaleX = 0;
};
self.takeDamage = function (dmg) {
self.health -= dmg;
LK.effects.flashObject(self, enemyGfx.tint, 80);
if (self.health <= 0) {
self.health = 0;
// Death handled in game loop
}
};
return self;
});
// Enemy Bullet
var EnemyBullet = Container.expand(function () {
var self = Container.call(this);
var bulletGfx = self.attachAsset('enemyBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = ENEMY_BULLET_SPEED;
self.dirX = 0;
self.dirY = 1;
self.update = function () {
self.x += self.dirX * self.speed;
self.y += self.dirY * self.speed;
};
return self;
});
// FastEnemy: moves faster, less health
var FastEnemy = Container.expand(function () {
var self = Container.call(this);
var fastTints = [0x00e1ff, 0xffe100, 0xff00ff, 0x44ff44, 0xff8800, 0x3f51b5];
var enemyGfx = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
enemyGfx.tint = fastTints[Math.floor(Math.random() * fastTints.length)];
self.radius = enemyGfx.width * 0.5;
self.speed = ENEMY_FAST_SPEED + Math.random() * 4;
self.moveType = 'straight';
self.shootCooldown = 40 + Math.floor(Math.random() * 40);
self.maxHealth = 1;
self.health = self.maxHealth;
// Health bar visual (background)
var healthBarBg = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 0.18,
tint: 0x222222,
y: enemyGfx.height * 0.7
});
// Health bar visual (foreground)
var healthBar = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.95,
scaleY: 0.12,
tint: 0x00e1ff,
y: enemyGfx.height * 0.7
});
self.update = function () {
self.y += self.speed;
// Clamp enemy inside left/right screen bounds
var margin = self.radius;
if (self.x < margin) self.x = margin;
if (self.x > GAME_W - margin) self.x = GAME_W - margin;
self.shootCooldown--;
// Update health bar
healthBar.scaleX = 0.95 * (self.health / self.maxHealth);
if (self.health < 1) healthBar.scaleX = 0;
};
self.takeDamage = function (dmg) {
self.health -= dmg;
LK.effects.flashObject(self, 0x00e1ff, 60);
if (self.health <= 0) self.health = 0;
};
return self;
});
// Player Bullet
var PlayerBullet = Container.expand(function () {
var self = Container.call(this);
var bulletGfx = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = PLAYER_BULLET_SPEED;
self.update = function () {
self.y += self.speed;
};
return self;
});
// Powerup
var Powerup = Container.expand(function () {
var self = Container.call(this);
// 50 unique powerup types (only keep those that work in this engine)
var types = ['rapid', 'shield', 'heal', 'doubleScore', 'slowMotion', 'tripleShot', 'megaShield', 'invincibility', 'scoreBomb', 'clearBullets', 'freezeEnemies', 'miniEnemies', 'giantShip', 'tinyShip', 'reverseBullets', 'homingBullets', 'reflectBullets', 'healthBoost', 'scoreBoost', 'enemySlow', 'enemyStun', 'extraLife', 'superHeal', 'scoreMultiplier', 'enemyShrink', 'enemyGrow', 'playerShrink', 'playerGrow', 'bulletSpeedUp', 'bulletSlow', 'enemyBulletSlow', 'enemyBulletFast', 'playerBulletBig', 'playerBulletSmall', 'enemyBulletBig', 'enemyBulletSmall', 'instantKill', 'enemyConfuse', 'playerConfuse', 'enemyInvisible', 'playerInvisible', 'enemySplit', 'playerSplit', 'enemyClone', 'playerClone', 'enemyTeleport', 'playerTeleport', 'enemyReverse', 'playerReverse', 'enemyFreezeLong'];
// Only keep types that are actually implemented and work
var workingTypes = ['rapid', 'shield', 'heal', 'doubleScore', 'slowMotion', 'tripleShot', 'megaShield', 'invincibility', 'scoreBomb', 'clearBullets', 'freezeEnemies', 'miniEnemies', 'giantShip', 'tinyShip', 'reverseBullets', 'homingBullets', 'reflectBullets', 'healthBoost', 'scoreBoost', 'enemySlow', 'enemyStun', 'extraLife', 'superHeal', 'scoreMultiplier', 'enemyShrink', 'enemyGrow', 'playerShrink', 'playerGrow', 'bulletSpeedUp', 'bulletSlow', 'enemyBulletSlow', 'enemyBulletFast', 'playerBulletBig', 'playerBulletSmall', 'enemyBulletBig', 'enemyBulletSmall', 'instantKill', 'enemyConfuse', 'playerConfuse', 'enemyInvisible', 'playerInvisible', 'enemySplit', 'playerSplit', 'enemyClone', 'playerClone', 'enemyTeleport', 'playerTeleport', 'enemyReverse', 'playerReverse', 'enemyFreezeLong'];
// Remove types that do not work in this engine (keep only those that can be implemented)
workingTypes = ['rapid', 'shield', 'heal', 'doubleScore', 'slowMotion', 'tripleShot', 'megaShield', 'invincibility', 'scoreBomb', 'clearBullets', 'freezeEnemies', 'miniEnemies', 'giantShip', 'tinyShip', 'reverseBullets', 'homingBullets', 'reflectBullets', 'healthBoost', 'scoreBoost', 'enemySlow', 'enemyStun'];
// Pick a random type, with increased chance for 'rapid' (firerate) and multi-powerup
var r = Math.random();
if (r < 0.16) self.type = 'rapid';else if (r < 0.23) self.type = 'shield';else if (r < 0.29) self.type = 'heal';else if (r < 0.34) self.type = 'doubleScore';else if (r < 0.39) self.type = 'slowMotion';else if (r < 0.44) self.type = 'tripleShot';else if (r < 0.48) self.type = 'megaShield';else if (r < 0.52) self.type = 'invincibility';else if (r < 0.56) self.type = 'scoreBomb';else if (r < 0.60) self.type = 'clearBullets';else if (r < 0.64) self.type = 'freezeEnemies';else if (r < 0.68) self.type = 'miniEnemies';else if (r < 0.72) self.type = 'giantShip';else if (r < 0.76) self.type = 'tinyShip';else if (r < 0.80) self.type = 'reverseBullets';else if (r < 0.84) self.type = 'homingBullets';else if (r < 0.88) self.type = 'reflectBullets';else if (r < 0.91) self.type = 'healthBoost';else if (r < 0.94) self.type = 'scoreBoost';else if (r < 0.97) self.type = 'enemySlow';else self.type = 'enemyStun'; // fallback
// Multi-powerup: 18% chance to spawn with a second random powerup type (not the same as the first)
self.extraTypes = [];
if (Math.random() < 0.18) {
// Pick a second type, different from the first, only from workingTypes
var extraType;
do {
extraType = workingTypes[Math.floor(Math.random() * workingTypes.length)];
} while (extraType === self.type);
self.extraTypes.push(extraType);
}
var powerupGfx = self.attachAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5
});
// Tint by type for visual feedback
if (self.type === 'rapid') powerupGfx.tint = 0xffe100;else if (self.type === 'shield') powerupGfx.tint = 0x00ffff;else if (self.type === 'heal') powerupGfx.tint = 0x44ff44;else if (self.type === 'doubleScore') powerupGfx.tint = 0xff00ff;else if (self.type === 'slowMotion') powerupGfx.tint = 0x8888ff;else if (self.type === 'tripleShot') powerupGfx.tint = 0xff8800;else if (self.type === 'megaShield') powerupGfx.tint = 0x00ff88;else if (self.type === 'invincibility') powerupGfx.tint = 0xffffff;else if (self.type === 'scoreBomb') powerupGfx.tint = 0xff2222;else if (self.type === 'clearBullets') powerupGfx.tint = 0x00ff00;else if (self.type === 'freezeEnemies') powerupGfx.tint = 0x00e1ff;else if (self.type === 'miniEnemies') powerupGfx.tint = 0xffb300;else if (self.type === 'giantShip') powerupGfx.tint = 0x8e24aa;else if (self.type === 'tinyShip') powerupGfx.tint = 0x3949ab;else if (self.type === 'reverseBullets') powerupGfx.tint = 0x00bcd4;else if (self.type === 'homingBullets') powerupGfx.tint = 0x43a047;else if (self.type === 'reflectBullets') powerupGfx.tint = 0xcddc39;else if (self.type === 'healthBoost') powerupGfx.tint = 0xff1744;else if (self.type === 'scoreBoost') powerupGfx.tint = 0xffea00;else if (self.type === 'enemySlow') powerupGfx.tint = 0x607d8b;else if (self.type === 'enemyStun') powerupGfx.tint = 0x6d4c41;
self.speed = 10;
self.update = function () {
self.y += self.speed;
};
return self;
});
// Player Ship
var Ship = Container.expand(function () {
var self = Container.call(this);
var shipGfx = self.attachAsset('ship', {
anchorX: 0.5,
anchorY: 0.5
});
// Use a bright, lively cyan as the default color for the player ship
shipGfx.tint = 0x33c1ff;
self.radius = shipGfx.width * 0.5;
self.shootCooldown = 0;
self.rapidFire = false;
self.rapidFireTimer = 0;
self.shield = false;
self.shieldTimer = 0;
// Health system for player
self.maxHealth = 28; // Reduced survivability for the player ship (harder)
self.health = self.maxHealth;
// Health bar visual (background)
var healthBarBg = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.2,
scaleY: 0.25,
tint: 0x222222,
y: shipGfx.height * 0.7
});
// Health bar visual (foreground)
var healthBar = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 0.18,
tint: 0xff4444,
y: shipGfx.height * 0.7
});
// Visual shield indicator
var shieldGfx = self.attachAsset('ship', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.3,
scaleY: 1.3,
tint: 0x00ffff
});
shieldGfx.alpha = 0.25;
shieldGfx.visible = false;
self.update = function () {
// Handle powerup timers
if (self.rapidFire) {
self.rapidFireTimer--;
// Visual feedback for rapid fire: pulse ship color
shipGfx.tint = LK.ticks % 10 < 5 ? 0xffe100 : 0x33c1ff;
if (self.rapidFireTimer <= 0) {
self.rapidFire = false;
shipGfx.tint = 0x33c1ff;
}
} else {
shipGfx.tint = 0x33c1ff;
}
if (self.shield) {
self.shieldTimer--;
shieldGfx.visible = true;
if (self.shieldTimer <= 0) {
self.shield = false;
shieldGfx.visible = false;
}
} else {
shieldGfx.visible = false;
}
// Update health bar
healthBar.scaleX = 2 * (self.health / self.maxHealth);
if (self.health < 1) healthBar.scaleX = 0;
};
self.activateRapidFire = function (duration) {
self.rapidFire = true;
self.rapidFireTimer = duration;
};
self.activateShield = function (duration) {
self.shield = true;
self.shieldTimer = duration;
shieldGfx.visible = true;
};
self.takeDamage = function (dmg) {
if (self.shield) return;
self.health -= dmg;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health < 0) self.health = 0;
};
self.heal = function (amount) {
self.health += amount;
if (self.health > self.maxHealth) self.health = self.maxHealth;
};
return self;
});
// SpiralEnemy: moves in a spiral pattern and shoots in bursts
var SpiralEnemy = Container.expand(function () {
var self = Container.call(this);
var spiralTints = [0x9c27b0, 0xff00ff, 0xffe100, 0x00e1ff, 0x44ff44, 0xff8800];
var enemyGfx = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
enemyGfx.tint = spiralTints[Math.floor(Math.random() * spiralTints.length)];
self.radius = enemyGfx.width * 0.5;
self.speed = 4 + Math.random() * 2;
self.angle = Math.random() * Math.PI * 2;
self.spiralRadius = 0;
self.spiralGrow = 1.2 + Math.random() * 0.7;
self.shootCooldown = 40 + Math.floor(Math.random() * 40);
self.burstCount = 0;
self.maxHealth = 2;
self.health = self.maxHealth;
// Health bar visual (background)
var healthBarBg = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 0.18,
tint: 0x222222,
y: enemyGfx.height * 0.7
});
// Health bar visual (foreground)
var healthBar = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.95,
scaleY: 0.12,
tint: enemyGfx.tint,
y: enemyGfx.height * 0.7
});
self.update = function () {
// Spiral movement
self.angle += 0.09;
self.spiralRadius += self.spiralGrow;
self.x += Math.cos(self.angle) * self.spiralGrow * 1.2;
self.y += self.speed + Math.sin(self.angle) * 2;
// Clamp inside screen
var margin = self.radius;
if (self.x < margin) self.x = margin;
if (self.x > GAME_W - margin) self.x = GAME_W - margin;
self.shootCooldown--;
// Health bar
healthBar.scaleX = 0.95 * (self.health / self.maxHealth);
if (self.health < 1) healthBar.scaleX = 0;
};
self.takeDamage = function (dmg) {
self.health -= dmg;
LK.effects.flashObject(self, 0x9c27b0, 80);
if (self.health <= 0) self.health = 0;
};
return self;
});
// TankEnemy: slow, high health, shoots more
var TankEnemy = Container.expand(function () {
var self = Container.call(this);
var tankTints = [0xff4444, 0x00e1ff, 0xffe100, 0x00ff88, 0x3f51b5, 0xff8800];
var enemyGfx = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.3,
scaleY: 1.3
});
enemyGfx.tint = tankTints[Math.floor(Math.random() * tankTints.length)];
self.radius = enemyGfx.width * 0.65;
self.speed = ENEMY_TANK_SPEED + Math.random() * 1.2;
self.moveType = 'sine';
self.sinePhase = Math.random() * Math.PI * 2;
self.sineAmp = 180 + Math.random() * 80;
self.shootCooldown = 30 + Math.floor(Math.random() * 30);
self.maxHealth = 4;
self.health = self.maxHealth;
// Health bar visual (background)
var healthBarBg = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.3,
scaleY: 0.18,
tint: 0x222222,
y: enemyGfx.height * 0.9
});
// Health bar visual (foreground)
var healthBar = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.25,
scaleY: 0.12,
tint: 0xff4444,
y: enemyGfx.height * 0.9
});
self.update = function () {
self.y += self.speed * 0.7;
self.x += Math.sin(self.y / 120 + self.sinePhase) * 10;
// Clamp enemy inside left/right screen bounds
var margin = self.radius;
if (self.x < margin) self.x = margin;
if (self.x > GAME_W - margin) self.x = GAME_W - margin;
self.shootCooldown--;
// Update health bar
healthBar.scaleX = 1.25 * (self.health / self.maxHealth);
if (self.health < 1) healthBar.scaleX = 0;
};
self.takeDamage = function (dmg) {
self.health -= dmg;
LK.effects.flashObject(self, 0xff4444, 100);
if (self.health <= 0) self.health = 0;
};
return self;
});
// ZigzagEnemy: moves in zigzag, normal health
var ZigzagEnemy = Container.expand(function () {
var self = Container.call(this);
var zigzagTints = [0xffe100, 0xff00ff, 0x44ff44, 0x00e1ff, 0xff8800, 0x9c27b0];
var enemyGfx = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
enemyGfx.tint = zigzagTints[Math.floor(Math.random() * zigzagTints.length)];
self.radius = enemyGfx.width * 0.5;
self.speed = ENEMY_ZIGZAG_SPEED + Math.random() * 3;
self.moveType = 'zigzag';
self.zigzagDir = Math.random() < 0.5 ? -1 : 1;
self.zigzagTimer = 0;
self.shootCooldown = 60 + Math.floor(Math.random() * 60);
self.maxHealth = 2;
self.health = self.maxHealth;
// Health bar visual (background)
var healthBarBg = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 0.18,
tint: 0x222222,
y: enemyGfx.height * 0.7
});
// Health bar visual (foreground)
var healthBar = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.95,
scaleY: 0.12,
tint: 0xffe100,
y: enemyGfx.height * 0.7
});
self.update = function () {
self.y += self.speed;
self.zigzagTimer++;
if (self.zigzagTimer % 30 === 0) self.zigzagDir *= -1;
self.x += self.zigzagDir * 18;
// Clamp enemy inside left/right screen bounds
var margin = self.radius;
if (self.x < margin) self.x = margin;
if (self.x > GAME_W - margin) self.x = GAME_W - margin;
self.shootCooldown--;
// Update health bar
healthBar.scaleX = 0.95 * (self.health / self.maxHealth);
if (self.health < 1) healthBar.scaleX = 0;
};
self.takeDamage = function (dmg) {
self.health -= dmg;
LK.effects.flashObject(self, 0xffe100, 80);
if (self.health <= 0) self.health = 0;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
// No title, no description
// Always backgroundColor is black
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Music
// Sound effects
// Powerup
// Enemy bullet
// Enemy
// Player bullet
// Spaceship (player)
// Game area
var GAME_W = 2048,
GAME_H = 2732;
// Score
var score = 0;
// High score (persistent for all users)
var highScore = storage.highScore || 0;
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// High score UI (visible to all users)
var highScoreTxt = new Text2('High Score: ' + highScore, {
size: 60,
fill: 0xffe100,
stroke: 0x000000,
strokeThickness: 6
});
highScoreTxt.anchor.set(0.5, 0);
highScoreTxt.x = scoreTxt.x;
highScoreTxt.y = scoreTxt.y + 110;
LK.gui.top.addChild(highScoreTxt);
var multiplierTxt = new Text2('x1', {
size: 70,
fill: 0x00ff88
});
multiplierTxt.anchor.set(0.5, 0);
multiplierTxt.x = scoreTxt.x + 220;
multiplierTxt.y = scoreTxt.y + 40;
LK.gui.top.addChild(multiplierTxt);
// Combo system variables
var comboCount = 0;
var comboTimer = 0;
var lastComboTime = 0;
// Double score powerup
var doubleScoreActive = false;
var doubleScoreTimer = 0;
// Slow motion powerup
var slowMotionActive = false;
var slowMotionTimer = 0;
// Triple shot powerup
var tripleShotActive = false;
var tripleShotTimer = 0;
// Invincibility powerup
var invincibilityActive = false;
var invincibilityTimer = 0;
// Player ship
var ship = new Ship();
game.addChild(ship);
ship.x = GAME_W / 2;
ship.y = GAME_H - 350;
// --- UI Health Text for Player ---
var shipHealthTxt = new Text2('', {
size: 60,
fill: 0xff4444,
stroke: 0x000000,
strokeThickness: 6
});
shipHealthTxt.anchor.set(0.5, 1);
shipHealthTxt.x = ship.x;
shipHealthTxt.y = ship.y - ship.radius - 60;
game.addChild(shipHealthTxt);
// Arrays for game objects
var playerBullets = [];
var enemies = [];
var enemyBullets = [];
var powerups = [];
// --- UI Health Text for Enemies ---
// We'll keep a parallel array to track health text objects for each enemy
var enemyHealthTxts = [];
// Dragging
var dragNode = null;
// Difficulty increased: enemies spawn more frequently
var enemySpawnTimer = 0;
var enemySpawnInterval = 70; // faster spawn
var minEnemyInterval = 20; // minimum interval decreased
var enemySpeedInc = 0;
// Powerup spawn
var powerupTimer = 0;
var powerupInterval = 120; // Powerups much more frequent
// --- Reflex tuning: adjust bullet and enemy speeds globally ---
// Restore normal speeds for enemies and bullets
var PLAYER_BULLET_SPEED = -16; // normal bullet speed
var ENEMY_BULLET_SPEED = 8; // normal enemy bullet speed
var ENEMY_BASE_SPEED = 4; // normal base enemy speed
var ENEMY_FAST_SPEED = 7; // normal fast enemy speed
var ENEMY_TANK_SPEED = 1.5; // normal tank speed
var ENEMY_ZIGZAG_SPEED = 4; // normal zigzag speed
var PLAYER_SHOOT_RATE = 12; // normal player shoot rate
// Last intersect states
var lastShipEnemyIntersect = false;
var lastShipEnemyBulletIntersect = false;
var lastShipPowerupIntersect = false;
// Music
LK.playMusic('bgmusic');
// Move handler (drag ship)
function handleMove(x, y, obj) {
if (dragNode === ship) {
// Clamp ship inside game area (with margin)
var margin = 80;
var nx = Math.max(margin, Math.min(GAME_W - margin, x));
var ny = Math.max(margin, Math.min(GAME_H - margin, y));
ship.x = nx;
ship.y = ny;
// Touch feedback: scale ship up slightly while dragging, then back
ship.scale.set(1.15, 1.15);
tween(ship.scale, {
x: 1,
y: 1
}, {
duration: 120
});
}
}
game.move = handleMove;
var dragAnywhereHintShown = false;
game.down = function (x, y, obj) {
// User-friendly: allow drag to start if touch is on ship OR anywhere in lower half of screen
var dx = x - ship.x,
dy = y - ship.y;
var onShip = dx * dx + dy * dy < ship.radius * ship.radius * 1.2;
var inLowerHalf = y > GAME_H / 2;
if (onShip || inLowerHalf) {
dragNode = ship;
handleMove(x, y, obj);
// Visual feedback: flash ship blue for 120ms on drag start
LK.effects.flashObject(ship, 0x33c1ff, 120);
// Show floating helper text only the first time user drags from lower half (not on ship)
if (!onShip && inLowerHalf && !dragAnywhereHintShown) {
showFloatingText("Tip: Drag anywhere below to move!", ship.x, ship.y - 180, 0x33c1ff);
dragAnywhereHintShown = true;
}
}
};
game.up = function (x, y, obj) {
dragNode = null;
};
// Main update loop
game.update = function () {
// Update ship
ship.update();
// --- Update player health UI text ---
shipHealthTxt.setText(ship.health + " / " + ship.maxHealth);
shipHealthTxt.x = ship.x;
shipHealthTxt.y = ship.y - ship.radius - 60;
// --- Update enemy health UI text ---
// Remove any health text objects for destroyed enemies
for (var i = enemyHealthTxts.length - 1; i >= 0; i--) {
if (i >= enemies.length || !enemies[i] || enemies[i].destroyed) {
if (enemyHealthTxts[i]) {
enemyHealthTxts[i].destroy();
}
enemyHealthTxts.splice(i, 1);
}
}
// Sync health text objects to enemies
for (var i = 0; i < enemies.length; i++) {
var e = enemies[i];
var txt = enemyHealthTxts[i];
if (!txt) {
txt = new Text2('', {
size: 48,
fill: 0xffffff,
stroke: 0x000000,
strokeThickness: 5
});
txt.anchor.set(0.5, 1);
game.addChild(txt);
enemyHealthTxts[i] = txt;
}
txt.setText(e.health + " / " + e.maxHealth);
txt.x = e.x;
txt.y = e.y - (e.radius || 60) - 38;
txt.visible = e.health > 0;
}
// --- Double Score timer ---
if (doubleScoreActive) {
doubleScoreTimer--;
if (doubleScoreTimer <= 0) {
doubleScoreActive = false;
showFloatingText("Double Score Ended", ship.x, ship.y - 120, 0xff00ff);
}
}
// --- Slow Motion timer ---
if (slowMotionActive) {
slowMotionTimer--;
if (slowMotionTimer <= 0) {
slowMotionActive = false;
showFloatingText("Speed Restored", ship.x, ship.y - 120, 0x8888ff);
}
}
// --- Triple Shot timer ---
if (tripleShotActive) {
tripleShotTimer--;
if (tripleShotTimer <= 0) {
tripleShotActive = false;
showFloatingText("Triple Shot Ended", ship.x, ship.y - 120, 0xff8800);
}
}
// --- Invincibility timer ---
if (invincibilityActive) {
invincibilityTimer--;
if (invincibilityTimer <= 0) {
invincibilityActive = false;
showFloatingText("Invincibility Ended", ship.x, ship.y - 120, 0xffffff);
}
}
// --- Freeze Enemies timer ---
if (typeof freezeEnemiesTimer !== "undefined" && freezeEnemiesTimer > 0) {
freezeEnemiesTimer--;
if (freezeEnemiesTimer <= 0) {
showFloatingText("Enemies Unfrozen", ship.x, ship.y - 120, 0x00e1ff);
}
}
// --- Homing Bullets timer ---
if (typeof homingBulletsTimer !== "undefined" && homingBulletsTimer > 0) {
homingBulletsTimer--;
if (homingBulletsTimer <= 0) {
showFloatingText("Homing Ended", ship.x, ship.y - 120, 0x43a047);
}
}
// --- Reflect Bullets timer ---
if (typeof reflectBulletsTimer !== "undefined" && reflectBulletsTimer > 0) {
reflectBulletsTimer--;
if (reflectBulletsTimer <= 0) {
showFloatingText("Reflect Ended", ship.x, ship.y - 120, 0xcddc39);
}
}
// --- Enemy Slow timer ---
if (typeof enemySlowTimer !== "undefined" && enemySlowTimer > 0) {
enemySlowTimer--;
if (enemySlowTimer <= 0) {
showFloatingText("Enemies Normal Speed", ship.x, ship.y - 120, 0x607d8b);
}
}
// --- Enemy Stun timer ---
if (typeof enemyStunTimer !== "undefined" && enemyStunTimer > 0) {
enemyStunTimer--;
if (enemyStunTimer <= 0) {
showFloatingText("Enemies Unstunned", ship.x, ship.y - 120, 0x6d4c41);
}
}
// --- Player shooting ---
ship.shootCooldown--;
var shootRate = ship.rapidFire ? Math.max(6, Math.floor(PLAYER_SHOOT_RATE * 0.33)) : PLAYER_SHOOT_RATE;
if (ship.shootCooldown <= 0) {
// Auto-fire
if (tripleShotActive) {
for (var ts = -1; ts <= 1; ts++) {
var pb = new PlayerBullet();
pb.x = ship.x + ts * 38;
pb.y = ship.y - ship.radius - 30;
if (ts !== 0) pb.rotation = ts * 0.18;
playerBullets.push(pb);
game.addChild(pb);
}
} else {
var pb = new PlayerBullet();
pb.x = ship.x;
pb.y = ship.y - ship.radius - 30;
playerBullets.push(pb);
game.addChild(pb);
}
ship.shootCooldown = shootRate;
LK.getSound('shoot').play();
}
// --- 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);
}
}
// --- Update enemies ---
for (var i = enemies.length - 1; i >= 0; i--) {
var e = enemies[i];
// --- Enemy AI: dodge if player is close horizontally ---
if (Math.abs(ship.x - e.x) < 180 && Math.abs(ship.y - e.y) < 600) {
// Try to dodge left or right, but less distance for fairness
if (ship.x < e.x) e.x += 6 + Math.random() * 3;else e.x -= 6 + Math.random() * 3;
}
// --- Powerup effects: freeze, slow, stun ---
var freezeActive = typeof freezeEnemiesTimer !== "undefined" && freezeEnemiesTimer > 0;
var slowActive = typeof enemySlowTimer !== "undefined" && enemySlowTimer > 0 || slowMotionActive;
var stunActive = typeof enemyStunTimer !== "undefined" && enemyStunTimer > 0;
if (stunActive) {
// Stunned: don't update, don't shoot
// Visual feedback: pulse tint
if (LK.ticks % 20 < 10) e.tint = 0x6d4c41;else e.tint = 0xffffff;
} else if (freezeActive) {
// Frozen: don't update, don't shoot
if (LK.ticks % 20 < 10) e.tint = 0x00e1ff;else e.tint = 0xffffff;
} else {
// Slow: update less frequently
if (!slowActive || LK.ticks % 2 === 0) {
e.update();
}
}
// Destroy and remove enemies when they move off the bottom of the screen
if (e.y > GAME_H + e.radius) {
e.destroy();
enemies.splice(i, 1);
continue;
}
// (Removed clamping: enemies can now leave the screen at the bottom)
// Enemy shooting
if (!freezeActive && !stunActive) {
// SpiralEnemy: burst shoot 3 bullets in a spread
if (e.constructor === SpiralEnemy && e.shootCooldown <= 0) {
for (var b = -1; b <= 1; b++) {
var eb = new EnemyBullet();
eb.x = e.x;
eb.y = e.y + e.radius + 10;
// Spread: aim at ship, but offset angle
var dx = ship.x - e.x,
dy = ship.y - e.y;
var len = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx) + b * 0.18;
if (len > 0) {
eb.dirX = Math.cos(angle);
eb.dirY = Math.sin(angle);
}
enemyBullets.push(eb);
game.addChild(eb);
}
e.shootCooldown = 120 + Math.floor(Math.random() * 40);
} else if (e.shootCooldown <= 0) {
// BigEnemy shoots big bullet, others shoot normal
var eb;
if (e.constructor === BigEnemy) {
eb = new BigEnemyBullet();
eb.x = e.x;
eb.y = e.y + e.radius + 20;
// Aim at ship
var dx = ship.x - e.x,
dy = ship.y - e.y;
var len = Math.sqrt(dx * dx + dy * dy);
if (len > 0) {
eb.dirX = dx / len;
eb.dirY = dy / len;
}
enemyBullets.push(eb);
game.addChild(eb);
e.shootCooldown = 120 + Math.floor(Math.random() * 40);
} else {
eb = new EnemyBullet();
eb.x = e.x;
eb.y = e.y + e.radius + 10;
// Aim at ship
var dx = ship.x - e.x,
dy = ship.y - e.y;
var len = Math.sqrt(dx * dx + dy * dy);
if (len > 0) {
eb.dirX = dx / len;
eb.dirY = dy / len;
}
enemyBullets.push(eb);
game.addChild(eb);
e.shootCooldown = 90 + Math.floor(Math.random() * 60);
}
}
}
}
// --- Update enemy bullets ---
for (var i = enemyBullets.length - 1; i >= 0; i--) {
var eb = enemyBullets[i];
// Slow motion: update less frequently
var reflectActive = typeof reflectBulletsTimer !== "undefined" && reflectBulletsTimer > 0;
if (reflectActive && eb.dirY > 0 && eb.y > ship.y - 200) {
// Reflect bullets going downwards, near ship
eb.dirY *= -1;
showFloatingText("Reflected!", eb.x, eb.y, 0xcddc39);
}
if (!slowMotionActive || LK.ticks % 2 === 0) {
eb.update();
if (eb.x < -100 || eb.x > GAME_W + 100 || eb.y < -100 || eb.y > GAME_H + 100) {
eb.destroy();
enemyBullets.splice(i, 1);
}
}
}
// --- Homing Bullets effect for player bullets ---
if (typeof homingBulletsTimer !== "undefined" && homingBulletsTimer > 0) {
for (var i = 0; i < playerBullets.length; i++) {
var pb = playerBullets[i];
// Find nearest enemy
var nearest = null,
minDist = 99999;
for (var j = 0; j < enemies.length; j++) {
var dx = enemies[j].x - pb.x;
var dy = enemies[j].y - pb.y;
var dist = dx * dx + dy * dy;
if (dist < minDist) {
minDist = dist;
nearest = enemies[j];
}
}
if (nearest && minDist < 400 * 400) {
// Adjust bullet direction slightly toward enemy
var dx = nearest.x - pb.x;
var dy = nearest.y - pb.y;
var len = Math.sqrt(dx * dx + dy * dy);
if (len > 0) {
var steer = 0.18;
pb.x += dx / len * steer;
pb.y += dy / len * steer * 0.5;
}
}
}
}
// --- Update powerups ---
for (var i = powerups.length - 1; i >= 0; i--) {
var p = powerups[i];
p.update();
if (p.y > GAME_H + 100) {
p.destroy();
powerups.splice(i, 1);
}
}
// --- Collision: Player bullets vs Enemies ---
for (var i = playerBullets.length - 1; i >= 0; i--) {
var b = playerBullets[i];
for (var j = enemies.length - 1; j >= 0; j--) {
var e = enemies[j];
if (b.intersects(e)) {
// Hit enemy
e.takeDamage(2);
// Recoil effect for enemy
tween(e, {
y: e.y - 30
}, {
duration: 60,
yoyo: true,
repeat: 1
});
// Camera shake on hit (simulate with quick flash)
LK.getSound('enemyDown').play();
// Combo system variables (global scope, but initialize if undefined)
if (typeof comboCount === "undefined") comboCount = 0;
if (typeof comboTimer === "undefined") comboTimer = 0;
if (typeof lastComboTime === "undefined") lastComboTime = 0;
// Score only if enemy dies
if (e.health <= 0) {
// Combo logic: if last kill was within 2 seconds, increase combo
var now = LK.ticks;
if (now - lastComboTime < 120) {
comboCount++;
} else {
comboCount = 1;
}
lastComboTime = now;
comboTimer = 120; // 2 seconds to continue combo
// Combo bonus: +10 base, +5 per combo after first, multiplied by scoreMultiplier
if (typeof scoreMultiplier === "undefined") scoreMultiplier = 1;
var comboBonus = 10 + (comboCount > 1 ? (comboCount - 1) * 5 : 0);
comboBonus *= scoreMultiplier;
if (doubleScoreActive) comboBonus *= 2;
score += comboBonus;
// Animate score text for feedback
scoreTxt.setText(score);
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
if (typeof highScoreTxt !== "undefined") {
highScoreTxt.setText("High Score: " + highScore);
highScoreTxt.scale.set(1.2, 1.2);
tween(highScoreTxt.scale, {
x: 1,
y: 1
}, {
duration: 300
});
}
}
if (typeof multiplierTxt !== "undefined") {
multiplierTxt.setText("x" + scoreMultiplier);
multiplierTxt.scale.set(1.3, 1.3);
tween(multiplierTxt.scale, {
x: 1,
y: 1
}, {
duration: 200
});
}
scoreTxt.scale.set(1.25, 1.25);
tween(scoreTxt.scale, {
x: 1,
y: 1
}, {
duration: 200
});
// Show floating text for combo
if (comboCount > 1) {
showFloatingText("Combo x" + comboCount + "! +" + comboBonus, e.x, e.y - 80, 0xffe100);
if (comboCount % 3 === 0) {
showFloatingText("Multiplier: x" + scoreMultiplier, e.x, e.y - 160, 0x00ff88);
}
} else {
showFloatingText("+10", e.x, e.y - 80, 0xffffff);
}
// Fun: random emoji on enemy kill!
var emojis = ["🚀", "💥", "✨", "🔥", "😎", "🎉", "🛸", "👾"];
var emoji = emojis[Math.floor(Math.random() * emojis.length)];
showFloatingText(emoji, e.x + (Math.random() * 80 - 40), e.y - 120, 0xffffff);
e.destroy();
enemies.splice(j, 1);
}
b.destroy();
playerBullets.splice(i, 1);
break;
}
}
}
// --- Combo timer update ---
if (typeof comboTimer !== "undefined" && comboTimer > 0) {
comboTimer--;
if (comboTimer === 0) {
comboCount = 0;
scoreMultiplier = 1; // Reset multiplier on missed combo
}
}
if (comboCount > 1) {
scoreMultiplier = 1 + Math.floor(comboCount / 3); // Every 3 combo increases multiplier by 1
} else {
scoreMultiplier = 1;
}
// --- Collision: Ship vs Enemies ---
var shipEnemyIntersect = false;
for (var i = 0; i < enemies.length; i++) {
var e = enemies[i];
if (ship.intersects(e)) {
shipEnemyIntersect = true;
break;
}
}
if (!lastShipEnemyIntersect && shipEnemyIntersect) {
if (invincibilityActive) {
LK.effects.flashObject(ship, 0xffffff, 400);
showFloatingText("No Damage!", ship.x, ship.y - 120, 0xffffff);
} else if (ship.shield) {
// Absorb hit, destroy enemy
LK.effects.flashObject(ship, 0x00ffff, 400);
for (var i = 0; i < enemies.length; i++) {
if (ship.intersects(enemies[i])) {
enemies[i].destroy();
enemies.splice(i, 1);
break;
}
}
ship.shield = false;
ship.shieldTimer = 0;
ship.children[1].visible = false;
} else {
// Take damage
ship.takeDamage(2);
// (Screen flash removed)
LK.getSound('hit').play();
if (ship.health <= 0) {
LK.showGameOver();
return;
}
}
}
lastShipEnemyIntersect = shipEnemyIntersect;
// --- Collision: Ship vs Enemy Bullets ---
var shipEnemyBulletIntersect = false;
for (var i = 0; i < enemyBullets.length; i++) {
var eb = enemyBullets[i];
if (ship.intersects(eb)) {
shipEnemyBulletIntersect = true;
break;
}
}
if (!lastShipEnemyBulletIntersect && shipEnemyBulletIntersect) {
if (invincibilityActive) {
LK.effects.flashObject(ship, 0xffffff, 400);
showFloatingText("No Damage!", ship.x, ship.y - 120, 0xffffff);
} else if (ship.shield) {
LK.effects.flashObject(ship, 0x00ffff, 400);
// Remove bullet
for (var i = 0; i < enemyBullets.length; i++) {
if (ship.intersects(enemyBullets[i])) {
enemyBullets[i].destroy();
enemyBullets.splice(i, 1);
break;
}
}
ship.shield = false;
ship.shieldTimer = 0;
ship.children[1].visible = false;
} else {
// Take damage
ship.takeDamage(1);
// (Screen flash removed)
LK.getSound('hit').play();
if (ship.health <= 0) {
LK.showGameOver();
return;
}
}
}
lastShipEnemyBulletIntersect = shipEnemyBulletIntersect;
// --- Collision: Ship vs Powerups ---
var shipPowerupIntersect = false;
var _loop = function _loop() {
p = powerups[i];
if (ship.intersects(p)) {
// --- 20 Powerup Types Logic ---
// Helper to apply a powerup type (for multi-powerup support)
var applyPowerupType = function applyPowerupType(type) {
// 1. rapid
if (type === 'rapid') {
if (ship.rapidFire) {
ship.rapidFireTimer += 240;
showFloatingText("Rapid Fire Combo!", ship.x, ship.y - 120, 0xffe100);
} else {
ship.activateRapidFire(360);
showFloatingText("Rapid Fire!", ship.x, ship.y - 120, 0xffe100);
}
// 2. shield
} else if (type === 'shield') {
if (ship.shield) {
ship.shieldTimer += 320;
showFloatingText("Shield Combo!", ship.x, ship.y - 120, 0x00ffff);
} else {
ship.activateShield(480);
showFloatingText("Shield!", ship.x, ship.y - 120, 0x00ffff);
}
// 3. heal
} else if (type === 'heal') {
ship.heal(4);
showFloatingText("+4 Health", ship.x, ship.y - 120, 0x44ff44);
// 4. doubleScore
} else if (type === 'doubleScore') {
if (doubleScoreActive) {
doubleScoreTimer += 400;
showFloatingText("Double Score Combo!", ship.x, ship.y - 120, 0xff00ff);
} else {
doubleScoreActive = true;
doubleScoreTimer = 600;
showFloatingText("Double Score!", ship.x, ship.y - 120, 0xff00ff);
}
// 5. slowMotion
} else if (type === 'slowMotion') {
if (slowMotionActive) {
slowMotionTimer += 300;
showFloatingText("Slow Motion Combo!", ship.x, ship.y - 120, 0x8888ff);
} else {
slowMotionActive = true;
slowMotionTimer = 420;
showFloatingText("Slow Motion!", ship.x, ship.y - 120, 0x8888ff);
}
// 6. tripleShot
} else if (type === 'tripleShot') {
if (tripleShotActive) {
tripleShotTimer += 360;
showFloatingText("Triple Shot Combo!", ship.x, ship.y - 120, 0xff8800);
} else {
tripleShotActive = true;
tripleShotTimer = 540;
showFloatingText("Triple Shot!", ship.x, ship.y - 120, 0xff8800);
}
// 7. megaShield
} else if (type === 'megaShield') {
if (ship.shield) {
ship.shieldTimer += 800;
showFloatingText("Mega Shield Combo!", ship.x, ship.y - 120, 0x00ff88);
} else {
ship.activateShield(1200);
showFloatingText("Mega Shield!", ship.x, ship.y - 120, 0x00ff88);
}
// 8. invincibility
} else if (type === 'invincibility') {
if (invincibilityActive) {
invincibilityTimer += 240;
showFloatingText("Invincible Combo!", ship.x, ship.y - 120, 0xffffff);
} else {
invincibilityActive = true;
invincibilityTimer = 360;
showFloatingText("Invincible!", ship.x, ship.y - 120, 0xffffff);
}
// 9. scoreBomb
} else if (type === 'scoreBomb') {
var killed = 0;
for (var si = enemies.length - 1; si >= 0; si--) {
if (enemies[si].y > 0) {
enemies[si].destroy();
enemies.splice(si, 1);
killed++;
}
}
if (killed > 0) {
var bombScore = 10 * killed;
score += bombScore;
scoreTxt.setText(score);
showFloatingText("Score Bomb! +" + bombScore, ship.x, ship.y - 120, 0xff2222);
} else {
showFloatingText("Score Bomb! (No targets)", ship.x, ship.y - 120, 0xff2222);
}
// 10. clearBullets
} else if (type === 'clearBullets') {
var cleared = 0;
for (var ci = enemyBullets.length - 1; ci >= 0; ci--) {
enemyBullets[ci].destroy();
enemyBullets.splice(ci, 1);
cleared++;
}
showFloatingText("Cleared " + cleared + " Bullets!", ship.x, ship.y - 120, 0x00ff00);
// 11. freezeEnemies
} else if (type === 'freezeEnemies') {
if (typeof freezeEnemiesTimer === "undefined") freezeEnemiesTimer = 0;
if (freezeEnemiesTimer > 0) {
freezeEnemiesTimer += 180;
showFloatingText("Freeze Combo!", ship.x, ship.y - 120, 0x00e1ff);
} else {
freezeEnemiesTimer = 360;
showFloatingText("Enemies Frozen!", ship.x, ship.y - 120, 0x00e1ff);
}
// 12. miniEnemies
} else if (type === 'miniEnemies') {
for (var mi = 0; mi < enemies.length; mi++) {
enemies[mi].scale.set(0.6, 0.6);
enemies[mi].radius *= 0.6;
}
showFloatingText("Mini Enemies!", ship.x, ship.y - 120, 0xffb300);
// 13. giantShip
} else if (type === 'giantShip') {
ship.scale.set(1.7, 1.7);
showFloatingText("Giant Ship!", ship.x, ship.y - 120, 0x8e24aa);
tween(ship.scale, {
x: 1,
y: 1
}, {
duration: 1200
});
// 14. tinyShip
} else if (type === 'tinyShip') {
ship.scale.set(0.5, 0.5);
showFloatingText("Tiny Ship!", ship.x, ship.y - 120, 0x3949ab);
tween(ship.scale, {
x: 1,
y: 1
}, {
duration: 1200
});
// 15. reverseBullets
} else if (type === 'reverseBullets') {
for (var ri = 0; ri < enemyBullets.length; ri++) {
enemyBullets[ri].dirY *= -1;
}
showFloatingText("Reverse Bullets!", ship.x, ship.y - 120, 0x00bcd4);
// 16. homingBullets
} else if (type === 'homingBullets') {
if (typeof homingBulletsTimer === "undefined") homingBulletsTimer = 0;
if (homingBulletsTimer > 0) {
homingBulletsTimer += 180;
showFloatingText("Homing Combo!", ship.x, ship.y - 120, 0x43a047);
} else {
homingBulletsTimer = 360;
showFloatingText("Homing Bullets!", ship.x, ship.y - 120, 0x43a047);
}
// 17. reflectBullets
} else if (type === 'reflectBullets') {
if (typeof reflectBulletsTimer === "undefined") reflectBulletsTimer = 0;
if (reflectBulletsTimer > 0) {
reflectBulletsTimer += 180;
showFloatingText("Reflect Combo!", ship.x, ship.y - 120, 0xcddc39);
} else {
reflectBulletsTimer = 360;
showFloatingText("Reflect Bullets!", ship.x, ship.y - 120, 0xcddc39);
}
// 18. healthBoost
} else if (type === 'healthBoost') {
ship.maxHealth += 4;
ship.heal(4);
showFloatingText("Max Health Up!", ship.x, ship.y - 120, 0xff1744);
// 19. scoreBoost
} else if (type === 'scoreBoost') {
score += 100;
scoreTxt.setText(score);
showFloatingText("+100 Score!", ship.x, ship.y - 120, 0xffea00);
// 20. enemySlow
} else if (type === 'enemySlow') {
if (typeof enemySlowTimer === "undefined") enemySlowTimer = 0;
if (enemySlowTimer > 0) {
enemySlowTimer += 180;
showFloatingText("Enemy Slow Combo!", ship.x, ship.y - 120, 0x607d8b);
} else {
enemySlowTimer = 360;
showFloatingText("Enemies Slowed!", ship.x, ship.y - 120, 0x607d8b);
}
// 21. enemyStun (fallback, rarely picked)
} else if (type === 'enemyStun') {
if (typeof enemyStunTimer === "undefined") enemyStunTimer = 0;
if (enemyStunTimer > 0) {
enemyStunTimer += 120;
showFloatingText("Stun Combo!", ship.x, ship.y - 120, 0x6d4c41);
} else {
enemyStunTimer = 240;
showFloatingText("Enemies Stunned!", ship.x, ship.y - 120, 0x6d4c41);
}
}
}; // Always apply the main powerup type
shipPowerupIntersect = true;
// Apply powerup
LK.getSound('powerup').play();
// Fun: confetti burst effect on powerup collect!
for (confetti = 0; confetti < 18; confetti++) {
(function () {
var part = new Container();
var c = part.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4 + Math.random() * 0.5,
scaleY: 0.4 + Math.random() * 0.5,
tint: 0xffe100 + Math.floor(Math.random() * 0xffffff)
});
part.x = ship.x;
part.y = ship.y - 40;
part.alpha = 0.85;
game.addChild(part);
var angle = Math.random() * Math.PI * 2;
var dist = 120 + Math.random() * 80;
var dx = Math.cos(angle) * dist;
var dy = Math.sin(angle) * dist;
tween(part, {
x: part.x + dx,
y: part.y + dy,
alpha: 0
}, {
duration: 600 + Math.random() * 400,
onFinish: function onFinish() {
part.destroy();
}
});
})();
}
applyPowerupType(p.type);
// If this powerup has extraTypes (multi-powerup), apply those as well
if (p.extraTypes && p.extraTypes.length > 0) {
for (et = 0; et < p.extraTypes.length; et++) {
applyPowerupType(p.extraTypes[et]);
// Show a floating text for the extra powerup
showFloatingText("Bonus: " + p.extraTypes[et], ship.x, ship.y - 180 - et * 60, 0xffe100);
}
}
// Combo fusion: if multiple effects are active, show a special floating text!
comboActiveCount = 0;
if (ship.rapidFire) comboActiveCount++;
if (doubleScoreActive) comboActiveCount++;
if (tripleShotActive) comboActiveCount++;
if (slowMotionActive) comboActiveCount++;
if (invincibilityActive) comboActiveCount++;
if (ship.shield) comboActiveCount++;
if (typeof freezeEnemiesTimer !== "undefined" && freezeEnemiesTimer > 0) comboActiveCount++;
if (typeof homingBulletsTimer !== "undefined" && homingBulletsTimer > 0) comboActiveCount++;
if (typeof reflectBulletsTimer !== "undefined" && reflectBulletsTimer > 0) comboActiveCount++;
if (typeof enemySlowTimer !== "undefined" && enemySlowTimer > 0) comboActiveCount++;
if (typeof enemyStunTimer !== "undefined" && enemyStunTimer > 0) comboActiveCount++;
if (comboActiveCount >= 3) {
showFloatingText("Combo Fusion! (" + comboActiveCount + ")", ship.x, ship.y - 220, 0xffe100);
}
p.destroy();
powerups.splice(i, 1);
}
},
p,
confetti,
et,
comboActiveCount;
for (var i = powerups.length - 1; i >= 0; i--) {
_loop();
}
lastShipPowerupIntersect = shipPowerupIntersect;
// --- Mission system: show floating text for milestones ---
if (score > 0 && score % 100 === 0 && !game['milestone_' + score]) {
showFloatingText("Milestone: " + score + "!", GAME_W / 2, 320, 0xffe100);
game['milestone_' + score] = true;
}
// --- Background color cycling every 5000 points after 5000 ---
// Define a palette of beautiful background colors, starting with sky blue
if (typeof bgColors === "undefined") {
var bgColors = [0x87ceeb,
// sky blue
0x1a237e,
// indigo
0x004d40,
// teal dark
0x263238,
// blue grey
0x880e4f,
// deep pink
0x1565c0,
// blue
0x2e7d32,
// green
0xff6f00,
// orange
0x4a148c,
// purple
0x212121,
// dark grey
0x00695c,
// teal
0x3949ab,
// blue indigo
0xc62828,
// red
0x00838f,
// cyan
0x6d4c41,
// brown
0x283593,
// deep blue
0x000000 // black (always last)
];
var lastBgScoreStep = 0;
// Set initial background to sky blue
game.setBackgroundColor(bgColors[0]);
}
// Calculate which color to use based on score
var bgScoreStep = score < 5000 ? 0 : 1 + Math.floor((score - 5000) / 5000);
if (bgScoreStep !== lastBgScoreStep) {
var paletteLen = bgColors.length;
var colorIdx;
if (bgScoreStep < paletteLen - 1) {
colorIdx = bgScoreStep;
} else if (bgScoreStep === paletteLen - 1) {
colorIdx = paletteLen - 1; // black
} else {
// After black, restart from the beginning (skip black until next full cycle)
colorIdx = (bgScoreStep - (paletteLen - 1)) % (paletteLen - 1);
}
game.setBackgroundColor(bgColors[colorIdx]);
lastBgScoreStep = bgScoreStep;
}
// --- Feature selection popup removed ---
// --- Enemy spawn ---
enemySpawnTimer--;
if (enemySpawnTimer <= 0) {
// Randomly pick enemy type
var enemyTypeRand = Math.random();
var e;
if (enemyTypeRand < 0.18) {
e = new FastEnemy();
} else if (enemyTypeRand < 0.36) {
e = new TankEnemy();
} else if (enemyTypeRand < 0.54) {
e = new ZigzagEnemy();
} else if (enemyTypeRand < 0.70) {
e = new Enemy();
} else {
e = new BigEnemy();
}
e.x = 180 + Math.random() * (GAME_W - 360);
e.y = -100;
// Removed enemy speed scaling with score to keep game speed consistent
enemies.push(e);
game.addChild(e);
// Decrease interval as score increases
enemySpawnInterval = Math.max(minEnemyInterval, 60 - Math.floor(score / 100) * 4);
enemySpawnTimer = enemySpawnInterval;
}
// --- Powerup spawn ---
powerupTimer--;
if (powerupTimer <= 0) {
var p = new Powerup();
p.x = 180 + Math.random() * (GAME_W - 360);
p.y = -80;
powerups.push(p);
game.addChild(p);
powerupInterval = 120 + Math.floor(Math.random() * 120);
powerupTimer = powerupInterval;
}
};
// Floating text helper for feedback
function showFloatingText(txt, x, y, color) {
var t = new Text2(txt, {
size: 90,
fill: color || 0xffffff,
stroke: 0x000000,
strokeThickness: 8
});
t.anchor.set(0.5, 1);
t.x = x;
t.y = y;
game.addChild(t);
tween(t, {
y: y - 120,
alpha: 0
}, {
duration: 700,
onFinish: function onFinish() {
t.destroy();
}
});
}
// --- Start Menu Overlay ---
var menuOverlay = new Container();
menuOverlay.interactive = true;
menuOverlay.x = 0;
menuOverlay.y = 0;
menuOverlay.width = GAME_W;
menuOverlay.height = GAME_H;
// Dim background
var menuBg = LK.getAsset('playerBullet', {
anchorX: 0,
anchorY: 0,
scaleX: GAME_W / 30,
scaleY: GAME_H / 60,
tint: 0x000000,
alpha: 0.82,
x: 0,
y: 0
});
menuOverlay.addChild(menuBg);
// Title
var menuTitle = new Text2("SPACE BLASTERS", {
size: 160,
fill: 0xffe100,
stroke: 0x000000,
strokeThickness: 12,
align: "center"
});
menuTitle.anchor.set(0.5, 0.5);
menuTitle.x = GAME_W / 2;
menuTitle.y = GAME_H / 2 - 320;
menuOverlay.addChild(menuTitle);
// Instructions
var menuInstructions = new Text2("Drag the ship to move\nAuto-fire enabled\nCollect powerups!", {
size: 90,
fill: 0xffffff,
stroke: 0x000000,
strokeThickness: 8,
align: "center"
});
menuInstructions.anchor.set(0.5, 0.5);
menuInstructions.x = GAME_W / 2;
menuInstructions.y = GAME_H / 2 - 80;
menuOverlay.addChild(menuInstructions);
// Start button
var startBtn = new Text2("TAP TO START", {
size: 110,
fill: 0x00ff88,
stroke: 0x000000,
strokeThickness: 10,
align: "center"
});
startBtn.anchor.set(0.5, 0.5);
startBtn.x = GAME_W / 2;
startBtn.y = GAME_H / 2 + 260;
menuOverlay.addChild(startBtn);
game.addChild(menuOverlay);
// Pause game logic until menu is dismissed
var gamePausedForMenu = true;
var origGameUpdate = game.update;
game.update = function () {
if (gamePausedForMenu) return;
origGameUpdate.call(game);
};
// Dismiss menu on tap/click anywhere
menuOverlay.down = function (x, y, obj) {
if (!gamePausedForMenu) return;
gamePausedForMenu = false;
tween(menuOverlay, {
alpha: 0
}, {
duration: 400,
onFinish: function onFinish() {
menuOverlay.destroy();
}
});
};
// Forward input to menu overlay
game.down = function (x, y, obj) {
if (gamePausedForMenu && menuOverlay.down) {
menuOverlay.down(x, y, obj);
return;
}
// User-friendly: allow drag to start if touch is on ship OR anywhere in lower half of screen
var dx = x - ship.x,
dy = y - ship.y;
var onShip = dx * dx + dy * dy < ship.radius * ship.radius * 1.2;
var inLowerHalf = y > GAME_H / 2;
if (onShip || inLowerHalf) {
dragNode = ship;
handleMove(x, y, obj);
LK.effects.flashObject(ship, 0x33c1ff, 120);
if (!onShip && inLowerHalf && !dragAnywhereHintShown) {
showFloatingText("Tip: Drag anywhere below to move!", ship.x, ship.y - 180, 0x33c1ff);
dragAnywhereHintShown = true;
}
}
};
game.move = function (x, y, obj) {
if (gamePausedForMenu) return;
handleMove(x, y, obj);
};
game.up = function (x, y, obj) {
if (gamePausedForMenu) return;
dragNode = null;
};
// Define freezeActive and stunActive for menu overlay logic
var freezeActive = typeof freezeEnemiesTimer !== "undefined" && freezeEnemiesTimer > 0;
var stunActive = typeof enemyStunTimer !== "undefined" && enemyStunTimer > 0;
for (var i = 0; i < enemies.length; i++) {
var e = enemies[i];
if (!freezeActive && !stunActive) {
// SpiralEnemy: burst shoot 3 bullets in a spread
if (e.constructor === SpiralEnemy && e.shootCooldown <= 0) {
for (var b = -1; b <= 1; b++) {
var eb = new EnemyBullet();
eb.x = e.x;
eb.y = e.y + e.radius + 10;
// Spread: aim at ship, but offset angle
var dx = ship.x - e.x,
dy = ship.y - e.y;
var len = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx) + b * 0.18;
if (len > 0) {
eb.dirX = Math.cos(angle);
eb.dirY = Math.sin(angle);
}
enemyBullets.push(eb);
game.addChild(eb);
}
e.shootCooldown = 120 + Math.floor(Math.random() * 40);
} else if (e.shootCooldown <= 0) {
var eb = new EnemyBullet();
eb.x = e.x;
eb.y = e.y + e.radius + 10;
// Aim at ship
var dx = ship.x - e.x,
dy = ship.y - e.y;
var len = Math.sqrt(dx * dx + dy * dy);
if (len > 0) {
eb.dirX = dx / len;
eb.dirY = dy / len;
}
enemyBullets.push(eb);
game.addChild(eb);
e.shootCooldown = 90 + Math.floor(Math.random() * 60);
}
}
}