User prompt
Can you also add a design for the bosses, Place one bomb in a different location on the map for each wave — only one bomb per wave. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
The game is almost finished now! But can you remove the overpower from the pistol during the final boss fight? Make it strong, but not too much. Also, make the enemies much tougher in the last wave.
User prompt
Perfect! Can you add a better design for the zombies and the main character, place health pickups on the map, and decorate the map with visual details?” ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Code it, and put a character design ↪💡 Consider importing and using the following plugins: @upit/tween.v1
Code edit (1 edits merged)
Please save this source code
User prompt
Zombie Waves: Key to Survival
Initial prompt
Zombie Waves Fight zombies through five rounds. At the end of each round, there is a strong enemy that’s different from the regular ones. After defeating it, you get a key that leads you to the next round, and so on. In the final round, you face a very powerful enemy, and the player is given a strong weapon to fight him. The game should have classic-style graphics, with control buttons, and a designed character appearance.
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Bomb = Container.expand(function () {
var self = Container.call(this);
var bombBase = self.attachAsset('bomb', {
anchorX: 0.5,
anchorY: 0.5
});
var fuse = self.attachAsset('bombFuse', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -35
});
var spark = self.attachAsset('bombSpark', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -40
});
self.health = 25;
self.exploded = false;
self.sparkOffset = 0;
self.update = function () {
// Animate the spark on the fuse
self.sparkOffset += 0.15;
spark.alpha = 0.8 + Math.sin(self.sparkOffset) * 0.2;
spark.scaleX = 0.8 + Math.sin(self.sparkOffset * 2) * 0.3;
spark.scaleY = 0.8 + Math.sin(self.sparkOffset * 2) * 0.3;
// Gentle bobbing animation
self.y += Math.sin(LK.ticks * 0.05) * 0.3;
};
self.explode = function () {
if (self.exploded) return;
self.exploded = true;
// Create explosion effect
LK.effects.flashScreen(0xff4500, 300);
// Damage nearby enemies and player
var explosionRadius = 150;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var distance = Math.sqrt(Math.pow(zombie.x - self.x, 2) + Math.pow(zombie.y - self.y, 2));
if (distance < explosionRadius) {
if (zombie.takeDamage(75)) {
LK.getSound('zombieDeath').play();
LK.setScore(LK.getScore() + 10);
zombie.destroy();
zombies.splice(i, 1);
i--;
}
}
}
// Check boss damage
if (boss) {
var bossDistance = Math.sqrt(Math.pow(boss.x - self.x, 2) + Math.pow(boss.y - self.y, 2));
if (bossDistance < explosionRadius) {
boss.takeDamage(100);
}
}
// Check player damage
if (player) {
var playerDistance = Math.sqrt(Math.pow(player.x - self.x, 2) + Math.pow(player.y - self.y, 2));
if (playerDistance < explosionRadius) {
player.takeDamage(30);
}
}
// Remove bomb
self.destroy();
var index = bombs.indexOf(self);
if (index > -1) bombs.splice(index, 1);
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xffffff, 100);
if (self.health <= 0) {
self.explode();
return true;
}
return false;
};
return self;
});
var Boss = Container.expand(function (type) {
var self = Container.call(this);
var bossAsset = type === 5 ? 'finalBoss' : 'boss' + type;
var bossGraphics = self.attachAsset(bossAsset, {
anchorX: 0.5,
anchorY: 0.5
});
// Add unique design elements for each boss
if (type === 1) {
// Speed boss with spikes
var eyes1 = self.attachAsset('boss1Eyes', {
anchorX: 0.5,
anchorY: 0.5,
x: -15,
y: -10
});
var eyes2 = self.attachAsset('boss1Eyes', {
anchorX: 0.5,
anchorY: 0.5,
x: 15,
y: -10
});
var spike1 = self.attachAsset('boss1Spikes', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -35,
rotation: 0
});
var spike2 = self.attachAsset('boss1Spikes', {
anchorX: 0.5,
anchorY: 0.5,
x: -25,
y: 0,
rotation: 1.57
});
var spike3 = self.attachAsset('boss1Spikes', {
anchorX: 0.5,
anchorY: 0.5,
x: 25,
y: 0,
rotation: -1.57
});
} else if (type === 2) {
// Summoner boss with horns and claws
var horn = self.attachAsset('boss2Horn', {
anchorX: 0.5,
anchorY: 1,
x: 0,
y: -40
});
var claw1 = self.attachAsset('boss2Claws', {
anchorX: 0.5,
anchorY: 0.5,
x: -35,
y: 0
});
var claw2 = self.attachAsset('boss2Claws', {
anchorX: 0.5,
anchorY: 0.5,
x: 35,
y: 0
});
} else if (type === 3) {
// Screamer boss with armor and gem
var armor = self.attachAsset('boss3Armor', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0
});
var gem = self.attachAsset('boss3Gem', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -20
});
self.gemRef = gem; // Store reference for pulsing animation
} else if (type === 4) {
// Healer boss with wings and crown
var wing1 = self.attachAsset('boss4Wings', {
anchorX: 0.5,
anchorY: 0.5,
x: -50,
y: 0
});
var wing2 = self.attachAsset('boss4Wings', {
anchorX: 0.5,
anchorY: 0.5,
x: 50,
y: 0
});
var crown = self.attachAsset('boss4Crown', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -50
});
self.wing1Ref = wing1;
self.wing2Ref = wing2;
}
self.type = type;
self.health = 150 + type * 50;
// Make final boss extra tough
if (type === 5) {
self.health = 500; // Much higher health for final boss
}
self.maxHealth = self.health;
self.speed = 0.8 + type * 0.2;
self.damage = 15 + type * 5;
// Increase final boss damage
if (type === 5) {
self.damage = 40; // Much higher damage for final boss
}
self.lastAttack = 0;
self.attackCooldown = 90 - type * 10;
self.lastSpecial = 0;
self.specialCooldown = 300;
self.update = function () {
// Boss-specific animations
if (self.type === 3 && self.gemRef) {
// Pulsing gem animation for screamer boss
self.gemRef.scaleX = 1 + Math.sin(LK.ticks * 0.1) * 0.3;
self.gemRef.scaleY = 1 + Math.sin(LK.ticks * 0.1) * 0.3;
} else if (self.type === 4 && self.wing1Ref && self.wing2Ref) {
// Flapping wings animation for healer boss
var wingFlap = Math.sin(LK.ticks * 0.15) * 0.2;
self.wing1Ref.rotation = wingFlap;
self.wing2Ref.rotation = -wingFlap;
}
if (player) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
if (distance < 80 && LK.ticks - self.lastAttack > self.attackCooldown) {
player.takeDamage(self.damage);
self.lastAttack = LK.ticks;
LK.effects.flashScreen(0xff0000, 200);
}
if (LK.ticks - self.lastSpecial > self.specialCooldown) {
self.specialAttack();
self.lastSpecial = LK.ticks;
}
}
};
self.specialAttack = function () {
if (self.type === 1) {
self.speed *= 2;
tween(self, {
speed: 0.8
}, {
duration: 2000
});
} else if (self.type === 2) {
for (var i = 0; i < 3; i++) {
var minion = new Zombie();
minion.x = self.x + (Math.random() - 0.5) * 200;
minion.y = self.y + (Math.random() - 0.5) * 200;
minion.health = 25;
zombies.push(minion);
game.addChild(minion);
}
} else if (self.type === 3) {
LK.effects.flashScreen(0x800080, 500);
player.takeDamage(20);
} else if (self.type === 4) {
self.health += 25;
self.health = Math.min(self.health, self.maxHealth);
LK.effects.flashObject(self, 0x00ff00, 500);
} else if (self.type === 5) {
for (var i = 0; i < 8; i++) {
var angle = i / 8 * Math.PI * 2;
var shockwave = new Zombie();
shockwave.x = self.x;
shockwave.y = self.y;
shockwave.speed = 3;
shockwave.moveAngle = angle;
shockwave.health = 1;
shockwave.damage = 25;
shockwave.update = function () {
this.x += Math.cos(this.moveAngle) * this.speed;
this.y += Math.sin(this.moveAngle) * this.speed;
if (this.x < 0 || this.x > 2048 || this.y < 0 || this.y > 2732) {
this.destroy();
var index = zombies.indexOf(this);
if (index > -1) zombies.splice(index, 1);
}
};
zombies.push(shockwave);
game.addChild(shockwave);
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xffffff, 150);
return self.health <= 0;
};
return self;
});
var Bullet = Container.expand(function (isSuper) {
var self = Container.call(this);
var bulletAsset = isSuper ? 'superBullet' : 'bullet';
var bulletGraphics = self.attachAsset(bulletAsset, {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8;
self.damage = isSuper ? 30 : 25;
self.dx = 0;
self.dy = 0;
self.update = function () {
self.x += self.dx * self.speed;
self.y += self.dy * self.speed;
if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
self.destroy();
var index = bullets.indexOf(self);
if (index > -1) bullets.splice(index, 1);
}
};
return self;
});
var HealthPickup = Container.expand(function () {
var self = Container.call(this);
var healthBase = self.attachAsset('healthPickup', {
anchorX: 0.5,
anchorY: 0.5
});
var crossH = self.attachAsset('healthCross', {
anchorX: 0.5,
anchorY: 0.5
});
var crossV = self.attachAsset('healthCrossV', {
anchorX: 0.5,
anchorY: 0.5
});
self.healAmount = 30;
self.collected = false;
self.pulseDirection = 1;
self.update = function () {
// Gentle pulsing animation
self.scaleX += 0.01 * self.pulseDirection;
self.scaleY += 0.01 * self.pulseDirection;
if (self.scaleX > 1.2) self.pulseDirection = -1;
if (self.scaleX < 0.9) self.pulseDirection = 1;
self.rotation += 0.02;
if (player && !self.collected && self.intersects(player)) {
self.collected = true;
player.heal(self.healAmount);
LK.effects.flashObject(player, 0x00ff00, 500);
self.destroy();
var index = healthPickups.indexOf(self);
if (index > -1) healthPickups.splice(index, 1);
}
};
return self;
});
var Key = Container.expand(function () {
var self = Container.call(this);
var keyGraphics = self.attachAsset('key', {
anchorX: 0.5,
anchorY: 0.5
});
self.collected = false;
self.update = function () {
self.rotation += 0.05;
if (player && !self.collected && self.intersects(player)) {
self.collected = true;
keys++;
LK.getSound('keyCollect').play();
LK.effects.flashObject(self, 0xffffff, 300);
self.destroy();
}
};
return self;
});
var MapDecoration = Container.expand(function (type) {
var self = Container.call(this);
if (type === 'rock') {
var rock = self.attachAsset('rock', {
anchorX: 0.5,
anchorY: 0.5
});
self.scaleX = 0.8 + Math.random() * 0.4;
self.scaleY = 0.8 + Math.random() * 0.4;
self.rotation = Math.random() * Math.PI * 2;
} else if (type === 'tree') {
var trunk = self.attachAsset('tree', {
anchorX: 0.5,
anchorY: 1
});
var crown = self.attachAsset('treeCrown', {
anchorX: 0.5,
anchorY: 0.8,
y: -60
});
self.scaleX = 0.7 + Math.random() * 0.6;
self.scaleY = 0.7 + Math.random() * 0.6;
} else if (type === 'grass') {
var grass = self.attachAsset('grass', {
anchorX: 0.5,
anchorY: 0.5
});
self.scaleX = 0.5 + Math.random() * 0.8;
self.scaleY = 0.5 + Math.random() * 0.8;
self.rotation = Math.random() * Math.PI * 2;
self.alpha = 0.6 + Math.random() * 0.4;
}
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
// Create character design with multiple parts
var playerBase = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
var playerBody = self.attachAsset('playerBody', {
anchorX: 0.5,
anchorY: 0.5,
y: -5
});
var playerHead = self.attachAsset('playerHead', {
anchorX: 0.5,
anchorY: 0.5,
y: -25
});
self.health = 100;
self.maxHealth = 100;
self.speed = 5;
self.lastShot = 0;
self.shootCooldown = 15;
self.weaponLevel = 1;
self.velocityX = 0;
self.velocityY = 0;
self.friction = 0.85;
self.smoothMove = function (moveX, moveY) {
// Add acceleration instead of instant movement
self.velocityX += moveX * 0.3;
self.velocityY += moveY * 0.3;
// Cap maximum velocity
var maxVel = self.speed;
if (Math.abs(self.velocityX) > maxVel) self.velocityX = self.velocityX > 0 ? maxVel : -maxVel;
if (Math.abs(self.velocityY) > maxVel) self.velocityY = self.velocityY > 0 ? maxVel : -maxVel;
};
self.updateMovement = function () {
// Apply velocity to position
self.x += self.velocityX;
self.y += self.velocityY;
// Apply friction when not moving
self.velocityX *= self.friction;
self.velocityY *= self.friction;
// Keep player in bounds
self.x = Math.max(40, Math.min(2008, self.x));
self.y = Math.max(40, Math.min(2692, self.y));
// Rotate character slightly based on movement direction
if (Math.abs(self.velocityX) > 0.1 || Math.abs(self.velocityY) > 0.1) {
var targetRotation = Math.atan2(self.velocityY, self.velocityX) * 0.1;
tween(playerBase, {
rotation: targetRotation
}, {
duration: 200
});
}
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.health = 0;
LK.showGameOver();
}
LK.effects.flashObject(self, 0xff0000, 300);
};
self.heal = function (amount) {
self.health = Math.min(self.maxHealth, self.health + amount);
// Visual healing feedback
tween(self, {
tint: 0x00ff00
}, {
duration: 300,
onFinish: function onFinish() {
tween(self, {
tint: 0xffffff
}, {
duration: 200
});
}
});
};
self.upgrade = function () {
self.weaponLevel++;
self.shootCooldown = Math.max(5, self.shootCooldown - 2);
if (self.weaponLevel >= 5) {
self.shootCooldown = 8;
}
// Visual upgrade feedback
tween(self, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 300,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 200
});
}
});
};
return self;
});
var Zombie = Container.expand(function () {
var self = Container.call(this);
// Create zombie with multiple body parts for better design
var zombieBase = self.attachAsset('zombie', {
anchorX: 0.5,
anchorY: 0.5
});
var zombieBody = self.attachAsset('zombieBody', {
anchorX: 0.5,
anchorY: 0.5,
y: -5
});
var zombieHead = self.attachAsset('zombieHead', {
anchorX: 0.5,
anchorY: 0.5,
y: -25
});
var zombieArms = self.attachAsset('zombieArms', {
anchorX: 0.5,
anchorY: 0.5,
y: -8
});
// Add some visual variety
self.scaleX = 0.8 + Math.random() * 0.4;
self.scaleY = 0.8 + Math.random() * 0.4;
zombieHead.tint = Math.random() > 0.5 ? 0x9acd32 : 0x556b2f;
self.wobbleOffset = Math.random() * Math.PI * 2;
self.health = 50;
self.speed = 1.5;
self.damage = 10;
self.lastAttack = 0;
self.attackCooldown = 60;
self.update = function () {
// Add wobble animation while moving
self.rotation = Math.sin(LK.ticks * 0.1 + self.wobbleOffset) * 0.1;
zombieArms.rotation = Math.sin(LK.ticks * 0.15 + self.wobbleOffset) * 0.3;
if (player) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
if (distance < 50 && LK.ticks - self.lastAttack > self.attackCooldown) {
player.takeDamage(self.damage);
self.lastAttack = LK.ticks;
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xffffff, 100);
return self.health <= 0;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2d1b1b
});
/****
* Game Code
****/
var player;
var zombies = [];
var bullets = [];
var healthPickups = [];
var bombs = [];
var boss = null;
var keys = 0;
var currentWave = 1;
var waveInProgress = false;
var zombiesSpawned = 0;
var zombiesToSpawn = 0;
var spawnTimer = 0;
var healthSpawnTimer = 0;
var moveButton, shootButton, joystickKnob;
var isDragging = false;
var isShootPressed = false;
var joystickRadius = 120;
var joystickCenterX = 200;
var joystickCenterY = 2500;
var currentMoveX = 0;
var currentMoveY = 0;
// UI Elements
var waveText = new Text2('Wave 1', {
size: 80,
fill: 0xFFFFFF
});
waveText.anchor.set(0.5, 0);
LK.gui.top.addChild(waveText);
var healthText = new Text2('Health: 100', {
size: 60,
fill: 0xFF0000
});
healthText.anchor.set(0, 0);
healthText.x = 120;
healthText.y = 20;
LK.gui.topLeft.addChild(healthText);
var keysText = new Text2('Keys: 0', {
size: 60,
fill: 0xFFD700
});
keysText.anchor.set(1, 0);
LK.gui.topRight.addChild(keysText);
var instructionText = new Text2('Survive the zombie waves!\nDefeat bosses to get keys!', {
size: 50,
fill: 0xFFFFFF
});
instructionText.anchor.set(0.5, 0.5);
instructionText.x = 0;
instructionText.y = -200;
LK.gui.center.addChild(instructionText);
// Initialize player
player = new Player();
player.x = 1024;
player.y = 1366;
game.addChild(player);
// Control buttons with enhanced design
moveButton = game.addChild(LK.getAsset('moveButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.6,
scaleY: 1.6
}));
moveButton.x = joystickCenterX;
moveButton.y = joystickCenterY;
moveButton.alpha = 0.4;
// Add joystick knob
joystickKnob = game.addChild(LK.getAsset('joystickKnob', {
anchorX: 0.5,
anchorY: 0.5
}));
joystickKnob.x = joystickCenterX;
joystickKnob.y = joystickCenterY;
joystickKnob.alpha = 0.8;
shootButton = game.addChild(LK.getAsset('shootButton', {
anchorX: 0.5,
anchorY: 0.5
}));
shootButton.x = 1850;
shootButton.y = 2500;
shootButton.alpha = 0.6;
function startWave() {
if (currentWave > 5) {
LK.showYouWin();
return;
}
waveInProgress = true;
zombiesSpawned = 0;
zombiesToSpawn = 5 + currentWave * 3;
spawnTimer = 0;
waveText.setText('Wave ' + currentWave);
instructionText.setText('');
// Spawn bomb for this wave
spawnBombForWave(currentWave);
if (currentWave > 1) {
player.upgrade();
}
}
function spawnZombie() {
var zombie = new Zombie();
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
// Top
zombie.x = Math.random() * 2048;
zombie.y = -30;
break;
case 1:
// Right
zombie.x = 2078;
zombie.y = Math.random() * 2732;
break;
case 2:
// Bottom
zombie.x = Math.random() * 2048;
zombie.y = 2762;
break;
case 3:
// Left
zombie.x = -30;
zombie.y = Math.random() * 2732;
break;
}
zombie.health = 50 + currentWave * 15;
zombie.speed = 1.5 + currentWave * 0.4;
// Make final wave zombies extra tough
if (currentWave === 5) {
zombie.health += 50;
zombie.speed += 0.5;
zombie.damage = 20; // Increased damage for final wave
}
zombies.push(zombie);
game.addChild(zombie);
}
function spawnBoss() {
boss = new Boss(currentWave);
boss.x = 1024;
boss.y = 200;
game.addChild(boss);
}
function shootBullet() {
if (LK.ticks - player.lastShot < player.shootCooldown) return;
var isSuper = player.weaponLevel >= 5;
var bullet = new Bullet(isSuper);
bullet.x = player.x;
bullet.y = player.y;
var nearestEnemy = null;
var minDistance = Infinity;
for (var i = 0; i < zombies.length; i++) {
var distance = Math.sqrt(Math.pow(zombies[i].x - player.x, 2) + Math.pow(zombies[i].y - player.y, 2));
if (distance < minDistance) {
minDistance = distance;
nearestEnemy = zombies[i];
}
}
if (boss) {
var bossDistance = Math.sqrt(Math.pow(boss.x - player.x, 2) + Math.pow(boss.y - player.y, 2));
if (bossDistance < minDistance) {
nearestEnemy = boss;
}
}
if (nearestEnemy) {
var dx = nearestEnemy.x - bullet.x;
var dy = nearestEnemy.y - bullet.y;
var distance = Math.sqrt(dx * dx + dy * dy);
bullet.dx = dx / distance;
bullet.dy = dy / distance;
} else {
bullet.dx = 0;
bullet.dy = -1;
}
bullets.push(bullet);
game.addChild(bullet);
player.lastShot = LK.ticks;
LK.getSound('shoot').play();
}
// Touch controls with smooth analog movement
moveButton.down = function (x, y, obj) {
isDragging = true;
// Visual feedback - make joystick base slightly larger
tween(moveButton, {
scaleX: 1.8,
scaleY: 1.8,
alpha: 0.6
}, {
duration: 150
});
};
shootButton.down = function (x, y, obj) {
isShootPressed = true;
// Visual feedback for shoot button
tween(shootButton, {
scaleX: 0.9,
scaleY: 0.9,
tint: 0xffaa00
}, {
duration: 100
});
shootBullet();
};
shootButton.up = function (x, y, obj) {
isShootPressed = false;
// Return shoot button to normal
tween(shootButton, {
scaleX: 1,
scaleY: 1,
tint: 0xffffff
}, {
duration: 150
});
};
game.move = function (x, y, obj) {
if (isDragging) {
var dx = x - joystickCenterX;
var dy = y - joystickCenterY;
var distance = Math.sqrt(dx * dx + dy * dy);
// Limit knob position within joystick radius
if (distance > joystickRadius) {
dx = dx / distance * joystickRadius;
dy = dy / distance * joystickRadius;
distance = joystickRadius;
}
// Update knob position smoothly
joystickKnob.x = joystickCenterX + dx;
joystickKnob.y = joystickCenterY + dy;
// Calculate movement with smooth analog control
var intensity = distance / joystickRadius;
currentMoveX = dx / joystickRadius * player.speed * intensity;
currentMoveY = dy / joystickRadius * player.speed * intensity;
// Apply smooth movement to player
if (player) {
player.smoothMove(currentMoveX, currentMoveY);
}
}
};
game.up = function (x, y, obj) {
if (isDragging) {
isDragging = false;
currentMoveX = 0;
currentMoveY = 0;
// Return joystick to center with smooth animation
tween(joystickKnob, {
x: joystickCenterX,
y: joystickCenterY
}, {
duration: 200,
easing: tween.easeOut
});
tween(moveButton, {
scaleX: 1.6,
scaleY: 1.6,
alpha: 0.4
}, {
duration: 200
});
}
isShootPressed = false;
};
function spawnHealthPickup() {
var health = new HealthPickup();
health.x = 200 + Math.random() * 1648; // Keep away from edges
health.y = 200 + Math.random() * 2332;
healthPickups.push(health);
game.addChild(health);
}
function spawnBombForWave(wave) {
// Remove existing bomb if any
for (var i = bombs.length - 1; i >= 0; i--) {
bombs[i].destroy();
bombs.splice(i, 1);
}
var bomb = new Bomb();
// Position bomb at different locations based on wave
switch (wave) {
case 1:
bomb.x = 400;
bomb.y = 600;
break;
case 2:
bomb.x = 1600;
bomb.y = 1000;
break;
case 3:
bomb.x = 800;
bomb.y = 2000;
break;
case 4:
bomb.x = 1400;
bomb.y = 1600;
break;
case 5:
bomb.x = 1024;
bomb.y = 1000;
break;
}
bombs.push(bomb);
game.addChild(bomb);
}
function createMapDecorations() {
// Add trees around the map
for (var i = 0; i < 8; i++) {
var tree = new MapDecoration('tree');
tree.x = 100 + Math.random() * 1848;
tree.y = 100 + Math.random() * 2532;
game.addChild(tree);
}
// Add rocks scattered around
for (var i = 0; i < 15; i++) {
var rock = new MapDecoration('rock');
rock.x = 100 + Math.random() * 1848;
rock.y = 100 + Math.random() * 2532;
game.addChild(rock);
}
// Add grass patches
for (var i = 0; i < 25; i++) {
var grass = new MapDecoration('grass');
grass.x = Math.random() * 2048;
grass.y = Math.random() * 2732;
game.addChild(grass);
}
}
// Create map decorations
createMapDecorations();
// Start first wave
startWave();
LK.playMusic('gameMusic');
game.update = function () {
// Update UI
healthText.setText('Health: ' + player.health);
keysText.setText('Keys: ' + keys);
// Update player movement
if (player) {
player.updateMovement();
}
// Spawn health pickups occasionally
healthSpawnTimer++;
if (healthSpawnTimer >= 1800 && healthPickups.length < 2) {
// Every 30 seconds max 2 on map
spawnHealthPickup();
healthSpawnTimer = 0;
}
// Smooth continuous shooting when button held
if (isShootPressed) {
shootBullet();
}
// Spawn zombies during wave
if (waveInProgress && zombiesSpawned < zombiesToSpawn) {
spawnTimer++;
if (spawnTimer >= 60) {
spawnZombie();
zombiesSpawned++;
spawnTimer = 0;
}
}
// Check if wave is complete
if (waveInProgress && zombiesSpawned >= zombiesToSpawn && zombies.length === 0 && !boss) {
spawnBoss();
}
// Bullet collision detection
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
var hit = false;
// Check bomb collisions
for (var k = bombs.length - 1; k >= 0; k--) {
if (bullet.intersects(bombs[k])) {
if (bombs[k].takeDamage(bullet.damage)) {
// Bomb will handle its own destruction and explosion
}
bullet.destroy();
bullets.splice(i, 1);
hit = true;
break;
}
}
if (!hit) {
// Check zombie collisions
for (var j = zombies.length - 1; j >= 0; j--) {
if (bullet.intersects(zombies[j])) {
if (zombies[j].takeDamage(bullet.damage)) {
LK.getSound('zombieDeath').play();
LK.setScore(LK.getScore() + 10);
zombies[j].destroy();
zombies.splice(j, 1);
}
bullet.destroy();
bullets.splice(i, 1);
hit = true;
break;
}
}
}
// Check boss collision
if (!hit && boss && bullet.intersects(boss)) {
if (boss.takeDamage(bullet.damage)) {
LK.getSound('bossDefeat').play();
LK.setScore(LK.getScore() + 100);
// Drop key
var key = new Key();
key.x = boss.x;
key.y = boss.y;
game.addChild(key);
boss.destroy();
boss = null;
waveInProgress = false;
currentWave++;
LK.setTimeout(function () {
if (currentWave <= 5) {
startWave();
} else {
instructionText.setText('Congratulations!\nYou survived all waves!');
LK.setTimeout(function () {
LK.showYouWin();
}, 3000);
}
}, 2000);
}
bullet.destroy();
bullets.splice(i, 1);
}
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Bomb = Container.expand(function () {
var self = Container.call(this);
var bombBase = self.attachAsset('bomb', {
anchorX: 0.5,
anchorY: 0.5
});
var fuse = self.attachAsset('bombFuse', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -35
});
var spark = self.attachAsset('bombSpark', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -40
});
self.health = 25;
self.exploded = false;
self.sparkOffset = 0;
self.update = function () {
// Animate the spark on the fuse
self.sparkOffset += 0.15;
spark.alpha = 0.8 + Math.sin(self.sparkOffset) * 0.2;
spark.scaleX = 0.8 + Math.sin(self.sparkOffset * 2) * 0.3;
spark.scaleY = 0.8 + Math.sin(self.sparkOffset * 2) * 0.3;
// Gentle bobbing animation
self.y += Math.sin(LK.ticks * 0.05) * 0.3;
};
self.explode = function () {
if (self.exploded) return;
self.exploded = true;
// Create explosion effect
LK.effects.flashScreen(0xff4500, 300);
// Damage nearby enemies and player
var explosionRadius = 150;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var distance = Math.sqrt(Math.pow(zombie.x - self.x, 2) + Math.pow(zombie.y - self.y, 2));
if (distance < explosionRadius) {
if (zombie.takeDamage(75)) {
LK.getSound('zombieDeath').play();
LK.setScore(LK.getScore() + 10);
zombie.destroy();
zombies.splice(i, 1);
i--;
}
}
}
// Check boss damage
if (boss) {
var bossDistance = Math.sqrt(Math.pow(boss.x - self.x, 2) + Math.pow(boss.y - self.y, 2));
if (bossDistance < explosionRadius) {
boss.takeDamage(100);
}
}
// Check player damage
if (player) {
var playerDistance = Math.sqrt(Math.pow(player.x - self.x, 2) + Math.pow(player.y - self.y, 2));
if (playerDistance < explosionRadius) {
player.takeDamage(30);
}
}
// Remove bomb
self.destroy();
var index = bombs.indexOf(self);
if (index > -1) bombs.splice(index, 1);
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xffffff, 100);
if (self.health <= 0) {
self.explode();
return true;
}
return false;
};
return self;
});
var Boss = Container.expand(function (type) {
var self = Container.call(this);
var bossAsset = type === 5 ? 'finalBoss' : 'boss' + type;
var bossGraphics = self.attachAsset(bossAsset, {
anchorX: 0.5,
anchorY: 0.5
});
// Add unique design elements for each boss
if (type === 1) {
// Speed boss with spikes
var eyes1 = self.attachAsset('boss1Eyes', {
anchorX: 0.5,
anchorY: 0.5,
x: -15,
y: -10
});
var eyes2 = self.attachAsset('boss1Eyes', {
anchorX: 0.5,
anchorY: 0.5,
x: 15,
y: -10
});
var spike1 = self.attachAsset('boss1Spikes', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -35,
rotation: 0
});
var spike2 = self.attachAsset('boss1Spikes', {
anchorX: 0.5,
anchorY: 0.5,
x: -25,
y: 0,
rotation: 1.57
});
var spike3 = self.attachAsset('boss1Spikes', {
anchorX: 0.5,
anchorY: 0.5,
x: 25,
y: 0,
rotation: -1.57
});
} else if (type === 2) {
// Summoner boss with horns and claws
var horn = self.attachAsset('boss2Horn', {
anchorX: 0.5,
anchorY: 1,
x: 0,
y: -40
});
var claw1 = self.attachAsset('boss2Claws', {
anchorX: 0.5,
anchorY: 0.5,
x: -35,
y: 0
});
var claw2 = self.attachAsset('boss2Claws', {
anchorX: 0.5,
anchorY: 0.5,
x: 35,
y: 0
});
} else if (type === 3) {
// Screamer boss with armor and gem
var armor = self.attachAsset('boss3Armor', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0
});
var gem = self.attachAsset('boss3Gem', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -20
});
self.gemRef = gem; // Store reference for pulsing animation
} else if (type === 4) {
// Healer boss with wings and crown
var wing1 = self.attachAsset('boss4Wings', {
anchorX: 0.5,
anchorY: 0.5,
x: -50,
y: 0
});
var wing2 = self.attachAsset('boss4Wings', {
anchorX: 0.5,
anchorY: 0.5,
x: 50,
y: 0
});
var crown = self.attachAsset('boss4Crown', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -50
});
self.wing1Ref = wing1;
self.wing2Ref = wing2;
}
self.type = type;
self.health = 150 + type * 50;
// Make final boss extra tough
if (type === 5) {
self.health = 500; // Much higher health for final boss
}
self.maxHealth = self.health;
self.speed = 0.8 + type * 0.2;
self.damage = 15 + type * 5;
// Increase final boss damage
if (type === 5) {
self.damage = 40; // Much higher damage for final boss
}
self.lastAttack = 0;
self.attackCooldown = 90 - type * 10;
self.lastSpecial = 0;
self.specialCooldown = 300;
self.update = function () {
// Boss-specific animations
if (self.type === 3 && self.gemRef) {
// Pulsing gem animation for screamer boss
self.gemRef.scaleX = 1 + Math.sin(LK.ticks * 0.1) * 0.3;
self.gemRef.scaleY = 1 + Math.sin(LK.ticks * 0.1) * 0.3;
} else if (self.type === 4 && self.wing1Ref && self.wing2Ref) {
// Flapping wings animation for healer boss
var wingFlap = Math.sin(LK.ticks * 0.15) * 0.2;
self.wing1Ref.rotation = wingFlap;
self.wing2Ref.rotation = -wingFlap;
}
if (player) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
if (distance < 80 && LK.ticks - self.lastAttack > self.attackCooldown) {
player.takeDamage(self.damage);
self.lastAttack = LK.ticks;
LK.effects.flashScreen(0xff0000, 200);
}
if (LK.ticks - self.lastSpecial > self.specialCooldown) {
self.specialAttack();
self.lastSpecial = LK.ticks;
}
}
};
self.specialAttack = function () {
if (self.type === 1) {
self.speed *= 2;
tween(self, {
speed: 0.8
}, {
duration: 2000
});
} else if (self.type === 2) {
for (var i = 0; i < 3; i++) {
var minion = new Zombie();
minion.x = self.x + (Math.random() - 0.5) * 200;
minion.y = self.y + (Math.random() - 0.5) * 200;
minion.health = 25;
zombies.push(minion);
game.addChild(minion);
}
} else if (self.type === 3) {
LK.effects.flashScreen(0x800080, 500);
player.takeDamage(20);
} else if (self.type === 4) {
self.health += 25;
self.health = Math.min(self.health, self.maxHealth);
LK.effects.flashObject(self, 0x00ff00, 500);
} else if (self.type === 5) {
for (var i = 0; i < 8; i++) {
var angle = i / 8 * Math.PI * 2;
var shockwave = new Zombie();
shockwave.x = self.x;
shockwave.y = self.y;
shockwave.speed = 3;
shockwave.moveAngle = angle;
shockwave.health = 1;
shockwave.damage = 25;
shockwave.update = function () {
this.x += Math.cos(this.moveAngle) * this.speed;
this.y += Math.sin(this.moveAngle) * this.speed;
if (this.x < 0 || this.x > 2048 || this.y < 0 || this.y > 2732) {
this.destroy();
var index = zombies.indexOf(this);
if (index > -1) zombies.splice(index, 1);
}
};
zombies.push(shockwave);
game.addChild(shockwave);
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xffffff, 150);
return self.health <= 0;
};
return self;
});
var Bullet = Container.expand(function (isSuper) {
var self = Container.call(this);
var bulletAsset = isSuper ? 'superBullet' : 'bullet';
var bulletGraphics = self.attachAsset(bulletAsset, {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8;
self.damage = isSuper ? 30 : 25;
self.dx = 0;
self.dy = 0;
self.update = function () {
self.x += self.dx * self.speed;
self.y += self.dy * self.speed;
if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
self.destroy();
var index = bullets.indexOf(self);
if (index > -1) bullets.splice(index, 1);
}
};
return self;
});
var HealthPickup = Container.expand(function () {
var self = Container.call(this);
var healthBase = self.attachAsset('healthPickup', {
anchorX: 0.5,
anchorY: 0.5
});
var crossH = self.attachAsset('healthCross', {
anchorX: 0.5,
anchorY: 0.5
});
var crossV = self.attachAsset('healthCrossV', {
anchorX: 0.5,
anchorY: 0.5
});
self.healAmount = 30;
self.collected = false;
self.pulseDirection = 1;
self.update = function () {
// Gentle pulsing animation
self.scaleX += 0.01 * self.pulseDirection;
self.scaleY += 0.01 * self.pulseDirection;
if (self.scaleX > 1.2) self.pulseDirection = -1;
if (self.scaleX < 0.9) self.pulseDirection = 1;
self.rotation += 0.02;
if (player && !self.collected && self.intersects(player)) {
self.collected = true;
player.heal(self.healAmount);
LK.effects.flashObject(player, 0x00ff00, 500);
self.destroy();
var index = healthPickups.indexOf(self);
if (index > -1) healthPickups.splice(index, 1);
}
};
return self;
});
var Key = Container.expand(function () {
var self = Container.call(this);
var keyGraphics = self.attachAsset('key', {
anchorX: 0.5,
anchorY: 0.5
});
self.collected = false;
self.update = function () {
self.rotation += 0.05;
if (player && !self.collected && self.intersects(player)) {
self.collected = true;
keys++;
LK.getSound('keyCollect').play();
LK.effects.flashObject(self, 0xffffff, 300);
self.destroy();
}
};
return self;
});
var MapDecoration = Container.expand(function (type) {
var self = Container.call(this);
if (type === 'rock') {
var rock = self.attachAsset('rock', {
anchorX: 0.5,
anchorY: 0.5
});
self.scaleX = 0.8 + Math.random() * 0.4;
self.scaleY = 0.8 + Math.random() * 0.4;
self.rotation = Math.random() * Math.PI * 2;
} else if (type === 'tree') {
var trunk = self.attachAsset('tree', {
anchorX: 0.5,
anchorY: 1
});
var crown = self.attachAsset('treeCrown', {
anchorX: 0.5,
anchorY: 0.8,
y: -60
});
self.scaleX = 0.7 + Math.random() * 0.6;
self.scaleY = 0.7 + Math.random() * 0.6;
} else if (type === 'grass') {
var grass = self.attachAsset('grass', {
anchorX: 0.5,
anchorY: 0.5
});
self.scaleX = 0.5 + Math.random() * 0.8;
self.scaleY = 0.5 + Math.random() * 0.8;
self.rotation = Math.random() * Math.PI * 2;
self.alpha = 0.6 + Math.random() * 0.4;
}
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
// Create character design with multiple parts
var playerBase = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
var playerBody = self.attachAsset('playerBody', {
anchorX: 0.5,
anchorY: 0.5,
y: -5
});
var playerHead = self.attachAsset('playerHead', {
anchorX: 0.5,
anchorY: 0.5,
y: -25
});
self.health = 100;
self.maxHealth = 100;
self.speed = 5;
self.lastShot = 0;
self.shootCooldown = 15;
self.weaponLevel = 1;
self.velocityX = 0;
self.velocityY = 0;
self.friction = 0.85;
self.smoothMove = function (moveX, moveY) {
// Add acceleration instead of instant movement
self.velocityX += moveX * 0.3;
self.velocityY += moveY * 0.3;
// Cap maximum velocity
var maxVel = self.speed;
if (Math.abs(self.velocityX) > maxVel) self.velocityX = self.velocityX > 0 ? maxVel : -maxVel;
if (Math.abs(self.velocityY) > maxVel) self.velocityY = self.velocityY > 0 ? maxVel : -maxVel;
};
self.updateMovement = function () {
// Apply velocity to position
self.x += self.velocityX;
self.y += self.velocityY;
// Apply friction when not moving
self.velocityX *= self.friction;
self.velocityY *= self.friction;
// Keep player in bounds
self.x = Math.max(40, Math.min(2008, self.x));
self.y = Math.max(40, Math.min(2692, self.y));
// Rotate character slightly based on movement direction
if (Math.abs(self.velocityX) > 0.1 || Math.abs(self.velocityY) > 0.1) {
var targetRotation = Math.atan2(self.velocityY, self.velocityX) * 0.1;
tween(playerBase, {
rotation: targetRotation
}, {
duration: 200
});
}
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.health = 0;
LK.showGameOver();
}
LK.effects.flashObject(self, 0xff0000, 300);
};
self.heal = function (amount) {
self.health = Math.min(self.maxHealth, self.health + amount);
// Visual healing feedback
tween(self, {
tint: 0x00ff00
}, {
duration: 300,
onFinish: function onFinish() {
tween(self, {
tint: 0xffffff
}, {
duration: 200
});
}
});
};
self.upgrade = function () {
self.weaponLevel++;
self.shootCooldown = Math.max(5, self.shootCooldown - 2);
if (self.weaponLevel >= 5) {
self.shootCooldown = 8;
}
// Visual upgrade feedback
tween(self, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 300,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 200
});
}
});
};
return self;
});
var Zombie = Container.expand(function () {
var self = Container.call(this);
// Create zombie with multiple body parts for better design
var zombieBase = self.attachAsset('zombie', {
anchorX: 0.5,
anchorY: 0.5
});
var zombieBody = self.attachAsset('zombieBody', {
anchorX: 0.5,
anchorY: 0.5,
y: -5
});
var zombieHead = self.attachAsset('zombieHead', {
anchorX: 0.5,
anchorY: 0.5,
y: -25
});
var zombieArms = self.attachAsset('zombieArms', {
anchorX: 0.5,
anchorY: 0.5,
y: -8
});
// Add some visual variety
self.scaleX = 0.8 + Math.random() * 0.4;
self.scaleY = 0.8 + Math.random() * 0.4;
zombieHead.tint = Math.random() > 0.5 ? 0x9acd32 : 0x556b2f;
self.wobbleOffset = Math.random() * Math.PI * 2;
self.health = 50;
self.speed = 1.5;
self.damage = 10;
self.lastAttack = 0;
self.attackCooldown = 60;
self.update = function () {
// Add wobble animation while moving
self.rotation = Math.sin(LK.ticks * 0.1 + self.wobbleOffset) * 0.1;
zombieArms.rotation = Math.sin(LK.ticks * 0.15 + self.wobbleOffset) * 0.3;
if (player) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
if (distance < 50 && LK.ticks - self.lastAttack > self.attackCooldown) {
player.takeDamage(self.damage);
self.lastAttack = LK.ticks;
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xffffff, 100);
return self.health <= 0;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2d1b1b
});
/****
* Game Code
****/
var player;
var zombies = [];
var bullets = [];
var healthPickups = [];
var bombs = [];
var boss = null;
var keys = 0;
var currentWave = 1;
var waveInProgress = false;
var zombiesSpawned = 0;
var zombiesToSpawn = 0;
var spawnTimer = 0;
var healthSpawnTimer = 0;
var moveButton, shootButton, joystickKnob;
var isDragging = false;
var isShootPressed = false;
var joystickRadius = 120;
var joystickCenterX = 200;
var joystickCenterY = 2500;
var currentMoveX = 0;
var currentMoveY = 0;
// UI Elements
var waveText = new Text2('Wave 1', {
size: 80,
fill: 0xFFFFFF
});
waveText.anchor.set(0.5, 0);
LK.gui.top.addChild(waveText);
var healthText = new Text2('Health: 100', {
size: 60,
fill: 0xFF0000
});
healthText.anchor.set(0, 0);
healthText.x = 120;
healthText.y = 20;
LK.gui.topLeft.addChild(healthText);
var keysText = new Text2('Keys: 0', {
size: 60,
fill: 0xFFD700
});
keysText.anchor.set(1, 0);
LK.gui.topRight.addChild(keysText);
var instructionText = new Text2('Survive the zombie waves!\nDefeat bosses to get keys!', {
size: 50,
fill: 0xFFFFFF
});
instructionText.anchor.set(0.5, 0.5);
instructionText.x = 0;
instructionText.y = -200;
LK.gui.center.addChild(instructionText);
// Initialize player
player = new Player();
player.x = 1024;
player.y = 1366;
game.addChild(player);
// Control buttons with enhanced design
moveButton = game.addChild(LK.getAsset('moveButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.6,
scaleY: 1.6
}));
moveButton.x = joystickCenterX;
moveButton.y = joystickCenterY;
moveButton.alpha = 0.4;
// Add joystick knob
joystickKnob = game.addChild(LK.getAsset('joystickKnob', {
anchorX: 0.5,
anchorY: 0.5
}));
joystickKnob.x = joystickCenterX;
joystickKnob.y = joystickCenterY;
joystickKnob.alpha = 0.8;
shootButton = game.addChild(LK.getAsset('shootButton', {
anchorX: 0.5,
anchorY: 0.5
}));
shootButton.x = 1850;
shootButton.y = 2500;
shootButton.alpha = 0.6;
function startWave() {
if (currentWave > 5) {
LK.showYouWin();
return;
}
waveInProgress = true;
zombiesSpawned = 0;
zombiesToSpawn = 5 + currentWave * 3;
spawnTimer = 0;
waveText.setText('Wave ' + currentWave);
instructionText.setText('');
// Spawn bomb for this wave
spawnBombForWave(currentWave);
if (currentWave > 1) {
player.upgrade();
}
}
function spawnZombie() {
var zombie = new Zombie();
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
// Top
zombie.x = Math.random() * 2048;
zombie.y = -30;
break;
case 1:
// Right
zombie.x = 2078;
zombie.y = Math.random() * 2732;
break;
case 2:
// Bottom
zombie.x = Math.random() * 2048;
zombie.y = 2762;
break;
case 3:
// Left
zombie.x = -30;
zombie.y = Math.random() * 2732;
break;
}
zombie.health = 50 + currentWave * 15;
zombie.speed = 1.5 + currentWave * 0.4;
// Make final wave zombies extra tough
if (currentWave === 5) {
zombie.health += 50;
zombie.speed += 0.5;
zombie.damage = 20; // Increased damage for final wave
}
zombies.push(zombie);
game.addChild(zombie);
}
function spawnBoss() {
boss = new Boss(currentWave);
boss.x = 1024;
boss.y = 200;
game.addChild(boss);
}
function shootBullet() {
if (LK.ticks - player.lastShot < player.shootCooldown) return;
var isSuper = player.weaponLevel >= 5;
var bullet = new Bullet(isSuper);
bullet.x = player.x;
bullet.y = player.y;
var nearestEnemy = null;
var minDistance = Infinity;
for (var i = 0; i < zombies.length; i++) {
var distance = Math.sqrt(Math.pow(zombies[i].x - player.x, 2) + Math.pow(zombies[i].y - player.y, 2));
if (distance < minDistance) {
minDistance = distance;
nearestEnemy = zombies[i];
}
}
if (boss) {
var bossDistance = Math.sqrt(Math.pow(boss.x - player.x, 2) + Math.pow(boss.y - player.y, 2));
if (bossDistance < minDistance) {
nearestEnemy = boss;
}
}
if (nearestEnemy) {
var dx = nearestEnemy.x - bullet.x;
var dy = nearestEnemy.y - bullet.y;
var distance = Math.sqrt(dx * dx + dy * dy);
bullet.dx = dx / distance;
bullet.dy = dy / distance;
} else {
bullet.dx = 0;
bullet.dy = -1;
}
bullets.push(bullet);
game.addChild(bullet);
player.lastShot = LK.ticks;
LK.getSound('shoot').play();
}
// Touch controls with smooth analog movement
moveButton.down = function (x, y, obj) {
isDragging = true;
// Visual feedback - make joystick base slightly larger
tween(moveButton, {
scaleX: 1.8,
scaleY: 1.8,
alpha: 0.6
}, {
duration: 150
});
};
shootButton.down = function (x, y, obj) {
isShootPressed = true;
// Visual feedback for shoot button
tween(shootButton, {
scaleX: 0.9,
scaleY: 0.9,
tint: 0xffaa00
}, {
duration: 100
});
shootBullet();
};
shootButton.up = function (x, y, obj) {
isShootPressed = false;
// Return shoot button to normal
tween(shootButton, {
scaleX: 1,
scaleY: 1,
tint: 0xffffff
}, {
duration: 150
});
};
game.move = function (x, y, obj) {
if (isDragging) {
var dx = x - joystickCenterX;
var dy = y - joystickCenterY;
var distance = Math.sqrt(dx * dx + dy * dy);
// Limit knob position within joystick radius
if (distance > joystickRadius) {
dx = dx / distance * joystickRadius;
dy = dy / distance * joystickRadius;
distance = joystickRadius;
}
// Update knob position smoothly
joystickKnob.x = joystickCenterX + dx;
joystickKnob.y = joystickCenterY + dy;
// Calculate movement with smooth analog control
var intensity = distance / joystickRadius;
currentMoveX = dx / joystickRadius * player.speed * intensity;
currentMoveY = dy / joystickRadius * player.speed * intensity;
// Apply smooth movement to player
if (player) {
player.smoothMove(currentMoveX, currentMoveY);
}
}
};
game.up = function (x, y, obj) {
if (isDragging) {
isDragging = false;
currentMoveX = 0;
currentMoveY = 0;
// Return joystick to center with smooth animation
tween(joystickKnob, {
x: joystickCenterX,
y: joystickCenterY
}, {
duration: 200,
easing: tween.easeOut
});
tween(moveButton, {
scaleX: 1.6,
scaleY: 1.6,
alpha: 0.4
}, {
duration: 200
});
}
isShootPressed = false;
};
function spawnHealthPickup() {
var health = new HealthPickup();
health.x = 200 + Math.random() * 1648; // Keep away from edges
health.y = 200 + Math.random() * 2332;
healthPickups.push(health);
game.addChild(health);
}
function spawnBombForWave(wave) {
// Remove existing bomb if any
for (var i = bombs.length - 1; i >= 0; i--) {
bombs[i].destroy();
bombs.splice(i, 1);
}
var bomb = new Bomb();
// Position bomb at different locations based on wave
switch (wave) {
case 1:
bomb.x = 400;
bomb.y = 600;
break;
case 2:
bomb.x = 1600;
bomb.y = 1000;
break;
case 3:
bomb.x = 800;
bomb.y = 2000;
break;
case 4:
bomb.x = 1400;
bomb.y = 1600;
break;
case 5:
bomb.x = 1024;
bomb.y = 1000;
break;
}
bombs.push(bomb);
game.addChild(bomb);
}
function createMapDecorations() {
// Add trees around the map
for (var i = 0; i < 8; i++) {
var tree = new MapDecoration('tree');
tree.x = 100 + Math.random() * 1848;
tree.y = 100 + Math.random() * 2532;
game.addChild(tree);
}
// Add rocks scattered around
for (var i = 0; i < 15; i++) {
var rock = new MapDecoration('rock');
rock.x = 100 + Math.random() * 1848;
rock.y = 100 + Math.random() * 2532;
game.addChild(rock);
}
// Add grass patches
for (var i = 0; i < 25; i++) {
var grass = new MapDecoration('grass');
grass.x = Math.random() * 2048;
grass.y = Math.random() * 2732;
game.addChild(grass);
}
}
// Create map decorations
createMapDecorations();
// Start first wave
startWave();
LK.playMusic('gameMusic');
game.update = function () {
// Update UI
healthText.setText('Health: ' + player.health);
keysText.setText('Keys: ' + keys);
// Update player movement
if (player) {
player.updateMovement();
}
// Spawn health pickups occasionally
healthSpawnTimer++;
if (healthSpawnTimer >= 1800 && healthPickups.length < 2) {
// Every 30 seconds max 2 on map
spawnHealthPickup();
healthSpawnTimer = 0;
}
// Smooth continuous shooting when button held
if (isShootPressed) {
shootBullet();
}
// Spawn zombies during wave
if (waveInProgress && zombiesSpawned < zombiesToSpawn) {
spawnTimer++;
if (spawnTimer >= 60) {
spawnZombie();
zombiesSpawned++;
spawnTimer = 0;
}
}
// Check if wave is complete
if (waveInProgress && zombiesSpawned >= zombiesToSpawn && zombies.length === 0 && !boss) {
spawnBoss();
}
// Bullet collision detection
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
var hit = false;
// Check bomb collisions
for (var k = bombs.length - 1; k >= 0; k--) {
if (bullet.intersects(bombs[k])) {
if (bombs[k].takeDamage(bullet.damage)) {
// Bomb will handle its own destruction and explosion
}
bullet.destroy();
bullets.splice(i, 1);
hit = true;
break;
}
}
if (!hit) {
// Check zombie collisions
for (var j = zombies.length - 1; j >= 0; j--) {
if (bullet.intersects(zombies[j])) {
if (zombies[j].takeDamage(bullet.damage)) {
LK.getSound('zombieDeath').play();
LK.setScore(LK.getScore() + 10);
zombies[j].destroy();
zombies.splice(j, 1);
}
bullet.destroy();
bullets.splice(i, 1);
hit = true;
break;
}
}
}
// Check boss collision
if (!hit && boss && bullet.intersects(boss)) {
if (boss.takeDamage(bullet.damage)) {
LK.getSound('bossDefeat').play();
LK.setScore(LK.getScore() + 100);
// Drop key
var key = new Key();
key.x = boss.x;
key.y = boss.y;
game.addChild(key);
boss.destroy();
boss = null;
waveInProgress = false;
currentWave++;
LK.setTimeout(function () {
if (currentWave <= 5) {
startWave();
} else {
instructionText.setText('Congratulations!\nYou survived all waves!');
LK.setTimeout(function () {
LK.showYouWin();
}, 3000);
}
}, 2000);
}
bullet.destroy();
bullets.splice(i, 1);
}
}
};