/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Animated Intro Class
var AnimatedIntro = Container.expand(function () {
var self = Container.call(this);
// Create battlefield
var field = self.attachAsset('battlefieldFloor', {
anchorX: 0.5,
anchorY: 0.5
});
field.x = GAME_W / 2;
field.y = GAME_H / 2;
// Create tanks for animation
var blueTank = self.attachAsset('playerTank', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
blueTank.x = GAME_W / 2;
blueTank.y = GAME_H - 400;
// Create enemy tanks
var redTanks = [];
for (var i = 0; i < 3; i++) {
var redTank = self.attachAsset('enemyTank', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
redTank.x = 600 + i * 400;
redTank.y = 500;
redTanks.push(redTank);
self.addChild(redTank);
}
// Animation state
self.state = 'ready';
self.timer = 0;
self.explosions = [];
// Animation sequence
self.update = function () {
self.timer++;
if (self.state === 'ready' && self.timer > 60) {
// Start battle animation
self.state = 'fire1';
// Animate blue tank firing
var shell = self.attachAsset('playerShell', {
anchorX: 0.5,
anchorY: 0.5
});
shell.x = blueTank.x;
shell.y = blueTank.y - 100;
// Play fire sound
LK.getSound('fire').play();
// Animate shell movement
tween(shell, {
x: redTanks[0].x,
y: redTanks[0].y
}, {
duration: 500,
easing: tween.linear,
onFinish: function onFinish() {
// Create explosion
var exp = new Explosion();
exp.x = redTanks[0].x;
exp.y = redTanks[0].y;
self.addChild(exp);
self.explosions.push(exp);
LK.getSound('explode').play();
// Remove tank and shell
redTanks[0].destroy();
shell.destroy();
// Next state
self.state = 'fire2';
self.timer = 0;
}
});
} else if (self.state === 'fire2' && self.timer > 30) {
// Second shot
var shell = self.attachAsset('playerShell', {
anchorX: 0.5,
anchorY: 0.5
});
shell.x = blueTank.x;
shell.y = blueTank.y - 100;
tween(shell, {
x: redTanks[1].x,
y: redTanks[1].y
}, {
duration: 500,
easing: tween.linear,
onFinish: function onFinish() {
// Create explosion
var exp = new Explosion();
exp.x = redTanks[1].x;
exp.y = redTanks[1].y;
self.addChild(exp);
self.explosions.push(exp);
// Remove tank and shell
redTanks[1].destroy();
shell.destroy();
// Next state
self.state = 'fire3';
self.timer = 0;
}
});
} else if (self.state === 'fire3' && self.timer > 30) {
// Third shot
var shell = self.attachAsset('playerShell', {
anchorX: 0.5,
anchorY: 0.5
});
shell.x = blueTank.x;
shell.y = blueTank.y - 100;
tween(shell, {
x: redTanks[2].x,
y: redTanks[2].y
}, {
duration: 500,
easing: tween.linear,
onFinish: function onFinish() {
// Create explosion
var exp = new Explosion();
exp.x = redTanks[2].x;
exp.y = redTanks[2].y;
self.addChild(exp);
self.explosions.push(exp);
// Remove tank and shell
redTanks[2].destroy();
shell.destroy();
// Next state
self.state = 'victory';
self.timer = 0;
}
});
} else if (self.state === 'victory' && self.timer > 90) {
// Show intro screen
self.finish();
}
};
// Complete intro and show menu
self.finish = function () {
// Clean up all explosions
for (var i = 0; i < self.explosions.length; i++) {
if (self.explosions[i].parent) {
self.explosions[i].destroy();
}
}
// Fade out and destroy
tween(self, {
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
self.destroy();
showIntroScreen();
}
});
};
return self;
});
// Enemy Shell Class
var EnemyShell = Container.expand(function () {
var self = Container.call(this);
var shell = self.attachAsset('enemyShell', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = shell.width;
self.height = shell.height;
self.vx = 0;
self.vy = 8; // Reduced speed
self.update = function () {
self.x += self.vx;
self.y += self.vy;
};
return self;
});
// Enemy Tank Class
var EnemyTank = Container.expand(function () {
var self = Container.call(this);
var tank = self.attachAsset('enemyTank', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = tank.width;
self.height = tank.height;
// Movement
self.speed = 1.5 + Math.random() * 0.5 + Math.floor(LK.getScore() / 30); // Decreased base speed and score scaling
// Firing
self.fireInterval = 240; // 4 seconds at 60fps (Increased fire interval further)
self.fireCounter = 0;
self.update = function () {
self.y += self.speed; // Move downwards
self.fireCounter++;
if (self.fireCounter >= self.fireInterval) {
self.fireCounter = 0;
self.fire();
}
};
self.fire = function () {
var shell = new EnemyShell();
shell.x = self.x;
shell.y = self.y + self.height / 2 + shell.height / 2; // Start below the enemy tank
// Aim at player
var dx = player.x - shell.x;
var dy = player.y - shell.y;
var mag = Math.sqrt(dx * dx + dy * dy);
// Rotate tank to face direction of fire
var angle = Math.atan2(dy, dx);
self.rotation = angle + Math.PI / 2; // +90 degrees since tank points up
// Lowered speed for enemy shells
var shellSpeed = 5 + Math.floor(LK.getScore() / 30);
shell.vx = dx / mag * shellSpeed;
shell.vy = dy / mag * shellSpeed;
enemyShells.push(shell);
game.addChild(shell);
LK.getSound('enemyFire').play();
};
return self;
});
// Explosion effect (for destroyed tanks)
var Explosion = Container.expand(function () {
var self = Container.call(this);
var exp = self.attachAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.1,
scaleY: 0.1
});
exp.alpha = 0.9;
// Animate explosion growth and fade
tween(exp, {
scaleX: 2.5,
scaleY: 2.5,
alpha: 0.7
}, {
duration: 150,
easing: tween.easeOut
});
// Animate fade out and destroy
tween(exp, {
alpha: 0
}, {
duration: 450,
easing: tween.linear,
onFinish: function onFinish() {
self.destroy();
}
});
return self;
});
// Game Over Animation Class
var GameOverAnimator = Container.expand(function () {
var self = Container.call(this);
// Create battlefield background
var field = self.attachAsset('battlefieldFloor', {
anchorX: 0.5,
anchorY: 0.5
});
field.x = GAME_W / 2;
field.y = GAME_H / 2;
// Game Over text with animation
var gameOverText = new Text2('GAME OVER', {
size: 250,
fill: 0xFF0000
});
gameOverText.anchor.set(0.5, 0.5);
gameOverText.x = GAME_W / 2;
gameOverText.y = GAME_H / 2;
gameOverText.alpha = 0;
self.addChild(gameOverText);
// Animation elements
self.explosions = [];
self.tanks = [];
self.shells = [];
self.animationTimer = 0;
// Initialize animation
self.initialize = function () {
// Prepare text for animation (start invisible and small)
gameOverText.alpha = 0;
gameOverText.scaleX = 0.5;
gameOverText.scaleY = 0.5;
// Dramatic entrance for game over text
tween(gameOverText, {
alpha: 1,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 800,
easing: tween.elasticOut,
onStart: function onStart() {
// Big explosion at the center when text appears
var centerExplosion = new Explosion();
centerExplosion.x = GAME_W / 2;
centerExplosion.y = GAME_H / 2;
self.addChild(centerExplosion);
// Play explosion sound
LK.getSound('explode').play();
// Screen flash
LK.effects.flashScreen(0xff0000, 500);
}
});
// Create enemy tanks coming from all sides
self.createTanks();
// Add initial explosions in a circle around game over text
var explosionCount = 6;
for (var i = 0; i < explosionCount; i++) {
var angle = i / explosionCount * Math.PI * 2;
var radius = 400;
var exp = new Explosion();
exp.x = GAME_W / 2 + Math.cos(angle) * radius;
exp.y = GAME_H / 2 + Math.sin(angle) * radius;
self.addChild(exp);
self.explosions.push(exp);
}
// Schedule more random explosions
LK.setTimeout(function () {
self.createRandomExplosion();
}, 300);
};
// Create tanks for animation
self.createTanks = function () {
// Create tanks coming from top and bottom of the game over text
// Create top tanks (facing down)
for (var i = 0; i < 4; i++) {
var topTank = self.attachAsset('enemyTank', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
topTank.x = GAME_W / 2 - 500 + i * 300; // Spread across horizontally, centered around text
topTank.y = -200; // Start above screen
topTank.rotation = Math.PI; // Face down
self.tanks.push({
sprite: topTank,
vx: 0,
vy: 6 + Math.random() * 3,
lastFired: 0,
fireRate: 30 + Math.floor(Math.random() * 60)
});
}
// Create bottom tanks (facing up)
for (var i = 0; i < 4; i++) {
var bottomTank = self.attachAsset('enemyTank', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
bottomTank.x = GAME_W / 2 - 350 + i * 300; // Staggered positioning from top tanks
bottomTank.y = GAME_H + 200; // Start below screen
bottomTank.rotation = 0; // Face up
self.tanks.push({
sprite: bottomTank,
vx: 0,
vy: -6 - Math.random() * 3,
lastFired: 0,
fireRate: 30 + Math.floor(Math.random() * 60)
});
}
// Create side tanks for more dynamic feel
for (var i = 0; i < 3; i++) {
// Left side tank
var leftTank = self.attachAsset('enemyTank', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
leftTank.x = -200;
leftTank.y = GAME_H / 2 - 300 + i * 300;
leftTank.rotation = Math.PI / 2; // Face right
self.tanks.push({
sprite: leftTank,
vx: 7 + Math.random() * 3,
vy: 0,
lastFired: 0,
fireRate: 30 + Math.floor(Math.random() * 60)
});
// Right side tank
var rightTank = self.attachAsset('enemyTank', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
rightTank.x = GAME_W + 200;
rightTank.y = GAME_H / 2 - 150 + i * 300;
rightTank.rotation = -Math.PI / 2; // Face left
self.tanks.push({
sprite: rightTank,
vx: -7 - Math.random() * 3,
vy: 0,
lastFired: 0,
fireRate: 30 + Math.floor(Math.random() * 60)
});
}
};
// Create a random explosion
self.createRandomExplosion = function () {
var exp = new Explosion();
exp.x = Math.random() * GAME_W;
exp.y = Math.random() * GAME_H;
self.addChild(exp);
self.explosions.push(exp);
// Schedule next explosion
var nextExplosionTime = 30 + Math.floor(Math.random() * 60);
LK.setTimeout(function () {
self.createRandomExplosion();
}, nextExplosionTime * 16); // Convert frames to ms (approx)
};
// Create a shell from a tank
self.createShell = function (tank) {
var shell = self.attachAsset('enemyShell', {
anchorX: 0.5,
anchorY: 0.5
});
shell.x = tank.sprite.x;
shell.y = tank.sprite.y;
// Random direction but mostly toward center
var targetX = GAME_W / 2 + (Math.random() * 600 - 300);
var targetY = GAME_H / 2 + (Math.random() * 600 - 300);
var dx = targetX - shell.x;
var dy = targetY - shell.y;
var mag = Math.sqrt(dx * dx + dy * dy);
var shellSpeed = 8 + Math.random() * 5;
self.shells.push({
sprite: shell,
vx: dx / mag * shellSpeed,
vy: dy / mag * shellSpeed
});
};
// Main update function
self.update = function () {
self.animationTimer++;
// Update tanks
for (var i = 0; i < self.tanks.length; i++) {
var tank = self.tanks[i];
tank.sprite.x += tank.vx;
tank.sprite.y += tank.vy;
// Fire shells randomly
tank.lastFired++;
if (tank.lastFired > tank.fireRate) {
tank.lastFired = 0;
tank.fireRate = 30 + Math.floor(Math.random() * 60);
self.createShell(tank);
// Play fire sound occasionally
if (Math.random() < 0.3) {
LK.getSound('enemyFire').play();
}
}
// Remove tanks that go off screen and create new ones
var offScreen = false;
if (tank.vx > 0 && tank.sprite.x > GAME_W + 200 || tank.vx < 0 && tank.sprite.x < -200 || tank.vy > 0 && tank.sprite.y > GAME_H + 200 || tank.vy < 0 && tank.sprite.y < -200) {
offScreen = true;
}
if (offScreen) {
tank.sprite.destroy();
self.tanks.splice(i, 1);
i--;
// Create a new tank from the opposite side with a delay
LK.setTimeout(function () {
// Determine spawn location (top, bottom, left, right)
var spawnLocation = Math.floor(Math.random() * 4); // 0=top, 1=right, 2=bottom, 3=left
var newTank = self.attachAsset('enemyTank', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
if (spawnLocation === 0) {
// Top
newTank.x = GAME_W / 2 - 400 + Math.random() * 800;
newTank.y = -200;
newTank.rotation = Math.PI; // Face down
self.tanks.push({
sprite: newTank,
vx: 0,
vy: 6 + Math.random() * 3,
lastFired: 0,
fireRate: 30 + Math.floor(Math.random() * 60)
});
} else if (spawnLocation === 1) {
newTank.x = GAME_W + 200;
newTank.y = GAME_H / 2 - 400 + Math.random() * 800;
newTank.rotation = -Math.PI / 2; // Face left
self.tanks.push({
sprite: newTank,
vx: -7 - Math.random() * 3,
vy: 0,
lastFired: 0,
fireRate: 30 + Math.floor(Math.random() * 60)
});
} else if (spawnLocation === 2) {
newTank.x = GAME_W / 2 - 400 + Math.random() * 800;
newTank.y = GAME_H + 200;
newTank.rotation = 0; // Face up
self.tanks.push({
sprite: newTank,
vx: 0,
vy: -6 - Math.random() * 3,
lastFired: 0,
fireRate: 30 + Math.floor(Math.random() * 60)
});
} else {
// Left
newTank.x = -200;
newTank.y = GAME_H / 2 - 400 + Math.random() * 800;
newTank.rotation = Math.PI / 2; // Face right
self.tanks.push({
sprite: newTank,
vx: 7 + Math.random() * 3,
vy: 0,
lastFired: 0,
fireRate: 30 + Math.floor(Math.random() * 60)
});
}
}, Math.random() * 800);
}
}
// Update shells
for (var i = 0; i < self.shells.length; i++) {
var shell = self.shells[i];
shell.sprite.x += shell.vx;
shell.sprite.y += shell.vy;
// Remove shells that go off screen
if (shell.sprite.x < -50 || shell.sprite.x > GAME_W + 50 || shell.sprite.y < -50 || shell.sprite.y > GAME_H + 50) {
shell.sprite.destroy();
self.shells.splice(i, 1);
i--;
continue;
}
// Create explosion if shells collide
for (var j = i + 1; j < self.shells.length; j++) {
var otherShell = self.shells[j];
var dx = shell.sprite.x - otherShell.sprite.x;
var dy = shell.sprite.y - otherShell.sprite.y;
var distSq = dx * dx + dy * dy;
if (distSq < 50 * 50) {
// If shells are close enough
var exp = new Explosion();
exp.x = shell.sprite.x;
exp.y = shell.sprite.y;
self.addChild(exp);
self.explosions.push(exp);
// Play explosion sound occasionally
if (Math.random() < 0.5) {
LK.getSound('explode').play();
}
shell.sprite.destroy();
otherShell.sprite.destroy();
self.shells.splice(j, 1);
self.shells.splice(i, 1);
i--;
break;
}
}
}
// Pulse text animation
if (self.animationTimer % 60 < 30) {
gameOverText.scale.x = 1.1 + Math.sin(self.animationTimer / 15) * 0.1;
gameOverText.scale.y = 1.1 + Math.sin(self.animationTimer / 15) * 0.1;
}
};
return self;
});
// Health Power-up Class
var Health = Container.expand(function () {
var self = Container.call(this);
// Create heart graphic
var heart = self.attachAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.7,
scaleY: 0.7
});
// Set heart color to red
heart.tint = 0xFF0000;
// Size properties
self.width = heart.width * 0.7;
self.height = heart.height * 0.7;
// Animation properties
self.floatOffset = Math.random() * Math.PI * 2;
self.rotationSpeed = 0.01 + Math.random() * 0.01;
self.lastY = 0;
// Update function to animate the heart
self.update = function () {
self.lastY = self.y;
// Floating animation
self.y += Math.sin(LK.ticks * 0.05 + self.floatOffset) * 0.5;
self.rotation += self.rotationSpeed;
// Pulsing animation
var scale = 0.7 + Math.sin(LK.ticks * 0.1) * 0.05;
heart.scaleX = scale;
heart.scaleY = scale;
};
// Apply pick-up animation
self.collect = function () {
// Flash effect
tween(heart, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 300,
easing: tween.easeOut
});
};
return self;
});
// Intro Screen Class
var IntroScreen = Container.expand(function () {
var self = Container.call(this);
// Create title text
var titleText = new Text2('TANK BATTLE', {
size: 200,
fill: 0xFF8800
});
titleText.anchor.set(0.5, 0.5);
titleText.x = GAME_W / 2;
titleText.y = GAME_H / 3;
self.addChild(titleText);
// Create subtitle text
var subtitleText = new Text2('Dodge enemy attacks and destroy enemy tanks', {
size: 80,
fill: 0xFFFFFF
});
subtitleText.anchor.set(0.5, 0.5);
subtitleText.x = GAME_W / 2;
subtitleText.y = GAME_H / 3 + 200;
self.addChild(subtitleText);
// Create start button
var startButton = self.attachAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 1.5
});
startButton.x = GAME_W / 2;
startButton.y = GAME_H / 3 + 400;
startButton.tint = 0x44aa44;
// Create start text
var startText = new Text2('TAP TO START', {
size: 100,
fill: 0xFFFFFF
});
startText.anchor.set(0.5, 0.5);
startText.x = GAME_W / 2;
startText.y = GAME_H / 3 + 400;
self.addChild(startText);
// Create tank decoration
var tankIcon = self.attachAsset('playerTank', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 2
});
tankIcon.x = GAME_W / 2;
tankIcon.y = GAME_H / 3 + 900; // Lower the tank position
// Animate button
self.update = function () {
// Pulse animation for button
startButton.scaleX = 3 + Math.sin(LK.ticks / 20) * 0.2;
startButton.scaleY = 1.5 + Math.sin(LK.ticks / 20) * 0.1;
};
// Add interaction
self.down = function (x, y, obj) {
startGame();
};
return self;
});
// Player Shell Class
var PlayerShell = Container.expand(function () {
var self = Container.call(this);
var shell = self.attachAsset('playerShell', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = shell.width;
self.height = shell.height;
self.vx = 0;
self.vy = -32; // Default upwards
self.lastX = 0;
self.lastY = 0;
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
self.x += self.vx;
self.y += self.vy;
};
return self;
});
// Player Tank Class
var PlayerTank = Container.expand(function () {
var self = Container.call(this);
var tank = self.attachAsset('playerTank', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = tank.width;
self.height = tank.height;
// Fire cooldown
self.canFire = true;
self.fireCooldown = 18; // frames (0.3s)
self.cooldownCounter = 0;
// Movement physics
self.targetX = 0;
self.targetY = 0;
self.velocityX = 0;
self.velocityY = 0;
self.acceleration = 0.4; // Increased acceleration
self.maxSpeed = 60; // Increased max speed
self.friction = 0.94; // Slightly less friction for smoother movement
self.isMoving = false;
self.reachedTarget = true;
self.rotationSpeed = 0.3; // Faster rotation
self.tracksFX = null;
// Move to a position with physics
self.moveTo = function (x, y) {
self.targetX = x;
self.targetY = y;
self.isMoving = true;
self.reachedTarget = false;
// Create dust/tracks effect
if (!self.tracksFX) {
self.createTracksFX();
}
};
// Create track/dust effect
self.createTracksFX = function () {
if (self.tracksFX) {
// Remove old track FX if it exists
if (self.tracksFX.parent) {
self.tracksFX.parent.removeChild(self.tracksFX);
}
self.tracksFX = null;
}
// Create new track effects
self.tracksFX = self.attachAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3
});
self.tracksFX.y = self.height / 3;
self.tracksFX.alpha = 0.3;
self.tracksFX.tint = 0xCCCCCC;
};
// Stop movement
self.stopMoving = function () {
self.isMoving = false;
if (self.tracksFX) {
tween(self.tracksFX, {
alpha: 0
}, {
duration: 300,
easing: tween.easeOut
});
}
};
self.update = function () {
// Handle fire cooldown
if (!self.canFire) {
self.cooldownCounter++;
if (self.cooldownCounter >= self.fireCooldown) {
self.canFire = true;
self.cooldownCounter = 0;
}
}
// Handle physics-based movement
if (self.isMoving) {
// Calculate distance and direction to target
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var distSq = dx * dx + dy * dy;
// Calculate desired direction
var targetAngle = Math.atan2(dy, dx) + Math.PI / 2;
// Smoothly rotate toward target direction
var angleDiff = targetAngle - self.rotation;
// Normalize angle difference to -PI to PI
while (angleDiff > Math.PI) {
angleDiff -= Math.PI * 2;
}
while (angleDiff < -Math.PI) {
angleDiff += Math.PI * 2;
}
self.rotation += angleDiff * self.rotationSpeed;
// If we're close to target, slow down
if (distSq < 10000) {
// 100*100
self.velocityX *= 0.9;
self.velocityY *= 0.9;
if (distSq < 100) {
// 10*10
self.reachedTarget = true;
self.velocityX = 0;
self.velocityY = 0;
self.stopMoving();
}
} else {
// Accelerate toward target based on tank's orientation
var facingX = Math.sin(self.rotation);
var facingY = -Math.cos(self.rotation);
// Only accelerate if facing approximately the right direction
var dotProduct = facingX * dx + facingY * dy;
if (dotProduct > 0 || distSq > 90000) {
// Allow movement if far away regardless of direction
self.velocityX += facingX * self.acceleration;
self.velocityY += facingY * self.acceleration;
}
}
// Apply friction and speed limits
self.velocityX *= self.friction;
self.velocityY *= self.friction;
// Limit max speed
var speed = Math.sqrt(self.velocityX * self.velocityX + self.velocityY * self.velocityY);
if (speed > self.maxSpeed) {
self.velocityX = self.velocityX / speed * self.maxSpeed;
self.velocityY = self.velocityY / speed * self.maxSpeed;
}
// Apply velocity
self.x += self.velocityX;
self.y += self.velocityY;
// Animate tracks effect
if (self.tracksFX && speed > 0.5) {
self.tracksFX.rotation = Math.random() * 0.2 - 0.1;
self.tracksFX.alpha = Math.min(0.2 + speed / 30, 0.7);
self.tracksFX.scaleX = 0.3 + Math.random() * 0.1;
self.tracksFX.scaleY = 0.3 + Math.random() * 0.1;
}
}
};
// Fire a shell
self.fire = function (targetX, targetY) {
if (self.canFire) {
var shell = new PlayerShell();
shell.x = self.x;
shell.y = self.y - self.height / 3; // Offset slightly to start from tank cannon
// Calculate direction
if (targetX !== undefined && targetY !== undefined) {
var dx = targetX - shell.x;
var dy = targetY - shell.y;
var mag = Math.sqrt(dx * dx + dy * dy);
var shellSpeed = 32; // Shell speed
shell.vx = dx / mag * shellSpeed;
shell.vy = dy / mag * shellSpeed;
// Rotate tank to face direction of fire
var angle = Math.atan2(dy, dx);
self.rotation = angle + Math.PI / 2; // +90 degrees since tank points up
} else {
// Default upward if no target
shell.vx = 0;
shell.vy = -32;
self.rotation = 0;
}
playerShells.push(shell);
game.addChild(shell);
LK.getSound('fire').play();
self.canFire = false;
shell.lastX = shell.x;
shell.lastY = shell.y;
// Animate recoil effect on firing
var originalY = self.y;
tween(self, {
y: self.y + 20
}, {
duration: 50,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
y: originalY
}, {
duration: 150,
easing: tween.elasticOut
});
}
});
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x22272b
});
/****
* Game Code
****/
// Play background music
LK.playMusic('bgmusic', {
loop: true,
fade: {
start: 0,
end: 1,
duration: 1000
}
});
// Sound effects
// Explosion: white ellipse (for flash effect)
// Enemy shell: orange ellipse
// Enemy tank: red box
// Player shell: yellow ellipse
// Player tank: green box
// Game area
var GAME_W = 2048;
var GAME_H = 2732;
// Game state
var gameStarted = false;
var introScreen = null;
var animatedIntro = null;
var introShown = false;
// Game elements - will be initialized when game starts
var player = null;
var playerShells = [];
var enemyTanks = [];
var enemyShells = [];
var healthPowerUps = [];
var healthPowerUps = [];
// Create animated intro
function showAnimatedIntro() {
animatedIntro = new AnimatedIntro();
game.addChild(animatedIntro);
}
// Create intro screen
function showIntroScreen() {
// Only show intro screen if game hasn't started
if (gameStarted || introShown) {
return;
}
introScreen = new IntroScreen();
game.addChild(introScreen);
introShown = true;
}
// Start the actual game
function startGame() {
// Remove intro screen
if (introScreen) {
introScreen.destroy();
introScreen = null;
introShown = false;
}
// Initialize game elements
gameStarted = true;
introScreen = null;
animatedIntro = null;
// Create player
player = new PlayerTank();
// Make sure track effects are properly reset
player.tracksFX = null;
player.x = GAME_W / 2;
player.y = GAME_H - 350;
game.addChild(player);
// Reset arrays
playerShells = [];
enemyTanks = [];
enemyShells = [];
healthPowerUps = [];
healthPowerUps = [];
// Reset score and health
LK.setScore(0);
scoreTxt.setText('0');
playerHealth = 5;
var hearts = '';
for (var h = 0; h < playerHealth; h++) {
hearts += '❤';
}
healthTxt.setText(hearts);
}
// Start with animated intro
showAnimatedIntro();
// Score display
var scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Player health
var playerHealth = 5;
var healthTxt = new Text2('❤❤❤❤❤', {
size: 100,
fill: 0xFF4444
});
healthTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(healthTxt);
healthTxt.x = 400;
healthTxt.y = 0;
// "Glaud" text
var glaudTxt = new Text2('Glaud', {
size: 40,
fill: 0xFFA500 // Orange color
});
glaudTxt.anchor.set(1, 0); // Anchor to the top right
LK.gui.topRight.addChild(glaudTxt);
// Dragging
var dragNode = null;
// Spawn enemy timer
var enemySpawnInterval = 90; // frames (1.5s)
var enemySpawnCounter = 0; // Start at 0 to spawn immediately
// Difficulty escalation
function getEnemySpawnInterval() {
var s = LK.getScore();
if (s < 10) {
return 90;
}
if (s < 25) {
return 70;
}
if (s < 50) {
return 55;
}
return 40;
}
// Move handler (physics-based tank movement)
function handleMove(x, y, obj) {
if (dragNode) {
// Calculate target position with bounds checking
var minX = dragNode.width / 2;
var maxX = GAME_W - dragNode.width / 2;
var minY = 100 + dragNode.height / 2;
var maxY = GAME_H - dragNode.height / 2;
var targetX = Math.max(minX, Math.min(maxX, x));
var targetY = Math.max(minY, Math.min(maxY, y));
// If it's the player tank, use the physics system
if (dragNode === player) {
player.moveTo(targetX, targetY);
} else {
// For other objects, use direct positioning
dragNode.x = targetX;
dragNode.y = targetY;
}
} else if (isFiring) {
// Update firing target if holding down
fireTarget.x = x;
fireTarget.y = y;
// Rotate tank to face direction
var dx = x - player.x;
var dy = y - player.y;
var angle = Math.atan2(dy, dx);
player.rotation = angle + Math.PI / 2; // +90 degrees since tank points up
}
}
game.move = handleMove;
game.down = function (x, y, obj) {
// Only drag if touch is on player tank
var local = player.toLocal(game.toGlobal({
x: x,
y: y
}));
if (local.x > -player.width / 2 && local.x < player.width / 2 && local.y > -player.height / 2 && local.y < player.height / 2) {
dragNode = player;
}
};
game.up = function (x, y, obj) {
// If we were dragging the player, let it continue to its last target
if (dragNode === player) {
// Already set target in handleMove, just release drag indicator
}
dragNode = null;
isFiring = false;
autoFireCounter = 0;
};
// Tap to move and fire
game.tap = function (x, y, obj) {
// If animated intro is playing, skip to intro screen
if (animatedIntro) {
animatedIntro.finish();
return;
}
// Only start the game if it hasn't started yet and introScreen is showing
if (!gameStarted && introScreen) {
startGame();
introScreen = null;
return;
}
// Avoid firing if already dragging
if (!dragNode) {
// Move the tank to the target position (using physics)
player.moveTo(x, y);
// Fire toward target
player.fire(x, y);
}
};
// Variables for firing control
var isFiring = false;
var fireTarget = {
x: 0,
y: 0
};
var autoFireRate = 10; // Fire every 10 frames while holding
var autoFireCounter = 0;
// Handle both movement and firing
game.down = function (x, y, obj) {
// Skip animated intro on tap
if (animatedIntro) {
animatedIntro.finish();
return;
}
// Only handle tank interactions if player exists
if (!player) {
return;
}
var local = player.toLocal(game.toGlobal({
x: x,
y: y
}));
if (local.x > -player.width / 2 && local.x < player.width / 2 && local.y > -player.height / 2 && local.y < player.height / 2) {
dragNode = player;
// When clicking on tank, start listening for drag
} else {
// When clicking elsewhere, move tank there and fire
// Set firing target and start firing
fireTarget.x = x;
fireTarget.y = y;
isFiring = true;
autoFireCounter = 0;
// Move the tank to the target position (using physics)
player.moveTo(x, y);
// Fire toward target
player.fire(fireTarget.x, fireTarget.y);
}
};
// Game over animation
var gameOverAnimation = null;
// Main update loop
game.update = function () {
// Update game over animation if active
if (gameOverAnimation) {
gameOverAnimation.update();
return;
}
// Update animated intro or intro screen if game hasn't started
if (!gameStarted) {
if (animatedIntro) {
animatedIntro.update();
return;
}
if (introScreen) {
introScreen.update();
}
return; // Don't run game logic until game starts
}
// Update player
player.update();
// Handle continuous firing while holding down
if (isFiring) {
autoFireCounter++;
if (autoFireCounter >= autoFireRate && player.canFire) {
player.fire(fireTarget.x, fireTarget.y);
autoFireCounter = 0;
}
}
// Update player shells
for (var i = playerShells.length - 1; i >= 0; i--) {
var shell = playerShells[i];
shell.update();
// Remove if off screen
if (shell.y < -shell.height / 2) {
shell.destroy();
playerShells.splice(i, 1);
continue;
}
// Check collision with enemy tanks
for (var j = enemyTanks.length - 1; j >= 0; j--) {
var enemy = enemyTanks[j];
if (shell.intersects(enemy)) {
// Explosion
var exp = new Explosion();
exp.x = enemy.x;
exp.y = enemy.y;
game.addChild(exp);
LK.getSound('explode').play();
// Remove both
shell.destroy();
playerShells.splice(i, 1);
enemy.destroy();
enemyTanks.splice(j, 1);
// Score
LK.setScore(LK.getScore() + 1);
scoreTxt.setText(LK.getScore());
break;
}
}
}
// Update enemy tanks
for (var i = enemyTanks.length - 1; i >= 0; i--) {
var enemy = enemyTanks[i];
enemy.update();
// Remove if off screen
if (enemy.y > GAME_H + enemy.height / 2) {
enemy.destroy();
enemyTanks.splice(i, 1);
continue;
}
// Check collision with player
if (enemy.intersects(player)) {
// Explosion
var exp = new Explosion();
exp.x = player.x;
exp.y = player.y;
game.addChild(exp);
LK.getSound('explode').play();
LK.effects.flashScreen(0xff0000, 800);
// Decrease health
playerHealth--;
// Update health display
var hearts = '';
for (var h = 0; h < playerHealth; h++) {
hearts += '❤';
}
healthTxt.setText(hearts);
if (playerHealth <= 0) {
// Create animated game over screen
gameOverAnimation = new GameOverAnimator();
game.addChild(gameOverAnimation);
gameOverAnimation.initialize();
// Delay the actual game over to allow animation to play
LK.setTimeout(function () {
LK.showGameOver();
}, 5000); // Show game over after 5 seconds of animation
return;
}
// Animate player hit effect
tween.stop(player, {
rotation: true,
scaleX: true,
scaleY: true,
alpha: true
});
tween(player, {
scaleX: 1.3,
scaleY: 1.3,
alpha: 0.6
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(player, {
scaleX: 1,
scaleY: 1,
alpha: 1
}, {
duration: 300,
easing: tween.elasticOut
});
}
});
// Remove enemy tank
enemy.destroy();
enemyTanks.splice(i, 1);
continue;
}
}
// Update enemy shells
for (var i = enemyShells.length - 1; i >= 0; i--) {
var shell = enemyShells[i];
shell.update();
// Remove if off screen
if (shell.y > GAME_H + shell.height / 2 || shell.x < -shell.width / 2 || shell.x > GAME_W + shell.width / 2) {
shell.destroy();
enemyShells.splice(i, 1);
continue;
}
// Check collision with player
if (shell.intersects(player)) {
// Explosion
var exp = new Explosion();
exp.x = shell.x;
exp.y = shell.y;
game.addChild(exp);
LK.getSound('explode').play();
LK.effects.flashScreen(0xff0000, 800);
// Decrease health
playerHealth--;
// Update health display
var hearts = '';
for (var h = 0; h < playerHealth; h++) {
hearts += '❤';
}
healthTxt.setText(hearts);
if (playerHealth <= 0) {
// Create animated game over screen
gameOverAnimation = new GameOverAnimator();
game.addChild(gameOverAnimation);
gameOverAnimation.initialize();
// Delay the actual game over to allow animation to play
LK.setTimeout(function () {
LK.showGameOver();
}, 5000); // Show game over after 5 seconds of animation
return;
}
// Animate player hit by shell
tween.stop(player, {
rotation: true,
alpha: true
});
// Push player slightly in the direction of the hit
var pushX = (player.x - shell.x) * 0.05;
var pushY = (player.y - shell.y) * 0.05;
var origX = player.x;
var origY = player.y;
tween(player, {
x: player.x + pushX,
y: player.y + pushY,
alpha: 0.7
}, {
duration: 120,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(player, {
x: origX,
y: origY,
alpha: 1
}, {
duration: 280,
easing: tween.easeInOut
});
}
});
// Remove shell
shell.destroy();
enemyShells.splice(i, 1);
continue;
}
}
// Update health power-ups
for (var i = healthPowerUps.length - 1; i >= 0; i--) {
var heart = healthPowerUps[i];
heart.update();
// Check if player collected the heart
if (player.intersects(heart)) {
// Only collect if health is less than maximum
if (playerHealth < 5) {
// Play collect sound
LK.getSound('enemyFire').play();
// Increase health
playerHealth++;
// Update health display
var hearts = '';
for (var h = 0; h < playerHealth; h++) {
hearts += '❤';
}
healthTxt.setText(hearts);
// Animation
heart.collect();
// Remove heart
LK.setTimeout(function () {
heart.destroy();
}, 300);
healthPowerUps.splice(i, 1);
// Flash effect
LK.effects.flashScreen(0x00ff00, 300);
}
}
// Remove if off screen
if (heart.y > GAME_H + heart.height / 2) {
heart.destroy();
healthPowerUps.splice(i, 1);
}
}
// Update health power-ups
for (var i = healthPowerUps.length - 1; i >= 0; i--) {
var heart = healthPowerUps[i];
heart.update();
// Check if player collected the heart
if (player.intersects(heart)) {
// Only collect if health is less than maximum
if (playerHealth < 5) {
// Play collect sound
LK.getSound('enemyFire').play();
// Increase health
playerHealth++;
// Update health display
var hearts = '';
for (var h = 0; h < playerHealth; h++) {
hearts += '❤';
}
healthTxt.setText(hearts);
// Animation
heart.collect();
// Remove heart
LK.setTimeout(function () {
heart.destroy();
}, 300);
healthPowerUps.splice(i, 1);
// Flash effect
LK.effects.flashScreen(0x00ff00, 300);
}
}
// Remove if off screen
if (heart.y > GAME_H + heart.height / 2) {
heart.destroy();
healthPowerUps.splice(i, 1);
}
}
// Spawn enemies
enemySpawnCounter++;
if (enemySpawnCounter >= getEnemySpawnInterval()) {
enemySpawnCounter = 0;
// Randomly spawn health power-up (around 10% chance, only if player's health is not full)
if (Math.random() < 0.1 && playerHealth < 5 && healthPowerUps.length < 2) {
var healthPowerUp = new Health();
var margin = 300;
healthPowerUp.x = margin + Math.random() * (GAME_W - margin * 2);
healthPowerUp.y = -healthPowerUp.height;
healthPowerUp.lastY = healthPowerUp.y;
// Add to game
healthPowerUps.push(healthPowerUp);
game.addChild(healthPowerUp);
// Apply slow falling movement
tween(healthPowerUp, {
y: GAME_H + healthPowerUp.height
}, {
duration: 20000,
// Slow fall over 20 seconds
easing: tween.linear
});
}
// Randomly spawn health power-up (around 10% chance, only if player's health is not full)
if (Math.random() < 0.1 && playerHealth < 5 && healthPowerUps.length < 2) {
var healthPowerUp = new Health();
var margin = 300;
healthPowerUp.x = margin + Math.random() * (GAME_W - margin * 2);
healthPowerUp.y = -healthPowerUp.height;
healthPowerUp.lastY = healthPowerUp.y;
// Add to game
healthPowerUps.push(healthPowerUp);
game.addChild(healthPowerUp);
// Apply slow falling movement
tween(healthPowerUp, {
y: GAME_H + healthPowerUp.height
}, {
duration: 20000,
// Slow fall over 20 seconds
easing: tween.linear
});
}
// Only spawn an enemy if there are not too many already
if (enemyTanks.length < 5 + Math.floor(LK.getScore() / 10)) {
var enemy = new EnemyTank();
// Position enemies at the top, across the width of the screen
var margin = 200;
enemy.x = margin / 2 + Math.random() * (GAME_W - margin);
enemy.y = -enemy.height / 2; // Start just off-screen at the top
// Rotate to face down
enemy.rotation = Math.PI; // 180 degrees to face down
enemyTanks.push(enemy);
game.addChild(enemy);
// Add spawn animation
enemy.alpha = 0;
enemy.scaleX = 0.2;
enemy.scaleY = 0.2;
tween(enemy, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 400,
easing: tween.elasticOut
});
console.log("Spawned enemy tank from top");
}
}
};
// Game initialization happens in startGame function /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Animated Intro Class
var AnimatedIntro = Container.expand(function () {
var self = Container.call(this);
// Create battlefield
var field = self.attachAsset('battlefieldFloor', {
anchorX: 0.5,
anchorY: 0.5
});
field.x = GAME_W / 2;
field.y = GAME_H / 2;
// Create tanks for animation
var blueTank = self.attachAsset('playerTank', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
blueTank.x = GAME_W / 2;
blueTank.y = GAME_H - 400;
// Create enemy tanks
var redTanks = [];
for (var i = 0; i < 3; i++) {
var redTank = self.attachAsset('enemyTank', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
redTank.x = 600 + i * 400;
redTank.y = 500;
redTanks.push(redTank);
self.addChild(redTank);
}
// Animation state
self.state = 'ready';
self.timer = 0;
self.explosions = [];
// Animation sequence
self.update = function () {
self.timer++;
if (self.state === 'ready' && self.timer > 60) {
// Start battle animation
self.state = 'fire1';
// Animate blue tank firing
var shell = self.attachAsset('playerShell', {
anchorX: 0.5,
anchorY: 0.5
});
shell.x = blueTank.x;
shell.y = blueTank.y - 100;
// Play fire sound
LK.getSound('fire').play();
// Animate shell movement
tween(shell, {
x: redTanks[0].x,
y: redTanks[0].y
}, {
duration: 500,
easing: tween.linear,
onFinish: function onFinish() {
// Create explosion
var exp = new Explosion();
exp.x = redTanks[0].x;
exp.y = redTanks[0].y;
self.addChild(exp);
self.explosions.push(exp);
LK.getSound('explode').play();
// Remove tank and shell
redTanks[0].destroy();
shell.destroy();
// Next state
self.state = 'fire2';
self.timer = 0;
}
});
} else if (self.state === 'fire2' && self.timer > 30) {
// Second shot
var shell = self.attachAsset('playerShell', {
anchorX: 0.5,
anchorY: 0.5
});
shell.x = blueTank.x;
shell.y = blueTank.y - 100;
tween(shell, {
x: redTanks[1].x,
y: redTanks[1].y
}, {
duration: 500,
easing: tween.linear,
onFinish: function onFinish() {
// Create explosion
var exp = new Explosion();
exp.x = redTanks[1].x;
exp.y = redTanks[1].y;
self.addChild(exp);
self.explosions.push(exp);
// Remove tank and shell
redTanks[1].destroy();
shell.destroy();
// Next state
self.state = 'fire3';
self.timer = 0;
}
});
} else if (self.state === 'fire3' && self.timer > 30) {
// Third shot
var shell = self.attachAsset('playerShell', {
anchorX: 0.5,
anchorY: 0.5
});
shell.x = blueTank.x;
shell.y = blueTank.y - 100;
tween(shell, {
x: redTanks[2].x,
y: redTanks[2].y
}, {
duration: 500,
easing: tween.linear,
onFinish: function onFinish() {
// Create explosion
var exp = new Explosion();
exp.x = redTanks[2].x;
exp.y = redTanks[2].y;
self.addChild(exp);
self.explosions.push(exp);
// Remove tank and shell
redTanks[2].destroy();
shell.destroy();
// Next state
self.state = 'victory';
self.timer = 0;
}
});
} else if (self.state === 'victory' && self.timer > 90) {
// Show intro screen
self.finish();
}
};
// Complete intro and show menu
self.finish = function () {
// Clean up all explosions
for (var i = 0; i < self.explosions.length; i++) {
if (self.explosions[i].parent) {
self.explosions[i].destroy();
}
}
// Fade out and destroy
tween(self, {
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
self.destroy();
showIntroScreen();
}
});
};
return self;
});
// Enemy Shell Class
var EnemyShell = Container.expand(function () {
var self = Container.call(this);
var shell = self.attachAsset('enemyShell', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = shell.width;
self.height = shell.height;
self.vx = 0;
self.vy = 8; // Reduced speed
self.update = function () {
self.x += self.vx;
self.y += self.vy;
};
return self;
});
// Enemy Tank Class
var EnemyTank = Container.expand(function () {
var self = Container.call(this);
var tank = self.attachAsset('enemyTank', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = tank.width;
self.height = tank.height;
// Movement
self.speed = 1.5 + Math.random() * 0.5 + Math.floor(LK.getScore() / 30); // Decreased base speed and score scaling
// Firing
self.fireInterval = 240; // 4 seconds at 60fps (Increased fire interval further)
self.fireCounter = 0;
self.update = function () {
self.y += self.speed; // Move downwards
self.fireCounter++;
if (self.fireCounter >= self.fireInterval) {
self.fireCounter = 0;
self.fire();
}
};
self.fire = function () {
var shell = new EnemyShell();
shell.x = self.x;
shell.y = self.y + self.height / 2 + shell.height / 2; // Start below the enemy tank
// Aim at player
var dx = player.x - shell.x;
var dy = player.y - shell.y;
var mag = Math.sqrt(dx * dx + dy * dy);
// Rotate tank to face direction of fire
var angle = Math.atan2(dy, dx);
self.rotation = angle + Math.PI / 2; // +90 degrees since tank points up
// Lowered speed for enemy shells
var shellSpeed = 5 + Math.floor(LK.getScore() / 30);
shell.vx = dx / mag * shellSpeed;
shell.vy = dy / mag * shellSpeed;
enemyShells.push(shell);
game.addChild(shell);
LK.getSound('enemyFire').play();
};
return self;
});
// Explosion effect (for destroyed tanks)
var Explosion = Container.expand(function () {
var self = Container.call(this);
var exp = self.attachAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.1,
scaleY: 0.1
});
exp.alpha = 0.9;
// Animate explosion growth and fade
tween(exp, {
scaleX: 2.5,
scaleY: 2.5,
alpha: 0.7
}, {
duration: 150,
easing: tween.easeOut
});
// Animate fade out and destroy
tween(exp, {
alpha: 0
}, {
duration: 450,
easing: tween.linear,
onFinish: function onFinish() {
self.destroy();
}
});
return self;
});
// Game Over Animation Class
var GameOverAnimator = Container.expand(function () {
var self = Container.call(this);
// Create battlefield background
var field = self.attachAsset('battlefieldFloor', {
anchorX: 0.5,
anchorY: 0.5
});
field.x = GAME_W / 2;
field.y = GAME_H / 2;
// Game Over text with animation
var gameOverText = new Text2('GAME OVER', {
size: 250,
fill: 0xFF0000
});
gameOverText.anchor.set(0.5, 0.5);
gameOverText.x = GAME_W / 2;
gameOverText.y = GAME_H / 2;
gameOverText.alpha = 0;
self.addChild(gameOverText);
// Animation elements
self.explosions = [];
self.tanks = [];
self.shells = [];
self.animationTimer = 0;
// Initialize animation
self.initialize = function () {
// Prepare text for animation (start invisible and small)
gameOverText.alpha = 0;
gameOverText.scaleX = 0.5;
gameOverText.scaleY = 0.5;
// Dramatic entrance for game over text
tween(gameOverText, {
alpha: 1,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 800,
easing: tween.elasticOut,
onStart: function onStart() {
// Big explosion at the center when text appears
var centerExplosion = new Explosion();
centerExplosion.x = GAME_W / 2;
centerExplosion.y = GAME_H / 2;
self.addChild(centerExplosion);
// Play explosion sound
LK.getSound('explode').play();
// Screen flash
LK.effects.flashScreen(0xff0000, 500);
}
});
// Create enemy tanks coming from all sides
self.createTanks();
// Add initial explosions in a circle around game over text
var explosionCount = 6;
for (var i = 0; i < explosionCount; i++) {
var angle = i / explosionCount * Math.PI * 2;
var radius = 400;
var exp = new Explosion();
exp.x = GAME_W / 2 + Math.cos(angle) * radius;
exp.y = GAME_H / 2 + Math.sin(angle) * radius;
self.addChild(exp);
self.explosions.push(exp);
}
// Schedule more random explosions
LK.setTimeout(function () {
self.createRandomExplosion();
}, 300);
};
// Create tanks for animation
self.createTanks = function () {
// Create tanks coming from top and bottom of the game over text
// Create top tanks (facing down)
for (var i = 0; i < 4; i++) {
var topTank = self.attachAsset('enemyTank', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
topTank.x = GAME_W / 2 - 500 + i * 300; // Spread across horizontally, centered around text
topTank.y = -200; // Start above screen
topTank.rotation = Math.PI; // Face down
self.tanks.push({
sprite: topTank,
vx: 0,
vy: 6 + Math.random() * 3,
lastFired: 0,
fireRate: 30 + Math.floor(Math.random() * 60)
});
}
// Create bottom tanks (facing up)
for (var i = 0; i < 4; i++) {
var bottomTank = self.attachAsset('enemyTank', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
bottomTank.x = GAME_W / 2 - 350 + i * 300; // Staggered positioning from top tanks
bottomTank.y = GAME_H + 200; // Start below screen
bottomTank.rotation = 0; // Face up
self.tanks.push({
sprite: bottomTank,
vx: 0,
vy: -6 - Math.random() * 3,
lastFired: 0,
fireRate: 30 + Math.floor(Math.random() * 60)
});
}
// Create side tanks for more dynamic feel
for (var i = 0; i < 3; i++) {
// Left side tank
var leftTank = self.attachAsset('enemyTank', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
leftTank.x = -200;
leftTank.y = GAME_H / 2 - 300 + i * 300;
leftTank.rotation = Math.PI / 2; // Face right
self.tanks.push({
sprite: leftTank,
vx: 7 + Math.random() * 3,
vy: 0,
lastFired: 0,
fireRate: 30 + Math.floor(Math.random() * 60)
});
// Right side tank
var rightTank = self.attachAsset('enemyTank', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
rightTank.x = GAME_W + 200;
rightTank.y = GAME_H / 2 - 150 + i * 300;
rightTank.rotation = -Math.PI / 2; // Face left
self.tanks.push({
sprite: rightTank,
vx: -7 - Math.random() * 3,
vy: 0,
lastFired: 0,
fireRate: 30 + Math.floor(Math.random() * 60)
});
}
};
// Create a random explosion
self.createRandomExplosion = function () {
var exp = new Explosion();
exp.x = Math.random() * GAME_W;
exp.y = Math.random() * GAME_H;
self.addChild(exp);
self.explosions.push(exp);
// Schedule next explosion
var nextExplosionTime = 30 + Math.floor(Math.random() * 60);
LK.setTimeout(function () {
self.createRandomExplosion();
}, nextExplosionTime * 16); // Convert frames to ms (approx)
};
// Create a shell from a tank
self.createShell = function (tank) {
var shell = self.attachAsset('enemyShell', {
anchorX: 0.5,
anchorY: 0.5
});
shell.x = tank.sprite.x;
shell.y = tank.sprite.y;
// Random direction but mostly toward center
var targetX = GAME_W / 2 + (Math.random() * 600 - 300);
var targetY = GAME_H / 2 + (Math.random() * 600 - 300);
var dx = targetX - shell.x;
var dy = targetY - shell.y;
var mag = Math.sqrt(dx * dx + dy * dy);
var shellSpeed = 8 + Math.random() * 5;
self.shells.push({
sprite: shell,
vx: dx / mag * shellSpeed,
vy: dy / mag * shellSpeed
});
};
// Main update function
self.update = function () {
self.animationTimer++;
// Update tanks
for (var i = 0; i < self.tanks.length; i++) {
var tank = self.tanks[i];
tank.sprite.x += tank.vx;
tank.sprite.y += tank.vy;
// Fire shells randomly
tank.lastFired++;
if (tank.lastFired > tank.fireRate) {
tank.lastFired = 0;
tank.fireRate = 30 + Math.floor(Math.random() * 60);
self.createShell(tank);
// Play fire sound occasionally
if (Math.random() < 0.3) {
LK.getSound('enemyFire').play();
}
}
// Remove tanks that go off screen and create new ones
var offScreen = false;
if (tank.vx > 0 && tank.sprite.x > GAME_W + 200 || tank.vx < 0 && tank.sprite.x < -200 || tank.vy > 0 && tank.sprite.y > GAME_H + 200 || tank.vy < 0 && tank.sprite.y < -200) {
offScreen = true;
}
if (offScreen) {
tank.sprite.destroy();
self.tanks.splice(i, 1);
i--;
// Create a new tank from the opposite side with a delay
LK.setTimeout(function () {
// Determine spawn location (top, bottom, left, right)
var spawnLocation = Math.floor(Math.random() * 4); // 0=top, 1=right, 2=bottom, 3=left
var newTank = self.attachAsset('enemyTank', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
if (spawnLocation === 0) {
// Top
newTank.x = GAME_W / 2 - 400 + Math.random() * 800;
newTank.y = -200;
newTank.rotation = Math.PI; // Face down
self.tanks.push({
sprite: newTank,
vx: 0,
vy: 6 + Math.random() * 3,
lastFired: 0,
fireRate: 30 + Math.floor(Math.random() * 60)
});
} else if (spawnLocation === 1) {
newTank.x = GAME_W + 200;
newTank.y = GAME_H / 2 - 400 + Math.random() * 800;
newTank.rotation = -Math.PI / 2; // Face left
self.tanks.push({
sprite: newTank,
vx: -7 - Math.random() * 3,
vy: 0,
lastFired: 0,
fireRate: 30 + Math.floor(Math.random() * 60)
});
} else if (spawnLocation === 2) {
newTank.x = GAME_W / 2 - 400 + Math.random() * 800;
newTank.y = GAME_H + 200;
newTank.rotation = 0; // Face up
self.tanks.push({
sprite: newTank,
vx: 0,
vy: -6 - Math.random() * 3,
lastFired: 0,
fireRate: 30 + Math.floor(Math.random() * 60)
});
} else {
// Left
newTank.x = -200;
newTank.y = GAME_H / 2 - 400 + Math.random() * 800;
newTank.rotation = Math.PI / 2; // Face right
self.tanks.push({
sprite: newTank,
vx: 7 + Math.random() * 3,
vy: 0,
lastFired: 0,
fireRate: 30 + Math.floor(Math.random() * 60)
});
}
}, Math.random() * 800);
}
}
// Update shells
for (var i = 0; i < self.shells.length; i++) {
var shell = self.shells[i];
shell.sprite.x += shell.vx;
shell.sprite.y += shell.vy;
// Remove shells that go off screen
if (shell.sprite.x < -50 || shell.sprite.x > GAME_W + 50 || shell.sprite.y < -50 || shell.sprite.y > GAME_H + 50) {
shell.sprite.destroy();
self.shells.splice(i, 1);
i--;
continue;
}
// Create explosion if shells collide
for (var j = i + 1; j < self.shells.length; j++) {
var otherShell = self.shells[j];
var dx = shell.sprite.x - otherShell.sprite.x;
var dy = shell.sprite.y - otherShell.sprite.y;
var distSq = dx * dx + dy * dy;
if (distSq < 50 * 50) {
// If shells are close enough
var exp = new Explosion();
exp.x = shell.sprite.x;
exp.y = shell.sprite.y;
self.addChild(exp);
self.explosions.push(exp);
// Play explosion sound occasionally
if (Math.random() < 0.5) {
LK.getSound('explode').play();
}
shell.sprite.destroy();
otherShell.sprite.destroy();
self.shells.splice(j, 1);
self.shells.splice(i, 1);
i--;
break;
}
}
}
// Pulse text animation
if (self.animationTimer % 60 < 30) {
gameOverText.scale.x = 1.1 + Math.sin(self.animationTimer / 15) * 0.1;
gameOverText.scale.y = 1.1 + Math.sin(self.animationTimer / 15) * 0.1;
}
};
return self;
});
// Health Power-up Class
var Health = Container.expand(function () {
var self = Container.call(this);
// Create heart graphic
var heart = self.attachAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.7,
scaleY: 0.7
});
// Set heart color to red
heart.tint = 0xFF0000;
// Size properties
self.width = heart.width * 0.7;
self.height = heart.height * 0.7;
// Animation properties
self.floatOffset = Math.random() * Math.PI * 2;
self.rotationSpeed = 0.01 + Math.random() * 0.01;
self.lastY = 0;
// Update function to animate the heart
self.update = function () {
self.lastY = self.y;
// Floating animation
self.y += Math.sin(LK.ticks * 0.05 + self.floatOffset) * 0.5;
self.rotation += self.rotationSpeed;
// Pulsing animation
var scale = 0.7 + Math.sin(LK.ticks * 0.1) * 0.05;
heart.scaleX = scale;
heart.scaleY = scale;
};
// Apply pick-up animation
self.collect = function () {
// Flash effect
tween(heart, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 300,
easing: tween.easeOut
});
};
return self;
});
// Intro Screen Class
var IntroScreen = Container.expand(function () {
var self = Container.call(this);
// Create title text
var titleText = new Text2('TANK BATTLE', {
size: 200,
fill: 0xFF8800
});
titleText.anchor.set(0.5, 0.5);
titleText.x = GAME_W / 2;
titleText.y = GAME_H / 3;
self.addChild(titleText);
// Create subtitle text
var subtitleText = new Text2('Dodge enemy attacks and destroy enemy tanks', {
size: 80,
fill: 0xFFFFFF
});
subtitleText.anchor.set(0.5, 0.5);
subtitleText.x = GAME_W / 2;
subtitleText.y = GAME_H / 3 + 200;
self.addChild(subtitleText);
// Create start button
var startButton = self.attachAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 1.5
});
startButton.x = GAME_W / 2;
startButton.y = GAME_H / 3 + 400;
startButton.tint = 0x44aa44;
// Create start text
var startText = new Text2('TAP TO START', {
size: 100,
fill: 0xFFFFFF
});
startText.anchor.set(0.5, 0.5);
startText.x = GAME_W / 2;
startText.y = GAME_H / 3 + 400;
self.addChild(startText);
// Create tank decoration
var tankIcon = self.attachAsset('playerTank', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 2
});
tankIcon.x = GAME_W / 2;
tankIcon.y = GAME_H / 3 + 900; // Lower the tank position
// Animate button
self.update = function () {
// Pulse animation for button
startButton.scaleX = 3 + Math.sin(LK.ticks / 20) * 0.2;
startButton.scaleY = 1.5 + Math.sin(LK.ticks / 20) * 0.1;
};
// Add interaction
self.down = function (x, y, obj) {
startGame();
};
return self;
});
// Player Shell Class
var PlayerShell = Container.expand(function () {
var self = Container.call(this);
var shell = self.attachAsset('playerShell', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = shell.width;
self.height = shell.height;
self.vx = 0;
self.vy = -32; // Default upwards
self.lastX = 0;
self.lastY = 0;
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
self.x += self.vx;
self.y += self.vy;
};
return self;
});
// Player Tank Class
var PlayerTank = Container.expand(function () {
var self = Container.call(this);
var tank = self.attachAsset('playerTank', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = tank.width;
self.height = tank.height;
// Fire cooldown
self.canFire = true;
self.fireCooldown = 18; // frames (0.3s)
self.cooldownCounter = 0;
// Movement physics
self.targetX = 0;
self.targetY = 0;
self.velocityX = 0;
self.velocityY = 0;
self.acceleration = 0.4; // Increased acceleration
self.maxSpeed = 60; // Increased max speed
self.friction = 0.94; // Slightly less friction for smoother movement
self.isMoving = false;
self.reachedTarget = true;
self.rotationSpeed = 0.3; // Faster rotation
self.tracksFX = null;
// Move to a position with physics
self.moveTo = function (x, y) {
self.targetX = x;
self.targetY = y;
self.isMoving = true;
self.reachedTarget = false;
// Create dust/tracks effect
if (!self.tracksFX) {
self.createTracksFX();
}
};
// Create track/dust effect
self.createTracksFX = function () {
if (self.tracksFX) {
// Remove old track FX if it exists
if (self.tracksFX.parent) {
self.tracksFX.parent.removeChild(self.tracksFX);
}
self.tracksFX = null;
}
// Create new track effects
self.tracksFX = self.attachAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3
});
self.tracksFX.y = self.height / 3;
self.tracksFX.alpha = 0.3;
self.tracksFX.tint = 0xCCCCCC;
};
// Stop movement
self.stopMoving = function () {
self.isMoving = false;
if (self.tracksFX) {
tween(self.tracksFX, {
alpha: 0
}, {
duration: 300,
easing: tween.easeOut
});
}
};
self.update = function () {
// Handle fire cooldown
if (!self.canFire) {
self.cooldownCounter++;
if (self.cooldownCounter >= self.fireCooldown) {
self.canFire = true;
self.cooldownCounter = 0;
}
}
// Handle physics-based movement
if (self.isMoving) {
// Calculate distance and direction to target
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var distSq = dx * dx + dy * dy;
// Calculate desired direction
var targetAngle = Math.atan2(dy, dx) + Math.PI / 2;
// Smoothly rotate toward target direction
var angleDiff = targetAngle - self.rotation;
// Normalize angle difference to -PI to PI
while (angleDiff > Math.PI) {
angleDiff -= Math.PI * 2;
}
while (angleDiff < -Math.PI) {
angleDiff += Math.PI * 2;
}
self.rotation += angleDiff * self.rotationSpeed;
// If we're close to target, slow down
if (distSq < 10000) {
// 100*100
self.velocityX *= 0.9;
self.velocityY *= 0.9;
if (distSq < 100) {
// 10*10
self.reachedTarget = true;
self.velocityX = 0;
self.velocityY = 0;
self.stopMoving();
}
} else {
// Accelerate toward target based on tank's orientation
var facingX = Math.sin(self.rotation);
var facingY = -Math.cos(self.rotation);
// Only accelerate if facing approximately the right direction
var dotProduct = facingX * dx + facingY * dy;
if (dotProduct > 0 || distSq > 90000) {
// Allow movement if far away regardless of direction
self.velocityX += facingX * self.acceleration;
self.velocityY += facingY * self.acceleration;
}
}
// Apply friction and speed limits
self.velocityX *= self.friction;
self.velocityY *= self.friction;
// Limit max speed
var speed = Math.sqrt(self.velocityX * self.velocityX + self.velocityY * self.velocityY);
if (speed > self.maxSpeed) {
self.velocityX = self.velocityX / speed * self.maxSpeed;
self.velocityY = self.velocityY / speed * self.maxSpeed;
}
// Apply velocity
self.x += self.velocityX;
self.y += self.velocityY;
// Animate tracks effect
if (self.tracksFX && speed > 0.5) {
self.tracksFX.rotation = Math.random() * 0.2 - 0.1;
self.tracksFX.alpha = Math.min(0.2 + speed / 30, 0.7);
self.tracksFX.scaleX = 0.3 + Math.random() * 0.1;
self.tracksFX.scaleY = 0.3 + Math.random() * 0.1;
}
}
};
// Fire a shell
self.fire = function (targetX, targetY) {
if (self.canFire) {
var shell = new PlayerShell();
shell.x = self.x;
shell.y = self.y - self.height / 3; // Offset slightly to start from tank cannon
// Calculate direction
if (targetX !== undefined && targetY !== undefined) {
var dx = targetX - shell.x;
var dy = targetY - shell.y;
var mag = Math.sqrt(dx * dx + dy * dy);
var shellSpeed = 32; // Shell speed
shell.vx = dx / mag * shellSpeed;
shell.vy = dy / mag * shellSpeed;
// Rotate tank to face direction of fire
var angle = Math.atan2(dy, dx);
self.rotation = angle + Math.PI / 2; // +90 degrees since tank points up
} else {
// Default upward if no target
shell.vx = 0;
shell.vy = -32;
self.rotation = 0;
}
playerShells.push(shell);
game.addChild(shell);
LK.getSound('fire').play();
self.canFire = false;
shell.lastX = shell.x;
shell.lastY = shell.y;
// Animate recoil effect on firing
var originalY = self.y;
tween(self, {
y: self.y + 20
}, {
duration: 50,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
y: originalY
}, {
duration: 150,
easing: tween.elasticOut
});
}
});
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x22272b
});
/****
* Game Code
****/
// Play background music
LK.playMusic('bgmusic', {
loop: true,
fade: {
start: 0,
end: 1,
duration: 1000
}
});
// Sound effects
// Explosion: white ellipse (for flash effect)
// Enemy shell: orange ellipse
// Enemy tank: red box
// Player shell: yellow ellipse
// Player tank: green box
// Game area
var GAME_W = 2048;
var GAME_H = 2732;
// Game state
var gameStarted = false;
var introScreen = null;
var animatedIntro = null;
var introShown = false;
// Game elements - will be initialized when game starts
var player = null;
var playerShells = [];
var enemyTanks = [];
var enemyShells = [];
var healthPowerUps = [];
var healthPowerUps = [];
// Create animated intro
function showAnimatedIntro() {
animatedIntro = new AnimatedIntro();
game.addChild(animatedIntro);
}
// Create intro screen
function showIntroScreen() {
// Only show intro screen if game hasn't started
if (gameStarted || introShown) {
return;
}
introScreen = new IntroScreen();
game.addChild(introScreen);
introShown = true;
}
// Start the actual game
function startGame() {
// Remove intro screen
if (introScreen) {
introScreen.destroy();
introScreen = null;
introShown = false;
}
// Initialize game elements
gameStarted = true;
introScreen = null;
animatedIntro = null;
// Create player
player = new PlayerTank();
// Make sure track effects are properly reset
player.tracksFX = null;
player.x = GAME_W / 2;
player.y = GAME_H - 350;
game.addChild(player);
// Reset arrays
playerShells = [];
enemyTanks = [];
enemyShells = [];
healthPowerUps = [];
healthPowerUps = [];
// Reset score and health
LK.setScore(0);
scoreTxt.setText('0');
playerHealth = 5;
var hearts = '';
for (var h = 0; h < playerHealth; h++) {
hearts += '❤';
}
healthTxt.setText(hearts);
}
// Start with animated intro
showAnimatedIntro();
// Score display
var scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Player health
var playerHealth = 5;
var healthTxt = new Text2('❤❤❤❤❤', {
size: 100,
fill: 0xFF4444
});
healthTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(healthTxt);
healthTxt.x = 400;
healthTxt.y = 0;
// "Glaud" text
var glaudTxt = new Text2('Glaud', {
size: 40,
fill: 0xFFA500 // Orange color
});
glaudTxt.anchor.set(1, 0); // Anchor to the top right
LK.gui.topRight.addChild(glaudTxt);
// Dragging
var dragNode = null;
// Spawn enemy timer
var enemySpawnInterval = 90; // frames (1.5s)
var enemySpawnCounter = 0; // Start at 0 to spawn immediately
// Difficulty escalation
function getEnemySpawnInterval() {
var s = LK.getScore();
if (s < 10) {
return 90;
}
if (s < 25) {
return 70;
}
if (s < 50) {
return 55;
}
return 40;
}
// Move handler (physics-based tank movement)
function handleMove(x, y, obj) {
if (dragNode) {
// Calculate target position with bounds checking
var minX = dragNode.width / 2;
var maxX = GAME_W - dragNode.width / 2;
var minY = 100 + dragNode.height / 2;
var maxY = GAME_H - dragNode.height / 2;
var targetX = Math.max(minX, Math.min(maxX, x));
var targetY = Math.max(minY, Math.min(maxY, y));
// If it's the player tank, use the physics system
if (dragNode === player) {
player.moveTo(targetX, targetY);
} else {
// For other objects, use direct positioning
dragNode.x = targetX;
dragNode.y = targetY;
}
} else if (isFiring) {
// Update firing target if holding down
fireTarget.x = x;
fireTarget.y = y;
// Rotate tank to face direction
var dx = x - player.x;
var dy = y - player.y;
var angle = Math.atan2(dy, dx);
player.rotation = angle + Math.PI / 2; // +90 degrees since tank points up
}
}
game.move = handleMove;
game.down = function (x, y, obj) {
// Only drag if touch is on player tank
var local = player.toLocal(game.toGlobal({
x: x,
y: y
}));
if (local.x > -player.width / 2 && local.x < player.width / 2 && local.y > -player.height / 2 && local.y < player.height / 2) {
dragNode = player;
}
};
game.up = function (x, y, obj) {
// If we were dragging the player, let it continue to its last target
if (dragNode === player) {
// Already set target in handleMove, just release drag indicator
}
dragNode = null;
isFiring = false;
autoFireCounter = 0;
};
// Tap to move and fire
game.tap = function (x, y, obj) {
// If animated intro is playing, skip to intro screen
if (animatedIntro) {
animatedIntro.finish();
return;
}
// Only start the game if it hasn't started yet and introScreen is showing
if (!gameStarted && introScreen) {
startGame();
introScreen = null;
return;
}
// Avoid firing if already dragging
if (!dragNode) {
// Move the tank to the target position (using physics)
player.moveTo(x, y);
// Fire toward target
player.fire(x, y);
}
};
// Variables for firing control
var isFiring = false;
var fireTarget = {
x: 0,
y: 0
};
var autoFireRate = 10; // Fire every 10 frames while holding
var autoFireCounter = 0;
// Handle both movement and firing
game.down = function (x, y, obj) {
// Skip animated intro on tap
if (animatedIntro) {
animatedIntro.finish();
return;
}
// Only handle tank interactions if player exists
if (!player) {
return;
}
var local = player.toLocal(game.toGlobal({
x: x,
y: y
}));
if (local.x > -player.width / 2 && local.x < player.width / 2 && local.y > -player.height / 2 && local.y < player.height / 2) {
dragNode = player;
// When clicking on tank, start listening for drag
} else {
// When clicking elsewhere, move tank there and fire
// Set firing target and start firing
fireTarget.x = x;
fireTarget.y = y;
isFiring = true;
autoFireCounter = 0;
// Move the tank to the target position (using physics)
player.moveTo(x, y);
// Fire toward target
player.fire(fireTarget.x, fireTarget.y);
}
};
// Game over animation
var gameOverAnimation = null;
// Main update loop
game.update = function () {
// Update game over animation if active
if (gameOverAnimation) {
gameOverAnimation.update();
return;
}
// Update animated intro or intro screen if game hasn't started
if (!gameStarted) {
if (animatedIntro) {
animatedIntro.update();
return;
}
if (introScreen) {
introScreen.update();
}
return; // Don't run game logic until game starts
}
// Update player
player.update();
// Handle continuous firing while holding down
if (isFiring) {
autoFireCounter++;
if (autoFireCounter >= autoFireRate && player.canFire) {
player.fire(fireTarget.x, fireTarget.y);
autoFireCounter = 0;
}
}
// Update player shells
for (var i = playerShells.length - 1; i >= 0; i--) {
var shell = playerShells[i];
shell.update();
// Remove if off screen
if (shell.y < -shell.height / 2) {
shell.destroy();
playerShells.splice(i, 1);
continue;
}
// Check collision with enemy tanks
for (var j = enemyTanks.length - 1; j >= 0; j--) {
var enemy = enemyTanks[j];
if (shell.intersects(enemy)) {
// Explosion
var exp = new Explosion();
exp.x = enemy.x;
exp.y = enemy.y;
game.addChild(exp);
LK.getSound('explode').play();
// Remove both
shell.destroy();
playerShells.splice(i, 1);
enemy.destroy();
enemyTanks.splice(j, 1);
// Score
LK.setScore(LK.getScore() + 1);
scoreTxt.setText(LK.getScore());
break;
}
}
}
// Update enemy tanks
for (var i = enemyTanks.length - 1; i >= 0; i--) {
var enemy = enemyTanks[i];
enemy.update();
// Remove if off screen
if (enemy.y > GAME_H + enemy.height / 2) {
enemy.destroy();
enemyTanks.splice(i, 1);
continue;
}
// Check collision with player
if (enemy.intersects(player)) {
// Explosion
var exp = new Explosion();
exp.x = player.x;
exp.y = player.y;
game.addChild(exp);
LK.getSound('explode').play();
LK.effects.flashScreen(0xff0000, 800);
// Decrease health
playerHealth--;
// Update health display
var hearts = '';
for (var h = 0; h < playerHealth; h++) {
hearts += '❤';
}
healthTxt.setText(hearts);
if (playerHealth <= 0) {
// Create animated game over screen
gameOverAnimation = new GameOverAnimator();
game.addChild(gameOverAnimation);
gameOverAnimation.initialize();
// Delay the actual game over to allow animation to play
LK.setTimeout(function () {
LK.showGameOver();
}, 5000); // Show game over after 5 seconds of animation
return;
}
// Animate player hit effect
tween.stop(player, {
rotation: true,
scaleX: true,
scaleY: true,
alpha: true
});
tween(player, {
scaleX: 1.3,
scaleY: 1.3,
alpha: 0.6
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(player, {
scaleX: 1,
scaleY: 1,
alpha: 1
}, {
duration: 300,
easing: tween.elasticOut
});
}
});
// Remove enemy tank
enemy.destroy();
enemyTanks.splice(i, 1);
continue;
}
}
// Update enemy shells
for (var i = enemyShells.length - 1; i >= 0; i--) {
var shell = enemyShells[i];
shell.update();
// Remove if off screen
if (shell.y > GAME_H + shell.height / 2 || shell.x < -shell.width / 2 || shell.x > GAME_W + shell.width / 2) {
shell.destroy();
enemyShells.splice(i, 1);
continue;
}
// Check collision with player
if (shell.intersects(player)) {
// Explosion
var exp = new Explosion();
exp.x = shell.x;
exp.y = shell.y;
game.addChild(exp);
LK.getSound('explode').play();
LK.effects.flashScreen(0xff0000, 800);
// Decrease health
playerHealth--;
// Update health display
var hearts = '';
for (var h = 0; h < playerHealth; h++) {
hearts += '❤';
}
healthTxt.setText(hearts);
if (playerHealth <= 0) {
// Create animated game over screen
gameOverAnimation = new GameOverAnimator();
game.addChild(gameOverAnimation);
gameOverAnimation.initialize();
// Delay the actual game over to allow animation to play
LK.setTimeout(function () {
LK.showGameOver();
}, 5000); // Show game over after 5 seconds of animation
return;
}
// Animate player hit by shell
tween.stop(player, {
rotation: true,
alpha: true
});
// Push player slightly in the direction of the hit
var pushX = (player.x - shell.x) * 0.05;
var pushY = (player.y - shell.y) * 0.05;
var origX = player.x;
var origY = player.y;
tween(player, {
x: player.x + pushX,
y: player.y + pushY,
alpha: 0.7
}, {
duration: 120,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(player, {
x: origX,
y: origY,
alpha: 1
}, {
duration: 280,
easing: tween.easeInOut
});
}
});
// Remove shell
shell.destroy();
enemyShells.splice(i, 1);
continue;
}
}
// Update health power-ups
for (var i = healthPowerUps.length - 1; i >= 0; i--) {
var heart = healthPowerUps[i];
heart.update();
// Check if player collected the heart
if (player.intersects(heart)) {
// Only collect if health is less than maximum
if (playerHealth < 5) {
// Play collect sound
LK.getSound('enemyFire').play();
// Increase health
playerHealth++;
// Update health display
var hearts = '';
for (var h = 0; h < playerHealth; h++) {
hearts += '❤';
}
healthTxt.setText(hearts);
// Animation
heart.collect();
// Remove heart
LK.setTimeout(function () {
heart.destroy();
}, 300);
healthPowerUps.splice(i, 1);
// Flash effect
LK.effects.flashScreen(0x00ff00, 300);
}
}
// Remove if off screen
if (heart.y > GAME_H + heart.height / 2) {
heart.destroy();
healthPowerUps.splice(i, 1);
}
}
// Update health power-ups
for (var i = healthPowerUps.length - 1; i >= 0; i--) {
var heart = healthPowerUps[i];
heart.update();
// Check if player collected the heart
if (player.intersects(heart)) {
// Only collect if health is less than maximum
if (playerHealth < 5) {
// Play collect sound
LK.getSound('enemyFire').play();
// Increase health
playerHealth++;
// Update health display
var hearts = '';
for (var h = 0; h < playerHealth; h++) {
hearts += '❤';
}
healthTxt.setText(hearts);
// Animation
heart.collect();
// Remove heart
LK.setTimeout(function () {
heart.destroy();
}, 300);
healthPowerUps.splice(i, 1);
// Flash effect
LK.effects.flashScreen(0x00ff00, 300);
}
}
// Remove if off screen
if (heart.y > GAME_H + heart.height / 2) {
heart.destroy();
healthPowerUps.splice(i, 1);
}
}
// Spawn enemies
enemySpawnCounter++;
if (enemySpawnCounter >= getEnemySpawnInterval()) {
enemySpawnCounter = 0;
// Randomly spawn health power-up (around 10% chance, only if player's health is not full)
if (Math.random() < 0.1 && playerHealth < 5 && healthPowerUps.length < 2) {
var healthPowerUp = new Health();
var margin = 300;
healthPowerUp.x = margin + Math.random() * (GAME_W - margin * 2);
healthPowerUp.y = -healthPowerUp.height;
healthPowerUp.lastY = healthPowerUp.y;
// Add to game
healthPowerUps.push(healthPowerUp);
game.addChild(healthPowerUp);
// Apply slow falling movement
tween(healthPowerUp, {
y: GAME_H + healthPowerUp.height
}, {
duration: 20000,
// Slow fall over 20 seconds
easing: tween.linear
});
}
// Randomly spawn health power-up (around 10% chance, only if player's health is not full)
if (Math.random() < 0.1 && playerHealth < 5 && healthPowerUps.length < 2) {
var healthPowerUp = new Health();
var margin = 300;
healthPowerUp.x = margin + Math.random() * (GAME_W - margin * 2);
healthPowerUp.y = -healthPowerUp.height;
healthPowerUp.lastY = healthPowerUp.y;
// Add to game
healthPowerUps.push(healthPowerUp);
game.addChild(healthPowerUp);
// Apply slow falling movement
tween(healthPowerUp, {
y: GAME_H + healthPowerUp.height
}, {
duration: 20000,
// Slow fall over 20 seconds
easing: tween.linear
});
}
// Only spawn an enemy if there are not too many already
if (enemyTanks.length < 5 + Math.floor(LK.getScore() / 10)) {
var enemy = new EnemyTank();
// Position enemies at the top, across the width of the screen
var margin = 200;
enemy.x = margin / 2 + Math.random() * (GAME_W - margin);
enemy.y = -enemy.height / 2; // Start just off-screen at the top
// Rotate to face down
enemy.rotation = Math.PI; // 180 degrees to face down
enemyTanks.push(enemy);
game.addChild(enemy);
// Add spawn animation
enemy.alpha = 0;
enemy.scaleX = 0.2;
enemy.scaleY = 0.2;
tween(enemy, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 400,
easing: tween.elasticOut
});
console.log("Spawned enemy tank from top");
}
}
};
// Game initialization happens in startGame function