/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // BossCar Class var BossCar = Container.expand(function () { var self = Container.call(this); // Use unique bossCar asset var boss = self.attachAsset('bossCar', { anchorX: 0.5, anchorY: 0.5 }); // Target position (player car) self.targetX = 2048 / 2; self.targetY = 2732 / 2; // Speed (increased for faster boss movement) self.speed = 7.8; // Reduced by 1 // Direction vector self.dirX = 0; self.dirY = 1; // For collision self.radius = 110; // Reduced hitbox radius for smaller collision area // Boss health self.maxHealth = 20; self.health = self.maxHealth; // Update method self.update = function () { // Move toward target (player car) var dx = self.targetX - self.x; var dy = self.targetY - self.y; var len = Math.sqrt(dx * dx + dy * dy); if (len > 0.01) { self.dirX = dx / len; self.dirY = dy / len; self.x += self.dirX * self.speed; self.y += self.dirY * self.speed; boss.rotation = Math.atan2(self.dirY, self.dirX) + Math.PI / 2; } }; // Set target (player car position) self.setTarget = function (x, y) { self.targetX = x; self.targetY = y; }; // Take damage self.takeDamage = function (amount) { self.health -= amount; if (self.health < 0) self.health = 0; }; return self; }); // BossCar2 uses bossCar2 asset, but otherwise similar to BossCar var BossCar2 = Container.expand(function () { var self = Container.call(this); var boss = self.attachAsset('bossCar2', { anchorX: 0.5, anchorY: 0.5 }); self.targetX = 2048 / 2; self.targetY = 2732 / 2; self.speed = 8.5; // Slightly faster than BossCar, reduced by 1 self.dirX = 0; self.dirY = 1; self.radius = 100; self.maxHealth = 30; self.health = self.maxHealth; self.update = function () { var dx = self.targetX - self.x; var dy = self.targetY - self.y; var len = Math.sqrt(dx * dx + dy * dy); if (len > 0.01) { self.dirX = dx / len; self.dirY = dy / len; self.x += self.dirX * self.speed; self.y += self.dirY * self.speed; boss.rotation = Math.atan2(self.dirY, self.dirX) + Math.PI / 2; } }; self.setTarget = function (x, y) { self.targetX = x; self.targetY = y; }; self.takeDamage = function (amount) { self.health -= amount; if (self.health < 0) self.health = 0; }; return self; }); // BossCar3 uses bossCar3 asset, but otherwise similar to BossCar var BossCar3 = Container.expand(function () { var self = Container.call(this); var boss = self.attachAsset('bossCar3', { anchorX: 0.5, anchorY: 0.5 }); self.targetX = 2048 / 2; self.targetY = 2732 / 2; self.speed = 8.5; // Similar to BossCar2 self.dirX = 0; self.dirY = 1; self.radius = 120; self.maxHealth = 40; self.health = self.maxHealth; self.update = function () { var dx = self.targetX - self.x; var dy = self.targetY - self.y; var len = Math.sqrt(dx * dx + dy * dy); if (len > 0.01) { self.dirX = dx / len; self.dirY = dy / len; self.x += self.dirX * self.speed; self.y += self.dirY * self.speed; boss.rotation = Math.atan2(self.dirY, self.dirX) + Math.PI / 2; } }; self.setTarget = function (x, y) { self.targetX = x; self.targetY = y; }; self.takeDamage = function (amount) { self.health -= amount; if (self.health < 0) self.health = 0; }; return self; }); // Bullet Class var Bullet = Container.expand(function () { var self = Container.call(this); var bullet = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); // Direction vector self.dirX = 0; self.dirY = -1; // Speed self.speed = 38; // For collision self.radius = 30; // Update self.update = function () { self.x += self.dirX * self.speed; self.y += self.dirY * self.speed; }; // Set direction self.setDirection = function (dx, dy) { var len = Math.sqrt(dx * dx + dy * dy); if (len > 0.01) { self.dirX = dx / len; self.dirY = dy / len; } }; return self; }); // Enemy Car Class var EnemyCar = Container.expand(function () { var self = Container.call(this); var car = self.attachAsset('enemyCar', { anchorX: 0.5, anchorY: 0.5 }); // Target position (player car) self.targetX = 2048 / 2; self.targetY = 2732 / 2; // Speed (pixels per tick) self.speed = 7; // Direction vector self.dirX = 0; self.dirY = 1; // For collision self.radius = 90; // Update method self.update = function () { // Move toward target (player car) var dx = self.targetX - self.x; var dy = self.targetY - self.y; var len = Math.sqrt(dx * dx + dy * dy); if (len > 0.01) { self.dirX = dx / len; self.dirY = dy / len; // Enemy car speed is always self.speed, never boosted self.x += self.dirX * self.speed; self.y += self.dirY * self.speed; // Rotate car to face movement car.rotation = Math.atan2(self.dirY, self.dirX) + Math.PI / 2; } }; // Set target (player car position) self.setTarget = function (x, y) { self.targetX = x; self.targetY = y; }; return self; }); // Player Car Class var PlayerCar = Container.expand(function () { var self = Container.call(this); // Attach car asset, anchor center var car = self.attachAsset('playerCar', { anchorX: 0.5, anchorY: 0.5 }); // For visual orientation, add a "gun" at the top (optional) // Not required for MVP // Movement speed (pixels per tick) self.baseSpeed = 12; self.boostSpeed = 24; self.currentSpeed = self.baseSpeed; // Direction vector (normalized) self.dirX = 0; self.dirY = -1; // Set initial position to center self.x = 2048 / 2; self.y = 2732 / 2; // For auto-shoot cooldown self.shootCooldown = 0; // Update method: called every tick self.update = function () { // Move in direction of pointer // Only move if playerIsMoving is true (set by handleMove) if (typeof playerIsMoving !== "undefined" && playerIsMoving) { // Calculate next position var nextX = self.x + self.dirX * self.currentSpeed; var nextY = self.y + self.dirY * self.currentSpeed; // Clamp next position to game area bounds var minX = 90; var maxX = 2048 - 90; var minY = 160; var maxY = 2732 - 160; // Only update X if within bounds if (nextX >= minX && nextX <= maxX) { self.x = nextX; } else if (nextX < minX) { self.x = minX; } else if (nextX > maxX) { self.x = maxX; } // Only update Y if within bounds if (nextY >= minY && nextY <= maxY) { self.y = nextY; } else if (nextY < minY) { self.y = minY; } else if (nextY > maxY) { self.y = maxY; } } // Clamp to game area (keep inside bounds) if (self.x < 90) self.x = 90; if (self.x > 2048 - 90) self.x = 2048 - 90; if (self.y < 160) self.y = 160; if (self.y > 2732 - 160) self.y = 2732 - 160; // Cooldown for shooting if (self.shootCooldown > 0) self.shootCooldown--; }; // Set direction toward pointer self.setDirection = function (targetX, targetY) { var dx = targetX - self.x; var dy = targetY - self.y; var len = Math.sqrt(dx * dx + dy * dy); if (len > 0.01) { self.dirX = dx / len; self.dirY = dy / len; // Rotate car to face direction car.rotation = Math.atan2(self.dirY, self.dirX) + Math.PI / 2; } }; // Boost on press self.setBoost = function (boosting) { self.currentSpeed = boosting ? self.boostSpeed : self.baseSpeed; }; return self; }); // PowerUpBox Class: A box that gives player a shoot speed boost when collected var PowerUpBox = Container.expand(function (useBossDropAsset) { var self = Container.call(this); var box; if (useBossDropAsset) { // Use the bossDropBox asset if specified box = self.attachAsset('bossDropBox', { anchorX: 0.5, anchorY: 0.5, width: 120, height: 120, alpha: 0.95 }); } else { // Use a distinct color for the box box = self.attachAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, width: 120, height: 120, color: 0x4ddcfe, // cyan alpha: 0.85 }); } self.radius = 60; // Animate the box (pulse) using correct tween API var pulseTween = tween(box, { scaleX: 1.2, scaleY: 1.2 }, 500, { yoyo: true, repeat: Infinity }); // Update (optional: could float or spin) self.update = function () { // Could add floating or spinning effect here }; // Destroy: stop tween self.destroy = function () { if (pulseTween && typeof pulseTween.stop === "function") { pulseTween.stop(); } Container.prototype.destroy.call(self); }; return self; }); // SuperPackage Class: Spawns at random near center, kills all visible enemies on pickup, increments score for each var SuperPackage = Container.expand(function () { var self = Container.call(this); var box = self.attachAsset('superPackage', { anchorX: 0.5, anchorY: 0.5, width: 160, height: 160, alpha: 0.92 }); self.radius = 80; // Animate the box (pulse) var pulseTween = tween(box, { scaleX: 1.3, scaleY: 1.3 }, 400, { yoyo: true, repeat: Infinity }); self.update = function () { // Could float or spin if desired }; self.destroy = function () { if (pulseTween && typeof pulseTween.stop === "function") { pulseTween.stop(); } Container.prototype.destroy.call(self); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x222222 }); /**** * Game Code ****/ // Asset for the box dropped by BossCar on death // Add background image to the game // New background asset // SuperPackage asset var background = LK.getAsset('background', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); game.addChild(background); // --- Start Screen Overlay --- var startOverlay = new Container(); var startBg = LK.getAsset('startBg', { anchorX: 0.5, anchorY: 0.5, width: 2048, height: 2732, x: 2048 / 2, y: 2732 / 2, alpha: 0.92 }); startOverlay.addChild(startBg); // Title background asset (ellipse, behind text) var titleBg = LK.getAsset('titleBg', { anchorX: 0.5, anchorY: 0, x: 2048 / 2, y: 200 }); startOverlay.addChild(titleBg); // Title Text (smaller font, bold, white, centered) var titleTxt = new Text2("A Very Simple Tank Battle", { size: 110, fill: 0xffffff, font: "'Arial Black', 'Impact', 'GillSans-Bold', 'Arial', 'sans-serif', 'Segoe UI Black', 'Segoe UI', 'Roboto', 'Noto Sans', 'Liberation Sans'" }); titleTxt.anchor.set(0.5, 0); // top center titleTxt.x = 2048 / 2; titleTxt.y = 220; startOverlay.addChild(titleTxt); // Start Button var startButton = LK.getAsset('startButton', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 1500 }); startOverlay.addChild(startButton); var startBtnTxt = new Text2("Play", { size: 120, fill: 0xFFFFFF, font: "'Arial Black', 'Impact', 'GillSans-Bold', 'Arial', 'sans-serif', 'Segoe UI Black', 'Segoe UI', 'Roboto', 'Noto Sans', 'Liberation Sans'" // bold, stylized font stack }); startBtnTxt.anchor.set(0.5, 0.5); startBtnTxt.x = 2048 / 2; startBtnTxt.y = 1500; startOverlay.addChild(startBtnTxt); // Add overlay to game game.addChild(startOverlay); // --- Play Theme music on opening screen --- LK.playMusic('Theme'); // Block gameplay until started var gameStarted = false; // Only allow controller and gameplay after start function blockInputUntilStarted(fn) { return function (x, y, obj) { if (!gameStarted) return; return fn(x, y, obj); }; } // --- Replace event handlers to block input until started --- game.move = blockInputUntilStarted(handleMove); game.down = blockInputUntilStarted(function (x, y, obj) { var d = dist2(x, y, controllerCenterX, controllerCenterY); if (d <= controllerRadius) { handleMove(x, y, obj); } }); game.up = blockInputUntilStarted(function (x, y, obj) { boosting = false; player.setBoost(false); playerIsMoving = false; controllerActive = false; controllerTouchId = null; lastControllerActive = false; lastControllerDirX = 0; lastControllerDirY = 0; controllerKnob.x = controllerCenterX; controllerKnob.y = controllerCenterY; }); // --- Block update until started --- var realGameUpdate = function realGameUpdate() { // (all original game.update code will be here, see next change_block) }; game.update = function () { // Hide gameplay UI and controller until gameStarted if (!gameStarted) { // Hide gameplay UI scoreTxt.visible = false; levelTxt.visible = false; timerTxt.visible = false; controllerBg.visible = false; controllerKnob.visible = false; player.visible = false; // Hide all enemies, bullets, boss, powerup box if any for (var i = 0; i < enemies.length; i++) enemies[i].visible = false; for (var i = 0; i < bullets.length; i++) bullets[i].visible = false; if (bossCar) bossCar.visible = false; if (game._powerUpBox) game._powerUpBox.visible = false; return; } // Show gameplay UI and controller after gameStarted scoreTxt.visible = true; levelTxt.visible = true; timerTxt.visible = true; controllerBg.visible = true; controllerKnob.visible = true; player.visible = true; for (var i = 0; i < enemies.length; i++) enemies[i].visible = true; for (var i = 0; i < bullets.length; i++) bullets[i].visible = true; if (bossCar) bossCar.visible = true; if (game._powerUpBox) game._powerUpBox.visible = true; realGameUpdate(); }; // --- Start Button Event Handler --- startButton.down = function (x, y, obj) { if (gameStarted) return; gameStarted = true; // Stop Theme music when Play is pressed LK.stopMusic(); // Play Play sound when Play button is pressed LK.getSound('Play').play(); if (startOverlay && startOverlay.parent) { startOverlay.parent.removeChild(startOverlay); } }; startBtnTxt.down = startButton.down; // Also allow clicking text // Bullet: yellow ellipse // Enemy car: red box // Player car: blue box // --- Global variables --- var player = new PlayerCar(); player._fireRateBoosted = false; // Not boosted at start player._fireRateBoss2Boosted = false; // Not boosted at start player._fireRateBoss3Boosted = false; // Not boosted at start player._fireRateBoss4Boosted = false; // Not boosted at start game.addChild(player); var enemies = []; var bullets = []; // Boss state var bossSpawned = false; var bossCar = null; // Score var score = 0; var level = 1; var scoreTxt = new Text2('Score: 0', { size: 97.2, fill: 0xFFFFFF }); scoreTxt.anchor.set(1, 0); // right align score // Level text (centered between score and timer) var levelTxt = new Text2('Level 1', { size: 100, fill: 0xFFD700 // gold color for level }); levelTxt.anchor.set(0.5, 0); // center align // Timer text (elapsed time in seconds, right of score) var timerTxt = new Text2('0:00', { size: 99, fill: 0xFFFFFF }); timerTxt.anchor.set(0, 0); // left align timer // Position score, level, and timer // Arrange score, level, and timer in a single horizontal row, evenly spaced var guiRowY = 10; var guiSpacing = 80; // horizontal space between counters // Calculate total width of all counters and spacing var totalWidth = scoreTxt.width + levelTxt.width + timerTxt.width + 2 * guiSpacing; var startX = LK.gui.top.width / 2 - totalWidth / 2; // Score counter (left) scoreTxt.x = startX + scoreTxt.width; scoreTxt.y = guiRowY; // Level counter (center) levelTxt.x = scoreTxt.x + guiSpacing + levelTxt.width / 2; levelTxt.y = guiRowY; // Timer counter (right) timerTxt.x = levelTxt.x + levelTxt.width / 2 + guiSpacing; timerTxt.y = guiRowY; LK.gui.top.addChild(scoreTxt); LK.gui.top.addChild(levelTxt); LK.gui.top.addChild(timerTxt); // For pointer tracking (controller-based) var pointerX = 2048 / 2; var pointerY = 2732 / 2; var playerIsMoving = false; // Start as not moving // For boost var boosting = false; // --- Controller UI --- // Controller is a circular area at the bottom center of the screen var controllerRadius = 170; var controllerCenterX = 2048 / 2; var controllerCenterY = 2732 - controllerRadius - 60; // Controller visual (semi-transparent circle) var controllerBg = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, width: controllerRadius * 2, height: controllerRadius * 2, color: 0x888888, alpha: 0.25, x: controllerCenterX, y: controllerCenterY }); game.addChild(controllerBg); // Controller knob (shows current direction) var knobRadius = 80; var controllerKnob = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, width: knobRadius * 2, height: knobRadius * 2, color: 0x2980ff, alpha: 0.7, x: controllerCenterX, y: controllerCenterY }); game.addChild(controllerKnob); // Track controller state var controllerActive = false; var controllerTouchId = null; // Persistent controller direction for continuous movement var lastControllerActive = false; var lastControllerDirX = 0; var lastControllerDirY = 0; // Helper: get distance between two points function dist2(x1, y1, x2, y2) { var dx = x1 - x2; var dy = y1 - y2; return Math.sqrt(dx * dx + dy * dy); } // For enemy spawn timer var enemySpawnTicks = 0; // Dynamic spawn interval variables var ENEMY_SPAWN_INTERVAL_START = 180; // 3 seconds at 60fps var ENEMY_SPAWN_INTERVAL_END = 60; // 1 second at 60fps var ENEMY_SPAWN_INTERVAL = ENEMY_SPAWN_INTERVAL_START; // Timer for game progression (in ticks) var gameTimerTicks = 0; var GAME_TIMER_MAX = 60 * 60; // 60 seconds to reach max difficulty // --- Event Handlers --- // Move: handle controller input only if inside controller area function handleMove(x, y, obj) { // Only respond to touches inside controller area or if already dragging controller var isTouch = obj && obj.event && typeof obj.event.identifier !== "undefined"; var touchId = isTouch ? obj.event.identifier : null; // If not already active, only activate if touch is inside controller if (!controllerActive) { var d = dist2(x, y, controllerCenterX, controllerCenterY); if (d <= controllerRadius) { controllerActive = true; controllerTouchId = touchId; } else { // Ignore touches outside controller return; } } else { // If already active, only respond to the same touch if (isTouch && controllerTouchId !== null && touchId !== controllerTouchId) { return; } } // Calculate direction from controller center to touch var dx = x - controllerCenterX; var dy = y - controllerCenterY; var len = Math.sqrt(dx * dx + dy * dy); // Clamp knob to controller radius var knobX = controllerCenterX; var knobY = controllerCenterY; if (len > controllerRadius) { dx = dx * controllerRadius / len; dy = dy * controllerRadius / len; knobX += dx; knobY += dy; } else { knobX = x; knobY = y; } controllerKnob.x = knobX; controllerKnob.y = knobY; // If movement is significant, set direction and move if (len > 30) { // Move player in the direction of the knob relative to controller center player.setDirection(player.x + dx, player.y + dy); playerIsMoving = true; // Store last direction for persistent movement lastControllerDirX = dx / len; lastControllerDirY = dy / len; lastControllerActive = true; } else { playerIsMoving = false; lastControllerActive = false; } if (typeof LK !== "undefined" && typeof LK.ticks !== "undefined") { lastMoveTick = LK.ticks; } } game.move = handleMove; // Down: only activate boost if controller is pressed game.down = function (x, y, obj) { var d = dist2(x, y, controllerCenterX, controllerCenterY); // Do not activate boost on controller press anymore if (d <= controllerRadius) { handleMove(x, y, obj); } }; // Up: stop boost and controller movement game.up = function (x, y, obj) { boosting = false; player.setBoost(false); playerIsMoving = false; controllerActive = false; controllerTouchId = null; // Reset persistent controller direction lastControllerActive = false; lastControllerDirX = 0; lastControllerDirY = 0; // Reset knob to center controllerKnob.x = controllerCenterX; controllerKnob.y = controllerCenterY; }; // --- Main Game Loop --- // All main game logic is now in realGameUpdate for start screen support var realGameUpdate = function realGameUpdate() { // Block all updates if loser overlay is active if (game._loserOverlayActive) { return; } // --- SuperPackage spawn logic (Level 3+) --- if (typeof game._superPackageTimerTicks === "undefined") { game._superPackageTimerTicks = 0; } if (typeof game._superPackageActive === "undefined") { game._superPackageActive = false; } if (typeof game._superPackageObj === "undefined") { game._superPackageObj = null; } // --- bossDropBox3 spawn logic at score 100 --- if (typeof game._bossDropBox3Spawned === "undefined") { game._bossDropBox3Spawned = false; } if (typeof game._bossDropBox3Obj === "undefined") { game._bossDropBox3Obj = null; } if (score >= 100 && !game._bossDropBox3Spawned) { // Spawn bossDropBox3 at random near center (±250px) var cx3 = 1024 + (Math.random() - 0.5) * 500; var cy3 = 1366 + (Math.random() - 0.5) * 500; // Clamp to safe area if (cx3 < 200) cx3 = 200; if (cx3 > 2048 - 200) cx3 = 2048 - 200; if (cy3 < 200) cy3 = 200; if (cy3 > 2732 - 200) cy3 = 2732 - 200; var bossDropBox3 = LK.getAsset('bossDropBox3', { anchorX: 0.5, anchorY: 0.5, width: 120, height: 120, alpha: 0.98 }); bossDropBox3.x = cx3; bossDropBox3.y = cy3; game.addChild(bossDropBox3); game._bossDropBox3Obj = bossDropBox3; game._bossDropBox3Spawned = true; } // Check pickup for bossDropBox3 if (game._bossDropBox3Obj) { var dx3 = player.x - game._bossDropBox3Obj.x; var dy3 = player.y - game._bossDropBox3Obj.y; var dist3 = Math.sqrt(dx3 * dx3 + dy3 * dy3); if (dist3 < 120) { // Picked up! if (typeof game._bossDropBox3Obj.destroy === "function") { game._bossDropBox3Obj.destroy(); } game._bossDropBox3Obj = null; // Play PowerUp sound when bossDropBox3 is picked up LK.getSound('PowerUp').play(); // Effect: Increase fire rate to 5/sec and keep diagonal triple shot player._fireRateBoss2Boosted = true; player._fireRateBoosted = false; // Remove previous boost if any player._fireRateBoss3Boosted = true; // Mark that bossDropBox3 was picked up // Visual feedback LK.effects.flashScreen(0x00bfff, 800); // blue flash for bossDropBox3 } } if (level >= 3) { game._superPackageTimerTicks++; // Every 5 seconds (300 ticks), if not already active, spawn a SuperPackage if (!game._superPackageActive && game._superPackageTimerTicks >= 300) { // Random position near center (±200px) var cx = 1024 + (Math.random() - 0.5) * 400; var cy = 1366 + (Math.random() - 0.5) * 400; // Clamp to safe area if (cx < 200) cx = 200; if (cx > 2048 - 200) cx = 2048 - 200; if (cy < 200) cy = 200; if (cy > 2732 - 200) cy = 2732 - 200; var superPkg = new SuperPackage(); superPkg.x = cx; superPkg.y = cy; game.addChild(superPkg); game._superPackageObj = superPkg; game._superPackageActive = true; game._superPackageTimerTicks = 0; } // Check pickup if (game._superPackageActive && game._superPackageObj) { var dx = player.x - game._superPackageObj.x; var dy = player.y - game._superPackageObj.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 120) { // Picked up! Kill all visible enemies, increment score for each // Play Patlama sound when SuperPackage is picked up LK.getSound('Patlama').play(); var killed = 0; for (var i = enemies.length - 1; i >= 0; i--) { if (enemies[i] && typeof enemies[i].destroy === "function") { enemies[i].destroy(); enemies.splice(i, 1); killed++; } } // Score for each killed if (killed > 0) { score += killed; if (typeof scoreTxt !== "undefined") { scoreTxt.setText('Score: ' + score); } } // Visual feedback LK.effects.flashScreen(0xffa500, 600); // orange explosion for SuperPackage // Remove package game._superPackageObj.destroy(); game._superPackageObj = null; game._superPackageActive = false; game._superPackageTimerTicks = 0; } } // If package is still active but destroyed (e.g. by game reset), clear state if (game._superPackageActive && !game._superPackageObj) { game._superPackageActive = false; game._superPackageTimerTicks = 0; } } else { // Reset timer if below level 3 game._superPackageTimerTicks = 0; if (game._superPackageObj) { game._superPackageObj.destroy(); game._superPackageObj = null; } game._superPackageActive = false; } // Update player // If controller was last dragged in a direction, keep moving in that direction if (typeof lastControllerActive !== "undefined" && lastControllerActive && typeof lastControllerDirX !== "undefined" && typeof lastControllerDirY !== "undefined") { // Set direction to last drag direction player.dirX = lastControllerDirX; player.dirY = lastControllerDirY; playerIsMoving = true; } // Handle shoot speed boost timer if (player._shootBoostActive) { if (typeof player._shootBoostTicks === "undefined") player._shootBoostTicks = 0; player._shootBoostTicks--; if (player._shootBoostTicks <= 0) { player._shootBoostActive = false; // Do NOT revert fire rate after boost ends; keep it at 2.5/sec // player._fireRateBoosted remains true } } player.update(); // If no move event occurred this frame, stop player movement if (typeof LK !== "undefined" && typeof LK.ticks !== "undefined") { if (typeof lastMoveTick === "undefined") { lastMoveTick = LK.ticks; } if (lastMoveTick !== LK.ticks) { // Only stop if controller is not active if (!lastControllerActive) { playerIsMoving = false; } } } // --- Timer and Dynamic Enemy Spawning --- gameTimerTicks++; // Update timer text (show mm:ss) var elapsedSec = Math.floor(gameTimerTicks / 60); var min = Math.floor(elapsedSec / 60); var sec = elapsedSec % 60; timerTxt.setText(min + ':' + (sec < 10 ? '0' : '') + sec); // Every second, increase spawn rate by reducing ENEMY_SPAWN_INTERVAL_START, but clamp to ENEMY_SPAWN_INTERVAL_END if (gameTimerTicks % 60 === 0) { ENEMY_SPAWN_INTERVAL_START = Math.max(ENEMY_SPAWN_INTERVAL_END, ENEMY_SPAWN_INTERVAL_START - 5); } // Calculate progression (0 to 1) var progress = Math.min(gameTimerTicks / GAME_TIMER_MAX, 1); // Linearly interpolate spawn interval from start to end ENEMY_SPAWN_INTERVAL = Math.round(ENEMY_SPAWN_INTERVAL_START + (ENEMY_SPAWN_INTERVAL_END - ENEMY_SPAWN_INTERVAL_START) * progress); enemySpawnTicks++; // Determine how many enemies to spawn this cycle (1 + extra for every 10 kills) var spawnCount = 1 + Math.floor(score / 10); // Boss spawn logic if (!bossSpawned && score >= 10 && score < 50 && typeof game._bossDefeated === "undefined") { bossSpawned = true; bossCar = new BossCar(); // Spawn boss at random edge var edge = Math.floor(Math.random() * 4); var bx, by; if (edge === 0) { bx = 200 + Math.random() * (2048 - 400); by = -300; } else if (edge === 1) { bx = 2048 + 300; by = 200 + Math.random() * (2732 - 400); } else if (edge === 2) { bx = 200 + Math.random() * (2048 - 400); by = 2732 + 300; } else { bx = -300; by = 200 + Math.random() * (2732 - 400); } bossCar.x = bx; bossCar.y = by; bossCar.setTarget(player.x, player.y); game.addChild(bossCar); } // BossCar2 spawn logic at score 50 if (!bossSpawned && score >= 50 && typeof game._boss2Spawned === "undefined") { bossSpawned = true; game._boss2Spawned = true; bossCar = new BossCar2(); // Spawn boss2 at random edge var edge2 = Math.floor(Math.random() * 4); var bx2, by2; if (edge2 === 0) { bx2 = 200 + Math.random() * (2048 - 400); by2 = -300; } else if (edge2 === 1) { bx2 = 2048 + 300; by2 = 200 + Math.random() * (2732 - 400); } else if (edge2 === 2) { bx2 = 200 + Math.random() * (2048 - 400); by2 = 2732 + 300; } else { bx2 = -300; by2 = 200 + Math.random() * (2732 - 400); } bossCar.x = bx2; bossCar.y = by2; bossCar.setTarget(player.x, player.y); game.addChild(bossCar); } // BossCar3 spawn logic at score 200 if (!bossSpawned && score >= 200 && typeof game._boss3Spawned === "undefined") { bossSpawned = true; game._boss3Spawned = true; bossCar = new BossCar3(); // Spawn boss3 at random edge var edge3 = Math.floor(Math.random() * 4); var bx3, by3; if (edge3 === 0) { bx3 = 200 + Math.random() * (2048 - 400); by3 = -300; } else if (edge3 === 1) { bx3 = 2048 + 300; by3 = 200 + Math.random() * (2732 - 400); } else if (edge3 === 2) { bx3 = 200 + Math.random() * (2048 - 400); by3 = 2732 + 300; } else { bx3 = -300; by3 = 200 + Math.random() * (2732 - 400); } bossCar.x = bx3; bossCar.y = by3; bossCar.setTarget(player.x, player.y); game.addChild(bossCar); } // BossCar4 spawn logic at score 500 if (!bossSpawned && score >= 500 && typeof game._boss4Spawned === "undefined") { bossSpawned = true; game._boss4Spawned = true; bossCar = new (Container.expand(function () { var self = Container.call(this); var boss = self.attachAsset('bossCar4', { anchorX: 0.5, anchorY: 0.5 }); self.targetX = 2048 / 2; self.targetY = 2732 / 2; self.speed = 8.8; self.dirX = 0; self.dirY = 1; self.radius = 130; self.maxHealth = 60; self.health = self.maxHealth; self.update = function () { var dx = self.targetX - self.x; var dy = self.targetY - self.y; var len = Math.sqrt(dx * dx + dy * dy); if (len > 0.01) { self.dirX = dx / len; self.dirY = dy / len; self.x += self.dirX * self.speed; self.y += self.dirY * self.speed; boss.rotation = Math.atan2(self.dirY, self.dirX) + Math.PI / 2; } }; self.setTarget = function (x, y) { self.targetX = x; self.targetY = y; }; self.takeDamage = function (amount) { self.health -= amount; if (self.health < 0) self.health = 0; }; return self; }))(); // Spawn boss4 at random edge var edge4 = Math.floor(Math.random() * 4); var bx4, by4; if (edge4 === 0) { bx4 = 200 + Math.random() * (2048 - 400); by4 = -300; } else if (edge4 === 1) { bx4 = 2048 + 300; by4 = 200 + Math.random() * (2732 - 400); } else if (edge4 === 2) { bx4 = 200 + Math.random() * (2048 - 400); by4 = 2732 + 300; } else { bx4 = -300; by4 = 200 + Math.random() * (2732 - 400); } bossCar.x = bx4; bossCar.y = by4; bossCar.setTarget(player.x, player.y); game.addChild(bossCar); } // Only spawn normal enemies if boss is not spawned, not in enemy spawn delay, and BossCar2/BossCar3/BossCar4 is not alive var bossCar2Alive = false; var bossCar3Alive = false; var bossCar4Alive = false; if (bossCar && bossCar instanceof BossCar2 && bossSpawned) { bossCar2Alive = true; } if (bossCar && bossCar instanceof BossCar3 && bossSpawned) { bossCar3Alive = true; } // BossCar4 is a custom inline class, so check by asset id if (bossCar && bossCar.children && bossCar.children.length > 0 && bossCar.children[0].assetId === 'bossCar4' && bossSpawned) { bossCar4Alive = true; } if (!bossSpawned && enemySpawnTicks >= ENEMY_SPAWN_INTERVAL && !game._enemySpawnDelayActive && !bossCar2Alive && !bossCar3Alive && !bossCar4Alive) { enemySpawnTicks = 0; for (var spawnIdx = 0; spawnIdx < spawnCount; spawnIdx++) { // Spawn enemy at random edge var edge = Math.floor(Math.random() * 4); // 0:top, 1:right, 2:bottom, 3:left var ex, ey; if (edge === 0) { // top ex = 200 + Math.random() * (2048 - 400); ey = -160; } else if (edge === 1) { // right ex = 2048 + 160; ey = 200 + Math.random() * (2732 - 400); } else if (edge === 2) { // bottom ex = 200 + Math.random() * (2048 - 400); ey = 2732 + 160; } else { // left ex = -160; ey = 200 + Math.random() * (2732 - 400); } var enemy = new EnemyCar(); enemy.x = ex; enemy.y = ey; enemy.setTarget(player.x, player.y); enemies.push(enemy); game.addChild(enemy); } } // --- Update Enemies --- for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; enemy.setTarget(player.x, player.y); enemy.update(); // Check collision with player (circle collision) var dx = enemy.x - player.x; var dy = enemy.y - player.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 160) { // Game over: show YOU ARE LOSER overlay, pause counters, black bg, white text, 5s then end if (!game._loserOverlayShown) { LK.getSound('Dead').play(); game._loserOverlayShown = true; // Hide gameplay UI scoreTxt.visible = false; levelTxt.visible = false; timerTxt.visible = false; controllerBg.visible = false; controllerKnob.visible = false; player.visible = false; for (var i = 0; i < enemies.length; i++) enemies[i].visible = false; for (var i = 0; i < bullets.length; i++) bullets[i].visible = false; if (bossCar) bossCar.visible = false; if (game._powerUpBox) game._powerUpBox.visible = false; // Black overlay var loserOverlay = new Container(); loserOverlay._isLoserOverlay = true; var blackBg = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, width: 2048 * 1.5, height: 2732 * 1.5, color: 0x000000, x: 2048 / 2, y: 2732 / 2, alpha: 1 }); loserOverlay.addChild(blackBg); // YOU ARE LOSER text var loserTxt = new Text2("YOU ARE LOSER", { size: 220, fill: 0xffffff, font: "'Arial Black', 'Impact', 'GillSans-Bold', 'Arial', 'sans-serif', 'Segoe UI Black', 'Segoe UI', 'Roboto', 'Noto Sans', 'Liberation Sans'" }); loserTxt.anchor.set(0.5, 0.5); loserTxt.x = 2048 / 2; loserTxt.y = 2732 / 2; loserOverlay.addChild(loserTxt); game.addChild(loserOverlay); // Pause all counters/timers by blocking realGameUpdate game._loserOverlayActive = true; // After 5 seconds, remove overlay and end game LK.setTimeout(function () { if (loserOverlay && loserOverlay.parent) { loserOverlay.parent.removeChild(loserOverlay); } LK.showGameOver(); }, 5000); } return; } } // --- Update BossCar --- if (bossSpawned && bossCar) { bossCar.setTarget(player.x, player.y); bossCar.update(); // Check collision with player (circle collision, bigger radius) var bdx = bossCar.x - player.x; var bdy = bossCar.y - player.y; var bdist = Math.sqrt(bdx * bdx + bdy * bdy); if (bdist < 220) { // Game over: show YOU ARE LOSER overlay, pause counters, black bg, white text, 5s then end if (!game._loserOverlayShown) { LK.getSound('Dead').play(); game._loserOverlayShown = true; // Hide gameplay UI scoreTxt.visible = false; levelTxt.visible = false; timerTxt.visible = false; controllerBg.visible = false; controllerKnob.visible = false; player.visible = false; for (var i = 0; i < enemies.length; i++) enemies[i].visible = false; for (var i = 0; i < bullets.length; i++) bullets[i].visible = false; if (bossCar) bossCar.visible = false; if (game._powerUpBox) game._powerUpBox.visible = false; // Black overlay var loserOverlay = new Container(); loserOverlay._isLoserOverlay = true; var blackBg = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, width: 2048 * 1.5, height: 2732 * 1.5, color: 0x000000, x: 2048 / 2, y: 2732 / 2, alpha: 1 }); loserOverlay.addChild(blackBg); // YOU ARE LOSER text var loserTxt = new Text2("YOU ARE LOSER", { size: 220, fill: 0xffffff, font: "'Arial Black', 'Impact', 'GillSans-Bold', 'Arial', 'sans-serif', 'Segoe UI Black', 'Segoe UI', 'Roboto', 'Noto Sans', 'Liberation Sans'" }); loserTxt.anchor.set(0.5, 0.5); loserTxt.x = 2048 / 2; loserTxt.y = 2732 / 2; loserOverlay.addChild(loserTxt); game.addChild(loserOverlay); // Pause all counters/timers by blocking realGameUpdate game._loserOverlayActive = true; // After 5 seconds, remove overlay and end game LK.setTimeout(function () { if (loserOverlay && loserOverlay.parent) { loserOverlay.parent.removeChild(loserOverlay); } LK.showGameOver(); }, 5000); } return; } // BossCar damages enemies it collides with (just like player does) for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; var edx = bossCar.x - enemy.x; var edy = bossCar.y - enemy.y; var edist = Math.sqrt(edx * edx + edy * edy); if (edist < 160) { // Remove enemy enemy.destroy(); enemies.splice(i, 1); // Optionally, you could increment score or add effect, but for now just remove } } // If boss defeated, show win if (bossCar.health <= 0) { // Play Patlama sound on boss defeat LK.getSound('Patlama').play(); // If BossCar3 is defeated, drop bossDropBox4 and allow enemies to respawn, do not end game if (bossCar instanceof BossCar3) { // Drop bossDropBox4 at boss position var bossDropBox4 = LK.getAsset('bossDropBox4', { anchorX: 0.5, anchorY: 0.5, width: 120, height: 120, alpha: 0.98 }); bossDropBox4.x = bossCar.x; bossDropBox4.y = bossCar.y; game.addChild(bossDropBox4); game._bossDropBox4Obj = bossDropBox4; // Mark boss3 as defeated, but do not end game game._boss3Defeated = true; // Allow enemies to respawn after short delay bossSpawned = false; if (typeof game._enemySpawnDelayTimeout !== "undefined") { LK.clearTimeout(game._enemySpawnDelayTimeout); } game._enemySpawnDelayActive = true; game._enemySpawnDelayTimeout = LK.setTimeout(function () { game._enemySpawnDelayActive = false; }, 2000); LK.effects.flashScreen(0xff00ff, 1000); // Magenta flash for bossDropBox4 } else { // Mark boss as defeated so it never spawns again game._bossDefeated = true; // Spawn power-up box at boss position, using bossDropBox asset var powerUpBox = new PowerUpBox(true); powerUpBox.x = bossCar.x; powerUpBox.y = bossCar.y; game.addChild(powerUpBox); // Store reference for collision check game._powerUpBox = powerUpBox; // Level will be incremented only when package is collected LK.effects.flashScreen(0x00ff00, 1000); // Do NOT show win yet; wait for player to collect power-up // LK.showYouWin(); // return; } bossCar.destroy(); bossCar = null; } } // --- PowerUpBox pickup check --- if (game._powerUpBox) { var dx = player.x - game._powerUpBox.x; var dy = player.y - game._powerUpBox.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 120) { // Picked up! game._powerUpBox.destroy(); game._powerUpBox = null; // Play Sound2 when player collects the BossCar's dropped box LK.getSound('Sound2').play(); // If this is the drop from BossCar2, enable diagonal triple shot at 3/sec if (bossCar === null && typeof game._boss2Spawned !== "undefined" && typeof game._bossDefeated !== "undefined" && game._boss2Spawned && game._bossDefeated) { player._fireRateBoss2Boosted = true; player._fireRateBoosted = false; // Remove previous boost if any } else { // Apply shoot speed boost (reduce cooldown) player._shootBoostActive = true; player._shootBoostTicks = 60 * 8; // 8 seconds player.shootCooldown = 0; // allow instant shot player._fireRateBoosted = true; // Mark that fire rate should be boosted after pickup } // Visual feedback (flash) LK.effects.flashObject(player, 0x4ddcfe, 800); // Increment level when package is collected level++; if (typeof levelTxt !== "undefined") { levelTxt.setText("Level " + level); } // Instead of showing win, start a 2 second delay before allowing EnemyCar spawn again bossSpawned = false; // Boss will not spawn again, but allow enemies after delay if (typeof game._enemySpawnDelayTimeout !== "undefined") { LK.clearTimeout(game._enemySpawnDelayTimeout); } game._enemySpawnDelayActive = true; game._enemySpawnDelayTimeout = LK.setTimeout(function () { game._enemySpawnDelayActive = false; }, 2000); // Do NOT show win or end game here // LK.effects.flashScreen(0x00ff00, 1000); // LK.showYouWin(); // return; } } // --- bossDropBox4 pickup check --- if (game._bossDropBox4Obj) { var dx4 = player.x - game._bossDropBox4Obj.x; var dy4 = player.y - game._bossDropBox4Obj.y; var dist4 = Math.sqrt(dx4 * dx4 + dy4 * dy4); if (dist4 < 120) { // Picked up! if (typeof game._bossDropBox4Obj.destroy === "function") { game._bossDropBox4Obj.destroy(); } game._bossDropBox4Obj = null; // Visual feedback for bossDropBox4 LK.effects.flashObject(player, 0xff00ff, 800); // Set fire rate to 20/sec (3 ticks) after picking up bossDropBox4 player._fireRateBoss4Boosted = true; } } // --- Auto-shoot at nearest enemy or bossCar --- // If there are enemies, shoot at nearest enemy. If not, and bossCar is present, shoot at bossCar. // Always limit fire rate to 1 bullet per second (60 ticks), unless boosted if (player.shootCooldown === 0) { var target = null; if (enemies.length > 0) { // Find nearest enemy var minDist = 999999; for (var i = 0; i < enemies.length; i++) { var dx = enemies[i].x - player.x; var dy = enemies[i].y - player.y; var d = dx * dx + dy * dy; if (d < minDist) { minDist = d; target = enemies[i]; } } } else if (bossSpawned && bossCar) { // Only target bossCar if within enemy range (same as enemy targeting) var dx = bossCar.x - player.x; var dy = bossCar.y - player.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 900) { // 900 is 30*30, but enemy targeting uses squared distance, so use same target = bossCar; } } if (target) { // Check if player has picked up bossDropBox2 (from BossCar2) if (player._fireRateBoss2Boosted) { // If bossDropBox3 was picked up, fire 3 diagonal bullets at 5/sec (every 12 ticks) // If bossDropBox4 was picked up, fire 3 diagonal bullets at 20/sec (every 3 ticks) var carDir = Math.atan2(player.dirY, player.dirX); var offsetX = Math.cos(carDir) * 180; var offsetY = Math.sin(carDir) * 180; // Center bullet var bullet0 = new Bullet(); bullet0.x = player.x + offsetX; bullet0.y = player.y + offsetY; var bdx0 = target.x - bullet0.x; var bdy0 = target.y - bullet0.y; bullet0.setDirection(bdx0, bdy0); bullets.push(bullet0); game.addChild(bullet0); // Left diagonal bullet var angleLeft = carDir - Math.PI / 6; // 30 degrees left var bulletL = new Bullet(); bulletL.x = player.x + Math.cos(angleLeft) * 180; bulletL.y = player.y + Math.sin(angleLeft) * 180; bulletL.setDirection(Math.cos(angleLeft), Math.sin(angleLeft)); bullets.push(bulletL); game.addChild(bulletL); // Right diagonal bullet var angleRight = carDir + Math.PI / 6; // 30 degrees right var bulletR = new Bullet(); bulletR.x = player.x + Math.cos(angleRight) * 180; bulletR.y = player.y + Math.sin(angleRight) * 180; bulletR.setDirection(Math.cos(angleRight), Math.sin(angleRight)); bullets.push(bulletR); game.addChild(bulletR); // Play Sound1 when player fires LK.getSound('Sound1').play(); // Set cooldown for 20/sec (3 ticks) if bossDropBox4 picked up, else 5/sec (12 ticks) if bossDropBox3 picked up, else 3/sec (20 ticks) if (player._fireRateBoss4Boosted) { player.shootCooldown = 3; } else if (player._fireRateBoss3Boosted) { player.shootCooldown = 12; } else { player.shootCooldown = 20; } } else if (player._fireRateBoosted) { // Single shot at 2.5/sec (24 ticks) var bullet = new Bullet(); var carDir = Math.atan2(player.dirY, player.dirX); var offsetX = Math.cos(carDir) * 180; var offsetY = Math.sin(carDir) * 180; bullet.x = player.x + offsetX; bullet.y = player.y + offsetY; var bdx = target.x - bullet.x; var bdy = target.y - bullet.y; bullet.setDirection(bdx, bdy); bullets.push(bullet); game.addChild(bullet); LK.getSound('Sound1').play(); player.shootCooldown = 24; } else { // Single shot at 1/sec (60 ticks) var bullet = new Bullet(); var carDir = Math.atan2(player.dirY, player.dirX); var offsetX = Math.cos(carDir) * 180; var offsetY = Math.sin(carDir) * 180; bullet.x = player.x + offsetX; bullet.y = player.y + offsetY; var bdx = target.x - bullet.x; var bdy = target.y - bullet.y; bullet.setDirection(bdx, bdy); bullets.push(bullet); game.addChild(bullet); LK.getSound('Sound1').play(); player.shootCooldown = 60; } } } // --- Update Bullets --- for (var i = bullets.length - 1; i >= 0; i--) { var bullet = bullets[i]; bullet.update(); // Remove if out of bounds if (bullet.x < -100 || bullet.x > 2048 + 100 || bullet.y < -100 || bullet.y > 2732 + 100) { bullet.destroy(); bullets.splice(i, 1); continue; } // Check collision with enemies var hit = false; for (var j = enemies.length - 1; j >= 0; j--) { var enemy = enemies[j]; var dx = bullet.x - enemy.x; var dy = bullet.y - enemy.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 100) { // Hit! hit = true; // Remove enemy enemy.destroy(); enemies.splice(j, 1); // Remove bullet bullet.destroy(); bullets.splice(i, 1); // Score score++; scoreTxt.setText('Score: ' + score); break; } } // Check collision with bossCar if spawned, but only if in range (same as enemy range) if (!hit && bossSpawned && bossCar && function () { var dx = bullet.x - bossCar.x; var dy = bullet.y - bossCar.y; var dist = Math.sqrt(dx * dx + dy * dy); return dist < 100; // Use same range as enemy hit }()) { // Hit boss! hit = true; bossCar.takeDamage(1); bullet.destroy(); bullets.splice(i, 1); // Optionally, flash boss or show health bar (not required for MVP) } if (hit) continue; } // --- Camera: keep player centered --- // (In LK, the game area is fixed, so we simulate by keeping player in center and moving everything else if needed) // For MVP, player always stays in center, so no need to move camera. }; // --- Initial pointer direction --- // No initial movement; player will move when controller is used player.setDirection(player.x, player.y - 1);
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// BossCar Class
var BossCar = Container.expand(function () {
var self = Container.call(this);
// Use unique bossCar asset
var boss = self.attachAsset('bossCar', {
anchorX: 0.5,
anchorY: 0.5
});
// Target position (player car)
self.targetX = 2048 / 2;
self.targetY = 2732 / 2;
// Speed (increased for faster boss movement)
self.speed = 7.8; // Reduced by 1
// Direction vector
self.dirX = 0;
self.dirY = 1;
// For collision
self.radius = 110; // Reduced hitbox radius for smaller collision area
// Boss health
self.maxHealth = 20;
self.health = self.maxHealth;
// Update method
self.update = function () {
// Move toward target (player car)
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var len = Math.sqrt(dx * dx + dy * dy);
if (len > 0.01) {
self.dirX = dx / len;
self.dirY = dy / len;
self.x += self.dirX * self.speed;
self.y += self.dirY * self.speed;
boss.rotation = Math.atan2(self.dirY, self.dirX) + Math.PI / 2;
}
};
// Set target (player car position)
self.setTarget = function (x, y) {
self.targetX = x;
self.targetY = y;
};
// Take damage
self.takeDamage = function (amount) {
self.health -= amount;
if (self.health < 0) self.health = 0;
};
return self;
});
// BossCar2 uses bossCar2 asset, but otherwise similar to BossCar
var BossCar2 = Container.expand(function () {
var self = Container.call(this);
var boss = self.attachAsset('bossCar2', {
anchorX: 0.5,
anchorY: 0.5
});
self.targetX = 2048 / 2;
self.targetY = 2732 / 2;
self.speed = 8.5; // Slightly faster than BossCar, reduced by 1
self.dirX = 0;
self.dirY = 1;
self.radius = 100;
self.maxHealth = 30;
self.health = self.maxHealth;
self.update = function () {
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var len = Math.sqrt(dx * dx + dy * dy);
if (len > 0.01) {
self.dirX = dx / len;
self.dirY = dy / len;
self.x += self.dirX * self.speed;
self.y += self.dirY * self.speed;
boss.rotation = Math.atan2(self.dirY, self.dirX) + Math.PI / 2;
}
};
self.setTarget = function (x, y) {
self.targetX = x;
self.targetY = y;
};
self.takeDamage = function (amount) {
self.health -= amount;
if (self.health < 0) self.health = 0;
};
return self;
});
// BossCar3 uses bossCar3 asset, but otherwise similar to BossCar
var BossCar3 = Container.expand(function () {
var self = Container.call(this);
var boss = self.attachAsset('bossCar3', {
anchorX: 0.5,
anchorY: 0.5
});
self.targetX = 2048 / 2;
self.targetY = 2732 / 2;
self.speed = 8.5; // Similar to BossCar2
self.dirX = 0;
self.dirY = 1;
self.radius = 120;
self.maxHealth = 40;
self.health = self.maxHealth;
self.update = function () {
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var len = Math.sqrt(dx * dx + dy * dy);
if (len > 0.01) {
self.dirX = dx / len;
self.dirY = dy / len;
self.x += self.dirX * self.speed;
self.y += self.dirY * self.speed;
boss.rotation = Math.atan2(self.dirY, self.dirX) + Math.PI / 2;
}
};
self.setTarget = function (x, y) {
self.targetX = x;
self.targetY = y;
};
self.takeDamage = function (amount) {
self.health -= amount;
if (self.health < 0) self.health = 0;
};
return self;
});
// Bullet Class
var Bullet = Container.expand(function () {
var self = Container.call(this);
var bullet = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
// Direction vector
self.dirX = 0;
self.dirY = -1;
// Speed
self.speed = 38;
// For collision
self.radius = 30;
// Update
self.update = function () {
self.x += self.dirX * self.speed;
self.y += self.dirY * self.speed;
};
// Set direction
self.setDirection = function (dx, dy) {
var len = Math.sqrt(dx * dx + dy * dy);
if (len > 0.01) {
self.dirX = dx / len;
self.dirY = dy / len;
}
};
return self;
});
// Enemy Car Class
var EnemyCar = Container.expand(function () {
var self = Container.call(this);
var car = self.attachAsset('enemyCar', {
anchorX: 0.5,
anchorY: 0.5
});
// Target position (player car)
self.targetX = 2048 / 2;
self.targetY = 2732 / 2;
// Speed (pixels per tick)
self.speed = 7;
// Direction vector
self.dirX = 0;
self.dirY = 1;
// For collision
self.radius = 90;
// Update method
self.update = function () {
// Move toward target (player car)
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var len = Math.sqrt(dx * dx + dy * dy);
if (len > 0.01) {
self.dirX = dx / len;
self.dirY = dy / len;
// Enemy car speed is always self.speed, never boosted
self.x += self.dirX * self.speed;
self.y += self.dirY * self.speed;
// Rotate car to face movement
car.rotation = Math.atan2(self.dirY, self.dirX) + Math.PI / 2;
}
};
// Set target (player car position)
self.setTarget = function (x, y) {
self.targetX = x;
self.targetY = y;
};
return self;
});
// Player Car Class
var PlayerCar = Container.expand(function () {
var self = Container.call(this);
// Attach car asset, anchor center
var car = self.attachAsset('playerCar', {
anchorX: 0.5,
anchorY: 0.5
});
// For visual orientation, add a "gun" at the top (optional)
// Not required for MVP
// Movement speed (pixels per tick)
self.baseSpeed = 12;
self.boostSpeed = 24;
self.currentSpeed = self.baseSpeed;
// Direction vector (normalized)
self.dirX = 0;
self.dirY = -1;
// Set initial position to center
self.x = 2048 / 2;
self.y = 2732 / 2;
// For auto-shoot cooldown
self.shootCooldown = 0;
// Update method: called every tick
self.update = function () {
// Move in direction of pointer
// Only move if playerIsMoving is true (set by handleMove)
if (typeof playerIsMoving !== "undefined" && playerIsMoving) {
// Calculate next position
var nextX = self.x + self.dirX * self.currentSpeed;
var nextY = self.y + self.dirY * self.currentSpeed;
// Clamp next position to game area bounds
var minX = 90;
var maxX = 2048 - 90;
var minY = 160;
var maxY = 2732 - 160;
// Only update X if within bounds
if (nextX >= minX && nextX <= maxX) {
self.x = nextX;
} else if (nextX < minX) {
self.x = minX;
} else if (nextX > maxX) {
self.x = maxX;
}
// Only update Y if within bounds
if (nextY >= minY && nextY <= maxY) {
self.y = nextY;
} else if (nextY < minY) {
self.y = minY;
} else if (nextY > maxY) {
self.y = maxY;
}
}
// Clamp to game area (keep inside bounds)
if (self.x < 90) self.x = 90;
if (self.x > 2048 - 90) self.x = 2048 - 90;
if (self.y < 160) self.y = 160;
if (self.y > 2732 - 160) self.y = 2732 - 160;
// Cooldown for shooting
if (self.shootCooldown > 0) self.shootCooldown--;
};
// Set direction toward pointer
self.setDirection = function (targetX, targetY) {
var dx = targetX - self.x;
var dy = targetY - self.y;
var len = Math.sqrt(dx * dx + dy * dy);
if (len > 0.01) {
self.dirX = dx / len;
self.dirY = dy / len;
// Rotate car to face direction
car.rotation = Math.atan2(self.dirY, self.dirX) + Math.PI / 2;
}
};
// Boost on press
self.setBoost = function (boosting) {
self.currentSpeed = boosting ? self.boostSpeed : self.baseSpeed;
};
return self;
});
// PowerUpBox Class: A box that gives player a shoot speed boost when collected
var PowerUpBox = Container.expand(function (useBossDropAsset) {
var self = Container.call(this);
var box;
if (useBossDropAsset) {
// Use the bossDropBox asset if specified
box = self.attachAsset('bossDropBox', {
anchorX: 0.5,
anchorY: 0.5,
width: 120,
height: 120,
alpha: 0.95
});
} else {
// Use a distinct color for the box
box = self.attachAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
width: 120,
height: 120,
color: 0x4ddcfe,
// cyan
alpha: 0.85
});
}
self.radius = 60;
// Animate the box (pulse) using correct tween API
var pulseTween = tween(box, {
scaleX: 1.2,
scaleY: 1.2
}, 500, {
yoyo: true,
repeat: Infinity
});
// Update (optional: could float or spin)
self.update = function () {
// Could add floating or spinning effect here
};
// Destroy: stop tween
self.destroy = function () {
if (pulseTween && typeof pulseTween.stop === "function") {
pulseTween.stop();
}
Container.prototype.destroy.call(self);
};
return self;
});
// SuperPackage Class: Spawns at random near center, kills all visible enemies on pickup, increments score for each
var SuperPackage = Container.expand(function () {
var self = Container.call(this);
var box = self.attachAsset('superPackage', {
anchorX: 0.5,
anchorY: 0.5,
width: 160,
height: 160,
alpha: 0.92
});
self.radius = 80;
// Animate the box (pulse)
var pulseTween = tween(box, {
scaleX: 1.3,
scaleY: 1.3
}, 400, {
yoyo: true,
repeat: Infinity
});
self.update = function () {
// Could float or spin if desired
};
self.destroy = function () {
if (pulseTween && typeof pulseTween.stop === "function") {
pulseTween.stop();
}
Container.prototype.destroy.call(self);
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
// Asset for the box dropped by BossCar on death
// Add background image to the game
// New background asset
// SuperPackage asset
var background = LK.getAsset('background', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
game.addChild(background);
// --- Start Screen Overlay ---
var startOverlay = new Container();
var startBg = LK.getAsset('startBg', {
anchorX: 0.5,
anchorY: 0.5,
width: 2048,
height: 2732,
x: 2048 / 2,
y: 2732 / 2,
alpha: 0.92
});
startOverlay.addChild(startBg);
// Title background asset (ellipse, behind text)
var titleBg = LK.getAsset('titleBg', {
anchorX: 0.5,
anchorY: 0,
x: 2048 / 2,
y: 200
});
startOverlay.addChild(titleBg);
// Title Text (smaller font, bold, white, centered)
var titleTxt = new Text2("A Very Simple Tank Battle", {
size: 110,
fill: 0xffffff,
font: "'Arial Black', 'Impact', 'GillSans-Bold', 'Arial', 'sans-serif', 'Segoe UI Black', 'Segoe UI', 'Roboto', 'Noto Sans', 'Liberation Sans'"
});
titleTxt.anchor.set(0.5, 0); // top center
titleTxt.x = 2048 / 2;
titleTxt.y = 220;
startOverlay.addChild(titleTxt);
// Start Button
var startButton = LK.getAsset('startButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 1500
});
startOverlay.addChild(startButton);
var startBtnTxt = new Text2("Play", {
size: 120,
fill: 0xFFFFFF,
font: "'Arial Black', 'Impact', 'GillSans-Bold', 'Arial', 'sans-serif', 'Segoe UI Black', 'Segoe UI', 'Roboto', 'Noto Sans', 'Liberation Sans'" // bold, stylized font stack
});
startBtnTxt.anchor.set(0.5, 0.5);
startBtnTxt.x = 2048 / 2;
startBtnTxt.y = 1500;
startOverlay.addChild(startBtnTxt);
// Add overlay to game
game.addChild(startOverlay);
// --- Play Theme music on opening screen ---
LK.playMusic('Theme');
// Block gameplay until started
var gameStarted = false;
// Only allow controller and gameplay after start
function blockInputUntilStarted(fn) {
return function (x, y, obj) {
if (!gameStarted) return;
return fn(x, y, obj);
};
}
// --- Replace event handlers to block input until started ---
game.move = blockInputUntilStarted(handleMove);
game.down = blockInputUntilStarted(function (x, y, obj) {
var d = dist2(x, y, controllerCenterX, controllerCenterY);
if (d <= controllerRadius) {
handleMove(x, y, obj);
}
});
game.up = blockInputUntilStarted(function (x, y, obj) {
boosting = false;
player.setBoost(false);
playerIsMoving = false;
controllerActive = false;
controllerTouchId = null;
lastControllerActive = false;
lastControllerDirX = 0;
lastControllerDirY = 0;
controllerKnob.x = controllerCenterX;
controllerKnob.y = controllerCenterY;
});
// --- Block update until started ---
var realGameUpdate = function realGameUpdate() {
// (all original game.update code will be here, see next change_block)
};
game.update = function () {
// Hide gameplay UI and controller until gameStarted
if (!gameStarted) {
// Hide gameplay UI
scoreTxt.visible = false;
levelTxt.visible = false;
timerTxt.visible = false;
controllerBg.visible = false;
controllerKnob.visible = false;
player.visible = false;
// Hide all enemies, bullets, boss, powerup box if any
for (var i = 0; i < enemies.length; i++) enemies[i].visible = false;
for (var i = 0; i < bullets.length; i++) bullets[i].visible = false;
if (bossCar) bossCar.visible = false;
if (game._powerUpBox) game._powerUpBox.visible = false;
return;
}
// Show gameplay UI and controller after gameStarted
scoreTxt.visible = true;
levelTxt.visible = true;
timerTxt.visible = true;
controllerBg.visible = true;
controllerKnob.visible = true;
player.visible = true;
for (var i = 0; i < enemies.length; i++) enemies[i].visible = true;
for (var i = 0; i < bullets.length; i++) bullets[i].visible = true;
if (bossCar) bossCar.visible = true;
if (game._powerUpBox) game._powerUpBox.visible = true;
realGameUpdate();
};
// --- Start Button Event Handler ---
startButton.down = function (x, y, obj) {
if (gameStarted) return;
gameStarted = true;
// Stop Theme music when Play is pressed
LK.stopMusic();
// Play Play sound when Play button is pressed
LK.getSound('Play').play();
if (startOverlay && startOverlay.parent) {
startOverlay.parent.removeChild(startOverlay);
}
};
startBtnTxt.down = startButton.down; // Also allow clicking text
// Bullet: yellow ellipse
// Enemy car: red box
// Player car: blue box
// --- Global variables ---
var player = new PlayerCar();
player._fireRateBoosted = false; // Not boosted at start
player._fireRateBoss2Boosted = false; // Not boosted at start
player._fireRateBoss3Boosted = false; // Not boosted at start
player._fireRateBoss4Boosted = false; // Not boosted at start
game.addChild(player);
var enemies = [];
var bullets = [];
// Boss state
var bossSpawned = false;
var bossCar = null;
// Score
var score = 0;
var level = 1;
var scoreTxt = new Text2('Score: 0', {
size: 97.2,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(1, 0); // right align score
// Level text (centered between score and timer)
var levelTxt = new Text2('Level 1', {
size: 100,
fill: 0xFFD700 // gold color for level
});
levelTxt.anchor.set(0.5, 0); // center align
// Timer text (elapsed time in seconds, right of score)
var timerTxt = new Text2('0:00', {
size: 99,
fill: 0xFFFFFF
});
timerTxt.anchor.set(0, 0); // left align timer
// Position score, level, and timer
// Arrange score, level, and timer in a single horizontal row, evenly spaced
var guiRowY = 10;
var guiSpacing = 80; // horizontal space between counters
// Calculate total width of all counters and spacing
var totalWidth = scoreTxt.width + levelTxt.width + timerTxt.width + 2 * guiSpacing;
var startX = LK.gui.top.width / 2 - totalWidth / 2;
// Score counter (left)
scoreTxt.x = startX + scoreTxt.width;
scoreTxt.y = guiRowY;
// Level counter (center)
levelTxt.x = scoreTxt.x + guiSpacing + levelTxt.width / 2;
levelTxt.y = guiRowY;
// Timer counter (right)
timerTxt.x = levelTxt.x + levelTxt.width / 2 + guiSpacing;
timerTxt.y = guiRowY;
LK.gui.top.addChild(scoreTxt);
LK.gui.top.addChild(levelTxt);
LK.gui.top.addChild(timerTxt);
// For pointer tracking (controller-based)
var pointerX = 2048 / 2;
var pointerY = 2732 / 2;
var playerIsMoving = false; // Start as not moving
// For boost
var boosting = false;
// --- Controller UI ---
// Controller is a circular area at the bottom center of the screen
var controllerRadius = 170;
var controllerCenterX = 2048 / 2;
var controllerCenterY = 2732 - controllerRadius - 60;
// Controller visual (semi-transparent circle)
var controllerBg = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
width: controllerRadius * 2,
height: controllerRadius * 2,
color: 0x888888,
alpha: 0.25,
x: controllerCenterX,
y: controllerCenterY
});
game.addChild(controllerBg);
// Controller knob (shows current direction)
var knobRadius = 80;
var controllerKnob = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
width: knobRadius * 2,
height: knobRadius * 2,
color: 0x2980ff,
alpha: 0.7,
x: controllerCenterX,
y: controllerCenterY
});
game.addChild(controllerKnob);
// Track controller state
var controllerActive = false;
var controllerTouchId = null;
// Persistent controller direction for continuous movement
var lastControllerActive = false;
var lastControllerDirX = 0;
var lastControllerDirY = 0;
// Helper: get distance between two points
function dist2(x1, y1, x2, y2) {
var dx = x1 - x2;
var dy = y1 - y2;
return Math.sqrt(dx * dx + dy * dy);
}
// For enemy spawn timer
var enemySpawnTicks = 0;
// Dynamic spawn interval variables
var ENEMY_SPAWN_INTERVAL_START = 180; // 3 seconds at 60fps
var ENEMY_SPAWN_INTERVAL_END = 60; // 1 second at 60fps
var ENEMY_SPAWN_INTERVAL = ENEMY_SPAWN_INTERVAL_START;
// Timer for game progression (in ticks)
var gameTimerTicks = 0;
var GAME_TIMER_MAX = 60 * 60; // 60 seconds to reach max difficulty
// --- Event Handlers ---
// Move: handle controller input only if inside controller area
function handleMove(x, y, obj) {
// Only respond to touches inside controller area or if already dragging controller
var isTouch = obj && obj.event && typeof obj.event.identifier !== "undefined";
var touchId = isTouch ? obj.event.identifier : null;
// If not already active, only activate if touch is inside controller
if (!controllerActive) {
var d = dist2(x, y, controllerCenterX, controllerCenterY);
if (d <= controllerRadius) {
controllerActive = true;
controllerTouchId = touchId;
} else {
// Ignore touches outside controller
return;
}
} else {
// If already active, only respond to the same touch
if (isTouch && controllerTouchId !== null && touchId !== controllerTouchId) {
return;
}
}
// Calculate direction from controller center to touch
var dx = x - controllerCenterX;
var dy = y - controllerCenterY;
var len = Math.sqrt(dx * dx + dy * dy);
// Clamp knob to controller radius
var knobX = controllerCenterX;
var knobY = controllerCenterY;
if (len > controllerRadius) {
dx = dx * controllerRadius / len;
dy = dy * controllerRadius / len;
knobX += dx;
knobY += dy;
} else {
knobX = x;
knobY = y;
}
controllerKnob.x = knobX;
controllerKnob.y = knobY;
// If movement is significant, set direction and move
if (len > 30) {
// Move player in the direction of the knob relative to controller center
player.setDirection(player.x + dx, player.y + dy);
playerIsMoving = true;
// Store last direction for persistent movement
lastControllerDirX = dx / len;
lastControllerDirY = dy / len;
lastControllerActive = true;
} else {
playerIsMoving = false;
lastControllerActive = false;
}
if (typeof LK !== "undefined" && typeof LK.ticks !== "undefined") {
lastMoveTick = LK.ticks;
}
}
game.move = handleMove;
// Down: only activate boost if controller is pressed
game.down = function (x, y, obj) {
var d = dist2(x, y, controllerCenterX, controllerCenterY);
// Do not activate boost on controller press anymore
if (d <= controllerRadius) {
handleMove(x, y, obj);
}
};
// Up: stop boost and controller movement
game.up = function (x, y, obj) {
boosting = false;
player.setBoost(false);
playerIsMoving = false;
controllerActive = false;
controllerTouchId = null;
// Reset persistent controller direction
lastControllerActive = false;
lastControllerDirX = 0;
lastControllerDirY = 0;
// Reset knob to center
controllerKnob.x = controllerCenterX;
controllerKnob.y = controllerCenterY;
};
// --- Main Game Loop ---
// All main game logic is now in realGameUpdate for start screen support
var realGameUpdate = function realGameUpdate() {
// Block all updates if loser overlay is active
if (game._loserOverlayActive) {
return;
}
// --- SuperPackage spawn logic (Level 3+) ---
if (typeof game._superPackageTimerTicks === "undefined") {
game._superPackageTimerTicks = 0;
}
if (typeof game._superPackageActive === "undefined") {
game._superPackageActive = false;
}
if (typeof game._superPackageObj === "undefined") {
game._superPackageObj = null;
}
// --- bossDropBox3 spawn logic at score 100 ---
if (typeof game._bossDropBox3Spawned === "undefined") {
game._bossDropBox3Spawned = false;
}
if (typeof game._bossDropBox3Obj === "undefined") {
game._bossDropBox3Obj = null;
}
if (score >= 100 && !game._bossDropBox3Spawned) {
// Spawn bossDropBox3 at random near center (±250px)
var cx3 = 1024 + (Math.random() - 0.5) * 500;
var cy3 = 1366 + (Math.random() - 0.5) * 500;
// Clamp to safe area
if (cx3 < 200) cx3 = 200;
if (cx3 > 2048 - 200) cx3 = 2048 - 200;
if (cy3 < 200) cy3 = 200;
if (cy3 > 2732 - 200) cy3 = 2732 - 200;
var bossDropBox3 = LK.getAsset('bossDropBox3', {
anchorX: 0.5,
anchorY: 0.5,
width: 120,
height: 120,
alpha: 0.98
});
bossDropBox3.x = cx3;
bossDropBox3.y = cy3;
game.addChild(bossDropBox3);
game._bossDropBox3Obj = bossDropBox3;
game._bossDropBox3Spawned = true;
}
// Check pickup for bossDropBox3
if (game._bossDropBox3Obj) {
var dx3 = player.x - game._bossDropBox3Obj.x;
var dy3 = player.y - game._bossDropBox3Obj.y;
var dist3 = Math.sqrt(dx3 * dx3 + dy3 * dy3);
if (dist3 < 120) {
// Picked up!
if (typeof game._bossDropBox3Obj.destroy === "function") {
game._bossDropBox3Obj.destroy();
}
game._bossDropBox3Obj = null;
// Play PowerUp sound when bossDropBox3 is picked up
LK.getSound('PowerUp').play();
// Effect: Increase fire rate to 5/sec and keep diagonal triple shot
player._fireRateBoss2Boosted = true;
player._fireRateBoosted = false; // Remove previous boost if any
player._fireRateBoss3Boosted = true; // Mark that bossDropBox3 was picked up
// Visual feedback
LK.effects.flashScreen(0x00bfff, 800); // blue flash for bossDropBox3
}
}
if (level >= 3) {
game._superPackageTimerTicks++;
// Every 5 seconds (300 ticks), if not already active, spawn a SuperPackage
if (!game._superPackageActive && game._superPackageTimerTicks >= 300) {
// Random position near center (±200px)
var cx = 1024 + (Math.random() - 0.5) * 400;
var cy = 1366 + (Math.random() - 0.5) * 400;
// Clamp to safe area
if (cx < 200) cx = 200;
if (cx > 2048 - 200) cx = 2048 - 200;
if (cy < 200) cy = 200;
if (cy > 2732 - 200) cy = 2732 - 200;
var superPkg = new SuperPackage();
superPkg.x = cx;
superPkg.y = cy;
game.addChild(superPkg);
game._superPackageObj = superPkg;
game._superPackageActive = true;
game._superPackageTimerTicks = 0;
}
// Check pickup
if (game._superPackageActive && game._superPackageObj) {
var dx = player.x - game._superPackageObj.x;
var dy = player.y - game._superPackageObj.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 120) {
// Picked up! Kill all visible enemies, increment score for each
// Play Patlama sound when SuperPackage is picked up
LK.getSound('Patlama').play();
var killed = 0;
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] && typeof enemies[i].destroy === "function") {
enemies[i].destroy();
enemies.splice(i, 1);
killed++;
}
}
// Score for each killed
if (killed > 0) {
score += killed;
if (typeof scoreTxt !== "undefined") {
scoreTxt.setText('Score: ' + score);
}
}
// Visual feedback
LK.effects.flashScreen(0xffa500, 600); // orange explosion for SuperPackage
// Remove package
game._superPackageObj.destroy();
game._superPackageObj = null;
game._superPackageActive = false;
game._superPackageTimerTicks = 0;
}
}
// If package is still active but destroyed (e.g. by game reset), clear state
if (game._superPackageActive && !game._superPackageObj) {
game._superPackageActive = false;
game._superPackageTimerTicks = 0;
}
} else {
// Reset timer if below level 3
game._superPackageTimerTicks = 0;
if (game._superPackageObj) {
game._superPackageObj.destroy();
game._superPackageObj = null;
}
game._superPackageActive = false;
}
// Update player
// If controller was last dragged in a direction, keep moving in that direction
if (typeof lastControllerActive !== "undefined" && lastControllerActive && typeof lastControllerDirX !== "undefined" && typeof lastControllerDirY !== "undefined") {
// Set direction to last drag direction
player.dirX = lastControllerDirX;
player.dirY = lastControllerDirY;
playerIsMoving = true;
}
// Handle shoot speed boost timer
if (player._shootBoostActive) {
if (typeof player._shootBoostTicks === "undefined") player._shootBoostTicks = 0;
player._shootBoostTicks--;
if (player._shootBoostTicks <= 0) {
player._shootBoostActive = false;
// Do NOT revert fire rate after boost ends; keep it at 2.5/sec
// player._fireRateBoosted remains true
}
}
player.update();
// If no move event occurred this frame, stop player movement
if (typeof LK !== "undefined" && typeof LK.ticks !== "undefined") {
if (typeof lastMoveTick === "undefined") {
lastMoveTick = LK.ticks;
}
if (lastMoveTick !== LK.ticks) {
// Only stop if controller is not active
if (!lastControllerActive) {
playerIsMoving = false;
}
}
}
// --- Timer and Dynamic Enemy Spawning ---
gameTimerTicks++;
// Update timer text (show mm:ss)
var elapsedSec = Math.floor(gameTimerTicks / 60);
var min = Math.floor(elapsedSec / 60);
var sec = elapsedSec % 60;
timerTxt.setText(min + ':' + (sec < 10 ? '0' : '') + sec);
// Every second, increase spawn rate by reducing ENEMY_SPAWN_INTERVAL_START, but clamp to ENEMY_SPAWN_INTERVAL_END
if (gameTimerTicks % 60 === 0) {
ENEMY_SPAWN_INTERVAL_START = Math.max(ENEMY_SPAWN_INTERVAL_END, ENEMY_SPAWN_INTERVAL_START - 5);
}
// Calculate progression (0 to 1)
var progress = Math.min(gameTimerTicks / GAME_TIMER_MAX, 1);
// Linearly interpolate spawn interval from start to end
ENEMY_SPAWN_INTERVAL = Math.round(ENEMY_SPAWN_INTERVAL_START + (ENEMY_SPAWN_INTERVAL_END - ENEMY_SPAWN_INTERVAL_START) * progress);
enemySpawnTicks++;
// Determine how many enemies to spawn this cycle (1 + extra for every 10 kills)
var spawnCount = 1 + Math.floor(score / 10);
// Boss spawn logic
if (!bossSpawned && score >= 10 && score < 50 && typeof game._bossDefeated === "undefined") {
bossSpawned = true;
bossCar = new BossCar();
// Spawn boss at random edge
var edge = Math.floor(Math.random() * 4);
var bx, by;
if (edge === 0) {
bx = 200 + Math.random() * (2048 - 400);
by = -300;
} else if (edge === 1) {
bx = 2048 + 300;
by = 200 + Math.random() * (2732 - 400);
} else if (edge === 2) {
bx = 200 + Math.random() * (2048 - 400);
by = 2732 + 300;
} else {
bx = -300;
by = 200 + Math.random() * (2732 - 400);
}
bossCar.x = bx;
bossCar.y = by;
bossCar.setTarget(player.x, player.y);
game.addChild(bossCar);
}
// BossCar2 spawn logic at score 50
if (!bossSpawned && score >= 50 && typeof game._boss2Spawned === "undefined") {
bossSpawned = true;
game._boss2Spawned = true;
bossCar = new BossCar2();
// Spawn boss2 at random edge
var edge2 = Math.floor(Math.random() * 4);
var bx2, by2;
if (edge2 === 0) {
bx2 = 200 + Math.random() * (2048 - 400);
by2 = -300;
} else if (edge2 === 1) {
bx2 = 2048 + 300;
by2 = 200 + Math.random() * (2732 - 400);
} else if (edge2 === 2) {
bx2 = 200 + Math.random() * (2048 - 400);
by2 = 2732 + 300;
} else {
bx2 = -300;
by2 = 200 + Math.random() * (2732 - 400);
}
bossCar.x = bx2;
bossCar.y = by2;
bossCar.setTarget(player.x, player.y);
game.addChild(bossCar);
}
// BossCar3 spawn logic at score 200
if (!bossSpawned && score >= 200 && typeof game._boss3Spawned === "undefined") {
bossSpawned = true;
game._boss3Spawned = true;
bossCar = new BossCar3();
// Spawn boss3 at random edge
var edge3 = Math.floor(Math.random() * 4);
var bx3, by3;
if (edge3 === 0) {
bx3 = 200 + Math.random() * (2048 - 400);
by3 = -300;
} else if (edge3 === 1) {
bx3 = 2048 + 300;
by3 = 200 + Math.random() * (2732 - 400);
} else if (edge3 === 2) {
bx3 = 200 + Math.random() * (2048 - 400);
by3 = 2732 + 300;
} else {
bx3 = -300;
by3 = 200 + Math.random() * (2732 - 400);
}
bossCar.x = bx3;
bossCar.y = by3;
bossCar.setTarget(player.x, player.y);
game.addChild(bossCar);
}
// BossCar4 spawn logic at score 500
if (!bossSpawned && score >= 500 && typeof game._boss4Spawned === "undefined") {
bossSpawned = true;
game._boss4Spawned = true;
bossCar = new (Container.expand(function () {
var self = Container.call(this);
var boss = self.attachAsset('bossCar4', {
anchorX: 0.5,
anchorY: 0.5
});
self.targetX = 2048 / 2;
self.targetY = 2732 / 2;
self.speed = 8.8;
self.dirX = 0;
self.dirY = 1;
self.radius = 130;
self.maxHealth = 60;
self.health = self.maxHealth;
self.update = function () {
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var len = Math.sqrt(dx * dx + dy * dy);
if (len > 0.01) {
self.dirX = dx / len;
self.dirY = dy / len;
self.x += self.dirX * self.speed;
self.y += self.dirY * self.speed;
boss.rotation = Math.atan2(self.dirY, self.dirX) + Math.PI / 2;
}
};
self.setTarget = function (x, y) {
self.targetX = x;
self.targetY = y;
};
self.takeDamage = function (amount) {
self.health -= amount;
if (self.health < 0) self.health = 0;
};
return self;
}))();
// Spawn boss4 at random edge
var edge4 = Math.floor(Math.random() * 4);
var bx4, by4;
if (edge4 === 0) {
bx4 = 200 + Math.random() * (2048 - 400);
by4 = -300;
} else if (edge4 === 1) {
bx4 = 2048 + 300;
by4 = 200 + Math.random() * (2732 - 400);
} else if (edge4 === 2) {
bx4 = 200 + Math.random() * (2048 - 400);
by4 = 2732 + 300;
} else {
bx4 = -300;
by4 = 200 + Math.random() * (2732 - 400);
}
bossCar.x = bx4;
bossCar.y = by4;
bossCar.setTarget(player.x, player.y);
game.addChild(bossCar);
}
// Only spawn normal enemies if boss is not spawned, not in enemy spawn delay, and BossCar2/BossCar3/BossCar4 is not alive
var bossCar2Alive = false;
var bossCar3Alive = false;
var bossCar4Alive = false;
if (bossCar && bossCar instanceof BossCar2 && bossSpawned) {
bossCar2Alive = true;
}
if (bossCar && bossCar instanceof BossCar3 && bossSpawned) {
bossCar3Alive = true;
}
// BossCar4 is a custom inline class, so check by asset id
if (bossCar && bossCar.children && bossCar.children.length > 0 && bossCar.children[0].assetId === 'bossCar4' && bossSpawned) {
bossCar4Alive = true;
}
if (!bossSpawned && enemySpawnTicks >= ENEMY_SPAWN_INTERVAL && !game._enemySpawnDelayActive && !bossCar2Alive && !bossCar3Alive && !bossCar4Alive) {
enemySpawnTicks = 0;
for (var spawnIdx = 0; spawnIdx < spawnCount; spawnIdx++) {
// Spawn enemy at random edge
var edge = Math.floor(Math.random() * 4); // 0:top, 1:right, 2:bottom, 3:left
var ex, ey;
if (edge === 0) {
// top
ex = 200 + Math.random() * (2048 - 400);
ey = -160;
} else if (edge === 1) {
// right
ex = 2048 + 160;
ey = 200 + Math.random() * (2732 - 400);
} else if (edge === 2) {
// bottom
ex = 200 + Math.random() * (2048 - 400);
ey = 2732 + 160;
} else {
// left
ex = -160;
ey = 200 + Math.random() * (2732 - 400);
}
var enemy = new EnemyCar();
enemy.x = ex;
enemy.y = ey;
enemy.setTarget(player.x, player.y);
enemies.push(enemy);
game.addChild(enemy);
}
}
// --- Update Enemies ---
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
enemy.setTarget(player.x, player.y);
enemy.update();
// Check collision with player (circle collision)
var dx = enemy.x - player.x;
var dy = enemy.y - player.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 160) {
// Game over: show YOU ARE LOSER overlay, pause counters, black bg, white text, 5s then end
if (!game._loserOverlayShown) {
LK.getSound('Dead').play();
game._loserOverlayShown = true;
// Hide gameplay UI
scoreTxt.visible = false;
levelTxt.visible = false;
timerTxt.visible = false;
controllerBg.visible = false;
controllerKnob.visible = false;
player.visible = false;
for (var i = 0; i < enemies.length; i++) enemies[i].visible = false;
for (var i = 0; i < bullets.length; i++) bullets[i].visible = false;
if (bossCar) bossCar.visible = false;
if (game._powerUpBox) game._powerUpBox.visible = false;
// Black overlay
var loserOverlay = new Container();
loserOverlay._isLoserOverlay = true;
var blackBg = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
width: 2048 * 1.5,
height: 2732 * 1.5,
color: 0x000000,
x: 2048 / 2,
y: 2732 / 2,
alpha: 1
});
loserOverlay.addChild(blackBg);
// YOU ARE LOSER text
var loserTxt = new Text2("YOU ARE LOSER", {
size: 220,
fill: 0xffffff,
font: "'Arial Black', 'Impact', 'GillSans-Bold', 'Arial', 'sans-serif', 'Segoe UI Black', 'Segoe UI', 'Roboto', 'Noto Sans', 'Liberation Sans'"
});
loserTxt.anchor.set(0.5, 0.5);
loserTxt.x = 2048 / 2;
loserTxt.y = 2732 / 2;
loserOverlay.addChild(loserTxt);
game.addChild(loserOverlay);
// Pause all counters/timers by blocking realGameUpdate
game._loserOverlayActive = true;
// After 5 seconds, remove overlay and end game
LK.setTimeout(function () {
if (loserOverlay && loserOverlay.parent) {
loserOverlay.parent.removeChild(loserOverlay);
}
LK.showGameOver();
}, 5000);
}
return;
}
}
// --- Update BossCar ---
if (bossSpawned && bossCar) {
bossCar.setTarget(player.x, player.y);
bossCar.update();
// Check collision with player (circle collision, bigger radius)
var bdx = bossCar.x - player.x;
var bdy = bossCar.y - player.y;
var bdist = Math.sqrt(bdx * bdx + bdy * bdy);
if (bdist < 220) {
// Game over: show YOU ARE LOSER overlay, pause counters, black bg, white text, 5s then end
if (!game._loserOverlayShown) {
LK.getSound('Dead').play();
game._loserOverlayShown = true;
// Hide gameplay UI
scoreTxt.visible = false;
levelTxt.visible = false;
timerTxt.visible = false;
controllerBg.visible = false;
controllerKnob.visible = false;
player.visible = false;
for (var i = 0; i < enemies.length; i++) enemies[i].visible = false;
for (var i = 0; i < bullets.length; i++) bullets[i].visible = false;
if (bossCar) bossCar.visible = false;
if (game._powerUpBox) game._powerUpBox.visible = false;
// Black overlay
var loserOverlay = new Container();
loserOverlay._isLoserOverlay = true;
var blackBg = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
width: 2048 * 1.5,
height: 2732 * 1.5,
color: 0x000000,
x: 2048 / 2,
y: 2732 / 2,
alpha: 1
});
loserOverlay.addChild(blackBg);
// YOU ARE LOSER text
var loserTxt = new Text2("YOU ARE LOSER", {
size: 220,
fill: 0xffffff,
font: "'Arial Black', 'Impact', 'GillSans-Bold', 'Arial', 'sans-serif', 'Segoe UI Black', 'Segoe UI', 'Roboto', 'Noto Sans', 'Liberation Sans'"
});
loserTxt.anchor.set(0.5, 0.5);
loserTxt.x = 2048 / 2;
loserTxt.y = 2732 / 2;
loserOverlay.addChild(loserTxt);
game.addChild(loserOverlay);
// Pause all counters/timers by blocking realGameUpdate
game._loserOverlayActive = true;
// After 5 seconds, remove overlay and end game
LK.setTimeout(function () {
if (loserOverlay && loserOverlay.parent) {
loserOverlay.parent.removeChild(loserOverlay);
}
LK.showGameOver();
}, 5000);
}
return;
}
// BossCar damages enemies it collides with (just like player does)
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
var edx = bossCar.x - enemy.x;
var edy = bossCar.y - enemy.y;
var edist = Math.sqrt(edx * edx + edy * edy);
if (edist < 160) {
// Remove enemy
enemy.destroy();
enemies.splice(i, 1);
// Optionally, you could increment score or add effect, but for now just remove
}
}
// If boss defeated, show win
if (bossCar.health <= 0) {
// Play Patlama sound on boss defeat
LK.getSound('Patlama').play();
// If BossCar3 is defeated, drop bossDropBox4 and allow enemies to respawn, do not end game
if (bossCar instanceof BossCar3) {
// Drop bossDropBox4 at boss position
var bossDropBox4 = LK.getAsset('bossDropBox4', {
anchorX: 0.5,
anchorY: 0.5,
width: 120,
height: 120,
alpha: 0.98
});
bossDropBox4.x = bossCar.x;
bossDropBox4.y = bossCar.y;
game.addChild(bossDropBox4);
game._bossDropBox4Obj = bossDropBox4;
// Mark boss3 as defeated, but do not end game
game._boss3Defeated = true;
// Allow enemies to respawn after short delay
bossSpawned = false;
if (typeof game._enemySpawnDelayTimeout !== "undefined") {
LK.clearTimeout(game._enemySpawnDelayTimeout);
}
game._enemySpawnDelayActive = true;
game._enemySpawnDelayTimeout = LK.setTimeout(function () {
game._enemySpawnDelayActive = false;
}, 2000);
LK.effects.flashScreen(0xff00ff, 1000); // Magenta flash for bossDropBox4
} else {
// Mark boss as defeated so it never spawns again
game._bossDefeated = true;
// Spawn power-up box at boss position, using bossDropBox asset
var powerUpBox = new PowerUpBox(true);
powerUpBox.x = bossCar.x;
powerUpBox.y = bossCar.y;
game.addChild(powerUpBox);
// Store reference for collision check
game._powerUpBox = powerUpBox;
// Level will be incremented only when package is collected
LK.effects.flashScreen(0x00ff00, 1000);
// Do NOT show win yet; wait for player to collect power-up
// LK.showYouWin();
// return;
}
bossCar.destroy();
bossCar = null;
}
}
// --- PowerUpBox pickup check ---
if (game._powerUpBox) {
var dx = player.x - game._powerUpBox.x;
var dy = player.y - game._powerUpBox.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 120) {
// Picked up!
game._powerUpBox.destroy();
game._powerUpBox = null;
// Play Sound2 when player collects the BossCar's dropped box
LK.getSound('Sound2').play();
// If this is the drop from BossCar2, enable diagonal triple shot at 3/sec
if (bossCar === null && typeof game._boss2Spawned !== "undefined" && typeof game._bossDefeated !== "undefined" && game._boss2Spawned && game._bossDefeated) {
player._fireRateBoss2Boosted = true;
player._fireRateBoosted = false; // Remove previous boost if any
} else {
// Apply shoot speed boost (reduce cooldown)
player._shootBoostActive = true;
player._shootBoostTicks = 60 * 8; // 8 seconds
player.shootCooldown = 0; // allow instant shot
player._fireRateBoosted = true; // Mark that fire rate should be boosted after pickup
}
// Visual feedback (flash)
LK.effects.flashObject(player, 0x4ddcfe, 800);
// Increment level when package is collected
level++;
if (typeof levelTxt !== "undefined") {
levelTxt.setText("Level " + level);
}
// Instead of showing win, start a 2 second delay before allowing EnemyCar spawn again
bossSpawned = false; // Boss will not spawn again, but allow enemies after delay
if (typeof game._enemySpawnDelayTimeout !== "undefined") {
LK.clearTimeout(game._enemySpawnDelayTimeout);
}
game._enemySpawnDelayActive = true;
game._enemySpawnDelayTimeout = LK.setTimeout(function () {
game._enemySpawnDelayActive = false;
}, 2000);
// Do NOT show win or end game here
// LK.effects.flashScreen(0x00ff00, 1000);
// LK.showYouWin();
// return;
}
}
// --- bossDropBox4 pickup check ---
if (game._bossDropBox4Obj) {
var dx4 = player.x - game._bossDropBox4Obj.x;
var dy4 = player.y - game._bossDropBox4Obj.y;
var dist4 = Math.sqrt(dx4 * dx4 + dy4 * dy4);
if (dist4 < 120) {
// Picked up!
if (typeof game._bossDropBox4Obj.destroy === "function") {
game._bossDropBox4Obj.destroy();
}
game._bossDropBox4Obj = null;
// Visual feedback for bossDropBox4
LK.effects.flashObject(player, 0xff00ff, 800);
// Set fire rate to 20/sec (3 ticks) after picking up bossDropBox4
player._fireRateBoss4Boosted = true;
}
}
// --- Auto-shoot at nearest enemy or bossCar ---
// If there are enemies, shoot at nearest enemy. If not, and bossCar is present, shoot at bossCar.
// Always limit fire rate to 1 bullet per second (60 ticks), unless boosted
if (player.shootCooldown === 0) {
var target = null;
if (enemies.length > 0) {
// Find nearest enemy
var minDist = 999999;
for (var i = 0; i < enemies.length; i++) {
var dx = enemies[i].x - player.x;
var dy = enemies[i].y - player.y;
var d = dx * dx + dy * dy;
if (d < minDist) {
minDist = d;
target = enemies[i];
}
}
} else if (bossSpawned && bossCar) {
// Only target bossCar if within enemy range (same as enemy targeting)
var dx = bossCar.x - player.x;
var dy = bossCar.y - player.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 900) {
// 900 is 30*30, but enemy targeting uses squared distance, so use same
target = bossCar;
}
}
if (target) {
// Check if player has picked up bossDropBox2 (from BossCar2)
if (player._fireRateBoss2Boosted) {
// If bossDropBox3 was picked up, fire 3 diagonal bullets at 5/sec (every 12 ticks)
// If bossDropBox4 was picked up, fire 3 diagonal bullets at 20/sec (every 3 ticks)
var carDir = Math.atan2(player.dirY, player.dirX);
var offsetX = Math.cos(carDir) * 180;
var offsetY = Math.sin(carDir) * 180;
// Center bullet
var bullet0 = new Bullet();
bullet0.x = player.x + offsetX;
bullet0.y = player.y + offsetY;
var bdx0 = target.x - bullet0.x;
var bdy0 = target.y - bullet0.y;
bullet0.setDirection(bdx0, bdy0);
bullets.push(bullet0);
game.addChild(bullet0);
// Left diagonal bullet
var angleLeft = carDir - Math.PI / 6; // 30 degrees left
var bulletL = new Bullet();
bulletL.x = player.x + Math.cos(angleLeft) * 180;
bulletL.y = player.y + Math.sin(angleLeft) * 180;
bulletL.setDirection(Math.cos(angleLeft), Math.sin(angleLeft));
bullets.push(bulletL);
game.addChild(bulletL);
// Right diagonal bullet
var angleRight = carDir + Math.PI / 6; // 30 degrees right
var bulletR = new Bullet();
bulletR.x = player.x + Math.cos(angleRight) * 180;
bulletR.y = player.y + Math.sin(angleRight) * 180;
bulletR.setDirection(Math.cos(angleRight), Math.sin(angleRight));
bullets.push(bulletR);
game.addChild(bulletR);
// Play Sound1 when player fires
LK.getSound('Sound1').play();
// Set cooldown for 20/sec (3 ticks) if bossDropBox4 picked up, else 5/sec (12 ticks) if bossDropBox3 picked up, else 3/sec (20 ticks)
if (player._fireRateBoss4Boosted) {
player.shootCooldown = 3;
} else if (player._fireRateBoss3Boosted) {
player.shootCooldown = 12;
} else {
player.shootCooldown = 20;
}
} else if (player._fireRateBoosted) {
// Single shot at 2.5/sec (24 ticks)
var bullet = new Bullet();
var carDir = Math.atan2(player.dirY, player.dirX);
var offsetX = Math.cos(carDir) * 180;
var offsetY = Math.sin(carDir) * 180;
bullet.x = player.x + offsetX;
bullet.y = player.y + offsetY;
var bdx = target.x - bullet.x;
var bdy = target.y - bullet.y;
bullet.setDirection(bdx, bdy);
bullets.push(bullet);
game.addChild(bullet);
LK.getSound('Sound1').play();
player.shootCooldown = 24;
} else {
// Single shot at 1/sec (60 ticks)
var bullet = new Bullet();
var carDir = Math.atan2(player.dirY, player.dirX);
var offsetX = Math.cos(carDir) * 180;
var offsetY = Math.sin(carDir) * 180;
bullet.x = player.x + offsetX;
bullet.y = player.y + offsetY;
var bdx = target.x - bullet.x;
var bdy = target.y - bullet.y;
bullet.setDirection(bdx, bdy);
bullets.push(bullet);
game.addChild(bullet);
LK.getSound('Sound1').play();
player.shootCooldown = 60;
}
}
}
// --- Update Bullets ---
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
bullet.update();
// Remove if out of bounds
if (bullet.x < -100 || bullet.x > 2048 + 100 || bullet.y < -100 || bullet.y > 2732 + 100) {
bullet.destroy();
bullets.splice(i, 1);
continue;
}
// Check collision with enemies
var hit = false;
for (var j = enemies.length - 1; j >= 0; j--) {
var enemy = enemies[j];
var dx = bullet.x - enemy.x;
var dy = bullet.y - enemy.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 100) {
// Hit!
hit = true;
// Remove enemy
enemy.destroy();
enemies.splice(j, 1);
// Remove bullet
bullet.destroy();
bullets.splice(i, 1);
// Score
score++;
scoreTxt.setText('Score: ' + score);
break;
}
}
// Check collision with bossCar if spawned, but only if in range (same as enemy range)
if (!hit && bossSpawned && bossCar && function () {
var dx = bullet.x - bossCar.x;
var dy = bullet.y - bossCar.y;
var dist = Math.sqrt(dx * dx + dy * dy);
return dist < 100; // Use same range as enemy hit
}()) {
// Hit boss!
hit = true;
bossCar.takeDamage(1);
bullet.destroy();
bullets.splice(i, 1);
// Optionally, flash boss or show health bar (not required for MVP)
}
if (hit) continue;
}
// --- Camera: keep player centered ---
// (In LK, the game area is fixed, so we simulate by keeping player in center and moving everything else if needed)
// For MVP, player always stays in center, so no need to move camera.
};
// --- Initial pointer direction ---
// No initial movement; player will move when controller is used
player.setDirection(player.x, player.y - 1);
Kalitesini arttır
kalitesini arttır
kalitesini arttır yuvarlak olsun
remove the holes and get a flat land
mavi ve kırmızı renklerinde siyah çizgileri olan bir airdrop kutusu yap, kuş bakışı olsun üstten gözüksün. In-Game asset. 2d. High contrast. No shadows. bird eye
Bir tank savaşı temalı arkaplan görseli hazırla görselde 2 tank olsun ve bu 2 tankın önündeki 1 tankı yakalamaya çalışsın.. In-Game asset. 2d. High contrast. No shadows
daha kaliteli yap
War dropbox a bird view blue and red. In-Game asset. 2d. High contrast. No shadows. In-Game asset. 2d. High contrast. No shadows
real explosion. In-Game asset. 2d. High contrast. No shadows
Arka Planını temizle
clear background
Erase background