/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // BucketheadZombie: stronger zombie with a tin bucket on its head var BucketheadZombie = Container.expand(function () { var self = Container.call(this); // Use the same zombie base image, but add a tin bucket shape on top var zombieGfx = self.attachAsset('zombie', { anchorX: 0.5, anchorY: 0.5 }); // Tin bucket: gray box on head var bucketGfx = self.attachAsset('buckethead_bucket', { anchorX: 0.5, anchorY: 1.0, x: zombieGfx.width / 2, y: zombieGfx.height * 0.18, // place on head scaleX: 0.7, scaleY: 0.5 }); self.addChild(bucketGfx); self.lane = 0; self.speed = 1.0 + Math.random() * 1.5; // Slightly slower than normal zombie self.originalSpeed = self.speed; self.slowTimer = 0; self.hp = 60; // Even more HP for buckethead zombie self.eating = false; self.targetPlant = null; self.update = function () { if (self.slowTimer && self.slowTimer > 0) { self.slowTimer--; if (self.slowTimer === 0) { self.speed = self.originalSpeed; } } if (self.eating && self.targetPlant && !self.targetPlant.destroyed) { if (LK.ticks % 30 === 0) { self.targetPlant.takeDamage(1); LK.getSound('zombie_eat').play(); } if (self.targetPlant.destroyed) { self.eating = false; self.targetPlant.destroy(); plants.splice(plants.indexOf(self.targetPlant), 1); self.targetPlant = null; } } else { self.x -= self.speed; } }; self.takeDamage = function (dmg) { self.hp -= dmg; if (self.hp <= 0) { self.destroyed = true; } }; return self; }); // Register bucket asset (gray box) // Pea projectile var Pea = Container.expand(function () { var self = Container.call(this); var peaGfx = self.attachAsset('pea', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 18; self.lane = 0; self.update = function () { self.x += self.speed; }; return self; }); // Peashooter plant var Peashooter = Container.expand(function () { var self = Container.call(this); var plantGfx = self.attachAsset('plant_peashooter', { anchorX: 0.5, anchorY: 0.5 }); self.lane = 0; self.shootCooldown = 0; self.update = function () { if (self.shootCooldown > 0) { self.shootCooldown--; } }; // Called by game when it's time to shoot self.shoot = function () { if (self.shootCooldown <= 0) { var pea = new Pea(); pea.x = self.x + 60; pea.y = self.y; pea.lane = self.lane; pea.lastX = pea.x; pea.lastIntersecting = false; peas.push(pea); game.addChild(pea); LK.getSound('pea_shoot').play(); self.shootCooldown = 40; // About 0.66s at 60fps } }; // Add takeDamage method so zombies can attack peashooters self.takeDamage = function (dmg) { // Peashooters have 10 HP by default if (typeof self.hp === "undefined") self.hp = 10; self.hp -= dmg; if (self.hp <= 0) { self.destroyed = true; } }; return self; }); // Sun (currency) var Sun = Container.expand(function () { var self = Container.call(this); var sunGfx = self.attachAsset('sun', { anchorX: 0.5, anchorY: 0.5 }); self.fallSpeed = 4; self.update = function () { self.y += self.fallSpeed; }; return self; }); // Wall-nut plant (blocks zombies) var Wallnut = Container.expand(function () { var self = Container.call(this); var plantGfx = self.attachAsset('plant_wallnut', { anchorX: 0.5, anchorY: 0.5 }); self.lane = 0; self.hp = 20; self.update = function () {}; self.takeDamage = function (dmg) { self.hp -= dmg; if (self.hp <= 0) { self.destroyed = true; } }; return self; }); // Zombie var Zombie = Container.expand(function () { var self = Container.call(this); var zombieGfx = self.attachAsset('zombie', { anchorX: 0.5, anchorY: 0.5 }); self.lane = 0; self.speed = 1.0 + Math.random() * 2.0; // Random speed between 1.0 and 3.0 for more variation self.originalSpeed = self.speed; self.slowTimer = 0; self.hp = 10; self.eating = false; self.targetPlant = null; self.update = function () { // Handle slow effect timer if (self.slowTimer && self.slowTimer > 0) { self.slowTimer--; if (self.slowTimer === 0) { self.speed = self.originalSpeed; } } if (self.eating && self.targetPlant && !self.targetPlant.destroyed) { // Eat plant if (LK.ticks % 30 === 0) { self.targetPlant.takeDamage(1); LK.getSound('zombie_eat').play(); } if (self.targetPlant.destroyed) { self.eating = false; self.targetPlant.destroy(); plants.splice(plants.indexOf(self.targetPlant), 1); self.targetPlant = null; } } else { self.x -= self.speed; } }; self.takeDamage = function (dmg) { self.hp -= dmg; if (self.hp <= 0) { self.destroyed = true; } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x3e7d2c }); /**** * Game Code ****/ // Register bucket asset (gray box) // This flag signals the game is ready for publishing. // --- PUBLISH FLAG --- // Sound // Sun (currency) // Lawn tile // Zombie // Pea projectile // Plants // --- Game constants --- var PUBLISH_READY = true; var NUM_LANES = 5; var LANE_HEIGHT = 2732 / NUM_LANES; var TILE_SIZE = 140; var BOARD_LEFT = 220; var BOARD_RIGHT = 2048 - 80; var BOARD_TOP = 80; var BOARD_BOTTOM = 2732 - 80; var TILES_PER_LANE = 9; var PLANT_COST = { peashooter: 100, wallnut: 50 }; var INITIAL_SUN = 150; var ZOMBIE_SPAWN_INTERVAL = 120; // 2s (initial, will decrease as waves progress) var MAX_WAVES = 10; var ZOMBIES_PER_WAVE = 6; // initial, will increase as waves progress // --- Game state --- var boardTiles = []; var plants = []; var zombies = []; var peas = []; var suns = []; var sunPoints = INITIAL_SUN; var selectedPlantType = null; var dragPlantGhost = null; var placingPlant = false; var lastZombieSpawnTick = 0; var currentWave = 1; var zombiesSpawnedThisWave = 0; var zombiesDefeated = 0; var gameOver = false; var youWin = false; var zombiesEscaped = 0; // --- GUI elements --- var scoreTxt = new Text2('0', { size: 100, fill: "#fff" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); var sunTxt = new Text2(sunPoints + '', { size: 80, fill: 0xFFE600 }); sunTxt.anchor.set(0, 0); LK.gui.topRight.addChild(sunTxt); // Plant selection buttons var plantBtns = []; function createPlantBtn(type, x, y, color, label) { var btn = LK.getAsset(type === 'peashooter' ? 'plant_peashooter' : 'plant_wallnut', { anchorX: 0.5, anchorY: 0.5, x: x, y: y, scaleX: 0.8, scaleY: 0.8 }); var txt = new Text2(label, { size: 40, fill: "#fff" }); txt.anchor.set(0.5, 0.5); txt.x = btn.width / 2; txt.y = btn.height / 2; btn.addChild(txt); btn.type = type; btn.down = function (x, y, obj) { selectedPlantType = type; if (dragPlantGhost) dragPlantGhost.destroy(); dragPlantGhost = LK.getAsset(type === 'peashooter' ? 'plant_peashooter' : 'plant_wallnut', { anchorX: 0.5, anchorY: 0.5, x: x, y: y, alpha: 0.6, scaleX: 0.8, scaleY: 0.8 }); game.addChild(dragPlantGhost); placingPlant = true; }; plantBtns.push(btn); LK.gui.left.addChild(btn); } createPlantBtn('peashooter', 100, 300, 0x4caf50, '100'); createPlantBtn('wallnut', 100, 500, 0x8d6e63, '50'); // --- Board setup --- for (var lane = 0; lane < NUM_LANES; lane++) { boardTiles[lane] = []; for (var col = 0; col < TILES_PER_LANE; col++) { var tileX = BOARD_LEFT + col * TILE_SIZE + TILE_SIZE / 2; var tileY = BOARD_TOP + lane * LANE_HEIGHT + LANE_HEIGHT / 2; var tile = LK.getAsset('tile', { anchorX: 0.5, anchorY: 0.5, x: tileX, y: tileY, scaleX: 1, scaleY: 1 }); tile.lane = lane; tile.col = col; tile.x = tileX; tile.y = tileY; boardTiles[lane][col] = tile; game.addChild(tile); } } // --- Sun spawning --- function spawnSun() { var col = Math.floor(Math.random() * TILES_PER_LANE); var lane = Math.floor(Math.random() * NUM_LANES); var tile = boardTiles[lane][col]; var sun = new Sun(); sun.x = tile.x; sun.y = tile.y - 200; sun.lastY = sun.y; suns.push(sun); game.addChild(sun); } // --- Zombie spawning --- function spawnZombie() { var lane = Math.floor(Math.random() * NUM_LANES); // 25% chance to spawn a BucketheadZombie, but only after wave 2 var zombie; if (currentWave >= 2 && Math.random() < 0.25) { zombie = new BucketheadZombie(); } else { zombie = new Zombie(); } zombie.lane = lane; zombie.x = BOARD_RIGHT + 80; zombie.y = BOARD_TOP + lane * LANE_HEIGHT + LANE_HEIGHT / 2; zombie.lastX = zombie.x; zombie.lastIntersecting = false; zombies.push(zombie); game.addChild(zombie); zombiesSpawnedThisWave++; } // --- Plant placement logic --- function getTileAt(x, y) { for (var lane = 0; lane < NUM_LANES; lane++) { for (var col = 0; col < TILES_PER_LANE; col++) { var tile = boardTiles[lane][col]; var dx = x - tile.x; var dy = y - tile.y; if (Math.abs(dx) < TILE_SIZE / 2 && Math.abs(dy) < TILE_SIZE / 2) { return { tile: tile, lane: lane, col: col }; } } } return null; } function isTileOccupied(lane, col) { for (var i = 0; i < plants.length; i++) { if (plants[i].lane === lane && plants[i].col === col) return true; } return false; } // --- Game move/drag logic --- game.move = function (x, y, obj) { if (placingPlant && dragPlantGhost) { dragPlantGhost.x = x; dragPlantGhost.y = y; } }; // --- Game down/up logic --- game.down = function (x, y, obj) { // Check if clicking on plant button handled by button itself if (placingPlant && selectedPlantType && dragPlantGhost) { var tileInfo = getTileAt(x, y); if (tileInfo && !isTileOccupied(tileInfo.lane, tileInfo.col)) { // Place plant if enough sun var cost = PLANT_COST[selectedPlantType]; if (sunPoints >= cost) { var plant; if (selectedPlantType === 'peashooter') { plant = new Peashooter(); } else if (selectedPlantType === 'wallnut') { plant = new Wallnut(); } plant.x = tileInfo.tile.x; plant.y = tileInfo.tile.y; plant.lane = tileInfo.lane; plant.col = tileInfo.col; plants.push(plant); game.addChild(plant); sunPoints -= cost; sunTxt.setText(sunPoints + ''); LK.getSound('plant').play(); } } dragPlantGhost.destroy(); dragPlantGhost = null; placingPlant = false; selectedPlantType = null; } else { // Check if clicking on a sun for (var i = suns.length - 1; i >= 0; i--) { var sun = suns[i]; var dx = x - sun.x; var dy = y - sun.y; if (dx * dx + dy * dy < 60 * 60) { sunPoints += 25; sunTxt.setText(sunPoints + ''); sun.destroy(); suns.splice(i, 1); break; } } } }; game.up = function (x, y, obj) { // No drag logic for now }; // --- Main game update loop --- game.update = function () { if (gameOver || youWin) return; // --- Sun logic --- if (LK.ticks % 120 === 0) { spawnSun(); } for (var i = suns.length - 1; i >= 0; i--) { var sun = suns[i]; sun.update(); if (sun.lastY === undefined) sun.lastY = sun.y; if (sun.lastY < BOARD_BOTTOM && sun.y >= BOARD_BOTTOM) { sun.destroy(); suns.splice(i, 1); continue; } sun.lastY = sun.y; } // --- Plant logic --- for (var i = plants.length - 1; i >= 0; i--) { var plant = plants[i]; plant.update(); if (plant.destroyed) { plant.destroy(); plants.splice(i, 1); continue; } // Peashooter: shoot if zombie in lane if (plant instanceof Peashooter) { for (var j = 0; j < zombies.length; j++) { var zombie = zombies[j]; if (zombie.lane === plant.lane && zombie.x > plant.x) { plant.shoot(); break; } } } } // --- Pea logic --- for (var i = peas.length - 1; i >= 0; i--) { var pea = peas[i]; pea.update(); if (pea.lastX === undefined) pea.lastX = pea.x; if (pea.x > BOARD_RIGHT + 100) { pea.destroy(); peas.splice(i, 1); continue; } // Check collision with zombies in same lane for (var j = 0; j < zombies.length; j++) { var zombie = zombies[j]; if (zombie.lane === pea.lane && Math.abs(zombie.x - pea.x) < 60) { zombie.takeDamage(3); // No slow effect on zombie when hit by pea pea.destroy(); peas.splice(i, 1); if (zombie.hp <= 0) { LK.getSound('zombie_die').play(); zombie.destroyed = true; LK.setScore(LK.getScore() + 1); scoreTxt.setText(LK.getScore()); zombiesDefeated++; } break; } } pea.lastX = pea.x; } // --- Zombie logic --- for (var i = zombies.length - 1; i >= 0; i--) { var zombie = zombies[i]; zombie.update(); if (zombie.lastX === undefined) zombie.lastX = zombie.x; if (zombie.destroyed) { zombie.destroy(); zombies.splice(i, 1); continue; } // Check collision with plants in same lane if (!zombie.eating) { for (var j = 0; j < plants.length; j++) { var plant = plants[j]; if (plant.lane === zombie.lane && Math.abs(plant.x - zombie.x) < 60) { zombie.eating = true; zombie.targetPlant = plant; break; } } } // Check if zombie reached left edge if (zombie.x < BOARD_LEFT - 60) { if (typeof zombiesEscaped === "undefined") zombiesEscaped = 0; zombiesEscaped++; zombie.destroy(); zombies.splice(i, 1); if (zombiesEscaped >= 3) { LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); gameOver = true; return; } continue; } zombie.lastX = zombie.x; } // --- Zombie wave logic --- // Dynamically adjust spawn interval and zombies per wave as waves progress // Make zombies come faster and in greater numbers as time goes on var minSpawnInterval = 40; // minimum interval (faster) var maxZombiesPerWave = 20; // cap for zombies per wave // Calculate current values based on wave var currentSpawnInterval = Math.max(ZOMBIE_SPAWN_INTERVAL - (currentWave - 1) * 10, minSpawnInterval); var currentZombiesPerWave = Math.min(ZOMBIES_PER_WAVE + (currentWave - 1) * 2, maxZombiesPerWave); if (currentWave <= MAX_WAVES) { if (zombiesSpawnedThisWave < currentZombiesPerWave) { if (LK.ticks - lastZombieSpawnTick > currentSpawnInterval) { spawnZombie(); lastZombieSpawnTick = LK.ticks; } } else if (zombies.length === 0 && zombiesSpawnedThisWave === currentZombiesPerWave) { // Next wave currentWave++; zombiesSpawnedThisWave = 0; lastZombieSpawnTick = LK.ticks; } } else if (zombies.length === 0 && zombiesSpawnedThisWave === currentZombiesPerWave) { // Win condition LK.effects.flashScreen(0x00ff00, 1000); LK.showYouWin(); youWin = true; return; } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// BucketheadZombie: stronger zombie with a tin bucket on its head
var BucketheadZombie = Container.expand(function () {
var self = Container.call(this);
// Use the same zombie base image, but add a tin bucket shape on top
var zombieGfx = self.attachAsset('zombie', {
anchorX: 0.5,
anchorY: 0.5
});
// Tin bucket: gray box on head
var bucketGfx = self.attachAsset('buckethead_bucket', {
anchorX: 0.5,
anchorY: 1.0,
x: zombieGfx.width / 2,
y: zombieGfx.height * 0.18,
// place on head
scaleX: 0.7,
scaleY: 0.5
});
self.addChild(bucketGfx);
self.lane = 0;
self.speed = 1.0 + Math.random() * 1.5; // Slightly slower than normal zombie
self.originalSpeed = self.speed;
self.slowTimer = 0;
self.hp = 60; // Even more HP for buckethead zombie
self.eating = false;
self.targetPlant = null;
self.update = function () {
if (self.slowTimer && self.slowTimer > 0) {
self.slowTimer--;
if (self.slowTimer === 0) {
self.speed = self.originalSpeed;
}
}
if (self.eating && self.targetPlant && !self.targetPlant.destroyed) {
if (LK.ticks % 30 === 0) {
self.targetPlant.takeDamage(1);
LK.getSound('zombie_eat').play();
}
if (self.targetPlant.destroyed) {
self.eating = false;
self.targetPlant.destroy();
plants.splice(plants.indexOf(self.targetPlant), 1);
self.targetPlant = null;
}
} else {
self.x -= self.speed;
}
};
self.takeDamage = function (dmg) {
self.hp -= dmg;
if (self.hp <= 0) {
self.destroyed = true;
}
};
return self;
});
// Register bucket asset (gray box)
// Pea projectile
var Pea = Container.expand(function () {
var self = Container.call(this);
var peaGfx = self.attachAsset('pea', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 18;
self.lane = 0;
self.update = function () {
self.x += self.speed;
};
return self;
});
// Peashooter plant
var Peashooter = Container.expand(function () {
var self = Container.call(this);
var plantGfx = self.attachAsset('plant_peashooter', {
anchorX: 0.5,
anchorY: 0.5
});
self.lane = 0;
self.shootCooldown = 0;
self.update = function () {
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
};
// Called by game when it's time to shoot
self.shoot = function () {
if (self.shootCooldown <= 0) {
var pea = new Pea();
pea.x = self.x + 60;
pea.y = self.y;
pea.lane = self.lane;
pea.lastX = pea.x;
pea.lastIntersecting = false;
peas.push(pea);
game.addChild(pea);
LK.getSound('pea_shoot').play();
self.shootCooldown = 40; // About 0.66s at 60fps
}
};
// Add takeDamage method so zombies can attack peashooters
self.takeDamage = function (dmg) {
// Peashooters have 10 HP by default
if (typeof self.hp === "undefined") self.hp = 10;
self.hp -= dmg;
if (self.hp <= 0) {
self.destroyed = true;
}
};
return self;
});
// Sun (currency)
var Sun = Container.expand(function () {
var self = Container.call(this);
var sunGfx = self.attachAsset('sun', {
anchorX: 0.5,
anchorY: 0.5
});
self.fallSpeed = 4;
self.update = function () {
self.y += self.fallSpeed;
};
return self;
});
// Wall-nut plant (blocks zombies)
var Wallnut = Container.expand(function () {
var self = Container.call(this);
var plantGfx = self.attachAsset('plant_wallnut', {
anchorX: 0.5,
anchorY: 0.5
});
self.lane = 0;
self.hp = 20;
self.update = function () {};
self.takeDamage = function (dmg) {
self.hp -= dmg;
if (self.hp <= 0) {
self.destroyed = true;
}
};
return self;
});
// Zombie
var Zombie = Container.expand(function () {
var self = Container.call(this);
var zombieGfx = self.attachAsset('zombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.lane = 0;
self.speed = 1.0 + Math.random() * 2.0; // Random speed between 1.0 and 3.0 for more variation
self.originalSpeed = self.speed;
self.slowTimer = 0;
self.hp = 10;
self.eating = false;
self.targetPlant = null;
self.update = function () {
// Handle slow effect timer
if (self.slowTimer && self.slowTimer > 0) {
self.slowTimer--;
if (self.slowTimer === 0) {
self.speed = self.originalSpeed;
}
}
if (self.eating && self.targetPlant && !self.targetPlant.destroyed) {
// Eat plant
if (LK.ticks % 30 === 0) {
self.targetPlant.takeDamage(1);
LK.getSound('zombie_eat').play();
}
if (self.targetPlant.destroyed) {
self.eating = false;
self.targetPlant.destroy();
plants.splice(plants.indexOf(self.targetPlant), 1);
self.targetPlant = null;
}
} else {
self.x -= self.speed;
}
};
self.takeDamage = function (dmg) {
self.hp -= dmg;
if (self.hp <= 0) {
self.destroyed = true;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x3e7d2c
});
/****
* Game Code
****/
// Register bucket asset (gray box)
// This flag signals the game is ready for publishing.
// --- PUBLISH FLAG ---
// Sound
// Sun (currency)
// Lawn tile
// Zombie
// Pea projectile
// Plants
// --- Game constants ---
var PUBLISH_READY = true;
var NUM_LANES = 5;
var LANE_HEIGHT = 2732 / NUM_LANES;
var TILE_SIZE = 140;
var BOARD_LEFT = 220;
var BOARD_RIGHT = 2048 - 80;
var BOARD_TOP = 80;
var BOARD_BOTTOM = 2732 - 80;
var TILES_PER_LANE = 9;
var PLANT_COST = {
peashooter: 100,
wallnut: 50
};
var INITIAL_SUN = 150;
var ZOMBIE_SPAWN_INTERVAL = 120; // 2s (initial, will decrease as waves progress)
var MAX_WAVES = 10;
var ZOMBIES_PER_WAVE = 6; // initial, will increase as waves progress
// --- Game state ---
var boardTiles = [];
var plants = [];
var zombies = [];
var peas = [];
var suns = [];
var sunPoints = INITIAL_SUN;
var selectedPlantType = null;
var dragPlantGhost = null;
var placingPlant = false;
var lastZombieSpawnTick = 0;
var currentWave = 1;
var zombiesSpawnedThisWave = 0;
var zombiesDefeated = 0;
var gameOver = false;
var youWin = false;
var zombiesEscaped = 0;
// --- GUI elements ---
var scoreTxt = new Text2('0', {
size: 100,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var sunTxt = new Text2(sunPoints + '', {
size: 80,
fill: 0xFFE600
});
sunTxt.anchor.set(0, 0);
LK.gui.topRight.addChild(sunTxt);
// Plant selection buttons
var plantBtns = [];
function createPlantBtn(type, x, y, color, label) {
var btn = LK.getAsset(type === 'peashooter' ? 'plant_peashooter' : 'plant_wallnut', {
anchorX: 0.5,
anchorY: 0.5,
x: x,
y: y,
scaleX: 0.8,
scaleY: 0.8
});
var txt = new Text2(label, {
size: 40,
fill: "#fff"
});
txt.anchor.set(0.5, 0.5);
txt.x = btn.width / 2;
txt.y = btn.height / 2;
btn.addChild(txt);
btn.type = type;
btn.down = function (x, y, obj) {
selectedPlantType = type;
if (dragPlantGhost) dragPlantGhost.destroy();
dragPlantGhost = LK.getAsset(type === 'peashooter' ? 'plant_peashooter' : 'plant_wallnut', {
anchorX: 0.5,
anchorY: 0.5,
x: x,
y: y,
alpha: 0.6,
scaleX: 0.8,
scaleY: 0.8
});
game.addChild(dragPlantGhost);
placingPlant = true;
};
plantBtns.push(btn);
LK.gui.left.addChild(btn);
}
createPlantBtn('peashooter', 100, 300, 0x4caf50, '100');
createPlantBtn('wallnut', 100, 500, 0x8d6e63, '50');
// --- Board setup ---
for (var lane = 0; lane < NUM_LANES; lane++) {
boardTiles[lane] = [];
for (var col = 0; col < TILES_PER_LANE; col++) {
var tileX = BOARD_LEFT + col * TILE_SIZE + TILE_SIZE / 2;
var tileY = BOARD_TOP + lane * LANE_HEIGHT + LANE_HEIGHT / 2;
var tile = LK.getAsset('tile', {
anchorX: 0.5,
anchorY: 0.5,
x: tileX,
y: tileY,
scaleX: 1,
scaleY: 1
});
tile.lane = lane;
tile.col = col;
tile.x = tileX;
tile.y = tileY;
boardTiles[lane][col] = tile;
game.addChild(tile);
}
}
// --- Sun spawning ---
function spawnSun() {
var col = Math.floor(Math.random() * TILES_PER_LANE);
var lane = Math.floor(Math.random() * NUM_LANES);
var tile = boardTiles[lane][col];
var sun = new Sun();
sun.x = tile.x;
sun.y = tile.y - 200;
sun.lastY = sun.y;
suns.push(sun);
game.addChild(sun);
}
// --- Zombie spawning ---
function spawnZombie() {
var lane = Math.floor(Math.random() * NUM_LANES);
// 25% chance to spawn a BucketheadZombie, but only after wave 2
var zombie;
if (currentWave >= 2 && Math.random() < 0.25) {
zombie = new BucketheadZombie();
} else {
zombie = new Zombie();
}
zombie.lane = lane;
zombie.x = BOARD_RIGHT + 80;
zombie.y = BOARD_TOP + lane * LANE_HEIGHT + LANE_HEIGHT / 2;
zombie.lastX = zombie.x;
zombie.lastIntersecting = false;
zombies.push(zombie);
game.addChild(zombie);
zombiesSpawnedThisWave++;
}
// --- Plant placement logic ---
function getTileAt(x, y) {
for (var lane = 0; lane < NUM_LANES; lane++) {
for (var col = 0; col < TILES_PER_LANE; col++) {
var tile = boardTiles[lane][col];
var dx = x - tile.x;
var dy = y - tile.y;
if (Math.abs(dx) < TILE_SIZE / 2 && Math.abs(dy) < TILE_SIZE / 2) {
return {
tile: tile,
lane: lane,
col: col
};
}
}
}
return null;
}
function isTileOccupied(lane, col) {
for (var i = 0; i < plants.length; i++) {
if (plants[i].lane === lane && plants[i].col === col) return true;
}
return false;
}
// --- Game move/drag logic ---
game.move = function (x, y, obj) {
if (placingPlant && dragPlantGhost) {
dragPlantGhost.x = x;
dragPlantGhost.y = y;
}
};
// --- Game down/up logic ---
game.down = function (x, y, obj) {
// Check if clicking on plant button handled by button itself
if (placingPlant && selectedPlantType && dragPlantGhost) {
var tileInfo = getTileAt(x, y);
if (tileInfo && !isTileOccupied(tileInfo.lane, tileInfo.col)) {
// Place plant if enough sun
var cost = PLANT_COST[selectedPlantType];
if (sunPoints >= cost) {
var plant;
if (selectedPlantType === 'peashooter') {
plant = new Peashooter();
} else if (selectedPlantType === 'wallnut') {
plant = new Wallnut();
}
plant.x = tileInfo.tile.x;
plant.y = tileInfo.tile.y;
plant.lane = tileInfo.lane;
plant.col = tileInfo.col;
plants.push(plant);
game.addChild(plant);
sunPoints -= cost;
sunTxt.setText(sunPoints + '');
LK.getSound('plant').play();
}
}
dragPlantGhost.destroy();
dragPlantGhost = null;
placingPlant = false;
selectedPlantType = null;
} else {
// Check if clicking on a sun
for (var i = suns.length - 1; i >= 0; i--) {
var sun = suns[i];
var dx = x - sun.x;
var dy = y - sun.y;
if (dx * dx + dy * dy < 60 * 60) {
sunPoints += 25;
sunTxt.setText(sunPoints + '');
sun.destroy();
suns.splice(i, 1);
break;
}
}
}
};
game.up = function (x, y, obj) {
// No drag logic for now
};
// --- Main game update loop ---
game.update = function () {
if (gameOver || youWin) return;
// --- Sun logic ---
if (LK.ticks % 120 === 0) {
spawnSun();
}
for (var i = suns.length - 1; i >= 0; i--) {
var sun = suns[i];
sun.update();
if (sun.lastY === undefined) sun.lastY = sun.y;
if (sun.lastY < BOARD_BOTTOM && sun.y >= BOARD_BOTTOM) {
sun.destroy();
suns.splice(i, 1);
continue;
}
sun.lastY = sun.y;
}
// --- Plant logic ---
for (var i = plants.length - 1; i >= 0; i--) {
var plant = plants[i];
plant.update();
if (plant.destroyed) {
plant.destroy();
plants.splice(i, 1);
continue;
}
// Peashooter: shoot if zombie in lane
if (plant instanceof Peashooter) {
for (var j = 0; j < zombies.length; j++) {
var zombie = zombies[j];
if (zombie.lane === plant.lane && zombie.x > plant.x) {
plant.shoot();
break;
}
}
}
}
// --- Pea logic ---
for (var i = peas.length - 1; i >= 0; i--) {
var pea = peas[i];
pea.update();
if (pea.lastX === undefined) pea.lastX = pea.x;
if (pea.x > BOARD_RIGHT + 100) {
pea.destroy();
peas.splice(i, 1);
continue;
}
// Check collision with zombies in same lane
for (var j = 0; j < zombies.length; j++) {
var zombie = zombies[j];
if (zombie.lane === pea.lane && Math.abs(zombie.x - pea.x) < 60) {
zombie.takeDamage(3);
// No slow effect on zombie when hit by pea
pea.destroy();
peas.splice(i, 1);
if (zombie.hp <= 0) {
LK.getSound('zombie_die').play();
zombie.destroyed = true;
LK.setScore(LK.getScore() + 1);
scoreTxt.setText(LK.getScore());
zombiesDefeated++;
}
break;
}
}
pea.lastX = pea.x;
}
// --- Zombie logic ---
for (var i = zombies.length - 1; i >= 0; i--) {
var zombie = zombies[i];
zombie.update();
if (zombie.lastX === undefined) zombie.lastX = zombie.x;
if (zombie.destroyed) {
zombie.destroy();
zombies.splice(i, 1);
continue;
}
// Check collision with plants in same lane
if (!zombie.eating) {
for (var j = 0; j < plants.length; j++) {
var plant = plants[j];
if (plant.lane === zombie.lane && Math.abs(plant.x - zombie.x) < 60) {
zombie.eating = true;
zombie.targetPlant = plant;
break;
}
}
}
// Check if zombie reached left edge
if (zombie.x < BOARD_LEFT - 60) {
if (typeof zombiesEscaped === "undefined") zombiesEscaped = 0;
zombiesEscaped++;
zombie.destroy();
zombies.splice(i, 1);
if (zombiesEscaped >= 3) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
gameOver = true;
return;
}
continue;
}
zombie.lastX = zombie.x;
}
// --- Zombie wave logic ---
// Dynamically adjust spawn interval and zombies per wave as waves progress
// Make zombies come faster and in greater numbers as time goes on
var minSpawnInterval = 40; // minimum interval (faster)
var maxZombiesPerWave = 20; // cap for zombies per wave
// Calculate current values based on wave
var currentSpawnInterval = Math.max(ZOMBIE_SPAWN_INTERVAL - (currentWave - 1) * 10, minSpawnInterval);
var currentZombiesPerWave = Math.min(ZOMBIES_PER_WAVE + (currentWave - 1) * 2, maxZombiesPerWave);
if (currentWave <= MAX_WAVES) {
if (zombiesSpawnedThisWave < currentZombiesPerWave) {
if (LK.ticks - lastZombieSpawnTick > currentSpawnInterval) {
spawnZombie();
lastZombieSpawnTick = LK.ticks;
}
} else if (zombies.length === 0 && zombiesSpawnedThisWave === currentZombiesPerWave) {
// Next wave
currentWave++;
zombiesSpawnedThisWave = 0;
lastZombieSpawnTick = LK.ticks;
}
} else if (zombies.length === 0 && zombiesSpawnedThisWave === currentZombiesPerWave) {
// Win condition
LK.effects.flashScreen(0x00ff00, 1000);
LK.showYouWin();
youWin = true;
return;
}
};
PLANT VS ZOMBİ DEKİ ZOMBİ. In-Game asset. 2d. High contrast. No shadows
PLANT VS ZOMBİ DEKİ BEZELYE. In-Game asset. 2d. High contrast. No shadows
PLANT VS ZOMBİ DEKİ GÜNEŞ. In-Game asset. 2d. High contrast. No shadows
PLANT VS ZOMBİ DEKİ ÇİM. In-Game asset. 2d. High contrast. No shadows
PLANT VS ZOMBİ DEKİ CEVİZ. In-Game asset. 2d. High contrast. No shadows
PLANT VS ZOMBİ DEKİ KAFASINDA TENEKE OLAN ZOMBİ. In-Game asset. 2d. High contrast. No shadows