/**** * 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