/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { highScore: 0 }); /**** * Classes ****/ var AmmoDisplay = Container.expand(function () { var self = Container.call(this); self.indicators = []; self.player = null; self.init = function (playerRef) { self.player = playerRef; // Clear existing indicators for (var i = 0; i < self.indicators.length; i++) { self.removeChild(self.indicators[i]); } self.indicators = []; // Create new indicators for (var i = 0; i < self.player.maxAmmo; i++) { var indicator = LK.getAsset('ammoIndicator', { anchorX: 0.5, anchorY: 0.5, x: i * 20, y: 0 }); self.addChild(indicator); self.indicators.push(indicator); } }; self.update = function () { if (!self.player) { return; } // Update visibility based on current ammo for (var i = 0; i < self.indicators.length; i++) { self.indicators[i].visible = i < self.player.ammo; // Fade effect when reloading if (self.player.isReloading) { var reloadProgress = 1 - self.player.reloadCounter / self.player.reloadTime; if (i < Math.floor(reloadProgress * self.player.maxAmmo)) { self.indicators[i].visible = true; self.indicators[i].alpha = 0.5; } else { self.indicators[i].visible = false; } } else { self.indicators[i].alpha = 1; } } }; return self; }); var Blood = Container.expand(function () { var self = Container.call(this); var bloodGraphics = self.attachAsset('blood', { anchorX: 0.5, anchorY: 0.5, alpha: 0.8 }); self.lifespan = 60; // Frames self.init = function (x, y) { self.x = x; self.y = y; self.scale.x = 0.5 + Math.random() * 0.5; self.scale.y = 0.5 + Math.random() * 0.5; self.rotation = Math.random() * Math.PI * 2; self.alpha = 0.8; }; self.update = function () { self.lifespan--; if (self.lifespan <= 20) { self.alpha = self.lifespan / 20 * 0.8; } if (self.lifespan <= 0) { self.shouldDestroy = true; } }; return self; }); var Bullet = Container.expand(function () { var self = Container.call(this); var bulletGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 15; self.damage = 25; self.direction = { x: 0, y: 0 }; self.shooter = null; self.update = function () { self.x += self.direction.x * self.speed; self.y += self.direction.y * self.speed; // Destroy bullet if it goes off screen if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) { self.shouldDestroy = true; } }; self.fire = function (startX, startY, targetX, targetY, shooterRef) { self.x = startX; self.y = startY; self.shooter = shooterRef; // Calculate direction var dx = targetX - startX; var dy = targetY - startY; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { self.direction.x = dx / distance; self.direction.y = dy / distance; } }; return self; }); var HealthBar = Container.expand(function () { var self = Container.call(this); var background = self.attachAsset('healthBarBackground', { anchorX: 0, anchorY: 0 }); var bar = self.attachAsset('healthBar', { anchorX: 0, anchorY: 0, x: 2, y: 2 }); var playerText = new Text2("P1", { size: 20, fill: 0xFFFFFF }); playerText.anchor.set(0.5); playerText.x = background.width / 2; playerText.y = background.height / 2; self.addChild(playerText); self.player = null; self.init = function (playerRef, isPlayer1) { self.player = playerRef; playerText.setText(isPlayer1 ? "P1" : "P2"); }; self.update = function () { if (!self.player) { return; } var healthPercent = self.player.health / self.player.maxHealth; bar.scale.x = healthPercent; }; return self; }); var Player = Container.expand(function () { var self = Container.call(this); self.isPlayer1 = true; // Will be set when initializing var graphicsId = 'player1'; // Default self.health = 100; self.maxHealth = 100; self.moveSpeed = 5; self.fireRate = 15; // Frames between shots self.fireCounter = 0; self.ammo = 12; self.maxAmmo = 12; self.reloadTime = 120; // 2 seconds at 60fps self.reloadCounter = 0; self.isReloading = false; self.isDead = false; self.lastMoveDirection = { x: 0, y: -1 }; // Default facing up self.init = function (isFirstPlayer) { self.isPlayer1 = isFirstPlayer; graphicsId = isFirstPlayer ? 'player1' : 'player2'; if (self.graphics) { self.removeChild(self.graphics); } self.graphics = self.attachAsset(graphicsId, { anchorX: 0.5, anchorY: 0.5, tint: isFirstPlayer ? 0xff0000 : 0x0000ff // Red for player1, Blue for player2 }); self.health = self.maxHealth; var nameTag = new Text2(isFirstPlayer ? "Player 1" : "Player 2", { // Name tag size: 20, fill: 0xFFFFFF }); nameTag.anchor.set(0.5); nameTag.y = -self.graphics.height / 2 - 10; // Position above the player self.addChild(nameTag); self.ammo = self.maxAmmo; self.fireCounter = 0; self.reloadCounter = 0; self.isReloading = false; self.isDead = false; }; self.takeDamage = function (amount) { if (self.isDead) { return; } self.health -= amount; if (self.health <= 0) { self.health = 0; self.die(); } else { // Flash red when hit LK.effects.flashObject(self, self.isPlayer1 ? 0xff0000 : 0x0000ff, 300); // Red flash for player1, Blue flash for player2 LK.getSound('playerHit').play(); } }; self.die = function () { self.isDead = true; self.alpha = 0.5; // Flash red when dead LK.effects.flashObject(self, 0xff0000, 1000); }; self.shoot = function (targetX, targetY) { if (self.isDead || self.isReloading) { return null; } if (self.ammo <= 0) { self.reload(); return null; } if (self.fireCounter <= 0) { // Create bullet var bullet = new Bullet(); bullet.fire(self.x, self.y, targetX, targetY, self); // Update state self.ammo--; self.fireCounter = self.fireRate; // Keep track of facing direction based on shot var dx = targetX - self.x; var dy = targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { self.lastMoveDirection.x = dx / distance; self.lastMoveDirection.y = dy / distance; } // Play sound LK.getSound('shoot').play(); if (self.isPlayer1) { LK.getSound('player1Shoot').play(); // Unique sound for player 1 } else { LK.getSound('player2Shoot').play(); // Unique sound for player 2 } return bullet; } return null; }; self.reload = function () { if (self.isDead || self.isReloading || self.ammo === self.maxAmmo) { return; } self.isReloading = true; self.reloadCounter = self.reloadTime; LK.getSound('reload').play(); }; self.move = function (dx, dy) { if (self.isDead) { return; } // If moving, update last move direction if (dx !== 0 || dy !== 0) { var length = Math.sqrt(dx * dx + dy * dy); if (length > 0) { self.lastMoveDirection.x = dx / length; self.lastMoveDirection.y = dy / length; } } // Apply movement var newX = self.x + dx * self.moveSpeed; var newY = self.y + dy * self.moveSpeed; // Constrain to game bounds with a margin var margin = 50; newX = Math.max(margin, Math.min(2048 - margin, newX)); newY = Math.max(margin, Math.min(2732 - margin, newY)); self.x = newX; self.y = newY; }; self.update = function () { // Update reload state if (self.isReloading) { self.reloadCounter--; if (self.reloadCounter <= 0) { self.isReloading = false; self.ammo = self.maxAmmo; } } // Update fire rate counter if (self.fireCounter > 0) { self.fireCounter--; } // AI behavior for Player 2 if (!self.isPlayer1 && !self.isDead) { self.updateAI(); } }; self.updateAI = function () { // Find the nearest zombie var nearestZombie = null; var nearestDistance = Number.MAX_VALUE; for (var i = 0; i < zombies.length; i++) { var zombie = zombies[i]; var distance = getDistance(self, zombie); if (distance < nearestDistance) { nearestDistance = distance; nearestZombie = zombie; } } // Move and shoot logic if (nearestZombie) { // If zombie is close, move away from it if (nearestDistance < 300) { var dx = self.x - nearestZombie.x; var dy = self.y - nearestZombie.y; var length = Math.sqrt(dx * dx + dy * dy); if (length > 0) { dx /= length; dy /= length; self.move(dx, dy); } } // Otherwise, stay close to player 1 else { var distToPlayer1 = getDistance(self, player1); if (distToPlayer1 > 300) { var dx = player1.x - self.x; var dy = player1.y - self.y; var length = Math.sqrt(dx * dx + dy * dy); if (length > 0) { dx /= length; dy /= length; self.move(dx, dy); } } } // Shoot at nearest zombie if not reloading and it's in range if (!self.isReloading && nearestDistance < 600) { var bullet = self.shoot(nearestZombie.x, nearestZombie.y); if (bullet) { bullets.push(bullet); game.addChild(bullet); } } // Reload if low on ammo and no zombies nearby if (self.ammo <= self.maxAmmo / 4 && nearestDistance > 400) { self.reload(); } } }; return self; }); var Zombie = Container.expand(function () { var self = Container.call(this); var zombieGraphics = self.attachAsset('zombie', { anchorX: 0.5, anchorY: 0.5 }); self.health = 100; self.moveSpeed = 2; self.damage = 5; self.attackRate = 60; // 1 second between attacks self.attackCounter = 0; self.isDead = false; self.init = function (x, y, moveSpeed, health) { self.x = x; self.y = y; self.moveSpeed = moveSpeed || 2; self.health = health || 100; }; self.takeDamage = function (amount) { if (self.isDead) { return; } self.health -= amount; // Flash red when hit LK.effects.flashObject(self, 0xff0000, 150); LK.getSound('zombieHit').play(); if (self.health <= 0) { self.die(); } }; self.die = function () { self.isDead = true; self.shouldDestroy = true; // Create blood splatter createBlood(self.x, self.y); // Increase score currentScore += 10; updateScoreText(); }; self.attack = function (player) { if (self.isDead || self.attackCounter > 0) { return; } player.takeDamage(self.damage); self.attackCounter = self.attackRate; }; self.update = function () { if (self.isDead) { return; } // Update attack counter if (self.attackCounter > 0) { self.attackCounter--; } // Target the nearest player that's alive var target = null; if (!player1.isDead && !player2.isDead) { // Target closest player var dist1 = getDistance(self, player1); var dist2 = getDistance(self, player2); target = dist1 < dist2 ? player1 : player2; } else if (!player1.isDead) { target = player1; } else if (!player2.isDead) { target = player2; } if (target) { // Move towards target var dx = target.x - self.x; var dy = target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { var moveX = dx / distance * self.moveSpeed; var moveY = dy / distance * self.moveSpeed; self.x += moveX; self.y += moveY; } // Attack if close enough if (distance < 80) { self.attack(target); // Push the player out if they are stuck inside the zombie if (distance < 40) { var pushX = dx / distance * (40 - distance); var pushY = dy / distance * (40 - distance); target.x += pushX; target.y += pushY; } } } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // Game state variables var player1; var player2; var player1HealthBar; var player2HealthBar; var player1AmmoDisplay; var player2AmmoDisplay; var bullets = []; var zombies = []; var bloodSplatters = []; var currentWave = 0; var zombiesInWave = 0; var zombiesLeft = 0; var currentScore = 0; var gameActive = false; var waveText; var scoreText; var highScoreText; var gameStartText; var groundTiles = []; // Helper functions function getDistance(obj1, obj2) { var dx = obj1.x - obj2.x; var dy = obj1.y - obj2.y; return Math.sqrt(dx * dx + dy * dy); } function createGround() { var tileSize = 128; var tilesX = Math.ceil(2048 / tileSize); var tilesY = Math.ceil(2732 / tileSize); for (var y = 0; y < tilesY; y++) { for (var x = 0; x < tilesX; x++) { var tile = LK.getAsset('groundTile', { anchorX: 0, anchorY: 0, x: x * tileSize, y: y * tileSize, alpha: 0.3 }); // Add slight randomness to tiles tile.tint = 0x666666 + Math.floor(Math.random() * 0x111111); groundTiles.push(tile); game.addChild(tile); } } } function updateScoreText() { scoreText.setText("Score: " + currentScore); // Update high score if needed if (currentScore > storage.highScore) { storage.highScore = currentScore; highScoreText.setText("High Score: " + storage.highScore); } } function createBlood(x, y) { for (var i = 0; i < 3; i++) { var blood = new Blood(); blood.init(x + (Math.random() * 60 - 30), y + (Math.random() * 60 - 30)); bloodSplatters.push(blood); game.addChild(blood); } } function startGame() { // Reset game state gameActive = true; currentWave = 0; currentScore = 0; // Clear any existing entities clearEntities(); // Create players player1 = new Player(); player1.init(true); player1.x = 2048 / 2 - 150; player1.y = 2732 / 2; game.addChild(player1); player2 = new Player(); player2.init(false); player2.x = 2048 / 2 + 150; player2.y = 2732 / 2; game.addChild(player2); // Create health bars player1HealthBar = new HealthBar(); player1HealthBar.init(player1, true); player1HealthBar.x = 20; player1HealthBar.y = 100; LK.gui.left.addChild(player1HealthBar); player2HealthBar = new HealthBar(); player2HealthBar.init(player2, false); player2HealthBar.x = 20; player2HealthBar.y = 160; LK.gui.left.addChild(player2HealthBar); // Create ammo displays player1AmmoDisplay = new AmmoDisplay(); player1AmmoDisplay.init(player1); player1AmmoDisplay.x = 250; player1AmmoDisplay.y = 115; LK.gui.left.addChild(player1AmmoDisplay); player2AmmoDisplay = new AmmoDisplay(); player2AmmoDisplay.init(player2); player2AmmoDisplay.x = 250; player2AmmoDisplay.y = 175; LK.gui.left.addChild(player2AmmoDisplay); // Add UI elements for health and ammo var player1UI = new Container(); player1UI.addChild(player1HealthBar); player1UI.addChild(player1AmmoDisplay); LK.gui.left.addChild(player1UI); var player2UI = new Container(); player2UI.addChild(player2HealthBar); player2UI.addChild(player2AmmoDisplay); LK.gui.left.addChild(player2UI); // Update score display updateScoreText(); // Start first wave startNextWave(); // Hide start game text gameStartText.visible = false; // Play music LK.playMusic('bgMusic', { fade: { start: 0, end: 0.3, duration: 2000 } }); } function clearEntities() { // Remove all bullets for (var i = 0; i < bullets.length; i++) { if (bullets[i].parent) { bullets[i].parent.removeChild(bullets[i]); } } bullets = []; // Remove all zombies for (var i = 0; i < zombies.length; i++) { if (zombies[i].parent) { zombies[i].parent.removeChild(zombies[i]); } } zombies = []; // Remove all blood splatters for (var i = 0; i < bloodSplatters.length; i++) { if (bloodSplatters[i].parent) { bloodSplatters[i].parent.removeChild(bloodSplatters[i]); } } bloodSplatters = []; // Remove players and their UI if they exist if (player1 && player1.parent) { player1.parent.removeChild(player1); } if (player2 && player2.parent) { player2.parent.removeChild(player2); } // Remove UI elements if they exist if (player1HealthBar && player1HealthBar.parent) { player1HealthBar.parent.removeChild(player1HealthBar); } if (player2HealthBar && player2HealthBar.parent) { player2HealthBar.parent.removeChild(player2HealthBar); } if (player1AmmoDisplay && player1AmmoDisplay.parent) { player1AmmoDisplay.parent.removeChild(player1AmmoDisplay); } if (player2AmmoDisplay && player2AmmoDisplay.parent) { player2AmmoDisplay.parent.removeChild(player2AmmoDisplay); } } function spawnZombie() { if (zombiesLeft <= 0) { return; } var zombie = new Zombie(); // Determine zombie spawn position (outside the screen) var spawnSide = Math.floor(Math.random() * 4); // 0: top, 1: right, 2: bottom, 3: left var x, y; switch (spawnSide) { case 0: // top x = Math.random() * 2048; y = -100; break; case 1: // right x = 2148; y = Math.random() * 2732; break; case 2: // bottom x = Math.random() * 2048; y = 2832; break; case 3: // left x = -100; y = Math.random() * 2732; break; } // Scale zombie difficulty based on wave var healthMultiplier = 1 + currentWave * 0.1; var speedMultiplier = 1 + currentWave * 0.05; zombie.init(x, y, 2 * speedMultiplier, 100 * healthMultiplier); zombies.push(zombie); game.addChild(zombie); zombiesLeft--; } function startNextWave() { currentWave++; zombiesInWave = 5 + currentWave * 3; zombiesLeft = zombiesInWave; waveText.setText("Wave " + currentWave); tween(waveText, { alpha: 1 }, { duration: 500 }); // Play wave start sound LK.getSound('waveStart').play(); // After 2 seconds, fade out the wave text LK.setTimeout(function () { tween(waveText, { alpha: 0 }, { duration: 500 }); }, 2000); // Initial zombie spawn for (var i = 0; i < Math.min(5, zombiesInWave); i++) { spawnZombie(); } } function checkGameOver() { if (!gameActive) { return; } // Game over if both players are dead if (player1.isDead && player2.isDead) { gameActive = false; // Show game over screen after a short delay LK.setTimeout(function () { LK.showGameOver(); }, 1500); // Stop music with fade out LK.playMusic('bgMusic', { fade: { start: 0.3, end: 0, duration: 1500 } }); } } function handlePlayerMovement() { if (!gameActive || player1.isDead) { return; } var dx = 0; var dy = 0; if (moveUp) { dy -= 1; } if (moveDown) { dy += 1; } if (moveLeft) { dx -= 1; } if (moveRight) { dx += 1; } // Normalize diagonal movement if (dx !== 0 && dy !== 0) { var length = Math.sqrt(dx * dx + dy * dy); dx /= length; dy /= length; } player1.move(dx, dy); } // Initialize UI elements function createUI() { // Wave text waveText = new Text2("Wave 1", { size: 100, fill: 0xFFFFFF }); waveText.anchor.set(0.5); waveText.alpha = 0; LK.gui.center.addChild(waveText); // Score text scoreText = new Text2("Score: 0", { size: 40, fill: 0xFFFFFF }); scoreText.anchor.set(0.5, 0); scoreText.y = 20; LK.gui.top.addChild(scoreText); // High score text highScoreText = new Text2("High Score: " + storage.highScore, { size: 30, fill: 0xAAAAAA }); highScoreText.anchor.set(0.5, 0); highScoreText.y = 70; LK.gui.top.addChild(highScoreText); // Game start text gameStartText = new Text2("TAP TO START", { size: 80, fill: 0xFFFFFF }); gameStartText.anchor.set(0.5); LK.gui.center.addChild(gameStartText); // Pulse animation for game start text function pulseText() { tween(gameStartText.scale, { x: 1.1, y: 1.1 }, { duration: 800, easing: tween.sinceOut, onFinish: function onFinish() { tween(gameStartText.scale, { x: 1, y: 1 }, { duration: 800, easing: tween.sinceIn, onFinish: pulseText }); } }); } pulseText(); } // Input tracking variables var moveUp = false; var moveDown = false; var moveLeft = false; var moveRight = false; var isShooting = false; var shootTarget = { x: 0, y: 0 }; // Input handlers game.down = function (x, y, obj) { if (!gameActive) { startGame(); return; } // Determine which side of the screen was pressed if (x < 2048 / 2) { // Left side controls movement var centerX = 2048 / 4; var centerY = 2732 / 2; var dx = x - centerX; var dy = y - centerY; var length = Math.sqrt(dx * dx + dy * dy); if (length > 0) { dx /= length; dy /= length; } moveUp = dy < -0.5; moveDown = dy > 0.5; moveLeft = dx < -0.5; moveRight = dx > 0.5; } else { // Right side controls shooting isShooting = true; shootTarget.x = x; shootTarget.y = y; } }; game.up = function (x, y, obj) { // Stop movement or shooting based on which side was released if (x < 2048 / 2) { moveUp = moveDown = moveLeft = moveRight = false; } else { isShooting = false; } }; game.move = function (x, y, obj) { // Update shoot target if shooting if (isShooting && x > 2048 / 2) { shootTarget.x = x; shootTarget.y = y; } // Update movement direction if on left side if (x < 2048 / 2) { var centerX = 2048 / 4; var centerY = 2732 / 2; var dx = x - centerX; var dy = y - centerY; var length = Math.sqrt(dx * dx + dy * dy); if (length > 0) { dx /= length; dy /= length; } moveUp = dy < -0.5; moveDown = dy > 0.5; moveLeft = dx < -0.5; moveRight = dx > 0.5; } }; // Create the ground and UI createGround(); createUI(); // Main game update loop game.update = function () { if (gameActive) { // Handle player movement handlePlayerMovement(); // Handle player shooting if (isShooting && !player1.isDead) { var bullet = player1.shoot(shootTarget.x, shootTarget.y); if (bullet) { bullets.push(bullet); game.addChild(bullet); } } // Update players player1.update(); player2.update(); // Update health bars player1HealthBar.update(); player2HealthBar.update(); // Update ammo displays player1AmmoDisplay.update(); player2AmmoDisplay.update(); // Update bullets for (var i = bullets.length - 1; i >= 0; i--) { var bullet = bullets[i]; bullet.update(); // Check for bullet collisions with zombies for (var j = zombies.length - 1; j >= 0; j--) { var zombie = zombies[j]; if (!zombie.isDead && bullet.intersects(zombie)) { zombie.takeDamage(bullet.damage); bullet.shouldDestroy = true; break; } } // Remove bullets marked for destruction if (bullet.shouldDestroy) { game.removeChild(bullet); bullets.splice(i, 1); } } // Update zombies for (var i = zombies.length - 1; i >= 0; i--) { var zombie = zombies[i]; zombie.update(); // Remove zombies marked for destruction if (zombie.shouldDestroy) { game.removeChild(zombie); zombies.splice(i, 1); } } // Update blood splatters for (var i = bloodSplatters.length - 1; i >= 0; i--) { var blood = bloodSplatters[i]; blood.update(); // Remove blood marked for destruction if (blood.shouldDestroy) { game.removeChild(blood); bloodSplatters.splice(i, 1); } } // Spawn more zombies if needed if (zombies.length === 0 && zombiesLeft === 0) { // All zombies in wave defeated, start next wave startNextWave(); } else if (zombies.length < 10 && zombiesLeft > 0 && LK.ticks % 60 === 0) { // Spawn a new zombie every second if there are less than 10 on screen spawnZombie(); } // Check for game over checkGameOver(); } }; // Play music with low volume LK.playMusic('bgMusic', { volume: 0.3 });
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0
});
/****
* Classes
****/
var AmmoDisplay = Container.expand(function () {
var self = Container.call(this);
self.indicators = [];
self.player = null;
self.init = function (playerRef) {
self.player = playerRef;
// Clear existing indicators
for (var i = 0; i < self.indicators.length; i++) {
self.removeChild(self.indicators[i]);
}
self.indicators = [];
// Create new indicators
for (var i = 0; i < self.player.maxAmmo; i++) {
var indicator = LK.getAsset('ammoIndicator', {
anchorX: 0.5,
anchorY: 0.5,
x: i * 20,
y: 0
});
self.addChild(indicator);
self.indicators.push(indicator);
}
};
self.update = function () {
if (!self.player) {
return;
}
// Update visibility based on current ammo
for (var i = 0; i < self.indicators.length; i++) {
self.indicators[i].visible = i < self.player.ammo;
// Fade effect when reloading
if (self.player.isReloading) {
var reloadProgress = 1 - self.player.reloadCounter / self.player.reloadTime;
if (i < Math.floor(reloadProgress * self.player.maxAmmo)) {
self.indicators[i].visible = true;
self.indicators[i].alpha = 0.5;
} else {
self.indicators[i].visible = false;
}
} else {
self.indicators[i].alpha = 1;
}
}
};
return self;
});
var Blood = Container.expand(function () {
var self = Container.call(this);
var bloodGraphics = self.attachAsset('blood', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8
});
self.lifespan = 60; // Frames
self.init = function (x, y) {
self.x = x;
self.y = y;
self.scale.x = 0.5 + Math.random() * 0.5;
self.scale.y = 0.5 + Math.random() * 0.5;
self.rotation = Math.random() * Math.PI * 2;
self.alpha = 0.8;
};
self.update = function () {
self.lifespan--;
if (self.lifespan <= 20) {
self.alpha = self.lifespan / 20 * 0.8;
}
if (self.lifespan <= 0) {
self.shouldDestroy = true;
}
};
return self;
});
var Bullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 15;
self.damage = 25;
self.direction = {
x: 0,
y: 0
};
self.shooter = null;
self.update = function () {
self.x += self.direction.x * self.speed;
self.y += self.direction.y * self.speed;
// Destroy bullet if it goes off screen
if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
self.shouldDestroy = true;
}
};
self.fire = function (startX, startY, targetX, targetY, shooterRef) {
self.x = startX;
self.y = startY;
self.shooter = shooterRef;
// Calculate direction
var dx = targetX - startX;
var dy = targetY - startY;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.direction.x = dx / distance;
self.direction.y = dy / distance;
}
};
return self;
});
var HealthBar = Container.expand(function () {
var self = Container.call(this);
var background = self.attachAsset('healthBarBackground', {
anchorX: 0,
anchorY: 0
});
var bar = self.attachAsset('healthBar', {
anchorX: 0,
anchorY: 0,
x: 2,
y: 2
});
var playerText = new Text2("P1", {
size: 20,
fill: 0xFFFFFF
});
playerText.anchor.set(0.5);
playerText.x = background.width / 2;
playerText.y = background.height / 2;
self.addChild(playerText);
self.player = null;
self.init = function (playerRef, isPlayer1) {
self.player = playerRef;
playerText.setText(isPlayer1 ? "P1" : "P2");
};
self.update = function () {
if (!self.player) {
return;
}
var healthPercent = self.player.health / self.player.maxHealth;
bar.scale.x = healthPercent;
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
self.isPlayer1 = true; // Will be set when initializing
var graphicsId = 'player1'; // Default
self.health = 100;
self.maxHealth = 100;
self.moveSpeed = 5;
self.fireRate = 15; // Frames between shots
self.fireCounter = 0;
self.ammo = 12;
self.maxAmmo = 12;
self.reloadTime = 120; // 2 seconds at 60fps
self.reloadCounter = 0;
self.isReloading = false;
self.isDead = false;
self.lastMoveDirection = {
x: 0,
y: -1
}; // Default facing up
self.init = function (isFirstPlayer) {
self.isPlayer1 = isFirstPlayer;
graphicsId = isFirstPlayer ? 'player1' : 'player2';
if (self.graphics) {
self.removeChild(self.graphics);
}
self.graphics = self.attachAsset(graphicsId, {
anchorX: 0.5,
anchorY: 0.5,
tint: isFirstPlayer ? 0xff0000 : 0x0000ff // Red for player1, Blue for player2
});
self.health = self.maxHealth;
var nameTag = new Text2(isFirstPlayer ? "Player 1" : "Player 2", {
// Name tag
size: 20,
fill: 0xFFFFFF
});
nameTag.anchor.set(0.5);
nameTag.y = -self.graphics.height / 2 - 10; // Position above the player
self.addChild(nameTag);
self.ammo = self.maxAmmo;
self.fireCounter = 0;
self.reloadCounter = 0;
self.isReloading = false;
self.isDead = false;
};
self.takeDamage = function (amount) {
if (self.isDead) {
return;
}
self.health -= amount;
if (self.health <= 0) {
self.health = 0;
self.die();
} else {
// Flash red when hit
LK.effects.flashObject(self, self.isPlayer1 ? 0xff0000 : 0x0000ff, 300); // Red flash for player1, Blue flash for player2
LK.getSound('playerHit').play();
}
};
self.die = function () {
self.isDead = true;
self.alpha = 0.5;
// Flash red when dead
LK.effects.flashObject(self, 0xff0000, 1000);
};
self.shoot = function (targetX, targetY) {
if (self.isDead || self.isReloading) {
return null;
}
if (self.ammo <= 0) {
self.reload();
return null;
}
if (self.fireCounter <= 0) {
// Create bullet
var bullet = new Bullet();
bullet.fire(self.x, self.y, targetX, targetY, self);
// Update state
self.ammo--;
self.fireCounter = self.fireRate;
// Keep track of facing direction based on shot
var dx = targetX - self.x;
var dy = targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.lastMoveDirection.x = dx / distance;
self.lastMoveDirection.y = dy / distance;
}
// Play sound
LK.getSound('shoot').play();
if (self.isPlayer1) {
LK.getSound('player1Shoot').play(); // Unique sound for player 1
} else {
LK.getSound('player2Shoot').play(); // Unique sound for player 2
}
return bullet;
}
return null;
};
self.reload = function () {
if (self.isDead || self.isReloading || self.ammo === self.maxAmmo) {
return;
}
self.isReloading = true;
self.reloadCounter = self.reloadTime;
LK.getSound('reload').play();
};
self.move = function (dx, dy) {
if (self.isDead) {
return;
}
// If moving, update last move direction
if (dx !== 0 || dy !== 0) {
var length = Math.sqrt(dx * dx + dy * dy);
if (length > 0) {
self.lastMoveDirection.x = dx / length;
self.lastMoveDirection.y = dy / length;
}
}
// Apply movement
var newX = self.x + dx * self.moveSpeed;
var newY = self.y + dy * self.moveSpeed;
// Constrain to game bounds with a margin
var margin = 50;
newX = Math.max(margin, Math.min(2048 - margin, newX));
newY = Math.max(margin, Math.min(2732 - margin, newY));
self.x = newX;
self.y = newY;
};
self.update = function () {
// Update reload state
if (self.isReloading) {
self.reloadCounter--;
if (self.reloadCounter <= 0) {
self.isReloading = false;
self.ammo = self.maxAmmo;
}
}
// Update fire rate counter
if (self.fireCounter > 0) {
self.fireCounter--;
}
// AI behavior for Player 2
if (!self.isPlayer1 && !self.isDead) {
self.updateAI();
}
};
self.updateAI = function () {
// Find the nearest zombie
var nearestZombie = null;
var nearestDistance = Number.MAX_VALUE;
for (var i = 0; i < zombies.length; i++) {
var zombie = zombies[i];
var distance = getDistance(self, zombie);
if (distance < nearestDistance) {
nearestDistance = distance;
nearestZombie = zombie;
}
}
// Move and shoot logic
if (nearestZombie) {
// If zombie is close, move away from it
if (nearestDistance < 300) {
var dx = self.x - nearestZombie.x;
var dy = self.y - nearestZombie.y;
var length = Math.sqrt(dx * dx + dy * dy);
if (length > 0) {
dx /= length;
dy /= length;
self.move(dx, dy);
}
}
// Otherwise, stay close to player 1
else {
var distToPlayer1 = getDistance(self, player1);
if (distToPlayer1 > 300) {
var dx = player1.x - self.x;
var dy = player1.y - self.y;
var length = Math.sqrt(dx * dx + dy * dy);
if (length > 0) {
dx /= length;
dy /= length;
self.move(dx, dy);
}
}
}
// Shoot at nearest zombie if not reloading and it's in range
if (!self.isReloading && nearestDistance < 600) {
var bullet = self.shoot(nearestZombie.x, nearestZombie.y);
if (bullet) {
bullets.push(bullet);
game.addChild(bullet);
}
}
// Reload if low on ammo and no zombies nearby
if (self.ammo <= self.maxAmmo / 4 && nearestDistance > 400) {
self.reload();
}
}
};
return self;
});
var Zombie = Container.expand(function () {
var self = Container.call(this);
var zombieGraphics = self.attachAsset('zombie', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 100;
self.moveSpeed = 2;
self.damage = 5;
self.attackRate = 60; // 1 second between attacks
self.attackCounter = 0;
self.isDead = false;
self.init = function (x, y, moveSpeed, health) {
self.x = x;
self.y = y;
self.moveSpeed = moveSpeed || 2;
self.health = health || 100;
};
self.takeDamage = function (amount) {
if (self.isDead) {
return;
}
self.health -= amount;
// Flash red when hit
LK.effects.flashObject(self, 0xff0000, 150);
LK.getSound('zombieHit').play();
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
self.isDead = true;
self.shouldDestroy = true;
// Create blood splatter
createBlood(self.x, self.y);
// Increase score
currentScore += 10;
updateScoreText();
};
self.attack = function (player) {
if (self.isDead || self.attackCounter > 0) {
return;
}
player.takeDamage(self.damage);
self.attackCounter = self.attackRate;
};
self.update = function () {
if (self.isDead) {
return;
}
// Update attack counter
if (self.attackCounter > 0) {
self.attackCounter--;
}
// Target the nearest player that's alive
var target = null;
if (!player1.isDead && !player2.isDead) {
// Target closest player
var dist1 = getDistance(self, player1);
var dist2 = getDistance(self, player2);
target = dist1 < dist2 ? player1 : player2;
} else if (!player1.isDead) {
target = player1;
} else if (!player2.isDead) {
target = player2;
}
if (target) {
// Move towards target
var dx = target.x - self.x;
var dy = target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
var moveX = dx / distance * self.moveSpeed;
var moveY = dy / distance * self.moveSpeed;
self.x += moveX;
self.y += moveY;
}
// Attack if close enough
if (distance < 80) {
self.attack(target);
// Push the player out if they are stuck inside the zombie
if (distance < 40) {
var pushX = dx / distance * (40 - distance);
var pushY = dy / distance * (40 - distance);
target.x += pushX;
target.y += pushY;
}
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Game state variables
var player1;
var player2;
var player1HealthBar;
var player2HealthBar;
var player1AmmoDisplay;
var player2AmmoDisplay;
var bullets = [];
var zombies = [];
var bloodSplatters = [];
var currentWave = 0;
var zombiesInWave = 0;
var zombiesLeft = 0;
var currentScore = 0;
var gameActive = false;
var waveText;
var scoreText;
var highScoreText;
var gameStartText;
var groundTiles = [];
// Helper functions
function getDistance(obj1, obj2) {
var dx = obj1.x - obj2.x;
var dy = obj1.y - obj2.y;
return Math.sqrt(dx * dx + dy * dy);
}
function createGround() {
var tileSize = 128;
var tilesX = Math.ceil(2048 / tileSize);
var tilesY = Math.ceil(2732 / tileSize);
for (var y = 0; y < tilesY; y++) {
for (var x = 0; x < tilesX; x++) {
var tile = LK.getAsset('groundTile', {
anchorX: 0,
anchorY: 0,
x: x * tileSize,
y: y * tileSize,
alpha: 0.3
});
// Add slight randomness to tiles
tile.tint = 0x666666 + Math.floor(Math.random() * 0x111111);
groundTiles.push(tile);
game.addChild(tile);
}
}
}
function updateScoreText() {
scoreText.setText("Score: " + currentScore);
// Update high score if needed
if (currentScore > storage.highScore) {
storage.highScore = currentScore;
highScoreText.setText("High Score: " + storage.highScore);
}
}
function createBlood(x, y) {
for (var i = 0; i < 3; i++) {
var blood = new Blood();
blood.init(x + (Math.random() * 60 - 30), y + (Math.random() * 60 - 30));
bloodSplatters.push(blood);
game.addChild(blood);
}
}
function startGame() {
// Reset game state
gameActive = true;
currentWave = 0;
currentScore = 0;
// Clear any existing entities
clearEntities();
// Create players
player1 = new Player();
player1.init(true);
player1.x = 2048 / 2 - 150;
player1.y = 2732 / 2;
game.addChild(player1);
player2 = new Player();
player2.init(false);
player2.x = 2048 / 2 + 150;
player2.y = 2732 / 2;
game.addChild(player2);
// Create health bars
player1HealthBar = new HealthBar();
player1HealthBar.init(player1, true);
player1HealthBar.x = 20;
player1HealthBar.y = 100;
LK.gui.left.addChild(player1HealthBar);
player2HealthBar = new HealthBar();
player2HealthBar.init(player2, false);
player2HealthBar.x = 20;
player2HealthBar.y = 160;
LK.gui.left.addChild(player2HealthBar);
// Create ammo displays
player1AmmoDisplay = new AmmoDisplay();
player1AmmoDisplay.init(player1);
player1AmmoDisplay.x = 250;
player1AmmoDisplay.y = 115;
LK.gui.left.addChild(player1AmmoDisplay);
player2AmmoDisplay = new AmmoDisplay();
player2AmmoDisplay.init(player2);
player2AmmoDisplay.x = 250;
player2AmmoDisplay.y = 175;
LK.gui.left.addChild(player2AmmoDisplay);
// Add UI elements for health and ammo
var player1UI = new Container();
player1UI.addChild(player1HealthBar);
player1UI.addChild(player1AmmoDisplay);
LK.gui.left.addChild(player1UI);
var player2UI = new Container();
player2UI.addChild(player2HealthBar);
player2UI.addChild(player2AmmoDisplay);
LK.gui.left.addChild(player2UI);
// Update score display
updateScoreText();
// Start first wave
startNextWave();
// Hide start game text
gameStartText.visible = false;
// Play music
LK.playMusic('bgMusic', {
fade: {
start: 0,
end: 0.3,
duration: 2000
}
});
}
function clearEntities() {
// Remove all bullets
for (var i = 0; i < bullets.length; i++) {
if (bullets[i].parent) {
bullets[i].parent.removeChild(bullets[i]);
}
}
bullets = [];
// Remove all zombies
for (var i = 0; i < zombies.length; i++) {
if (zombies[i].parent) {
zombies[i].parent.removeChild(zombies[i]);
}
}
zombies = [];
// Remove all blood splatters
for (var i = 0; i < bloodSplatters.length; i++) {
if (bloodSplatters[i].parent) {
bloodSplatters[i].parent.removeChild(bloodSplatters[i]);
}
}
bloodSplatters = [];
// Remove players and their UI if they exist
if (player1 && player1.parent) {
player1.parent.removeChild(player1);
}
if (player2 && player2.parent) {
player2.parent.removeChild(player2);
}
// Remove UI elements if they exist
if (player1HealthBar && player1HealthBar.parent) {
player1HealthBar.parent.removeChild(player1HealthBar);
}
if (player2HealthBar && player2HealthBar.parent) {
player2HealthBar.parent.removeChild(player2HealthBar);
}
if (player1AmmoDisplay && player1AmmoDisplay.parent) {
player1AmmoDisplay.parent.removeChild(player1AmmoDisplay);
}
if (player2AmmoDisplay && player2AmmoDisplay.parent) {
player2AmmoDisplay.parent.removeChild(player2AmmoDisplay);
}
}
function spawnZombie() {
if (zombiesLeft <= 0) {
return;
}
var zombie = new Zombie();
// Determine zombie spawn position (outside the screen)
var spawnSide = Math.floor(Math.random() * 4); // 0: top, 1: right, 2: bottom, 3: left
var x, y;
switch (spawnSide) {
case 0:
// top
x = Math.random() * 2048;
y = -100;
break;
case 1:
// right
x = 2148;
y = Math.random() * 2732;
break;
case 2:
// bottom
x = Math.random() * 2048;
y = 2832;
break;
case 3:
// left
x = -100;
y = Math.random() * 2732;
break;
}
// Scale zombie difficulty based on wave
var healthMultiplier = 1 + currentWave * 0.1;
var speedMultiplier = 1 + currentWave * 0.05;
zombie.init(x, y, 2 * speedMultiplier, 100 * healthMultiplier);
zombies.push(zombie);
game.addChild(zombie);
zombiesLeft--;
}
function startNextWave() {
currentWave++;
zombiesInWave = 5 + currentWave * 3;
zombiesLeft = zombiesInWave;
waveText.setText("Wave " + currentWave);
tween(waveText, {
alpha: 1
}, {
duration: 500
});
// Play wave start sound
LK.getSound('waveStart').play();
// After 2 seconds, fade out the wave text
LK.setTimeout(function () {
tween(waveText, {
alpha: 0
}, {
duration: 500
});
}, 2000);
// Initial zombie spawn
for (var i = 0; i < Math.min(5, zombiesInWave); i++) {
spawnZombie();
}
}
function checkGameOver() {
if (!gameActive) {
return;
}
// Game over if both players are dead
if (player1.isDead && player2.isDead) {
gameActive = false;
// Show game over screen after a short delay
LK.setTimeout(function () {
LK.showGameOver();
}, 1500);
// Stop music with fade out
LK.playMusic('bgMusic', {
fade: {
start: 0.3,
end: 0,
duration: 1500
}
});
}
}
function handlePlayerMovement() {
if (!gameActive || player1.isDead) {
return;
}
var dx = 0;
var dy = 0;
if (moveUp) {
dy -= 1;
}
if (moveDown) {
dy += 1;
}
if (moveLeft) {
dx -= 1;
}
if (moveRight) {
dx += 1;
}
// Normalize diagonal movement
if (dx !== 0 && dy !== 0) {
var length = Math.sqrt(dx * dx + dy * dy);
dx /= length;
dy /= length;
}
player1.move(dx, dy);
}
// Initialize UI elements
function createUI() {
// Wave text
waveText = new Text2("Wave 1", {
size: 100,
fill: 0xFFFFFF
});
waveText.anchor.set(0.5);
waveText.alpha = 0;
LK.gui.center.addChild(waveText);
// Score text
scoreText = new Text2("Score: 0", {
size: 40,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0);
scoreText.y = 20;
LK.gui.top.addChild(scoreText);
// High score text
highScoreText = new Text2("High Score: " + storage.highScore, {
size: 30,
fill: 0xAAAAAA
});
highScoreText.anchor.set(0.5, 0);
highScoreText.y = 70;
LK.gui.top.addChild(highScoreText);
// Game start text
gameStartText = new Text2("TAP TO START", {
size: 80,
fill: 0xFFFFFF
});
gameStartText.anchor.set(0.5);
LK.gui.center.addChild(gameStartText);
// Pulse animation for game start text
function pulseText() {
tween(gameStartText.scale, {
x: 1.1,
y: 1.1
}, {
duration: 800,
easing: tween.sinceOut,
onFinish: function onFinish() {
tween(gameStartText.scale, {
x: 1,
y: 1
}, {
duration: 800,
easing: tween.sinceIn,
onFinish: pulseText
});
}
});
}
pulseText();
}
// Input tracking variables
var moveUp = false;
var moveDown = false;
var moveLeft = false;
var moveRight = false;
var isShooting = false;
var shootTarget = {
x: 0,
y: 0
};
// Input handlers
game.down = function (x, y, obj) {
if (!gameActive) {
startGame();
return;
}
// Determine which side of the screen was pressed
if (x < 2048 / 2) {
// Left side controls movement
var centerX = 2048 / 4;
var centerY = 2732 / 2;
var dx = x - centerX;
var dy = y - centerY;
var length = Math.sqrt(dx * dx + dy * dy);
if (length > 0) {
dx /= length;
dy /= length;
}
moveUp = dy < -0.5;
moveDown = dy > 0.5;
moveLeft = dx < -0.5;
moveRight = dx > 0.5;
} else {
// Right side controls shooting
isShooting = true;
shootTarget.x = x;
shootTarget.y = y;
}
};
game.up = function (x, y, obj) {
// Stop movement or shooting based on which side was released
if (x < 2048 / 2) {
moveUp = moveDown = moveLeft = moveRight = false;
} else {
isShooting = false;
}
};
game.move = function (x, y, obj) {
// Update shoot target if shooting
if (isShooting && x > 2048 / 2) {
shootTarget.x = x;
shootTarget.y = y;
}
// Update movement direction if on left side
if (x < 2048 / 2) {
var centerX = 2048 / 4;
var centerY = 2732 / 2;
var dx = x - centerX;
var dy = y - centerY;
var length = Math.sqrt(dx * dx + dy * dy);
if (length > 0) {
dx /= length;
dy /= length;
}
moveUp = dy < -0.5;
moveDown = dy > 0.5;
moveLeft = dx < -0.5;
moveRight = dx > 0.5;
}
};
// Create the ground and UI
createGround();
createUI();
// Main game update loop
game.update = function () {
if (gameActive) {
// Handle player movement
handlePlayerMovement();
// Handle player shooting
if (isShooting && !player1.isDead) {
var bullet = player1.shoot(shootTarget.x, shootTarget.y);
if (bullet) {
bullets.push(bullet);
game.addChild(bullet);
}
}
// Update players
player1.update();
player2.update();
// Update health bars
player1HealthBar.update();
player2HealthBar.update();
// Update ammo displays
player1AmmoDisplay.update();
player2AmmoDisplay.update();
// Update bullets
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
bullet.update();
// Check for bullet collisions with zombies
for (var j = zombies.length - 1; j >= 0; j--) {
var zombie = zombies[j];
if (!zombie.isDead && bullet.intersects(zombie)) {
zombie.takeDamage(bullet.damage);
bullet.shouldDestroy = true;
break;
}
}
// Remove bullets marked for destruction
if (bullet.shouldDestroy) {
game.removeChild(bullet);
bullets.splice(i, 1);
}
}
// Update zombies
for (var i = zombies.length - 1; i >= 0; i--) {
var zombie = zombies[i];
zombie.update();
// Remove zombies marked for destruction
if (zombie.shouldDestroy) {
game.removeChild(zombie);
zombies.splice(i, 1);
}
}
// Update blood splatters
for (var i = bloodSplatters.length - 1; i >= 0; i--) {
var blood = bloodSplatters[i];
blood.update();
// Remove blood marked for destruction
if (blood.shouldDestroy) {
game.removeChild(blood);
bloodSplatters.splice(i, 1);
}
}
// Spawn more zombies if needed
if (zombies.length === 0 && zombiesLeft === 0) {
// All zombies in wave defeated, start next wave
startNextWave();
} else if (zombies.length < 10 && zombiesLeft > 0 && LK.ticks % 60 === 0) {
// Spawn a new zombie every second if there are less than 10 on screen
spawnZombie();
}
// Check for game over
checkGameOver();
}
};
// Play music with low volume
LK.playMusic('bgMusic', {
volume: 0.3
});
Make it a 8 bit zombie that is menacing. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
Make an 8 bit boy. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
Make an 8 bit black and white grayscale boy. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
Make an 8bit red blood splater. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
Make an green grass 8bit texture 2d and flat. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows