/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Bullet class
var Bullet = Container.expand(function () {
var self = Container.call(this);
self.radius = 20;
self.speed = 30;
self.dir = -Math.PI / 2;
self.damage = 1;
self.fromPlayer = true;
self.bulletAsset = 'bullet_basic';
self.sprite = null;
self.init = function (asset, speed, dir, damage, fromPlayer) {
self.bulletAsset = asset;
self.speed = speed;
self.dir = dir;
self.damage = damage;
self.fromPlayer = fromPlayer;
if (self.sprite) self.removeChild(self.sprite);
// Use realistic bullet image asset if available, fallback to shape if not
var assetId = asset;
if (asset === 'bullet_basic' || asset === 'bullet_basic_real') assetId = 'bullet_basic_real';else if (asset === 'bullet_big' || asset === 'bullet_big_real') assetId = 'bullet_big_real';else if (asset === 'bullet_rapid' || asset === 'bullet_rapid_real') assetId = 'bullet_rapid_real';else if (asset === 'bullet_spread' || asset === 'bullet_spread_real') assetId = 'bullet_spread_real';
self.sprite = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = self.sprite.width / 2;
};
self.update = function () {
self.x += Math.cos(self.dir) * self.speed;
self.y += Math.sin(self.dir) * self.speed;
};
return self;
});
// Normal enemy (person-like, red shirt)
var Enemy = Container.expand(function () {
var self = Container.call(this);
// Head
var head = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 28,
height: 28,
y: -32,
color: 0xffe0b2,
shape: 'ellipse'
});
// Eyes
var leye = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 5,
height: 5,
x: -6,
y: -36,
color: 0x222222,
shape: 'ellipse'
});
var reye = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 5,
height: 5,
x: 6,
y: -36,
color: 0x222222,
shape: 'ellipse'
});
// Body
var body = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 22,
height: 38,
y: -18,
color: 0xc0392b,
shape: 'box'
});
// Left arm
var larm = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 7,
height: 24,
x: -16,
y: -12,
color: 0xffe0b2,
shape: 'box'
});
// Right arm
var rarm = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 7,
height: 24,
x: 16,
y: -12,
color: 0xffe0b2,
shape: 'box'
});
// Left leg
var lleg = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 7,
height: 22,
x: -7,
y: 20,
color: 0x222222,
shape: 'box'
});
// Right leg
var rleg = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 7,
height: 22,
x: 7,
y: 20,
color: 0x222222,
shape: 'box'
});
self.radius = 28;
self.hp = 2;
self.enemyType = "normal";
self.dir = Math.PI / 2 + (Math.random() - 0.5) * 0.5;
self.speed = 4 + Math.random() * 2;
self.update = function () {
self.x += Math.cos(self.dir) * self.speed;
self.y += Math.sin(self.dir) * self.speed;
};
return self;
});
// Fast enemy (person-like, blue shirt, smaller)
var EnemyFast = Container.expand(function () {
var self = Container.call(this);
// Head
var head = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 18,
height: 18,
y: -20,
color: 0xffe0b2,
shape: 'ellipse'
});
// Eyes
var leye = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 3,
height: 3,
x: -4,
y: -22,
color: 0x222222,
shape: 'ellipse'
});
var reye = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 3,
height: 3,
x: 4,
y: -22,
color: 0x222222,
shape: 'ellipse'
});
// Body
var body = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 13,
height: 24,
y: -10,
color: 0x2980b9,
shape: 'box'
});
// Left arm
var larm = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 4,
height: 14,
x: -10,
y: -6,
color: 0xffe0b2,
shape: 'box'
});
// Right arm
var rarm = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 4,
height: 14,
x: 10,
y: -6,
color: 0xffe0b2,
shape: 'box'
});
// Left leg
var lleg = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 4,
height: 12,
x: -4,
y: 12,
color: 0x222222,
shape: 'box'
});
// Right leg
var rleg = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 4,
height: 12,
x: 4,
y: 12,
color: 0x222222,
shape: 'box'
});
self.radius = 18;
self.hp = 1;
self.enemyType = "fast";
self.dir = Math.PI / 2 + (Math.random() - 0.5) * 0.5;
self.speed = 8 + Math.random() * 2.5;
self.update = function () {
self.x += Math.cos(self.dir) * self.speed;
self.y += Math.sin(self.dir) * self.speed;
};
return self;
});
// Tank enemy (person-like, green shirt, big)
var EnemyTank = Container.expand(function () {
var self = Container.call(this);
// Head
var head = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 38,
height: 38,
y: -48,
color: 0xffe0b2,
shape: 'ellipse'
});
// Eyes
var leye = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 7,
height: 7,
x: -10,
y: -54,
color: 0x222222,
shape: 'ellipse'
});
var reye = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 7,
height: 7,
x: 10,
y: -54,
color: 0x222222,
shape: 'ellipse'
});
// Body
var body = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 32,
height: 60,
y: -20,
color: 0x27ae60,
shape: 'box'
});
// Left arm
var larm = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 12,
height: 36,
x: -26,
y: -10,
color: 0xffe0b2,
shape: 'box'
});
// Right arm
var rarm = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 12,
height: 36,
x: 26,
y: -10,
color: 0xffe0b2,
shape: 'box'
});
// Left leg
var lleg = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 12,
height: 28,
x: -10,
y: 44,
color: 0x222222,
shape: 'box'
});
// Right leg
var rleg = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 12,
height: 28,
x: 10,
y: 44,
color: 0x222222,
shape: 'box'
});
self.radius = 38;
self.hp = 6;
self.enemyType = "tank";
self.dir = Math.PI / 2 + (Math.random() - 0.5) * 0.5;
self.speed = 2 + Math.random() * 1.2;
self.update = function () {
self.x += Math.cos(self.dir) * self.speed;
self.y += Math.sin(self.dir) * self.speed;
};
return self;
});
// GunDrop class
var GunDrop = Container.expand(function () {
var self = Container.call(this);
self.gun = null;
var dropSprite = self.attachAsset('gun_drop', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = dropSprite.width / 2;
self.setGun = function (gun) {
self.gun = gun;
dropSprite.tint = gun.color;
};
self.update = function () {
self.y += 8;
};
return self;
});
// Player class
var Player = Container.expand(function () {
var self = Container.call(this);
// Head
var head = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
width: 54,
height: 54,
y: -44,
color: 0xffe0b2 // skin color
});
// Face (simple: eyes and mouth)
var leye = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
width: 8,
height: 8,
x: -12,
y: -44 - 6,
color: 0x222222,
shape: 'ellipse'
});
var reye = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
width: 8,
height: 8,
x: 12,
y: -44 - 6,
color: 0x222222,
shape: 'ellipse'
});
var mouth = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
width: 18,
height: 6,
x: 0,
y: -44 + 12,
color: 0x8d5524,
shape: 'ellipse'
});
// Body
var body = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.0,
width: 28,
height: 64,
y: -17,
color: 0x2980b9,
shape: 'box'
});
// Left arm
var larm = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.0,
width: 12,
height: 40,
x: -24,
y: -8,
color: 0x27ae60,
shape: 'box'
});
// Right arm
var rarm = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.0,
width: 12,
height: 40,
x: 24,
y: -8,
color: 0x27ae60,
shape: 'box'
});
// Gun sprite (player) - now using a realistic gun image
var gunSprite = self.attachAsset('gun_real_player_img', {
anchorX: 0.2,
anchorY: 0.5,
width: 64,
height: 32,
x: 36,
y: 12
});
rarm.addChild(gunSprite);
// Left leg
var lleg = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.0,
width: 12,
height: 40,
x: -10,
y: 48,
color: 0xf1c40f,
shape: 'box'
});
// Right leg
var rleg = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.0,
width: 12,
height: 40,
x: 10,
y: 48,
color: 0xf1c40f,
shape: 'box'
});
self.radius = 54; // head radius
self.invulnTicks = 0;
self.flashTween = null;
// Animation state for realism
self.animTick = Math.random() * 1000;
self.lastX = 0;
self.lastY = 0;
self.update = function () {
// Animate limbs for realism based on movement
var dx = self.x - (self.lastX || self.x);
var dy = self.y - (self.lastY || self.y);
var speed = Math.sqrt(dx * dx + dy * dy);
self.animTick += speed * 0.7 + 1;
var phase = self.animTick * 0.09;
var walkSwing = Math.sin(phase) * 0.7;
var walkSwingLeg = Math.sin(phase + Math.PI / 2) * 0.7;
// Arms swing
larm.rotation = walkSwing * 0.5;
rarm.rotation = -walkSwing * 0.5;
// Legs swing
lleg.rotation = -walkSwingLeg * 0.5;
rleg.rotation = walkSwingLeg * 0.5;
// Head bob
head.y = -44 + Math.sin(phase) * 2;
// Store last position for next frame
self.lastX = self.x;
self.lastY = self.y;
};
self.setInvulnerable = function (ticks) {
self.invulnTicks = ticks;
if (self.flashTween) tween.stop(head, {
alpha: true
});
head.alpha = 0.5;
self.flashTween = tween(head, {
alpha: 1
}, {
duration: 200,
easing: tween.linear,
onFinish: function onFinish() {
tween(head, {
alpha: 0.5
}, {
duration: 200,
easing: tween.linear,
onFinish: function onFinish() {
if (self.invulnTicks > 0) self.setInvulnerable(self.invulnTicks - 1);else head.alpha = 1;
}
});
}
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181c20
});
/****
* Game Code
****/
// Realistic bullet image assets
// --- POLISH: Animated background ---
var bgPolish = LK.getAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 2048,
height: 2732,
x: 2048 / 2,
y: 2732 / 2,
color: 0x232a34,
alpha: 0.13
});
game.addChildAt(bgPolish, 0);
var bgPolish2 = LK.getAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 2048,
height: 2732,
x: 2048 / 2,
y: 2732 / 2,
color: 0x3a506b,
alpha: 0.09
});
game.addChildAt(bgPolish2, 1);
var bgPolishTick = 0;
game.update = function (origUpdate) {
return function () {
bgPolishTick++;
bgPolish.rotation = Math.sin(bgPolishTick / 300) * 0.04;
bgPolish2.rotation = -Math.sin(bgPolishTick / 400) * 0.03;
if (origUpdate) origUpdate.apply(this, arguments);
};
}(game.update);
// --- END POLISH ---
// Game state
// Guns (shapes for now, will be replaced by images automatically)
// Sounds
// Gun definitions
var GUNS = [{
id: 'basic',
name: 'Basic Gun',
bulletAsset: 'bullet_basic_real',
fireRate: 18,
// ticks between shots
bulletSpeed: 32,
spread: 0,
bulletsPerShot: 1,
damage: 1,
color: 0xf1c40f
}, {
id: 'spread',
name: 'Spread Shot',
bulletAsset: 'bullet_spread_real',
fireRate: 28,
bulletSpeed: 26,
spread: 0.25,
bulletsPerShot: 3,
damage: 1,
color: 0x8e44ad
}, {
id: 'rapid',
name: 'Rapid Fire',
bulletAsset: 'bullet_rapid_real',
fireRate: 7,
bulletSpeed: 24,
spread: 0.08,
bulletsPerShot: 1,
damage: 1,
color: 0x16a085
}, {
id: 'big',
name: 'Big Blaster',
bulletAsset: 'bullet_big_real',
fireRate: 30,
bulletSpeed: 20,
spread: 0,
bulletsPerShot: 1,
damage: 3,
color: 0xff9800
}];
// Helper: get random gun (not in excludeIds)
function getRandomGun(excludeIds) {
var pool = [];
for (var i = 0; i < GUNS.length; ++i) {
if (!excludeIds || excludeIds.indexOf(GUNS[i].id) === -1) pool.push(GUNS[i]);
if (enemyGunFireTick > 0) {
enemyGunFireTick--;
}
}
;
if (pool.length === 0) pool = GUNS;
return pool[Math.floor(Math.random() * pool.length)];
}
var player = null;
var enemies = [];
var bullets = [];
var gunDrops = [];
var score = 0;
var scoreTxt = null;
var gunTxt = null;
var wave = 1;
var ticksToNextWave = 0;
var playerGuns = [];
var currentGunIndex = 0;
var fireCooldown = 0;
var dragging = false;
var dragOffsetX = 0;
var dragOffsetY = 0;
var lastTouchX = 0;
var lastTouchY = 0;
var enemySpeedMultiplier = 1; // NEW: multiplier for enemy speed
var enemySpeedupTicks = 0; // NEW: ticks counter for speedup
var gameArea = {
x: 0,
y: 0,
w: 2048,
h: 2732 - 200
}; // leave some space at bottom for gun UI
// Setup UI
scoreTxt = new Text2('0', {
size: 120,
fill: '#fff'
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// (Wave UI removed)
gunTxt = new Text2('', {
size: 70,
fill: '#fff'
});
gunTxt.anchor.set(0.5, 0);
LK.gui.bottom.addChild(gunTxt);
// Inventory UI
var inventoryUI = [];
function updateInventoryUI() {
// Remove old UI
for (var i = 0; i < inventoryUI.length; ++i) {
if (inventoryUI[i].parent) inventoryUI[i].parent.removeChild(inventoryUI[i]);
}
inventoryUI = [];
// Show all guns in inventory at the bottom, horizontally
var spacing = 180;
var startX = (2048 - (playerGuns.length - 1) * spacing) / 2;
var y = 2732 - 120;
for (var i = 0; i < playerGuns.length; ++i) {
var gun = playerGuns[i];
var assetId = gun.bulletAsset;
// Use gun image for inventory, fallback to bullet asset if not available
var gunAsset = null;
if (gun.id === 'basic') gunAsset = LK.getAsset('gun_real_player_img', {
anchorX: 0.5,
anchorY: 0.5,
width: 90,
height: 90
});else if (gun.id === 'spread') gunAsset = LK.getAsset('gun_real_player_img', {
anchorX: 0.5,
anchorY: 0.5,
width: 90,
height: 90
});else if (gun.id === 'rapid') gunAsset = LK.getAsset('gun_real_player_img', {
anchorX: 0.5,
anchorY: 0.5,
width: 90,
height: 90
});else if (gun.id === 'big') gunAsset = LK.getAsset('gun_real_player_img', {
anchorX: 0.5,
anchorY: 0.5,
width: 90,
height: 90
});else gunAsset = LK.getAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5,
width: 90,
height: 90
});
gunAsset.x = startX + i * spacing;
gunAsset.y = y;
// Highlight current gun
gunAsset.alpha = i === currentGunIndex ? 1 : 0.5;
LK.gui.bottom.addChild(gunAsset);
inventoryUI.push(gunAsset);
// POLISH: pop effect for selected gun
if (i === currentGunIndex) {
gunAsset.scaleX = 1.25;
gunAsset.scaleY = 1.25;
tween(gunAsset, {
scaleX: 1,
scaleY: 1
}, {
duration: 180,
easing: tween.easeOutBack
});
}
}
}
// Initialize player
player = new Player();
player.x = 2048 / 2;
player.y = 2732 - 350;
game.addChild(player);
// POLISH: player spawn flash
LK.effects.flashObject(player, 0xffffff, 300);
// Give player a starting gun
playerGuns = [GUNS[0]];
currentGunIndex = 0;
updateGunUI();
updateInventoryUI();
// Spawn first wave
spawnWave(wave);
// Touch controls
game.down = function (x, y, obj) {
// Only start drag if touch is on player
var dx = x - player.x;
var dy = y - player.y;
if (dx * dx + dy * dy < player.radius * player.radius * 1.5) {
dragging = true;
dragOffsetX = player.x - x;
dragOffsetY = player.y - y;
lastTouchX = x;
lastTouchY = y;
}
};
game.move = function (x, y, obj) {
if (dragging) {
// Move player directly to the touch/mouse position, clamped to game area
var nx = x;
var ny = y;
if (nx < gameArea.x + player.radius) nx = gameArea.x + player.radius;
if (nx > gameArea.x + gameArea.w - player.radius) nx = gameArea.x + gameArea.w - player.radius;
if (ny < gameArea.y + player.radius) ny = gameArea.y + player.radius;
if (ny > gameArea.y + gameArea.h - player.radius) ny = gameArea.y + gameArea.h - player.radius;
player.x = nx;
player.y = ny;
lastTouchX = x;
lastTouchY = y;
}
};
game.up = function (x, y, obj) {
dragging = false;
};
// Tap bottom of screen to switch gun
game.on('down', function (x, y, obj) {
if (y > 2732 - 200) {
// Switch gun
if (playerGuns.length > 1) {
currentGunIndex = (currentGunIndex + 1) % playerGuns.length;
updateGunUI();
updateInventoryUI();
LK.getSound('gunchange').play();
// Clamp player to game area in case new gun is larger
if (player.x < gameArea.x + player.radius) player.x = gameArea.x + player.radius;
if (player.x > gameArea.x + gameArea.w - player.radius) player.x = gameArea.x + gameArea.w - player.radius;
if (player.y < gameArea.y + player.radius) player.y = gameArea.y + player.radius;
if (player.y > gameArea.y + gameArea.h - player.radius) player.y = gameArea.y + gameArea.h - player.radius;
}
}
});
// Enemy gun fire rate control
var enemyGunFireRate = 80; // Higher = slower, default 80 ticks between shots (was much faster before)
var enemyGunFireTick = 0;
// NEW: variables to control enemy gun speedup
var enemyGunFireRateMin = 20; // Minimum allowed fire rate (fastest)
var enemyGunFireRateSpeedupTicks = 0; // Ticks counter for gun speedup
// Main update loop
game.update = function () {
// Player invulnerability
if (player.invulnTicks > 0) player.invulnTicks--;
// Firing
fireCooldown--;
if (dragging && fireCooldown <= 0) {
var gun = playerGuns[currentGunIndex];
fireGun(gun);
fireCooldown = gun.fireRate;
}
// Update bullets
for (var i = bullets.length - 1; i >= 0; --i) {
var b = bullets[i];
b.update();
// Remove if out of bounds
if (b.x < -100 || b.x > 2148 || b.y < -100 || b.y > 2832) {
b.destroy();
bullets.splice(i, 1);
continue;
}
// Collisions
if (b.fromPlayer) {
// Hit enemy
for (var j = enemies.length - 1; j >= 0; --j) {
var e = enemies[j];
var dx = b.x - e.x;
var dy = b.y - e.y;
var dist = dx * dx + dy * dy;
if (dist < (b.radius + e.radius) * (b.radius + e.radius)) {
// Bullet hits enemy: deal damage
e.hp -= b.damage;
LK.getSound('enemyhit').play();
b.destroy();
bullets.splice(i, 1);
if (e.hp <= 0) {
// Enemy dies
spawnGunDrop(e.x, e.y);
// Kill animation: flash enemy red and scale up, then destroy
tween(e, {
scaleX: 1.7,
scaleY: 1.7,
alpha: 0
}, {
duration: 320,
easing: tween.easeOutCubic,
onFinish: function onFinish() {
e.destroy();
}
});
enemies.splice(j, 1);
score += 10;
scoreTxt.setText(score);
// POLISH: score pop
scoreTxt.scaleX = 1.3;
scoreTxt.scaleY = 1.3;
tween(scoreTxt, {
scaleX: 1,
scaleY: 1
}, {
duration: 180,
easing: tween.easeOutBack
});
// Win condition: player wins at 500 points
if (score >= 500) {
LK.effects.flashScreen(0x00ff00, 1000);
LK.showYouWin();
return;
}
}
break;
}
}
} else {
// Enemy bullet hits player
var dx = b.x - player.x;
var dy = b.y - player.y;
var dist = dx * dx + dy * dy;
if (dist < (b.radius + player.radius) * (b.radius + player.radius)) {
if (player.invulnTicks <= 0) {
LK.effects.flashScreen(0xff0000, 600);
player.setInvulnerable(40);
b.destroy();
bullets.splice(i, 1);
// Game over
LK.showGameOver();
return;
}
}
}
}
// Update enemies
// Slowly increase enemy speed multiplier every 120 ticks (~2 seconds)
enemySpeedupTicks++;
if (enemySpeedupTicks >= 120) {
enemySpeedupTicks = 0;
enemySpeedMultiplier += 0.03; // Increase speed multiplier slowly
if (enemySpeedMultiplier > 3) enemySpeedMultiplier = 3; // Cap to avoid going too fast
}
// NEW: Slowly speed up enemy gun by decreasing fire rate every 180 ticks (~3 seconds)
enemyGunFireRateSpeedupTicks++;
if (enemyGunFireRateSpeedupTicks >= 180) {
enemyGunFireRateSpeedupTicks = 0;
if (enemyGunFireRate > enemyGunFireRateMin) {
enemyGunFireRate -= 2; // Decrease fire rate (speed up gun)
if (enemyGunFireRate < enemyGunFireRateMin) enemyGunFireRate = enemyGunFireRateMin;
}
}
for (var i = enemies.length - 1; i >= 0; --i) {
var e = enemies[i];
// Apply speed multiplier for this frame
var origSpeed = e.speed;
e.speed = origSpeed * enemySpeedMultiplier;
e.update();
e.speed = origSpeed; // Restore original speed for next frame
// Remove if out of bounds
if (e.x < -120 || e.x > 2168 || e.y > 2832) {
e.destroy();
enemies.splice(i, 1);
continue;
}
// Enemy collides with player
var dx = e.x - player.x;
var dy = e.y - player.y;
var dist = dx * dx + dy * dy;
if (dist < (e.radius + player.radius) * (e.radius + player.radius)) {
if (player.invulnTicks <= 0) {
LK.effects.flashScreen(0xff0000, 600);
player.setInvulnerable(40);
// Game over
LK.showGameOver();
return;
}
}
// Enemy fires at player (throttled by enemyGunFireRate)
if (enemyGunFireTick <= 0) {
if (Math.random() < 0.008 + wave * 0.001) {
var dir = Math.atan2(player.y - e.y, player.x - e.x);
var eb = new Bullet();
eb.init('bullet_basic', 18 + Math.random() * 6, dir, 1, false);
eb.x = e.x;
eb.y = e.y;
bullets.push(eb);
game.addChild(eb);
enemyGunFireTick = enemyGunFireRate;
}
}
}
// Update gun drops
for (var i = gunDrops.length - 1; i >= 0; --i) {
var g = gunDrops[i];
g.update();
// Remove if out of bounds
if (g.y > 2832) {
g.destroy();
gunDrops.splice(i, 1);
continue;
}
// Pickup
var dx = g.x - player.x;
var dy = g.y - player.y;
var dist = dx * dx + dy * dy;
if (dist < (g.radius + player.radius) * (g.radius + player.radius)) {
// Add gun if not already owned
if (!hasGun(g.gun.id)) {
playerGuns.push(g.gun);
currentGunIndex = playerGuns.length - 1;
updateGunUI();
updateInventoryUI();
LK.getSound('pickup').play();
// Visual feedback for pickup
LK.effects.flashObject(player, 0x00ff00, 400);
}
g.destroy();
gunDrops.splice(i, 1);
}
}
// Next wave
if (enemies.length === 0 && gunDrops.length === 0 && ticksToNextWave <= 0) {
ticksToNextWave = 60;
}
if (ticksToNextWave > 0) {
ticksToNextWave--;
if (ticksToNextWave === 0) {
wave++;
spawnWave(wave);
// (Wave UI update/animation removed)
}
}
};
// Helper: fire gun
function fireGun(gun) {
for (var i = 0; i < gun.bulletsPerShot; ++i) {
var b = new Bullet();
var spread = gun.spread * (Math.random() - 0.5);
var dir = -Math.PI / 2 + spread + (gun.bulletsPerShot > 1 ? (i - (gun.bulletsPerShot - 1) / 2) * gun.spread : 0);
b.init(gun.bulletAsset, gun.bulletSpeed, dir, gun.damage, true);
b.x = player.x;
b.y = player.y - player.radius;
bullets.push(b);
game.addChild(b);
}
LK.getSound('shoot').play();
}
// Helper: spawn wave
function spawnWave(w) {
var n = 3 + Math.floor(w * 1.5);
for (var i = 0; i < n; ++i) {
// Choose enemy type: more tanks and fast enemies as wave increases
var typeRand = Math.random();
var e;
if (w > 2 && typeRand < Math.min(0.18 + w * 0.01, 0.33)) {
// Tank enemy
e = new EnemyTank();
e.hp = 6 + Math.floor(w / 2);
e.speed = 2 + Math.random() * 0.8 + w * 0.04;
} else if (w > 1 && typeRand < Math.min(0.45 + w * 0.01, 0.6)) {
// Fast enemy
e = new EnemyFast();
e.hp = 1 + Math.floor(w / 4);
e.speed = 8 + Math.random() * 2.5 + w * 0.12;
} else {
// Normal enemy
e = new Enemy();
e.hp = 2 + Math.floor(w / 2);
e.speed = 2 + Math.random() * 1.5 + w * 0.08;
}
e.x = 200 + Math.random() * (2048 - 400);
e.y = -100 - Math.random() * 200;
// Add enemy to the game scene so it is visible
game.addChild(e);
enemies.push(e);
// POLISH: spawn flash
LK.effects.flashObject(e, 0xffffff, 200);
}
}
// Helper: spawn gun drop
function spawnGunDrop(x, y) {
// 50% chance to drop a gun, only if player doesn't have all guns
if (playerGuns.length >= GUNS.length) return;
if (Math.random() < 0.5) {
var exclude = [];
for (var i = 0; i < playerGuns.length; ++i) exclude.push(playerGuns[i].id);
var gun = getRandomGun(exclude);
var g = new GunDrop();
g.x = x;
g.y = y;
g.setGun(gun);
gunDrops.push(g);
game.addChild(g);
}
}
// Helper: update gun UI
function updateGunUI() {
var gun = playerGuns[currentGunIndex];
gunTxt.setText("Gun: " + gun.name + " (Tap here to switch)");
gunTxt.setStyle({
fill: "#" + gun.color.toString(16)
});
updateInventoryUI();
}
// Helper: has gun
function hasGun(id) {
for (var i = 0; i < playerGuns.length; ++i) if (playerGuns[i].id === id) return true;
return false;
} /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Bullet class
var Bullet = Container.expand(function () {
var self = Container.call(this);
self.radius = 20;
self.speed = 30;
self.dir = -Math.PI / 2;
self.damage = 1;
self.fromPlayer = true;
self.bulletAsset = 'bullet_basic';
self.sprite = null;
self.init = function (asset, speed, dir, damage, fromPlayer) {
self.bulletAsset = asset;
self.speed = speed;
self.dir = dir;
self.damage = damage;
self.fromPlayer = fromPlayer;
if (self.sprite) self.removeChild(self.sprite);
// Use realistic bullet image asset if available, fallback to shape if not
var assetId = asset;
if (asset === 'bullet_basic' || asset === 'bullet_basic_real') assetId = 'bullet_basic_real';else if (asset === 'bullet_big' || asset === 'bullet_big_real') assetId = 'bullet_big_real';else if (asset === 'bullet_rapid' || asset === 'bullet_rapid_real') assetId = 'bullet_rapid_real';else if (asset === 'bullet_spread' || asset === 'bullet_spread_real') assetId = 'bullet_spread_real';
self.sprite = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = self.sprite.width / 2;
};
self.update = function () {
self.x += Math.cos(self.dir) * self.speed;
self.y += Math.sin(self.dir) * self.speed;
};
return self;
});
// Normal enemy (person-like, red shirt)
var Enemy = Container.expand(function () {
var self = Container.call(this);
// Head
var head = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 28,
height: 28,
y: -32,
color: 0xffe0b2,
shape: 'ellipse'
});
// Eyes
var leye = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 5,
height: 5,
x: -6,
y: -36,
color: 0x222222,
shape: 'ellipse'
});
var reye = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 5,
height: 5,
x: 6,
y: -36,
color: 0x222222,
shape: 'ellipse'
});
// Body
var body = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 22,
height: 38,
y: -18,
color: 0xc0392b,
shape: 'box'
});
// Left arm
var larm = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 7,
height: 24,
x: -16,
y: -12,
color: 0xffe0b2,
shape: 'box'
});
// Right arm
var rarm = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 7,
height: 24,
x: 16,
y: -12,
color: 0xffe0b2,
shape: 'box'
});
// Left leg
var lleg = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 7,
height: 22,
x: -7,
y: 20,
color: 0x222222,
shape: 'box'
});
// Right leg
var rleg = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 7,
height: 22,
x: 7,
y: 20,
color: 0x222222,
shape: 'box'
});
self.radius = 28;
self.hp = 2;
self.enemyType = "normal";
self.dir = Math.PI / 2 + (Math.random() - 0.5) * 0.5;
self.speed = 4 + Math.random() * 2;
self.update = function () {
self.x += Math.cos(self.dir) * self.speed;
self.y += Math.sin(self.dir) * self.speed;
};
return self;
});
// Fast enemy (person-like, blue shirt, smaller)
var EnemyFast = Container.expand(function () {
var self = Container.call(this);
// Head
var head = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 18,
height: 18,
y: -20,
color: 0xffe0b2,
shape: 'ellipse'
});
// Eyes
var leye = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 3,
height: 3,
x: -4,
y: -22,
color: 0x222222,
shape: 'ellipse'
});
var reye = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 3,
height: 3,
x: 4,
y: -22,
color: 0x222222,
shape: 'ellipse'
});
// Body
var body = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 13,
height: 24,
y: -10,
color: 0x2980b9,
shape: 'box'
});
// Left arm
var larm = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 4,
height: 14,
x: -10,
y: -6,
color: 0xffe0b2,
shape: 'box'
});
// Right arm
var rarm = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 4,
height: 14,
x: 10,
y: -6,
color: 0xffe0b2,
shape: 'box'
});
// Left leg
var lleg = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 4,
height: 12,
x: -4,
y: 12,
color: 0x222222,
shape: 'box'
});
// Right leg
var rleg = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 4,
height: 12,
x: 4,
y: 12,
color: 0x222222,
shape: 'box'
});
self.radius = 18;
self.hp = 1;
self.enemyType = "fast";
self.dir = Math.PI / 2 + (Math.random() - 0.5) * 0.5;
self.speed = 8 + Math.random() * 2.5;
self.update = function () {
self.x += Math.cos(self.dir) * self.speed;
self.y += Math.sin(self.dir) * self.speed;
};
return self;
});
// Tank enemy (person-like, green shirt, big)
var EnemyTank = Container.expand(function () {
var self = Container.call(this);
// Head
var head = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 38,
height: 38,
y: -48,
color: 0xffe0b2,
shape: 'ellipse'
});
// Eyes
var leye = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 7,
height: 7,
x: -10,
y: -54,
color: 0x222222,
shape: 'ellipse'
});
var reye = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 7,
height: 7,
x: 10,
y: -54,
color: 0x222222,
shape: 'ellipse'
});
// Body
var body = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 32,
height: 60,
y: -20,
color: 0x27ae60,
shape: 'box'
});
// Left arm
var larm = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 12,
height: 36,
x: -26,
y: -10,
color: 0xffe0b2,
shape: 'box'
});
// Right arm
var rarm = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 12,
height: 36,
x: 26,
y: -10,
color: 0xffe0b2,
shape: 'box'
});
// Left leg
var lleg = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 12,
height: 28,
x: -10,
y: 44,
color: 0x222222,
shape: 'box'
});
// Right leg
var rleg = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.0,
width: 12,
height: 28,
x: 10,
y: 44,
color: 0x222222,
shape: 'box'
});
self.radius = 38;
self.hp = 6;
self.enemyType = "tank";
self.dir = Math.PI / 2 + (Math.random() - 0.5) * 0.5;
self.speed = 2 + Math.random() * 1.2;
self.update = function () {
self.x += Math.cos(self.dir) * self.speed;
self.y += Math.sin(self.dir) * self.speed;
};
return self;
});
// GunDrop class
var GunDrop = Container.expand(function () {
var self = Container.call(this);
self.gun = null;
var dropSprite = self.attachAsset('gun_drop', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = dropSprite.width / 2;
self.setGun = function (gun) {
self.gun = gun;
dropSprite.tint = gun.color;
};
self.update = function () {
self.y += 8;
};
return self;
});
// Player class
var Player = Container.expand(function () {
var self = Container.call(this);
// Head
var head = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
width: 54,
height: 54,
y: -44,
color: 0xffe0b2 // skin color
});
// Face (simple: eyes and mouth)
var leye = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
width: 8,
height: 8,
x: -12,
y: -44 - 6,
color: 0x222222,
shape: 'ellipse'
});
var reye = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
width: 8,
height: 8,
x: 12,
y: -44 - 6,
color: 0x222222,
shape: 'ellipse'
});
var mouth = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
width: 18,
height: 6,
x: 0,
y: -44 + 12,
color: 0x8d5524,
shape: 'ellipse'
});
// Body
var body = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.0,
width: 28,
height: 64,
y: -17,
color: 0x2980b9,
shape: 'box'
});
// Left arm
var larm = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.0,
width: 12,
height: 40,
x: -24,
y: -8,
color: 0x27ae60,
shape: 'box'
});
// Right arm
var rarm = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.0,
width: 12,
height: 40,
x: 24,
y: -8,
color: 0x27ae60,
shape: 'box'
});
// Gun sprite (player) - now using a realistic gun image
var gunSprite = self.attachAsset('gun_real_player_img', {
anchorX: 0.2,
anchorY: 0.5,
width: 64,
height: 32,
x: 36,
y: 12
});
rarm.addChild(gunSprite);
// Left leg
var lleg = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.0,
width: 12,
height: 40,
x: -10,
y: 48,
color: 0xf1c40f,
shape: 'box'
});
// Right leg
var rleg = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.0,
width: 12,
height: 40,
x: 10,
y: 48,
color: 0xf1c40f,
shape: 'box'
});
self.radius = 54; // head radius
self.invulnTicks = 0;
self.flashTween = null;
// Animation state for realism
self.animTick = Math.random() * 1000;
self.lastX = 0;
self.lastY = 0;
self.update = function () {
// Animate limbs for realism based on movement
var dx = self.x - (self.lastX || self.x);
var dy = self.y - (self.lastY || self.y);
var speed = Math.sqrt(dx * dx + dy * dy);
self.animTick += speed * 0.7 + 1;
var phase = self.animTick * 0.09;
var walkSwing = Math.sin(phase) * 0.7;
var walkSwingLeg = Math.sin(phase + Math.PI / 2) * 0.7;
// Arms swing
larm.rotation = walkSwing * 0.5;
rarm.rotation = -walkSwing * 0.5;
// Legs swing
lleg.rotation = -walkSwingLeg * 0.5;
rleg.rotation = walkSwingLeg * 0.5;
// Head bob
head.y = -44 + Math.sin(phase) * 2;
// Store last position for next frame
self.lastX = self.x;
self.lastY = self.y;
};
self.setInvulnerable = function (ticks) {
self.invulnTicks = ticks;
if (self.flashTween) tween.stop(head, {
alpha: true
});
head.alpha = 0.5;
self.flashTween = tween(head, {
alpha: 1
}, {
duration: 200,
easing: tween.linear,
onFinish: function onFinish() {
tween(head, {
alpha: 0.5
}, {
duration: 200,
easing: tween.linear,
onFinish: function onFinish() {
if (self.invulnTicks > 0) self.setInvulnerable(self.invulnTicks - 1);else head.alpha = 1;
}
});
}
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181c20
});
/****
* Game Code
****/
// Realistic bullet image assets
// --- POLISH: Animated background ---
var bgPolish = LK.getAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 2048,
height: 2732,
x: 2048 / 2,
y: 2732 / 2,
color: 0x232a34,
alpha: 0.13
});
game.addChildAt(bgPolish, 0);
var bgPolish2 = LK.getAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 2048,
height: 2732,
x: 2048 / 2,
y: 2732 / 2,
color: 0x3a506b,
alpha: 0.09
});
game.addChildAt(bgPolish2, 1);
var bgPolishTick = 0;
game.update = function (origUpdate) {
return function () {
bgPolishTick++;
bgPolish.rotation = Math.sin(bgPolishTick / 300) * 0.04;
bgPolish2.rotation = -Math.sin(bgPolishTick / 400) * 0.03;
if (origUpdate) origUpdate.apply(this, arguments);
};
}(game.update);
// --- END POLISH ---
// Game state
// Guns (shapes for now, will be replaced by images automatically)
// Sounds
// Gun definitions
var GUNS = [{
id: 'basic',
name: 'Basic Gun',
bulletAsset: 'bullet_basic_real',
fireRate: 18,
// ticks between shots
bulletSpeed: 32,
spread: 0,
bulletsPerShot: 1,
damage: 1,
color: 0xf1c40f
}, {
id: 'spread',
name: 'Spread Shot',
bulletAsset: 'bullet_spread_real',
fireRate: 28,
bulletSpeed: 26,
spread: 0.25,
bulletsPerShot: 3,
damage: 1,
color: 0x8e44ad
}, {
id: 'rapid',
name: 'Rapid Fire',
bulletAsset: 'bullet_rapid_real',
fireRate: 7,
bulletSpeed: 24,
spread: 0.08,
bulletsPerShot: 1,
damage: 1,
color: 0x16a085
}, {
id: 'big',
name: 'Big Blaster',
bulletAsset: 'bullet_big_real',
fireRate: 30,
bulletSpeed: 20,
spread: 0,
bulletsPerShot: 1,
damage: 3,
color: 0xff9800
}];
// Helper: get random gun (not in excludeIds)
function getRandomGun(excludeIds) {
var pool = [];
for (var i = 0; i < GUNS.length; ++i) {
if (!excludeIds || excludeIds.indexOf(GUNS[i].id) === -1) pool.push(GUNS[i]);
if (enemyGunFireTick > 0) {
enemyGunFireTick--;
}
}
;
if (pool.length === 0) pool = GUNS;
return pool[Math.floor(Math.random() * pool.length)];
}
var player = null;
var enemies = [];
var bullets = [];
var gunDrops = [];
var score = 0;
var scoreTxt = null;
var gunTxt = null;
var wave = 1;
var ticksToNextWave = 0;
var playerGuns = [];
var currentGunIndex = 0;
var fireCooldown = 0;
var dragging = false;
var dragOffsetX = 0;
var dragOffsetY = 0;
var lastTouchX = 0;
var lastTouchY = 0;
var enemySpeedMultiplier = 1; // NEW: multiplier for enemy speed
var enemySpeedupTicks = 0; // NEW: ticks counter for speedup
var gameArea = {
x: 0,
y: 0,
w: 2048,
h: 2732 - 200
}; // leave some space at bottom for gun UI
// Setup UI
scoreTxt = new Text2('0', {
size: 120,
fill: '#fff'
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// (Wave UI removed)
gunTxt = new Text2('', {
size: 70,
fill: '#fff'
});
gunTxt.anchor.set(0.5, 0);
LK.gui.bottom.addChild(gunTxt);
// Inventory UI
var inventoryUI = [];
function updateInventoryUI() {
// Remove old UI
for (var i = 0; i < inventoryUI.length; ++i) {
if (inventoryUI[i].parent) inventoryUI[i].parent.removeChild(inventoryUI[i]);
}
inventoryUI = [];
// Show all guns in inventory at the bottom, horizontally
var spacing = 180;
var startX = (2048 - (playerGuns.length - 1) * spacing) / 2;
var y = 2732 - 120;
for (var i = 0; i < playerGuns.length; ++i) {
var gun = playerGuns[i];
var assetId = gun.bulletAsset;
// Use gun image for inventory, fallback to bullet asset if not available
var gunAsset = null;
if (gun.id === 'basic') gunAsset = LK.getAsset('gun_real_player_img', {
anchorX: 0.5,
anchorY: 0.5,
width: 90,
height: 90
});else if (gun.id === 'spread') gunAsset = LK.getAsset('gun_real_player_img', {
anchorX: 0.5,
anchorY: 0.5,
width: 90,
height: 90
});else if (gun.id === 'rapid') gunAsset = LK.getAsset('gun_real_player_img', {
anchorX: 0.5,
anchorY: 0.5,
width: 90,
height: 90
});else if (gun.id === 'big') gunAsset = LK.getAsset('gun_real_player_img', {
anchorX: 0.5,
anchorY: 0.5,
width: 90,
height: 90
});else gunAsset = LK.getAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5,
width: 90,
height: 90
});
gunAsset.x = startX + i * spacing;
gunAsset.y = y;
// Highlight current gun
gunAsset.alpha = i === currentGunIndex ? 1 : 0.5;
LK.gui.bottom.addChild(gunAsset);
inventoryUI.push(gunAsset);
// POLISH: pop effect for selected gun
if (i === currentGunIndex) {
gunAsset.scaleX = 1.25;
gunAsset.scaleY = 1.25;
tween(gunAsset, {
scaleX: 1,
scaleY: 1
}, {
duration: 180,
easing: tween.easeOutBack
});
}
}
}
// Initialize player
player = new Player();
player.x = 2048 / 2;
player.y = 2732 - 350;
game.addChild(player);
// POLISH: player spawn flash
LK.effects.flashObject(player, 0xffffff, 300);
// Give player a starting gun
playerGuns = [GUNS[0]];
currentGunIndex = 0;
updateGunUI();
updateInventoryUI();
// Spawn first wave
spawnWave(wave);
// Touch controls
game.down = function (x, y, obj) {
// Only start drag if touch is on player
var dx = x - player.x;
var dy = y - player.y;
if (dx * dx + dy * dy < player.radius * player.radius * 1.5) {
dragging = true;
dragOffsetX = player.x - x;
dragOffsetY = player.y - y;
lastTouchX = x;
lastTouchY = y;
}
};
game.move = function (x, y, obj) {
if (dragging) {
// Move player directly to the touch/mouse position, clamped to game area
var nx = x;
var ny = y;
if (nx < gameArea.x + player.radius) nx = gameArea.x + player.radius;
if (nx > gameArea.x + gameArea.w - player.radius) nx = gameArea.x + gameArea.w - player.radius;
if (ny < gameArea.y + player.radius) ny = gameArea.y + player.radius;
if (ny > gameArea.y + gameArea.h - player.radius) ny = gameArea.y + gameArea.h - player.radius;
player.x = nx;
player.y = ny;
lastTouchX = x;
lastTouchY = y;
}
};
game.up = function (x, y, obj) {
dragging = false;
};
// Tap bottom of screen to switch gun
game.on('down', function (x, y, obj) {
if (y > 2732 - 200) {
// Switch gun
if (playerGuns.length > 1) {
currentGunIndex = (currentGunIndex + 1) % playerGuns.length;
updateGunUI();
updateInventoryUI();
LK.getSound('gunchange').play();
// Clamp player to game area in case new gun is larger
if (player.x < gameArea.x + player.radius) player.x = gameArea.x + player.radius;
if (player.x > gameArea.x + gameArea.w - player.radius) player.x = gameArea.x + gameArea.w - player.radius;
if (player.y < gameArea.y + player.radius) player.y = gameArea.y + player.radius;
if (player.y > gameArea.y + gameArea.h - player.radius) player.y = gameArea.y + gameArea.h - player.radius;
}
}
});
// Enemy gun fire rate control
var enemyGunFireRate = 80; // Higher = slower, default 80 ticks between shots (was much faster before)
var enemyGunFireTick = 0;
// NEW: variables to control enemy gun speedup
var enemyGunFireRateMin = 20; // Minimum allowed fire rate (fastest)
var enemyGunFireRateSpeedupTicks = 0; // Ticks counter for gun speedup
// Main update loop
game.update = function () {
// Player invulnerability
if (player.invulnTicks > 0) player.invulnTicks--;
// Firing
fireCooldown--;
if (dragging && fireCooldown <= 0) {
var gun = playerGuns[currentGunIndex];
fireGun(gun);
fireCooldown = gun.fireRate;
}
// Update bullets
for (var i = bullets.length - 1; i >= 0; --i) {
var b = bullets[i];
b.update();
// Remove if out of bounds
if (b.x < -100 || b.x > 2148 || b.y < -100 || b.y > 2832) {
b.destroy();
bullets.splice(i, 1);
continue;
}
// Collisions
if (b.fromPlayer) {
// Hit enemy
for (var j = enemies.length - 1; j >= 0; --j) {
var e = enemies[j];
var dx = b.x - e.x;
var dy = b.y - e.y;
var dist = dx * dx + dy * dy;
if (dist < (b.radius + e.radius) * (b.radius + e.radius)) {
// Bullet hits enemy: deal damage
e.hp -= b.damage;
LK.getSound('enemyhit').play();
b.destroy();
bullets.splice(i, 1);
if (e.hp <= 0) {
// Enemy dies
spawnGunDrop(e.x, e.y);
// Kill animation: flash enemy red and scale up, then destroy
tween(e, {
scaleX: 1.7,
scaleY: 1.7,
alpha: 0
}, {
duration: 320,
easing: tween.easeOutCubic,
onFinish: function onFinish() {
e.destroy();
}
});
enemies.splice(j, 1);
score += 10;
scoreTxt.setText(score);
// POLISH: score pop
scoreTxt.scaleX = 1.3;
scoreTxt.scaleY = 1.3;
tween(scoreTxt, {
scaleX: 1,
scaleY: 1
}, {
duration: 180,
easing: tween.easeOutBack
});
// Win condition: player wins at 500 points
if (score >= 500) {
LK.effects.flashScreen(0x00ff00, 1000);
LK.showYouWin();
return;
}
}
break;
}
}
} else {
// Enemy bullet hits player
var dx = b.x - player.x;
var dy = b.y - player.y;
var dist = dx * dx + dy * dy;
if (dist < (b.radius + player.radius) * (b.radius + player.radius)) {
if (player.invulnTicks <= 0) {
LK.effects.flashScreen(0xff0000, 600);
player.setInvulnerable(40);
b.destroy();
bullets.splice(i, 1);
// Game over
LK.showGameOver();
return;
}
}
}
}
// Update enemies
// Slowly increase enemy speed multiplier every 120 ticks (~2 seconds)
enemySpeedupTicks++;
if (enemySpeedupTicks >= 120) {
enemySpeedupTicks = 0;
enemySpeedMultiplier += 0.03; // Increase speed multiplier slowly
if (enemySpeedMultiplier > 3) enemySpeedMultiplier = 3; // Cap to avoid going too fast
}
// NEW: Slowly speed up enemy gun by decreasing fire rate every 180 ticks (~3 seconds)
enemyGunFireRateSpeedupTicks++;
if (enemyGunFireRateSpeedupTicks >= 180) {
enemyGunFireRateSpeedupTicks = 0;
if (enemyGunFireRate > enemyGunFireRateMin) {
enemyGunFireRate -= 2; // Decrease fire rate (speed up gun)
if (enemyGunFireRate < enemyGunFireRateMin) enemyGunFireRate = enemyGunFireRateMin;
}
}
for (var i = enemies.length - 1; i >= 0; --i) {
var e = enemies[i];
// Apply speed multiplier for this frame
var origSpeed = e.speed;
e.speed = origSpeed * enemySpeedMultiplier;
e.update();
e.speed = origSpeed; // Restore original speed for next frame
// Remove if out of bounds
if (e.x < -120 || e.x > 2168 || e.y > 2832) {
e.destroy();
enemies.splice(i, 1);
continue;
}
// Enemy collides with player
var dx = e.x - player.x;
var dy = e.y - player.y;
var dist = dx * dx + dy * dy;
if (dist < (e.radius + player.radius) * (e.radius + player.radius)) {
if (player.invulnTicks <= 0) {
LK.effects.flashScreen(0xff0000, 600);
player.setInvulnerable(40);
// Game over
LK.showGameOver();
return;
}
}
// Enemy fires at player (throttled by enemyGunFireRate)
if (enemyGunFireTick <= 0) {
if (Math.random() < 0.008 + wave * 0.001) {
var dir = Math.atan2(player.y - e.y, player.x - e.x);
var eb = new Bullet();
eb.init('bullet_basic', 18 + Math.random() * 6, dir, 1, false);
eb.x = e.x;
eb.y = e.y;
bullets.push(eb);
game.addChild(eb);
enemyGunFireTick = enemyGunFireRate;
}
}
}
// Update gun drops
for (var i = gunDrops.length - 1; i >= 0; --i) {
var g = gunDrops[i];
g.update();
// Remove if out of bounds
if (g.y > 2832) {
g.destroy();
gunDrops.splice(i, 1);
continue;
}
// Pickup
var dx = g.x - player.x;
var dy = g.y - player.y;
var dist = dx * dx + dy * dy;
if (dist < (g.radius + player.radius) * (g.radius + player.radius)) {
// Add gun if not already owned
if (!hasGun(g.gun.id)) {
playerGuns.push(g.gun);
currentGunIndex = playerGuns.length - 1;
updateGunUI();
updateInventoryUI();
LK.getSound('pickup').play();
// Visual feedback for pickup
LK.effects.flashObject(player, 0x00ff00, 400);
}
g.destroy();
gunDrops.splice(i, 1);
}
}
// Next wave
if (enemies.length === 0 && gunDrops.length === 0 && ticksToNextWave <= 0) {
ticksToNextWave = 60;
}
if (ticksToNextWave > 0) {
ticksToNextWave--;
if (ticksToNextWave === 0) {
wave++;
spawnWave(wave);
// (Wave UI update/animation removed)
}
}
};
// Helper: fire gun
function fireGun(gun) {
for (var i = 0; i < gun.bulletsPerShot; ++i) {
var b = new Bullet();
var spread = gun.spread * (Math.random() - 0.5);
var dir = -Math.PI / 2 + spread + (gun.bulletsPerShot > 1 ? (i - (gun.bulletsPerShot - 1) / 2) * gun.spread : 0);
b.init(gun.bulletAsset, gun.bulletSpeed, dir, gun.damage, true);
b.x = player.x;
b.y = player.y - player.radius;
bullets.push(b);
game.addChild(b);
}
LK.getSound('shoot').play();
}
// Helper: spawn wave
function spawnWave(w) {
var n = 3 + Math.floor(w * 1.5);
for (var i = 0; i < n; ++i) {
// Choose enemy type: more tanks and fast enemies as wave increases
var typeRand = Math.random();
var e;
if (w > 2 && typeRand < Math.min(0.18 + w * 0.01, 0.33)) {
// Tank enemy
e = new EnemyTank();
e.hp = 6 + Math.floor(w / 2);
e.speed = 2 + Math.random() * 0.8 + w * 0.04;
} else if (w > 1 && typeRand < Math.min(0.45 + w * 0.01, 0.6)) {
// Fast enemy
e = new EnemyFast();
e.hp = 1 + Math.floor(w / 4);
e.speed = 8 + Math.random() * 2.5 + w * 0.12;
} else {
// Normal enemy
e = new Enemy();
e.hp = 2 + Math.floor(w / 2);
e.speed = 2 + Math.random() * 1.5 + w * 0.08;
}
e.x = 200 + Math.random() * (2048 - 400);
e.y = -100 - Math.random() * 200;
// Add enemy to the game scene so it is visible
game.addChild(e);
enemies.push(e);
// POLISH: spawn flash
LK.effects.flashObject(e, 0xffffff, 200);
}
}
// Helper: spawn gun drop
function spawnGunDrop(x, y) {
// 50% chance to drop a gun, only if player doesn't have all guns
if (playerGuns.length >= GUNS.length) return;
if (Math.random() < 0.5) {
var exclude = [];
for (var i = 0; i < playerGuns.length; ++i) exclude.push(playerGuns[i].id);
var gun = getRandomGun(exclude);
var g = new GunDrop();
g.x = x;
g.y = y;
g.setGun(gun);
gunDrops.push(g);
game.addChild(g);
}
}
// Helper: update gun UI
function updateGunUI() {
var gun = playerGuns[currentGunIndex];
gunTxt.setText("Gun: " + gun.name + " (Tap here to switch)");
gunTxt.setStyle({
fill: "#" + gun.color.toString(16)
});
updateInventoryUI();
}
// Helper: has gun
function hasGun(id) {
for (var i = 0; i < playerGuns.length; ++i) if (playerGuns[i].id === id) return true;
return false;
}