/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// BossZombie class
var BossZombie = Container.expand(function () {
var self = Container.call(this);
var bossSprite = self.attachAsset('zombie', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.7,
scaleY: 1.7,
color: 0x8e44ad // purple tint for boss
});
self.radius = bossSprite.width / 2;
var bossPowerLevel = Math.floor((currentWave - 1) / 3);
self.speed = 1.5 + bossPowerLevel * 0.5;
self.hp = 10 + bossPowerLevel * 5;
self.maxHp = self.hp;
self.target = null;
self.lastHitTick = 0;
self.update = function () {
// Move towards player
var tx = player.x;
var ty = player.y;
// If wall/trap/tower is closer, target that
var minDist = Math.sqrt((self.x - tx) * (self.x - tx) + (self.y - ty) * (self.y - ty));
var targetObj = player;
for (var i = 0; i < defenses.length; i++) {
var d = defenses[i];
var dist = Math.sqrt((self.x - d.x) * (self.x - d.x) + (self.y - d.y) * (self.y - d.y));
if (dist < minDist) {
minDist = dist;
targetObj = d;
}
}
self.target = targetObj;
var dx = targetObj.x - self.x;
var dy = targetObj.y - self.y;
var len = Math.sqrt(dx * dx + dy * dy);
if (len > 0) {
dx /= len;
dy /= len;
self.x += dx * self.speed;
self.y += dy * self.speed;
}
// Attack if close
if (len < self.radius + targetObj.radius + 10) {
if (LK.ticks - self.lastHitTick > 30) {
if (targetObj === player) {
player.takeDamage(2); // Boss does more damage
} else if (targetObj.takeDamage) {
targetObj.takeDamage(2);
}
self.lastHitTick = LK.ticks;
}
}
};
self.takeDamage = function (amount) {
self.hp -= amount;
if (self.hp <= 0) {
self.die();
} else {
LK.effects.flashObject(self, 0xff00ff, 300);
}
};
self.die = function () {
LK.getSound('zombie_die').play();
// Drop 5 coins
var coinCount = 5;
if (typeof coinMultiplierEnabled !== "undefined" && coinMultiplierEnabled) {
coinCount = Math.round(5 * 1.5);
}
for (var i = 0; i < coinCount; i++) {
var coin = new Coin();
// Spread coins a bit
var angle = Math.PI * 2 * i / coinCount;
coin.x = self.x + Math.cos(angle) * 60;
coin.y = self.y + Math.sin(angle) * 60;
coins.push(coin);
game.addChild(coin);
}
self.destroy();
var idx = zombies.indexOf(self);
if (idx !== -1) zombies.splice(idx, 1);
zombiesKilledThisWave++;
};
return self;
});
// 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.vx = 0;
self.vy = 0;
self.damage = 1;
self.update = function () {
self.x += self.vx;
self.y += self.vy;
};
return self;
});
// Coin class
var Coin = Container.expand(function () {
var self = Container.call(this);
var coinSprite = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = coinSprite.width * 1.5; // Even further increase hitbox radius for much easier collection
// Remove vy and falling logic so coin stays at spawn position
self.collected = false;
self.update = function () {
// No falling, coin stays at its spawn position
};
self.collect = function () {
if (self.collected) return;
self.collected = true;
LK.getSound('coin').play();
// Animate to coinText
var guiPos = LK.gui.topRight.toLocal(game.toGlobal({
x: self.x,
y: self.y
}));
var startX = self.x,
startY = self.y;
var endX = 2048 - 200,
endY = 80;
tween(self, {
x: endX,
y: endY,
alpha: 0
}, {
duration: 400,
easing: tween.cubicIn,
onFinish: function onFinish() {
self.destroy();
var idx = coins.indexOf(self);
if (idx !== -1) coins.splice(idx, 1);
if (typeof coinMultiplierLevel !== "undefined" && coinMultiplierLevel > 0) {
coinsCollected += Math.pow(2, coinMultiplierLevel);
} else {
coinsCollected++;
}
coinsCollected = Math.floor(coinsCollected);
updateCoinText();
}
});
};
return self;
});
// Player class
var Player = Container.expand(function () {
var self = Container.call(this);
var playerSprite = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = playerSprite.width / 2;
self.hp = 10;
self.maxHp = 10;
self.shootCooldown = 0;
self.shootDelay = 20; // frames
self.weaponLevel = 1;
self.lastShotTick = 0;
// For drag
self.down = function (x, y, obj) {};
self.up = function (x, y, obj) {};
self.update = function () {
if (self.shootCooldown > 0) self.shootCooldown--;
};
// Shoot method
self.shoot = function (targetX, targetY) {
if (self.shootCooldown > 0) return;
var dx = targetX - self.x;
var dy = targetY - self.y;
var len = Math.sqrt(dx * dx + dy * dy);
if (len === 0) return;
dx /= len;
dy /= len;
var bullet = new Bullet();
bullet.x = self.x + dx * (self.radius + 30);
bullet.y = self.y + dy * (self.radius + 30);
bullet.vx = dx * (22 + self.weaponLevel * 2);
bullet.vy = dy * (22 + self.weaponLevel * 2);
bullet.damage = self.weaponLevel;
// Set bullet rotation so the tip faces the direction of movement
if (bullet.children && bullet.children.length > 0) {
bullet.children[0].rotation = Math.atan2(bullet.vy, bullet.vx);
}
bullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
self.shootCooldown = self.shootDelay;
};
// Take damage
self.takeDamage = function (amount) {
self.hp -= amount;
if (self.hp < 0) self.hp = 0;
LK.effects.flashObject(self, 0xff0000, 300);
updateHpBar();
if (self.hp <= 0) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
}
};
return self;
});
// Tower class
var Tower = Container.expand(function () {
var self = Container.call(this);
var towerSprite = self.attachAsset('tower', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = Math.max(towerSprite.width, towerSprite.height) / 2;
self.hp = 20;
self.maxHp = 20;
self.shootCooldown = 0;
self.shootDelay = 40;
self.takeDamage = function (amount) {
self.hp -= amount;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.hp <= 0) {
self.destroy();
var idx = defenses.indexOf(self);
if (idx !== -1) defenses.splice(idx, 1);
}
};
self.update = function () {
if (self.shootCooldown > 0) self.shootCooldown--;
// Find nearest zombie
var nearest = null,
minDist = 99999;
for (var i = 0; i < zombies.length; i++) {
var z = zombies[i];
var dx = z.x - self.x;
var dy = z.y - self.y;
var dist = dx * dx + dy * dy;
if (dist < minDist) {
minDist = dist;
nearest = z;
}
}
if (nearest && self.shootCooldown <= 0) {
// Shoot at nearest zombie
var dx = nearest.x - self.x;
var dy = nearest.y - self.y;
var len = Math.sqrt(dx * dx + dy * dy);
if (len > 0) {
dx /= len;
dy /= len;
var bullet = new Bullet();
bullet.x = self.x + dx * (self.radius + 20);
bullet.y = self.y + dy * (self.radius + 20);
var towerWeaponLevel = typeof self.weaponLevel !== "undefined" ? self.weaponLevel : 1;
bullet.vx = dx * (18 + towerWeaponLevel * 2);
bullet.vy = dy * (18 + towerWeaponLevel * 2);
bullet.damage = towerWeaponLevel;
// Set bullet rotation so the tip faces the direction of movement
if (bullet.children && bullet.children.length > 0) {
bullet.children[0].rotation = Math.atan2(bullet.vy, bullet.vx);
}
bullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
self.shootCooldown = self.shootDelay;
}
}
};
return self;
});
// Trap class
var Trap = Container.expand(function () {
var self = Container.call(this);
var trapSprite = self.attachAsset('trap', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = Math.max(trapSprite.width, trapSprite.height) / 2;
self.hp = 2;
self.maxHp = 2;
self.takeDamage = function (amount) {
self.hp -= amount;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.hp <= 0) {
self.destroy();
var idx = defenses.indexOf(self);
if (idx !== -1) defenses.splice(idx, 1);
}
};
return self;
});
// Wall class
var Wall = Container.expand(function () {
var self = Container.call(this);
var wallSprite = self.attachAsset('wall', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = Math.max(wallSprite.width, wallSprite.height) / 2;
self.hp = 6;
self.maxHp = 6;
self.takeDamage = function (amount) {
self.hp -= amount;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.hp <= 0) {
self.destroy();
var idx = defenses.indexOf(self);
if (idx !== -1) defenses.splice(idx, 1);
}
};
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;
// Zombies get stronger only every 3 waves
var zombiePowerLevel = Math.floor((currentWave - 1) / 3);
self.speed = 2 + zombiePowerLevel * 0.9;
self.hp = 1 + zombiePowerLevel;
self.maxHp = self.hp;
self.target = null;
self.lastHitTick = 0;
self.update = function () {
// Move towards player
var tx = player.x;
var ty = player.y;
// If wall/trap/tower is closer, target that
var minDist = Math.sqrt((self.x - tx) * (self.x - tx) + (self.y - ty) * (self.y - ty));
var targetObj = player;
for (var i = 0; i < defenses.length; i++) {
var d = defenses[i];
var dist = Math.sqrt((self.x - d.x) * (self.x - d.x) + (self.y - d.y) * (self.y - d.y));
if (dist < minDist) {
minDist = dist;
targetObj = d;
}
}
self.target = targetObj;
var dx = targetObj.x - self.x;
var dy = targetObj.y - self.y;
var len = Math.sqrt(dx * dx + dy * dy);
if (len > 0) {
dx /= len;
dy /= len;
self.x += dx * self.speed;
self.y += dy * self.speed;
}
// Attack if close
if (len < self.radius + targetObj.radius + 10) {
if (LK.ticks - self.lastHitTick > 30) {
if (targetObj === player) {
player.takeDamage(1);
} else if (targetObj.takeDamage) {
targetObj.takeDamage(1);
}
self.lastHitTick = LK.ticks;
}
}
};
self.takeDamage = function (amount) {
self.hp -= amount;
if (self.hp <= 0) {
self.die();
} else {
LK.effects.flashObject(self, 0xff0000, 200);
}
};
self.die = function () {
LK.getSound('zombie_die').play();
// Drop coin
var coin = new Coin();
coin.x = self.x;
coin.y = self.y;
coins.push(coin);
game.addChild(coin);
self.destroy();
var idx = zombies.indexOf(self);
if (idx !== -1) zombies.splice(idx, 1);
zombiesKilledThisWave++;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Sounds
// Tower
// Trap
// Wall
// Coin
// Bullet
// Zombie
// Character (player)
// Global variables
var player;
var zombies = [];
var bullets = [];
var coins = [];
var defenses = [];
var coinsCollected = 0;
var autoCollectEnabled = false;
var coinMultiplierEnabled = false;
var coinMultiplier2Enabled = false;
var currentWave = 1;
var zombiesToSpawn = 0;
var zombiesSpawnedThisWave = 0;
var zombiesKilledThisWave = 0;
var waveInProgress = false;
var dragNode = null;
var buildMode = null; // 'wall', 'trap', 'tower', 'upgrade', 'autocollect', 'coinmult'
var buildPreview = null;
var buildCost = {
wall: 5,
trap: 7,
tower: 30,
upgrade: 10,
autocollect: 20,
coinmult: 50
};
var buildNames = {
wall: "Duvar",
trap: "Tuzak",
tower: "Kule",
upgrade: "Silah+",
autocollect: "Oto-Para",
coinmult: "Para x2"
};
// Track current coin multiplier and its cost
var coinMultiplierLevel = 1; // 1 means 2x, 2 means 4x, etc.
var coinMultiplierCost = 100; // Cost for next multiplier (starts at 100 for 2x)
// buildDesc removed
var hpBar,
coinText,
waveText,
buildPanel,
buildButtons = [];
var lastTouchX = 0,
lastTouchY = 0;
// UI: HP Bar
function updateHpBar() {
if (!hpBar) return;
var txt = "";
for (var i = 0; i < player.maxHp; i++) {
txt += i < player.hp ? "♥ " : "♡ ";
}
hpBar.setText(txt);
}
// UI: Coin Text
function updateCoinText() {
if (coinText) coinText.setText("₺ " + coinsCollected);
// Rebuild build panel to update overlays/locks if it exists
if (typeof buildPanel !== "undefined" && buildPanel) {
buildPanel.destroy();
buildButtons = [];
createBuildPanel();
}
}
// UI: Wave Text
function updateWaveText() {
if (waveText) waveText.setText("Dalga: " + currentWave);
}
// UI: Build Panel
function createBuildPanel() {
buildPanel = new Container();
buildPanel.y = 0;
buildPanel.x = 0;
var btnW = 140,
btnH = 56,
gap = 18;
// Dynamically build types array based on upgrade state
var types = ['wall', 'trap', 'tower', 'upgrade', 'heal'];
if (!autoCollectEnabled) {
types.push('autocollect');
} else {
types.push('coinmult');
}
// If coinmult is present, we will handle its label and cost dynamically below
for (var i = 0; i < types.length; i++) {
(function (type, idx) {
var btn = new Container();
// Use original images for each type except for upgrade/autocollect/coinmult/checkpoint
var assetId = 'wall';
if (type === 'trap') assetId = 'trap';else if (type === 'tower') assetId = 'tower';
// Remove blue color, always use gray for button backgrounds
var bg = LK.getAsset(assetId, {
width: btnW,
height: btnH,
color: 0x888888,
anchorX: 0,
anchorY: 0
});
btn.addChild(bg);
var label, cost;
if (type === 'coinmult') {
// Show next multiplier (2x, 4x, 8x, ...) and its cost
var nextMult = Math.pow(2, coinMultiplierLevel + 1);
label = "Para x" + nextMult;
cost = coinMultiplierCost;
} else if (type === 'heal') {
label = "Can +1";
cost = 75;
} else {
label = buildNames[type] ? buildNames[type] : type;
cost = buildCost[type] ? buildCost[type] : 0;
}
var txt = new Text2(label + "\n₺" + cost, {
size: 32,
fill: "#fff"
});
txt.x = btnW / 2;
txt.y = 12;
txt.anchor.set(0.5, 0);
btn.addChild(txt);
btn.x = idx * (btnW + gap) - 10; // Move each button 10px to the left (was 100px)
btn.y = 0;
btn.type = type;
// Check if player can afford this item
var canAfford = coinsCollected >= cost;
if (!canAfford) {
// Add a semi-transparent black overlay
// Remove blue color, always use gray for lock overlays
var overlay = LK.getAsset('wall', {
width: btnW,
height: btnH,
color: 0x888888,
anchorX: 0,
anchorY: 0
});
overlay.alpha = 0.45;
btn.addChild(overlay);
// Add a lock icon (use a Text2 lock emoji for simplicity)
var lockIcon = new Text2("🔒", {
size: 38,
fill: "#fff"
});
lockIcon.x = btnW / 2;
lockIcon.y = btnH / 2 - 18;
lockIcon.anchor.set(0.5, 0.5);
btn.addChild(lockIcon);
// Disable interaction
btn.down = function () {};
} else {
btn.down = function (x, y, obj) {
setBuildMode(type);
};
}
buildPanel.addChild(btn);
buildButtons.push(btn);
})(types[i], i);
}
buildPanel.y = -120; // Move the build panel further up by 120px (closer to bottom)
buildPanel.x = -320; // Move the build panel to the right (was -540)
LK.gui.bottom.addChild(buildPanel);
}
// Set build mode
function setBuildMode(type) {
buildMode = type;
if (buildPreview) {
buildPreview.destroy();
buildPreview = null;
}
if (type === 'wall') {
buildPreview = new Wall();
} else if (type === 'trap') {
buildPreview = new Trap();
} else if (type === 'tower') {
buildPreview = new Tower();
} else if (type === 'upgrade' || type === 'autocollect') {
buildPreview = null;
}
if (buildPreview) {
buildPreview.alpha = 0.5;
game.addChild(buildPreview);
}
}
// Remove build preview
function clearBuildPreview() {
if (buildPreview) {
buildPreview.destroy();
buildPreview = null;
}
buildMode = null;
}
// Start new wave
function startWave() {
zombiesToSpawn = 5 + currentWave * 2;
zombiesSpawnedThisWave = 0;
zombiesKilledThisWave = 0;
waveInProgress = true;
updateWaveText();
// Spawn boss every 3 waves
if (currentWave % 3 === 0) {
// Boss will be spawned after all regular zombies
zombiesToSpawn += 1;
}
}
// Spawn zombie
function spawnZombie() {
// Spawn at random edge
var edge = Math.floor(Math.random() * 4);
var zx, zy;
if (edge === 0) {
// top
zx = 200 + Math.random() * (2048 - 400);
zy = -60;
} else if (edge === 1) {
// right
zx = 2048 + 60;
zy = 200 + Math.random() * (2732 - 400);
} else if (edge === 2) {
// bottom
zx = 200 + Math.random() * (2048 - 400);
zy = 2732 + 60;
} else {
// left
zx = -60;
zy = 200 + Math.random() * (2732 - 400);
}
// If this is a boss wave and this is the last zombie, spawn boss
var isBossWave = currentWave % 3 === 0;
var isLastZombie = zombiesSpawnedThisWave === zombiesToSpawn - 1 && isBossWave;
var z;
if (isLastZombie) {
z = new BossZombie();
} else {
z = new Zombie();
}
z.x = zx;
z.y = zy;
zombies.push(z);
game.addChild(z);
zombiesSpawnedThisWave++;
}
// Build defense
function buildDefense(type, x, y) {
if (coinsCollected < buildCost[type]) return false;
var obj;
if (type === 'wall') {
obj = new Wall();
} else if (type === 'trap') {
obj = new Trap();
} else if (type === 'tower') {
obj = new Tower();
}
if (obj) {
obj.x = x;
obj.y = y;
defenses.push(obj);
game.addChild(obj);
coinsCollected -= buildCost[type];
updateCoinText();
LK.getSound('build').play();
return true;
}
return false;
}
// Upgrade weapon
function upgradeWeapon() {
if (coinsCollected < buildCost.upgrade) return false;
player.weaponLevel++;
// Upgrade all towers' shoot power
for (var i = 0; i < defenses.length; i++) {
if (defenses[i] && defenses[i] instanceof Tower) {
// Increase tower bullet damage and speed with each upgrade
if (typeof defenses[i].weaponLevel === "undefined") {
defenses[i].weaponLevel = 1;
}
defenses[i].weaponLevel++;
}
}
coinsCollected -= buildCost.upgrade;
updateCoinText();
LK.effects.flashObject(player, 0xffff00, 600);
LK.getSound('build').play();
return true;
}
// Handle move (drag, build preview, collect coin)
function handleMove(x, y, obj) {
lastTouchX = x;
lastTouchY = y;
// Drag player
if (dragNode === player) {
// Clamp to game area
var r = player.radius + 10;
player.x = Math.max(r, Math.min(2048 - r, x));
player.y = Math.max(r, Math.min(2732 - r, y));
}
// Build preview follows finger
if (buildPreview) {
buildPreview.x = x;
buildPreview.y = y;
}
// Coin collection
for (var i = coins.length - 1; i >= 0; i--) {
var c = coins[i];
var dx = c.x - x,
dy = c.y - y;
if (!c.collected && dx * dx + dy * dy < c.radius * c.radius + 1200) {
c.collect();
}
}
}
// Handle down (start drag, build, upgrade)
game.down = function (x, y, obj) {
// If build mode is active
if (buildMode) {
if (buildMode === 'upgrade') {
if (upgradeWeapon()) {
clearBuildPreview();
}
} else if (buildMode === 'autocollect') {
if (!autoCollectEnabled && coinsCollected >= buildCost.autocollect) {
coinsCollected -= buildCost.autocollect;
updateCoinText();
autoCollectEnabled = true;
LK.effects.flashObject(player, 0x00ffcc, 600);
LK.getSound('build').play();
clearBuildPreview();
// Recreate build panel to remove autocollect and add coinmult
if (buildPanel) {
buildPanel.destroy();
buildButtons = [];
createBuildPanel();
}
}
} else if (buildMode === 'coinmult') {
if (coinsCollected >= coinMultiplierCost) {
coinsCollected -= coinMultiplierCost;
updateCoinText();
coinMultiplierLevel++;
coinMultiplierCost += 100;
LK.effects.flashObject(player, 0xffe066, 600);
LK.getSound('build').play();
clearBuildPreview();
// Recreate build panel to update multiplier label/cost
if (buildPanel) {
buildPanel.destroy();
buildButtons = [];
createBuildPanel();
}
}
} else if (buildMode === 'heal') {
if (coinsCollected >= 75 && player.hp < player.maxHp) {
coinsCollected -= 75;
player.hp += 1;
if (player.hp > player.maxHp) player.hp = player.maxHp;
updateCoinText();
updateHpBar();
LK.effects.flashObject(player, 0x00ff44, 600);
LK.getSound('build').play();
clearBuildPreview();
}
} else {
// Place defense if possible
if (buildPreview) {
// Don't allow build on player or on top of other defenses
var canBuild = true;
var dx = player.x - x,
dy = player.y - y;
if (dx * dx + dy * dy < (player.radius + buildPreview.radius + 40) * (player.radius + buildPreview.radius + 40)) {
canBuild = false;
}
for (var i = 0; i < defenses.length; i++) {
var d = defenses[i];
var ddx = d.x - x,
ddy = d.y - y;
if (ddx * ddx + ddy * ddy < (d.radius + buildPreview.radius + 30) * (d.radius + buildPreview.radius + 30)) {
canBuild = false;
break;
}
}
if (canBuild && buildDefense(buildMode, x, y)) {
clearBuildPreview();
}
}
}
return;
}
// Only handle mouse/touch events with event object
var event = obj && obj.event ? obj.event : null;
var isLeftClick = false;
var isMiddleClick = false;
// Mouse events
if (event && typeof event.button !== "undefined") {
// 0: left, 1: middle
if (event.button === 0) isLeftClick = true;
if (event.button === 1) isMiddleClick = true;
} else {
// Touch events: treat as left click (drag/shoot)
isLeftClick = true;
}
if (isMiddleClick) {
// Teleport player to clicked position, clamp to game area
var r = player.radius + 10;
player.x = Math.max(r, Math.min(2048 - r, x));
player.y = Math.max(r, Math.min(2732 - r, y));
} else if (isLeftClick) {
// If click/touch is on player, start drag
var dx = player.x - x,
dy = player.y - y;
if (dx * dx + dy * dy < player.radius * player.radius + 2000) {
dragNode = player;
} else {
// Otherwise, shoot
player.shoot(x, y);
}
}
handleMove(x, y, obj);
};
// Prevent context menu on right click for the canvas
if (typeof window !== "undefined" && typeof document !== "undefined") {
var canvas = document.querySelector('canvas');
if (canvas) {
canvas.oncontextmenu = function (e) {
e.preventDefault();
return false;
};
}
}
// Handle up (stop drag, clear build preview)
game.up = function (x, y, obj) {
dragNode = null;
// If build preview exists, clear it if not placed
if (buildPreview && buildMode) {
clearBuildPreview();
}
};
// Handle move
game.move = handleMove;
// Game update
game.update = function () {
// --- Virtual Joystick Movement ---
var moveSpeed = 18 + player.weaponLevel * 2;
if (typeof vbtns !== "undefined") {
var dx = 0,
dy = 0;
if (vbtns.up && vbtns.up.isDown) dy -= 1;
if (vbtns.down && vbtns.down.isDown) dy += 1;
if (vbtns.left && vbtns.left.isDown) dx -= 1;
if (vbtns.right && vbtns.right.isDown) dx += 1;
if (dx !== 0 || dy !== 0) {
var len = Math.sqrt(dx * dx + dy * dy);
if (len > 0) {
dx /= len;
dy /= len;
// Clamp to game area
var r = player.radius + 10;
player.x = Math.max(r, Math.min(2048 - r, player.x + dx * moveSpeed));
player.y = Math.max(r, Math.min(2732 - r, player.y + dy * moveSpeed));
}
}
}
// Player update
player.update();
// Zombies update
for (var i = zombies.length - 1; i >= 0; i--) {
zombies[i].update();
}
// Bullets update
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;
}
// Check collision with zombies
for (var j = zombies.length - 1; j >= 0; j--) {
var z = zombies[j];
var dx = z.x - b.x,
dy = z.y - b.y;
if (dx * dx + dy * dy < (z.radius + b.radius) * (z.radius + b.radius)) {
z.takeDamage(b.damage);
b.destroy();
bullets.splice(i, 1);
break;
}
}
}
// Coins update
for (var i = coins.length - 1; i >= 0; i--) {
coins[i].update();
if (autoCollectEnabled && !coins[i].collected) {
coins[i].collect();
}
}
// Defenses update
for (var i = defenses.length - 1; i >= 0; i--) {
if (defenses[i].update) defenses[i].update();
}
// Wave logic
if (waveInProgress) {
// Spawn zombies
if (zombiesSpawnedThisWave < zombiesToSpawn && LK.ticks % Math.max(20, 60 - currentWave * 2) === 0) {
spawnZombie();
}
// Check wave end
if (zombiesKilledThisWave >= zombiesToSpawn && zombies.length === 0) {
waveInProgress = false;
// Give 5 coins every 5 waves at the end of the wave
if (currentWave % 5 === 0) {
coinsCollected += 5;
updateCoinText();
}
// Next wave after 4 seconds (4000 ms)
LK.setTimeout(function () {
currentWave++;
startWave();
}, 4000);
}
}
};
// --- Game Initialization ---
// Add forest background (drawn as a large green rectangle with tree-like ellipses)
var forestBg = new Container();
var bgRect = LK.getAsset('military_bg', {
width: 2048,
height: 2732,
color: 0x2e4d1a,
// deep forest green
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
forestBg.addChild(bgRect);
// Add some tree ellipses for a forest look
for (var i = 0; i < 18; i++) {
var tree = LK.getAsset('military_bg', {
width: 180 + Math.floor(Math.random() * 80),
height: 320 + Math.floor(Math.random() * 60),
color: 0x3e7d2a + Math.floor(Math.random() * 0x002000),
shape: 'ellipse',
anchorX: 0.5,
anchorY: 1,
x: 120 + Math.random() * 1800,
y: 400 + Math.random() * 2100
});
tree.alpha = 0.22 + Math.random() * 0.18;
forestBg.addChild(tree);
}
game.addChild(forestBg);
// Place player at center
player = new Player();
player.x = 2048 / 2;
player.y = 2732 / 2;
game.addChild(player);
// --- Virtual Joystick Buttons ---
// Button size and margin
var vbtnSize = 160;
var vbtnMargin = 40;
var vbtnAlpha = 0.45;
var vbtnColor = 0x888888;
var vbtns = {};
// Helper to create a button
function createVBtn(label, x, y, dir) {
var btn = new Container();
// Remove blue color, always use gray for joystick button backgrounds
var bg = LK.getAsset('wall', {
width: vbtnSize,
height: vbtnSize,
color: 0x888888,
anchorX: 0.5,
anchorY: 0.5
});
bg.alpha = vbtnAlpha;
btn.addChild(bg);
var txt = new Text2(label, {
size: 80,
fill: "#fff"
});
txt.anchor.set(0.5, 0.5);
txt.x = 0;
txt.y = 0;
btn.addChild(txt);
btn.x = x;
btn.y = y;
btn.dir = dir;
btn.isDown = false;
btn.down = function () {
btn.isDown = true;
};
btn.up = function () {
btn.isDown = false;
};
LK.gui.bottomLeft.addChild(btn);
return btn;
}
// Positioning for bottom left corner
var baseX = vbtnSize + vbtnMargin;
var baseY = -vbtnSize - vbtnMargin;
// Up
vbtns.up = createVBtn("▲", baseX, baseY - vbtnSize, "up");
// Down
vbtns.down = createVBtn("▼", baseX, baseY + vbtnSize, "down");
// Left
vbtns.left = createVBtn("◀", baseX - vbtnSize, baseY, "left");
// Right
vbtns.right = createVBtn("▶", baseX + vbtnSize, baseY, "right");
// UI: HP Bar
hpBar = new Text2("", {
size: 38,
fill: 0xFF4444
});
// Move hearts to the absolute left (x=0), keep y=20
hpBar.anchor.set(0, 0);
LK.gui.top.addChild(hpBar);
hpBar.x = 0;
hpBar.y = 20;
updateHpBar();
// UI: Coin Text
coinText = new Text2("₺ 0", {
size: 70,
fill: 0xFFE066
});
coinText.anchor.set(1, 0);
LK.gui.topRight.addChild(coinText);
coinText.x = -40;
coinText.y = 20;
// UI: Wave Text
waveText = new Text2("Dalga: 1", {
size: 60,
fill: "#fff"
});
waveText.anchor.set(0, 0);
LK.gui.top.addChild(waveText);
waveText.x = 60;
waveText.y = 120;
// UI: Build Panel
createBuildPanel();
// Start first wave
startWave();
updateCoinText();
updateWaveText(); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// BossZombie class
var BossZombie = Container.expand(function () {
var self = Container.call(this);
var bossSprite = self.attachAsset('zombie', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.7,
scaleY: 1.7,
color: 0x8e44ad // purple tint for boss
});
self.radius = bossSprite.width / 2;
var bossPowerLevel = Math.floor((currentWave - 1) / 3);
self.speed = 1.5 + bossPowerLevel * 0.5;
self.hp = 10 + bossPowerLevel * 5;
self.maxHp = self.hp;
self.target = null;
self.lastHitTick = 0;
self.update = function () {
// Move towards player
var tx = player.x;
var ty = player.y;
// If wall/trap/tower is closer, target that
var minDist = Math.sqrt((self.x - tx) * (self.x - tx) + (self.y - ty) * (self.y - ty));
var targetObj = player;
for (var i = 0; i < defenses.length; i++) {
var d = defenses[i];
var dist = Math.sqrt((self.x - d.x) * (self.x - d.x) + (self.y - d.y) * (self.y - d.y));
if (dist < minDist) {
minDist = dist;
targetObj = d;
}
}
self.target = targetObj;
var dx = targetObj.x - self.x;
var dy = targetObj.y - self.y;
var len = Math.sqrt(dx * dx + dy * dy);
if (len > 0) {
dx /= len;
dy /= len;
self.x += dx * self.speed;
self.y += dy * self.speed;
}
// Attack if close
if (len < self.radius + targetObj.radius + 10) {
if (LK.ticks - self.lastHitTick > 30) {
if (targetObj === player) {
player.takeDamage(2); // Boss does more damage
} else if (targetObj.takeDamage) {
targetObj.takeDamage(2);
}
self.lastHitTick = LK.ticks;
}
}
};
self.takeDamage = function (amount) {
self.hp -= amount;
if (self.hp <= 0) {
self.die();
} else {
LK.effects.flashObject(self, 0xff00ff, 300);
}
};
self.die = function () {
LK.getSound('zombie_die').play();
// Drop 5 coins
var coinCount = 5;
if (typeof coinMultiplierEnabled !== "undefined" && coinMultiplierEnabled) {
coinCount = Math.round(5 * 1.5);
}
for (var i = 0; i < coinCount; i++) {
var coin = new Coin();
// Spread coins a bit
var angle = Math.PI * 2 * i / coinCount;
coin.x = self.x + Math.cos(angle) * 60;
coin.y = self.y + Math.sin(angle) * 60;
coins.push(coin);
game.addChild(coin);
}
self.destroy();
var idx = zombies.indexOf(self);
if (idx !== -1) zombies.splice(idx, 1);
zombiesKilledThisWave++;
};
return self;
});
// 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.vx = 0;
self.vy = 0;
self.damage = 1;
self.update = function () {
self.x += self.vx;
self.y += self.vy;
};
return self;
});
// Coin class
var Coin = Container.expand(function () {
var self = Container.call(this);
var coinSprite = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = coinSprite.width * 1.5; // Even further increase hitbox radius for much easier collection
// Remove vy and falling logic so coin stays at spawn position
self.collected = false;
self.update = function () {
// No falling, coin stays at its spawn position
};
self.collect = function () {
if (self.collected) return;
self.collected = true;
LK.getSound('coin').play();
// Animate to coinText
var guiPos = LK.gui.topRight.toLocal(game.toGlobal({
x: self.x,
y: self.y
}));
var startX = self.x,
startY = self.y;
var endX = 2048 - 200,
endY = 80;
tween(self, {
x: endX,
y: endY,
alpha: 0
}, {
duration: 400,
easing: tween.cubicIn,
onFinish: function onFinish() {
self.destroy();
var idx = coins.indexOf(self);
if (idx !== -1) coins.splice(idx, 1);
if (typeof coinMultiplierLevel !== "undefined" && coinMultiplierLevel > 0) {
coinsCollected += Math.pow(2, coinMultiplierLevel);
} else {
coinsCollected++;
}
coinsCollected = Math.floor(coinsCollected);
updateCoinText();
}
});
};
return self;
});
// Player class
var Player = Container.expand(function () {
var self = Container.call(this);
var playerSprite = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = playerSprite.width / 2;
self.hp = 10;
self.maxHp = 10;
self.shootCooldown = 0;
self.shootDelay = 20; // frames
self.weaponLevel = 1;
self.lastShotTick = 0;
// For drag
self.down = function (x, y, obj) {};
self.up = function (x, y, obj) {};
self.update = function () {
if (self.shootCooldown > 0) self.shootCooldown--;
};
// Shoot method
self.shoot = function (targetX, targetY) {
if (self.shootCooldown > 0) return;
var dx = targetX - self.x;
var dy = targetY - self.y;
var len = Math.sqrt(dx * dx + dy * dy);
if (len === 0) return;
dx /= len;
dy /= len;
var bullet = new Bullet();
bullet.x = self.x + dx * (self.radius + 30);
bullet.y = self.y + dy * (self.radius + 30);
bullet.vx = dx * (22 + self.weaponLevel * 2);
bullet.vy = dy * (22 + self.weaponLevel * 2);
bullet.damage = self.weaponLevel;
// Set bullet rotation so the tip faces the direction of movement
if (bullet.children && bullet.children.length > 0) {
bullet.children[0].rotation = Math.atan2(bullet.vy, bullet.vx);
}
bullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
self.shootCooldown = self.shootDelay;
};
// Take damage
self.takeDamage = function (amount) {
self.hp -= amount;
if (self.hp < 0) self.hp = 0;
LK.effects.flashObject(self, 0xff0000, 300);
updateHpBar();
if (self.hp <= 0) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
}
};
return self;
});
// Tower class
var Tower = Container.expand(function () {
var self = Container.call(this);
var towerSprite = self.attachAsset('tower', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = Math.max(towerSprite.width, towerSprite.height) / 2;
self.hp = 20;
self.maxHp = 20;
self.shootCooldown = 0;
self.shootDelay = 40;
self.takeDamage = function (amount) {
self.hp -= amount;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.hp <= 0) {
self.destroy();
var idx = defenses.indexOf(self);
if (idx !== -1) defenses.splice(idx, 1);
}
};
self.update = function () {
if (self.shootCooldown > 0) self.shootCooldown--;
// Find nearest zombie
var nearest = null,
minDist = 99999;
for (var i = 0; i < zombies.length; i++) {
var z = zombies[i];
var dx = z.x - self.x;
var dy = z.y - self.y;
var dist = dx * dx + dy * dy;
if (dist < minDist) {
minDist = dist;
nearest = z;
}
}
if (nearest && self.shootCooldown <= 0) {
// Shoot at nearest zombie
var dx = nearest.x - self.x;
var dy = nearest.y - self.y;
var len = Math.sqrt(dx * dx + dy * dy);
if (len > 0) {
dx /= len;
dy /= len;
var bullet = new Bullet();
bullet.x = self.x + dx * (self.radius + 20);
bullet.y = self.y + dy * (self.radius + 20);
var towerWeaponLevel = typeof self.weaponLevel !== "undefined" ? self.weaponLevel : 1;
bullet.vx = dx * (18 + towerWeaponLevel * 2);
bullet.vy = dy * (18 + towerWeaponLevel * 2);
bullet.damage = towerWeaponLevel;
// Set bullet rotation so the tip faces the direction of movement
if (bullet.children && bullet.children.length > 0) {
bullet.children[0].rotation = Math.atan2(bullet.vy, bullet.vx);
}
bullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
self.shootCooldown = self.shootDelay;
}
}
};
return self;
});
// Trap class
var Trap = Container.expand(function () {
var self = Container.call(this);
var trapSprite = self.attachAsset('trap', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = Math.max(trapSprite.width, trapSprite.height) / 2;
self.hp = 2;
self.maxHp = 2;
self.takeDamage = function (amount) {
self.hp -= amount;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.hp <= 0) {
self.destroy();
var idx = defenses.indexOf(self);
if (idx !== -1) defenses.splice(idx, 1);
}
};
return self;
});
// Wall class
var Wall = Container.expand(function () {
var self = Container.call(this);
var wallSprite = self.attachAsset('wall', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = Math.max(wallSprite.width, wallSprite.height) / 2;
self.hp = 6;
self.maxHp = 6;
self.takeDamage = function (amount) {
self.hp -= amount;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.hp <= 0) {
self.destroy();
var idx = defenses.indexOf(self);
if (idx !== -1) defenses.splice(idx, 1);
}
};
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;
// Zombies get stronger only every 3 waves
var zombiePowerLevel = Math.floor((currentWave - 1) / 3);
self.speed = 2 + zombiePowerLevel * 0.9;
self.hp = 1 + zombiePowerLevel;
self.maxHp = self.hp;
self.target = null;
self.lastHitTick = 0;
self.update = function () {
// Move towards player
var tx = player.x;
var ty = player.y;
// If wall/trap/tower is closer, target that
var minDist = Math.sqrt((self.x - tx) * (self.x - tx) + (self.y - ty) * (self.y - ty));
var targetObj = player;
for (var i = 0; i < defenses.length; i++) {
var d = defenses[i];
var dist = Math.sqrt((self.x - d.x) * (self.x - d.x) + (self.y - d.y) * (self.y - d.y));
if (dist < minDist) {
minDist = dist;
targetObj = d;
}
}
self.target = targetObj;
var dx = targetObj.x - self.x;
var dy = targetObj.y - self.y;
var len = Math.sqrt(dx * dx + dy * dy);
if (len > 0) {
dx /= len;
dy /= len;
self.x += dx * self.speed;
self.y += dy * self.speed;
}
// Attack if close
if (len < self.radius + targetObj.radius + 10) {
if (LK.ticks - self.lastHitTick > 30) {
if (targetObj === player) {
player.takeDamage(1);
} else if (targetObj.takeDamage) {
targetObj.takeDamage(1);
}
self.lastHitTick = LK.ticks;
}
}
};
self.takeDamage = function (amount) {
self.hp -= amount;
if (self.hp <= 0) {
self.die();
} else {
LK.effects.flashObject(self, 0xff0000, 200);
}
};
self.die = function () {
LK.getSound('zombie_die').play();
// Drop coin
var coin = new Coin();
coin.x = self.x;
coin.y = self.y;
coins.push(coin);
game.addChild(coin);
self.destroy();
var idx = zombies.indexOf(self);
if (idx !== -1) zombies.splice(idx, 1);
zombiesKilledThisWave++;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Sounds
// Tower
// Trap
// Wall
// Coin
// Bullet
// Zombie
// Character (player)
// Global variables
var player;
var zombies = [];
var bullets = [];
var coins = [];
var defenses = [];
var coinsCollected = 0;
var autoCollectEnabled = false;
var coinMultiplierEnabled = false;
var coinMultiplier2Enabled = false;
var currentWave = 1;
var zombiesToSpawn = 0;
var zombiesSpawnedThisWave = 0;
var zombiesKilledThisWave = 0;
var waveInProgress = false;
var dragNode = null;
var buildMode = null; // 'wall', 'trap', 'tower', 'upgrade', 'autocollect', 'coinmult'
var buildPreview = null;
var buildCost = {
wall: 5,
trap: 7,
tower: 30,
upgrade: 10,
autocollect: 20,
coinmult: 50
};
var buildNames = {
wall: "Duvar",
trap: "Tuzak",
tower: "Kule",
upgrade: "Silah+",
autocollect: "Oto-Para",
coinmult: "Para x2"
};
// Track current coin multiplier and its cost
var coinMultiplierLevel = 1; // 1 means 2x, 2 means 4x, etc.
var coinMultiplierCost = 100; // Cost for next multiplier (starts at 100 for 2x)
// buildDesc removed
var hpBar,
coinText,
waveText,
buildPanel,
buildButtons = [];
var lastTouchX = 0,
lastTouchY = 0;
// UI: HP Bar
function updateHpBar() {
if (!hpBar) return;
var txt = "";
for (var i = 0; i < player.maxHp; i++) {
txt += i < player.hp ? "♥ " : "♡ ";
}
hpBar.setText(txt);
}
// UI: Coin Text
function updateCoinText() {
if (coinText) coinText.setText("₺ " + coinsCollected);
// Rebuild build panel to update overlays/locks if it exists
if (typeof buildPanel !== "undefined" && buildPanel) {
buildPanel.destroy();
buildButtons = [];
createBuildPanel();
}
}
// UI: Wave Text
function updateWaveText() {
if (waveText) waveText.setText("Dalga: " + currentWave);
}
// UI: Build Panel
function createBuildPanel() {
buildPanel = new Container();
buildPanel.y = 0;
buildPanel.x = 0;
var btnW = 140,
btnH = 56,
gap = 18;
// Dynamically build types array based on upgrade state
var types = ['wall', 'trap', 'tower', 'upgrade', 'heal'];
if (!autoCollectEnabled) {
types.push('autocollect');
} else {
types.push('coinmult');
}
// If coinmult is present, we will handle its label and cost dynamically below
for (var i = 0; i < types.length; i++) {
(function (type, idx) {
var btn = new Container();
// Use original images for each type except for upgrade/autocollect/coinmult/checkpoint
var assetId = 'wall';
if (type === 'trap') assetId = 'trap';else if (type === 'tower') assetId = 'tower';
// Remove blue color, always use gray for button backgrounds
var bg = LK.getAsset(assetId, {
width: btnW,
height: btnH,
color: 0x888888,
anchorX: 0,
anchorY: 0
});
btn.addChild(bg);
var label, cost;
if (type === 'coinmult') {
// Show next multiplier (2x, 4x, 8x, ...) and its cost
var nextMult = Math.pow(2, coinMultiplierLevel + 1);
label = "Para x" + nextMult;
cost = coinMultiplierCost;
} else if (type === 'heal') {
label = "Can +1";
cost = 75;
} else {
label = buildNames[type] ? buildNames[type] : type;
cost = buildCost[type] ? buildCost[type] : 0;
}
var txt = new Text2(label + "\n₺" + cost, {
size: 32,
fill: "#fff"
});
txt.x = btnW / 2;
txt.y = 12;
txt.anchor.set(0.5, 0);
btn.addChild(txt);
btn.x = idx * (btnW + gap) - 10; // Move each button 10px to the left (was 100px)
btn.y = 0;
btn.type = type;
// Check if player can afford this item
var canAfford = coinsCollected >= cost;
if (!canAfford) {
// Add a semi-transparent black overlay
// Remove blue color, always use gray for lock overlays
var overlay = LK.getAsset('wall', {
width: btnW,
height: btnH,
color: 0x888888,
anchorX: 0,
anchorY: 0
});
overlay.alpha = 0.45;
btn.addChild(overlay);
// Add a lock icon (use a Text2 lock emoji for simplicity)
var lockIcon = new Text2("🔒", {
size: 38,
fill: "#fff"
});
lockIcon.x = btnW / 2;
lockIcon.y = btnH / 2 - 18;
lockIcon.anchor.set(0.5, 0.5);
btn.addChild(lockIcon);
// Disable interaction
btn.down = function () {};
} else {
btn.down = function (x, y, obj) {
setBuildMode(type);
};
}
buildPanel.addChild(btn);
buildButtons.push(btn);
})(types[i], i);
}
buildPanel.y = -120; // Move the build panel further up by 120px (closer to bottom)
buildPanel.x = -320; // Move the build panel to the right (was -540)
LK.gui.bottom.addChild(buildPanel);
}
// Set build mode
function setBuildMode(type) {
buildMode = type;
if (buildPreview) {
buildPreview.destroy();
buildPreview = null;
}
if (type === 'wall') {
buildPreview = new Wall();
} else if (type === 'trap') {
buildPreview = new Trap();
} else if (type === 'tower') {
buildPreview = new Tower();
} else if (type === 'upgrade' || type === 'autocollect') {
buildPreview = null;
}
if (buildPreview) {
buildPreview.alpha = 0.5;
game.addChild(buildPreview);
}
}
// Remove build preview
function clearBuildPreview() {
if (buildPreview) {
buildPreview.destroy();
buildPreview = null;
}
buildMode = null;
}
// Start new wave
function startWave() {
zombiesToSpawn = 5 + currentWave * 2;
zombiesSpawnedThisWave = 0;
zombiesKilledThisWave = 0;
waveInProgress = true;
updateWaveText();
// Spawn boss every 3 waves
if (currentWave % 3 === 0) {
// Boss will be spawned after all regular zombies
zombiesToSpawn += 1;
}
}
// Spawn zombie
function spawnZombie() {
// Spawn at random edge
var edge = Math.floor(Math.random() * 4);
var zx, zy;
if (edge === 0) {
// top
zx = 200 + Math.random() * (2048 - 400);
zy = -60;
} else if (edge === 1) {
// right
zx = 2048 + 60;
zy = 200 + Math.random() * (2732 - 400);
} else if (edge === 2) {
// bottom
zx = 200 + Math.random() * (2048 - 400);
zy = 2732 + 60;
} else {
// left
zx = -60;
zy = 200 + Math.random() * (2732 - 400);
}
// If this is a boss wave and this is the last zombie, spawn boss
var isBossWave = currentWave % 3 === 0;
var isLastZombie = zombiesSpawnedThisWave === zombiesToSpawn - 1 && isBossWave;
var z;
if (isLastZombie) {
z = new BossZombie();
} else {
z = new Zombie();
}
z.x = zx;
z.y = zy;
zombies.push(z);
game.addChild(z);
zombiesSpawnedThisWave++;
}
// Build defense
function buildDefense(type, x, y) {
if (coinsCollected < buildCost[type]) return false;
var obj;
if (type === 'wall') {
obj = new Wall();
} else if (type === 'trap') {
obj = new Trap();
} else if (type === 'tower') {
obj = new Tower();
}
if (obj) {
obj.x = x;
obj.y = y;
defenses.push(obj);
game.addChild(obj);
coinsCollected -= buildCost[type];
updateCoinText();
LK.getSound('build').play();
return true;
}
return false;
}
// Upgrade weapon
function upgradeWeapon() {
if (coinsCollected < buildCost.upgrade) return false;
player.weaponLevel++;
// Upgrade all towers' shoot power
for (var i = 0; i < defenses.length; i++) {
if (defenses[i] && defenses[i] instanceof Tower) {
// Increase tower bullet damage and speed with each upgrade
if (typeof defenses[i].weaponLevel === "undefined") {
defenses[i].weaponLevel = 1;
}
defenses[i].weaponLevel++;
}
}
coinsCollected -= buildCost.upgrade;
updateCoinText();
LK.effects.flashObject(player, 0xffff00, 600);
LK.getSound('build').play();
return true;
}
// Handle move (drag, build preview, collect coin)
function handleMove(x, y, obj) {
lastTouchX = x;
lastTouchY = y;
// Drag player
if (dragNode === player) {
// Clamp to game area
var r = player.radius + 10;
player.x = Math.max(r, Math.min(2048 - r, x));
player.y = Math.max(r, Math.min(2732 - r, y));
}
// Build preview follows finger
if (buildPreview) {
buildPreview.x = x;
buildPreview.y = y;
}
// Coin collection
for (var i = coins.length - 1; i >= 0; i--) {
var c = coins[i];
var dx = c.x - x,
dy = c.y - y;
if (!c.collected && dx * dx + dy * dy < c.radius * c.radius + 1200) {
c.collect();
}
}
}
// Handle down (start drag, build, upgrade)
game.down = function (x, y, obj) {
// If build mode is active
if (buildMode) {
if (buildMode === 'upgrade') {
if (upgradeWeapon()) {
clearBuildPreview();
}
} else if (buildMode === 'autocollect') {
if (!autoCollectEnabled && coinsCollected >= buildCost.autocollect) {
coinsCollected -= buildCost.autocollect;
updateCoinText();
autoCollectEnabled = true;
LK.effects.flashObject(player, 0x00ffcc, 600);
LK.getSound('build').play();
clearBuildPreview();
// Recreate build panel to remove autocollect and add coinmult
if (buildPanel) {
buildPanel.destroy();
buildButtons = [];
createBuildPanel();
}
}
} else if (buildMode === 'coinmult') {
if (coinsCollected >= coinMultiplierCost) {
coinsCollected -= coinMultiplierCost;
updateCoinText();
coinMultiplierLevel++;
coinMultiplierCost += 100;
LK.effects.flashObject(player, 0xffe066, 600);
LK.getSound('build').play();
clearBuildPreview();
// Recreate build panel to update multiplier label/cost
if (buildPanel) {
buildPanel.destroy();
buildButtons = [];
createBuildPanel();
}
}
} else if (buildMode === 'heal') {
if (coinsCollected >= 75 && player.hp < player.maxHp) {
coinsCollected -= 75;
player.hp += 1;
if (player.hp > player.maxHp) player.hp = player.maxHp;
updateCoinText();
updateHpBar();
LK.effects.flashObject(player, 0x00ff44, 600);
LK.getSound('build').play();
clearBuildPreview();
}
} else {
// Place defense if possible
if (buildPreview) {
// Don't allow build on player or on top of other defenses
var canBuild = true;
var dx = player.x - x,
dy = player.y - y;
if (dx * dx + dy * dy < (player.radius + buildPreview.radius + 40) * (player.radius + buildPreview.radius + 40)) {
canBuild = false;
}
for (var i = 0; i < defenses.length; i++) {
var d = defenses[i];
var ddx = d.x - x,
ddy = d.y - y;
if (ddx * ddx + ddy * ddy < (d.radius + buildPreview.radius + 30) * (d.radius + buildPreview.radius + 30)) {
canBuild = false;
break;
}
}
if (canBuild && buildDefense(buildMode, x, y)) {
clearBuildPreview();
}
}
}
return;
}
// Only handle mouse/touch events with event object
var event = obj && obj.event ? obj.event : null;
var isLeftClick = false;
var isMiddleClick = false;
// Mouse events
if (event && typeof event.button !== "undefined") {
// 0: left, 1: middle
if (event.button === 0) isLeftClick = true;
if (event.button === 1) isMiddleClick = true;
} else {
// Touch events: treat as left click (drag/shoot)
isLeftClick = true;
}
if (isMiddleClick) {
// Teleport player to clicked position, clamp to game area
var r = player.radius + 10;
player.x = Math.max(r, Math.min(2048 - r, x));
player.y = Math.max(r, Math.min(2732 - r, y));
} else if (isLeftClick) {
// If click/touch is on player, start drag
var dx = player.x - x,
dy = player.y - y;
if (dx * dx + dy * dy < player.radius * player.radius + 2000) {
dragNode = player;
} else {
// Otherwise, shoot
player.shoot(x, y);
}
}
handleMove(x, y, obj);
};
// Prevent context menu on right click for the canvas
if (typeof window !== "undefined" && typeof document !== "undefined") {
var canvas = document.querySelector('canvas');
if (canvas) {
canvas.oncontextmenu = function (e) {
e.preventDefault();
return false;
};
}
}
// Handle up (stop drag, clear build preview)
game.up = function (x, y, obj) {
dragNode = null;
// If build preview exists, clear it if not placed
if (buildPreview && buildMode) {
clearBuildPreview();
}
};
// Handle move
game.move = handleMove;
// Game update
game.update = function () {
// --- Virtual Joystick Movement ---
var moveSpeed = 18 + player.weaponLevel * 2;
if (typeof vbtns !== "undefined") {
var dx = 0,
dy = 0;
if (vbtns.up && vbtns.up.isDown) dy -= 1;
if (vbtns.down && vbtns.down.isDown) dy += 1;
if (vbtns.left && vbtns.left.isDown) dx -= 1;
if (vbtns.right && vbtns.right.isDown) dx += 1;
if (dx !== 0 || dy !== 0) {
var len = Math.sqrt(dx * dx + dy * dy);
if (len > 0) {
dx /= len;
dy /= len;
// Clamp to game area
var r = player.radius + 10;
player.x = Math.max(r, Math.min(2048 - r, player.x + dx * moveSpeed));
player.y = Math.max(r, Math.min(2732 - r, player.y + dy * moveSpeed));
}
}
}
// Player update
player.update();
// Zombies update
for (var i = zombies.length - 1; i >= 0; i--) {
zombies[i].update();
}
// Bullets update
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;
}
// Check collision with zombies
for (var j = zombies.length - 1; j >= 0; j--) {
var z = zombies[j];
var dx = z.x - b.x,
dy = z.y - b.y;
if (dx * dx + dy * dy < (z.radius + b.radius) * (z.radius + b.radius)) {
z.takeDamage(b.damage);
b.destroy();
bullets.splice(i, 1);
break;
}
}
}
// Coins update
for (var i = coins.length - 1; i >= 0; i--) {
coins[i].update();
if (autoCollectEnabled && !coins[i].collected) {
coins[i].collect();
}
}
// Defenses update
for (var i = defenses.length - 1; i >= 0; i--) {
if (defenses[i].update) defenses[i].update();
}
// Wave logic
if (waveInProgress) {
// Spawn zombies
if (zombiesSpawnedThisWave < zombiesToSpawn && LK.ticks % Math.max(20, 60 - currentWave * 2) === 0) {
spawnZombie();
}
// Check wave end
if (zombiesKilledThisWave >= zombiesToSpawn && zombies.length === 0) {
waveInProgress = false;
// Give 5 coins every 5 waves at the end of the wave
if (currentWave % 5 === 0) {
coinsCollected += 5;
updateCoinText();
}
// Next wave after 4 seconds (4000 ms)
LK.setTimeout(function () {
currentWave++;
startWave();
}, 4000);
}
}
};
// --- Game Initialization ---
// Add forest background (drawn as a large green rectangle with tree-like ellipses)
var forestBg = new Container();
var bgRect = LK.getAsset('military_bg', {
width: 2048,
height: 2732,
color: 0x2e4d1a,
// deep forest green
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
forestBg.addChild(bgRect);
// Add some tree ellipses for a forest look
for (var i = 0; i < 18; i++) {
var tree = LK.getAsset('military_bg', {
width: 180 + Math.floor(Math.random() * 80),
height: 320 + Math.floor(Math.random() * 60),
color: 0x3e7d2a + Math.floor(Math.random() * 0x002000),
shape: 'ellipse',
anchorX: 0.5,
anchorY: 1,
x: 120 + Math.random() * 1800,
y: 400 + Math.random() * 2100
});
tree.alpha = 0.22 + Math.random() * 0.18;
forestBg.addChild(tree);
}
game.addChild(forestBg);
// Place player at center
player = new Player();
player.x = 2048 / 2;
player.y = 2732 / 2;
game.addChild(player);
// --- Virtual Joystick Buttons ---
// Button size and margin
var vbtnSize = 160;
var vbtnMargin = 40;
var vbtnAlpha = 0.45;
var vbtnColor = 0x888888;
var vbtns = {};
// Helper to create a button
function createVBtn(label, x, y, dir) {
var btn = new Container();
// Remove blue color, always use gray for joystick button backgrounds
var bg = LK.getAsset('wall', {
width: vbtnSize,
height: vbtnSize,
color: 0x888888,
anchorX: 0.5,
anchorY: 0.5
});
bg.alpha = vbtnAlpha;
btn.addChild(bg);
var txt = new Text2(label, {
size: 80,
fill: "#fff"
});
txt.anchor.set(0.5, 0.5);
txt.x = 0;
txt.y = 0;
btn.addChild(txt);
btn.x = x;
btn.y = y;
btn.dir = dir;
btn.isDown = false;
btn.down = function () {
btn.isDown = true;
};
btn.up = function () {
btn.isDown = false;
};
LK.gui.bottomLeft.addChild(btn);
return btn;
}
// Positioning for bottom left corner
var baseX = vbtnSize + vbtnMargin;
var baseY = -vbtnSize - vbtnMargin;
// Up
vbtns.up = createVBtn("▲", baseX, baseY - vbtnSize, "up");
// Down
vbtns.down = createVBtn("▼", baseX, baseY + vbtnSize, "down");
// Left
vbtns.left = createVBtn("◀", baseX - vbtnSize, baseY, "left");
// Right
vbtns.right = createVBtn("▶", baseX + vbtnSize, baseY, "right");
// UI: HP Bar
hpBar = new Text2("", {
size: 38,
fill: 0xFF4444
});
// Move hearts to the absolute left (x=0), keep y=20
hpBar.anchor.set(0, 0);
LK.gui.top.addChild(hpBar);
hpBar.x = 0;
hpBar.y = 20;
updateHpBar();
// UI: Coin Text
coinText = new Text2("₺ 0", {
size: 70,
fill: 0xFFE066
});
coinText.anchor.set(1, 0);
LK.gui.topRight.addChild(coinText);
coinText.x = -40;
coinText.y = 20;
// UI: Wave Text
waveText = new Text2("Dalga: 1", {
size: 60,
fill: "#fff"
});
waveText.anchor.set(0, 0);
LK.gui.top.addChild(waveText);
waveText.x = 60;
waveText.y = 120;
// UI: Build Panel
createBuildPanel();
// Start first wave
startWave();
updateCoinText();
updateWaveText();
9mm ammo. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
içinde c yazan sarı renkli bir madeni para. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
clash of clans klan kalesi. In-Game asset. 2d. High contrast. No shadows
the zombie. In-Game asset. 2d. High contrast. No shadows
gray block. In-Game asset. 2d. High contrast. No shadows
trap. In-Game asset. 2d. High contrast. No shadows
arazi pxelart sadece yeşillikle dolu. In-Game asset. 2d. High contrast. No shadows
elinde silahı olan bir asker. In-Game asset. 2d. High contrast. No shadows