User prompt
Hala çalışmıyor
User prompt
Kaza sesi çalışmıyor
User prompt
Çarpma sesi için varlık ekle
User prompt
Polis arabaları için bir oyun müzigi ekle
User prompt
Oyundaki polis arabalarına kırmızı ve mavi ışıklar ekle ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Coin toplama sesi 7
User prompt
Yan kısımları yeşil renkli yap ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Oyunun yan taraflarında kalan yere ormanlık ekle ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Yol rengi degişmesin
User prompt
Yol rengini degiştirebilmem için varlık ekle
User prompt
3 şeritli bir yol ekle
User prompt
Zemin çizgisini sil
User prompt
Oyun varlıklarını büyült ↪💡 Consider importing and using the following plugins: @upit/tween.v1
Code edit (1 edits merged)
Please save this source code
User prompt
Endless Runner
Initial prompt
Sonsuz koşu
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Bullet = Container.expand(function () { var self = Container.call(this); var bulletGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.speed = -25; self.update = function () { self.y += self.speed; }; return self; }); var Coin = Container.expand(function () { var self = Container.call(this); var coinGraphics = self.attachAsset('coin', { anchorX: 0.5, anchorY: 0.5 }); self.speed = gameSpeed; self.collected = false; self.update = function () { self.y += self.speed; self.rotation += 0.1; }; return self; }); var DebrisParticle = Container.expand(function () { var self = Container.call(this); var debrisGraphics = self.attachAsset('debrisParticle', { anchorX: 0.5, anchorY: 0.5 }); self.speedX = (Math.random() - 0.5) * 20; // Random horizontal speed self.speedY = -5 - Math.random() * 15; // Upward initial speed self.gravity = 0.8; // Gravity acceleration self.lifetime = 90 + Math.random() * 60; // 1.5-2.5 seconds self.maxLifetime = self.lifetime; self.rotationSpeed = (Math.random() - 0.5) * 0.3; // Random colors for debris (metal, glass, etc.) var debrisColors = [0x666666, 0x888888, 0x444444, 0x999999, 0x333333]; debrisGraphics.tint = debrisColors[Math.floor(Math.random() * debrisColors.length)]; self.update = function () { // Apply physics self.x += self.speedX; self.y += self.speedY; self.speedY += self.gravity; // Apply gravity self.rotation += self.rotationSpeed; self.lifetime--; // Fade out over time var fadeProgress = 1 - self.lifetime / self.maxLifetime; self.alpha = Math.max(0, 1 - fadeProgress * 1.5); }; return self; }); var ExhaustParticle = Container.expand(function () { var self = Container.call(this); var particleGraphics = self.attachAsset('exhaustParticle', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 3 + Math.random() * 2; self.sideSpeed = (Math.random() - 0.5) * 2; self.lifetime = 60 + Math.random() * 30; self.maxLifetime = self.lifetime; self.update = function () { self.y += self.speed; self.x += self.sideSpeed; self.lifetime--; // Fade out and scale up over time var fadeProgress = 1 - self.lifetime / self.maxLifetime; self.alpha = Math.max(0, 1 - fadeProgress * 1.5); self.scaleX = 1 + fadeProgress * 0.8; self.scaleY = 1 + fadeProgress * 0.8; }; return self; }); var Flame = Container.expand(function () { var self = Container.call(this); var flameGraphics = self.attachAsset('flame', { anchorX: 0.5, anchorY: 1.0 }); self.speed = -2 - Math.random() * 3; self.sideSpeed = (Math.random() - 0.5) * 4; self.lifetime = 30 + Math.random() * 20; self.maxLifetime = self.lifetime; self.flickerTimer = 0; self.parentCar = null; // Reference to the police car this flame belongs to self.offsetX = 0; // Offset from car position self.offsetY = 0; // Offset from car position self.update = function () { // If attached to a police car, follow its position if (self.parentCar && !self.parentCar.isDestroyed) { self.x = self.parentCar.x + self.offsetX; self.y = self.parentCar.y + self.offsetY; } self.lifetime--; // Flicker effect self.flickerTimer++; if (self.flickerTimer % 3 === 0) { var colors = [0xff4500, 0xff6600, 0xff8800, 0xffaa00]; flameGraphics.tint = colors[Math.floor(Math.random() * colors.length)]; } // Fade and scale over time var fadeProgress = 1 - self.lifetime / self.maxLifetime; self.alpha = Math.max(0, 1 - fadeProgress * 1.2); self.scaleX = 1.5 + fadeProgress * 1.2; // Increased base size from 0.5 to 1.5 self.scaleY = 2.0 + fadeProgress * 2.0; // Increased base size from 0.8 to 2.0 }; return self; }); var Flower = Container.expand(function () { var self = Container.call(this); var flowerGraphics = self.attachAsset('flower', { anchorX: 0.5, anchorY: 1.0 }); self.speed = gameSpeed; self.update = function () { self.y += self.speed; }; return self; }); var HeartPowerUp = Container.expand(function () { var self = Container.call(this); var heartGraphics = self.attachAsset('heart', { anchorX: 0.5, anchorY: 0.5 }); self.speed = gameSpeed; self.collected = false; self.lifetime = 420; // 7 seconds at 60fps self.update = function () { self.y += self.speed; self.rotation += 0.1; // Pulsing effect self.scaleX = 1 + Math.sin(LK.ticks * 0.1) * 0.1; self.scaleY = 1 + Math.sin(LK.ticks * 0.1) * 0.1; // Decrease lifetime self.lifetime--; // Flash when about to disappear (last 3 seconds) if (self.lifetime <= 180 && self.lifetime > 0) { self.alpha = (Math.sin(self.lifetime * 0.3) + 1) * 0.5; } }; return self; }); var LaneLine = Container.expand(function () { var self = Container.call(this); var lineGraphics = self.attachAsset('laneLine', { anchorX: 0.5, anchorY: 0.5 }); // Make lane lines semi-transparent so they appear behind cars lineGraphics.alpha = 0.7; self.speed = gameSpeed; self.update = function () { self.y += self.speed; }; return self; }); var MovingBarrier = Container.expand(function () { var self = Container.call(this); var barrierGraphics = self.attachAsset('movingBarrier', { anchorX: 0.5, anchorY: 1.0 }); self.speed = gameSpeed; self.horizontalSpeed = 2; // Speed of horizontal movement self.direction = 1; // 1 for right, -1 for left self.lanes = [580, 1024, 1468]; // Reference to lane positions self.startLaneIndex = 1; // Start in middle lane by default self.minX = 580; // Will be set based on start lane self.maxX = 1468; // Will be set based on start lane self.changeDirectionTimer = 0; self.changeDirectionDelay = 120 + Math.random() * 180; // 2-5 seconds // Function to set movement boundaries for one lane change only self.setMovementBounds = function (startLaneIndex) { self.startLaneIndex = startLaneIndex; if (startLaneIndex === 0) { // Left lane - can only move to middle lane self.minX = self.lanes[0]; self.maxX = self.lanes[1]; } else if (startLaneIndex === 1) { // Middle lane - can move to left or right lane var canMoveLeft = Math.random() < 0.5; if (canMoveLeft) { self.minX = self.lanes[0]; self.maxX = self.lanes[1]; self.direction = -1; // Start moving left } else { self.minX = self.lanes[1]; self.maxX = self.lanes[2]; self.direction = 1; // Start moving right } } else { // Right lane - can only move to middle lane self.minX = self.lanes[1]; self.maxX = self.lanes[2]; } }; self.update = function () { self.y += self.speed; // Move horizontally self.x += self.horizontalSpeed * self.direction; // Bounce off lane boundaries with smooth animation if (self.x <= self.minX) { self.x = self.minX; self.direction = 1; // Change to move right // Add bounce animation tween(self, { scaleX: 2.6, rotation: 0.1 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { scaleX: 2.4, rotation: 0 }, { duration: 200, easing: tween.easeInOut }); } }); } else if (self.x >= self.maxX) { self.x = self.maxX; self.direction = -1; // Change to move left // Add bounce animation tween(self, { scaleX: 2.6, rotation: -0.1 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { scaleX: 2.4, rotation: 0 }, { duration: 200, easing: tween.easeInOut }); } }); } // Random direction changes for unpredictability self.changeDirectionTimer++; if (self.changeDirectionTimer >= self.changeDirectionDelay) { self.direction *= -1; // Reverse direction self.changeDirectionTimer = 0; self.changeDirectionDelay = 120 + Math.random() * 180; // New random delay // Add direction change animation tween(self, { scaleY: 2.6 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { scaleY: 2.4 }, { duration: 150, easing: tween.easeInOut }); } }); } }; return self; }); var Obstacle = Container.expand(function () { var self = Container.call(this); var obstacleGraphics = self.attachAsset('obstacle', { anchorX: 0.5, anchorY: 1.0 }); self.speed = gameSpeed; // Add police lights to the obstacle (police car) - properly attached to self container // Main roof lights - positioned on top of the police car var leftLight = self.attachAsset('policeLight', { anchorX: 0.5, anchorY: 0.5 }); leftLight.x = -20; leftLight.y = -130; var rightLight = self.attachAsset('policeLight', { anchorX: 0.5, anchorY: 0.5 }); rightLight.x = 20; rightLight.y = -130; rightLight.tint = 0x0000ff; // Make right light blue // Side mirror lights var leftSideLight = self.attachAsset('policeLight', { anchorX: 0.5, anchorY: 0.5 }); leftSideLight.x = -35; leftSideLight.y = -100; var rightSideLight = self.attachAsset('policeLight', { anchorX: 0.5, anchorY: 0.5 }); rightSideLight.x = 35; rightSideLight.y = -100; rightSideLight.tint = 0x0000ff; // Make right side light blue // Front bumper lights var topLeftLight = self.attachAsset('policeLight', { anchorX: 0.5, anchorY: 0.5 }); topLeftLight.x = -25; topLeftLight.y = -145; var topRightLight = self.attachAsset('policeLight', { anchorX: 0.5, anchorY: 0.5 }); topRightLight.x = 25; topRightLight.y = -145; topRightLight.tint = 0x0000ff; // Make top right light blue // Store light references for animation self.leftLight = leftLight; self.rightLight = rightLight; self.leftSideLight = leftSideLight; self.rightSideLight = rightSideLight; self.topLeftLight = topLeftLight; self.topRightLight = topRightLight; self.lightTimer = 0; self.isRedActive = true; // Lane changing properties self.canChangeLanes = false; self.laneChangeTimer = 0; self.laneChangeDelay = 180 + Math.random() * 240; // 3-7 seconds random delay self.targetLaneIndex = Math.floor(Math.random() * 3); // Random target lane self.isChangingLanes = false; self.originalLaneIndex = 0; // Will be set when spawned self.update = function () { self.y += self.speed; // Handle lane changing behavior - only at night if (self.canChangeLanes && !self.isChangingLanes && self.y > 200 && self.y < 2000 && !isDay) { self.laneChangeTimer++; if (self.laneChangeTimer >= self.laneChangeDelay) { // Start lane change self.isChangingLanes = true; var currentLaneIndex = self.originalLaneIndex; // Choose a different lane var availableLanes = []; for (var i = 0; i < 3; i++) { if (i !== currentLaneIndex) { availableLanes.push(i); } } if (availableLanes.length > 0) { var newLaneIndex = availableLanes[Math.floor(Math.random() * availableLanes.length)]; var targetX = player.lanes[newLaneIndex]; // Animate lane change using tween tween(self, { x: targetX }, { duration: 1000 + Math.random() * 1000, easing: tween.easeInOut, onFinish: function onFinish() { self.isChangingLanes = false; self.originalLaneIndex = newLaneIndex; // Set new delay for next potential lane change self.laneChangeTimer = 0; self.laneChangeDelay = 300 + Math.random() * 600; // 5-15 seconds } }); } } } // Police lights visible during day and night, more prominent at night var dayAlpha = isDay ? 0.6 : 1.0; // Visible during day, full at night var dayAlphaLow = isDay ? 0.3 : 0.5; // Reduced for inactive light but still visible // Animate police lights with enhanced tween effects self.lightTimer++; if (self.lightTimer >= 15) { // Change every 15 frames (0.25 seconds at 60fps) self.lightTimer = 0; self.isRedActive = !self.isRedActive; if (self.isRedActive) { // Red lights active - animate with pulse effect tween(self.leftLight, { alpha: dayAlpha, scaleX: 1.2, scaleY: 1.2 }, { duration: 100, easing: tween.easeOut }); tween(self.leftSideLight, { alpha: dayAlpha, scaleX: 1.2, scaleY: 1.2 }, { duration: 100, easing: tween.easeOut }); tween(self.topLeftLight, { alpha: dayAlpha, scaleX: 1.2, scaleY: 1.2 }, { duration: 100, easing: tween.easeOut }); // Blue lights dimmed tween(self.rightLight, { alpha: dayAlphaLow, scaleX: 1.0, scaleY: 1.0 }, { duration: 100, easing: tween.easeOut }); tween(self.rightSideLight, { alpha: dayAlphaLow, scaleX: 1.0, scaleY: 1.0 }, { duration: 100, easing: tween.easeOut }); tween(self.topRightLight, { alpha: dayAlphaLow, scaleX: 1.0, scaleY: 1.0 }, { duration: 100, easing: tween.easeOut }); } else { // Blue lights active - animate with pulse effect tween(self.rightLight, { alpha: dayAlpha, scaleX: 1.2, scaleY: 1.2 }, { duration: 100, easing: tween.easeOut }); tween(self.rightSideLight, { alpha: dayAlpha, scaleX: 1.2, scaleY: 1.2 }, { duration: 100, easing: tween.easeOut }); tween(self.topRightLight, { alpha: dayAlpha, scaleX: 1.2, scaleY: 1.2 }, { duration: 100, easing: tween.easeOut }); // Red lights dimmed tween(self.leftLight, { alpha: dayAlphaLow, scaleX: 1.0, scaleY: 1.0 }, { duration: 100, easing: tween.easeOut }); tween(self.leftSideLight, { alpha: dayAlphaLow, scaleX: 1.0, scaleY: 1.0 }, { duration: 100, easing: tween.easeOut }); tween(self.topLeftLight, { alpha: dayAlphaLow, scaleX: 1.0, scaleY: 1.0 }, { duration: 100, easing: tween.easeOut }); } } }; return self; }); var Player = Container.expand(function () { var self = Container.call(this); var playerGraphics = self.attachAsset('player', { anchorX: 0.5, anchorY: 1.0 }); // Add headlights to the player car - positioned at the front of the vehicle var leftHeadlight = self.attachAsset('headlight', { anchorX: 0.5, anchorY: 0.5 }); leftHeadlight.x = -25; leftHeadlight.y = -150; // Moved further forward to the front of the car leftHeadlight.alpha = 0; // Start invisible var rightHeadlight = self.attachAsset('headlight', { anchorX: 0.5, anchorY: 0.5 }); rightHeadlight.x = 25; rightHeadlight.y = -150; // Moved further forward to the front of the car rightHeadlight.alpha = 0; // Start invisible // Store headlight references self.leftHeadlight = leftHeadlight; self.rightHeadlight = rightHeadlight; // Move headlights to front of all elements self.moveHeadlightsToFront = function () { if (self.parent) { // Remove headlights from current position self.removeChild(leftHeadlight); self.removeChild(rightHeadlight); // Add them back to the front (last children render on top) self.addChild(leftHeadlight); self.addChild(rightHeadlight); } }; self.groundY = 2300; self.lanes = [580, 1024, 1468]; // Three lanes adjusted for wider road self.currentLane = 1; // Middle lane self.targetX = self.lanes[self.currentLane]; self.x = self.targetX; self.y = self.groundY; self.moveLeft = function () { if (self.currentLane > 0) { self.currentLane--; self.targetX = self.lanes[self.currentLane]; // Add shake effect when moving left tween(self, { rotation: -0.1 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { rotation: 0 }, { duration: 100, easing: tween.easeInOut }); } }); // Play car movement sound try { var carMoveSound = LK.getSound('carMove'); carMoveSound.volume = 0.005; carMoveSound.play(); } catch (e) { console.log('Car move sound error:', e); } } }; self.moveRight = function () { if (self.currentLane < 2) { self.currentLane++; self.targetX = self.lanes[self.currentLane]; // Add shake effect when moving right tween(self, { rotation: 0.1 }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { rotation: 0 }, { duration: 100, easing: tween.easeInOut }); } }); // Play car movement sound try { var carMoveSound = LK.getSound('carMove'); carMoveSound.volume = 0.005; carMoveSound.play(); } catch (e) { console.log('Car move sound error:', e); } } }; self.update = function () { // Handle lane switching var dx = self.targetX - self.x; if (Math.abs(dx) > 2) { self.x += dx * 0.25; } else { self.x = self.targetX; } // Ensure headlights are always in front self.moveHeadlightsToFront(); }; return self; }); var PoliceChaser = Container.expand(function () { var self = Container.call(this); var policeGraphics = self.attachAsset('policeChaser', { anchorX: 0.5, anchorY: 1.0 }); // Add police lights - same as regular obstacles var leftLight = self.attachAsset('policeLight', { anchorX: 0.5, anchorY: 0.5 }); leftLight.x = -20; leftLight.y = -130; var rightLight = self.attachAsset('policeLight', { anchorX: 0.5, anchorY: 0.5 }); rightLight.x = 20; rightLight.y = -130; rightLight.tint = 0x0000ff; // Store light references for animation self.leftLight = leftLight; self.rightLight = rightLight; self.lightTimer = 0; self.isRedActive = true; // Chase behavior properties self.followDistance = 500; // Distance to maintain behind player self.maxFollowDistance = 700; // Maximum distance to prevent getting too close self.minFollowDistance = 400; // Minimum distance to maintain self.speed = gameSpeed; self.targetX = 1024; // Center lane by default self.lastPlayerLane = 1; // Track player's last lane self.update = function () { // Calculate dynamic follow distance based on current distance to player var currentDistance = self.y - player.y; var targetDistance = self.followDistance; // If too close, increase follow distance if (currentDistance < self.minFollowDistance) { targetDistance = self.maxFollowDistance; } else if (currentDistance > self.maxFollowDistance) { targetDistance = self.minFollowDistance; } // Smoothly adjust position to maintain proper distance var distanceDiff = currentDistance - targetDistance; self.y = player.y + targetDistance + distanceDiff * 0.95; // Gradual adjustment // Follow player's lane with slight delay for realistic behavior var currentPlayerLane = player.currentLane; if (currentPlayerLane !== self.lastPlayerLane) { // Player changed lanes, start following self.targetX = player.lanes[currentPlayerLane]; self.lastPlayerLane = currentPlayerLane; } // Smooth lane following var dx = self.targetX - self.x; if (Math.abs(dx) > 5) { self.x += dx * 0.08; // Slower than player for realistic chase } else { self.x = self.targetX; } // Ensure police lights are always in front self.removeChild(self.leftLight); self.removeChild(self.rightLight); self.addChild(self.leftLight); self.addChild(self.rightLight); // Animate police lights self.lightTimer++; if (self.lightTimer >= 15) { self.lightTimer = 0; self.isRedActive = !self.isRedActive; var dayAlpha = isDay ? 0.8 : 1.0; var dayAlphaLow = isDay ? 0.4 : 0.6; if (self.isRedActive) { // Red light active tween(self.leftLight, { alpha: dayAlpha, scaleX: 1.2, scaleY: 1.2 }, { duration: 100, easing: tween.easeOut }); tween(self.rightLight, { alpha: dayAlphaLow, scaleX: 1.0, scaleY: 1.0 }, { duration: 100, easing: tween.easeOut }); } else { // Blue light active tween(self.rightLight, { alpha: dayAlpha, scaleX: 1.2, scaleY: 1.2 }, { duration: 100, easing: tween.easeOut }); tween(self.leftLight, { alpha: dayAlphaLow, scaleX: 1.0, scaleY: 1.0 }, { duration: 100, easing: tween.easeOut }); } } }; return self; }); var PowerUp = Container.expand(function () { var self = Container.call(this); var powerupGraphics = self.attachAsset('powerup', { anchorX: 0.5, anchorY: 0.5 }); self.speed = gameSpeed; self.collected = false; self.lifetime = 420; // 7 seconds at 60fps self.update = function () { self.y += self.speed; self.rotation += 0.15; // Decrease lifetime self.lifetime--; // Flash when about to disappear (last 3 seconds) if (self.lifetime <= 180 && self.lifetime > 0) { self.alpha = (Math.sin(self.lifetime * 0.3) + 1) * 0.5; } }; return self; }); var RainDrop = Container.expand(function () { var self = Container.call(this); var dropGraphics = self.attachAsset('rainDrop', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 15 + Math.random() * 10; // Rain falls fast self.sideSpeed = -2 + Math.random() * 4; // Slight horizontal movement self.alpha = 0.3 + Math.random() * 0.4; // Semi-transparent self.update = function () { self.y += self.speed; self.x += self.sideSpeed; }; return self; }); var Smoke = Container.expand(function () { var self = Container.call(this); var smokeGraphics = self.attachAsset('smoke', { anchorX: 0.5, anchorY: 1.0 }); self.speed = -1 - Math.random() * 2; self.sideSpeed = (Math.random() - 0.5) * 3; self.lifetime = 60 + Math.random() * 40; self.maxLifetime = self.lifetime; self.update = function () { self.y += self.speed; self.x += self.sideSpeed; self.lifetime--; // Fade and expand over time var fadeProgress = 1 - self.lifetime / self.maxLifetime; self.alpha = Math.max(0, 0.8 - fadeProgress * 1.2); self.scaleX = 0.3 + fadeProgress * 2; self.scaleY = 0.3 + fadeProgress * 2; }; return self; }); var Tree = Container.expand(function () { var self = Container.call(this); var treeGraphics = self.attachAsset('tree', { anchorX: 0.5, anchorY: 1.0 }); self.speed = gameSpeed; self.update = function () { self.y += self.speed; }; return self; }); var Tree2 = Container.expand(function () { var self = Container.call(this); var treeGraphics = self.attachAsset('tree2', { anchorX: 0.5, anchorY: 1.0 }); self.speed = gameSpeed; self.update = function () { self.y += self.speed; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x87CEEB }); /**** * Game Code ****/ var player; var obstacles = []; var coins = []; var powerups = []; var heartPowerups = []; var bullets = []; var movingBarriers = []; var hasWeapon = false; var weaponTimer = 0; var weaponDuration = 1200; // 20 seconds at 60fps var heartPowerupSpawnTimer = 0; var movingBarrierSpawnTimer = 0; var laneLines = []; var trees = []; var trees2 = []; var flowers = []; var policeChaser = null; var policeChaserActive = false; var policeChaserTimer = 0; var policeChaserActivationTime = 900; // 15 seconds at 60fps (15 * 60) var policeChaserChaseDuration = 0; // Will be set randomly when activated var policeChaserCooldownDuration = 0; // Will be set randomly when deactivated var policeChaserTimeWindow = 3600; // 1 minute window (60 seconds at 60fps) var policeChaserMaxChaseTime = 600; // 10 seconds maximum chase time per window (10 * 60fps) var policeChaserUsedTime = 0; // Time used in current window var policeChaserWindowTimer = 0; // Timer for the current 1-minute window var gameSpeed = 8; var maxSpeed = 20; var speedIncrement = 0.005; var distance = 0; var obstacleSpawnTimer = 0; var coinSpawnTimer = 0; var powerupSpawnTimer = 0; var laneLineSpawnTimer = 0; var treeSpawnTimer = 0; var lastSwipeX = 0; var lastSwipeY = 0; var swipeStarted = false; var policeMusicPlaying = false; var policeOnScreen = false; var lives = 3; var maxLives = 10; var exhaustParticles = []; var exhaustSpawnTimer = 0; var flames = []; var smokes = []; var debrisParticles = []; var rainDrops = []; var rainSpawnTimer = 0; // Rain cycle variables var isRaining = false; var rainTimer = 0; var rainDuration = 0; // Will be set randomly var rainCooldown = 0; // Will be set randomly var nextRainEvent = 0; // Timer for next rain event var rainSoundPlaying = false; var thunderTimer = 0; var nextThunderTime = 0; // Day/night cycle variables var dayNightTimer = 0; var dayNightCycleDuration = 3600; // 60 seconds (1 minute) at 60fps var isDay = true; var isTransitioning = false; var cycleCount = 0; // Track completed day/night cycles var cycleSpeedBoost = 0; // Additional speed from completed cycles var speedBoostPerCycle = 2; // Speed increase per completed cycle // Game timer for police lane changing feature var gameTimer = 0; var laneChangeStartTime = 7200; // 2 minutes at 60fps (2 * 60 * 60) // Night speed multiplier var nightSpeedMultiplier = 1.25; // 25% faster during night var baseGameSpeed = 8; // Store the base game speed var dayColors = { sky: 0x87CEEB, // Light blue sky forest: 0x228b22, // Forest green road: 0xffffff // Normal road color }; var nightColors = { sky: 0x191970, // Midnight blue forest: 0x0f2f0f, // Dark forest green road: 0x404040 // Darker road color }; // Create road background - widened var roadBackground = game.addChild(LK.getAsset('roadBackground', { anchorX: 0.5, anchorY: 0.5 })); roadBackground.x = 1024; // Center of screen roadBackground.y = 1366; roadBackground.scaleX = 1.3; // Widen road by 30% // Create green forest backgrounds on sides - narrowed var leftForest = game.addChild(LK.getAsset('forestBackground', { anchorX: 0.5, anchorY: 0.5 })); leftForest.x = 190; // Moved closer to center - narrower area leftForest.y = 1366; leftForest.scaleX = 0.8; // Make forest area narrower var rightForest = game.addChild(LK.getAsset('forestBackground', { anchorX: 0.5, anchorY: 0.5 })); rightForest.x = 1858; // Moved closer to center - narrower area rightForest.y = 1366; rightForest.scaleX = 0.8; // Make forest area narrower // Create road borders - adjusted for wider road var leftBorder = game.addChild(LK.getAsset('roadBorder', { anchorX: 0.5, anchorY: 0.5 })); leftBorder.x = 390; // Moved outward for wider road leftBorder.y = 1366; var rightBorder = game.addChild(LK.getAsset('roadBorder', { anchorX: 0.5, anchorY: 0.5 })); rightBorder.x = 1658; // Moved outward for wider road rightBorder.y = 1366; // Spawn initial lane lines to establish proper layering spawnLaneLine(); // Create player player = game.addChild(new Player()); player.scaleX = 2.4; player.scaleY = 2.4; // Create police chaser that follows the player (initially hidden) policeChaser = game.addChild(new PoliceChaser()); policeChaser.scaleX = 2.2; // Slightly smaller than player policeChaser.scaleY = 2.2; policeChaser.x = player.x; // Start in same lane as player policeChaser.visible = false; // Start hidden policeChaser.alpha = 0; // Start transparent // Create score display var scoreText = new Text2('Score: 0', { size: 60, fill: 0xFFFFFF }); scoreText.anchor.set(0.5, 0); LK.gui.top.addChild(scoreText); // Create health bar label var healthLabel = new Text2('Lives:', { size: 40, fill: 0xFFFFFF }); healthLabel.anchor.set(1, 0.5); healthLabel.x = -200; healthLabel.y = 100; LK.gui.topRight.addChild(healthLabel); // Distance display removed - only counting coins for score // Create lives display as hearts in top right with better styling var healthBars = []; for (var h = 0; h < maxLives; h++) { var heart = LK.getAsset('heart', { anchorX: 0.5, anchorY: 0.5 }); // Arrange hearts in two rows for better space usage with 10 hearts var row = Math.floor(h / 5); // 5 hearts per row var col = h % 5; // Column position within row heart.x = -80 - col * 50; // Closer spacing for more hearts heart.y = 80 + row * 50; // Two rows: first at y=80, second at y=130 heart.scaleX = 1.0; // Slightly smaller to fit more hearts heart.scaleY = 1.0; // Add a subtle glow effect heart.alpha = 0.9; // Only show the first 3 hearts initially (player starts with 3 lives) if (h >= 3) { heart.visible = false; heart.alpha = 0; } LK.gui.topRight.addChild(heart); healthBars.push(heart); } // Touch controls game.down = function (x, y, obj) { lastSwipeX = x; lastSwipeY = y; swipeStarted = true; }; game.up = function (x, y, obj) { if (swipeStarted) { var deltaX = x - lastSwipeX; var deltaY = y - lastSwipeY; var swipeThreshold = 100; if (Math.abs(deltaX) > Math.abs(deltaY)) { // Horizontal swipe if (deltaX > swipeThreshold) { player.moveRight(); } else if (deltaX < -swipeThreshold) { player.moveLeft(); } } } swipeStarted = false; }; function spawnObstacle() { var obstacle = new Obstacle(); var laneIndex = Math.floor(Math.random() * 3); obstacle.x = player.lanes[laneIndex]; obstacle.y = -100; obstacle.speed = gameSpeed; obstacle.scaleX = 0.9; obstacle.scaleY = 0.9; // Set original lane index for lane changing obstacle.originalLaneIndex = laneIndex; // Enable lane changing after 2 minutes if game has been running long enough if (gameTimer >= laneChangeStartTime) { obstacle.canChangeLanes = Math.random() < 0.3; // 30% chance to be a lane changer } tween(obstacle, { scaleX: 2.4, scaleY: 2.4 }, { duration: 400, easing: tween.easeOut }); obstacles.push(obstacle); game.addChild(obstacle); } function spawnCoin() { var coin = new Coin(); var laneIndex = Math.floor(Math.random() * 3); var targetX = player.lanes[laneIndex]; var canSpawn = true; // Check if there's an obstacle in this lane that would conflict for (var i = 0; i < obstacles.length; i++) { var obstacle = obstacles[i]; // Check if obstacle is in the same lane and close to spawn area if (Math.abs(obstacle.x - targetX) < 50 && obstacle.y >= -300 && obstacle.y <= 200) { canSpawn = false; break; } } // Check if player is in this lane and close to spawn area if (canSpawn) { if (Math.abs(player.x - targetX) < 100 && player.y >= 2000) { canSpawn = false; } } // Also check if there's a powerup in this lane that would conflict if (canSpawn) { for (var p = 0; p < powerups.length; p++) { var powerup = powerups[p]; // Check if powerup is in the same lane and close to spawn area if (Math.abs(powerup.x - targetX) < 50 && powerup.y >= -300 && powerup.y <= 200) { canSpawn = false; break; } } } // If we can't spawn in the selected lane, try other lanes if (!canSpawn) { var availableLanes = []; for (var lane = 0; lane < 3; lane++) { var laneX = player.lanes[lane]; var laneAvailable = true; // Check for obstacles in this lane for (var j = 0; j < obstacles.length; j++) { var obs = obstacles[j]; if (Math.abs(obs.x - laneX) < 50 && obs.y >= -300 && obs.y <= 200) { laneAvailable = false; break; } } // Check for player in this lane if (laneAvailable) { if (Math.abs(player.x - laneX) < 100 && player.y >= 2000) { laneAvailable = false; } } // Also check for powerups in this lane if (laneAvailable) { for (var k = 0; k < powerups.length; k++) { var pup = powerups[k]; if (Math.abs(pup.x - laneX) < 50 && pup.y >= -300 && pup.y <= 200) { laneAvailable = false; break; } } } if (laneAvailable) { availableLanes.push(lane); } } // If no lanes are available, don't spawn coin this time if (availableLanes.length === 0) { return; } // Pick a random available lane laneIndex = availableLanes[Math.floor(Math.random() * availableLanes.length)]; targetX = player.lanes[laneIndex]; } coin.x = targetX; coin.y = -50; coin.speed = gameSpeed; coin.scaleX = 0.5; coin.scaleY = 0.5; tween(coin, { scaleX: 1, scaleY: 1 }, { duration: 500, easing: tween.easeOut }); coins.push(coin); game.addChild(coin); } function spawnPowerUp() { var powerup = new PowerUp(); var laneIndex = Math.floor(Math.random() * 3); var targetX = player.lanes[laneIndex]; var canSpawn = true; // Check if there's an obstacle in this lane that would conflict for (var i = 0; i < obstacles.length; i++) { var obstacle = obstacles[i]; // Check if obstacle is in the same lane and close to spawn area if (Math.abs(obstacle.x - targetX) < 80 && obstacle.y >= -400 && obstacle.y <= 300) { canSpawn = false; break; } } // If we can't spawn in the selected lane, try other lanes if (!canSpawn) { var availableLanes = []; for (var lane = 0; lane < 3; lane++) { var laneX = player.lanes[lane]; var laneAvailable = true; // Check for obstacles in this lane for (var j = 0; j < obstacles.length; j++) { var obs = obstacles[j]; if (Math.abs(obs.x - laneX) < 80 && obs.y >= -400 && obs.y <= 300) { laneAvailable = false; break; } } if (laneAvailable) { availableLanes.push(lane); } } // If no lanes are available, don't spawn powerup this time if (availableLanes.length === 0) { return; } // Pick a random available lane laneIndex = availableLanes[Math.floor(Math.random() * availableLanes.length)]; targetX = player.lanes[laneIndex]; } powerup.x = targetX; powerup.y = -50; powerup.speed = gameSpeed; powerup.scaleX = 0.3; powerup.scaleY = 0.3; tween(powerup, { scaleX: 1, scaleY: 1 }, { duration: 600, easing: tween.easeOut }); powerups.push(powerup); game.addChild(powerup); } function spawnLaneLine() { // Lane dividers between lanes - adjusted for wider road var positions = [802, 1246]; // Between lanes 0-1 and 1-2, proportionally spaced for (var i = 0; i < positions.length; i++) { var laneLine = new LaneLine(); laneLine.x = positions[i]; laneLine.y = -20; laneLine.speed = gameSpeed; laneLines.push(laneLine); // Simply add lane lines normally - layering optimization removed for performance game.addChild(laneLine); } } function spawnExhaustParticle() { var particle = new ExhaustParticle(); particle.x = player.x + (Math.random() - 0.5) * 40; particle.y = player.y + 20; particle.scaleX = 0.5 + Math.random() * 0.5; particle.scaleY = 0.5 + Math.random() * 0.5; particle.alpha = 0.6 + Math.random() * 0.4; exhaustParticles.push(particle); game.addChild(particle); } function createFlameEffect(x, y, parentCar) { // Create multiple flame particles (optimized amount) for (var i = 0; i < 8; i++) { // Balanced flame count for performance var flame = new Flame(); flame.x = x + (Math.random() - 0.5) * 80; // Increased spread from 60 to 80 flame.y = y + Math.random() * 40; // Increased spread from 30 to 40 flame.scaleX = 1.2 + Math.random() * 0.8; // Increased base size from 0.5 to 1.2 flame.scaleY = 1.2 + Math.random() * 0.8; // Increased base size from 0.5 to 1.2 // Attach flame to police car if provided if (parentCar) { flame.parentCar = parentCar; flame.offsetX = (Math.random() - 0.5) * 80; flame.offsetY = Math.random() * 40 - 60; // Flames above the car } flames.push(flame); game.addChild(flame); } // Create smoke particles (optimized amount) for (var j = 0; j < 4; j++) { // Balanced smoke particle count var smoke = new Smoke(); smoke.x = x + (Math.random() - 0.5) * 50; // Increased spread from 40 to 50 smoke.y = y - 10 + Math.random() * 30; // Increased spread from 20 to 30 smoke.alpha = 0.4 + Math.random() * 0.3; smoke.scaleX = 1.5 + Math.random() * 0.5; // Increased smoke size smoke.scaleY = 1.5 + Math.random() * 0.5; // Increased smoke size smokes.push(smoke); game.addChild(smoke); } // Create debris particles for explosion effect (optimized amount) for (var k = 0; k < 10; k++) { var debris = new DebrisParticle(); debris.x = x + (Math.random() - 0.5) * 40; debris.y = y + (Math.random() - 0.5) * 30; debris.scaleX = 0.5 + Math.random() * 1.0; debris.scaleY = 0.5 + Math.random() * 1.0; debrisParticles.push(debris); game.addChild(debris); } } function spawnTrees() { // Spawn trees on left side of road - adjusted for narrower forest var leftPositions = [120, 200, 280, 340]; for (var i = 0; i < leftPositions.length; i++) { // Randomly choose between Tree and Tree2 if (Math.random() < 0.65) { var leftTree; if (Math.random() < 0.5) { leftTree = new Tree(); trees.push(leftTree); } else { leftTree = new Tree2(); trees2.push(leftTree); } leftTree.x = leftPositions[i]; leftTree.y = -80; leftTree.speed = gameSpeed; // Add some variation in scale - bigger trees leftTree.scaleX = 1.2 + Math.random() * 0.8; leftTree.scaleY = 1.2 + Math.random() * 0.8; // Add slight tint variation for natural look var tintVariation = 0.9 + Math.random() * 0.2; leftTree.tint = tintVariation * 0x90EE90 | 0; // Apply current day/night brightness if (!isDay) { leftTree.alpha = 0.6; } // Simply add trees normally - layering optimization removed for performance game.addChild(leftTree); } // Spawn flowers in left forest area if (Math.random() < 0.25) { var leftFlower = new Flower(); leftFlower.x = leftPositions[i] + (Math.random() - 0.5) * 80; leftFlower.y = -40; leftFlower.speed = gameSpeed; leftFlower.scaleX = 0.8 + Math.random() * 0.4; leftFlower.scaleY = 0.8 + Math.random() * 0.4; // Add flower color variations var flowerColors = [0xff69b4, 0xff1493, 0xffffff, 0xffff00, 0xff4500]; leftFlower.tint = flowerColors[Math.floor(Math.random() * flowerColors.length)]; if (!isDay) { leftFlower.alpha = 0.5; } flowers.push(leftFlower); game.addChild(leftFlower); } } // Spawn trees on right side of road - adjusted for narrower forest var rightPositions = [1708, 1788, 1868, 1928]; for (var j = 0; j < rightPositions.length; j++) { // Randomly choose between Tree and Tree2 if (Math.random() < 0.65) { var rightTree; if (Math.random() < 0.5) { rightTree = new Tree(); trees.push(rightTree); } else { rightTree = new Tree2(); trees2.push(rightTree); } rightTree.x = rightPositions[j]; rightTree.y = -80; rightTree.speed = gameSpeed; // Add some variation in scale - bigger trees rightTree.scaleX = 1.2 + Math.random() * 0.8; rightTree.scaleY = 1.2 + Math.random() * 0.8; // Add slight tint variation for natural look var tintVariation = 0.9 + Math.random() * 0.2; rightTree.tint = tintVariation * 0x90EE90 | 0; // Apply current day/night brightness if (!isDay) { rightTree.alpha = 0.6; } // Simply add trees normally - layering optimization removed for performance game.addChild(rightTree); } // Spawn flowers in right forest area if (Math.random() < 0.25) { var rightFlower = new Flower(); rightFlower.x = rightPositions[j] + (Math.random() - 0.5) * 80; rightFlower.y = -40; rightFlower.speed = gameSpeed; rightFlower.scaleX = 0.8 + Math.random() * 0.4; rightFlower.scaleY = 0.8 + Math.random() * 0.4; // Add flower color variations var flowerColors = [0xff69b4, 0xff1493, 0xffffff, 0xffff00, 0xff4500]; rightFlower.tint = flowerColors[Math.floor(Math.random() * flowerColors.length)]; if (!isDay) { rightFlower.alpha = 0.5; } flowers.push(rightFlower); game.addChild(rightFlower); } } } function spawnHeartPowerUp() { var heartPowerup = new HeartPowerUp(); var laneIndex = Math.floor(Math.random() * 3); var targetX = player.lanes[laneIndex]; var canSpawn = true; // Check if there's an obstacle in this lane that would conflict for (var i = 0; i < obstacles.length; i++) { var obstacle = obstacles[i]; // Check if obstacle is in the same lane and close to spawn area if (Math.abs(obstacle.x - targetX) < 80 && obstacle.y >= -400 && obstacle.y <= 300) { canSpawn = false; break; } } // If we can't spawn in the selected lane, try other lanes if (!canSpawn) { var availableLanes = []; for (var lane = 0; lane < 3; lane++) { var laneX = player.lanes[lane]; var laneAvailable = true; // Check for obstacles in this lane for (var j = 0; j < obstacles.length; j++) { var obs = obstacles[j]; if (Math.abs(obs.x - laneX) < 80 && obs.y >= -400 && obs.y <= 300) { laneAvailable = false; break; } } if (laneAvailable) { availableLanes.push(lane); } } // If no lanes are available, don't spawn heart powerup this time if (availableLanes.length === 0) { return; } // Pick a random available lane laneIndex = availableLanes[Math.floor(Math.random() * availableLanes.length)]; targetX = player.lanes[laneIndex]; } heartPowerup.x = targetX; heartPowerup.y = -50; heartPowerup.speed = gameSpeed; heartPowerup.scaleX = 0.3; heartPowerup.scaleY = 0.3; tween(heartPowerup, { scaleX: 1, scaleY: 1 }, { duration: 600, easing: tween.easeOut }); heartPowerups.push(heartPowerup); game.addChild(heartPowerup); } function spawnMovingBarrier() { var barrier = new MovingBarrier(); // Choose random starting lane var startLaneIndex = Math.floor(Math.random() * 3); barrier.x = player.lanes[startLaneIndex]; barrier.y = -100; barrier.speed = gameSpeed; barrier.scaleX = 0.9; barrier.scaleY = 0.9; // Set movement bounds for one lane change only barrier.setMovementBounds(startLaneIndex); // Animate barrier scaling up when spawned tween(barrier, { scaleX: 2.4, scaleY: 2.4 }, { duration: 400, easing: tween.easeOut }); movingBarriers.push(barrier); game.addChild(barrier); } function spawnRain() { // Spawn multiple rain drops across the screen width (optimized amount) for (var i = 0; i < 6; i++) { var rainDrop = new RainDrop(); rainDrop.x = Math.random() * 2048; // Random X across screen width rainDrop.y = -20 - Math.random() * 50; // Start above screen rainDrop.speed = gameSpeed + 15 + Math.random() * 10; // Rain speed based on game speed rainDrops.push(rainDrop); game.addChild(rainDrop); } } // Main game loop game.update = function () { // Update game timer gameTimer++; // Handle police chaser timing and visibility with 1-minute window limit policeChaserTimer++; policeChaserWindowTimer++; // Reset the time window every minute if (policeChaserWindowTimer >= policeChaserTimeWindow) { policeChaserWindowTimer = 0; policeChaserUsedTime = 0; // Reset used time for new window console.log('Police chaser time window reset - new 1 minute period'); } if (!policeChaserActive) { // Check if it's time to activate police chaser and if we have time left in current window if (policeChaserTimer >= policeChaserActivationTime && policeChaserUsedTime < policeChaserMaxChaseTime) { // Calculate remaining time in current window var remainingTime = policeChaserMaxChaseTime - policeChaserUsedTime; // Activate police chaser policeChaserActive = true; policeChaserTimer = 0; // Set chase duration to minimum of remaining time or desired duration (max 10 seconds) policeChaserChaseDuration = Math.min(remainingTime, 600); // Max 10 seconds or remaining time // Position police chaser off-screen behind player and make visible policeChaser.visible = true; policeChaser.alpha = 0; // Start transparent for smooth fade in policeChaser.y = player.y + 1200; // Start well behind player off-screen policeChaser.x = player.x; // Start in same lane as player // Fade in police chaser first tween(policeChaser, { alpha: 1 }, { duration: 1500, easing: tween.easeInOut }); // Smoothly move police chaser into proper following position tween(policeChaser, { y: player.y + policeChaser.followDistance }, { duration: 3000, // Slower entry for smoother appearance easing: tween.easeInOut }); console.log('Police chaser activated for', Math.floor(policeChaserChaseDuration / 60), 'seconds. Used time:', Math.floor(policeChaserUsedTime / 60), 'Remaining:', Math.floor(remainingTime / 60)); } } else { // Police chaser is active, track used time and check if chase duration is over policeChaserUsedTime++; // Track time being used if (policeChaserTimer >= policeChaserChaseDuration || policeChaserUsedTime >= policeChaserMaxChaseTime) { // Deactivate police chaser policeChaserActive = false; policeChaserTimer = 0; // Set cooldown duration - wait until next window if we've used all time if (policeChaserUsedTime >= policeChaserMaxChaseTime) { // Used all time in window, wait until window resets var timeUntilWindowReset = policeChaserTimeWindow - policeChaserWindowTimer; policeChaserActivationTime = timeUntilWindowReset + 300; // Wait for window reset + 5 seconds console.log('Police chaser time limit reached. Waiting', Math.floor(timeUntilWindowReset / 60), 'seconds for window reset'); } else { // Still have time left, set normal cooldown policeChaserActivationTime = 1800 + Math.random() * 1800; // 30-60 seconds } // Make police chaser fall behind by reducing its speed and moving it back tween(policeChaser, { y: policeChaser.y + 1000 // Move it further behind the player }, { duration: 4000, // Slower transition over 4 seconds easing: tween.easeInOut, onFinish: function onFinish() { // After falling behind, fade out and hide tween(policeChaser, { alpha: 0 }, { duration: 1500, // Longer fade out easing: tween.easeInOut, onFinish: function onFinish() { policeChaser.visible = false; // Reset position completely off-screen to prevent any issues policeChaser.y = player.y + 1500; } }); } }); console.log('Police chaser deactivated. Next activation in', Math.floor(policeChaserActivationTime / 60), 'seconds'); } } // Calculate current base speed with gradual increase and cycle boost var currentBaseSpeed = Math.min(maxSpeed, baseGameSpeed + gameTimer * speedIncrement + cycleSpeedBoost); // Apply night speed multiplier gameSpeed = isDay ? currentBaseSpeed : currentBaseSpeed * nightSpeedMultiplier; // Distance tracking removed - only counting coins for score // Spawn obstacles - optimized spawn rates for better gameplay flow obstacleSpawnTimer++; var obstacleSpawnRate = isDay ? 150 + Math.random() * 60 : 120 + Math.random() * 90; // More balanced spawn rates if (obstacleSpawnTimer > obstacleSpawnRate) { spawnObstacle(); obstacleSpawnTimer = 0; } // Spawn coins - optimized spawn rate for better balance coinSpawnTimer++; var coinSpawnRate = Math.max(40, 100 - (gameSpeed - 8) * 3); // Balanced coin spawning if (coinSpawnTimer > coinSpawnRate + Math.random() * 30) { spawnCoin(); coinSpawnTimer = 0; } // Spawn powerups (rare spawn - every 1 minute) powerupSpawnTimer++; if (powerupSpawnTimer > 3600) { // 1 minute (60 seconds * 60 fps) spawnPowerUp(); powerupSpawnTimer = 0; } // Spawn heart powerups (every 3 minutes) heartPowerupSpawnTimer++; if (heartPowerupSpawnTimer > 10800) { // 3 minutes (180 seconds * 60 fps) spawnHeartPowerUp(); heartPowerupSpawnTimer = 0; } // Spawn moving barriers (every 45 seconds) movingBarrierSpawnTimer++; if (movingBarrierSpawnTimer > 2700) { // 45 seconds (45 * 60 fps) spawnMovingBarrier(); movingBarrierSpawnTimer = 0; } // Spawn lane lines laneLineSpawnTimer++; if (laneLineSpawnTimer > 30) { spawnLaneLine(); laneLineSpawnTimer = 0; } // Spawn trees treeSpawnTimer++; if (treeSpawnTimer > 45) { spawnTrees(); treeSpawnTimer = 0; } // Update obstacles var currentPoliceOnScreen = false; for (var i = obstacles.length - 1; i >= 0; i--) { var obstacle = obstacles[i]; obstacle.speed = gameSpeed; // Check if police car is on screen if (obstacle.y >= -200 && obstacle.y <= 2900) { currentPoliceOnScreen = true; } // Initialize fade tracking if needed if (obstacle.lastY === undefined) obstacle.lastY = obstacle.y; if (obstacle.isFadingOut === undefined) obstacle.isFadingOut = false; // Check if obstacle just crossed the fade threshold if (!obstacle.isFadingOut && obstacle.lastY <= 2800 && obstacle.y > 2800) { obstacle.isFadingOut = true; // Start fade out animation tween(obstacle, { alpha: 0, scaleX: obstacle.scaleX * 0.7, scaleY: obstacle.scaleY * 0.7 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { obstacle.destroy(); // Remove from obstacles array after fade completes var index = obstacles.indexOf(obstacle); if (index > -1) { obstacles.splice(index, 1); } } }); } // Update last position obstacle.lastY = obstacle.y; // Skip further processing if already fading or destroyed if (obstacle.isFadingOut || obstacle.y > 3500) { continue; } // Check collision with player (skip if obstacle is already hit) if (!obstacle.isHit && player.intersects(obstacle)) { // Play crash sound try { LK.getSound('crash').play(); } catch (e) { console.log('Crash sound error:', e); } // Reduce lives lives--; // Update health bar display - hide the lost heart if (lives >= 0 && lives < maxLives) { var lostHeart = healthBars[lives]; // Use current lives as index (lives already decremented) if (lostHeart) { // Animate heart disappearing with red flash tween(lostHeart, { alpha: 0, scaleX: 0.5, scaleY: 0.5, tint: 0xff0000 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { lostHeart.visible = false; } }); } } // Flash screen and remove obstacle LK.effects.flashScreen(0xff0000, 500); // Mark obstacle as hit to prevent multiple collisions obstacle.isHit = true; // Check if game over if (lives <= 0) { // Add delay to allow crash sound to play before game over LK.setTimeout(function () { LK.showGameOver(); }, 800); // Wait 800ms for crash sound to play return; } // Make player temporarily invulnerable player.alpha = 0.5; LK.setTimeout(function () { player.alpha = 1.0; }, 1500); // 1.5 seconds of invulnerability continue; } } // Handle police music based on police presence if (currentPoliceOnScreen && !policeMusicPlaying) { // Start police siren as a sound effect that loops instead of music // This allows it to play alongside the background music try { var policeSiren = LK.getSound('policemusic'); if (policeSiren) { policeSiren.loop = true; policeSiren.play(); } } catch (e) { console.log('Police siren play error:', e); } policeMusicPlaying = true; policeOnScreen = true; } else if (!currentPoliceOnScreen && policeMusicPlaying) { // Stop police siren sound try { var policeSiren = LK.getSound('policemusic'); if (policeSiren) { policeSiren.stop(); } } catch (e) { console.log('Police siren stop error:', e); } policeMusicPlaying = false; policeOnScreen = false; } else if (currentPoliceOnScreen && policeMusicPlaying) { // Ensure police siren continues playing if it stopped try { var policeSiren = LK.getSound('policemusic'); if (policeSiren && !policeSiren.playing) { policeSiren.loop = true; policeSiren.play(); } } catch (e) { console.log('Police siren restart error:', e); } } // Update coins for (var j = coins.length - 1; j >= 0; j--) { var coin = coins[j]; coin.speed = gameSpeed; // Remove coins that are off screen if (coin.y > 2800) { coin.destroy(); coins.splice(j, 1); continue; } // Check collection if (!coin.collected && player.intersects(coin)) { coin.collected = true; LK.setScore(LK.getScore() + 1); // Each coin is worth exactly 1 point scoreText.setText('Score: ' + LK.getScore()); LK.getSound('collect').play(); // Enhanced visual effect with faster animation for higher speeds var animationDuration = Math.max(200, 400 - (gameSpeed - 8) * 10); tween(coin, { alpha: 0, scaleX: 2.5, scaleY: 2.5, y: coin.y - 50 }, { duration: animationDuration, easing: tween.easeOut, onFinish: function onFinish() { coin.destroy(); } }); coins.splice(j, 1); } } // Update powerups for (var p = powerups.length - 1; p >= 0; p--) { var powerup = powerups[p]; powerup.speed = gameSpeed; // Remove powerups that are off screen or expired if (powerup.y > 2800 || powerup.lifetime <= 0) { powerup.destroy(); powerups.splice(p, 1); continue; } // Check collection if (!powerup.collected && player.intersects(powerup)) { powerup.collected = true; hasWeapon = true; weaponTimer = weaponDuration; // 20 seconds LK.getSound('collect').play(); // Visual effect tween(powerup, { alpha: 0, scaleX: 3, scaleY: 3 }, { duration: 400, onFinish: function onFinish() { powerup.destroy(); } }); powerups.splice(p, 1); } } // Update heart powerups for (var hp = heartPowerups.length - 1; hp >= 0; hp--) { var heartPowerup = heartPowerups[hp]; heartPowerup.speed = gameSpeed; // Remove heart powerups that are off screen or expired if (heartPowerup.y > 2800 || heartPowerup.lifetime <= 0) { heartPowerup.destroy(); heartPowerups.splice(hp, 1); continue; } // Check collection if (!heartPowerup.collected && player.intersects(heartPowerup)) { heartPowerup.collected = true; // Add one life if not at maximum if (lives < maxLives) { lives++; // Show the health bar corresponding to gained life with animation if (lives > 0 && lives <= maxLives) { var restoredHeart = healthBars[lives - 1]; // Use lives-1 as index (lives already incremented) if (restoredHeart) { restoredHeart.visible = true; restoredHeart.alpha = 0; restoredHeart.scaleX = 2.0; restoredHeart.scaleY = 2.0; restoredHeart.tint = 0x00ff00; // Green flash // Animate heart appearing tween(restoredHeart, { alpha: 0.9, scaleX: 1.0, scaleY: 1.0, tint: 0xffffff }, { duration: 600, easing: tween.easeOut }); } } } LK.getSound('collect').play(); // Visual effect with heart animation tween(heartPowerup, { alpha: 0, scaleX: 2.5, scaleY: 2.5, y: heartPowerup.y - 100 }, { duration: 600, easing: tween.easeOut, onFinish: function onFinish() { heartPowerup.destroy(); } }); heartPowerups.splice(hp, 1); } } // Update lane lines for (var k = laneLines.length - 1; k >= 0; k--) { var laneLine = laneLines[k]; laneLine.speed = gameSpeed; // Remove lane lines that are off screen if (laneLine.y > 2800) { laneLine.destroy(); laneLines.splice(k, 1); } } // Update trees for (var t = trees.length - 1; t >= 0; t--) { var tree = trees[t]; tree.speed = gameSpeed; // Remove trees that are off screen - increased threshold to prevent immediate disappearing if (tree.y > 3200) { tree.destroy(); trees.splice(t, 1); } } // Update trees2 for (var t2 = trees2.length - 1; t2 >= 0; t2--) { var tree2 = trees2[t2]; tree2.speed = gameSpeed; // Remove trees2 that are off screen - increased threshold to prevent immediate disappearing if (tree2.y > 3200) { tree2.destroy(); trees2.splice(t2, 1); } } // Update flowers for (var fl = flowers.length - 1; fl >= 0; fl--) { var flower = flowers[fl]; flower.speed = gameSpeed; // Remove flowers that are off screen if (flower.y > 3200) { flower.destroy(); flowers.splice(fl, 1); } } // Handle rain cycle logic if (!isRaining) { // Check if it's time to start raining nextRainEvent++; if (nextRainEvent >= rainCooldown) { // Start rain cycle isRaining = true; rainTimer = 0; // Random rain duration: 10-60 seconds (600-3600 frames at 60fps) rainDuration = 600 + Math.random() * 3000; // 10 seconds to 1 minute nextRainEvent = 0; // Start rain music if (!rainSoundPlaying) { try { LK.playMusic('rain'); } catch (e) { console.log('Rain music play error:', e); } rainSoundPlaying = true; } // Initialize thunder timing for this rain period thunderTimer = 0; nextThunderTime = 180 + Math.random() * 600; // First thunder in 3-13 seconds console.log('Rain started for', Math.floor(rainDuration / 60), 'seconds'); } } else { // Currently raining rainTimer++; // Spawn rain particles while raining - optimized frequency for performance rainSpawnTimer++; if (rainSpawnTimer >= 6) { // Spawn rain every 6 frames for balanced performance spawnRain(); rainSpawnTimer = 0; } // Handle thunder during rain thunderTimer++; if (thunderTimer >= nextThunderTime) { // Play thunder sound try { var thunderSound = LK.getSound('thunder'); if (thunderSound) { thunderSound.play(); } } catch (e) { console.log('Thunder sound play error:', e); } // Add thunder flash effect - bright white flash LK.effects.flashScreen(0xffffff, 300); // White flash for 300ms // Set next thunder time (random between 5-20 seconds) nextThunderTime = thunderTimer + (300 + Math.random() * 900); // 5-20 seconds at 60fps } // Check if rain should stop if (rainTimer >= rainDuration) { // Stop rain cycle isRaining = false; rainTimer = 0; // Random cooldown period: 30-180 seconds (1800-10800 frames at 60fps) rainCooldown = 1800 + Math.random() * 9000; // 30 seconds to 3 minutes nextRainEvent = 0; // Reset thunder timer thunderTimer = 0; nextThunderTime = 0; // Gradually fade out existing rain drops for (var fadeRain = 0; fadeRain < rainDrops.length; fadeRain++) { var rainDrop = rainDrops[fadeRain]; tween(rainDrop, { alpha: 0, speed: rainDrop.speed * 0.3 }, { duration: 2000 + Math.random() * 1000, easing: tween.easeOut }); } console.log('Rain stopped. Next rain in', Math.floor(rainCooldown / 60), 'seconds'); } } // Stop rain music only when all rain effects are complete if (!isRaining && rainSoundPlaying && rainDrops.length === 0) { try { LK.stopMusic(); // Restart background music after rain stops LK.playMusic('bgmusic'); } catch (e) { console.log('Rain music stop error:', e); } rainSoundPlaying = false; } // Spawn exhaust particles - optimized frequency for performance exhaustSpawnTimer++; if (exhaustSpawnTimer >= 12) { spawnExhaustParticle(); exhaustSpawnTimer = 0; } // Update exhaust particles for (var e = exhaustParticles.length - 1; e >= 0; e--) { var particle = exhaustParticles[e]; // Remove particles that are expired or off screen if (particle.lifetime <= 0 || particle.y < -50) { particle.destroy(); exhaustParticles.splice(e, 1); } } // Update flame particles for (var f = flames.length - 1; f >= 0; f--) { var flame = flames[f]; // Remove flames that are expired or off screen if (flame.lifetime <= 0 || flame.y < -50) { flame.destroy(); flames.splice(f, 1); } } // Update smoke particles for (var s = smokes.length - 1; s >= 0; s--) { var smoke = smokes[s]; // Remove smoke that is expired or off screen if (smoke.lifetime <= 0 || smoke.y < -50) { smoke.destroy(); smokes.splice(s, 1); } } // Update debris particles for (var d = debrisParticles.length - 1; d >= 0; d--) { var debris = debrisParticles[d]; // Remove debris that is expired or off screen if (debris.lifetime <= 0 || debris.y > 2800) { debris.destroy(); debrisParticles.splice(d, 1); } } // Update rain particles for (var r = rainDrops.length - 1; r >= 0; r--) { var rain = rainDrops[r]; // Remove rain drops that are off screen if (rain.y > 2800 || rain.x < -50 || rain.x > 2098) { rain.destroy(); rainDrops.splice(r, 1); } } // Update moving barriers for (var mb = movingBarriers.length - 1; mb >= 0; mb--) { var barrier = movingBarriers[mb]; barrier.speed = gameSpeed; // Initialize fade tracking if needed if (barrier.lastY === undefined) barrier.lastY = barrier.y; if (barrier.isFadingOut === undefined) barrier.isFadingOut = false; // Check if barrier just crossed the fade threshold if (!barrier.isFadingOut && barrier.lastY <= 2800 && barrier.y > 2800) { barrier.isFadingOut = true; // Start fade out animation tween(barrier, { alpha: 0, scaleX: barrier.scaleX * 0.7, scaleY: barrier.scaleY * 0.7 }, { duration: 1000, easing: tween.easeOut, onFinish: function onFinish() { barrier.destroy(); // Remove from movingBarriers array after fade completes var index = movingBarriers.indexOf(barrier); if (index > -1) { movingBarriers.splice(index, 1); } } }); } // Update last position barrier.lastY = barrier.y; // Skip further processing if already fading or destroyed if (barrier.isFadingOut || barrier.y > 3500) { continue; } // Check collision with player (skip if barrier is already hit) if (!barrier.isHit && player.intersects(barrier)) { // Play crash sound try { LK.getSound('crash').play(); } catch (e) { console.log('Crash sound error:', e); } // Reduce lives lives--; // Update health bar display - hide the lost heart if (lives >= 0 && lives < maxLives) { var lostHeart = healthBars[lives]; // Use current lives as index (lives already decremented) if (lostHeart) { // Animate heart disappearing with red flash tween(lostHeart, { alpha: 0, scaleX: 0.5, scaleY: 0.5, tint: 0xff0000 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { lostHeart.visible = false; } }); } } // Flash screen and mark barrier as hit LK.effects.flashScreen(0xff0000, 500); barrier.isHit = true; // Check if game over if (lives <= 0) { LK.setTimeout(function () { LK.showGameOver(); }, 800); return; } // Make player temporarily invulnerable player.alpha = 0.5; LK.setTimeout(function () { player.alpha = 1.0; }, 1500); continue; } // Check bullet collision with moving barriers for (var bb = bullets.length - 1; bb >= 0; bb--) { var bullet = bullets[bb]; if (bullet && !barrier.isHit && bullet.intersects(barrier)) { // Play police hit sound try { LK.getSound('policeHit').play(); } catch (e) { console.log('Police hit sound error:', e); } // Destroy bullet immediately bullet.destroy(); bullets.splice(bb, 1); // Mark barrier as hit barrier.isHit = true; // Create flame effect at barrier position createFlameEffect(barrier.x, barrier.y - 50, barrier); // Create gold coins flying out when barrier is destroyed (optimized amount) for (var goldCount = 0; goldCount < 5; goldCount++) { var goldCoin = new Coin(); goldCoin.x = barrier.x + (Math.random() - 0.5) * 100; goldCoin.y = barrier.y + (Math.random() - 0.5) * 80; goldCoin.scaleX = 0.8 + Math.random() * 0.4; goldCoin.scaleY = 0.8 + Math.random() * 0.4; goldCoin.collected = false; goldCoin.speed = gameSpeed * 0.5; goldCoin.tint = 0xFFD700; var randomX = goldCoin.x + (Math.random() - 0.5) * 200; var randomY = goldCoin.y + Math.random() * 150 + 50; tween(goldCoin, { x: randomX, y: randomY, rotation: Math.PI * 4 }, { duration: 800 + Math.random() * 400, easing: tween.easeOut }); coins.push(goldCoin); game.addChild(goldCoin); } // Create burning effect tween(barrier, { tint: 0x000000, alpha: 0 }, { duration: 800, easing: tween.easeOut, onFinish: function onFinish() { barrier.isDestroyed = true; barrier.destroy(); var index = movingBarriers.indexOf(barrier); if (index > -1) { movingBarriers.splice(index, 1); } } }); break; } } } // Handle weapon system if (hasWeapon) { weaponTimer--; if (weaponTimer <= 0) { hasWeapon = false; } // Shoot bullets every 15 frames for balanced firing rate if (LK.ticks % 15 === 0) { var bullet = new Bullet(); bullet.x = player.x; bullet.y = player.y - 100; bullets.push(bullet); game.addChild(bullet); // Play gun sound with reduced volume for less audio clutter try { var gunSound = LK.getSound('gunshot'); gunSound.volume = 0.2; gunSound.play(); } catch (e) { console.log('Gun sound error:', e); } } } // Update bullets for (var b = bullets.length - 1; b >= 0; b--) { var bullet = bullets[b]; // Remove bullets that are off screen if (bullet.y < -50) { bullet.destroy(); bullets.splice(b, 1); continue; } // Check bullet collision with obstacles for (var o = obstacles.length - 1; o >= 0; o--) { var obstacle = obstacles[o]; if (!obstacle.isHit && bullet.intersects(obstacle)) { // Play police hit sound try { LK.getSound('policeHit').play(); } catch (e) { console.log('Police hit sound error:', e); } // Destroy bullet immediately bullet.destroy(); bullets.splice(b, 1); // Mark obstacle as hit to prevent further collisions obstacle.isHit = true; // Create flame effect at police car position with car reference createFlameEffect(obstacle.x, obstacle.y - 50, obstacle); // Create 6 gold coins flying out when police car is destroyed (optimized amount) for (var goldCount = 0; goldCount < 6; goldCount++) { var goldCoin = new Coin(); goldCoin.x = obstacle.x + (Math.random() - 0.5) * 100; goldCoin.y = obstacle.y + (Math.random() - 0.5) * 80; goldCoin.scaleX = 0.8 + Math.random() * 0.4; goldCoin.scaleY = 0.8 + Math.random() * 0.4; goldCoin.collected = false; goldCoin.speed = gameSpeed * 0.5; // Slower than normal coins // Make coins golden colored goldCoin.tint = 0xFFD700; // Add random movement with tween animation var randomX = goldCoin.x + (Math.random() - 0.5) * 200; var randomY = goldCoin.y + Math.random() * 150 + 50; tween(goldCoin, { x: randomX, y: randomY, rotation: Math.PI * 4 }, { duration: 800 + Math.random() * 400, easing: tween.easeOut }); coins.push(goldCoin); game.addChild(goldCoin); } // Create burning effect with flames tween(obstacle, { tint: 0x000000, alpha: 0 }, { duration: 800, easing: tween.easeOut, onFinish: function onFinish() { obstacle.isDestroyed = true; // Mark as destroyed for flame cleanup obstacle.destroy(); // Remove from obstacles array after animation completes var index = obstacles.indexOf(obstacle); if (index > -1) { obstacles.splice(index, 1); } } }); break; } } } // Update day/night cycle dayNightTimer++; if (dayNightTimer >= dayNightCycleDuration) { transitionDayNight(); dayNightTimer = 0; } // Police exhaust particles now stay in their natural layer order // Road color no longer changes automatically }; // Road color change function (kept for potential future use) function changeRoadColor(newColor) { roadBackground.tint = newColor; } // Day/night cycle transition function function transitionDayNight() { if (isTransitioning) return; isTransitioning = true; isDay = !isDay; var targetColors = isDay ? dayColors : nightColors; var transitionDuration = 2000; // 2 seconds transition // Increment cycle count and apply speed boost when transitioning to day (completing full cycle) if (isDay) { cycleCount++; cycleSpeedBoost = cycleCount * speedBoostPerCycle; } // Immediately update game speed for the new cycle var currentBaseSpeed = Math.min(maxSpeed, baseGameSpeed + gameTimer * speedIncrement + cycleSpeedBoost); gameSpeed = isDay ? currentBaseSpeed : currentBaseSpeed * nightSpeedMultiplier; // Transition background color tween(game, { backgroundColor: targetColors.sky }, { duration: transitionDuration, easing: tween.easeInOut }); // Transition forest backgrounds tween(leftForest, { tint: targetColors.forest }, { duration: transitionDuration, easing: tween.easeInOut }); tween(rightForest, { tint: targetColors.forest }, { duration: transitionDuration, easing: tween.easeInOut }); // Transition road background tween(roadBackground, { tint: targetColors.road }, { duration: transitionDuration, easing: tween.easeInOut, onFinish: function onFinish() { isTransitioning = false; } }); // Adjust trees brightness for night/day var treeBrightness = isDay ? 1.0 : 0.6; for (var i = 0; i < trees.length; i++) { var tree = trees[i]; tween(tree, { alpha: treeBrightness }, { duration: transitionDuration, easing: tween.easeInOut }); } // Adjust trees2 brightness for night/day for (var i2 = 0; i2 < trees2.length; i2++) { var tree2 = trees2[i2]; tween(tree2, { alpha: treeBrightness }, { duration: transitionDuration, easing: tween.easeInOut }); } // Adjust flowers brightness for night/day var flowerBrightness = isDay ? 1.0 : 0.5; for (var f = 0; f < flowers.length; f++) { var flower = flowers[f]; tween(flower, { alpha: flowerBrightness }, { duration: transitionDuration, easing: tween.easeInOut }); } // Control player headlights based on day/night var headlightAlpha = isDay ? 0 : 0.8; tween(player.leftHeadlight, { alpha: headlightAlpha }, { duration: transitionDuration, easing: tween.easeInOut }); tween(player.rightHeadlight, { alpha: headlightAlpha }, { duration: transitionDuration, easing: tween.easeInOut }); } // Spawn initial powerup at game start spawnPowerUp(); // Initialize first rain cycle rainCooldown = 1800 + Math.random() * 9000; // 30 seconds to 3 minutes before first rain nextRainEvent = 0; // Start background music - this will be the base music layer LK.playMusic('bgmusic', { fade: { start: 0, end: 1, duration: 1000 } }); ;
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Bullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = -25;
self.update = function () {
self.y += self.speed;
};
return self;
});
var Coin = Container.expand(function () {
var self = Container.call(this);
var coinGraphics = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = gameSpeed;
self.collected = false;
self.update = function () {
self.y += self.speed;
self.rotation += 0.1;
};
return self;
});
var DebrisParticle = Container.expand(function () {
var self = Container.call(this);
var debrisGraphics = self.attachAsset('debrisParticle', {
anchorX: 0.5,
anchorY: 0.5
});
self.speedX = (Math.random() - 0.5) * 20; // Random horizontal speed
self.speedY = -5 - Math.random() * 15; // Upward initial speed
self.gravity = 0.8; // Gravity acceleration
self.lifetime = 90 + Math.random() * 60; // 1.5-2.5 seconds
self.maxLifetime = self.lifetime;
self.rotationSpeed = (Math.random() - 0.5) * 0.3;
// Random colors for debris (metal, glass, etc.)
var debrisColors = [0x666666, 0x888888, 0x444444, 0x999999, 0x333333];
debrisGraphics.tint = debrisColors[Math.floor(Math.random() * debrisColors.length)];
self.update = function () {
// Apply physics
self.x += self.speedX;
self.y += self.speedY;
self.speedY += self.gravity; // Apply gravity
self.rotation += self.rotationSpeed;
self.lifetime--;
// Fade out over time
var fadeProgress = 1 - self.lifetime / self.maxLifetime;
self.alpha = Math.max(0, 1 - fadeProgress * 1.5);
};
return self;
});
var ExhaustParticle = Container.expand(function () {
var self = Container.call(this);
var particleGraphics = self.attachAsset('exhaustParticle', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 3 + Math.random() * 2;
self.sideSpeed = (Math.random() - 0.5) * 2;
self.lifetime = 60 + Math.random() * 30;
self.maxLifetime = self.lifetime;
self.update = function () {
self.y += self.speed;
self.x += self.sideSpeed;
self.lifetime--;
// Fade out and scale up over time
var fadeProgress = 1 - self.lifetime / self.maxLifetime;
self.alpha = Math.max(0, 1 - fadeProgress * 1.5);
self.scaleX = 1 + fadeProgress * 0.8;
self.scaleY = 1 + fadeProgress * 0.8;
};
return self;
});
var Flame = Container.expand(function () {
var self = Container.call(this);
var flameGraphics = self.attachAsset('flame', {
anchorX: 0.5,
anchorY: 1.0
});
self.speed = -2 - Math.random() * 3;
self.sideSpeed = (Math.random() - 0.5) * 4;
self.lifetime = 30 + Math.random() * 20;
self.maxLifetime = self.lifetime;
self.flickerTimer = 0;
self.parentCar = null; // Reference to the police car this flame belongs to
self.offsetX = 0; // Offset from car position
self.offsetY = 0; // Offset from car position
self.update = function () {
// If attached to a police car, follow its position
if (self.parentCar && !self.parentCar.isDestroyed) {
self.x = self.parentCar.x + self.offsetX;
self.y = self.parentCar.y + self.offsetY;
}
self.lifetime--;
// Flicker effect
self.flickerTimer++;
if (self.flickerTimer % 3 === 0) {
var colors = [0xff4500, 0xff6600, 0xff8800, 0xffaa00];
flameGraphics.tint = colors[Math.floor(Math.random() * colors.length)];
}
// Fade and scale over time
var fadeProgress = 1 - self.lifetime / self.maxLifetime;
self.alpha = Math.max(0, 1 - fadeProgress * 1.2);
self.scaleX = 1.5 + fadeProgress * 1.2; // Increased base size from 0.5 to 1.5
self.scaleY = 2.0 + fadeProgress * 2.0; // Increased base size from 0.8 to 2.0
};
return self;
});
var Flower = Container.expand(function () {
var self = Container.call(this);
var flowerGraphics = self.attachAsset('flower', {
anchorX: 0.5,
anchorY: 1.0
});
self.speed = gameSpeed;
self.update = function () {
self.y += self.speed;
};
return self;
});
var HeartPowerUp = Container.expand(function () {
var self = Container.call(this);
var heartGraphics = self.attachAsset('heart', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = gameSpeed;
self.collected = false;
self.lifetime = 420; // 7 seconds at 60fps
self.update = function () {
self.y += self.speed;
self.rotation += 0.1;
// Pulsing effect
self.scaleX = 1 + Math.sin(LK.ticks * 0.1) * 0.1;
self.scaleY = 1 + Math.sin(LK.ticks * 0.1) * 0.1;
// Decrease lifetime
self.lifetime--;
// Flash when about to disappear (last 3 seconds)
if (self.lifetime <= 180 && self.lifetime > 0) {
self.alpha = (Math.sin(self.lifetime * 0.3) + 1) * 0.5;
}
};
return self;
});
var LaneLine = Container.expand(function () {
var self = Container.call(this);
var lineGraphics = self.attachAsset('laneLine', {
anchorX: 0.5,
anchorY: 0.5
});
// Make lane lines semi-transparent so they appear behind cars
lineGraphics.alpha = 0.7;
self.speed = gameSpeed;
self.update = function () {
self.y += self.speed;
};
return self;
});
var MovingBarrier = Container.expand(function () {
var self = Container.call(this);
var barrierGraphics = self.attachAsset('movingBarrier', {
anchorX: 0.5,
anchorY: 1.0
});
self.speed = gameSpeed;
self.horizontalSpeed = 2; // Speed of horizontal movement
self.direction = 1; // 1 for right, -1 for left
self.lanes = [580, 1024, 1468]; // Reference to lane positions
self.startLaneIndex = 1; // Start in middle lane by default
self.minX = 580; // Will be set based on start lane
self.maxX = 1468; // Will be set based on start lane
self.changeDirectionTimer = 0;
self.changeDirectionDelay = 120 + Math.random() * 180; // 2-5 seconds
// Function to set movement boundaries for one lane change only
self.setMovementBounds = function (startLaneIndex) {
self.startLaneIndex = startLaneIndex;
if (startLaneIndex === 0) {
// Left lane - can only move to middle lane
self.minX = self.lanes[0];
self.maxX = self.lanes[1];
} else if (startLaneIndex === 1) {
// Middle lane - can move to left or right lane
var canMoveLeft = Math.random() < 0.5;
if (canMoveLeft) {
self.minX = self.lanes[0];
self.maxX = self.lanes[1];
self.direction = -1; // Start moving left
} else {
self.minX = self.lanes[1];
self.maxX = self.lanes[2];
self.direction = 1; // Start moving right
}
} else {
// Right lane - can only move to middle lane
self.minX = self.lanes[1];
self.maxX = self.lanes[2];
}
};
self.update = function () {
self.y += self.speed;
// Move horizontally
self.x += self.horizontalSpeed * self.direction;
// Bounce off lane boundaries with smooth animation
if (self.x <= self.minX) {
self.x = self.minX;
self.direction = 1; // Change to move right
// Add bounce animation
tween(self, {
scaleX: 2.6,
rotation: 0.1
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 2.4,
rotation: 0
}, {
duration: 200,
easing: tween.easeInOut
});
}
});
} else if (self.x >= self.maxX) {
self.x = self.maxX;
self.direction = -1; // Change to move left
// Add bounce animation
tween(self, {
scaleX: 2.6,
rotation: -0.1
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 2.4,
rotation: 0
}, {
duration: 200,
easing: tween.easeInOut
});
}
});
}
// Random direction changes for unpredictability
self.changeDirectionTimer++;
if (self.changeDirectionTimer >= self.changeDirectionDelay) {
self.direction *= -1; // Reverse direction
self.changeDirectionTimer = 0;
self.changeDirectionDelay = 120 + Math.random() * 180; // New random delay
// Add direction change animation
tween(self, {
scaleY: 2.6
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
scaleY: 2.4
}, {
duration: 150,
easing: tween.easeInOut
});
}
});
}
};
return self;
});
var Obstacle = Container.expand(function () {
var self = Container.call(this);
var obstacleGraphics = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 1.0
});
self.speed = gameSpeed;
// Add police lights to the obstacle (police car) - properly attached to self container
// Main roof lights - positioned on top of the police car
var leftLight = self.attachAsset('policeLight', {
anchorX: 0.5,
anchorY: 0.5
});
leftLight.x = -20;
leftLight.y = -130;
var rightLight = self.attachAsset('policeLight', {
anchorX: 0.5,
anchorY: 0.5
});
rightLight.x = 20;
rightLight.y = -130;
rightLight.tint = 0x0000ff; // Make right light blue
// Side mirror lights
var leftSideLight = self.attachAsset('policeLight', {
anchorX: 0.5,
anchorY: 0.5
});
leftSideLight.x = -35;
leftSideLight.y = -100;
var rightSideLight = self.attachAsset('policeLight', {
anchorX: 0.5,
anchorY: 0.5
});
rightSideLight.x = 35;
rightSideLight.y = -100;
rightSideLight.tint = 0x0000ff; // Make right side light blue
// Front bumper lights
var topLeftLight = self.attachAsset('policeLight', {
anchorX: 0.5,
anchorY: 0.5
});
topLeftLight.x = -25;
topLeftLight.y = -145;
var topRightLight = self.attachAsset('policeLight', {
anchorX: 0.5,
anchorY: 0.5
});
topRightLight.x = 25;
topRightLight.y = -145;
topRightLight.tint = 0x0000ff; // Make top right light blue
// Store light references for animation
self.leftLight = leftLight;
self.rightLight = rightLight;
self.leftSideLight = leftSideLight;
self.rightSideLight = rightSideLight;
self.topLeftLight = topLeftLight;
self.topRightLight = topRightLight;
self.lightTimer = 0;
self.isRedActive = true;
// Lane changing properties
self.canChangeLanes = false;
self.laneChangeTimer = 0;
self.laneChangeDelay = 180 + Math.random() * 240; // 3-7 seconds random delay
self.targetLaneIndex = Math.floor(Math.random() * 3); // Random target lane
self.isChangingLanes = false;
self.originalLaneIndex = 0; // Will be set when spawned
self.update = function () {
self.y += self.speed;
// Handle lane changing behavior - only at night
if (self.canChangeLanes && !self.isChangingLanes && self.y > 200 && self.y < 2000 && !isDay) {
self.laneChangeTimer++;
if (self.laneChangeTimer >= self.laneChangeDelay) {
// Start lane change
self.isChangingLanes = true;
var currentLaneIndex = self.originalLaneIndex;
// Choose a different lane
var availableLanes = [];
for (var i = 0; i < 3; i++) {
if (i !== currentLaneIndex) {
availableLanes.push(i);
}
}
if (availableLanes.length > 0) {
var newLaneIndex = availableLanes[Math.floor(Math.random() * availableLanes.length)];
var targetX = player.lanes[newLaneIndex];
// Animate lane change using tween
tween(self, {
x: targetX
}, {
duration: 1000 + Math.random() * 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
self.isChangingLanes = false;
self.originalLaneIndex = newLaneIndex;
// Set new delay for next potential lane change
self.laneChangeTimer = 0;
self.laneChangeDelay = 300 + Math.random() * 600; // 5-15 seconds
}
});
}
}
}
// Police lights visible during day and night, more prominent at night
var dayAlpha = isDay ? 0.6 : 1.0; // Visible during day, full at night
var dayAlphaLow = isDay ? 0.3 : 0.5; // Reduced for inactive light but still visible
// Animate police lights with enhanced tween effects
self.lightTimer++;
if (self.lightTimer >= 15) {
// Change every 15 frames (0.25 seconds at 60fps)
self.lightTimer = 0;
self.isRedActive = !self.isRedActive;
if (self.isRedActive) {
// Red lights active - animate with pulse effect
tween(self.leftLight, {
alpha: dayAlpha,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
easing: tween.easeOut
});
tween(self.leftSideLight, {
alpha: dayAlpha,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
easing: tween.easeOut
});
tween(self.topLeftLight, {
alpha: dayAlpha,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
easing: tween.easeOut
});
// Blue lights dimmed
tween(self.rightLight, {
alpha: dayAlphaLow,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeOut
});
tween(self.rightSideLight, {
alpha: dayAlphaLow,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeOut
});
tween(self.topRightLight, {
alpha: dayAlphaLow,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeOut
});
} else {
// Blue lights active - animate with pulse effect
tween(self.rightLight, {
alpha: dayAlpha,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
easing: tween.easeOut
});
tween(self.rightSideLight, {
alpha: dayAlpha,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
easing: tween.easeOut
});
tween(self.topRightLight, {
alpha: dayAlpha,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
easing: tween.easeOut
});
// Red lights dimmed
tween(self.leftLight, {
alpha: dayAlphaLow,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeOut
});
tween(self.leftSideLight, {
alpha: dayAlphaLow,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeOut
});
tween(self.topLeftLight, {
alpha: dayAlphaLow,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeOut
});
}
}
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 1.0
});
// Add headlights to the player car - positioned at the front of the vehicle
var leftHeadlight = self.attachAsset('headlight', {
anchorX: 0.5,
anchorY: 0.5
});
leftHeadlight.x = -25;
leftHeadlight.y = -150; // Moved further forward to the front of the car
leftHeadlight.alpha = 0; // Start invisible
var rightHeadlight = self.attachAsset('headlight', {
anchorX: 0.5,
anchorY: 0.5
});
rightHeadlight.x = 25;
rightHeadlight.y = -150; // Moved further forward to the front of the car
rightHeadlight.alpha = 0; // Start invisible
// Store headlight references
self.leftHeadlight = leftHeadlight;
self.rightHeadlight = rightHeadlight;
// Move headlights to front of all elements
self.moveHeadlightsToFront = function () {
if (self.parent) {
// Remove headlights from current position
self.removeChild(leftHeadlight);
self.removeChild(rightHeadlight);
// Add them back to the front (last children render on top)
self.addChild(leftHeadlight);
self.addChild(rightHeadlight);
}
};
self.groundY = 2300;
self.lanes = [580, 1024, 1468]; // Three lanes adjusted for wider road
self.currentLane = 1; // Middle lane
self.targetX = self.lanes[self.currentLane];
self.x = self.targetX;
self.y = self.groundY;
self.moveLeft = function () {
if (self.currentLane > 0) {
self.currentLane--;
self.targetX = self.lanes[self.currentLane];
// Add shake effect when moving left
tween(self, {
rotation: -0.1
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
rotation: 0
}, {
duration: 100,
easing: tween.easeInOut
});
}
});
// Play car movement sound
try {
var carMoveSound = LK.getSound('carMove');
carMoveSound.volume = 0.005;
carMoveSound.play();
} catch (e) {
console.log('Car move sound error:', e);
}
}
};
self.moveRight = function () {
if (self.currentLane < 2) {
self.currentLane++;
self.targetX = self.lanes[self.currentLane];
// Add shake effect when moving right
tween(self, {
rotation: 0.1
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
rotation: 0
}, {
duration: 100,
easing: tween.easeInOut
});
}
});
// Play car movement sound
try {
var carMoveSound = LK.getSound('carMove');
carMoveSound.volume = 0.005;
carMoveSound.play();
} catch (e) {
console.log('Car move sound error:', e);
}
}
};
self.update = function () {
// Handle lane switching
var dx = self.targetX - self.x;
if (Math.abs(dx) > 2) {
self.x += dx * 0.25;
} else {
self.x = self.targetX;
}
// Ensure headlights are always in front
self.moveHeadlightsToFront();
};
return self;
});
var PoliceChaser = Container.expand(function () {
var self = Container.call(this);
var policeGraphics = self.attachAsset('policeChaser', {
anchorX: 0.5,
anchorY: 1.0
});
// Add police lights - same as regular obstacles
var leftLight = self.attachAsset('policeLight', {
anchorX: 0.5,
anchorY: 0.5
});
leftLight.x = -20;
leftLight.y = -130;
var rightLight = self.attachAsset('policeLight', {
anchorX: 0.5,
anchorY: 0.5
});
rightLight.x = 20;
rightLight.y = -130;
rightLight.tint = 0x0000ff;
// Store light references for animation
self.leftLight = leftLight;
self.rightLight = rightLight;
self.lightTimer = 0;
self.isRedActive = true;
// Chase behavior properties
self.followDistance = 500; // Distance to maintain behind player
self.maxFollowDistance = 700; // Maximum distance to prevent getting too close
self.minFollowDistance = 400; // Minimum distance to maintain
self.speed = gameSpeed;
self.targetX = 1024; // Center lane by default
self.lastPlayerLane = 1; // Track player's last lane
self.update = function () {
// Calculate dynamic follow distance based on current distance to player
var currentDistance = self.y - player.y;
var targetDistance = self.followDistance;
// If too close, increase follow distance
if (currentDistance < self.minFollowDistance) {
targetDistance = self.maxFollowDistance;
} else if (currentDistance > self.maxFollowDistance) {
targetDistance = self.minFollowDistance;
}
// Smoothly adjust position to maintain proper distance
var distanceDiff = currentDistance - targetDistance;
self.y = player.y + targetDistance + distanceDiff * 0.95; // Gradual adjustment
// Follow player's lane with slight delay for realistic behavior
var currentPlayerLane = player.currentLane;
if (currentPlayerLane !== self.lastPlayerLane) {
// Player changed lanes, start following
self.targetX = player.lanes[currentPlayerLane];
self.lastPlayerLane = currentPlayerLane;
}
// Smooth lane following
var dx = self.targetX - self.x;
if (Math.abs(dx) > 5) {
self.x += dx * 0.08; // Slower than player for realistic chase
} else {
self.x = self.targetX;
}
// Ensure police lights are always in front
self.removeChild(self.leftLight);
self.removeChild(self.rightLight);
self.addChild(self.leftLight);
self.addChild(self.rightLight);
// Animate police lights
self.lightTimer++;
if (self.lightTimer >= 15) {
self.lightTimer = 0;
self.isRedActive = !self.isRedActive;
var dayAlpha = isDay ? 0.8 : 1.0;
var dayAlphaLow = isDay ? 0.4 : 0.6;
if (self.isRedActive) {
// Red light active
tween(self.leftLight, {
alpha: dayAlpha,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
easing: tween.easeOut
});
tween(self.rightLight, {
alpha: dayAlphaLow,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeOut
});
} else {
// Blue light active
tween(self.rightLight, {
alpha: dayAlpha,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
easing: tween.easeOut
});
tween(self.leftLight, {
alpha: dayAlphaLow,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeOut
});
}
}
};
return self;
});
var PowerUp = Container.expand(function () {
var self = Container.call(this);
var powerupGraphics = self.attachAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = gameSpeed;
self.collected = false;
self.lifetime = 420; // 7 seconds at 60fps
self.update = function () {
self.y += self.speed;
self.rotation += 0.15;
// Decrease lifetime
self.lifetime--;
// Flash when about to disappear (last 3 seconds)
if (self.lifetime <= 180 && self.lifetime > 0) {
self.alpha = (Math.sin(self.lifetime * 0.3) + 1) * 0.5;
}
};
return self;
});
var RainDrop = Container.expand(function () {
var self = Container.call(this);
var dropGraphics = self.attachAsset('rainDrop', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 15 + Math.random() * 10; // Rain falls fast
self.sideSpeed = -2 + Math.random() * 4; // Slight horizontal movement
self.alpha = 0.3 + Math.random() * 0.4; // Semi-transparent
self.update = function () {
self.y += self.speed;
self.x += self.sideSpeed;
};
return self;
});
var Smoke = Container.expand(function () {
var self = Container.call(this);
var smokeGraphics = self.attachAsset('smoke', {
anchorX: 0.5,
anchorY: 1.0
});
self.speed = -1 - Math.random() * 2;
self.sideSpeed = (Math.random() - 0.5) * 3;
self.lifetime = 60 + Math.random() * 40;
self.maxLifetime = self.lifetime;
self.update = function () {
self.y += self.speed;
self.x += self.sideSpeed;
self.lifetime--;
// Fade and expand over time
var fadeProgress = 1 - self.lifetime / self.maxLifetime;
self.alpha = Math.max(0, 0.8 - fadeProgress * 1.2);
self.scaleX = 0.3 + fadeProgress * 2;
self.scaleY = 0.3 + fadeProgress * 2;
};
return self;
});
var Tree = Container.expand(function () {
var self = Container.call(this);
var treeGraphics = self.attachAsset('tree', {
anchorX: 0.5,
anchorY: 1.0
});
self.speed = gameSpeed;
self.update = function () {
self.y += self.speed;
};
return self;
});
var Tree2 = Container.expand(function () {
var self = Container.call(this);
var treeGraphics = self.attachAsset('tree2', {
anchorX: 0.5,
anchorY: 1.0
});
self.speed = gameSpeed;
self.update = function () {
self.y += self.speed;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87CEEB
});
/****
* Game Code
****/
var player;
var obstacles = [];
var coins = [];
var powerups = [];
var heartPowerups = [];
var bullets = [];
var movingBarriers = [];
var hasWeapon = false;
var weaponTimer = 0;
var weaponDuration = 1200; // 20 seconds at 60fps
var heartPowerupSpawnTimer = 0;
var movingBarrierSpawnTimer = 0;
var laneLines = [];
var trees = [];
var trees2 = [];
var flowers = [];
var policeChaser = null;
var policeChaserActive = false;
var policeChaserTimer = 0;
var policeChaserActivationTime = 900; // 15 seconds at 60fps (15 * 60)
var policeChaserChaseDuration = 0; // Will be set randomly when activated
var policeChaserCooldownDuration = 0; // Will be set randomly when deactivated
var policeChaserTimeWindow = 3600; // 1 minute window (60 seconds at 60fps)
var policeChaserMaxChaseTime = 600; // 10 seconds maximum chase time per window (10 * 60fps)
var policeChaserUsedTime = 0; // Time used in current window
var policeChaserWindowTimer = 0; // Timer for the current 1-minute window
var gameSpeed = 8;
var maxSpeed = 20;
var speedIncrement = 0.005;
var distance = 0;
var obstacleSpawnTimer = 0;
var coinSpawnTimer = 0;
var powerupSpawnTimer = 0;
var laneLineSpawnTimer = 0;
var treeSpawnTimer = 0;
var lastSwipeX = 0;
var lastSwipeY = 0;
var swipeStarted = false;
var policeMusicPlaying = false;
var policeOnScreen = false;
var lives = 3;
var maxLives = 10;
var exhaustParticles = [];
var exhaustSpawnTimer = 0;
var flames = [];
var smokes = [];
var debrisParticles = [];
var rainDrops = [];
var rainSpawnTimer = 0;
// Rain cycle variables
var isRaining = false;
var rainTimer = 0;
var rainDuration = 0; // Will be set randomly
var rainCooldown = 0; // Will be set randomly
var nextRainEvent = 0; // Timer for next rain event
var rainSoundPlaying = false;
var thunderTimer = 0;
var nextThunderTime = 0;
// Day/night cycle variables
var dayNightTimer = 0;
var dayNightCycleDuration = 3600; // 60 seconds (1 minute) at 60fps
var isDay = true;
var isTransitioning = false;
var cycleCount = 0; // Track completed day/night cycles
var cycleSpeedBoost = 0; // Additional speed from completed cycles
var speedBoostPerCycle = 2; // Speed increase per completed cycle
// Game timer for police lane changing feature
var gameTimer = 0;
var laneChangeStartTime = 7200; // 2 minutes at 60fps (2 * 60 * 60)
// Night speed multiplier
var nightSpeedMultiplier = 1.25; // 25% faster during night
var baseGameSpeed = 8; // Store the base game speed
var dayColors = {
sky: 0x87CEEB,
// Light blue sky
forest: 0x228b22,
// Forest green
road: 0xffffff // Normal road color
};
var nightColors = {
sky: 0x191970,
// Midnight blue
forest: 0x0f2f0f,
// Dark forest green
road: 0x404040 // Darker road color
};
// Create road background - widened
var roadBackground = game.addChild(LK.getAsset('roadBackground', {
anchorX: 0.5,
anchorY: 0.5
}));
roadBackground.x = 1024; // Center of screen
roadBackground.y = 1366;
roadBackground.scaleX = 1.3; // Widen road by 30%
// Create green forest backgrounds on sides - narrowed
var leftForest = game.addChild(LK.getAsset('forestBackground', {
anchorX: 0.5,
anchorY: 0.5
}));
leftForest.x = 190; // Moved closer to center - narrower area
leftForest.y = 1366;
leftForest.scaleX = 0.8; // Make forest area narrower
var rightForest = game.addChild(LK.getAsset('forestBackground', {
anchorX: 0.5,
anchorY: 0.5
}));
rightForest.x = 1858; // Moved closer to center - narrower area
rightForest.y = 1366;
rightForest.scaleX = 0.8; // Make forest area narrower
// Create road borders - adjusted for wider road
var leftBorder = game.addChild(LK.getAsset('roadBorder', {
anchorX: 0.5,
anchorY: 0.5
}));
leftBorder.x = 390; // Moved outward for wider road
leftBorder.y = 1366;
var rightBorder = game.addChild(LK.getAsset('roadBorder', {
anchorX: 0.5,
anchorY: 0.5
}));
rightBorder.x = 1658; // Moved outward for wider road
rightBorder.y = 1366;
// Spawn initial lane lines to establish proper layering
spawnLaneLine();
// Create player
player = game.addChild(new Player());
player.scaleX = 2.4;
player.scaleY = 2.4;
// Create police chaser that follows the player (initially hidden)
policeChaser = game.addChild(new PoliceChaser());
policeChaser.scaleX = 2.2; // Slightly smaller than player
policeChaser.scaleY = 2.2;
policeChaser.x = player.x; // Start in same lane as player
policeChaser.visible = false; // Start hidden
policeChaser.alpha = 0; // Start transparent
// Create score display
var scoreText = new Text2('Score: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreText);
// Create health bar label
var healthLabel = new Text2('Lives:', {
size: 40,
fill: 0xFFFFFF
});
healthLabel.anchor.set(1, 0.5);
healthLabel.x = -200;
healthLabel.y = 100;
LK.gui.topRight.addChild(healthLabel);
// Distance display removed - only counting coins for score
// Create lives display as hearts in top right with better styling
var healthBars = [];
for (var h = 0; h < maxLives; h++) {
var heart = LK.getAsset('heart', {
anchorX: 0.5,
anchorY: 0.5
});
// Arrange hearts in two rows for better space usage with 10 hearts
var row = Math.floor(h / 5); // 5 hearts per row
var col = h % 5; // Column position within row
heart.x = -80 - col * 50; // Closer spacing for more hearts
heart.y = 80 + row * 50; // Two rows: first at y=80, second at y=130
heart.scaleX = 1.0; // Slightly smaller to fit more hearts
heart.scaleY = 1.0;
// Add a subtle glow effect
heart.alpha = 0.9;
// Only show the first 3 hearts initially (player starts with 3 lives)
if (h >= 3) {
heart.visible = false;
heart.alpha = 0;
}
LK.gui.topRight.addChild(heart);
healthBars.push(heart);
}
// Touch controls
game.down = function (x, y, obj) {
lastSwipeX = x;
lastSwipeY = y;
swipeStarted = true;
};
game.up = function (x, y, obj) {
if (swipeStarted) {
var deltaX = x - lastSwipeX;
var deltaY = y - lastSwipeY;
var swipeThreshold = 100;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
// Horizontal swipe
if (deltaX > swipeThreshold) {
player.moveRight();
} else if (deltaX < -swipeThreshold) {
player.moveLeft();
}
}
}
swipeStarted = false;
};
function spawnObstacle() {
var obstacle = new Obstacle();
var laneIndex = Math.floor(Math.random() * 3);
obstacle.x = player.lanes[laneIndex];
obstacle.y = -100;
obstacle.speed = gameSpeed;
obstacle.scaleX = 0.9;
obstacle.scaleY = 0.9;
// Set original lane index for lane changing
obstacle.originalLaneIndex = laneIndex;
// Enable lane changing after 2 minutes if game has been running long enough
if (gameTimer >= laneChangeStartTime) {
obstacle.canChangeLanes = Math.random() < 0.3; // 30% chance to be a lane changer
}
tween(obstacle, {
scaleX: 2.4,
scaleY: 2.4
}, {
duration: 400,
easing: tween.easeOut
});
obstacles.push(obstacle);
game.addChild(obstacle);
}
function spawnCoin() {
var coin = new Coin();
var laneIndex = Math.floor(Math.random() * 3);
var targetX = player.lanes[laneIndex];
var canSpawn = true;
// Check if there's an obstacle in this lane that would conflict
for (var i = 0; i < obstacles.length; i++) {
var obstacle = obstacles[i];
// Check if obstacle is in the same lane and close to spawn area
if (Math.abs(obstacle.x - targetX) < 50 && obstacle.y >= -300 && obstacle.y <= 200) {
canSpawn = false;
break;
}
}
// Check if player is in this lane and close to spawn area
if (canSpawn) {
if (Math.abs(player.x - targetX) < 100 && player.y >= 2000) {
canSpawn = false;
}
}
// Also check if there's a powerup in this lane that would conflict
if (canSpawn) {
for (var p = 0; p < powerups.length; p++) {
var powerup = powerups[p];
// Check if powerup is in the same lane and close to spawn area
if (Math.abs(powerup.x - targetX) < 50 && powerup.y >= -300 && powerup.y <= 200) {
canSpawn = false;
break;
}
}
}
// If we can't spawn in the selected lane, try other lanes
if (!canSpawn) {
var availableLanes = [];
for (var lane = 0; lane < 3; lane++) {
var laneX = player.lanes[lane];
var laneAvailable = true;
// Check for obstacles in this lane
for (var j = 0; j < obstacles.length; j++) {
var obs = obstacles[j];
if (Math.abs(obs.x - laneX) < 50 && obs.y >= -300 && obs.y <= 200) {
laneAvailable = false;
break;
}
}
// Check for player in this lane
if (laneAvailable) {
if (Math.abs(player.x - laneX) < 100 && player.y >= 2000) {
laneAvailable = false;
}
}
// Also check for powerups in this lane
if (laneAvailable) {
for (var k = 0; k < powerups.length; k++) {
var pup = powerups[k];
if (Math.abs(pup.x - laneX) < 50 && pup.y >= -300 && pup.y <= 200) {
laneAvailable = false;
break;
}
}
}
if (laneAvailable) {
availableLanes.push(lane);
}
}
// If no lanes are available, don't spawn coin this time
if (availableLanes.length === 0) {
return;
}
// Pick a random available lane
laneIndex = availableLanes[Math.floor(Math.random() * availableLanes.length)];
targetX = player.lanes[laneIndex];
}
coin.x = targetX;
coin.y = -50;
coin.speed = gameSpeed;
coin.scaleX = 0.5;
coin.scaleY = 0.5;
tween(coin, {
scaleX: 1,
scaleY: 1
}, {
duration: 500,
easing: tween.easeOut
});
coins.push(coin);
game.addChild(coin);
}
function spawnPowerUp() {
var powerup = new PowerUp();
var laneIndex = Math.floor(Math.random() * 3);
var targetX = player.lanes[laneIndex];
var canSpawn = true;
// Check if there's an obstacle in this lane that would conflict
for (var i = 0; i < obstacles.length; i++) {
var obstacle = obstacles[i];
// Check if obstacle is in the same lane and close to spawn area
if (Math.abs(obstacle.x - targetX) < 80 && obstacle.y >= -400 && obstacle.y <= 300) {
canSpawn = false;
break;
}
}
// If we can't spawn in the selected lane, try other lanes
if (!canSpawn) {
var availableLanes = [];
for (var lane = 0; lane < 3; lane++) {
var laneX = player.lanes[lane];
var laneAvailable = true;
// Check for obstacles in this lane
for (var j = 0; j < obstacles.length; j++) {
var obs = obstacles[j];
if (Math.abs(obs.x - laneX) < 80 && obs.y >= -400 && obs.y <= 300) {
laneAvailable = false;
break;
}
}
if (laneAvailable) {
availableLanes.push(lane);
}
}
// If no lanes are available, don't spawn powerup this time
if (availableLanes.length === 0) {
return;
}
// Pick a random available lane
laneIndex = availableLanes[Math.floor(Math.random() * availableLanes.length)];
targetX = player.lanes[laneIndex];
}
powerup.x = targetX;
powerup.y = -50;
powerup.speed = gameSpeed;
powerup.scaleX = 0.3;
powerup.scaleY = 0.3;
tween(powerup, {
scaleX: 1,
scaleY: 1
}, {
duration: 600,
easing: tween.easeOut
});
powerups.push(powerup);
game.addChild(powerup);
}
function spawnLaneLine() {
// Lane dividers between lanes - adjusted for wider road
var positions = [802, 1246]; // Between lanes 0-1 and 1-2, proportionally spaced
for (var i = 0; i < positions.length; i++) {
var laneLine = new LaneLine();
laneLine.x = positions[i];
laneLine.y = -20;
laneLine.speed = gameSpeed;
laneLines.push(laneLine);
// Simply add lane lines normally - layering optimization removed for performance
game.addChild(laneLine);
}
}
function spawnExhaustParticle() {
var particle = new ExhaustParticle();
particle.x = player.x + (Math.random() - 0.5) * 40;
particle.y = player.y + 20;
particle.scaleX = 0.5 + Math.random() * 0.5;
particle.scaleY = 0.5 + Math.random() * 0.5;
particle.alpha = 0.6 + Math.random() * 0.4;
exhaustParticles.push(particle);
game.addChild(particle);
}
function createFlameEffect(x, y, parentCar) {
// Create multiple flame particles (optimized amount)
for (var i = 0; i < 8; i++) {
// Balanced flame count for performance
var flame = new Flame();
flame.x = x + (Math.random() - 0.5) * 80; // Increased spread from 60 to 80
flame.y = y + Math.random() * 40; // Increased spread from 30 to 40
flame.scaleX = 1.2 + Math.random() * 0.8; // Increased base size from 0.5 to 1.2
flame.scaleY = 1.2 + Math.random() * 0.8; // Increased base size from 0.5 to 1.2
// Attach flame to police car if provided
if (parentCar) {
flame.parentCar = parentCar;
flame.offsetX = (Math.random() - 0.5) * 80;
flame.offsetY = Math.random() * 40 - 60; // Flames above the car
}
flames.push(flame);
game.addChild(flame);
}
// Create smoke particles (optimized amount)
for (var j = 0; j < 4; j++) {
// Balanced smoke particle count
var smoke = new Smoke();
smoke.x = x + (Math.random() - 0.5) * 50; // Increased spread from 40 to 50
smoke.y = y - 10 + Math.random() * 30; // Increased spread from 20 to 30
smoke.alpha = 0.4 + Math.random() * 0.3;
smoke.scaleX = 1.5 + Math.random() * 0.5; // Increased smoke size
smoke.scaleY = 1.5 + Math.random() * 0.5; // Increased smoke size
smokes.push(smoke);
game.addChild(smoke);
}
// Create debris particles for explosion effect (optimized amount)
for (var k = 0; k < 10; k++) {
var debris = new DebrisParticle();
debris.x = x + (Math.random() - 0.5) * 40;
debris.y = y + (Math.random() - 0.5) * 30;
debris.scaleX = 0.5 + Math.random() * 1.0;
debris.scaleY = 0.5 + Math.random() * 1.0;
debrisParticles.push(debris);
game.addChild(debris);
}
}
function spawnTrees() {
// Spawn trees on left side of road - adjusted for narrower forest
var leftPositions = [120, 200, 280, 340];
for (var i = 0; i < leftPositions.length; i++) {
// Randomly choose between Tree and Tree2
if (Math.random() < 0.65) {
var leftTree;
if (Math.random() < 0.5) {
leftTree = new Tree();
trees.push(leftTree);
} else {
leftTree = new Tree2();
trees2.push(leftTree);
}
leftTree.x = leftPositions[i];
leftTree.y = -80;
leftTree.speed = gameSpeed;
// Add some variation in scale - bigger trees
leftTree.scaleX = 1.2 + Math.random() * 0.8;
leftTree.scaleY = 1.2 + Math.random() * 0.8;
// Add slight tint variation for natural look
var tintVariation = 0.9 + Math.random() * 0.2;
leftTree.tint = tintVariation * 0x90EE90 | 0;
// Apply current day/night brightness
if (!isDay) {
leftTree.alpha = 0.6;
}
// Simply add trees normally - layering optimization removed for performance
game.addChild(leftTree);
}
// Spawn flowers in left forest area
if (Math.random() < 0.25) {
var leftFlower = new Flower();
leftFlower.x = leftPositions[i] + (Math.random() - 0.5) * 80;
leftFlower.y = -40;
leftFlower.speed = gameSpeed;
leftFlower.scaleX = 0.8 + Math.random() * 0.4;
leftFlower.scaleY = 0.8 + Math.random() * 0.4;
// Add flower color variations
var flowerColors = [0xff69b4, 0xff1493, 0xffffff, 0xffff00, 0xff4500];
leftFlower.tint = flowerColors[Math.floor(Math.random() * flowerColors.length)];
if (!isDay) {
leftFlower.alpha = 0.5;
}
flowers.push(leftFlower);
game.addChild(leftFlower);
}
}
// Spawn trees on right side of road - adjusted for narrower forest
var rightPositions = [1708, 1788, 1868, 1928];
for (var j = 0; j < rightPositions.length; j++) {
// Randomly choose between Tree and Tree2
if (Math.random() < 0.65) {
var rightTree;
if (Math.random() < 0.5) {
rightTree = new Tree();
trees.push(rightTree);
} else {
rightTree = new Tree2();
trees2.push(rightTree);
}
rightTree.x = rightPositions[j];
rightTree.y = -80;
rightTree.speed = gameSpeed;
// Add some variation in scale - bigger trees
rightTree.scaleX = 1.2 + Math.random() * 0.8;
rightTree.scaleY = 1.2 + Math.random() * 0.8;
// Add slight tint variation for natural look
var tintVariation = 0.9 + Math.random() * 0.2;
rightTree.tint = tintVariation * 0x90EE90 | 0;
// Apply current day/night brightness
if (!isDay) {
rightTree.alpha = 0.6;
}
// Simply add trees normally - layering optimization removed for performance
game.addChild(rightTree);
}
// Spawn flowers in right forest area
if (Math.random() < 0.25) {
var rightFlower = new Flower();
rightFlower.x = rightPositions[j] + (Math.random() - 0.5) * 80;
rightFlower.y = -40;
rightFlower.speed = gameSpeed;
rightFlower.scaleX = 0.8 + Math.random() * 0.4;
rightFlower.scaleY = 0.8 + Math.random() * 0.4;
// Add flower color variations
var flowerColors = [0xff69b4, 0xff1493, 0xffffff, 0xffff00, 0xff4500];
rightFlower.tint = flowerColors[Math.floor(Math.random() * flowerColors.length)];
if (!isDay) {
rightFlower.alpha = 0.5;
}
flowers.push(rightFlower);
game.addChild(rightFlower);
}
}
}
function spawnHeartPowerUp() {
var heartPowerup = new HeartPowerUp();
var laneIndex = Math.floor(Math.random() * 3);
var targetX = player.lanes[laneIndex];
var canSpawn = true;
// Check if there's an obstacle in this lane that would conflict
for (var i = 0; i < obstacles.length; i++) {
var obstacle = obstacles[i];
// Check if obstacle is in the same lane and close to spawn area
if (Math.abs(obstacle.x - targetX) < 80 && obstacle.y >= -400 && obstacle.y <= 300) {
canSpawn = false;
break;
}
}
// If we can't spawn in the selected lane, try other lanes
if (!canSpawn) {
var availableLanes = [];
for (var lane = 0; lane < 3; lane++) {
var laneX = player.lanes[lane];
var laneAvailable = true;
// Check for obstacles in this lane
for (var j = 0; j < obstacles.length; j++) {
var obs = obstacles[j];
if (Math.abs(obs.x - laneX) < 80 && obs.y >= -400 && obs.y <= 300) {
laneAvailable = false;
break;
}
}
if (laneAvailable) {
availableLanes.push(lane);
}
}
// If no lanes are available, don't spawn heart powerup this time
if (availableLanes.length === 0) {
return;
}
// Pick a random available lane
laneIndex = availableLanes[Math.floor(Math.random() * availableLanes.length)];
targetX = player.lanes[laneIndex];
}
heartPowerup.x = targetX;
heartPowerup.y = -50;
heartPowerup.speed = gameSpeed;
heartPowerup.scaleX = 0.3;
heartPowerup.scaleY = 0.3;
tween(heartPowerup, {
scaleX: 1,
scaleY: 1
}, {
duration: 600,
easing: tween.easeOut
});
heartPowerups.push(heartPowerup);
game.addChild(heartPowerup);
}
function spawnMovingBarrier() {
var barrier = new MovingBarrier();
// Choose random starting lane
var startLaneIndex = Math.floor(Math.random() * 3);
barrier.x = player.lanes[startLaneIndex];
barrier.y = -100;
barrier.speed = gameSpeed;
barrier.scaleX = 0.9;
barrier.scaleY = 0.9;
// Set movement bounds for one lane change only
barrier.setMovementBounds(startLaneIndex);
// Animate barrier scaling up when spawned
tween(barrier, {
scaleX: 2.4,
scaleY: 2.4
}, {
duration: 400,
easing: tween.easeOut
});
movingBarriers.push(barrier);
game.addChild(barrier);
}
function spawnRain() {
// Spawn multiple rain drops across the screen width (optimized amount)
for (var i = 0; i < 6; i++) {
var rainDrop = new RainDrop();
rainDrop.x = Math.random() * 2048; // Random X across screen width
rainDrop.y = -20 - Math.random() * 50; // Start above screen
rainDrop.speed = gameSpeed + 15 + Math.random() * 10; // Rain speed based on game speed
rainDrops.push(rainDrop);
game.addChild(rainDrop);
}
}
// Main game loop
game.update = function () {
// Update game timer
gameTimer++;
// Handle police chaser timing and visibility with 1-minute window limit
policeChaserTimer++;
policeChaserWindowTimer++;
// Reset the time window every minute
if (policeChaserWindowTimer >= policeChaserTimeWindow) {
policeChaserWindowTimer = 0;
policeChaserUsedTime = 0; // Reset used time for new window
console.log('Police chaser time window reset - new 1 minute period');
}
if (!policeChaserActive) {
// Check if it's time to activate police chaser and if we have time left in current window
if (policeChaserTimer >= policeChaserActivationTime && policeChaserUsedTime < policeChaserMaxChaseTime) {
// Calculate remaining time in current window
var remainingTime = policeChaserMaxChaseTime - policeChaserUsedTime;
// Activate police chaser
policeChaserActive = true;
policeChaserTimer = 0;
// Set chase duration to minimum of remaining time or desired duration (max 10 seconds)
policeChaserChaseDuration = Math.min(remainingTime, 600); // Max 10 seconds or remaining time
// Position police chaser off-screen behind player and make visible
policeChaser.visible = true;
policeChaser.alpha = 0; // Start transparent for smooth fade in
policeChaser.y = player.y + 1200; // Start well behind player off-screen
policeChaser.x = player.x; // Start in same lane as player
// Fade in police chaser first
tween(policeChaser, {
alpha: 1
}, {
duration: 1500,
easing: tween.easeInOut
});
// Smoothly move police chaser into proper following position
tween(policeChaser, {
y: player.y + policeChaser.followDistance
}, {
duration: 3000,
// Slower entry for smoother appearance
easing: tween.easeInOut
});
console.log('Police chaser activated for', Math.floor(policeChaserChaseDuration / 60), 'seconds. Used time:', Math.floor(policeChaserUsedTime / 60), 'Remaining:', Math.floor(remainingTime / 60));
}
} else {
// Police chaser is active, track used time and check if chase duration is over
policeChaserUsedTime++; // Track time being used
if (policeChaserTimer >= policeChaserChaseDuration || policeChaserUsedTime >= policeChaserMaxChaseTime) {
// Deactivate police chaser
policeChaserActive = false;
policeChaserTimer = 0;
// Set cooldown duration - wait until next window if we've used all time
if (policeChaserUsedTime >= policeChaserMaxChaseTime) {
// Used all time in window, wait until window resets
var timeUntilWindowReset = policeChaserTimeWindow - policeChaserWindowTimer;
policeChaserActivationTime = timeUntilWindowReset + 300; // Wait for window reset + 5 seconds
console.log('Police chaser time limit reached. Waiting', Math.floor(timeUntilWindowReset / 60), 'seconds for window reset');
} else {
// Still have time left, set normal cooldown
policeChaserActivationTime = 1800 + Math.random() * 1800; // 30-60 seconds
}
// Make police chaser fall behind by reducing its speed and moving it back
tween(policeChaser, {
y: policeChaser.y + 1000 // Move it further behind the player
}, {
duration: 4000,
// Slower transition over 4 seconds
easing: tween.easeInOut,
onFinish: function onFinish() {
// After falling behind, fade out and hide
tween(policeChaser, {
alpha: 0
}, {
duration: 1500,
// Longer fade out
easing: tween.easeInOut,
onFinish: function onFinish() {
policeChaser.visible = false;
// Reset position completely off-screen to prevent any issues
policeChaser.y = player.y + 1500;
}
});
}
});
console.log('Police chaser deactivated. Next activation in', Math.floor(policeChaserActivationTime / 60), 'seconds');
}
}
// Calculate current base speed with gradual increase and cycle boost
var currentBaseSpeed = Math.min(maxSpeed, baseGameSpeed + gameTimer * speedIncrement + cycleSpeedBoost);
// Apply night speed multiplier
gameSpeed = isDay ? currentBaseSpeed : currentBaseSpeed * nightSpeedMultiplier;
// Distance tracking removed - only counting coins for score
// Spawn obstacles - optimized spawn rates for better gameplay flow
obstacleSpawnTimer++;
var obstacleSpawnRate = isDay ? 150 + Math.random() * 60 : 120 + Math.random() * 90; // More balanced spawn rates
if (obstacleSpawnTimer > obstacleSpawnRate) {
spawnObstacle();
obstacleSpawnTimer = 0;
}
// Spawn coins - optimized spawn rate for better balance
coinSpawnTimer++;
var coinSpawnRate = Math.max(40, 100 - (gameSpeed - 8) * 3); // Balanced coin spawning
if (coinSpawnTimer > coinSpawnRate + Math.random() * 30) {
spawnCoin();
coinSpawnTimer = 0;
}
// Spawn powerups (rare spawn - every 1 minute)
powerupSpawnTimer++;
if (powerupSpawnTimer > 3600) {
// 1 minute (60 seconds * 60 fps)
spawnPowerUp();
powerupSpawnTimer = 0;
}
// Spawn heart powerups (every 3 minutes)
heartPowerupSpawnTimer++;
if (heartPowerupSpawnTimer > 10800) {
// 3 minutes (180 seconds * 60 fps)
spawnHeartPowerUp();
heartPowerupSpawnTimer = 0;
}
// Spawn moving barriers (every 45 seconds)
movingBarrierSpawnTimer++;
if (movingBarrierSpawnTimer > 2700) {
// 45 seconds (45 * 60 fps)
spawnMovingBarrier();
movingBarrierSpawnTimer = 0;
}
// Spawn lane lines
laneLineSpawnTimer++;
if (laneLineSpawnTimer > 30) {
spawnLaneLine();
laneLineSpawnTimer = 0;
}
// Spawn trees
treeSpawnTimer++;
if (treeSpawnTimer > 45) {
spawnTrees();
treeSpawnTimer = 0;
}
// Update obstacles
var currentPoliceOnScreen = false;
for (var i = obstacles.length - 1; i >= 0; i--) {
var obstacle = obstacles[i];
obstacle.speed = gameSpeed;
// Check if police car is on screen
if (obstacle.y >= -200 && obstacle.y <= 2900) {
currentPoliceOnScreen = true;
}
// Initialize fade tracking if needed
if (obstacle.lastY === undefined) obstacle.lastY = obstacle.y;
if (obstacle.isFadingOut === undefined) obstacle.isFadingOut = false;
// Check if obstacle just crossed the fade threshold
if (!obstacle.isFadingOut && obstacle.lastY <= 2800 && obstacle.y > 2800) {
obstacle.isFadingOut = true;
// Start fade out animation
tween(obstacle, {
alpha: 0,
scaleX: obstacle.scaleX * 0.7,
scaleY: obstacle.scaleY * 0.7
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
obstacle.destroy();
// Remove from obstacles array after fade completes
var index = obstacles.indexOf(obstacle);
if (index > -1) {
obstacles.splice(index, 1);
}
}
});
}
// Update last position
obstacle.lastY = obstacle.y;
// Skip further processing if already fading or destroyed
if (obstacle.isFadingOut || obstacle.y > 3500) {
continue;
}
// Check collision with player (skip if obstacle is already hit)
if (!obstacle.isHit && player.intersects(obstacle)) {
// Play crash sound
try {
LK.getSound('crash').play();
} catch (e) {
console.log('Crash sound error:', e);
}
// Reduce lives
lives--;
// Update health bar display - hide the lost heart
if (lives >= 0 && lives < maxLives) {
var lostHeart = healthBars[lives]; // Use current lives as index (lives already decremented)
if (lostHeart) {
// Animate heart disappearing with red flash
tween(lostHeart, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5,
tint: 0xff0000
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
lostHeart.visible = false;
}
});
}
}
// Flash screen and remove obstacle
LK.effects.flashScreen(0xff0000, 500);
// Mark obstacle as hit to prevent multiple collisions
obstacle.isHit = true;
// Check if game over
if (lives <= 0) {
// Add delay to allow crash sound to play before game over
LK.setTimeout(function () {
LK.showGameOver();
}, 800); // Wait 800ms for crash sound to play
return;
}
// Make player temporarily invulnerable
player.alpha = 0.5;
LK.setTimeout(function () {
player.alpha = 1.0;
}, 1500); // 1.5 seconds of invulnerability
continue;
}
}
// Handle police music based on police presence
if (currentPoliceOnScreen && !policeMusicPlaying) {
// Start police siren as a sound effect that loops instead of music
// This allows it to play alongside the background music
try {
var policeSiren = LK.getSound('policemusic');
if (policeSiren) {
policeSiren.loop = true;
policeSiren.play();
}
} catch (e) {
console.log('Police siren play error:', e);
}
policeMusicPlaying = true;
policeOnScreen = true;
} else if (!currentPoliceOnScreen && policeMusicPlaying) {
// Stop police siren sound
try {
var policeSiren = LK.getSound('policemusic');
if (policeSiren) {
policeSiren.stop();
}
} catch (e) {
console.log('Police siren stop error:', e);
}
policeMusicPlaying = false;
policeOnScreen = false;
} else if (currentPoliceOnScreen && policeMusicPlaying) {
// Ensure police siren continues playing if it stopped
try {
var policeSiren = LK.getSound('policemusic');
if (policeSiren && !policeSiren.playing) {
policeSiren.loop = true;
policeSiren.play();
}
} catch (e) {
console.log('Police siren restart error:', e);
}
}
// Update coins
for (var j = coins.length - 1; j >= 0; j--) {
var coin = coins[j];
coin.speed = gameSpeed;
// Remove coins that are off screen
if (coin.y > 2800) {
coin.destroy();
coins.splice(j, 1);
continue;
}
// Check collection
if (!coin.collected && player.intersects(coin)) {
coin.collected = true;
LK.setScore(LK.getScore() + 1); // Each coin is worth exactly 1 point
scoreText.setText('Score: ' + LK.getScore());
LK.getSound('collect').play();
// Enhanced visual effect with faster animation for higher speeds
var animationDuration = Math.max(200, 400 - (gameSpeed - 8) * 10);
tween(coin, {
alpha: 0,
scaleX: 2.5,
scaleY: 2.5,
y: coin.y - 50
}, {
duration: animationDuration,
easing: tween.easeOut,
onFinish: function onFinish() {
coin.destroy();
}
});
coins.splice(j, 1);
}
}
// Update powerups
for (var p = powerups.length - 1; p >= 0; p--) {
var powerup = powerups[p];
powerup.speed = gameSpeed;
// Remove powerups that are off screen or expired
if (powerup.y > 2800 || powerup.lifetime <= 0) {
powerup.destroy();
powerups.splice(p, 1);
continue;
}
// Check collection
if (!powerup.collected && player.intersects(powerup)) {
powerup.collected = true;
hasWeapon = true;
weaponTimer = weaponDuration; // 20 seconds
LK.getSound('collect').play();
// Visual effect
tween(powerup, {
alpha: 0,
scaleX: 3,
scaleY: 3
}, {
duration: 400,
onFinish: function onFinish() {
powerup.destroy();
}
});
powerups.splice(p, 1);
}
}
// Update heart powerups
for (var hp = heartPowerups.length - 1; hp >= 0; hp--) {
var heartPowerup = heartPowerups[hp];
heartPowerup.speed = gameSpeed;
// Remove heart powerups that are off screen or expired
if (heartPowerup.y > 2800 || heartPowerup.lifetime <= 0) {
heartPowerup.destroy();
heartPowerups.splice(hp, 1);
continue;
}
// Check collection
if (!heartPowerup.collected && player.intersects(heartPowerup)) {
heartPowerup.collected = true;
// Add one life if not at maximum
if (lives < maxLives) {
lives++;
// Show the health bar corresponding to gained life with animation
if (lives > 0 && lives <= maxLives) {
var restoredHeart = healthBars[lives - 1]; // Use lives-1 as index (lives already incremented)
if (restoredHeart) {
restoredHeart.visible = true;
restoredHeart.alpha = 0;
restoredHeart.scaleX = 2.0;
restoredHeart.scaleY = 2.0;
restoredHeart.tint = 0x00ff00; // Green flash
// Animate heart appearing
tween(restoredHeart, {
alpha: 0.9,
scaleX: 1.0,
scaleY: 1.0,
tint: 0xffffff
}, {
duration: 600,
easing: tween.easeOut
});
}
}
}
LK.getSound('collect').play();
// Visual effect with heart animation
tween(heartPowerup, {
alpha: 0,
scaleX: 2.5,
scaleY: 2.5,
y: heartPowerup.y - 100
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
heartPowerup.destroy();
}
});
heartPowerups.splice(hp, 1);
}
}
// Update lane lines
for (var k = laneLines.length - 1; k >= 0; k--) {
var laneLine = laneLines[k];
laneLine.speed = gameSpeed;
// Remove lane lines that are off screen
if (laneLine.y > 2800) {
laneLine.destroy();
laneLines.splice(k, 1);
}
}
// Update trees
for (var t = trees.length - 1; t >= 0; t--) {
var tree = trees[t];
tree.speed = gameSpeed;
// Remove trees that are off screen - increased threshold to prevent immediate disappearing
if (tree.y > 3200) {
tree.destroy();
trees.splice(t, 1);
}
}
// Update trees2
for (var t2 = trees2.length - 1; t2 >= 0; t2--) {
var tree2 = trees2[t2];
tree2.speed = gameSpeed;
// Remove trees2 that are off screen - increased threshold to prevent immediate disappearing
if (tree2.y > 3200) {
tree2.destroy();
trees2.splice(t2, 1);
}
}
// Update flowers
for (var fl = flowers.length - 1; fl >= 0; fl--) {
var flower = flowers[fl];
flower.speed = gameSpeed;
// Remove flowers that are off screen
if (flower.y > 3200) {
flower.destroy();
flowers.splice(fl, 1);
}
}
// Handle rain cycle logic
if (!isRaining) {
// Check if it's time to start raining
nextRainEvent++;
if (nextRainEvent >= rainCooldown) {
// Start rain cycle
isRaining = true;
rainTimer = 0;
// Random rain duration: 10-60 seconds (600-3600 frames at 60fps)
rainDuration = 600 + Math.random() * 3000; // 10 seconds to 1 minute
nextRainEvent = 0;
// Start rain music
if (!rainSoundPlaying) {
try {
LK.playMusic('rain');
} catch (e) {
console.log('Rain music play error:', e);
}
rainSoundPlaying = true;
}
// Initialize thunder timing for this rain period
thunderTimer = 0;
nextThunderTime = 180 + Math.random() * 600; // First thunder in 3-13 seconds
console.log('Rain started for', Math.floor(rainDuration / 60), 'seconds');
}
} else {
// Currently raining
rainTimer++;
// Spawn rain particles while raining - optimized frequency for performance
rainSpawnTimer++;
if (rainSpawnTimer >= 6) {
// Spawn rain every 6 frames for balanced performance
spawnRain();
rainSpawnTimer = 0;
}
// Handle thunder during rain
thunderTimer++;
if (thunderTimer >= nextThunderTime) {
// Play thunder sound
try {
var thunderSound = LK.getSound('thunder');
if (thunderSound) {
thunderSound.play();
}
} catch (e) {
console.log('Thunder sound play error:', e);
}
// Add thunder flash effect - bright white flash
LK.effects.flashScreen(0xffffff, 300); // White flash for 300ms
// Set next thunder time (random between 5-20 seconds)
nextThunderTime = thunderTimer + (300 + Math.random() * 900); // 5-20 seconds at 60fps
}
// Check if rain should stop
if (rainTimer >= rainDuration) {
// Stop rain cycle
isRaining = false;
rainTimer = 0;
// Random cooldown period: 30-180 seconds (1800-10800 frames at 60fps)
rainCooldown = 1800 + Math.random() * 9000; // 30 seconds to 3 minutes
nextRainEvent = 0;
// Reset thunder timer
thunderTimer = 0;
nextThunderTime = 0;
// Gradually fade out existing rain drops
for (var fadeRain = 0; fadeRain < rainDrops.length; fadeRain++) {
var rainDrop = rainDrops[fadeRain];
tween(rainDrop, {
alpha: 0,
speed: rainDrop.speed * 0.3
}, {
duration: 2000 + Math.random() * 1000,
easing: tween.easeOut
});
}
console.log('Rain stopped. Next rain in', Math.floor(rainCooldown / 60), 'seconds');
}
}
// Stop rain music only when all rain effects are complete
if (!isRaining && rainSoundPlaying && rainDrops.length === 0) {
try {
LK.stopMusic();
// Restart background music after rain stops
LK.playMusic('bgmusic');
} catch (e) {
console.log('Rain music stop error:', e);
}
rainSoundPlaying = false;
}
// Spawn exhaust particles - optimized frequency for performance
exhaustSpawnTimer++;
if (exhaustSpawnTimer >= 12) {
spawnExhaustParticle();
exhaustSpawnTimer = 0;
}
// Update exhaust particles
for (var e = exhaustParticles.length - 1; e >= 0; e--) {
var particle = exhaustParticles[e];
// Remove particles that are expired or off screen
if (particle.lifetime <= 0 || particle.y < -50) {
particle.destroy();
exhaustParticles.splice(e, 1);
}
}
// Update flame particles
for (var f = flames.length - 1; f >= 0; f--) {
var flame = flames[f];
// Remove flames that are expired or off screen
if (flame.lifetime <= 0 || flame.y < -50) {
flame.destroy();
flames.splice(f, 1);
}
}
// Update smoke particles
for (var s = smokes.length - 1; s >= 0; s--) {
var smoke = smokes[s];
// Remove smoke that is expired or off screen
if (smoke.lifetime <= 0 || smoke.y < -50) {
smoke.destroy();
smokes.splice(s, 1);
}
}
// Update debris particles
for (var d = debrisParticles.length - 1; d >= 0; d--) {
var debris = debrisParticles[d];
// Remove debris that is expired or off screen
if (debris.lifetime <= 0 || debris.y > 2800) {
debris.destroy();
debrisParticles.splice(d, 1);
}
}
// Update rain particles
for (var r = rainDrops.length - 1; r >= 0; r--) {
var rain = rainDrops[r];
// Remove rain drops that are off screen
if (rain.y > 2800 || rain.x < -50 || rain.x > 2098) {
rain.destroy();
rainDrops.splice(r, 1);
}
}
// Update moving barriers
for (var mb = movingBarriers.length - 1; mb >= 0; mb--) {
var barrier = movingBarriers[mb];
barrier.speed = gameSpeed;
// Initialize fade tracking if needed
if (barrier.lastY === undefined) barrier.lastY = barrier.y;
if (barrier.isFadingOut === undefined) barrier.isFadingOut = false;
// Check if barrier just crossed the fade threshold
if (!barrier.isFadingOut && barrier.lastY <= 2800 && barrier.y > 2800) {
barrier.isFadingOut = true;
// Start fade out animation
tween(barrier, {
alpha: 0,
scaleX: barrier.scaleX * 0.7,
scaleY: barrier.scaleY * 0.7
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
barrier.destroy();
// Remove from movingBarriers array after fade completes
var index = movingBarriers.indexOf(barrier);
if (index > -1) {
movingBarriers.splice(index, 1);
}
}
});
}
// Update last position
barrier.lastY = barrier.y;
// Skip further processing if already fading or destroyed
if (barrier.isFadingOut || barrier.y > 3500) {
continue;
}
// Check collision with player (skip if barrier is already hit)
if (!barrier.isHit && player.intersects(barrier)) {
// Play crash sound
try {
LK.getSound('crash').play();
} catch (e) {
console.log('Crash sound error:', e);
}
// Reduce lives
lives--;
// Update health bar display - hide the lost heart
if (lives >= 0 && lives < maxLives) {
var lostHeart = healthBars[lives]; // Use current lives as index (lives already decremented)
if (lostHeart) {
// Animate heart disappearing with red flash
tween(lostHeart, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5,
tint: 0xff0000
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
lostHeart.visible = false;
}
});
}
}
// Flash screen and mark barrier as hit
LK.effects.flashScreen(0xff0000, 500);
barrier.isHit = true;
// Check if game over
if (lives <= 0) {
LK.setTimeout(function () {
LK.showGameOver();
}, 800);
return;
}
// Make player temporarily invulnerable
player.alpha = 0.5;
LK.setTimeout(function () {
player.alpha = 1.0;
}, 1500);
continue;
}
// Check bullet collision with moving barriers
for (var bb = bullets.length - 1; bb >= 0; bb--) {
var bullet = bullets[bb];
if (bullet && !barrier.isHit && bullet.intersects(barrier)) {
// Play police hit sound
try {
LK.getSound('policeHit').play();
} catch (e) {
console.log('Police hit sound error:', e);
}
// Destroy bullet immediately
bullet.destroy();
bullets.splice(bb, 1);
// Mark barrier as hit
barrier.isHit = true;
// Create flame effect at barrier position
createFlameEffect(barrier.x, barrier.y - 50, barrier);
// Create gold coins flying out when barrier is destroyed (optimized amount)
for (var goldCount = 0; goldCount < 5; goldCount++) {
var goldCoin = new Coin();
goldCoin.x = barrier.x + (Math.random() - 0.5) * 100;
goldCoin.y = barrier.y + (Math.random() - 0.5) * 80;
goldCoin.scaleX = 0.8 + Math.random() * 0.4;
goldCoin.scaleY = 0.8 + Math.random() * 0.4;
goldCoin.collected = false;
goldCoin.speed = gameSpeed * 0.5;
goldCoin.tint = 0xFFD700;
var randomX = goldCoin.x + (Math.random() - 0.5) * 200;
var randomY = goldCoin.y + Math.random() * 150 + 50;
tween(goldCoin, {
x: randomX,
y: randomY,
rotation: Math.PI * 4
}, {
duration: 800 + Math.random() * 400,
easing: tween.easeOut
});
coins.push(goldCoin);
game.addChild(goldCoin);
}
// Create burning effect
tween(barrier, {
tint: 0x000000,
alpha: 0
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
barrier.isDestroyed = true;
barrier.destroy();
var index = movingBarriers.indexOf(barrier);
if (index > -1) {
movingBarriers.splice(index, 1);
}
}
});
break;
}
}
}
// Handle weapon system
if (hasWeapon) {
weaponTimer--;
if (weaponTimer <= 0) {
hasWeapon = false;
}
// Shoot bullets every 15 frames for balanced firing rate
if (LK.ticks % 15 === 0) {
var bullet = new Bullet();
bullet.x = player.x;
bullet.y = player.y - 100;
bullets.push(bullet);
game.addChild(bullet);
// Play gun sound with reduced volume for less audio clutter
try {
var gunSound = LK.getSound('gunshot');
gunSound.volume = 0.2;
gunSound.play();
} catch (e) {
console.log('Gun sound error:', e);
}
}
}
// Update bullets
for (var b = bullets.length - 1; b >= 0; b--) {
var bullet = bullets[b];
// Remove bullets that are off screen
if (bullet.y < -50) {
bullet.destroy();
bullets.splice(b, 1);
continue;
}
// Check bullet collision with obstacles
for (var o = obstacles.length - 1; o >= 0; o--) {
var obstacle = obstacles[o];
if (!obstacle.isHit && bullet.intersects(obstacle)) {
// Play police hit sound
try {
LK.getSound('policeHit').play();
} catch (e) {
console.log('Police hit sound error:', e);
}
// Destroy bullet immediately
bullet.destroy();
bullets.splice(b, 1);
// Mark obstacle as hit to prevent further collisions
obstacle.isHit = true;
// Create flame effect at police car position with car reference
createFlameEffect(obstacle.x, obstacle.y - 50, obstacle);
// Create 6 gold coins flying out when police car is destroyed (optimized amount)
for (var goldCount = 0; goldCount < 6; goldCount++) {
var goldCoin = new Coin();
goldCoin.x = obstacle.x + (Math.random() - 0.5) * 100;
goldCoin.y = obstacle.y + (Math.random() - 0.5) * 80;
goldCoin.scaleX = 0.8 + Math.random() * 0.4;
goldCoin.scaleY = 0.8 + Math.random() * 0.4;
goldCoin.collected = false;
goldCoin.speed = gameSpeed * 0.5; // Slower than normal coins
// Make coins golden colored
goldCoin.tint = 0xFFD700;
// Add random movement with tween animation
var randomX = goldCoin.x + (Math.random() - 0.5) * 200;
var randomY = goldCoin.y + Math.random() * 150 + 50;
tween(goldCoin, {
x: randomX,
y: randomY,
rotation: Math.PI * 4
}, {
duration: 800 + Math.random() * 400,
easing: tween.easeOut
});
coins.push(goldCoin);
game.addChild(goldCoin);
}
// Create burning effect with flames
tween(obstacle, {
tint: 0x000000,
alpha: 0
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
obstacle.isDestroyed = true; // Mark as destroyed for flame cleanup
obstacle.destroy();
// Remove from obstacles array after animation completes
var index = obstacles.indexOf(obstacle);
if (index > -1) {
obstacles.splice(index, 1);
}
}
});
break;
}
}
}
// Update day/night cycle
dayNightTimer++;
if (dayNightTimer >= dayNightCycleDuration) {
transitionDayNight();
dayNightTimer = 0;
}
// Police exhaust particles now stay in their natural layer order
// Road color no longer changes automatically
};
// Road color change function (kept for potential future use)
function changeRoadColor(newColor) {
roadBackground.tint = newColor;
}
// Day/night cycle transition function
function transitionDayNight() {
if (isTransitioning) return;
isTransitioning = true;
isDay = !isDay;
var targetColors = isDay ? dayColors : nightColors;
var transitionDuration = 2000; // 2 seconds transition
// Increment cycle count and apply speed boost when transitioning to day (completing full cycle)
if (isDay) {
cycleCount++;
cycleSpeedBoost = cycleCount * speedBoostPerCycle;
}
// Immediately update game speed for the new cycle
var currentBaseSpeed = Math.min(maxSpeed, baseGameSpeed + gameTimer * speedIncrement + cycleSpeedBoost);
gameSpeed = isDay ? currentBaseSpeed : currentBaseSpeed * nightSpeedMultiplier;
// Transition background color
tween(game, {
backgroundColor: targetColors.sky
}, {
duration: transitionDuration,
easing: tween.easeInOut
});
// Transition forest backgrounds
tween(leftForest, {
tint: targetColors.forest
}, {
duration: transitionDuration,
easing: tween.easeInOut
});
tween(rightForest, {
tint: targetColors.forest
}, {
duration: transitionDuration,
easing: tween.easeInOut
});
// Transition road background
tween(roadBackground, {
tint: targetColors.road
}, {
duration: transitionDuration,
easing: tween.easeInOut,
onFinish: function onFinish() {
isTransitioning = false;
}
});
// Adjust trees brightness for night/day
var treeBrightness = isDay ? 1.0 : 0.6;
for (var i = 0; i < trees.length; i++) {
var tree = trees[i];
tween(tree, {
alpha: treeBrightness
}, {
duration: transitionDuration,
easing: tween.easeInOut
});
}
// Adjust trees2 brightness for night/day
for (var i2 = 0; i2 < trees2.length; i2++) {
var tree2 = trees2[i2];
tween(tree2, {
alpha: treeBrightness
}, {
duration: transitionDuration,
easing: tween.easeInOut
});
}
// Adjust flowers brightness for night/day
var flowerBrightness = isDay ? 1.0 : 0.5;
for (var f = 0; f < flowers.length; f++) {
var flower = flowers[f];
tween(flower, {
alpha: flowerBrightness
}, {
duration: transitionDuration,
easing: tween.easeInOut
});
}
// Control player headlights based on day/night
var headlightAlpha = isDay ? 0 : 0.8;
tween(player.leftHeadlight, {
alpha: headlightAlpha
}, {
duration: transitionDuration,
easing: tween.easeInOut
});
tween(player.rightHeadlight, {
alpha: headlightAlpha
}, {
duration: transitionDuration,
easing: tween.easeInOut
});
}
// Spawn initial powerup at game start
spawnPowerUp();
// Initialize first rain cycle
rainCooldown = 1800 + Math.random() * 9000; // 30 seconds to 3 minutes before first rain
nextRainEvent = 0;
// Start background music - this will be the base music layer
LK.playMusic('bgmusic', {
fade: {
start: 0,
end: 1,
duration: 1000
}
});
;
Polis arabası kuş bakışı. In-Game asset. 2d. High contrast. No shadows
Düz gri renk. In-Game asset. 2d. High contrast. No shadows
Kuş bakışı agaç. In-Game asset. 2d. High contrast. No shadows
Kalp 3d. In-Game asset. 2d. High contrast. No shadows
Kırmızı ve sarı gradient renk. In-Game asset. 2d. High contrast. No shadows
Çiçek kuş bakışı. In-Game asset. 2d. High contrast. No shadows
Kuş bakışı polis aracı. In-Game asset. 2d. High contrast. No shadows