User prompt
Please fix the bug: 'TypeError: Cannot read properties of undefined (reading 'to')' in or related to this line: 'tween(leftLine).to({' Line Number: 358 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
Code edit (1 edits merged)
Please save this source code
User prompt
Please fix the bug
User prompt
Enhance the overall visual quality.
User prompt
Everything appears slightly too small in the scene. Zoom in a bit to make all elements—cars, road, trees, signs—slightly larger and more visible, while keeping the slight rear top view and real-world proportions
User prompt
A realistic 3-lane asphalt road with dashed white lane markings, viewed from a slight rear top angle. Each lane is about 3.5 meters wide, and vehicles follow real-world scale and proportions based on their type and name (e.g., sedan, bus, motorcycle, etc.). Place a [CAR_NAME] centered in one lane, perfectly aligned and driving straight forward, showing only the top and rear of the vehicle. Other traffic vehicles (also realistic) may appear in other lanes or ahead at a distance, but none of them overlap or touch each other — no collisions. Vehicles are spaced clearly apart, and if any overlap occurs, trigger a visible crash or explosion effect. No visible drivers. Daylight environment with occasional roadside trees and traffic signs. ---
User prompt
A realistic 3-lane asphalt road with dashed white lane markings, viewed from a slight rear top angle. The road and vehicles follow real-world scale: each lane is about 3.5 meters wide. Automatically use realistic proportions for each vehicle based on its type and name (e.g., sedan, bus, motorcycle, etc.). Place a [CAR_NAME] centered in one of the lanes, perfectly aligned and driving straight ahead, showing only the top and rear of the vehicle. Other traffic vehicles may appear in the background with correct real-life proportions. No visible driver. Daylight, clean scene with occasional roadside trees and traffic signs.
Code edit (1 edits merged)
Please save this source code
User prompt
Highway Rush
Initial prompt
Create a 3-lane asphalt road driving game with a top-rear camera view showing the top and back of the player's car. The player moves left or right by tapping, with slight steering animations. Random vehicles (buses, trucks, police, ambulance, fire trucks, etc.) appear at different speeds and lanes, occasionally switching lanes randomly. Player car speed and handling improve over levels, boosted by pickups (fuel, nitro, tuning). Show current speed and gear at the top of the screen. Add roadside signs, plants, and occasional pedestrians.
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var PlayerCar = Container.expand(function () { var self = Container.call(this); var carGraphics = self.attachAsset('playerCar', { anchorX: 0.5, anchorY: 0.5 }); self.currentLane = 1; // 0 = left, 1 = center, 2 = right self.targetX = 0; self.speed = 0; // Start at zero speed self.baseSpeed = 0; // Base speed that increases with progress self.maxSpeed = 15; self.gear = 1; self.nitroBoost = 0; self.tuningLevel = 1; self.shield = false; self.originalSpeed = 0; self.checkpointPosition = { x: getLaneX(1), y: 2732 - 300 }; self.isBraking = false; self.isBoosting = false; self.boostTimer = 0; self.boostMultiplier = 2.5; self.boostDuration = 120; // 2 seconds at 60fps self.update = function () { // Handle brake and boost states if (self.isBraking) { self.speed = 0; LK.effects.flashObject(carGraphics, 0xff0000, 100); } else if (self.isBoosting) { // Calculate boosted speed based on current base speed var baseSpeed = self.baseSpeed + self.gear * 1.5; if (self.nitroBoost > 0) { baseSpeed *= 1.5; self.nitroBoost--; if (self.nitroBoost % 10 === 0) { LK.effects.flashObject(carGraphics, 0x00ccff, 200); } } self.speed = Math.min(baseSpeed * self.tuningLevel * self.boostMultiplier, self.maxSpeed * self.boostMultiplier); self.boostTimer--; if (self.boostTimer <= 0) { self.isBoosting = false; } LK.effects.flashObject(carGraphics, 0x00ff00, 100); } else { // Normal speed calculation // Gradually increase base speed with distance traveled self.baseSpeed = Math.min(distanceTraveled * 0.008, 12); // Max base speed of 12 // Update speed based on gear and power-ups var baseSpeed = self.baseSpeed + self.gear * 1.5; if (self.nitroBoost > 0) { baseSpeed *= 1.5; self.nitroBoost--; // Add nitro visual effect if (self.nitroBoost % 10 === 0) { LK.effects.flashObject(carGraphics, 0x00ccff, 200); } } self.speed = Math.min(baseSpeed * self.tuningLevel, self.maxSpeed); } // Update gear based on speed with proper gear ranges var newGear = 1; if (self.speed < 0.5) { newGear = 0; // Reverse gear when nearly stopped } else if (self.speed < 2) { newGear = 1; } else if (self.speed < 5) { newGear = 2; } else if (self.speed < 8) { newGear = 3; } else if (self.speed < 12) { newGear = 4; } else { newGear = 5; } if (newGear !== self.gear) { self.gear = newGear; updateGearDisplay(); // Flash for gear change LK.effects.flashObject(carGraphics, 0xffff00, 300); } }; self.switchLane = function (direction) { var newLane = self.currentLane; if (direction === 'left' && self.currentLane > 0) { newLane = self.currentLane - 1; } else if (direction === 'right' && self.currentLane < 2) { newLane = self.currentLane + 1; } // Only move if lane change is valid if (newLane !== self.currentLane) { self.currentLane = newLane; var newTargetX = getLaneX(self.currentLane); // Stop any existing tween tween.stop(self, { x: true }); // Smooth tween to new lane position with level-based turn boost var turnDuration = 300 / (self.turnBoost || 1.0); tween(self, { x: newTargetX }, { duration: turnDuration, easing: tween.easeOut }); // Add steering tilt animation var tiltDirection = direction === 'left' ? -0.2 : 0.2; tween(carGraphics, { rotation: tiltDirection }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { tween(carGraphics, { rotation: 0 }, { duration: 150, easing: tween.easeOut }); } }); } }; self.brake = function () { self.isBraking = true; self.isBoosting = false; }; self.releaseBrake = function () { self.isBraking = false; }; self.boost = function () { if (!self.isBoosting) { // Only boost if not already boosting self.isBoosting = true; self.isBraking = false; self.boostTimer = self.boostDuration; // 2 seconds boost // Add boost visual effect LK.effects.flashObject(carGraphics, 0x00ff00, 200); } }; return self; }); var PowerUp = Container.expand(function (powerUpType) { var self = Container.call(this); var powerUpGraphics = self.attachAsset(powerUpType, { anchorX: 0.5, anchorY: 0.5 }); self.powerUpType = powerUpType; self.speed = 4; self.bobOffset = Math.random() * Math.PI * 2; self.bobSpeed = 0.1; self.update = function () { self.y += self.speed; // Bobbing animation self.bobOffset += self.bobSpeed; powerUpGraphics.y = Math.sin(self.bobOffset) * 5; }; return self; }); var RoadElement = Container.expand(function (elementType) { var self = Container.call(this); var elementGraphics = self.attachAsset(elementType, { anchorX: 0.5, anchorY: 0.5 }); self.speed = 5; self.update = function () { self.y += self.speed; }; return self; }); var TrafficVehicle = Container.expand(function (vehicleType) { var self = Container.call(this); var vehicleGraphics = self.attachAsset(vehicleType, { anchorX: 0.5, anchorY: 0.5 }); // Vehicles now use their asset-defined proportions for realistic scaling // Asset sizes are: // - Cars (player, enemy, police): 160x280 // - JustCar variants: 145-160x265-280 (realistic car proportions) // - Van: 140x260 // - Truck: 180x380 // - Bus: 180x420 // - Fire truck: 180x350 // - Motorcycle: 80x180 (realistic motorcycle proportions) // - Pedestrian: 50x80 (realistic human proportions) // All vehicles maintain proper proportional relationships self.vehicleType = vehicleType; self.speed = 3 + Math.random() * 4; self.currentLane = Math.floor(Math.random() * 3); self.targetX = getLaneX(self.currentLane); self.laneChangeTimer = 0; self.laneChangeDelay = 120 + Math.random() * 240; self.update = function () { // Move vehicle forward self.y += self.speed; // Smooth lane switching with steering animation if (Math.abs(self.x - self.targetX) > 2) { self.x += (self.targetX - self.x) * 0.1; // Add subtle steering rotation var steerDirection = self.targetX - self.x; vehicleGraphics.rotation = Math.max(-0.08, Math.min(0.08, steerDirection * 0.0005)); } else { // Return to straight position vehicleGraphics.rotation *= 0.95; } // Add subtle engine vibration for larger vehicles if (self.vehicleType === 'truck' || self.vehicleType === 'bus') { vehicleGraphics.x = Math.sin(LK.ticks * 0.1) * 0.5; } // Lane changing behavior based on vehicle type self.laneChangeTimer++; if (self.laneChangeTimer > self.laneChangeDelay) { // Determine if this is an enemy vehicle var isEnemyVehicle = self.vehicleType === 'enemyCar' || self.vehicleType === 'van' || self.vehicleType === 'motorcycle' || self.vehicleType === 'truck' || self.vehicleType === 'bus' || self.vehicleType === 'policeCar' || self.vehicleType === 'fireTruck'; // Emergency vehicles can change lanes more aggressively var isEmergencyVehicle = self.vehicleType === 'policeCar' || self.vehicleType === 'fireTruck'; if (isEnemyVehicle) { // Enemy vehicles - aggressive behavior, may drive toward player if (Math.random() < 0.08) { // Higher chance of lane change // Sometimes target the player's lane aggressively if (Math.random() < 0.3 && player) { self.currentLane = player.currentLane; self.targetX = getLaneX(self.currentLane); self.laneChangeTimer = 0; self.laneChangeDelay = 60 + Math.random() * 120; // Shorter delay for enemies } else { // Random aggressive lane change var newLane = Math.floor(Math.random() * 3); if (newLane !== self.currentLane) { self.currentLane = newLane; self.targetX = getLaneX(self.currentLane); self.laneChangeTimer = 0; self.laneChangeDelay = 60 + Math.random() * 120; } } } } else { // Neutral vehicles - cautious lane changing if (Math.random() < 0.015) { // Lower chance of lane change // Only change one lane at a time var possibleLanes = []; if (self.currentLane > 0) possibleLanes.push(self.currentLane - 1); if (self.currentLane < 2) possibleLanes.push(self.currentLane + 1); if (possibleLanes.length > 0) { var targetLane = possibleLanes[Math.floor(Math.random() * possibleLanes.length)]; // Check if target lane is clear before changing var laneIsClear = true; var targetLaneX = getLaneX(targetLane); for (var v = 0; v < trafficVehicles.length; v++) { var otherVehicle = trafficVehicles[v]; if (otherVehicle !== self && Math.abs(otherVehicle.x - targetLaneX) < 100) { // Check if there's a vehicle in the target lane within safety distance var distanceToOther = Math.abs(otherVehicle.y - self.y); if (distanceToOther < 200) { // Safety distance laneIsClear = false; break; } } } // Only change lanes if target lane is clear if (laneIsClear) { self.currentLane = targetLane; self.targetX = getLaneX(self.currentLane); self.laneChangeTimer = 0; self.laneChangeDelay = 180 + Math.random() * 300; // Longer delay for cautious driving } } } } // Special behavior for emergency vehicles if (isEmergencyVehicle && Math.random() < 0.1) { // Emergency vehicles can change lanes rapidly with sirens if (Math.random() < 0.5) { // 50% chance to activate siren during lane change LK.getSound('siren').play(); // Flash emergency lights LK.effects.flashObject(vehicleGraphics, 0xff0000, 300); } } } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x2c3e50 }); /**** * Game Code ****/ // Game variables var player; var trafficVehicles = []; var powerUps = []; var roadElements = []; var roadLines = []; var gameSpeed = 1; var distanceTraveled = 0; var spawnTimer = 0; var powerUpSpawnTimer = 0; var environmentSpawnTimer = 0; var roadLineSpawnTimer = 0; var pedestrianSpawnTimer = 0; // Level system variables var currentLevel = storage.currentLevel || 1; var totalScore = storage.totalScore || 0; var sessionScore = 0; // Vehicle scoring values var vehicleScores = { 'justcar': 5, 'justcar2': 5, 'justcar3': 5, 'justcar4': 5, 'justcar5': 5, 'justcar6': 5, 'justcar7': 5, 'van': 8, 'truck': 12, 'policeCar': 15, 'Health': 18, // Ambulance 'fireTruck': 20, 'motorcycle': 5, 'bus': 8, 'enemyCar': 5 }; // Level unlock requirements var levelRequirements = [0, 200, 500, 900, 1400, 2000]; // Level scaling factors var levelScaling = { 1: { speedBoost: 1.0, turnBoost: 1.0 }, 2: { speedBoost: 1.1, turnBoost: 1.1 }, 3: { speedBoost: 1.2, turnBoost: 1.2 }, 4: { speedBoost: 1.3, turnBoost: 1.3 }, 5: { speedBoost: 1.4, turnBoost: 1.4 }, 6: { speedBoost: 1.5, turnBoost: 1.5 } }; // Lane positions - realistic 3.5m wide lanes (scaled to game coordinates) var lanePositions = [2048 / 2 - 420, // Left lane 2048 / 2, // Center lane 2048 / 2 + 420 // Right lane ]; function getLaneX(lane) { return lanePositions[lane]; } // Create road background with realistic asphalt appearance var roadBackground = LK.getAsset('roadLane', { anchorX: 0.5, anchorY: 0.5, scaleY: 30 }); roadBackground.x = 2048 / 2; roadBackground.y = 2732 / 2; game.addChild(roadBackground); // Create road shoulders var leftShoulder = LK.getAsset('roadShoulder', { anchorX: 0.5, anchorY: 0.5, scaleY: 30 }); leftShoulder.x = 2048 / 2 - 840; leftShoulder.y = 2732 / 2; game.addChild(leftShoulder); var rightShoulder = LK.getAsset('roadShoulder', { anchorX: 0.5, anchorY: 0.5, scaleY: 30 }); rightShoulder.x = 2048 / 2 + 840; rightShoulder.y = 2732 / 2; game.addChild(rightShoulder); // Create player car player = new PlayerCar(); player.x = getLaneX(1); player.y = 2732 - 300; game.addChild(player); // Apply initial level scaling applyLevelScaling(); // UI Elements var speedText = new Text2('Speed: 0 KM/H', { size: 60, fill: '#ffffff' }); speedText.anchor.set(0, 0); speedText.x = 200; speedText.y = 50; LK.gui.topLeft.addChild(speedText); // Removed duplicate gear text display - keeping only the large gear number display var scoreText = new Text2('Distance: 0m', { size: 50, fill: '#ffffff' }); scoreText.anchor.set(0.5, 0); scoreText.x = 0; scoreText.y = 120; LK.gui.top.addChild(scoreText); // Manual gear display var gearDisplayText = new Text2('1', { size: 120, fill: '#00ff00' }); gearDisplayText.anchor.set(1, 0); gearDisplayText.x = -50; gearDisplayText.y = 120; LK.gui.topRight.addChild(gearDisplayText); // Gear indicator background var gearIndicator = new Text2('GEAR', { size: 40, fill: '#888888' }); gearIndicator.anchor.set(1, 0); gearIndicator.x = -50; gearIndicator.y = 90; LK.gui.topRight.addChild(gearIndicator); // Level display var levelText = new Text2('Level: ' + currentLevel, { size: 50, fill: '#00ff00' }); levelText.anchor.set(0, 0); levelText.x = 200; levelText.y = 120; LK.gui.topLeft.addChild(levelText); // Total score display var totalScoreText = new Text2('Total: ' + totalScore, { size: 45, fill: '#ffff00' }); totalScoreText.anchor.set(0, 0); totalScoreText.x = 200; totalScoreText.y = 180; LK.gui.topLeft.addChild(totalScoreText); // Create brake button var brakeButton = new Text2('BRAKE', { size: 80, fill: '#ff0000' }); brakeButton.anchor.set(0, 1); brakeButton.x = 50; brakeButton.y = -50; LK.gui.bottomLeft.addChild(brakeButton); // Create boost button var boostButton = new Text2('BOOST', { size: 80, fill: '#00ff00' }); boostButton.anchor.set(1, 1); boostButton.x = -50; boostButton.y = -50; LK.gui.bottomRight.addChild(boostButton); function updateGearDisplay() { // Update the manual gear display var displayGear = player.gear; if (displayGear > 5) displayGear = 5; // Cap at gear 5 if (player.speed < 0.5) { // Show 'R' for reverse when speed is very low (simulating reverse) gearDisplayText.setText('R'); gearDisplayText.fill = '#ff0000'; // Red for reverse } else { gearDisplayText.setText(displayGear.toString()); // Color coding for different gears if (displayGear === 1) { gearDisplayText.fill = '#00ff00'; // Green for 1st gear } else if (displayGear === 2) { gearDisplayText.fill = '#ffff00'; // Yellow for 2nd gear } else if (displayGear === 3) { gearDisplayText.fill = '#ff8800'; // Orange for 3rd gear } else if (displayGear === 4) { gearDisplayText.fill = '#ff4400'; // Red-orange for 4th gear } else { gearDisplayText.fill = '#ff0000'; // Red for 5th gear } } } // Lane-based tap control variables var leftTapZone = 2048 / 3; var rightTapZone = 2048 * 2 / 3; // Lane-based tap control system game.down = function (x, y, obj) { // Check if tap is on brake button (bottom left quarter) if (x < 2048 / 4 && y > 2732 - 300) { player.brake(); // Flash brake button for visual feedback LK.effects.flashObject(brakeButton, 0xffffff, 200); return; } // Check if tap is on boost button (bottom right quarter) if (x > 2048 * 3 / 4 && y > 2732 - 300) { player.boost(); // Flash boost button for visual feedback LK.effects.flashObject(boostButton, 0xffffff, 200); return; } // Determine if tap is on left or right side of screen for lane changes if (x < leftTapZone) { // Left side tap - move left player.switchLane('left'); } else if (x > rightTapZone) { // Right side tap - move right player.switchLane('right'); } }; // Release brake on touch up game.up = function (x, y, obj) { // Always release brake on touch up for better control if (player.isBraking) { player.releaseBrake(); } }; // Spawn traffic vehicles function spawnTrafficVehicle() { // Calculate progress factor for enemy density scaling var progressFactor = Math.min(distanceTraveled / 3000, 1); // Scale over 3000m var enemyDensity = 0.1 + progressFactor * 0.4; // Start at 10%, scale to 50% var neutralDensity = 0.9 - progressFactor * 0.4; // Start at 90%, scale to 50% // Determine if spawning enemy or neutral vehicle var isEnemyVehicle = Math.random() < enemyDensity; var vehicleWeights; if (isEnemyVehicle) { // Enemy vehicles - aggressive and dangerous vehicleWeights = [{ type: 'enemyCar', weight: 35 }, // Aggressive sedan { type: 'van', weight: 15 }, // Medium threat { type: 'motorcycle', weight: 10 }, // Fast and dangerous { type: 'truck', weight: 15 }, // Large threat { type: 'bus', weight: 10 }, // Large threat { type: 'policeCar', weight: 10 }, // Special threat { type: 'fireTruck', weight: 5 } // Special threat ]; } else { // Neutral JustCar traffic - normal commuter vehicles vehicleWeights = [{ type: 'justcar', weight: 20 }, { type: 'justcar2', weight: 20 }, { type: 'justcar3', weight: 20 }, { type: 'justcar4', weight: 15 }, { type: 'justcar5', weight: 5 }, { type: 'justcar6', weight: 5 }, { type: 'justcar7', weight: 5 }]; } var totalWeight = vehicleWeights.reduce(function (sum, item) { return sum + item.weight; }, 0); var randomWeight = Math.random() * totalWeight; var currentWeight = 0; var vehicleType = isEnemyVehicle ? 'enemyCar' : 'justcar'; // fallback for (var i = 0; i < vehicleWeights.length; i++) { currentWeight += vehicleWeights[i].weight; if (randomWeight <= currentWeight) { vehicleType = vehicleWeights[i].type; break; } } var vehicle = new TrafficVehicle(vehicleType); vehicle.x = getLaneX(vehicle.currentLane); vehicle.y = -100; vehicle.speed += gameSpeed; // Adjust speed based on vehicle type - neutral cars drive more normally if (!isEnemyVehicle) { vehicle.speed *= 0.8; // Neutral cars drive 20% slower } // Check lane availability and spacing var laneAvailability = [true, true, true]; // Track which lanes are available var minSpacing = 250; // Minimum car-width gap for safe passing var spawnZone = 500; // Check vehicles within this distance from spawn point // Check all existing vehicles to determine lane availability for (var i = 0; i < trafficVehicles.length; i++) { var existingVehicle = trafficVehicles[i]; // Only consider vehicles in the spawn zone if (existingVehicle.y < spawnZone && existingVehicle.y > -200) { // Determine which lane this vehicle is in var vehicleLane = -1; for (var l = 0; l < 3; l++) { if (Math.abs(existingVehicle.x - getLaneX(l)) < 100) { vehicleLane = l; break; } } // Mark this lane as unavailable if vehicle is too close if (vehicleLane >= 0) { laneAvailability[vehicleLane] = false; } } } // Count available lanes var availableLanes = []; for (var l = 0; l < 3; l++) { if (laneAvailability[l]) { availableLanes.push(l); } } // Never block all lanes - ensure at least one lane is always free if (availableLanes.length === 0) { // If all lanes would be blocked, don't spawn vehicle.destroy(); return; } // Randomly select from available lanes with slight preference for center var selectedLane; if (availableLanes.length === 1) { selectedLane = availableLanes[0]; } else { // Add slight randomization for natural feel if (Math.random() < 0.7) { // 70% chance to use a random available lane selectedLane = availableLanes[Math.floor(Math.random() * availableLanes.length)]; } else { // 30% chance to prefer center lane if available if (availableLanes.indexOf(1) !== -1) { selectedLane = 1; } else { selectedLane = availableLanes[Math.floor(Math.random() * availableLanes.length)]; } } } // Update vehicle position to selected lane vehicle.currentLane = selectedLane; vehicle.x = getLaneX(selectedLane); vehicle.targetX = vehicle.x; // Double-check spacing in the selected lane var finalSpacingCheck = true; for (var i = 0; i < trafficVehicles.length; i++) { var existingVehicle = trafficVehicles[i]; if (Math.abs(existingVehicle.x - vehicle.x) < 120 && Math.abs(existingVehicle.y - vehicle.y) < minSpacing) { finalSpacingCheck = false; break; } } // Only spawn if final spacing check passes if (finalSpacingCheck) { trafficVehicles.push(vehicle); game.addChild(vehicle); } else { // Don't spawn, maintain spacing vehicle.destroy(); } } // Spawn power-ups function spawnPowerUp() { var powerUpTypes = ['fuelPowerUp', 'nitroPowerUp', 'tuningPowerUp', 'elixirPowerUp']; var powerUpType = powerUpTypes[Math.floor(Math.random() * powerUpTypes.length)]; var powerUp = new PowerUp(powerUpType); powerUp.x = getLaneX(Math.floor(Math.random() * 3)); powerUp.y = -50; powerUps.push(powerUp); game.addChild(powerUp); } // Spawn road elements function spawnRoadElement() { // Weighted element spawning - heavily favor trees over signs var elementWeights = [{ type: 'tree', weight: 40 }, // Trees are common { type: 'treeshort', weight: 45 }, // Short trees are most common { type: 'roadSign', weight: 15 } // Signs are less common ]; var totalWeight = elementWeights.reduce(function (sum, item) { return sum + item.weight; }, 0); var randomWeight = Math.random() * totalWeight; var currentWeight = 0; var elementType = 'tree'; // fallback to tree for (var i = 0; i < elementWeights.length; i++) { currentWeight += elementWeights[i].weight; if (randomWeight <= currentWeight) { elementType = elementWeights[i].type; break; } } var element = new RoadElement(elementType); // Place on road shoulders with proper spacing // For treeshort, place closer to road for better visibility if (elementType === 'treeshort') { element.x = Math.random() < 0.5 ? 2048 / 2 - 800 : 2048 / 2 + 800; // Closer to road } else { element.x = Math.random() < 0.5 ? 2048 / 2 - 900 : 2048 / 2 + 900; // Roadside shoulders } element.y = -50; roadElements.push(element); game.addChild(element); } // Spawn pedestrians function spawnPedestrian() { var pedestrian = new RoadElement('pedestrian'); // Place pedestrians on far roadside (sidewalk areas) pedestrian.x = Math.random() < 0.5 ? 2048 / 2 - 1000 : 2048 / 2 + 1000; pedestrian.y = -30; pedestrian.speed = 1 + Math.random() * 2; // Slower than vehicles roadElements.push(pedestrian); game.addChild(pedestrian); } // Check for level progression function checkLevelProgression() { var newLevel = currentLevel; // Check if total score qualifies for higher level for (var i = levelRequirements.length - 1; i >= 0; i--) { if (totalScore >= levelRequirements[i]) { newLevel = i + 1; break; } } // Level up if qualified if (newLevel > currentLevel) { currentLevel = newLevel; storage.currentLevel = currentLevel; // Update level display levelText.setText('Level: ' + currentLevel); // Apply level scaling to player applyLevelScaling(); // Visual feedback for level up LK.effects.flashObject(levelText, 0x00ff00, 1000); LK.effects.flashScreen(0x00ff00, 500); } } // Apply level-based scaling to player performance function applyLevelScaling() { var scaling = levelScaling[currentLevel] || levelScaling[6]; // Apply speed boost to max speed player.maxSpeed = 15 * scaling.speedBoost; // Apply turn boost by modifying lane change speed // This will be used in the switchLane method player.turnBoost = scaling.turnBoost; } // Award points for passing vehicle function awardVehiclePoints(vehicleType) { var points = vehicleScores[vehicleType] || 5; sessionScore += points; totalScore += points; // Update storage storage.totalScore = totalScore; // Update displays totalScoreText.setText('Total: ' + totalScore); LK.setScore(sessionScore); // Check for level progression checkLevelProgression(); } // Spawn road lines function spawnRoadLine() { // Left lane divider (between left and center lanes) var leftLine = new RoadElement('roadLine'); leftLine.x = 2048 / 2 - 210; leftLine.y = -40; // Add subtle fade in animation leftLine.alpha = 0; tween(leftLine, { alpha: 1 }, { duration: 300 }); roadLines.push(leftLine); game.addChild(leftLine); // Right lane divider (between center and right lanes) var rightLine = new RoadElement('roadLine'); rightLine.x = 2048 / 2 + 210; rightLine.y = -40; // Add subtle fade in animation rightLine.alpha = 0; tween(rightLine, { alpha: 1 }, { duration: 300 }); roadLines.push(rightLine); game.addChild(rightLine); } // Handle power-up collection function collectPowerUp(powerUp) { // Add visual collection effect LK.effects.flashObject(powerUp, 0x00ff00, 300); tween(powerUp, { scaleX: 1.5, scaleY: 1.5, alpha: 0 }, { duration: 300 }); switch (powerUp.powerUpType) { case 'fuelPowerUp': // Fuel extends game time or boosts score LK.setScore(LK.getScore() + 50); // Flash player car green for fuel LK.effects.flashObject(player, 0x00ff00, 500); break; case 'nitroPowerUp': player.nitroBoost = 180; // 3 seconds of boost // Flash player car blue for nitro LK.effects.flashObject(player, 0x0099ff, 500); break; case 'tuningPowerUp': player.tuningLevel = Math.min(player.tuningLevel + 0.1, 2.0); // Flash player car purple for tuning LK.effects.flashObject(player, 0x9b59b6, 500); break; case 'elixirPowerUp': player.shield = true; player.originalSpeed = player.speed; // Flash player car golden for elixir shield LK.effects.flashObject(player, 0xffd700, 500); break; } LK.getSound('powerUpCollect').play(); } // Main game loop game.update = function () { // Update distance traveled distanceTraveled += player.speed * 0.1; // Update speed display var speedKmh = Math.floor(player.speed * 10); speedText.setText('Speed: ' + speedKmh + ' KM/H'); scoreText.setText('Distance: ' + Math.floor(distanceTraveled) + 'm'); // Increase game difficulty over time gameSpeed = 1 + distanceTraveled / 1000; // Spawn traffic vehicles with progressive density and natural randomization spawnTimer++; // Start with very low traffic density (240 frames = 4 seconds at 60fps) // Gradually increase frequency as player progresses var baseSpawnDelay = 240; // 4 seconds initially var minSpawnDelay = 45; // Minimum 0.75 seconds between spawns var progressFactor = Math.min(distanceTraveled / 2000, 1); // Normalize progress over 2000m var currentSpawnDelay = baseSpawnDelay - (baseSpawnDelay - minSpawnDelay) * progressFactor; // Add randomization to make spawning feel more natural (±30% variation) var randomVariation = 0.7 + Math.random() * 0.6; // 0.7 to 1.3 multiplier var randomizedSpawnDelay = Math.floor(currentSpawnDelay * randomVariation); if (spawnTimer > randomizedSpawnDelay) { spawnTrafficVehicle(); spawnTimer = 0; } // Spawn power-ups powerUpSpawnTimer++; if (powerUpSpawnTimer > 300 + Math.random() * 300) { spawnPowerUp(); powerUpSpawnTimer = 0; } // Spawn road elements environmentSpawnTimer++; if (environmentSpawnTimer > 60 + Math.random() * 40) { spawnRoadElement(); environmentSpawnTimer = 0; } // Spawn road lines roadLineSpawnTimer++; if (roadLineSpawnTimer > 40) { spawnRoadLine(); roadLineSpawnTimer = 0; } // Spawn pedestrians occasionally pedestrianSpawnTimer++; if (pedestrianSpawnTimer > 200 + Math.random() * 400) { spawnPedestrian(); pedestrianSpawnTimer = 0; } // Update and clean up traffic vehicles for (var i = trafficVehicles.length - 1; i >= 0; i--) { var vehicle = trafficVehicles[i]; // Check collision with player if (vehicle.intersects(player)) { if (player.shield) { // Shield active - move player back to checkpoint player.x = player.checkpointPosition.x; player.y = player.checkpointPosition.y; player.currentLane = 1; // Reset to center lane // Reduce speed by half player.speed = player.speed * 0.5; // Remove shield player.shield = false; // Visual feedback for shield activation LK.effects.flashObject(player, 0x00ffff, 800); LK.effects.flashScreen(0x00ffff, 500); // Start timer to restore original speed after 5 seconds LK.setTimeout(function () { if (player.originalSpeed > 0) { tween(player, { speed: player.originalSpeed }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { player.originalSpeed = 0; } }); } }, 5000); } else { // No shield - normal crash // Play crash sound immediately LK.getSound('crush1').play(); // Enhanced crash effect with multiple flashes LK.effects.flashScreen(0xff0000, 1000); LK.effects.flashObject(player, 0xff0000, 800); LK.effects.flashObject(vehicle, 0xff0000, 800); // Screen shake effect tween(game, { x: 10 }, { duration: 50, onFinish: function onFinish() { tween(game, { x: -10 }, { duration: 50, onFinish: function onFinish() { tween(game, { x: 5 }, { duration: 50, onFinish: function onFinish() { tween(game, { x: -5 }, { duration: 50, onFinish: function onFinish() { tween(game, { x: 0 }, { duration: 50 }); } }); } }); } }); } }); LK.showGameOver(); return; } } // Check collisions between traffic vehicles for (var j = trafficVehicles.length - 1; j >= 0; j--) { if (i !== j) { var otherVehicle = trafficVehicles[j]; if (vehicle.intersects(otherVehicle)) { // Create enhanced crash effect LK.effects.flashObject(vehicle, 0xff0000, 500); LK.effects.flashObject(otherVehicle, 0xff0000, 500); // Add explosion-like scaling effect tween(vehicle, { scaleX: 1.3, scaleY: 1.3 }, { duration: 100, onFinish: function onFinish() { tween(vehicle, { scaleX: 1, scaleY: 1 }, { duration: 100 }); } }); tween(otherVehicle, { scaleX: 1.3, scaleY: 1.3 }, { duration: 100, onFinish: function onFinish() { tween(otherVehicle, { scaleX: 1, scaleY: 1 }, { duration: 100 }); } }); LK.getSound('carCrash').play(); // Remove both vehicles involved in crash vehicle.destroy(); otherVehicle.destroy(); trafficVehicles.splice(i, 1); // Adjust index for removed vehicle var otherIndex = trafficVehicles.indexOf(otherVehicle); if (otherIndex > -1) { trafficVehicles.splice(otherIndex, 1); if (otherIndex < i) { i--; } // Adjust current index if needed } break; // Exit inner loop since vehicle is destroyed } } } // Remove off-screen vehicles if (vehicle.y > 2732 + 100) { // Award points based on vehicle type awardVehiclePoints(vehicle.vehicleType); vehicle.destroy(); trafficVehicles.splice(i, 1); } } // Update and clean up power-ups for (var i = powerUps.length - 1; i >= 0; i--) { var powerUp = powerUps[i]; // Check collection if (powerUp.intersects(player)) { collectPowerUp(powerUp); powerUp.destroy(); powerUps.splice(i, 1); continue; } // Remove off-screen power-ups if (powerUp.y > 2732 + 50) { powerUp.destroy(); powerUps.splice(i, 1); } } // Update and clean up road elements for (var i = roadElements.length - 1; i >= 0; i--) { var element = roadElements[i]; // Add speed-based movement for visual effect element.speed = 5 + gameSpeed * 2; // Add subtle sway animation to trees if (element.elementType === 'tree') { element.x += Math.sin(LK.ticks * 0.02 + element.y * 0.01) * 0.3; } if (element.y > 2732 + 100) { element.destroy(); roadElements.splice(i, 1); } } // Update and clean up road lines for (var i = roadLines.length - 1; i >= 0; i--) { var line = roadLines[i]; if (line.y > 2732 + 100) { line.destroy(); roadLines.splice(i, 1); } } // Update final score (sessionScore is already set via LK.setScore in awardVehiclePoints) // Distance is shown separately in scoreText }; // Play background music LK.playMusic('LachinRoad');
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var PlayerCar = Container.expand(function () {
var self = Container.call(this);
var carGraphics = self.attachAsset('playerCar', {
anchorX: 0.5,
anchorY: 0.5
});
self.currentLane = 1; // 0 = left, 1 = center, 2 = right
self.targetX = 0;
self.speed = 0; // Start at zero speed
self.baseSpeed = 0; // Base speed that increases with progress
self.maxSpeed = 15;
self.gear = 1;
self.nitroBoost = 0;
self.tuningLevel = 1;
self.shield = false;
self.originalSpeed = 0;
self.checkpointPosition = {
x: getLaneX(1),
y: 2732 - 300
};
self.isBraking = false;
self.isBoosting = false;
self.boostTimer = 0;
self.boostMultiplier = 2.5;
self.boostDuration = 120; // 2 seconds at 60fps
self.update = function () {
// Handle brake and boost states
if (self.isBraking) {
self.speed = 0;
LK.effects.flashObject(carGraphics, 0xff0000, 100);
} else if (self.isBoosting) {
// Calculate boosted speed based on current base speed
var baseSpeed = self.baseSpeed + self.gear * 1.5;
if (self.nitroBoost > 0) {
baseSpeed *= 1.5;
self.nitroBoost--;
if (self.nitroBoost % 10 === 0) {
LK.effects.flashObject(carGraphics, 0x00ccff, 200);
}
}
self.speed = Math.min(baseSpeed * self.tuningLevel * self.boostMultiplier, self.maxSpeed * self.boostMultiplier);
self.boostTimer--;
if (self.boostTimer <= 0) {
self.isBoosting = false;
}
LK.effects.flashObject(carGraphics, 0x00ff00, 100);
} else {
// Normal speed calculation
// Gradually increase base speed with distance traveled
self.baseSpeed = Math.min(distanceTraveled * 0.008, 12); // Max base speed of 12
// Update speed based on gear and power-ups
var baseSpeed = self.baseSpeed + self.gear * 1.5;
if (self.nitroBoost > 0) {
baseSpeed *= 1.5;
self.nitroBoost--;
// Add nitro visual effect
if (self.nitroBoost % 10 === 0) {
LK.effects.flashObject(carGraphics, 0x00ccff, 200);
}
}
self.speed = Math.min(baseSpeed * self.tuningLevel, self.maxSpeed);
}
// Update gear based on speed with proper gear ranges
var newGear = 1;
if (self.speed < 0.5) {
newGear = 0; // Reverse gear when nearly stopped
} else if (self.speed < 2) {
newGear = 1;
} else if (self.speed < 5) {
newGear = 2;
} else if (self.speed < 8) {
newGear = 3;
} else if (self.speed < 12) {
newGear = 4;
} else {
newGear = 5;
}
if (newGear !== self.gear) {
self.gear = newGear;
updateGearDisplay();
// Flash for gear change
LK.effects.flashObject(carGraphics, 0xffff00, 300);
}
};
self.switchLane = function (direction) {
var newLane = self.currentLane;
if (direction === 'left' && self.currentLane > 0) {
newLane = self.currentLane - 1;
} else if (direction === 'right' && self.currentLane < 2) {
newLane = self.currentLane + 1;
}
// Only move if lane change is valid
if (newLane !== self.currentLane) {
self.currentLane = newLane;
var newTargetX = getLaneX(self.currentLane);
// Stop any existing tween
tween.stop(self, {
x: true
});
// Smooth tween to new lane position with level-based turn boost
var turnDuration = 300 / (self.turnBoost || 1.0);
tween(self, {
x: newTargetX
}, {
duration: turnDuration,
easing: tween.easeOut
});
// Add steering tilt animation
var tiltDirection = direction === 'left' ? -0.2 : 0.2;
tween(carGraphics, {
rotation: tiltDirection
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(carGraphics, {
rotation: 0
}, {
duration: 150,
easing: tween.easeOut
});
}
});
}
};
self.brake = function () {
self.isBraking = true;
self.isBoosting = false;
};
self.releaseBrake = function () {
self.isBraking = false;
};
self.boost = function () {
if (!self.isBoosting) {
// Only boost if not already boosting
self.isBoosting = true;
self.isBraking = false;
self.boostTimer = self.boostDuration; // 2 seconds boost
// Add boost visual effect
LK.effects.flashObject(carGraphics, 0x00ff00, 200);
}
};
return self;
});
var PowerUp = Container.expand(function (powerUpType) {
var self = Container.call(this);
var powerUpGraphics = self.attachAsset(powerUpType, {
anchorX: 0.5,
anchorY: 0.5
});
self.powerUpType = powerUpType;
self.speed = 4;
self.bobOffset = Math.random() * Math.PI * 2;
self.bobSpeed = 0.1;
self.update = function () {
self.y += self.speed;
// Bobbing animation
self.bobOffset += self.bobSpeed;
powerUpGraphics.y = Math.sin(self.bobOffset) * 5;
};
return self;
});
var RoadElement = Container.expand(function (elementType) {
var self = Container.call(this);
var elementGraphics = self.attachAsset(elementType, {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 5;
self.update = function () {
self.y += self.speed;
};
return self;
});
var TrafficVehicle = Container.expand(function (vehicleType) {
var self = Container.call(this);
var vehicleGraphics = self.attachAsset(vehicleType, {
anchorX: 0.5,
anchorY: 0.5
});
// Vehicles now use their asset-defined proportions for realistic scaling
// Asset sizes are:
// - Cars (player, enemy, police): 160x280
// - JustCar variants: 145-160x265-280 (realistic car proportions)
// - Van: 140x260
// - Truck: 180x380
// - Bus: 180x420
// - Fire truck: 180x350
// - Motorcycle: 80x180 (realistic motorcycle proportions)
// - Pedestrian: 50x80 (realistic human proportions)
// All vehicles maintain proper proportional relationships
self.vehicleType = vehicleType;
self.speed = 3 + Math.random() * 4;
self.currentLane = Math.floor(Math.random() * 3);
self.targetX = getLaneX(self.currentLane);
self.laneChangeTimer = 0;
self.laneChangeDelay = 120 + Math.random() * 240;
self.update = function () {
// Move vehicle forward
self.y += self.speed;
// Smooth lane switching with steering animation
if (Math.abs(self.x - self.targetX) > 2) {
self.x += (self.targetX - self.x) * 0.1;
// Add subtle steering rotation
var steerDirection = self.targetX - self.x;
vehicleGraphics.rotation = Math.max(-0.08, Math.min(0.08, steerDirection * 0.0005));
} else {
// Return to straight position
vehicleGraphics.rotation *= 0.95;
}
// Add subtle engine vibration for larger vehicles
if (self.vehicleType === 'truck' || self.vehicleType === 'bus') {
vehicleGraphics.x = Math.sin(LK.ticks * 0.1) * 0.5;
}
// Lane changing behavior based on vehicle type
self.laneChangeTimer++;
if (self.laneChangeTimer > self.laneChangeDelay) {
// Determine if this is an enemy vehicle
var isEnemyVehicle = self.vehicleType === 'enemyCar' || self.vehicleType === 'van' || self.vehicleType === 'motorcycle' || self.vehicleType === 'truck' || self.vehicleType === 'bus' || self.vehicleType === 'policeCar' || self.vehicleType === 'fireTruck';
// Emergency vehicles can change lanes more aggressively
var isEmergencyVehicle = self.vehicleType === 'policeCar' || self.vehicleType === 'fireTruck';
if (isEnemyVehicle) {
// Enemy vehicles - aggressive behavior, may drive toward player
if (Math.random() < 0.08) {
// Higher chance of lane change
// Sometimes target the player's lane aggressively
if (Math.random() < 0.3 && player) {
self.currentLane = player.currentLane;
self.targetX = getLaneX(self.currentLane);
self.laneChangeTimer = 0;
self.laneChangeDelay = 60 + Math.random() * 120; // Shorter delay for enemies
} else {
// Random aggressive lane change
var newLane = Math.floor(Math.random() * 3);
if (newLane !== self.currentLane) {
self.currentLane = newLane;
self.targetX = getLaneX(self.currentLane);
self.laneChangeTimer = 0;
self.laneChangeDelay = 60 + Math.random() * 120;
}
}
}
} else {
// Neutral vehicles - cautious lane changing
if (Math.random() < 0.015) {
// Lower chance of lane change
// Only change one lane at a time
var possibleLanes = [];
if (self.currentLane > 0) possibleLanes.push(self.currentLane - 1);
if (self.currentLane < 2) possibleLanes.push(self.currentLane + 1);
if (possibleLanes.length > 0) {
var targetLane = possibleLanes[Math.floor(Math.random() * possibleLanes.length)];
// Check if target lane is clear before changing
var laneIsClear = true;
var targetLaneX = getLaneX(targetLane);
for (var v = 0; v < trafficVehicles.length; v++) {
var otherVehicle = trafficVehicles[v];
if (otherVehicle !== self && Math.abs(otherVehicle.x - targetLaneX) < 100) {
// Check if there's a vehicle in the target lane within safety distance
var distanceToOther = Math.abs(otherVehicle.y - self.y);
if (distanceToOther < 200) {
// Safety distance
laneIsClear = false;
break;
}
}
}
// Only change lanes if target lane is clear
if (laneIsClear) {
self.currentLane = targetLane;
self.targetX = getLaneX(self.currentLane);
self.laneChangeTimer = 0;
self.laneChangeDelay = 180 + Math.random() * 300; // Longer delay for cautious driving
}
}
}
}
// Special behavior for emergency vehicles
if (isEmergencyVehicle && Math.random() < 0.1) {
// Emergency vehicles can change lanes rapidly with sirens
if (Math.random() < 0.5) {
// 50% chance to activate siren during lane change
LK.getSound('siren').play();
// Flash emergency lights
LK.effects.flashObject(vehicleGraphics, 0xff0000, 300);
}
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2c3e50
});
/****
* Game Code
****/
// Game variables
var player;
var trafficVehicles = [];
var powerUps = [];
var roadElements = [];
var roadLines = [];
var gameSpeed = 1;
var distanceTraveled = 0;
var spawnTimer = 0;
var powerUpSpawnTimer = 0;
var environmentSpawnTimer = 0;
var roadLineSpawnTimer = 0;
var pedestrianSpawnTimer = 0;
// Level system variables
var currentLevel = storage.currentLevel || 1;
var totalScore = storage.totalScore || 0;
var sessionScore = 0;
// Vehicle scoring values
var vehicleScores = {
'justcar': 5,
'justcar2': 5,
'justcar3': 5,
'justcar4': 5,
'justcar5': 5,
'justcar6': 5,
'justcar7': 5,
'van': 8,
'truck': 12,
'policeCar': 15,
'Health': 18,
// Ambulance
'fireTruck': 20,
'motorcycle': 5,
'bus': 8,
'enemyCar': 5
};
// Level unlock requirements
var levelRequirements = [0, 200, 500, 900, 1400, 2000];
// Level scaling factors
var levelScaling = {
1: {
speedBoost: 1.0,
turnBoost: 1.0
},
2: {
speedBoost: 1.1,
turnBoost: 1.1
},
3: {
speedBoost: 1.2,
turnBoost: 1.2
},
4: {
speedBoost: 1.3,
turnBoost: 1.3
},
5: {
speedBoost: 1.4,
turnBoost: 1.4
},
6: {
speedBoost: 1.5,
turnBoost: 1.5
}
};
// Lane positions - realistic 3.5m wide lanes (scaled to game coordinates)
var lanePositions = [2048 / 2 - 420,
// Left lane
2048 / 2,
// Center lane
2048 / 2 + 420 // Right lane
];
function getLaneX(lane) {
return lanePositions[lane];
}
// Create road background with realistic asphalt appearance
var roadBackground = LK.getAsset('roadLane', {
anchorX: 0.5,
anchorY: 0.5,
scaleY: 30
});
roadBackground.x = 2048 / 2;
roadBackground.y = 2732 / 2;
game.addChild(roadBackground);
// Create road shoulders
var leftShoulder = LK.getAsset('roadShoulder', {
anchorX: 0.5,
anchorY: 0.5,
scaleY: 30
});
leftShoulder.x = 2048 / 2 - 840;
leftShoulder.y = 2732 / 2;
game.addChild(leftShoulder);
var rightShoulder = LK.getAsset('roadShoulder', {
anchorX: 0.5,
anchorY: 0.5,
scaleY: 30
});
rightShoulder.x = 2048 / 2 + 840;
rightShoulder.y = 2732 / 2;
game.addChild(rightShoulder);
// Create player car
player = new PlayerCar();
player.x = getLaneX(1);
player.y = 2732 - 300;
game.addChild(player);
// Apply initial level scaling
applyLevelScaling();
// UI Elements
var speedText = new Text2('Speed: 0 KM/H', {
size: 60,
fill: '#ffffff'
});
speedText.anchor.set(0, 0);
speedText.x = 200;
speedText.y = 50;
LK.gui.topLeft.addChild(speedText);
// Removed duplicate gear text display - keeping only the large gear number display
var scoreText = new Text2('Distance: 0m', {
size: 50,
fill: '#ffffff'
});
scoreText.anchor.set(0.5, 0);
scoreText.x = 0;
scoreText.y = 120;
LK.gui.top.addChild(scoreText);
// Manual gear display
var gearDisplayText = new Text2('1', {
size: 120,
fill: '#00ff00'
});
gearDisplayText.anchor.set(1, 0);
gearDisplayText.x = -50;
gearDisplayText.y = 120;
LK.gui.topRight.addChild(gearDisplayText);
// Gear indicator background
var gearIndicator = new Text2('GEAR', {
size: 40,
fill: '#888888'
});
gearIndicator.anchor.set(1, 0);
gearIndicator.x = -50;
gearIndicator.y = 90;
LK.gui.topRight.addChild(gearIndicator);
// Level display
var levelText = new Text2('Level: ' + currentLevel, {
size: 50,
fill: '#00ff00'
});
levelText.anchor.set(0, 0);
levelText.x = 200;
levelText.y = 120;
LK.gui.topLeft.addChild(levelText);
// Total score display
var totalScoreText = new Text2('Total: ' + totalScore, {
size: 45,
fill: '#ffff00'
});
totalScoreText.anchor.set(0, 0);
totalScoreText.x = 200;
totalScoreText.y = 180;
LK.gui.topLeft.addChild(totalScoreText);
// Create brake button
var brakeButton = new Text2('BRAKE', {
size: 80,
fill: '#ff0000'
});
brakeButton.anchor.set(0, 1);
brakeButton.x = 50;
brakeButton.y = -50;
LK.gui.bottomLeft.addChild(brakeButton);
// Create boost button
var boostButton = new Text2('BOOST', {
size: 80,
fill: '#00ff00'
});
boostButton.anchor.set(1, 1);
boostButton.x = -50;
boostButton.y = -50;
LK.gui.bottomRight.addChild(boostButton);
function updateGearDisplay() {
// Update the manual gear display
var displayGear = player.gear;
if (displayGear > 5) displayGear = 5; // Cap at gear 5
if (player.speed < 0.5) {
// Show 'R' for reverse when speed is very low (simulating reverse)
gearDisplayText.setText('R');
gearDisplayText.fill = '#ff0000'; // Red for reverse
} else {
gearDisplayText.setText(displayGear.toString());
// Color coding for different gears
if (displayGear === 1) {
gearDisplayText.fill = '#00ff00'; // Green for 1st gear
} else if (displayGear === 2) {
gearDisplayText.fill = '#ffff00'; // Yellow for 2nd gear
} else if (displayGear === 3) {
gearDisplayText.fill = '#ff8800'; // Orange for 3rd gear
} else if (displayGear === 4) {
gearDisplayText.fill = '#ff4400'; // Red-orange for 4th gear
} else {
gearDisplayText.fill = '#ff0000'; // Red for 5th gear
}
}
}
// Lane-based tap control variables
var leftTapZone = 2048 / 3;
var rightTapZone = 2048 * 2 / 3;
// Lane-based tap control system
game.down = function (x, y, obj) {
// Check if tap is on brake button (bottom left quarter)
if (x < 2048 / 4 && y > 2732 - 300) {
player.brake();
// Flash brake button for visual feedback
LK.effects.flashObject(brakeButton, 0xffffff, 200);
return;
}
// Check if tap is on boost button (bottom right quarter)
if (x > 2048 * 3 / 4 && y > 2732 - 300) {
player.boost();
// Flash boost button for visual feedback
LK.effects.flashObject(boostButton, 0xffffff, 200);
return;
}
// Determine if tap is on left or right side of screen for lane changes
if (x < leftTapZone) {
// Left side tap - move left
player.switchLane('left');
} else if (x > rightTapZone) {
// Right side tap - move right
player.switchLane('right');
}
};
// Release brake on touch up
game.up = function (x, y, obj) {
// Always release brake on touch up for better control
if (player.isBraking) {
player.releaseBrake();
}
};
// Spawn traffic vehicles
function spawnTrafficVehicle() {
// Calculate progress factor for enemy density scaling
var progressFactor = Math.min(distanceTraveled / 3000, 1); // Scale over 3000m
var enemyDensity = 0.1 + progressFactor * 0.4; // Start at 10%, scale to 50%
var neutralDensity = 0.9 - progressFactor * 0.4; // Start at 90%, scale to 50%
// Determine if spawning enemy or neutral vehicle
var isEnemyVehicle = Math.random() < enemyDensity;
var vehicleWeights;
if (isEnemyVehicle) {
// Enemy vehicles - aggressive and dangerous
vehicleWeights = [{
type: 'enemyCar',
weight: 35
},
// Aggressive sedan
{
type: 'van',
weight: 15
},
// Medium threat
{
type: 'motorcycle',
weight: 10
},
// Fast and dangerous
{
type: 'truck',
weight: 15
},
// Large threat
{
type: 'bus',
weight: 10
},
// Large threat
{
type: 'policeCar',
weight: 10
},
// Special threat
{
type: 'fireTruck',
weight: 5
} // Special threat
];
} else {
// Neutral JustCar traffic - normal commuter vehicles
vehicleWeights = [{
type: 'justcar',
weight: 20
}, {
type: 'justcar2',
weight: 20
}, {
type: 'justcar3',
weight: 20
}, {
type: 'justcar4',
weight: 15
}, {
type: 'justcar5',
weight: 5
}, {
type: 'justcar6',
weight: 5
}, {
type: 'justcar7',
weight: 5
}];
}
var totalWeight = vehicleWeights.reduce(function (sum, item) {
return sum + item.weight;
}, 0);
var randomWeight = Math.random() * totalWeight;
var currentWeight = 0;
var vehicleType = isEnemyVehicle ? 'enemyCar' : 'justcar'; // fallback
for (var i = 0; i < vehicleWeights.length; i++) {
currentWeight += vehicleWeights[i].weight;
if (randomWeight <= currentWeight) {
vehicleType = vehicleWeights[i].type;
break;
}
}
var vehicle = new TrafficVehicle(vehicleType);
vehicle.x = getLaneX(vehicle.currentLane);
vehicle.y = -100;
vehicle.speed += gameSpeed;
// Adjust speed based on vehicle type - neutral cars drive more normally
if (!isEnemyVehicle) {
vehicle.speed *= 0.8; // Neutral cars drive 20% slower
}
// Check lane availability and spacing
var laneAvailability = [true, true, true]; // Track which lanes are available
var minSpacing = 250; // Minimum car-width gap for safe passing
var spawnZone = 500; // Check vehicles within this distance from spawn point
// Check all existing vehicles to determine lane availability
for (var i = 0; i < trafficVehicles.length; i++) {
var existingVehicle = trafficVehicles[i];
// Only consider vehicles in the spawn zone
if (existingVehicle.y < spawnZone && existingVehicle.y > -200) {
// Determine which lane this vehicle is in
var vehicleLane = -1;
for (var l = 0; l < 3; l++) {
if (Math.abs(existingVehicle.x - getLaneX(l)) < 100) {
vehicleLane = l;
break;
}
}
// Mark this lane as unavailable if vehicle is too close
if (vehicleLane >= 0) {
laneAvailability[vehicleLane] = false;
}
}
}
// Count available lanes
var availableLanes = [];
for (var l = 0; l < 3; l++) {
if (laneAvailability[l]) {
availableLanes.push(l);
}
}
// Never block all lanes - ensure at least one lane is always free
if (availableLanes.length === 0) {
// If all lanes would be blocked, don't spawn
vehicle.destroy();
return;
}
// Randomly select from available lanes with slight preference for center
var selectedLane;
if (availableLanes.length === 1) {
selectedLane = availableLanes[0];
} else {
// Add slight randomization for natural feel
if (Math.random() < 0.7) {
// 70% chance to use a random available lane
selectedLane = availableLanes[Math.floor(Math.random() * availableLanes.length)];
} else {
// 30% chance to prefer center lane if available
if (availableLanes.indexOf(1) !== -1) {
selectedLane = 1;
} else {
selectedLane = availableLanes[Math.floor(Math.random() * availableLanes.length)];
}
}
}
// Update vehicle position to selected lane
vehicle.currentLane = selectedLane;
vehicle.x = getLaneX(selectedLane);
vehicle.targetX = vehicle.x;
// Double-check spacing in the selected lane
var finalSpacingCheck = true;
for (var i = 0; i < trafficVehicles.length; i++) {
var existingVehicle = trafficVehicles[i];
if (Math.abs(existingVehicle.x - vehicle.x) < 120 && Math.abs(existingVehicle.y - vehicle.y) < minSpacing) {
finalSpacingCheck = false;
break;
}
}
// Only spawn if final spacing check passes
if (finalSpacingCheck) {
trafficVehicles.push(vehicle);
game.addChild(vehicle);
} else {
// Don't spawn, maintain spacing
vehicle.destroy();
}
}
// Spawn power-ups
function spawnPowerUp() {
var powerUpTypes = ['fuelPowerUp', 'nitroPowerUp', 'tuningPowerUp', 'elixirPowerUp'];
var powerUpType = powerUpTypes[Math.floor(Math.random() * powerUpTypes.length)];
var powerUp = new PowerUp(powerUpType);
powerUp.x = getLaneX(Math.floor(Math.random() * 3));
powerUp.y = -50;
powerUps.push(powerUp);
game.addChild(powerUp);
}
// Spawn road elements
function spawnRoadElement() {
// Weighted element spawning - heavily favor trees over signs
var elementWeights = [{
type: 'tree',
weight: 40
},
// Trees are common
{
type: 'treeshort',
weight: 45
},
// Short trees are most common
{
type: 'roadSign',
weight: 15
} // Signs are less common
];
var totalWeight = elementWeights.reduce(function (sum, item) {
return sum + item.weight;
}, 0);
var randomWeight = Math.random() * totalWeight;
var currentWeight = 0;
var elementType = 'tree'; // fallback to tree
for (var i = 0; i < elementWeights.length; i++) {
currentWeight += elementWeights[i].weight;
if (randomWeight <= currentWeight) {
elementType = elementWeights[i].type;
break;
}
}
var element = new RoadElement(elementType);
// Place on road shoulders with proper spacing
// For treeshort, place closer to road for better visibility
if (elementType === 'treeshort') {
element.x = Math.random() < 0.5 ? 2048 / 2 - 800 : 2048 / 2 + 800; // Closer to road
} else {
element.x = Math.random() < 0.5 ? 2048 / 2 - 900 : 2048 / 2 + 900; // Roadside shoulders
}
element.y = -50;
roadElements.push(element);
game.addChild(element);
}
// Spawn pedestrians
function spawnPedestrian() {
var pedestrian = new RoadElement('pedestrian');
// Place pedestrians on far roadside (sidewalk areas)
pedestrian.x = Math.random() < 0.5 ? 2048 / 2 - 1000 : 2048 / 2 + 1000;
pedestrian.y = -30;
pedestrian.speed = 1 + Math.random() * 2; // Slower than vehicles
roadElements.push(pedestrian);
game.addChild(pedestrian);
}
// Check for level progression
function checkLevelProgression() {
var newLevel = currentLevel;
// Check if total score qualifies for higher level
for (var i = levelRequirements.length - 1; i >= 0; i--) {
if (totalScore >= levelRequirements[i]) {
newLevel = i + 1;
break;
}
}
// Level up if qualified
if (newLevel > currentLevel) {
currentLevel = newLevel;
storage.currentLevel = currentLevel;
// Update level display
levelText.setText('Level: ' + currentLevel);
// Apply level scaling to player
applyLevelScaling();
// Visual feedback for level up
LK.effects.flashObject(levelText, 0x00ff00, 1000);
LK.effects.flashScreen(0x00ff00, 500);
}
}
// Apply level-based scaling to player performance
function applyLevelScaling() {
var scaling = levelScaling[currentLevel] || levelScaling[6];
// Apply speed boost to max speed
player.maxSpeed = 15 * scaling.speedBoost;
// Apply turn boost by modifying lane change speed
// This will be used in the switchLane method
player.turnBoost = scaling.turnBoost;
}
// Award points for passing vehicle
function awardVehiclePoints(vehicleType) {
var points = vehicleScores[vehicleType] || 5;
sessionScore += points;
totalScore += points;
// Update storage
storage.totalScore = totalScore;
// Update displays
totalScoreText.setText('Total: ' + totalScore);
LK.setScore(sessionScore);
// Check for level progression
checkLevelProgression();
}
// Spawn road lines
function spawnRoadLine() {
// Left lane divider (between left and center lanes)
var leftLine = new RoadElement('roadLine');
leftLine.x = 2048 / 2 - 210;
leftLine.y = -40;
// Add subtle fade in animation
leftLine.alpha = 0;
tween(leftLine, {
alpha: 1
}, {
duration: 300
});
roadLines.push(leftLine);
game.addChild(leftLine);
// Right lane divider (between center and right lanes)
var rightLine = new RoadElement('roadLine');
rightLine.x = 2048 / 2 + 210;
rightLine.y = -40;
// Add subtle fade in animation
rightLine.alpha = 0;
tween(rightLine, {
alpha: 1
}, {
duration: 300
});
roadLines.push(rightLine);
game.addChild(rightLine);
}
// Handle power-up collection
function collectPowerUp(powerUp) {
// Add visual collection effect
LK.effects.flashObject(powerUp, 0x00ff00, 300);
tween(powerUp, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 300
});
switch (powerUp.powerUpType) {
case 'fuelPowerUp':
// Fuel extends game time or boosts score
LK.setScore(LK.getScore() + 50);
// Flash player car green for fuel
LK.effects.flashObject(player, 0x00ff00, 500);
break;
case 'nitroPowerUp':
player.nitroBoost = 180; // 3 seconds of boost
// Flash player car blue for nitro
LK.effects.flashObject(player, 0x0099ff, 500);
break;
case 'tuningPowerUp':
player.tuningLevel = Math.min(player.tuningLevel + 0.1, 2.0);
// Flash player car purple for tuning
LK.effects.flashObject(player, 0x9b59b6, 500);
break;
case 'elixirPowerUp':
player.shield = true;
player.originalSpeed = player.speed;
// Flash player car golden for elixir shield
LK.effects.flashObject(player, 0xffd700, 500);
break;
}
LK.getSound('powerUpCollect').play();
}
// Main game loop
game.update = function () {
// Update distance traveled
distanceTraveled += player.speed * 0.1;
// Update speed display
var speedKmh = Math.floor(player.speed * 10);
speedText.setText('Speed: ' + speedKmh + ' KM/H');
scoreText.setText('Distance: ' + Math.floor(distanceTraveled) + 'm');
// Increase game difficulty over time
gameSpeed = 1 + distanceTraveled / 1000;
// Spawn traffic vehicles with progressive density and natural randomization
spawnTimer++;
// Start with very low traffic density (240 frames = 4 seconds at 60fps)
// Gradually increase frequency as player progresses
var baseSpawnDelay = 240; // 4 seconds initially
var minSpawnDelay = 45; // Minimum 0.75 seconds between spawns
var progressFactor = Math.min(distanceTraveled / 2000, 1); // Normalize progress over 2000m
var currentSpawnDelay = baseSpawnDelay - (baseSpawnDelay - minSpawnDelay) * progressFactor;
// Add randomization to make spawning feel more natural (±30% variation)
var randomVariation = 0.7 + Math.random() * 0.6; // 0.7 to 1.3 multiplier
var randomizedSpawnDelay = Math.floor(currentSpawnDelay * randomVariation);
if (spawnTimer > randomizedSpawnDelay) {
spawnTrafficVehicle();
spawnTimer = 0;
}
// Spawn power-ups
powerUpSpawnTimer++;
if (powerUpSpawnTimer > 300 + Math.random() * 300) {
spawnPowerUp();
powerUpSpawnTimer = 0;
}
// Spawn road elements
environmentSpawnTimer++;
if (environmentSpawnTimer > 60 + Math.random() * 40) {
spawnRoadElement();
environmentSpawnTimer = 0;
}
// Spawn road lines
roadLineSpawnTimer++;
if (roadLineSpawnTimer > 40) {
spawnRoadLine();
roadLineSpawnTimer = 0;
}
// Spawn pedestrians occasionally
pedestrianSpawnTimer++;
if (pedestrianSpawnTimer > 200 + Math.random() * 400) {
spawnPedestrian();
pedestrianSpawnTimer = 0;
}
// Update and clean up traffic vehicles
for (var i = trafficVehicles.length - 1; i >= 0; i--) {
var vehicle = trafficVehicles[i];
// Check collision with player
if (vehicle.intersects(player)) {
if (player.shield) {
// Shield active - move player back to checkpoint
player.x = player.checkpointPosition.x;
player.y = player.checkpointPosition.y;
player.currentLane = 1; // Reset to center lane
// Reduce speed by half
player.speed = player.speed * 0.5;
// Remove shield
player.shield = false;
// Visual feedback for shield activation
LK.effects.flashObject(player, 0x00ffff, 800);
LK.effects.flashScreen(0x00ffff, 500);
// Start timer to restore original speed after 5 seconds
LK.setTimeout(function () {
if (player.originalSpeed > 0) {
tween(player, {
speed: player.originalSpeed
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
player.originalSpeed = 0;
}
});
}
}, 5000);
} else {
// No shield - normal crash
// Play crash sound immediately
LK.getSound('crush1').play();
// Enhanced crash effect with multiple flashes
LK.effects.flashScreen(0xff0000, 1000);
LK.effects.flashObject(player, 0xff0000, 800);
LK.effects.flashObject(vehicle, 0xff0000, 800);
// Screen shake effect
tween(game, {
x: 10
}, {
duration: 50,
onFinish: function onFinish() {
tween(game, {
x: -10
}, {
duration: 50,
onFinish: function onFinish() {
tween(game, {
x: 5
}, {
duration: 50,
onFinish: function onFinish() {
tween(game, {
x: -5
}, {
duration: 50,
onFinish: function onFinish() {
tween(game, {
x: 0
}, {
duration: 50
});
}
});
}
});
}
});
}
});
LK.showGameOver();
return;
}
}
// Check collisions between traffic vehicles
for (var j = trafficVehicles.length - 1; j >= 0; j--) {
if (i !== j) {
var otherVehicle = trafficVehicles[j];
if (vehicle.intersects(otherVehicle)) {
// Create enhanced crash effect
LK.effects.flashObject(vehicle, 0xff0000, 500);
LK.effects.flashObject(otherVehicle, 0xff0000, 500);
// Add explosion-like scaling effect
tween(vehicle, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 100,
onFinish: function onFinish() {
tween(vehicle, {
scaleX: 1,
scaleY: 1
}, {
duration: 100
});
}
});
tween(otherVehicle, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 100,
onFinish: function onFinish() {
tween(otherVehicle, {
scaleX: 1,
scaleY: 1
}, {
duration: 100
});
}
});
LK.getSound('carCrash').play();
// Remove both vehicles involved in crash
vehicle.destroy();
otherVehicle.destroy();
trafficVehicles.splice(i, 1);
// Adjust index for removed vehicle
var otherIndex = trafficVehicles.indexOf(otherVehicle);
if (otherIndex > -1) {
trafficVehicles.splice(otherIndex, 1);
if (otherIndex < i) {
i--;
} // Adjust current index if needed
}
break; // Exit inner loop since vehicle is destroyed
}
}
}
// Remove off-screen vehicles
if (vehicle.y > 2732 + 100) {
// Award points based on vehicle type
awardVehiclePoints(vehicle.vehicleType);
vehicle.destroy();
trafficVehicles.splice(i, 1);
}
}
// Update and clean up power-ups
for (var i = powerUps.length - 1; i >= 0; i--) {
var powerUp = powerUps[i];
// Check collection
if (powerUp.intersects(player)) {
collectPowerUp(powerUp);
powerUp.destroy();
powerUps.splice(i, 1);
continue;
}
// Remove off-screen power-ups
if (powerUp.y > 2732 + 50) {
powerUp.destroy();
powerUps.splice(i, 1);
}
}
// Update and clean up road elements
for (var i = roadElements.length - 1; i >= 0; i--) {
var element = roadElements[i];
// Add speed-based movement for visual effect
element.speed = 5 + gameSpeed * 2;
// Add subtle sway animation to trees
if (element.elementType === 'tree') {
element.x += Math.sin(LK.ticks * 0.02 + element.y * 0.01) * 0.3;
}
if (element.y > 2732 + 100) {
element.destroy();
roadElements.splice(i, 1);
}
}
// Update and clean up road lines
for (var i = roadLines.length - 1; i >= 0; i--) {
var line = roadLines[i];
if (line.y > 2732 + 100) {
line.destroy();
roadLines.splice(i, 1);
}
}
// Update final score (sessionScore is already set via LK.setScore in awardVehiclePoints)
// Distance is shown separately in scoreText
};
// Play background music
LK.playMusic('LachinRoad');
Tuning Material image. In-Game asset. 2d. High contrast. No shadows
A realistic 2D render of a Yellow Taxi viewed from a slight rear top angle, perfectly aligned and driving straight forward on a clean 3-lane asphalt road with dashed white lane markings. The car is centered, facing directly ahead with no turn, showing only the top and back clearly. No visible driver. Daylight scene. In-Game asset. 2d. High contrast. No shadows. In-Game asset. 2d. High contrast. No shadows. In-Game asset. 2d. High contrast. No shadows. In-Game asset. 2d. High contrast. No shadows
Draw a health elixir.. In-Game asset. 2d. High contrast. No shadows
A 2D top-down tree (a little bit short and weird) viewed from a slight rear top angle, positioned beside a road, showing the top and a bit of the back side. The tree is slightly angled to match the camera perspective, with visible foliage and trunk shape. Daylight, clean background, suitable for roadside environment in a driving game.. In-Game. In-Game asset. 2d. High contrast. No shadows
A realistic 2D render of a BMW M3 viewed from a slight rear top angle, perfectly aligned and driving straight forward on a clean 3-lane asphalt road with dashed white lane markings. The car is centered, facing directly ahead with no turn, showing only the top and back clearly. No visible driver. Daylight scene. In-Game asset. 2d. High contrast. No shadows. In-Game asset. 2d. High contrast. No shadows. In-Game asset. 2d. High contrast. No shadows. In-Game asset. 2d. High contrast. No shadows. In-Game asset. 2d. High contrast. No shadows