/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Bullet class
var Bullet = Container.expand(function () {
var self = Container.call(this);
var bulletSprite = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = bulletSprite.width / 2;
self.speed = 32;
self.dirX = 0;
self.dirY = 0;
self.lastIntersecting = false;
self.lastY = self.y;
return self;
});
// Hero class
var Hero = Container.expand(function () {
var self = Container.call(this);
var heroSprite = self.attachAsset('hero', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = heroSprite.width / 2;
// For powerup state
self.powered = false;
self.powerTimer = 0;
// Flash effect for powerup
self.flashTween = null;
// Powerup visual effect
self.setPowered = function (on) {
if (on) {
self.powered = true;
// Flash hero yellow
if (self.flashTween) tween.stop(heroSprite, {
tint: true
});
heroSprite.tint = 0xffff00;
self.flashTween = tween(heroSprite, {
tint: 0xd83318
}, {
duration: 400,
easing: tween.linear,
onFinish: function onFinish() {
heroSprite.tint = 0xffff00;
self.flashTween = tween(heroSprite, {
tint: 0xd83318
}, {
duration: 400,
easing: tween.linear,
onFinish: function onFinish() {
heroSprite.tint = 0xffff00;
}
});
}
});
} else {
self.powered = false;
if (self.flashTween) tween.stop(heroSprite, {
tint: true
});
heroSprite.tint = 0xd83318;
}
};
return self;
});
// MegaZombie class
var MegaZombie = Container.expand(function () {
var self = Container.call(this);
var megaZombieSprite = self.attachAsset('zombie', {
anchorX: 0.5,
anchorY: 0.5
});
// Make it visually bigger
megaZombieSprite.scaleX = 2;
megaZombieSprite.scaleY = 2;
self.radius = megaZombieSprite.width; // much bigger
self.speed = 1.2; // slower than normal zombies
self.targetX = 0;
self.targetY = 0;
self.alive = true;
self.hp = 8; // takes 8 hits to kill
self.lastIntersecting = false;
self.lastY = self.y;
return self;
});
// Zombie class
var Zombie = Container.expand(function () {
var self = Container.call(this);
var zombieSprite = self.attachAsset('zombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = zombieSprite.width / 2;
self.speed = 2; // Will be set on spawn
self.targetX = 0;
self.targetY = 0;
self.alive = true;
self.lastIntersecting = false;
self.lastY = self.y;
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
// Music
// Sound for shooting
// Powerup: purple ellipse
// Base: blue box
// Bullet: yellow box
// Zombie: green ellipse
// Hero: red box
// Game area
var GAME_W = 2048;
var GAME_H = 2732;
// Base
var base = game.addChild(LK.getAsset('base', {
anchorX: 0.5,
anchorY: 0.5
}));
base.x = GAME_W / 2;
base.y = GAME_H - 120;
// Hero
var hero = new Hero();
game.addChild(hero);
hero.x = GAME_W / 2;
hero.y = GAME_H - 350;
// Score
var score = 0;
var scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Bullet count text (top left, avoid 100x100 area)
var bulletCountTxt = new Text2('20', {
size: 90,
fill: "#fff"
});
bulletCountTxt.anchor.set(0, 0);
LK.gui.top.addChild(bulletCountTxt);
bulletCountTxt.x = 120; // leave 120px margin from left
bulletCountTxt.y = 10; // leave 10px margin from top
// High Score (not persistent, just for session)
var highScore = 0;
// Timer
var elapsedFrames = 0;
var timeTxt = new Text2('00:00', {
size: 70,
fill: "#fff"
});
timeTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(timeTxt);
timeTxt.y = 200;
// Wave
var wave = 1;
var waveTxt = new Text2('Wave 1', {
size: 70,
fill: "#fff"
});
waveTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(waveTxt);
waveTxt.y = 120;
// Arrays for game objects
var zombies = [];
var megaZombies = [];
var bullets = [];
var powerups = [];
// Gold
var gold = 0;
var goldTxt = new Text2('Gold: 0', {
size: 70,
fill: 0xFFE066
});
goldTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(goldTxt);
goldTxt.y = 320;
// Dragging
var dragNode = null;
// Shooting cooldown
var shootCooldown = 0;
var shootInterval = 18; // frames (0.3s at 60fps)
var rapidFire = false;
// Hero refresh after 10 shots
var heroShots = 0;
var heroNeedsRefresh = false;
// Zombie spawn
var zombieSpawnTimer = 0;
var zombieSpawnInterval = 90; // frames (1.5s)
var zombiesPerWave = 6;
var zombiesSpawned = 0;
var zombiesToSpawn = zombiesPerWave;
// Game state
var gameOver = false;
// Start music
LK.playMusic('bgmusic', {
fade: {
start: 0,
end: 1,
duration: 1000
}
});
// Helper: clamp hero inside game area
function clampHero() {
var margin = hero.radius + 20;
if (hero.x < margin) hero.x = margin;
if (hero.x > GAME_W - margin) hero.x = GAME_W - margin;
if (hero.y < margin + 100) hero.y = margin + 100; // avoid top menu
if (hero.y > base.y - hero.radius - 40) hero.y = base.y - hero.radius - 40;
}
// Helper: spawn a zombie at random edge, target base
function spawnZombie() {
// Decide if this should be a mega zombie
var isMega = false;
// Start spawning mega zombies at wave 4, increase chance as wave rises
if (wave >= 4) {
// 10% at wave 4, 20% at wave 6, 30% at wave 8, max 40%
var megaChance = Math.min(0.1 + 0.05 * (wave - 4), 0.4);
if (Math.random() < megaChance) isMega = true;
}
// Random edge: 0=top, 1=left, 2=right
var edge = Math.floor(Math.random() * 3);
var zx, zy;
if (edge === 0) {
// top
zx = 200 + Math.random() * (GAME_W - 400);
zy = -80;
} else if (edge === 1) {
// left
zx = -80;
zy = 400 + Math.random() * (GAME_H - 1000);
} else {
// right
zx = GAME_W + 80;
zy = 400 + Math.random() * (GAME_H - 1000);
}
if (isMega) {
var mz = new MegaZombie();
mz.x = zx;
mz.y = zy;
mz.targetX = base.x;
mz.targetY = base.y;
// Slightly increase speed with wave, but always slower than normal zombies
mz.speed = 1.2 + (wave - 4) * 0.1 + Math.random() * 0.2;
megaZombies.push(mz);
game.addChild(mz);
} else {
var z = new Zombie();
z.x = zx;
z.y = zy;
z.targetX = base.x;
z.targetY = base.y;
z.speed = 2 + wave * 0.5 + Math.random();
zombies.push(z);
game.addChild(z);
}
}
// Helper: shoot bullet towards (tx, ty)
function shootBullet(tx, ty) {
if (shootCooldown > 0) return;
if (heroNeedsRefresh) return;
// Count shots and check for refresh
heroShots++;
bulletCountTxt.setText(20 - heroShots); // update bullet count display
if (heroShots >= 20) {
heroNeedsRefresh = true;
// Optionally, flash hero to indicate refresh needed
LK.effects.flashObject(hero, 0x888888, 800);
// Automatically refresh after 20 shots for şampiyon
LK.setTimeout(function () {
heroNeedsRefresh = false;
heroShots = 0;
bulletCountTxt.setText(20); // reset bullet count display after refresh
}, 800); // 800ms matches the flash duration
return;
}
var b = new Bullet();
b.x = hero.x;
b.y = hero.y;
// Direction
var dx = tx - hero.x;
var dy = ty - hero.y;
var len = Math.sqrt(dx * dx + dy * dy);
if (len === 0) {
b.dirX = 0;
b.dirY = -1;
} else {
b.dirX = dx / len;
b.dirY = dy / len;
}
// If powered, increase speed
b.speed = hero.powered ? 48 : 32;
bullets.push(b);
game.addChild(b);
shootCooldown = hero.powered ? 6 : shootInterval;
// Play shoot sound every time a bullet is fired
LK.getSound('shoot').play();
}
// Helper: start new wave
function startWave() {
waveTxt.setText('Wave ' + wave);
zombiesPerWave = 6 + (wave - 1) * 2;
zombiesToSpawn = zombiesPerWave;
zombiesSpawned = 0;
zombieSpawnInterval = Math.max(40, 90 - wave * 5);
// Clean up any remaining mega zombies (shouldn't be any, but for safety)
for (var i = megaZombies.length - 1; i >= 0; i--) {
if (megaZombies[i]) {
megaZombies[i].destroy();
}
}
megaZombies = [];
}
// Move handler: drag hero, shoot on tap
function handleMove(x, y, obj) {
// Drag hero if dragNode is set
if (dragNode === hero) {
hero.x = x;
hero.y = y;
clampHero();
// Reset hero refresh after dragging
if (heroNeedsRefresh) {
heroNeedsRefresh = false;
heroShots = 0;
bulletCountTxt.setText(20); // reset bullet count display after drag refresh
}
}
}
// Down: start dragging hero or shoot if tap on zombie
game.down = function (x, y, obj) {
// Check if tap on zombie
for (var i = 0; i < zombies.length; i++) {
var z = zombies[i];
var dx = x - z.x;
var dy = y - z.y;
if (dx * dx + dy * dy < z.radius * z.radius) {
// Shoot at zombie
shootBullet(z.x, z.y);
return;
}
}
// Otherwise, drag hero
var dxh = x - hero.x;
var dyh = y - hero.y;
if (dxh * dxh + dyh * dyh < hero.radius * hero.radius) {
dragNode = hero;
}
// Also allow shooting by tapping anywhere else
if (!dragNode) {
shootBullet(x, y);
}
handleMove(x, y, obj);
};
game.move = handleMove;
game.up = function (x, y, obj) {
dragNode = null;
};
// Main update loop
game.update = function () {
if (gameOver) return;
// Timer update
elapsedFrames++;
var seconds = Math.floor(elapsedFrames / 60);
var min = Math.floor(seconds / 60);
var sec = seconds % 60;
var timeStr = (min < 10 ? "0" : "") + min + ":" + (sec < 10 ? "0" : "") + sec;
timeTxt.setText(timeStr);
// Every 20 seconds, increase zombies per wave and make them spawn faster
if (elapsedFrames % (60 * 20) === 0 && elapsedFrames > 0) {
zombiesPerWave += 2;
zombieSpawnInterval = Math.max(20, zombieSpawnInterval - 5);
}
// Cooldowns
if (shootCooldown > 0) shootCooldown--;
// Powerup timer
if (hero.powered) {
hero.powerTimer--;
if (hero.powerTimer <= 0) {
hero.setPowered(false);
}
}
// Zombie spawn
if (zombiesToSpawn > 0) {
zombieSpawnTimer++;
if (zombieSpawnTimer >= zombieSpawnInterval) {
zombieSpawnTimer = 0;
spawnZombie();
zombiesToSpawn--;
zombiesSpawned++;
}
} else if (zombies.length === 0) {
// Next wave
if (wave >= 10) {
// End game at wave 10
LK.effects.flashScreen(0x00ff00, 1000);
LK.showYouWin();
gameOver = true;
return;
}
wave++;
startWave();
waveTxt.setText('Wave ' + wave + (wave >= 4 ? ' - Mega Zombies!' : ''));
}
// Update bullets
for (var i = bullets.length - 1; i >= 0; i--) {
var b = bullets[i];
b.x += b.dirX * b.speed;
b.y += b.dirY * b.speed;
// Remove if off screen
if (b.x < -100 || b.x > GAME_W + 100 || b.y < -100 || b.y > GAME_H + 100) {
b.destroy();
bullets.splice(i, 1);
continue;
}
// Check collision with zombies
var bulletDestroyed = false;
for (var j = zombies.length - 1; j >= 0; j--) {
var z = zombies[j];
var dx = b.x - z.x;
var dy = b.y - z.y;
var dist2 = dx * dx + dy * dy;
var r2 = (b.radius + z.radius) * (b.radius + z.radius);
if (dist2 < r2) {
// Hit!
LK.getSound('zombiehit').play();
score++;
scoreTxt.setText(score);
if (score > highScore) highScore = score;
// Earn gold for killing zombie
gold += 5;
goldTxt.setText('Gold: ' + gold);
// Remove zombie and bullet
z.destroy();
zombies.splice(j, 1);
b.destroy();
bullets.splice(i, 1);
bulletDestroyed = true;
break;
}
}
if (bulletDestroyed) continue;
// Check collision with mega zombies
for (var j = megaZombies.length - 1; j >= 0; j--) {
var mz = megaZombies[j];
var dx = b.x - mz.x;
var dy = b.y - mz.y;
var dist2 = dx * dx + dy * dy;
var r2 = (b.radius + mz.radius) * (b.radius + mz.radius);
if (dist2 < r2) {
// Hit mega zombie!
LK.getSound('zombiehit').play();
mz.hp--;
// Flash mega zombie on hit
LK.effects.flashObject(mz, 0xffff00, 200);
if (mz.hp <= 0) {
// Kill mega zombie
score += 5; // Worth more points
scoreTxt.setText(score);
if (score > highScore) highScore = score;
gold += 25; // More gold
goldTxt.setText('Gold: ' + gold);
mz.destroy();
megaZombies.splice(j, 1);
}
// Destroy bullet
b.destroy();
bullets.splice(i, 1);
break;
}
}
}
// Update zombies
for (var i = zombies.length - 1; i >= 0; i--) {
var z = zombies[i];
// Move towards base
var dx = base.x - z.x;
var dy = base.y - z.y;
var len = Math.sqrt(dx * dx + dy * dy);
if (len > 0) {
z.x += dx / len * z.speed;
z.y += dy / len * z.speed;
}
// Check collision with base
var dbx = z.x - base.x;
var dby = z.y - base.y;
var baseR = base.width / 2 + z.radius - 10;
if (dbx * dbx + dby * dby < baseR * baseR) {
// Game over
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
gameOver = true;
return;
}
// Check collision with hero (for fun, just flash)
var dhx = z.x - hero.x;
var dhy = z.y - hero.y;
var heroR = hero.radius + z.radius;
if (dhx * dhx + dhy * dhy < heroR * heroR) {
LK.effects.flashObject(hero, 0xff0000, 300);
}
}
// Update mega zombies
for (var i = megaZombies.length - 1; i >= 0; i--) {
var mz = megaZombies[i];
// Move towards base
var dx = base.x - mz.x;
var dy = base.y - mz.y;
var len = Math.sqrt(dx * dx + dy * dy);
if (len > 0) {
mz.x += dx / len * mz.speed;
mz.y += dy / len * mz.speed;
}
// Check collision with base
var dbx = mz.x - base.x;
var dby = mz.y - base.y;
var baseR = base.width / 2 + mz.radius - 10;
if (dbx * dbx + dby * dby < baseR * baseR) {
// Game over
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
gameOver = true;
return;
}
// Check collision with hero (for fun, just flash)
var dhx = mz.x - hero.x;
var dhy = mz.y - hero.y;
var heroR = hero.radius + mz.radius;
if (dhx * dhx + dhy * dhy < heroR * heroR) {
LK.effects.flashObject(hero, 0xff0000, 300);
}
}
};
// Start first wave
startWave(); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Bullet class
var Bullet = Container.expand(function () {
var self = Container.call(this);
var bulletSprite = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = bulletSprite.width / 2;
self.speed = 32;
self.dirX = 0;
self.dirY = 0;
self.lastIntersecting = false;
self.lastY = self.y;
return self;
});
// Hero class
var Hero = Container.expand(function () {
var self = Container.call(this);
var heroSprite = self.attachAsset('hero', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = heroSprite.width / 2;
// For powerup state
self.powered = false;
self.powerTimer = 0;
// Flash effect for powerup
self.flashTween = null;
// Powerup visual effect
self.setPowered = function (on) {
if (on) {
self.powered = true;
// Flash hero yellow
if (self.flashTween) tween.stop(heroSprite, {
tint: true
});
heroSprite.tint = 0xffff00;
self.flashTween = tween(heroSprite, {
tint: 0xd83318
}, {
duration: 400,
easing: tween.linear,
onFinish: function onFinish() {
heroSprite.tint = 0xffff00;
self.flashTween = tween(heroSprite, {
tint: 0xd83318
}, {
duration: 400,
easing: tween.linear,
onFinish: function onFinish() {
heroSprite.tint = 0xffff00;
}
});
}
});
} else {
self.powered = false;
if (self.flashTween) tween.stop(heroSprite, {
tint: true
});
heroSprite.tint = 0xd83318;
}
};
return self;
});
// MegaZombie class
var MegaZombie = Container.expand(function () {
var self = Container.call(this);
var megaZombieSprite = self.attachAsset('zombie', {
anchorX: 0.5,
anchorY: 0.5
});
// Make it visually bigger
megaZombieSprite.scaleX = 2;
megaZombieSprite.scaleY = 2;
self.radius = megaZombieSprite.width; // much bigger
self.speed = 1.2; // slower than normal zombies
self.targetX = 0;
self.targetY = 0;
self.alive = true;
self.hp = 8; // takes 8 hits to kill
self.lastIntersecting = false;
self.lastY = self.y;
return self;
});
// Zombie class
var Zombie = Container.expand(function () {
var self = Container.call(this);
var zombieSprite = self.attachAsset('zombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = zombieSprite.width / 2;
self.speed = 2; // Will be set on spawn
self.targetX = 0;
self.targetY = 0;
self.alive = true;
self.lastIntersecting = false;
self.lastY = self.y;
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
// Music
// Sound for shooting
// Powerup: purple ellipse
// Base: blue box
// Bullet: yellow box
// Zombie: green ellipse
// Hero: red box
// Game area
var GAME_W = 2048;
var GAME_H = 2732;
// Base
var base = game.addChild(LK.getAsset('base', {
anchorX: 0.5,
anchorY: 0.5
}));
base.x = GAME_W / 2;
base.y = GAME_H - 120;
// Hero
var hero = new Hero();
game.addChild(hero);
hero.x = GAME_W / 2;
hero.y = GAME_H - 350;
// Score
var score = 0;
var scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Bullet count text (top left, avoid 100x100 area)
var bulletCountTxt = new Text2('20', {
size: 90,
fill: "#fff"
});
bulletCountTxt.anchor.set(0, 0);
LK.gui.top.addChild(bulletCountTxt);
bulletCountTxt.x = 120; // leave 120px margin from left
bulletCountTxt.y = 10; // leave 10px margin from top
// High Score (not persistent, just for session)
var highScore = 0;
// Timer
var elapsedFrames = 0;
var timeTxt = new Text2('00:00', {
size: 70,
fill: "#fff"
});
timeTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(timeTxt);
timeTxt.y = 200;
// Wave
var wave = 1;
var waveTxt = new Text2('Wave 1', {
size: 70,
fill: "#fff"
});
waveTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(waveTxt);
waveTxt.y = 120;
// Arrays for game objects
var zombies = [];
var megaZombies = [];
var bullets = [];
var powerups = [];
// Gold
var gold = 0;
var goldTxt = new Text2('Gold: 0', {
size: 70,
fill: 0xFFE066
});
goldTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(goldTxt);
goldTxt.y = 320;
// Dragging
var dragNode = null;
// Shooting cooldown
var shootCooldown = 0;
var shootInterval = 18; // frames (0.3s at 60fps)
var rapidFire = false;
// Hero refresh after 10 shots
var heroShots = 0;
var heroNeedsRefresh = false;
// Zombie spawn
var zombieSpawnTimer = 0;
var zombieSpawnInterval = 90; // frames (1.5s)
var zombiesPerWave = 6;
var zombiesSpawned = 0;
var zombiesToSpawn = zombiesPerWave;
// Game state
var gameOver = false;
// Start music
LK.playMusic('bgmusic', {
fade: {
start: 0,
end: 1,
duration: 1000
}
});
// Helper: clamp hero inside game area
function clampHero() {
var margin = hero.radius + 20;
if (hero.x < margin) hero.x = margin;
if (hero.x > GAME_W - margin) hero.x = GAME_W - margin;
if (hero.y < margin + 100) hero.y = margin + 100; // avoid top menu
if (hero.y > base.y - hero.radius - 40) hero.y = base.y - hero.radius - 40;
}
// Helper: spawn a zombie at random edge, target base
function spawnZombie() {
// Decide if this should be a mega zombie
var isMega = false;
// Start spawning mega zombies at wave 4, increase chance as wave rises
if (wave >= 4) {
// 10% at wave 4, 20% at wave 6, 30% at wave 8, max 40%
var megaChance = Math.min(0.1 + 0.05 * (wave - 4), 0.4);
if (Math.random() < megaChance) isMega = true;
}
// Random edge: 0=top, 1=left, 2=right
var edge = Math.floor(Math.random() * 3);
var zx, zy;
if (edge === 0) {
// top
zx = 200 + Math.random() * (GAME_W - 400);
zy = -80;
} else if (edge === 1) {
// left
zx = -80;
zy = 400 + Math.random() * (GAME_H - 1000);
} else {
// right
zx = GAME_W + 80;
zy = 400 + Math.random() * (GAME_H - 1000);
}
if (isMega) {
var mz = new MegaZombie();
mz.x = zx;
mz.y = zy;
mz.targetX = base.x;
mz.targetY = base.y;
// Slightly increase speed with wave, but always slower than normal zombies
mz.speed = 1.2 + (wave - 4) * 0.1 + Math.random() * 0.2;
megaZombies.push(mz);
game.addChild(mz);
} else {
var z = new Zombie();
z.x = zx;
z.y = zy;
z.targetX = base.x;
z.targetY = base.y;
z.speed = 2 + wave * 0.5 + Math.random();
zombies.push(z);
game.addChild(z);
}
}
// Helper: shoot bullet towards (tx, ty)
function shootBullet(tx, ty) {
if (shootCooldown > 0) return;
if (heroNeedsRefresh) return;
// Count shots and check for refresh
heroShots++;
bulletCountTxt.setText(20 - heroShots); // update bullet count display
if (heroShots >= 20) {
heroNeedsRefresh = true;
// Optionally, flash hero to indicate refresh needed
LK.effects.flashObject(hero, 0x888888, 800);
// Automatically refresh after 20 shots for şampiyon
LK.setTimeout(function () {
heroNeedsRefresh = false;
heroShots = 0;
bulletCountTxt.setText(20); // reset bullet count display after refresh
}, 800); // 800ms matches the flash duration
return;
}
var b = new Bullet();
b.x = hero.x;
b.y = hero.y;
// Direction
var dx = tx - hero.x;
var dy = ty - hero.y;
var len = Math.sqrt(dx * dx + dy * dy);
if (len === 0) {
b.dirX = 0;
b.dirY = -1;
} else {
b.dirX = dx / len;
b.dirY = dy / len;
}
// If powered, increase speed
b.speed = hero.powered ? 48 : 32;
bullets.push(b);
game.addChild(b);
shootCooldown = hero.powered ? 6 : shootInterval;
// Play shoot sound every time a bullet is fired
LK.getSound('shoot').play();
}
// Helper: start new wave
function startWave() {
waveTxt.setText('Wave ' + wave);
zombiesPerWave = 6 + (wave - 1) * 2;
zombiesToSpawn = zombiesPerWave;
zombiesSpawned = 0;
zombieSpawnInterval = Math.max(40, 90 - wave * 5);
// Clean up any remaining mega zombies (shouldn't be any, but for safety)
for (var i = megaZombies.length - 1; i >= 0; i--) {
if (megaZombies[i]) {
megaZombies[i].destroy();
}
}
megaZombies = [];
}
// Move handler: drag hero, shoot on tap
function handleMove(x, y, obj) {
// Drag hero if dragNode is set
if (dragNode === hero) {
hero.x = x;
hero.y = y;
clampHero();
// Reset hero refresh after dragging
if (heroNeedsRefresh) {
heroNeedsRefresh = false;
heroShots = 0;
bulletCountTxt.setText(20); // reset bullet count display after drag refresh
}
}
}
// Down: start dragging hero or shoot if tap on zombie
game.down = function (x, y, obj) {
// Check if tap on zombie
for (var i = 0; i < zombies.length; i++) {
var z = zombies[i];
var dx = x - z.x;
var dy = y - z.y;
if (dx * dx + dy * dy < z.radius * z.radius) {
// Shoot at zombie
shootBullet(z.x, z.y);
return;
}
}
// Otherwise, drag hero
var dxh = x - hero.x;
var dyh = y - hero.y;
if (dxh * dxh + dyh * dyh < hero.radius * hero.radius) {
dragNode = hero;
}
// Also allow shooting by tapping anywhere else
if (!dragNode) {
shootBullet(x, y);
}
handleMove(x, y, obj);
};
game.move = handleMove;
game.up = function (x, y, obj) {
dragNode = null;
};
// Main update loop
game.update = function () {
if (gameOver) return;
// Timer update
elapsedFrames++;
var seconds = Math.floor(elapsedFrames / 60);
var min = Math.floor(seconds / 60);
var sec = seconds % 60;
var timeStr = (min < 10 ? "0" : "") + min + ":" + (sec < 10 ? "0" : "") + sec;
timeTxt.setText(timeStr);
// Every 20 seconds, increase zombies per wave and make them spawn faster
if (elapsedFrames % (60 * 20) === 0 && elapsedFrames > 0) {
zombiesPerWave += 2;
zombieSpawnInterval = Math.max(20, zombieSpawnInterval - 5);
}
// Cooldowns
if (shootCooldown > 0) shootCooldown--;
// Powerup timer
if (hero.powered) {
hero.powerTimer--;
if (hero.powerTimer <= 0) {
hero.setPowered(false);
}
}
// Zombie spawn
if (zombiesToSpawn > 0) {
zombieSpawnTimer++;
if (zombieSpawnTimer >= zombieSpawnInterval) {
zombieSpawnTimer = 0;
spawnZombie();
zombiesToSpawn--;
zombiesSpawned++;
}
} else if (zombies.length === 0) {
// Next wave
if (wave >= 10) {
// End game at wave 10
LK.effects.flashScreen(0x00ff00, 1000);
LK.showYouWin();
gameOver = true;
return;
}
wave++;
startWave();
waveTxt.setText('Wave ' + wave + (wave >= 4 ? ' - Mega Zombies!' : ''));
}
// Update bullets
for (var i = bullets.length - 1; i >= 0; i--) {
var b = bullets[i];
b.x += b.dirX * b.speed;
b.y += b.dirY * b.speed;
// Remove if off screen
if (b.x < -100 || b.x > GAME_W + 100 || b.y < -100 || b.y > GAME_H + 100) {
b.destroy();
bullets.splice(i, 1);
continue;
}
// Check collision with zombies
var bulletDestroyed = false;
for (var j = zombies.length - 1; j >= 0; j--) {
var z = zombies[j];
var dx = b.x - z.x;
var dy = b.y - z.y;
var dist2 = dx * dx + dy * dy;
var r2 = (b.radius + z.radius) * (b.radius + z.radius);
if (dist2 < r2) {
// Hit!
LK.getSound('zombiehit').play();
score++;
scoreTxt.setText(score);
if (score > highScore) highScore = score;
// Earn gold for killing zombie
gold += 5;
goldTxt.setText('Gold: ' + gold);
// Remove zombie and bullet
z.destroy();
zombies.splice(j, 1);
b.destroy();
bullets.splice(i, 1);
bulletDestroyed = true;
break;
}
}
if (bulletDestroyed) continue;
// Check collision with mega zombies
for (var j = megaZombies.length - 1; j >= 0; j--) {
var mz = megaZombies[j];
var dx = b.x - mz.x;
var dy = b.y - mz.y;
var dist2 = dx * dx + dy * dy;
var r2 = (b.radius + mz.radius) * (b.radius + mz.radius);
if (dist2 < r2) {
// Hit mega zombie!
LK.getSound('zombiehit').play();
mz.hp--;
// Flash mega zombie on hit
LK.effects.flashObject(mz, 0xffff00, 200);
if (mz.hp <= 0) {
// Kill mega zombie
score += 5; // Worth more points
scoreTxt.setText(score);
if (score > highScore) highScore = score;
gold += 25; // More gold
goldTxt.setText('Gold: ' + gold);
mz.destroy();
megaZombies.splice(j, 1);
}
// Destroy bullet
b.destroy();
bullets.splice(i, 1);
break;
}
}
}
// Update zombies
for (var i = zombies.length - 1; i >= 0; i--) {
var z = zombies[i];
// Move towards base
var dx = base.x - z.x;
var dy = base.y - z.y;
var len = Math.sqrt(dx * dx + dy * dy);
if (len > 0) {
z.x += dx / len * z.speed;
z.y += dy / len * z.speed;
}
// Check collision with base
var dbx = z.x - base.x;
var dby = z.y - base.y;
var baseR = base.width / 2 + z.radius - 10;
if (dbx * dbx + dby * dby < baseR * baseR) {
// Game over
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
gameOver = true;
return;
}
// Check collision with hero (for fun, just flash)
var dhx = z.x - hero.x;
var dhy = z.y - hero.y;
var heroR = hero.radius + z.radius;
if (dhx * dhx + dhy * dhy < heroR * heroR) {
LK.effects.flashObject(hero, 0xff0000, 300);
}
}
// Update mega zombies
for (var i = megaZombies.length - 1; i >= 0; i--) {
var mz = megaZombies[i];
// Move towards base
var dx = base.x - mz.x;
var dy = base.y - mz.y;
var len = Math.sqrt(dx * dx + dy * dy);
if (len > 0) {
mz.x += dx / len * mz.speed;
mz.y += dy / len * mz.speed;
}
// Check collision with base
var dbx = mz.x - base.x;
var dby = mz.y - base.y;
var baseR = base.width / 2 + mz.radius - 10;
if (dbx * dbx + dby * dby < baseR * baseR) {
// Game over
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
gameOver = true;
return;
}
// Check collision with hero (for fun, just flash)
var dhx = mz.x - hero.x;
var dhy = mz.y - hero.y;
var heroR = hero.radius + mz.radius;
if (dhx * dhx + dhy * dhy < heroR * heroR) {
LK.effects.flashObject(hero, 0xff0000, 300);
}
}
};
// Start first wave
startWave();
zombie. In-Game asset. 2d. High contrast. No shadows
straight line. In-Game asset. 2d. High contrast. No shadows
Create a game character with a crossbow in hand. In-Game asset. 2d. High contrast. No shadows
purple potion bottle filled with purple liquid. In-Game asset. 2d. High contrast. No shadows
mermi. In-Game asset. 2d. High contrast. No shadows