/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ // Coin var Coin = Container.expand(function () { var self = Container.call(this); var coin = self.attachAsset('coin', { anchorX: 0.5, anchorY: 0.5 }); self.width = coin.width; self.height = coin.height; self.speed = 20; self.lane = 1; self.update = function () { self.y += self.speed; }; return self; }); // Enemy Car var EnemyCar = Container.expand(function () { var self = Container.call(this); var car = self.attachAsset('enemyCar', { anchorX: 0.5, anchorY: 0.5 }); self.width = car.width; self.height = car.height; self.speed = 20 + Math.random() * 5; self.lane = 1; self.update = function () { self.y += self.speed; }; return self; }); // Enemy Car 2 var EnemyCar2 = Container.expand(function () { var self = Container.call(this); var car = self.attachAsset('enemyCar2', { anchorX: 0.5, anchorY: 0.5 }); self.width = car.width; self.height = car.height; self.speed = 22 + Math.random() * 6; self.lane = 1; self.update = function () { self.y += self.speed; }; return self; }); // Enemy Car 3 var EnemyCar3 = Container.expand(function () { var self = Container.call(this); var car = self.attachAsset('enemyCar3', { anchorX: 0.5, anchorY: 0.5 }); self.width = car.width; self.height = car.height; self.speed = 18 + Math.random() * 8; self.lane = 1; self.update = function () { self.y += self.speed; }; return self; }); // Player Car var PlayerCar = Container.expand(function () { var self = Container.call(this); var car = self.attachAsset('playerCar', { anchorX: 0.5, anchorY: 0.5 }); self.width = car.width; self.height = car.height; self.lane = 1; // 0: left, 1: center, 2: right self.invincible = false; self.update = function () { // No movement here; handled by input }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x222222 }); /**** * Game Code ****/ // Button assets for Start and Choose Car // Road and lanes setup var roadWidth = 900; var laneCount = 3; var laneWidth = roadWidth / laneCount; var roadX = (2048 - roadWidth) / 2; var roadY = 0; // --- Car Selection and Start Menu --- var carAssetIds = ['playerCar', // default 'car2', 'car3', 'car4', 'car5', 'car6', 'car7', 'car8', 'car9', 'car10', 'car11', 'car12', 'car13', 'car14', 'car15', 'car16']; // Register 8 more car assets (IDs: car9 to car16) // Store selected car index (default 0) var selectedCarIndex = 0; // Overlay containers for menu and car select var startMenuOverlay = new Container(); var carSelectOverlay = new Container(); // --- Start Menu UI --- var titleTxt = new Text2('Turbo Racer 3', { size: 180, fill: "#fff" }); titleTxt.anchor.set(0.5, 0.5); titleTxt.x = 2048 / 2; titleTxt.y = 600; // Start Button var startBtn = LK.getAsset('startBtnImg', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 1000 }); var startBtnLabel = new Text2('Start', { size: 90, fill: "#222" }); startBtnLabel.anchor.set(0.5, 0.5); startBtnLabel.x = startBtn.x; startBtnLabel.y = startBtn.y; // Choose Car Button var chooseCarBtn = LK.getAsset('chooseCarBtnImg', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 1200 }); var chooseCarBtnLabel = new Text2('Choose a Car', { size: 70, fill: "#222" }); chooseCarBtnLabel.anchor.set(0.5, 0.5); chooseCarBtnLabel.x = chooseCarBtn.x; chooseCarBtnLabel.y = chooseCarBtn.y; // Adventure Mode Button var adventureBtn = LK.getAsset('adventureBtnImg', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 1400 }); var adventureBtnLabel = new Text2('Adventure Mode', { size: 70, fill: "#222" }); adventureBtnLabel.anchor = { x: 0.5, y: 0.5 }; adventureBtnLabel.x = adventureBtn.x; adventureBtnLabel.y = adventureBtn.y; // Add to overlay startMenuOverlay.addChild(titleTxt); startMenuOverlay.addChild(startBtn); startMenuOverlay.addChild(startBtnLabel); startMenuOverlay.addChild(chooseCarBtn); startMenuOverlay.addChild(chooseCarBtnLabel); startMenuOverlay.addChild(adventureBtn); startMenuOverlay.addChild(adventureBtnLabel); game.addChild(startMenuOverlay); // --- Adventure Mode Overlay --- var adventureOverlay = new Container(); var adventureTitle = new Text2('Adventure Mode', { size: 120, fill: "#fff" }); adventureTitle.anchor.set(0.5, 0.5); adventureTitle.x = 2048 / 2; adventureTitle.y = 400; adventureOverlay.addChild(adventureTitle); // Section grid (6 columns x 5 rows = 30 sections) var sectionCols = 6; var sectionRows = 5; var sectionCount = 30; var sectionSpacingX = 250; var sectionSpacingY = 220; var sectionStartX = 2048 / 2 - (sectionCols - 1) * sectionSpacingX / 2; var sectionStartY = 700; var sectionIcons = []; // Persistent progress using storage plugin var unlockedSections = storage.unlockedSections || 1; // Always at least 1 unlocked for (var i = 0; i < sectionCount; i++) { var col = i % sectionCols; var row = Math.floor(i / sectionCols); var sectionIcon = LK.getAsset('sectionIcon', { anchorX: 0.5, anchorY: 0.5, x: sectionStartX + col * sectionSpacingX, y: sectionStartY + row * sectionSpacingY }); var sectionLabel = new Text2('' + (i + 1), { size: 60, fill: "#fff" }); sectionLabel.anchor.set(0.5, 0.5); sectionLabel.x = sectionIcon.x; sectionLabel.y = sectionIcon.y; // Lock icon overlay if locked var isUnlocked = i < unlockedSections; var lockIcon = null; if (!isUnlocked) { lockIcon = LK.getAsset('star1', { anchorX: 0.5, anchorY: 0.5, x: sectionIcon.x, y: sectionIcon.y, scaleX: 1.1, scaleY: 1.1 }); lockIcon.alpha = 0.7; adventureOverlay.addChild(lockIcon); } // Section icon click handler (function (idx, icon, label, locked, lockIconRef) { icon.down = function () { if (locked) { // Optionally: flash or shake to indicate locked LK.effects.flashObject(icon, 0xff0000, 400); return; } // Start adventure section idx+1 if (adventureOverlay.parent) adventureOverlay.destroy(); startAdventureSection(idx + 1); }; label.down = icon.down; if (lockIconRef) lockIconRef.down = icon.down; })(i, sectionIcon, sectionLabel, !isUnlocked, lockIcon); adventureOverlay.addChild(sectionIcon); adventureOverlay.addChild(sectionLabel); sectionIcons.push(sectionIcon); } // Back button for adventure overlay var adventureBackBtn = new Text2('Back', { size: 80, fill: "#fff" }); adventureBackBtn.anchor.set(0.5, 0.5); adventureBackBtn.x = 2048 / 2; adventureBackBtn.y = sectionStartY + sectionRows * sectionSpacingY + 100; adventureOverlay.addChild(adventureBackBtn); // Adventure button handler adventureBtn.down = function () { if (startMenuOverlay.parent) startMenuOverlay.destroy(); game.addChild(adventureOverlay); }; // Back button handler adventureBackBtn.down = function () { if (adventureOverlay.parent) adventureOverlay.destroy(); game.addChild(startMenuOverlay); }; // --- Car Selection UI --- var carSelectTitle = new Text2('Select Your Car', { size: 120, fill: "#fff" }); carSelectTitle.anchor.set(0.5, 0.5); carSelectTitle.x = 2048 / 2; carSelectTitle.y = 400; carSelectOverlay.addChild(carSelectTitle); var carIcons = []; var carIconSpacing = 220; var carIconsPerRow = Math.ceil(carAssetIds.length / 2); var carIconStartX = 2048 / 2 - (carIconsPerRow - 1) * carIconSpacing / 2; var carIconY1 = 800; var carIconY2 = 1250; // Increased spacing between rows for (var i = 0; i < carAssetIds.length; i++) { var row = i < carIconsPerRow ? 0 : 1; var col = row === 0 ? i : i - carIconsPerRow; var carIcon = LK.getAsset(carAssetIds[i], { anchorX: 0.5, anchorY: 0.5, x: carIconStartX + col * carIconSpacing, y: row === 0 ? carIconY1 : carIconY2, scaleX: 0.7, scaleY: 0.7 }); // Highlight border for selected carIcon._border = null; (function (idx, icon) { icon.down = function () { // Remove highlight from all for (var j = 0; j < carIcons.length; j++) { if (carIcons[j]._border) { carIcons[j]._border.destroy(); carIcons[j]._border = null; } } // Add highlight (no border, just set selectedCarIndex) selectedCarIndex = idx; // Animate icon to grow a little when selected tween(icon, { scaleX: 0.9, scaleY: 0.9 }, { duration: 180, easing: tween.cubicOut }); // Shrink all other icons back to normal for (var j = 0; j < carIcons.length; j++) { if (carIcons[j] !== icon) { tween(carIcons[j], { scaleX: 0.7, scaleY: 0.7 }, { duration: 180, easing: tween.cubicOut }); } } }; })(i, carIcon); carIcons.push(carIcon); carSelectOverlay.addChild(carIcon); } // Preselect first car carIcons[0].down(); // Confirm button var confirmBtn = new Text2('Confirm', { size: 100, fill: "#fff" }); confirmBtn.anchor.set(0.5, 0.5); confirmBtn.x = 2048 / 2; // Place confirmBtn below the second row of car icons var carIconY = carIconY2; // Use the y of the second row confirmBtn.y = carIconY + 300; carSelectOverlay.addChild(confirmBtn); // --- Menu Button Handlers --- startBtn.down = function () { // Remove overlays, start game if (startMenuOverlay.parent) startMenuOverlay.destroy(); if (carSelectOverlay.parent) carSelectOverlay.destroy(); // Remove all start menu UI text and buttons if (titleTxt.parent) titleTxt.destroy(); if (startBtn.parent) startBtn.destroy(); if (startBtnLabel.parent) startBtnLabel.destroy(); if (chooseCarBtn.parent) chooseCarBtn.destroy(); if (chooseCarBtnLabel.parent) chooseCarBtnLabel.destroy(); if (adventureBtn.parent) adventureBtn.destroy(); if (adventureBtnLabel.parent) adventureBtnLabel.destroy(); startGame(); }; chooseCarBtn.down = function () { if (startMenuOverlay.parent) startMenuOverlay.destroy(); game.addChild(carSelectOverlay); }; confirmBtn.down = function () { if (carSelectOverlay.parent) carSelectOverlay.destroy(); game.addChild(startMenuOverlay); }; // --- Game Start Logic --- var mainMenuBtn; // Main menu button for normal mode function startGame() { startAdventureSection(null); // Add main menu icon in upper left for normal mode if (typeof mainMenuBtn !== "undefined" && mainMenuBtn.parent) mainMenuBtn.destroy(); mainMenuBtn = LK.getAsset('mainMenuBtnImg', { anchorX: 0.5, anchorY: 0.5, x: 100, y: 180, scaleX: 0.7, scaleY: 0.7 }); mainMenuBtn.alpha = 0.95; LK.gui.topLeft.addChild(mainMenuBtn); // On press, return to main menu mainMenuBtn.down = function () { if (typeof mainMenuBtn !== "undefined" && mainMenuBtn.parent) mainMenuBtn.destroy(); if (typeof scoreTxt !== "undefined" && scoreTxt.parent) scoreTxt.destroy(); if (typeof sectionLabelTxt !== "undefined" && sectionLabelTxt.parent) sectionLabelTxt.destroy(); // Remove all game objects if (typeof player !== "undefined" && player && player.parent) player.destroy(); for (var i = 0; i < enemies.length; i++) if (enemies[i].parent) enemies[i].destroy(); for (var i = 0; i < coins.length; i++) if (coins[i].parent) coins[i].destroy(); enemies = []; coins = []; // Show main menu overlay game.addChild(startMenuOverlay); // Pause game logic while main menu is open var prevUpdate = game.update; game.update = function () {}; // When the start menu overlay is closed (startBtn or chooseCarBtn), restore game.update var _resumeGameLogic = function resumeGameLogic() { if (game.update === _resumeGameLogic) return; // Prevent double restore game.update = prevUpdate; }; var _oldStartBtnDown = startBtn.down; startBtn.down = function () { _resumeGameLogic(); if (_oldStartBtnDown) _oldStartBtnDown(); }; var _oldChooseCarBtnDown = chooseCarBtn.down; chooseCarBtn.down = function () { _resumeGameLogic(); if (_oldChooseCarBtnDown) _oldChooseCarBtnDown(); }; }; } // --- Adventure Section Start Logic --- function startAdventureSection(sectionNumber) { // Remove overlays if present if (startMenuOverlay.parent) startMenuOverlay.destroy(); if (carSelectOverlay.parent) carSelectOverlay.destroy(); if (adventureOverlay.parent) adventureOverlay.destroy(); // Remove any previous game objects if (typeof player !== "undefined" && player && player.parent) player.destroy(); for (var i = 0; i < enemies.length; i++) if (enemies[i].parent) enemies[i].destroy(); for (var i = 0; i < coins.length; i++) if (coins[i].parent) coins[i].destroy(); enemies = []; coins = []; // Road background var road = LK.getAsset('road', { anchorX: 0, anchorY: 0, x: roadX, y: roadY }); game.addChild(road); // Player car player = new PlayerCar(); player.x = roadX + laneWidth * 1.5; player.y = 2732 - 400; player.setCarAsset(carAssetIds[selectedCarIndex]); game.addChild(player); // Section number for adventure mode, null means normal mode game.currentSection = sectionNumber; // Score and fuel UI score = 0; distance = 0; coinsCollected = 0; scoreTxt = new Text2('Score: 0', { size: 90, fill: "#fff" }); // Place score above (top center) in adventure mode, else top right if (sectionNumber) { scoreTxt.anchor.set(0.5, 0); // top center scoreTxt.x = 2048 / 2; scoreTxt.y = 20; } else { scoreTxt.anchor.set(1, 0); // top right scoreTxt.x = 2048 - 100; scoreTxt.y = 20; } LK.gui.top.addChild(scoreTxt); // Section label for adventure mode if (sectionNumber) { if (typeof sectionLabelTxt !== "undefined" && sectionLabelTxt.parent) sectionLabelTxt.destroy(); sectionLabelTxt = new Text2('Section ' + sectionNumber, { size: 80, fill: 0xFFD600 }); sectionLabelTxt.anchor.set(0, 0); sectionLabelTxt.x = 120; sectionLabelTxt.y = 20; LK.gui.top.addChild(sectionLabelTxt); } else { if (typeof sectionLabelTxt !== "undefined" && sectionLabelTxt.parent) sectionLabelTxt.destroy(); } // Timer to update score every second if (typeof scoreUpdateTimer !== "undefined") LK.clearInterval(scoreUpdateTimer); scoreUpdateTimer = LK.setInterval(function () { scoreTxt.setText('Score: ' + score); }, 1000); // Lane positions lanePositions = [roadX + laneWidth * 0.5, roadX + laneWidth * 1.5, roadX + laneWidth * 2.5]; // Place finish line for adventure mode if (sectionNumber) { // Make each section a bit longer and more difficult // Section 1: base distance, base speeds; Section 2: longer, faster, etc. var baseFinishDistance = 2732 * 1.5; var finishDistance = baseFinishDistance + (sectionNumber - 1) * 600; // Each section is 600px longer finishLineY = -finishDistance; if (typeof finishLine !== "undefined" && finishLine.parent) finishLine.destroy(); finishLine = LK.getAsset('finishLine', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: finishLineY }); game.addChild(finishLine); finishLine.crossed = false; // Set difficulty scaling for this section game.adventureDifficulty = 1 + (sectionNumber - 1) * 0.12; // 12% harder per section // Add Exit button for adventure mode if (typeof adventureExitBtn !== "undefined" && adventureExitBtn.parent) adventureExitBtn.destroy(); adventureExitBtn = new Text2('Exit', { size: 80, fill: "#fff" }); adventureExitBtn.anchor.set(1, 0); adventureExitBtn.x = 2048 - 100; adventureExitBtn.y = 120; LK.gui.top.addChild(adventureExitBtn); adventureExitBtn.down = function () { // Remove overlays and return to adventure section select if (typeof sectionLabelTxt !== "undefined" && sectionLabelTxt.parent) sectionLabelTxt.destroy(); if (typeof scoreTxt !== "undefined" && scoreTxt.parent) scoreTxt.destroy(); if (typeof adventureExitBtn !== "undefined" && adventureExitBtn.parent) adventureExitBtn.destroy(); if (typeof finishLine !== "undefined" && finishLine.parent) finishLine.destroy(); // Remove all game objects if (typeof player !== "undefined" && player && player.parent) player.destroy(); for (var i = 0; i < enemies.length; i++) if (enemies[i].parent) enemies[i].destroy(); for (var i = 0; i < coins.length; i++) if (coins[i].parent) coins[i].destroy(); enemies = []; coins = []; // Show adventure section select overlay game.addChild(adventureOverlay); // Pause game logic game.update = function () {}; }; // Add main menu button for adventure mode in upper left if (typeof adventureMenuBtn !== "undefined" && adventureMenuBtn.parent) adventureMenuBtn.destroy(); adventureMenuBtn = LK.getAsset('mainMenuBtnImg', { anchorX: 0.5, anchorY: 0.5, x: 100, y: 180, scaleX: 0.7, scaleY: 0.7 }); adventureMenuBtn.alpha = 0.95; LK.gui.topLeft.addChild(adventureMenuBtn); adventureMenuBtn.down = function () { if (typeof adventureMenuBtn !== "undefined" && adventureMenuBtn.parent) adventureMenuBtn.destroy(); if (typeof scoreTxt !== "undefined" && scoreTxt.parent) scoreTxt.destroy(); if (typeof sectionLabelTxt !== "undefined" && sectionLabelTxt.parent) sectionLabelTxt.destroy(); if (typeof adventureExitBtn !== "undefined" && adventureExitBtn.parent) adventureExitBtn.destroy(); if (typeof finishLine !== "undefined" && finishLine.parent) finishLine.destroy(); if (typeof player !== "undefined" && player && player.parent) player.destroy(); for (var i = 0; i < enemies.length; i++) if (enemies[i].parent) enemies[i].destroy(); for (var i = 0; i < coins.length; i++) if (coins[i].parent) coins[i].destroy(); enemies = []; coins = []; game.addChild(startMenuOverlay); // Do NOT pause game logic in adventure mode }; } else { if (typeof finishLine !== "undefined" && finishLine.parent) finishLine.destroy(); finishLine = undefined; if (typeof adventureExitBtn !== "undefined" && adventureExitBtn.parent) adventureExitBtn.destroy(); if (typeof adventureMenuBtn !== "undefined" && adventureMenuBtn.parent) adventureMenuBtn.destroy(); } } // Defensive: ensure lanePositions is always defined before game.update runs if (typeof lanePositions === "undefined") { lanePositions = [roadX + laneWidth * 0.5, roadX + laneWidth * 1.5, roadX + laneWidth * 2.5]; } // --- Patch PlayerCar to allow dynamic car asset --- PlayerCar.prototype.setCarAsset = function (assetId) { // Remove old asset while (this.children.length) this.removeChild(this.children[0]); var car = this.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); this.width = car.width; this.height = car.height; }; // --- Start with menu, not game --- var player, score, distance, coinsCollected, scoreTxt, scoreUpdateTimer, lanePositions; // Touch controls // Use square placeholder assets for left/right buttons (replace 'leftBtnSquare' and 'rightBtnSquare' with your asset ids when available) var leftBtn = LK.getAsset('leftBtnSquare', { anchorX: 0.5, anchorY: 0.5, x: 200, y: -250 }); LK.gui.bottomLeft.addChild(leftBtn); var rightBtn = LK.getAsset('rightBtnSquare', { anchorX: 0.5, anchorY: 0.5, x: -200, y: -250 }); LK.gui.bottomRight.addChild(rightBtn); // Control handlers leftBtn.down = function () { if (typeof player !== "undefined" && player && typeof player.lane !== "undefined" && player.lane > 0) { player.lane--; tween(player, { x: lanePositions[player.lane] }, { duration: 120, easing: tween.cubicOut }); } }; rightBtn.down = function () { if (typeof player !== "undefined" && player && typeof player.lane !== "undefined" && player.lane < laneCount - 1) { player.lane++; tween(player, { x: lanePositions[player.lane] }, { duration: 120, easing: tween.cubicOut }); } }; // Touch drag to move car left/right var dragStartX = null; var dragStartLane = null; game.down = function (x, y, obj) { if (x > roadX && x < roadX + roadWidth && y > 2732 - 700) { dragStartX = x; dragStartLane = player.lane; } }; game.move = function (x, y, obj) { if (dragStartX !== null) { var dx = x - dragStartX; if (Math.abs(dx) > laneWidth * 0.7) { if (dx < 0 && player.lane > 0) { player.lane--; tween(player, { x: lanePositions[player.lane] }, { duration: 120, easing: tween.cubicOut }); dragStartX = x; dragStartLane = player.lane; } else if (dx > 0 && player.lane < laneCount - 1) { player.lane++; tween(player, { x: lanePositions[player.lane] }, { duration: 120, easing: tween.cubicOut }); dragStartX = x; dragStartLane = player.lane; } } } }; game.up = function (x, y, obj) { dragStartX = null; dragStartLane = null; }; // Game objects var enemies = []; var coins = []; // Spawning timers var enemySpawnTick = 0; var coinSpawnTick = 0; // Main game update game.update = function () { // Pause all game logic if car selection overlay is visible if (carSelectOverlay.parent) { return; } // Adventure mode finish line logic var inAdventure = !!game.currentSection; if (inAdventure && typeof finishLine !== "undefined" && finishLine) { // Move finish line down as the world scrolls finishLine.y += 20; // match enemy/car speed // Check for crossing finish line if (!finishLine.crossed && player.y <= finishLine.y + finishLine.height / 2 && player.y >= finishLine.y - finishLine.height / 2) { finishLine.crossed = true; // Section complete! // Award 20 points if score >= 1000 (no crash) var sectionScore = Math.floor(distance / 5) + coinsCollected * 10; var earned = sectionScore >= 1000 ? 20 : 0; // Unlock next section if not already unlocked var unlockedSections = storage.unlockedSections || 1; if (game.currentSection >= unlockedSections && game.currentSection < sectionCount) { unlockedSections = game.currentSection + 1; storage.unlockedSections = unlockedSections; } // Remove Exit button if present if (typeof adventureExitBtn !== "undefined" && adventureExitBtn.parent) adventureExitBtn.destroy(); // Show overlay: Section Complete! var overlay = new Container(); var msg = new Text2('Section ' + game.currentSection + ' Complete!', { size: 120, fill: 0xFFD600 }); msg.anchor.set(0.5, 0.5); msg.x = 2048 / 2; msg.y = 900; overlay.addChild(msg); var earnedTxt = new Text2('Score: ' + sectionScore + (earned ? ' (+20)' : ''), { size: 80, fill: "#fff" }); earnedTxt.anchor.set(0.5, 0.5); earnedTxt.x = 2048 / 2; earnedTxt.y = 1100; overlay.addChild(earnedTxt); var nextBtn = new Text2(game.currentSection < sectionCount ? 'Next Section' : 'Back to Menu', { size: 90, fill: "#fff" }); nextBtn.anchor.set(0.5, 0.5); nextBtn.x = 2048 / 2; nextBtn.y = 1300; overlay.addChild(nextBtn); game.addChild(overlay); nextBtn.down = function () { overlay.destroy(); if (game.currentSection < sectionCount) { startAdventureSection(game.currentSection + 1); } else { // Back to menu if (typeof sectionLabelTxt !== "undefined" && sectionLabelTxt.parent) sectionLabelTxt.destroy(); game.addChild(startMenuOverlay); } }; // No pause in adventure mode! return; } // Distance distance += 0.7; // Update UI score = Math.floor(distance / 5) + coinsCollected * 10; // --- Adventure mode: enemy and coin spawning --- // Difficulty scaling: lower spawn interval as section increases var difficulty = typeof game.adventureDifficulty !== "undefined" ? game.adventureDifficulty : 1; enemySpawnTick++; if (enemySpawnTick > (38 + Math.random() * 18) / difficulty) { enemySpawnTick = 0; var enemyType = Math.floor(Math.random() * 3); var enemy; if (enemyType === 0) { enemy = new EnemyCar(); } else if (enemyType === 1) { enemy = new EnemyCar2(); } else { enemy = new EnemyCar3(); } // Increase enemy speed for higher sections enemy.speed *= difficulty; enemy.lane = Math.floor(Math.random() * laneCount); enemy.x = lanePositions[enemy.lane]; enemy.y = -200; enemies.push(enemy); game.addChild(enemy); } // Spawn coins coinSpawnTick++; if (coinSpawnTick > (80 + Math.random() * 60) / (0.8 + difficulty * 0.3)) { // Coins spawn a bit less frequently as difficulty increases coinSpawnTick = 0; var coin = new Coin(); coin.lane = Math.floor(Math.random() * laneCount); coin.x = lanePositions[coin.lane]; coin.y = -120; coins.push(coin); game.addChild(coin); } // Update enemies for (var i = enemies.length - 1; i >= 0; i--) { var e = enemies[i]; e.update(); if (e.y > 2732 + 200) { e.destroy(); enemies.splice(i, 1); continue; } if (e.intersects(player)) { LK.effects.flashObject(player, 0xff0000, 600); LK.effects.flashScreen(0xff0000, 800); // Remove Exit button if present if (typeof adventureExitBtn !== "undefined" && adventureExitBtn.parent) adventureExitBtn.destroy(); // Remove player car from the scene so it does not continue after crash if (typeof player !== "undefined" && player && player.parent) player.destroy(); // Show fail overlay var overlay = new Container(); var failTxt = new Text2('Crashed!', { size: 150, fill: 0xFF0000 }); failTxt.anchor.set(0.5, 0.5); failTxt.x = 2048 / 2; failTxt.y = 900; overlay.addChild(failTxt); // Show score above retry button var sectionScore = Math.floor(distance / 5) + coinsCollected * 10; var scoreAboveRetry = new Text2('Score: ' + sectionScore, { size: 80, fill: "#fff" }); scoreAboveRetry.anchor.set(0.5, 0.5); scoreAboveRetry.x = 2048 / 2; scoreAboveRetry.y = 1100; overlay.addChild(scoreAboveRetry); var retryBtn = new Text2('Retry Section', { size: 100, fill: "#fff" }); retryBtn.anchor.set(0.5, 0.5); retryBtn.x = 2048 / 2; retryBtn.y = 1200; overlay.addChild(retryBtn); game.addChild(overlay); retryBtn.down = function () { overlay.destroy(); startAdventureSection(game.currentSection); // Enemies and coins will come immediately after retry enemySpawnTick = 1000; // force spawn on next update coinSpawnTick = 1000; }; // No pause in adventure mode! return; } } // Update coins for (var i = coins.length - 1; i >= 0; i--) { var c = coins[i]; c.update(); if (c.y > 2732 + 120) { c.destroy(); coins.splice(i, 1); continue; } if (c.intersects(player)) { coinsCollected++; score = Math.floor(distance / 5) + coinsCollected * 10; scoreTxt.setText('Score: ' + score); LK.effects.flashObject(player, 0xFFD600, 300); c.destroy(); coins.splice(i, 1); continue; } } return; } // Distance distance += 0.7; // Update UI score = Math.floor(distance / 5) + coinsCollected * 10; // Adventure mode: lockout on crash if (inAdventure) { // If player collides with enemy, show fail overlay and do not unlock section for (var i = enemies.length - 1; i >= 0; i--) { var e = enemies[i]; e.update(); if (e.y > 2732 + 200) { e.destroy(); enemies.splice(i, 1); continue; } if (e.intersects(player)) { LK.effects.flashObject(player, 0xff0000, 600); LK.effects.flashScreen(0xff0000, 800); // Remove Exit button if present if (typeof adventureExitBtn !== "undefined" && adventureExitBtn.parent) adventureExitBtn.destroy(); // Show fail overlay var overlay = new Container(); var failTxt = new Text2('Crashed!', { size: 150, fill: 0xFF0000 }); failTxt.anchor.set(0.5, 0.5); failTxt.x = 2048 / 2; failTxt.y = 900; overlay.addChild(failTxt); // Show score above retry button var sectionScore = Math.floor(distance / 5) + coinsCollected * 10; var scoreAboveRetry = new Text2('Score: ' + sectionScore, { size: 80, fill: "#fff" }); scoreAboveRetry.anchor.set(0.5, 0.5); scoreAboveRetry.x = 2048 / 2; scoreAboveRetry.y = 1100; overlay.addChild(scoreAboveRetry); var retryBtn = new Text2('Retry Section', { size: 100, fill: "#fff" }); retryBtn.anchor.set(0.5, 0.5); retryBtn.x = 2048 / 2; retryBtn.y = 1200; overlay.addChild(retryBtn); game.addChild(overlay); retryBtn.down = function () { overlay.destroy(); startAdventureSection(game.currentSection); // Enemies and coins will come immediately after retry enemySpawnTick = 1000; coinSpawnTick = 1000; }; // No pause in adventure mode! return; } } } else { // Normal mode: original logic if (score >= 999) { // Stop the score update timer if (typeof scoreUpdateTimer !== "undefined") LK.clearInterval(scoreUpdateTimer); // Show Congratulations overlay var congratsTxt = new Text2('Congratulations', { size: 180, fill: 0xFFD600 }); congratsTxt.anchor.set(0.5, 0.5); congratsTxt.x = 2048 / 2; congratsTxt.y = 900; var playAgainBtn = new Text2('Play Again', { size: 120, fill: "#fff" }); playAgainBtn.anchor.set(0.5, 0.5); playAgainBtn.x = 2048 / 2; playAgainBtn.y = 1200; // Overlay container var overlay = new Container(); overlay.addChild(congratsTxt); overlay.addChild(playAgainBtn); game.addChild(overlay); playAgainBtn.down = function () { // Remove overlay and restart game overlay.destroy(); LK.showGameOver(); // Triggers game reset }; // Pause game logic game.update = function () {}; return; } // Spawn enemy cars enemySpawnTick++; if (enemySpawnTick > 38 + Math.random() * 18) { enemySpawnTick = 0; var enemyType = Math.floor(Math.random() * 3); var enemy; if (enemyType === 0) { enemy = new EnemyCar(); } else if (enemyType === 1) { enemy = new EnemyCar2(); } else { enemy = new EnemyCar3(); } enemy.lane = Math.floor(Math.random() * laneCount); enemy.x = lanePositions[enemy.lane]; enemy.y = -200; enemies.push(enemy); game.addChild(enemy); } // Spawn coins coinSpawnTick++; if (coinSpawnTick > 80 + Math.random() * 60) { coinSpawnTick = 0; var coin = new Coin(); coin.lane = Math.floor(Math.random() * laneCount); coin.x = lanePositions[coin.lane]; coin.y = -120; coins.push(coin); game.addChild(coin); } // Update enemies for (var i = enemies.length - 1; i >= 0; i--) { var e = enemies[i]; e.update(); if (e.y > 2732 + 200) { e.destroy(); enemies.splice(i, 1); continue; } // Collision with player if (e.intersects(player)) { // Turbo Car 2 (enemyCar) hit! Player must dodge it. LK.effects.flashObject(player, 0xff0000, 600); LK.effects.flashScreen(0xff0000, 800); startGame(); // Restart the game automatically return; } } } // Update coins for (var i = coins.length - 1; i >= 0; i--) { var c = coins[i]; c.update(); if (c.y > 2732 + 120) { c.destroy(); coins.splice(i, 1); continue; } if (c.intersects(player)) { coinsCollected++; score = Math.floor(distance / 5) + coinsCollected * 10; scoreTxt.setText('Score: ' + score); LK.effects.flashObject(player, 0xFFD600, 300); c.destroy(); coins.splice(i, 1); continue; } } }; // Center UI elements // scoreTxt is always at top right, do not move it to center // Prevent UI from overlapping top left menu // (already handled by not placing anything at gui.topLeft or at x < 100, y < 100)
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Coin
var Coin = Container.expand(function () {
var self = Container.call(this);
var coin = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = coin.width;
self.height = coin.height;
self.speed = 20;
self.lane = 1;
self.update = function () {
self.y += self.speed;
};
return self;
});
// Enemy Car
var EnemyCar = Container.expand(function () {
var self = Container.call(this);
var car = self.attachAsset('enemyCar', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = car.width;
self.height = car.height;
self.speed = 20 + Math.random() * 5;
self.lane = 1;
self.update = function () {
self.y += self.speed;
};
return self;
});
// Enemy Car 2
var EnemyCar2 = Container.expand(function () {
var self = Container.call(this);
var car = self.attachAsset('enemyCar2', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = car.width;
self.height = car.height;
self.speed = 22 + Math.random() * 6;
self.lane = 1;
self.update = function () {
self.y += self.speed;
};
return self;
});
// Enemy Car 3
var EnemyCar3 = Container.expand(function () {
var self = Container.call(this);
var car = self.attachAsset('enemyCar3', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = car.width;
self.height = car.height;
self.speed = 18 + Math.random() * 8;
self.lane = 1;
self.update = function () {
self.y += self.speed;
};
return self;
});
// Player Car
var PlayerCar = Container.expand(function () {
var self = Container.call(this);
var car = self.attachAsset('playerCar', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = car.width;
self.height = car.height;
self.lane = 1; // 0: left, 1: center, 2: right
self.invincible = false;
self.update = function () {
// No movement here; handled by input
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
// Button assets for Start and Choose Car
// Road and lanes setup
var roadWidth = 900;
var laneCount = 3;
var laneWidth = roadWidth / laneCount;
var roadX = (2048 - roadWidth) / 2;
var roadY = 0;
// --- Car Selection and Start Menu ---
var carAssetIds = ['playerCar',
// default
'car2', 'car3', 'car4', 'car5', 'car6', 'car7', 'car8', 'car9', 'car10', 'car11', 'car12', 'car13', 'car14', 'car15', 'car16'];
// Register 8 more car assets (IDs: car9 to car16)
// Store selected car index (default 0)
var selectedCarIndex = 0;
// Overlay containers for menu and car select
var startMenuOverlay = new Container();
var carSelectOverlay = new Container();
// --- Start Menu UI ---
var titleTxt = new Text2('Turbo Racer 3', {
size: 180,
fill: "#fff"
});
titleTxt.anchor.set(0.5, 0.5);
titleTxt.x = 2048 / 2;
titleTxt.y = 600;
// Start Button
var startBtn = LK.getAsset('startBtnImg', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 1000
});
var startBtnLabel = new Text2('Start', {
size: 90,
fill: "#222"
});
startBtnLabel.anchor.set(0.5, 0.5);
startBtnLabel.x = startBtn.x;
startBtnLabel.y = startBtn.y;
// Choose Car Button
var chooseCarBtn = LK.getAsset('chooseCarBtnImg', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 1200
});
var chooseCarBtnLabel = new Text2('Choose a Car', {
size: 70,
fill: "#222"
});
chooseCarBtnLabel.anchor.set(0.5, 0.5);
chooseCarBtnLabel.x = chooseCarBtn.x;
chooseCarBtnLabel.y = chooseCarBtn.y;
// Adventure Mode Button
var adventureBtn = LK.getAsset('adventureBtnImg', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 1400
});
var adventureBtnLabel = new Text2('Adventure Mode', {
size: 70,
fill: "#222"
});
adventureBtnLabel.anchor = {
x: 0.5,
y: 0.5
};
adventureBtnLabel.x = adventureBtn.x;
adventureBtnLabel.y = adventureBtn.y;
// Add to overlay
startMenuOverlay.addChild(titleTxt);
startMenuOverlay.addChild(startBtn);
startMenuOverlay.addChild(startBtnLabel);
startMenuOverlay.addChild(chooseCarBtn);
startMenuOverlay.addChild(chooseCarBtnLabel);
startMenuOverlay.addChild(adventureBtn);
startMenuOverlay.addChild(adventureBtnLabel);
game.addChild(startMenuOverlay);
// --- Adventure Mode Overlay ---
var adventureOverlay = new Container();
var adventureTitle = new Text2('Adventure Mode', {
size: 120,
fill: "#fff"
});
adventureTitle.anchor.set(0.5, 0.5);
adventureTitle.x = 2048 / 2;
adventureTitle.y = 400;
adventureOverlay.addChild(adventureTitle);
// Section grid (6 columns x 5 rows = 30 sections)
var sectionCols = 6;
var sectionRows = 5;
var sectionCount = 30;
var sectionSpacingX = 250;
var sectionSpacingY = 220;
var sectionStartX = 2048 / 2 - (sectionCols - 1) * sectionSpacingX / 2;
var sectionStartY = 700;
var sectionIcons = [];
// Persistent progress using storage plugin
var unlockedSections = storage.unlockedSections || 1; // Always at least 1 unlocked
for (var i = 0; i < sectionCount; i++) {
var col = i % sectionCols;
var row = Math.floor(i / sectionCols);
var sectionIcon = LK.getAsset('sectionIcon', {
anchorX: 0.5,
anchorY: 0.5,
x: sectionStartX + col * sectionSpacingX,
y: sectionStartY + row * sectionSpacingY
});
var sectionLabel = new Text2('' + (i + 1), {
size: 60,
fill: "#fff"
});
sectionLabel.anchor.set(0.5, 0.5);
sectionLabel.x = sectionIcon.x;
sectionLabel.y = sectionIcon.y;
// Lock icon overlay if locked
var isUnlocked = i < unlockedSections;
var lockIcon = null;
if (!isUnlocked) {
lockIcon = LK.getAsset('star1', {
anchorX: 0.5,
anchorY: 0.5,
x: sectionIcon.x,
y: sectionIcon.y,
scaleX: 1.1,
scaleY: 1.1
});
lockIcon.alpha = 0.7;
adventureOverlay.addChild(lockIcon);
}
// Section icon click handler
(function (idx, icon, label, locked, lockIconRef) {
icon.down = function () {
if (locked) {
// Optionally: flash or shake to indicate locked
LK.effects.flashObject(icon, 0xff0000, 400);
return;
}
// Start adventure section idx+1
if (adventureOverlay.parent) adventureOverlay.destroy();
startAdventureSection(idx + 1);
};
label.down = icon.down;
if (lockIconRef) lockIconRef.down = icon.down;
})(i, sectionIcon, sectionLabel, !isUnlocked, lockIcon);
adventureOverlay.addChild(sectionIcon);
adventureOverlay.addChild(sectionLabel);
sectionIcons.push(sectionIcon);
}
// Back button for adventure overlay
var adventureBackBtn = new Text2('Back', {
size: 80,
fill: "#fff"
});
adventureBackBtn.anchor.set(0.5, 0.5);
adventureBackBtn.x = 2048 / 2;
adventureBackBtn.y = sectionStartY + sectionRows * sectionSpacingY + 100;
adventureOverlay.addChild(adventureBackBtn);
// Adventure button handler
adventureBtn.down = function () {
if (startMenuOverlay.parent) startMenuOverlay.destroy();
game.addChild(adventureOverlay);
};
// Back button handler
adventureBackBtn.down = function () {
if (adventureOverlay.parent) adventureOverlay.destroy();
game.addChild(startMenuOverlay);
};
// --- Car Selection UI ---
var carSelectTitle = new Text2('Select Your Car', {
size: 120,
fill: "#fff"
});
carSelectTitle.anchor.set(0.5, 0.5);
carSelectTitle.x = 2048 / 2;
carSelectTitle.y = 400;
carSelectOverlay.addChild(carSelectTitle);
var carIcons = [];
var carIconSpacing = 220;
var carIconsPerRow = Math.ceil(carAssetIds.length / 2);
var carIconStartX = 2048 / 2 - (carIconsPerRow - 1) * carIconSpacing / 2;
var carIconY1 = 800;
var carIconY2 = 1250; // Increased spacing between rows
for (var i = 0; i < carAssetIds.length; i++) {
var row = i < carIconsPerRow ? 0 : 1;
var col = row === 0 ? i : i - carIconsPerRow;
var carIcon = LK.getAsset(carAssetIds[i], {
anchorX: 0.5,
anchorY: 0.5,
x: carIconStartX + col * carIconSpacing,
y: row === 0 ? carIconY1 : carIconY2,
scaleX: 0.7,
scaleY: 0.7
});
// Highlight border for selected
carIcon._border = null;
(function (idx, icon) {
icon.down = function () {
// Remove highlight from all
for (var j = 0; j < carIcons.length; j++) {
if (carIcons[j]._border) {
carIcons[j]._border.destroy();
carIcons[j]._border = null;
}
}
// Add highlight (no border, just set selectedCarIndex)
selectedCarIndex = idx;
// Animate icon to grow a little when selected
tween(icon, {
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 180,
easing: tween.cubicOut
});
// Shrink all other icons back to normal
for (var j = 0; j < carIcons.length; j++) {
if (carIcons[j] !== icon) {
tween(carIcons[j], {
scaleX: 0.7,
scaleY: 0.7
}, {
duration: 180,
easing: tween.cubicOut
});
}
}
};
})(i, carIcon);
carIcons.push(carIcon);
carSelectOverlay.addChild(carIcon);
}
// Preselect first car
carIcons[0].down();
// Confirm button
var confirmBtn = new Text2('Confirm', {
size: 100,
fill: "#fff"
});
confirmBtn.anchor.set(0.5, 0.5);
confirmBtn.x = 2048 / 2;
// Place confirmBtn below the second row of car icons
var carIconY = carIconY2; // Use the y of the second row
confirmBtn.y = carIconY + 300;
carSelectOverlay.addChild(confirmBtn);
// --- Menu Button Handlers ---
startBtn.down = function () {
// Remove overlays, start game
if (startMenuOverlay.parent) startMenuOverlay.destroy();
if (carSelectOverlay.parent) carSelectOverlay.destroy();
// Remove all start menu UI text and buttons
if (titleTxt.parent) titleTxt.destroy();
if (startBtn.parent) startBtn.destroy();
if (startBtnLabel.parent) startBtnLabel.destroy();
if (chooseCarBtn.parent) chooseCarBtn.destroy();
if (chooseCarBtnLabel.parent) chooseCarBtnLabel.destroy();
if (adventureBtn.parent) adventureBtn.destroy();
if (adventureBtnLabel.parent) adventureBtnLabel.destroy();
startGame();
};
chooseCarBtn.down = function () {
if (startMenuOverlay.parent) startMenuOverlay.destroy();
game.addChild(carSelectOverlay);
};
confirmBtn.down = function () {
if (carSelectOverlay.parent) carSelectOverlay.destroy();
game.addChild(startMenuOverlay);
};
// --- Game Start Logic ---
var mainMenuBtn; // Main menu button for normal mode
function startGame() {
startAdventureSection(null);
// Add main menu icon in upper left for normal mode
if (typeof mainMenuBtn !== "undefined" && mainMenuBtn.parent) mainMenuBtn.destroy();
mainMenuBtn = LK.getAsset('mainMenuBtnImg', {
anchorX: 0.5,
anchorY: 0.5,
x: 100,
y: 180,
scaleX: 0.7,
scaleY: 0.7
});
mainMenuBtn.alpha = 0.95;
LK.gui.topLeft.addChild(mainMenuBtn);
// On press, return to main menu
mainMenuBtn.down = function () {
if (typeof mainMenuBtn !== "undefined" && mainMenuBtn.parent) mainMenuBtn.destroy();
if (typeof scoreTxt !== "undefined" && scoreTxt.parent) scoreTxt.destroy();
if (typeof sectionLabelTxt !== "undefined" && sectionLabelTxt.parent) sectionLabelTxt.destroy();
// Remove all game objects
if (typeof player !== "undefined" && player && player.parent) player.destroy();
for (var i = 0; i < enemies.length; i++) if (enemies[i].parent) enemies[i].destroy();
for (var i = 0; i < coins.length; i++) if (coins[i].parent) coins[i].destroy();
enemies = [];
coins = [];
// Show main menu overlay
game.addChild(startMenuOverlay);
// Pause game logic while main menu is open
var prevUpdate = game.update;
game.update = function () {};
// When the start menu overlay is closed (startBtn or chooseCarBtn), restore game.update
var _resumeGameLogic = function resumeGameLogic() {
if (game.update === _resumeGameLogic) return; // Prevent double restore
game.update = prevUpdate;
};
var _oldStartBtnDown = startBtn.down;
startBtn.down = function () {
_resumeGameLogic();
if (_oldStartBtnDown) _oldStartBtnDown();
};
var _oldChooseCarBtnDown = chooseCarBtn.down;
chooseCarBtn.down = function () {
_resumeGameLogic();
if (_oldChooseCarBtnDown) _oldChooseCarBtnDown();
};
};
}
// --- Adventure Section Start Logic ---
function startAdventureSection(sectionNumber) {
// Remove overlays if present
if (startMenuOverlay.parent) startMenuOverlay.destroy();
if (carSelectOverlay.parent) carSelectOverlay.destroy();
if (adventureOverlay.parent) adventureOverlay.destroy();
// Remove any previous game objects
if (typeof player !== "undefined" && player && player.parent) player.destroy();
for (var i = 0; i < enemies.length; i++) if (enemies[i].parent) enemies[i].destroy();
for (var i = 0; i < coins.length; i++) if (coins[i].parent) coins[i].destroy();
enemies = [];
coins = [];
// Road background
var road = LK.getAsset('road', {
anchorX: 0,
anchorY: 0,
x: roadX,
y: roadY
});
game.addChild(road);
// Player car
player = new PlayerCar();
player.x = roadX + laneWidth * 1.5;
player.y = 2732 - 400;
player.setCarAsset(carAssetIds[selectedCarIndex]);
game.addChild(player);
// Section number for adventure mode, null means normal mode
game.currentSection = sectionNumber;
// Score and fuel UI
score = 0;
distance = 0;
coinsCollected = 0;
scoreTxt = new Text2('Score: 0', {
size: 90,
fill: "#fff"
});
// Place score above (top center) in adventure mode, else top right
if (sectionNumber) {
scoreTxt.anchor.set(0.5, 0); // top center
scoreTxt.x = 2048 / 2;
scoreTxt.y = 20;
} else {
scoreTxt.anchor.set(1, 0); // top right
scoreTxt.x = 2048 - 100;
scoreTxt.y = 20;
}
LK.gui.top.addChild(scoreTxt);
// Section label for adventure mode
if (sectionNumber) {
if (typeof sectionLabelTxt !== "undefined" && sectionLabelTxt.parent) sectionLabelTxt.destroy();
sectionLabelTxt = new Text2('Section ' + sectionNumber, {
size: 80,
fill: 0xFFD600
});
sectionLabelTxt.anchor.set(0, 0);
sectionLabelTxt.x = 120;
sectionLabelTxt.y = 20;
LK.gui.top.addChild(sectionLabelTxt);
} else {
if (typeof sectionLabelTxt !== "undefined" && sectionLabelTxt.parent) sectionLabelTxt.destroy();
}
// Timer to update score every second
if (typeof scoreUpdateTimer !== "undefined") LK.clearInterval(scoreUpdateTimer);
scoreUpdateTimer = LK.setInterval(function () {
scoreTxt.setText('Score: ' + score);
}, 1000);
// Lane positions
lanePositions = [roadX + laneWidth * 0.5, roadX + laneWidth * 1.5, roadX + laneWidth * 2.5];
// Place finish line for adventure mode
if (sectionNumber) {
// Make each section a bit longer and more difficult
// Section 1: base distance, base speeds; Section 2: longer, faster, etc.
var baseFinishDistance = 2732 * 1.5;
var finishDistance = baseFinishDistance + (sectionNumber - 1) * 600; // Each section is 600px longer
finishLineY = -finishDistance;
if (typeof finishLine !== "undefined" && finishLine.parent) finishLine.destroy();
finishLine = LK.getAsset('finishLine', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: finishLineY
});
game.addChild(finishLine);
finishLine.crossed = false;
// Set difficulty scaling for this section
game.adventureDifficulty = 1 + (sectionNumber - 1) * 0.12; // 12% harder per section
// Add Exit button for adventure mode
if (typeof adventureExitBtn !== "undefined" && adventureExitBtn.parent) adventureExitBtn.destroy();
adventureExitBtn = new Text2('Exit', {
size: 80,
fill: "#fff"
});
adventureExitBtn.anchor.set(1, 0);
adventureExitBtn.x = 2048 - 100;
adventureExitBtn.y = 120;
LK.gui.top.addChild(adventureExitBtn);
adventureExitBtn.down = function () {
// Remove overlays and return to adventure section select
if (typeof sectionLabelTxt !== "undefined" && sectionLabelTxt.parent) sectionLabelTxt.destroy();
if (typeof scoreTxt !== "undefined" && scoreTxt.parent) scoreTxt.destroy();
if (typeof adventureExitBtn !== "undefined" && adventureExitBtn.parent) adventureExitBtn.destroy();
if (typeof finishLine !== "undefined" && finishLine.parent) finishLine.destroy();
// Remove all game objects
if (typeof player !== "undefined" && player && player.parent) player.destroy();
for (var i = 0; i < enemies.length; i++) if (enemies[i].parent) enemies[i].destroy();
for (var i = 0; i < coins.length; i++) if (coins[i].parent) coins[i].destroy();
enemies = [];
coins = [];
// Show adventure section select overlay
game.addChild(adventureOverlay);
// Pause game logic
game.update = function () {};
};
// Add main menu button for adventure mode in upper left
if (typeof adventureMenuBtn !== "undefined" && adventureMenuBtn.parent) adventureMenuBtn.destroy();
adventureMenuBtn = LK.getAsset('mainMenuBtnImg', {
anchorX: 0.5,
anchorY: 0.5,
x: 100,
y: 180,
scaleX: 0.7,
scaleY: 0.7
});
adventureMenuBtn.alpha = 0.95;
LK.gui.topLeft.addChild(adventureMenuBtn);
adventureMenuBtn.down = function () {
if (typeof adventureMenuBtn !== "undefined" && adventureMenuBtn.parent) adventureMenuBtn.destroy();
if (typeof scoreTxt !== "undefined" && scoreTxt.parent) scoreTxt.destroy();
if (typeof sectionLabelTxt !== "undefined" && sectionLabelTxt.parent) sectionLabelTxt.destroy();
if (typeof adventureExitBtn !== "undefined" && adventureExitBtn.parent) adventureExitBtn.destroy();
if (typeof finishLine !== "undefined" && finishLine.parent) finishLine.destroy();
if (typeof player !== "undefined" && player && player.parent) player.destroy();
for (var i = 0; i < enemies.length; i++) if (enemies[i].parent) enemies[i].destroy();
for (var i = 0; i < coins.length; i++) if (coins[i].parent) coins[i].destroy();
enemies = [];
coins = [];
game.addChild(startMenuOverlay);
// Do NOT pause game logic in adventure mode
};
} else {
if (typeof finishLine !== "undefined" && finishLine.parent) finishLine.destroy();
finishLine = undefined;
if (typeof adventureExitBtn !== "undefined" && adventureExitBtn.parent) adventureExitBtn.destroy();
if (typeof adventureMenuBtn !== "undefined" && adventureMenuBtn.parent) adventureMenuBtn.destroy();
}
}
// Defensive: ensure lanePositions is always defined before game.update runs
if (typeof lanePositions === "undefined") {
lanePositions = [roadX + laneWidth * 0.5, roadX + laneWidth * 1.5, roadX + laneWidth * 2.5];
}
// --- Patch PlayerCar to allow dynamic car asset ---
PlayerCar.prototype.setCarAsset = function (assetId) {
// Remove old asset
while (this.children.length) this.removeChild(this.children[0]);
var car = this.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
this.width = car.width;
this.height = car.height;
};
// --- Start with menu, not game ---
var player, score, distance, coinsCollected, scoreTxt, scoreUpdateTimer, lanePositions;
// Touch controls
// Use square placeholder assets for left/right buttons (replace 'leftBtnSquare' and 'rightBtnSquare' with your asset ids when available)
var leftBtn = LK.getAsset('leftBtnSquare', {
anchorX: 0.5,
anchorY: 0.5,
x: 200,
y: -250
});
LK.gui.bottomLeft.addChild(leftBtn);
var rightBtn = LK.getAsset('rightBtnSquare', {
anchorX: 0.5,
anchorY: 0.5,
x: -200,
y: -250
});
LK.gui.bottomRight.addChild(rightBtn);
// Control handlers
leftBtn.down = function () {
if (typeof player !== "undefined" && player && typeof player.lane !== "undefined" && player.lane > 0) {
player.lane--;
tween(player, {
x: lanePositions[player.lane]
}, {
duration: 120,
easing: tween.cubicOut
});
}
};
rightBtn.down = function () {
if (typeof player !== "undefined" && player && typeof player.lane !== "undefined" && player.lane < laneCount - 1) {
player.lane++;
tween(player, {
x: lanePositions[player.lane]
}, {
duration: 120,
easing: tween.cubicOut
});
}
};
// Touch drag to move car left/right
var dragStartX = null;
var dragStartLane = null;
game.down = function (x, y, obj) {
if (x > roadX && x < roadX + roadWidth && y > 2732 - 700) {
dragStartX = x;
dragStartLane = player.lane;
}
};
game.move = function (x, y, obj) {
if (dragStartX !== null) {
var dx = x - dragStartX;
if (Math.abs(dx) > laneWidth * 0.7) {
if (dx < 0 && player.lane > 0) {
player.lane--;
tween(player, {
x: lanePositions[player.lane]
}, {
duration: 120,
easing: tween.cubicOut
});
dragStartX = x;
dragStartLane = player.lane;
} else if (dx > 0 && player.lane < laneCount - 1) {
player.lane++;
tween(player, {
x: lanePositions[player.lane]
}, {
duration: 120,
easing: tween.cubicOut
});
dragStartX = x;
dragStartLane = player.lane;
}
}
}
};
game.up = function (x, y, obj) {
dragStartX = null;
dragStartLane = null;
};
// Game objects
var enemies = [];
var coins = [];
// Spawning timers
var enemySpawnTick = 0;
var coinSpawnTick = 0;
// Main game update
game.update = function () {
// Pause all game logic if car selection overlay is visible
if (carSelectOverlay.parent) {
return;
}
// Adventure mode finish line logic
var inAdventure = !!game.currentSection;
if (inAdventure && typeof finishLine !== "undefined" && finishLine) {
// Move finish line down as the world scrolls
finishLine.y += 20; // match enemy/car speed
// Check for crossing finish line
if (!finishLine.crossed && player.y <= finishLine.y + finishLine.height / 2 && player.y >= finishLine.y - finishLine.height / 2) {
finishLine.crossed = true;
// Section complete!
// Award 20 points if score >= 1000 (no crash)
var sectionScore = Math.floor(distance / 5) + coinsCollected * 10;
var earned = sectionScore >= 1000 ? 20 : 0;
// Unlock next section if not already unlocked
var unlockedSections = storage.unlockedSections || 1;
if (game.currentSection >= unlockedSections && game.currentSection < sectionCount) {
unlockedSections = game.currentSection + 1;
storage.unlockedSections = unlockedSections;
}
// Remove Exit button if present
if (typeof adventureExitBtn !== "undefined" && adventureExitBtn.parent) adventureExitBtn.destroy();
// Show overlay: Section Complete!
var overlay = new Container();
var msg = new Text2('Section ' + game.currentSection + ' Complete!', {
size: 120,
fill: 0xFFD600
});
msg.anchor.set(0.5, 0.5);
msg.x = 2048 / 2;
msg.y = 900;
overlay.addChild(msg);
var earnedTxt = new Text2('Score: ' + sectionScore + (earned ? ' (+20)' : ''), {
size: 80,
fill: "#fff"
});
earnedTxt.anchor.set(0.5, 0.5);
earnedTxt.x = 2048 / 2;
earnedTxt.y = 1100;
overlay.addChild(earnedTxt);
var nextBtn = new Text2(game.currentSection < sectionCount ? 'Next Section' : 'Back to Menu', {
size: 90,
fill: "#fff"
});
nextBtn.anchor.set(0.5, 0.5);
nextBtn.x = 2048 / 2;
nextBtn.y = 1300;
overlay.addChild(nextBtn);
game.addChild(overlay);
nextBtn.down = function () {
overlay.destroy();
if (game.currentSection < sectionCount) {
startAdventureSection(game.currentSection + 1);
} else {
// Back to menu
if (typeof sectionLabelTxt !== "undefined" && sectionLabelTxt.parent) sectionLabelTxt.destroy();
game.addChild(startMenuOverlay);
}
};
// No pause in adventure mode!
return;
}
// Distance
distance += 0.7;
// Update UI
score = Math.floor(distance / 5) + coinsCollected * 10;
// --- Adventure mode: enemy and coin spawning ---
// Difficulty scaling: lower spawn interval as section increases
var difficulty = typeof game.adventureDifficulty !== "undefined" ? game.adventureDifficulty : 1;
enemySpawnTick++;
if (enemySpawnTick > (38 + Math.random() * 18) / difficulty) {
enemySpawnTick = 0;
var enemyType = Math.floor(Math.random() * 3);
var enemy;
if (enemyType === 0) {
enemy = new EnemyCar();
} else if (enemyType === 1) {
enemy = new EnemyCar2();
} else {
enemy = new EnemyCar3();
}
// Increase enemy speed for higher sections
enemy.speed *= difficulty;
enemy.lane = Math.floor(Math.random() * laneCount);
enemy.x = lanePositions[enemy.lane];
enemy.y = -200;
enemies.push(enemy);
game.addChild(enemy);
}
// Spawn coins
coinSpawnTick++;
if (coinSpawnTick > (80 + Math.random() * 60) / (0.8 + difficulty * 0.3)) {
// Coins spawn a bit less frequently as difficulty increases
coinSpawnTick = 0;
var coin = new Coin();
coin.lane = Math.floor(Math.random() * laneCount);
coin.x = lanePositions[coin.lane];
coin.y = -120;
coins.push(coin);
game.addChild(coin);
}
// Update enemies
for (var i = enemies.length - 1; i >= 0; i--) {
var e = enemies[i];
e.update();
if (e.y > 2732 + 200) {
e.destroy();
enemies.splice(i, 1);
continue;
}
if (e.intersects(player)) {
LK.effects.flashObject(player, 0xff0000, 600);
LK.effects.flashScreen(0xff0000, 800);
// Remove Exit button if present
if (typeof adventureExitBtn !== "undefined" && adventureExitBtn.parent) adventureExitBtn.destroy();
// Remove player car from the scene so it does not continue after crash
if (typeof player !== "undefined" && player && player.parent) player.destroy();
// Show fail overlay
var overlay = new Container();
var failTxt = new Text2('Crashed!', {
size: 150,
fill: 0xFF0000
});
failTxt.anchor.set(0.5, 0.5);
failTxt.x = 2048 / 2;
failTxt.y = 900;
overlay.addChild(failTxt);
// Show score above retry button
var sectionScore = Math.floor(distance / 5) + coinsCollected * 10;
var scoreAboveRetry = new Text2('Score: ' + sectionScore, {
size: 80,
fill: "#fff"
});
scoreAboveRetry.anchor.set(0.5, 0.5);
scoreAboveRetry.x = 2048 / 2;
scoreAboveRetry.y = 1100;
overlay.addChild(scoreAboveRetry);
var retryBtn = new Text2('Retry Section', {
size: 100,
fill: "#fff"
});
retryBtn.anchor.set(0.5, 0.5);
retryBtn.x = 2048 / 2;
retryBtn.y = 1200;
overlay.addChild(retryBtn);
game.addChild(overlay);
retryBtn.down = function () {
overlay.destroy();
startAdventureSection(game.currentSection);
// Enemies and coins will come immediately after retry
enemySpawnTick = 1000; // force spawn on next update
coinSpawnTick = 1000;
};
// No pause in adventure mode!
return;
}
}
// Update coins
for (var i = coins.length - 1; i >= 0; i--) {
var c = coins[i];
c.update();
if (c.y > 2732 + 120) {
c.destroy();
coins.splice(i, 1);
continue;
}
if (c.intersects(player)) {
coinsCollected++;
score = Math.floor(distance / 5) + coinsCollected * 10;
scoreTxt.setText('Score: ' + score);
LK.effects.flashObject(player, 0xFFD600, 300);
c.destroy();
coins.splice(i, 1);
continue;
}
}
return;
}
// Distance
distance += 0.7;
// Update UI
score = Math.floor(distance / 5) + coinsCollected * 10;
// Adventure mode: lockout on crash
if (inAdventure) {
// If player collides with enemy, show fail overlay and do not unlock section
for (var i = enemies.length - 1; i >= 0; i--) {
var e = enemies[i];
e.update();
if (e.y > 2732 + 200) {
e.destroy();
enemies.splice(i, 1);
continue;
}
if (e.intersects(player)) {
LK.effects.flashObject(player, 0xff0000, 600);
LK.effects.flashScreen(0xff0000, 800);
// Remove Exit button if present
if (typeof adventureExitBtn !== "undefined" && adventureExitBtn.parent) adventureExitBtn.destroy();
// Show fail overlay
var overlay = new Container();
var failTxt = new Text2('Crashed!', {
size: 150,
fill: 0xFF0000
});
failTxt.anchor.set(0.5, 0.5);
failTxt.x = 2048 / 2;
failTxt.y = 900;
overlay.addChild(failTxt);
// Show score above retry button
var sectionScore = Math.floor(distance / 5) + coinsCollected * 10;
var scoreAboveRetry = new Text2('Score: ' + sectionScore, {
size: 80,
fill: "#fff"
});
scoreAboveRetry.anchor.set(0.5, 0.5);
scoreAboveRetry.x = 2048 / 2;
scoreAboveRetry.y = 1100;
overlay.addChild(scoreAboveRetry);
var retryBtn = new Text2('Retry Section', {
size: 100,
fill: "#fff"
});
retryBtn.anchor.set(0.5, 0.5);
retryBtn.x = 2048 / 2;
retryBtn.y = 1200;
overlay.addChild(retryBtn);
game.addChild(overlay);
retryBtn.down = function () {
overlay.destroy();
startAdventureSection(game.currentSection);
// Enemies and coins will come immediately after retry
enemySpawnTick = 1000;
coinSpawnTick = 1000;
};
// No pause in adventure mode!
return;
}
}
} else {
// Normal mode: original logic
if (score >= 999) {
// Stop the score update timer
if (typeof scoreUpdateTimer !== "undefined") LK.clearInterval(scoreUpdateTimer);
// Show Congratulations overlay
var congratsTxt = new Text2('Congratulations', {
size: 180,
fill: 0xFFD600
});
congratsTxt.anchor.set(0.5, 0.5);
congratsTxt.x = 2048 / 2;
congratsTxt.y = 900;
var playAgainBtn = new Text2('Play Again', {
size: 120,
fill: "#fff"
});
playAgainBtn.anchor.set(0.5, 0.5);
playAgainBtn.x = 2048 / 2;
playAgainBtn.y = 1200;
// Overlay container
var overlay = new Container();
overlay.addChild(congratsTxt);
overlay.addChild(playAgainBtn);
game.addChild(overlay);
playAgainBtn.down = function () {
// Remove overlay and restart game
overlay.destroy();
LK.showGameOver(); // Triggers game reset
};
// Pause game logic
game.update = function () {};
return;
}
// Spawn enemy cars
enemySpawnTick++;
if (enemySpawnTick > 38 + Math.random() * 18) {
enemySpawnTick = 0;
var enemyType = Math.floor(Math.random() * 3);
var enemy;
if (enemyType === 0) {
enemy = new EnemyCar();
} else if (enemyType === 1) {
enemy = new EnemyCar2();
} else {
enemy = new EnemyCar3();
}
enemy.lane = Math.floor(Math.random() * laneCount);
enemy.x = lanePositions[enemy.lane];
enemy.y = -200;
enemies.push(enemy);
game.addChild(enemy);
}
// Spawn coins
coinSpawnTick++;
if (coinSpawnTick > 80 + Math.random() * 60) {
coinSpawnTick = 0;
var coin = new Coin();
coin.lane = Math.floor(Math.random() * laneCount);
coin.x = lanePositions[coin.lane];
coin.y = -120;
coins.push(coin);
game.addChild(coin);
}
// Update enemies
for (var i = enemies.length - 1; i >= 0; i--) {
var e = enemies[i];
e.update();
if (e.y > 2732 + 200) {
e.destroy();
enemies.splice(i, 1);
continue;
}
// Collision with player
if (e.intersects(player)) {
// Turbo Car 2 (enemyCar) hit! Player must dodge it.
LK.effects.flashObject(player, 0xff0000, 600);
LK.effects.flashScreen(0xff0000, 800);
startGame(); // Restart the game automatically
return;
}
}
}
// Update coins
for (var i = coins.length - 1; i >= 0; i--) {
var c = coins[i];
c.update();
if (c.y > 2732 + 120) {
c.destroy();
coins.splice(i, 1);
continue;
}
if (c.intersects(player)) {
coinsCollected++;
score = Math.floor(distance / 5) + coinsCollected * 10;
scoreTxt.setText('Score: ' + score);
LK.effects.flashObject(player, 0xFFD600, 300);
c.destroy();
coins.splice(i, 1);
continue;
}
}
};
// Center UI elements
// scoreTxt is always at top right, do not move it to center
// Prevent UI from overlapping top left menu
// (already handled by not placing anything at gui.topLeft or at x < 100, y < 100)
Make me a coin 2d pixel. In-Game asset. 2d. High contrast. No shadows
Draw 2d pixel car top view Red. In-Game asset. 2d. High contrast. No shadows
Draw 2d pixel car top view Blue. In-Game asset. 2d. High contrast. No shadows
draw a left facing 2d pixel game button. Yellow. Like this ▶️. In-Game asset. 2d. High contrast. No shadows
Try to do this photo again
Do this again but green
Without background
Without background
Without background
Without background
Without background
Without background
Without background
Make this pixel 2d
make a fireman truck like this
make a police car like this
make this green and red
make sooooo top view
make this white
Draw 2d pixel motorciycle top view light green.. In-Game asset. 2d. High contrast. No shadows
make this flying car and eflatun
Draw 2d pixel lamborghini car top view turkuaz.. In-Game asset. 2d. High contrast. No shadows
Draw 2d pixel car top view brown+green. In-Game asset. 2d. High contrast. No shadows
Draw 2d pixel car top view Red+white. In-Game asset. 2d. High contrast. No shadows
like a main menu symbol