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