/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Enemy
var Enemy = Container.expand(function () {
var self = Container.call(this);
var enemyGfx = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = enemyGfx.width;
self.height = enemyGfx.height;
self.speed = 5 + Math.random() * 4;
self.hp = 1;
self.shootCooldown = 0;
self.type = 'normal'; // or 'boss'
self.update = function () {
// Check if enemy is in falling state
if (self.isFalling) {
return; // Let the falling animation be managed separately
}
self.x -= self.speed;
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
};
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 = 8;
self.update = function () {
self.x -= self.speed;
};
return self;
});
// Explosion effect
var Explosion = Container.expand(function () {
var self = Container.call(this);
var explosionGfx = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xff5500
});
self.lifespan = 20;
self.scale = 0.2;
// Animation effect
self.start = function (size) {
self.scale = size || 1;
explosionGfx.scale.x = 0.2;
explosionGfx.scale.y = 0.2;
explosionGfx.alpha = 1;
explosionGfx.tint = 0xff5500;
// Growth animation
tween(explosionGfx, {
scaleX: self.scale,
scaleY: self.scale
}, {
duration: 200,
easing: tween.easeOut
});
// Color change animation
tween(explosionGfx, {
tint: 0xffcc00
}, {
duration: 100,
easing: tween.linear,
onFinish: function onFinish() {
tween(explosionGfx, {
tint: 0xffffaa,
alpha: 0
}, {
duration: 300,
easing: tween.easeOut
});
}
});
};
self.update = function () {
self.lifespan--;
if (self.lifespan <= 0) {
self.destroy();
}
};
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 = 12;
self.update = function () {
// Default angle is 0 (right)
if (typeof self.angle === "undefined") {
self.angle = 0;
}
self.x += self.speed * Math.cos(self.angle);
self.y += self.speed * Math.sin(self.angle);
};
return self;
});
// Power-up
var Powerup = Container.expand(function () {
var self = Container.call(this);
var powerGfx = self.attachAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 6;
self.type = 'power'; // could be 'power', 'heal', etc.
self.update = function () {
self.x -= self.speed;
};
return self;
});
// Player spaceship
var Ship = Container.expand(function () {
var self = Container.call(this);
var shipGfx = self.attachAsset('ship', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = shipGfx.width;
self.height = shipGfx.height;
self.fireCooldown = 0;
self.powerLevel = 1; // 1=single, 2=double, 3=triple shot
self.invincible = false;
self.invincibleTimer = 0;
// Invincibility flash effect
self.setInvincible = function (duration) {
self.invincible = true;
self.invincibleTimer = duration;
tween(shipGfx, {
alpha: 0.4
}, {
duration: 100,
easing: tween.linear,
onFinish: function onFinish() {
tween(shipGfx, {
alpha: 1
}, {
duration: 100,
easing: tween.linear
});
}
});
};
// Called every tick
self.update = function () {
if (self.invincible) {
self.invincibleTimer -= 1;
if (self.invincibleTimer <= 0) {
self.invincible = false;
shipGfx.alpha = 1;
}
}
// Track last drag direction for multi-directional shooting
if (typeof self.lastShootAngle === "undefined") {
self.lastShootAngle = 0;
}
if (typeof self.lastX === "undefined") {
self.lastX = self.x;
}
if (typeof self.lastY === "undefined") {
self.lastY = self.y;
}
if (self.x !== self.lastX || self.y !== self.lastY) {
// Calculate angle of movement
var dx = self.x - self.lastX;
var dy = self.y - self.lastY;
if (dx !== 0 || dy !== 0) {
self.lastShootAngle = Math.atan2(dy, dx);
}
}
self.lastX = self.x;
self.lastY = self.y;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000010
});
/****
* Game Code
****/
// Background
var background = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.addChild(background);
// Music
// Sound effects
// Boss
// Power-up
// Enemy bullet
// Enemy
// Player bullet
// Spaceship (player)
// Play background music
LK.playMusic('bgmusic');
// Game variables
var ship;
var playerBullets = [];
var enemies = [];
var enemyBullets = [];
var powerups = [];
var fallingEnemies = [];
var scoreTxt;
var levelTxt;
var dragNode = null;
var lastShipX = 0,
lastShipY = 0;
var spawnTimer = 0;
var powerupTimer = 0;
var level = 1;
var gameOver = false;
// Bullet pools for reusing objects (reduces garbage collection)
var playerBulletPool = [];
var enemyBulletPool = [];
// Helper function to get bullet from pool or create new one
function getPlayerBulletFromPool() {
if (playerBulletPool.length > 0) {
var bullet = playerBulletPool.pop();
bullet.visible = true;
return bullet;
}
return new PlayerBullet();
}
function getEnemyBulletFromPool() {
if (enemyBulletPool.length > 0) {
var bullet = enemyBulletPool.pop();
bullet.visible = true;
return bullet;
}
return new EnemyBullet();
}
// Helper function to return bullet to pool
function returnPlayerBulletToPool(bullet, index) {
bullet.visible = false;
playerBulletPool.push(bullet);
playerBullets.splice(index, 1);
}
function returnEnemyBulletToPool(bullet, index) {
bullet.visible = false;
enemyBulletPool.push(bullet);
enemyBullets.splice(index, 1);
}
// Score display
scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Level display removed
// Create player ship
ship = new Ship();
game.addChild(ship);
ship.x = 2048 / 2;
ship.y = 2732 / 2;
// Make ship visible and interactive
ship.interactive = true;
ship.buttonMode = true;
// Helper: clamp ship inside screen
function clampShipPosition() {
var halfW = ship.width / 2;
var halfH = ship.height / 2;
if (ship.x < 100 + halfW) {
ship.x = 100 + halfW;
}
if (ship.x > 2048 - halfW) {
ship.x = 2048 - halfW;
}
if (ship.y < halfH) {
ship.y = halfH;
}
if (ship.y > 2732 - halfH) {
ship.y = 2732 - halfH;
}
}
// Handle dragging ship
function handleMove(x, y, obj) {
if (dragNode) {
// Direct positioning for immediate response
dragNode.x = x;
dragNode.y = y;
clampShipPosition();
}
}
game.move = handleMove;
game.down = function (x, y, obj) {
// Improved touch detection with slightly larger hitbox for better responsiveness
var local = ship.toLocal(game.toGlobal({
x: x,
y: y
}));
var touchPadding = 20; // Extra padding for easier touch
if (local.x > -ship.width / 2 - touchPadding && local.x < ship.width / 2 + touchPadding && local.y > -ship.height / 2 - touchPadding && local.y < ship.height / 2 + touchPadding) {
dragNode = ship;
// Immediate position update
handleMove(x, y, obj);
}
};
game.up = function (x, y, obj) {
// Make sure position is updated one last time before releasing
if (dragNode) {
handleMove(x, y, obj);
}
dragNode = null;
};
// Spawn enemy
function spawnEnemy() {
var e = new Enemy();
e.x = 2048 + e.width / 2;
e.y = 200 + Math.random() * (2732 - 400);
e.hp = 1 + Math.floor(level / 2);
e.speed = 5 + Math.random() * 4 + level * 0.6;
e.shootCooldown = 90 + Math.floor(Math.random() * 80);
enemies.push(e);
game.addChild(e);
}
// Spawn powerup
function spawnPowerup() {
var p = new Powerup();
p.x = 2048 + 60;
p.y = 200 + Math.random() * (2732 - 400);
p.type = 'power';
powerups.push(p);
game.addChild(p);
}
// Main update loop
game.update = function () {
if (gameOver) {
return;
}
// Ship update
ship.update();
// Auto-fire
if (ship.fireCooldown > 0) {
ship.fireCooldown--;
}
if (ship.fireCooldown <= 0) {
// Fire based on power level, but allow multi-directional shooting
var bulletAngles = [ship.lastShootAngle || 0];
if (ship.powerLevel === 2) {
bulletAngles = [(ship.lastShootAngle || 0) - Math.PI / 16, (ship.lastShootAngle || 0) + Math.PI / 16];
}
if (ship.powerLevel >= 3) {
bulletAngles = [(ship.lastShootAngle || 0) - Math.PI / 8, ship.lastShootAngle || 0, (ship.lastShootAngle || 0) + Math.PI / 8];
}
for (var i = 0; i < bulletAngles.length; ++i) {
var b = getPlayerBulletFromPool();
// Start at ship's nose
b.x = ship.x + Math.cos(bulletAngles[i]) * (ship.width / 2 + 10);
b.y = ship.y + Math.sin(bulletAngles[i]) * (ship.width / 2 + 10);
b.angle = bulletAngles[i];
playerBullets.push(b);
if (!b.parent) {
game.addChild(b);
}
}
LK.getSound('shoot').play();
ship.fireCooldown = 18;
}
// Update player bullets - optimized to reduce collision checking
var bulletsToRemove = [];
for (var i = playerBullets.length - 1; i >= 0; --i) {
var b = playerBullets[i];
b.update();
// Remove if off screen
if (b.x > 2048 + 50 || b.x < -50 || b.y > 2732 + 50 || b.y < -50) {
returnPlayerBulletToPool(b, i);
continue;
}
// Check if bullet already marked for removal
if (bulletsToRemove.indexOf(i) !== -1) {
continue;
}
// Hit enemy - only check enemies in proximity
var bulletHitSomething = false;
for (var j = enemies.length - 1; j >= 0; --j) {
var e = enemies[j];
// Only check collision if enemy is close to bullet (rough proximity check)
if (Math.abs(e.x - b.x) < 150 && Math.abs(e.y - b.y) < 150) {
if (b.intersects(e)) {
e.hp--;
LK.getSound('explosion').play();
// Small hit explosion
var hitExplosion = new Explosion();
hitExplosion.x = b.x;
hitExplosion.y = b.y;
hitExplosion.start(0.5);
game.addChild(hitExplosion);
bulletHitSomething = true;
if (e.hp <= 0) {
// Create explosion at enemy position
var explosion = new Explosion();
explosion.x = e.x;
explosion.y = e.y;
explosion.start(1.2);
game.addChild(explosion);
LK.setScore(LK.getScore() + 10);
scoreTxt.setText(LK.getScore());
// Level progression for endless game
if (LK.getScore() > 0 && LK.getScore() % 100 === 0) {
level++;
// Flash effect
LK.effects.flashScreen(0x00ff00, 800);
// Power up as reward every few levels
if (level % 3 === 0) {
ship.powerLevel = Math.min(ship.powerLevel + 1, 3);
}
}
// Add falling animation instead of destroying immediately
e.isFalling = true;
e.fallSpeed = 2;
e.fallRotation = (Math.random() - 0.5) * 0.2;
e.fallAlpha = 1;
// Remove from enemies array but don't destroy yet
enemies.splice(j, 1);
// Add to separate falling enemies array to be managed separately
if (!fallingEnemies) {
fallingEnemies = [];
}
fallingEnemies.push(e);
}
break;
}
}
}
// If bullet hit an enemy, return the bullet to the pool and continue to next bullet
if (bulletHitSomething) {
returnPlayerBulletToPool(b, i);
continue;
}
}
// Update enemies - optimized
var enemiesNearPlayer = 0;
for (var i = enemies.length - 1; i >= 0; --i) {
var e = enemies[i];
e.update();
// Remove if off screen
if (e.x < -e.width / 2) {
e.destroy();
enemies.splice(i, 1);
continue;
}
// Check if enemy is near player
var isNearPlayer = Math.abs(e.x - ship.x) < 400;
if (isNearPlayer) {
enemiesNearPlayer++;
}
// Enemy shoots
if (e.shootCooldown <= 0 && e.x < 2048 && e.x > 0) {
// Limit shooting frequency based on how many enemies are near player
if (enemiesNearPlayer < 3 || Math.random() < 0.3) {
var eb = getEnemyBulletFromPool();
eb.x = e.x - e.width / 2 - 10;
eb.y = e.y;
enemyBullets.push(eb);
if (!eb.parent) {
game.addChild(eb);
}
LK.getSound('enemyShoot').play();
}
// Increase cooldown when many enemies are present
var cooldownBase = 80 + Math.floor(Math.random() * 60);
e.shootCooldown = cooldownBase * (1 + (enemies.length > 10 ? 0.5 : 0));
}
// Collide with ship - only check when close
if (!ship.invincible && isNearPlayer && Math.abs(e.y - ship.y) < 150) {
if (e.intersects(ship)) {
// Ship hit
// Create explosion at collision point
var explosion = new Explosion();
explosion.x = (ship.x + e.x) / 2;
explosion.y = (ship.y + e.y) / 2;
explosion.start(1);
game.addChild(explosion);
// Game over immediately when player is hit by enemy
gameOver = true;
LK.effects.flashObject(ship, 0xff0000, 600);
LK.effects.flashScreen(0xff0000, 400);
LK.showGameOver();
return;
}
}
}
// Update enemy bullets - batch process with reduced collision checks
var bulletsToCheck = [];
// First update all bullets and mark ones to remove (off-screen)
for (var i = enemyBullets.length - 1; i >= 0; --i) {
var eb = enemyBullets[i];
eb.update();
if (eb.x < -50) {
returnEnemyBulletToPool(eb, i);
continue;
}
// Only check collision if ship is not invincible and bullet is near ship
if (!ship.invincible && Math.abs(eb.x - ship.x) < 150 && Math.abs(eb.y - ship.y) < 150) {
bulletsToCheck.push({
index: i,
bullet: eb
});
}
}
// Now only check collisions for bullets near the ship
if (!ship.invincible) {
for (var i = 0; i < bulletsToCheck.length; i++) {
var bulletInfo = bulletsToCheck[i];
var eb = bulletInfo.bullet;
if (eb.intersects(ship)) {
// Create explosion at bullet position
var explosion = new Explosion();
explosion.x = eb.x;
explosion.y = eb.y;
explosion.start(1);
game.addChild(explosion);
LK.effects.flashObject(ship, 0xff0000, 600);
LK.effects.flashScreen(0xff0000, 400);
eb.destroy();
enemyBullets.splice(bulletInfo.index, 1);
// Game over immediately when player is hit by bullet
gameOver = true;
LK.showGameOver();
return;
break; // Exit loop after first hit
}
}
}
// Update powerups - only check collection when close
for (var i = powerups.length - 1; i >= 0; --i) {
var p = powerups[i];
p.update();
if (p.x < -50) {
p.destroy();
powerups.splice(i, 1);
continue;
}
// Collect - only check intersection when close to ship
if (Math.abs(p.x - ship.x) < 120 && Math.abs(p.y - ship.y) < 120) {
if (p.intersects(ship)) {
LK.getSound('powerup').play();
ship.powerLevel = Math.min(ship.powerLevel + 1, 3);
LK.setScore(LK.getScore() + 30);
scoreTxt.setText(LK.getScore());
p.destroy();
powerups.splice(i, 1);
}
}
}
// Spawning logic - always active in endless mode
spawnTimer--;
if (spawnTimer <= 0) {
// Spawn more enemies at higher levels
var enemiesToSpawn = 1 + Math.floor(level / 5);
for (var i = 0; i < enemiesToSpawn; i++) {
if (enemies.length < 25) {
// Cap max enemies to prevent lag
spawnEnemy();
}
}
spawnTimer = Math.max(30, 100 - level * 3);
}
// Powerup spawn
powerupTimer--;
if (powerupTimer <= 0) {
spawnPowerup();
powerupTimer = 600 + Math.floor(Math.random() * 400);
}
// Increase enemy difficulty with level
if (LK.ticks % 600 === 0) {
for (var i = 0; i < enemies.length; i++) {
// Slightly increase enemy speed based on level
if (Math.random() < 0.3) {
enemies[i].speed += 0.2;
}
}
}
// Update falling enemies
if (fallingEnemies && fallingEnemies.length > 0) {
for (var i = fallingEnemies.length - 1; i >= 0; i--) {
var fe = fallingEnemies[i];
if (fe.isFalling) {
// Add gravity effect
fe.fallSpeed += 0.3;
fe.y += fe.fallSpeed;
// Add rotation
fe.rotation += fe.fallRotation;
// Fade out
fe.fallAlpha -= 0.02;
if (fe.fallAlpha <= 0) {
fe.fallAlpha = 0;
}
fe.alpha = fe.fallAlpha;
// Remove when fallen off screen or fully faded
if (fe.y > 2732 + fe.height || fe.fallAlpha <= 0) {
fe.destroy();
fallingEnemies.splice(i, 1);
}
}
}
}
// No win condition - endless game
};
// Reset game state on restart
game.on('reset', function () {
// Return bullets to pools instead of destroying
for (var i = 0; i < playerBullets.length; ++i) {
playerBullets[i].visible = false;
playerBulletPool.push(playerBullets[i]);
}
for (var i = 0; i < enemyBullets.length; ++i) {
enemyBullets[i].visible = false;
enemyBulletPool.push(enemyBullets[i]);
}
// Destroy other objects
for (var i = 0; i < enemies.length; ++i) {
enemies[i].destroy();
}
// Destroy falling enemies
if (fallingEnemies) {
for (var i = 0; i < fallingEnemies.length; ++i) {
fallingEnemies[i].destroy();
}
}
for (var i = 0; i < powerups.length; ++i) {
powerups[i].destroy();
}
playerBullets = [];
enemies = [];
enemyBullets = [];
powerups = [];
fallingEnemies = [];
level = 1;
ship.x = 2048 / 2; // Center horizontally
ship.y = 2732 / 2;
ship.powerLevel = 1;
ship.invincible = false;
ship.invincibleTimer = 0;
LK.setScore(0);
scoreTxt.setText('0');
spawnTimer = 30;
powerupTimer = 400;
gameOver = false;
}); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Enemy
var Enemy = Container.expand(function () {
var self = Container.call(this);
var enemyGfx = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = enemyGfx.width;
self.height = enemyGfx.height;
self.speed = 5 + Math.random() * 4;
self.hp = 1;
self.shootCooldown = 0;
self.type = 'normal'; // or 'boss'
self.update = function () {
// Check if enemy is in falling state
if (self.isFalling) {
return; // Let the falling animation be managed separately
}
self.x -= self.speed;
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
};
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 = 8;
self.update = function () {
self.x -= self.speed;
};
return self;
});
// Explosion effect
var Explosion = Container.expand(function () {
var self = Container.call(this);
var explosionGfx = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xff5500
});
self.lifespan = 20;
self.scale = 0.2;
// Animation effect
self.start = function (size) {
self.scale = size || 1;
explosionGfx.scale.x = 0.2;
explosionGfx.scale.y = 0.2;
explosionGfx.alpha = 1;
explosionGfx.tint = 0xff5500;
// Growth animation
tween(explosionGfx, {
scaleX: self.scale,
scaleY: self.scale
}, {
duration: 200,
easing: tween.easeOut
});
// Color change animation
tween(explosionGfx, {
tint: 0xffcc00
}, {
duration: 100,
easing: tween.linear,
onFinish: function onFinish() {
tween(explosionGfx, {
tint: 0xffffaa,
alpha: 0
}, {
duration: 300,
easing: tween.easeOut
});
}
});
};
self.update = function () {
self.lifespan--;
if (self.lifespan <= 0) {
self.destroy();
}
};
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 = 12;
self.update = function () {
// Default angle is 0 (right)
if (typeof self.angle === "undefined") {
self.angle = 0;
}
self.x += self.speed * Math.cos(self.angle);
self.y += self.speed * Math.sin(self.angle);
};
return self;
});
// Power-up
var Powerup = Container.expand(function () {
var self = Container.call(this);
var powerGfx = self.attachAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 6;
self.type = 'power'; // could be 'power', 'heal', etc.
self.update = function () {
self.x -= self.speed;
};
return self;
});
// Player spaceship
var Ship = Container.expand(function () {
var self = Container.call(this);
var shipGfx = self.attachAsset('ship', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = shipGfx.width;
self.height = shipGfx.height;
self.fireCooldown = 0;
self.powerLevel = 1; // 1=single, 2=double, 3=triple shot
self.invincible = false;
self.invincibleTimer = 0;
// Invincibility flash effect
self.setInvincible = function (duration) {
self.invincible = true;
self.invincibleTimer = duration;
tween(shipGfx, {
alpha: 0.4
}, {
duration: 100,
easing: tween.linear,
onFinish: function onFinish() {
tween(shipGfx, {
alpha: 1
}, {
duration: 100,
easing: tween.linear
});
}
});
};
// Called every tick
self.update = function () {
if (self.invincible) {
self.invincibleTimer -= 1;
if (self.invincibleTimer <= 0) {
self.invincible = false;
shipGfx.alpha = 1;
}
}
// Track last drag direction for multi-directional shooting
if (typeof self.lastShootAngle === "undefined") {
self.lastShootAngle = 0;
}
if (typeof self.lastX === "undefined") {
self.lastX = self.x;
}
if (typeof self.lastY === "undefined") {
self.lastY = self.y;
}
if (self.x !== self.lastX || self.y !== self.lastY) {
// Calculate angle of movement
var dx = self.x - self.lastX;
var dy = self.y - self.lastY;
if (dx !== 0 || dy !== 0) {
self.lastShootAngle = Math.atan2(dy, dx);
}
}
self.lastX = self.x;
self.lastY = self.y;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000010
});
/****
* Game Code
****/
// Background
var background = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.addChild(background);
// Music
// Sound effects
// Boss
// Power-up
// Enemy bullet
// Enemy
// Player bullet
// Spaceship (player)
// Play background music
LK.playMusic('bgmusic');
// Game variables
var ship;
var playerBullets = [];
var enemies = [];
var enemyBullets = [];
var powerups = [];
var fallingEnemies = [];
var scoreTxt;
var levelTxt;
var dragNode = null;
var lastShipX = 0,
lastShipY = 0;
var spawnTimer = 0;
var powerupTimer = 0;
var level = 1;
var gameOver = false;
// Bullet pools for reusing objects (reduces garbage collection)
var playerBulletPool = [];
var enemyBulletPool = [];
// Helper function to get bullet from pool or create new one
function getPlayerBulletFromPool() {
if (playerBulletPool.length > 0) {
var bullet = playerBulletPool.pop();
bullet.visible = true;
return bullet;
}
return new PlayerBullet();
}
function getEnemyBulletFromPool() {
if (enemyBulletPool.length > 0) {
var bullet = enemyBulletPool.pop();
bullet.visible = true;
return bullet;
}
return new EnemyBullet();
}
// Helper function to return bullet to pool
function returnPlayerBulletToPool(bullet, index) {
bullet.visible = false;
playerBulletPool.push(bullet);
playerBullets.splice(index, 1);
}
function returnEnemyBulletToPool(bullet, index) {
bullet.visible = false;
enemyBulletPool.push(bullet);
enemyBullets.splice(index, 1);
}
// Score display
scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Level display removed
// Create player ship
ship = new Ship();
game.addChild(ship);
ship.x = 2048 / 2;
ship.y = 2732 / 2;
// Make ship visible and interactive
ship.interactive = true;
ship.buttonMode = true;
// Helper: clamp ship inside screen
function clampShipPosition() {
var halfW = ship.width / 2;
var halfH = ship.height / 2;
if (ship.x < 100 + halfW) {
ship.x = 100 + halfW;
}
if (ship.x > 2048 - halfW) {
ship.x = 2048 - halfW;
}
if (ship.y < halfH) {
ship.y = halfH;
}
if (ship.y > 2732 - halfH) {
ship.y = 2732 - halfH;
}
}
// Handle dragging ship
function handleMove(x, y, obj) {
if (dragNode) {
// Direct positioning for immediate response
dragNode.x = x;
dragNode.y = y;
clampShipPosition();
}
}
game.move = handleMove;
game.down = function (x, y, obj) {
// Improved touch detection with slightly larger hitbox for better responsiveness
var local = ship.toLocal(game.toGlobal({
x: x,
y: y
}));
var touchPadding = 20; // Extra padding for easier touch
if (local.x > -ship.width / 2 - touchPadding && local.x < ship.width / 2 + touchPadding && local.y > -ship.height / 2 - touchPadding && local.y < ship.height / 2 + touchPadding) {
dragNode = ship;
// Immediate position update
handleMove(x, y, obj);
}
};
game.up = function (x, y, obj) {
// Make sure position is updated one last time before releasing
if (dragNode) {
handleMove(x, y, obj);
}
dragNode = null;
};
// Spawn enemy
function spawnEnemy() {
var e = new Enemy();
e.x = 2048 + e.width / 2;
e.y = 200 + Math.random() * (2732 - 400);
e.hp = 1 + Math.floor(level / 2);
e.speed = 5 + Math.random() * 4 + level * 0.6;
e.shootCooldown = 90 + Math.floor(Math.random() * 80);
enemies.push(e);
game.addChild(e);
}
// Spawn powerup
function spawnPowerup() {
var p = new Powerup();
p.x = 2048 + 60;
p.y = 200 + Math.random() * (2732 - 400);
p.type = 'power';
powerups.push(p);
game.addChild(p);
}
// Main update loop
game.update = function () {
if (gameOver) {
return;
}
// Ship update
ship.update();
// Auto-fire
if (ship.fireCooldown > 0) {
ship.fireCooldown--;
}
if (ship.fireCooldown <= 0) {
// Fire based on power level, but allow multi-directional shooting
var bulletAngles = [ship.lastShootAngle || 0];
if (ship.powerLevel === 2) {
bulletAngles = [(ship.lastShootAngle || 0) - Math.PI / 16, (ship.lastShootAngle || 0) + Math.PI / 16];
}
if (ship.powerLevel >= 3) {
bulletAngles = [(ship.lastShootAngle || 0) - Math.PI / 8, ship.lastShootAngle || 0, (ship.lastShootAngle || 0) + Math.PI / 8];
}
for (var i = 0; i < bulletAngles.length; ++i) {
var b = getPlayerBulletFromPool();
// Start at ship's nose
b.x = ship.x + Math.cos(bulletAngles[i]) * (ship.width / 2 + 10);
b.y = ship.y + Math.sin(bulletAngles[i]) * (ship.width / 2 + 10);
b.angle = bulletAngles[i];
playerBullets.push(b);
if (!b.parent) {
game.addChild(b);
}
}
LK.getSound('shoot').play();
ship.fireCooldown = 18;
}
// Update player bullets - optimized to reduce collision checking
var bulletsToRemove = [];
for (var i = playerBullets.length - 1; i >= 0; --i) {
var b = playerBullets[i];
b.update();
// Remove if off screen
if (b.x > 2048 + 50 || b.x < -50 || b.y > 2732 + 50 || b.y < -50) {
returnPlayerBulletToPool(b, i);
continue;
}
// Check if bullet already marked for removal
if (bulletsToRemove.indexOf(i) !== -1) {
continue;
}
// Hit enemy - only check enemies in proximity
var bulletHitSomething = false;
for (var j = enemies.length - 1; j >= 0; --j) {
var e = enemies[j];
// Only check collision if enemy is close to bullet (rough proximity check)
if (Math.abs(e.x - b.x) < 150 && Math.abs(e.y - b.y) < 150) {
if (b.intersects(e)) {
e.hp--;
LK.getSound('explosion').play();
// Small hit explosion
var hitExplosion = new Explosion();
hitExplosion.x = b.x;
hitExplosion.y = b.y;
hitExplosion.start(0.5);
game.addChild(hitExplosion);
bulletHitSomething = true;
if (e.hp <= 0) {
// Create explosion at enemy position
var explosion = new Explosion();
explosion.x = e.x;
explosion.y = e.y;
explosion.start(1.2);
game.addChild(explosion);
LK.setScore(LK.getScore() + 10);
scoreTxt.setText(LK.getScore());
// Level progression for endless game
if (LK.getScore() > 0 && LK.getScore() % 100 === 0) {
level++;
// Flash effect
LK.effects.flashScreen(0x00ff00, 800);
// Power up as reward every few levels
if (level % 3 === 0) {
ship.powerLevel = Math.min(ship.powerLevel + 1, 3);
}
}
// Add falling animation instead of destroying immediately
e.isFalling = true;
e.fallSpeed = 2;
e.fallRotation = (Math.random() - 0.5) * 0.2;
e.fallAlpha = 1;
// Remove from enemies array but don't destroy yet
enemies.splice(j, 1);
// Add to separate falling enemies array to be managed separately
if (!fallingEnemies) {
fallingEnemies = [];
}
fallingEnemies.push(e);
}
break;
}
}
}
// If bullet hit an enemy, return the bullet to the pool and continue to next bullet
if (bulletHitSomething) {
returnPlayerBulletToPool(b, i);
continue;
}
}
// Update enemies - optimized
var enemiesNearPlayer = 0;
for (var i = enemies.length - 1; i >= 0; --i) {
var e = enemies[i];
e.update();
// Remove if off screen
if (e.x < -e.width / 2) {
e.destroy();
enemies.splice(i, 1);
continue;
}
// Check if enemy is near player
var isNearPlayer = Math.abs(e.x - ship.x) < 400;
if (isNearPlayer) {
enemiesNearPlayer++;
}
// Enemy shoots
if (e.shootCooldown <= 0 && e.x < 2048 && e.x > 0) {
// Limit shooting frequency based on how many enemies are near player
if (enemiesNearPlayer < 3 || Math.random() < 0.3) {
var eb = getEnemyBulletFromPool();
eb.x = e.x - e.width / 2 - 10;
eb.y = e.y;
enemyBullets.push(eb);
if (!eb.parent) {
game.addChild(eb);
}
LK.getSound('enemyShoot').play();
}
// Increase cooldown when many enemies are present
var cooldownBase = 80 + Math.floor(Math.random() * 60);
e.shootCooldown = cooldownBase * (1 + (enemies.length > 10 ? 0.5 : 0));
}
// Collide with ship - only check when close
if (!ship.invincible && isNearPlayer && Math.abs(e.y - ship.y) < 150) {
if (e.intersects(ship)) {
// Ship hit
// Create explosion at collision point
var explosion = new Explosion();
explosion.x = (ship.x + e.x) / 2;
explosion.y = (ship.y + e.y) / 2;
explosion.start(1);
game.addChild(explosion);
// Game over immediately when player is hit by enemy
gameOver = true;
LK.effects.flashObject(ship, 0xff0000, 600);
LK.effects.flashScreen(0xff0000, 400);
LK.showGameOver();
return;
}
}
}
// Update enemy bullets - batch process with reduced collision checks
var bulletsToCheck = [];
// First update all bullets and mark ones to remove (off-screen)
for (var i = enemyBullets.length - 1; i >= 0; --i) {
var eb = enemyBullets[i];
eb.update();
if (eb.x < -50) {
returnEnemyBulletToPool(eb, i);
continue;
}
// Only check collision if ship is not invincible and bullet is near ship
if (!ship.invincible && Math.abs(eb.x - ship.x) < 150 && Math.abs(eb.y - ship.y) < 150) {
bulletsToCheck.push({
index: i,
bullet: eb
});
}
}
// Now only check collisions for bullets near the ship
if (!ship.invincible) {
for (var i = 0; i < bulletsToCheck.length; i++) {
var bulletInfo = bulletsToCheck[i];
var eb = bulletInfo.bullet;
if (eb.intersects(ship)) {
// Create explosion at bullet position
var explosion = new Explosion();
explosion.x = eb.x;
explosion.y = eb.y;
explosion.start(1);
game.addChild(explosion);
LK.effects.flashObject(ship, 0xff0000, 600);
LK.effects.flashScreen(0xff0000, 400);
eb.destroy();
enemyBullets.splice(bulletInfo.index, 1);
// Game over immediately when player is hit by bullet
gameOver = true;
LK.showGameOver();
return;
break; // Exit loop after first hit
}
}
}
// Update powerups - only check collection when close
for (var i = powerups.length - 1; i >= 0; --i) {
var p = powerups[i];
p.update();
if (p.x < -50) {
p.destroy();
powerups.splice(i, 1);
continue;
}
// Collect - only check intersection when close to ship
if (Math.abs(p.x - ship.x) < 120 && Math.abs(p.y - ship.y) < 120) {
if (p.intersects(ship)) {
LK.getSound('powerup').play();
ship.powerLevel = Math.min(ship.powerLevel + 1, 3);
LK.setScore(LK.getScore() + 30);
scoreTxt.setText(LK.getScore());
p.destroy();
powerups.splice(i, 1);
}
}
}
// Spawning logic - always active in endless mode
spawnTimer--;
if (spawnTimer <= 0) {
// Spawn more enemies at higher levels
var enemiesToSpawn = 1 + Math.floor(level / 5);
for (var i = 0; i < enemiesToSpawn; i++) {
if (enemies.length < 25) {
// Cap max enemies to prevent lag
spawnEnemy();
}
}
spawnTimer = Math.max(30, 100 - level * 3);
}
// Powerup spawn
powerupTimer--;
if (powerupTimer <= 0) {
spawnPowerup();
powerupTimer = 600 + Math.floor(Math.random() * 400);
}
// Increase enemy difficulty with level
if (LK.ticks % 600 === 0) {
for (var i = 0; i < enemies.length; i++) {
// Slightly increase enemy speed based on level
if (Math.random() < 0.3) {
enemies[i].speed += 0.2;
}
}
}
// Update falling enemies
if (fallingEnemies && fallingEnemies.length > 0) {
for (var i = fallingEnemies.length - 1; i >= 0; i--) {
var fe = fallingEnemies[i];
if (fe.isFalling) {
// Add gravity effect
fe.fallSpeed += 0.3;
fe.y += fe.fallSpeed;
// Add rotation
fe.rotation += fe.fallRotation;
// Fade out
fe.fallAlpha -= 0.02;
if (fe.fallAlpha <= 0) {
fe.fallAlpha = 0;
}
fe.alpha = fe.fallAlpha;
// Remove when fallen off screen or fully faded
if (fe.y > 2732 + fe.height || fe.fallAlpha <= 0) {
fe.destroy();
fallingEnemies.splice(i, 1);
}
}
}
}
// No win condition - endless game
};
// Reset game state on restart
game.on('reset', function () {
// Return bullets to pools instead of destroying
for (var i = 0; i < playerBullets.length; ++i) {
playerBullets[i].visible = false;
playerBulletPool.push(playerBullets[i]);
}
for (var i = 0; i < enemyBullets.length; ++i) {
enemyBullets[i].visible = false;
enemyBulletPool.push(enemyBullets[i]);
}
// Destroy other objects
for (var i = 0; i < enemies.length; ++i) {
enemies[i].destroy();
}
// Destroy falling enemies
if (fallingEnemies) {
for (var i = 0; i < fallingEnemies.length; ++i) {
fallingEnemies[i].destroy();
}
}
for (var i = 0; i < powerups.length; ++i) {
powerups[i].destroy();
}
playerBullets = [];
enemies = [];
enemyBullets = [];
powerups = [];
fallingEnemies = [];
level = 1;
ship.x = 2048 / 2; // Center horizontally
ship.y = 2732 / 2;
ship.powerLevel = 1;
ship.invincible = false;
ship.invincibleTimer = 0;
LK.setScore(0);
scoreTxt.setText('0');
spawnTimer = 30;
powerupTimer = 400;
gameOver = false;
});
pufferfish disney 2d image style. In-Game asset. 2d. High contrast. No shadows
clown fish disney 2d image style. In-Game asset. 2d. High contrast. No shadows
oval blue sea buble comic 2d image style. In-Game asset. 2d. High contrast. No shadows
green seaweet comic 2d image style. In-Game asset. 2d. High contrast. No shadows
veryy quite coral reef under sea disney 2d image style. In-Game asset. 2d. High contrast. No shadows