/****
* 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