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