/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { highScore: 0 }); /**** * Classes ****/ var NonPerfectMessage = Container.expand(function () { var self = Container.call(this); var messageText = new Text2('Bad timing', { size: 120, fill: 0xFF0000, stroke: 0xffffff, strokeThickness: 8 }); messageText.anchor.set(0.5, 0.5); self.addChild(messageText); self.alpha = 1; self.scale.set(0.1); self.lifespan = 60; // 1 second at 60fps self.initialized = false; self.creationTime = Date.now(); // Track when message was created self.init = function () { if (self.initialized) { return; } self.initialized = true; // First tween: grow from small to large tween(self.scale, { x: 1.0, y: 1.0 }, { duration: 300, easing: tween.easeOut, onComplete: function onComplete() { // Second tween: wait a bit then fade out LK.setTimeout(function () { tween(self, { alpha: 0 }, { duration: 300, easing: tween.easeIn, onComplete: function onComplete() { // Find and remove from perfectMessages array var index = perfectMessages.indexOf(self); if (index > -1) { perfectMessages.splice(index, 1); } // Remove from game first before destroying if (self.parent) { self.parent.removeChild(self); } self.destroy(); } }); }, 600); } }); }; self.update = function () { if (!self.initialized) { self.init(); } // Manual animation as fallback if tweens fail if (!self.initialized) { self.lifespan--; if (self.lifespan <= 0) { // Find and remove from perfectMessages array var index = perfectMessages.indexOf(self); if (index > -1) { perfectMessages.splice(index, 1); } self.destroy(); return; } // Animation effect if (self.lifespan > 45) { // Growing phase self.scale.set(0.1 + (1.0 - 0.1) * (1 - (self.lifespan - 45) / 15)); } else if (self.lifespan < 15) { // Fading out phase self.alpha = self.lifespan / 15; } } }; return self; }); var Palm = Container.expand(function () { var self = Container.call(this); var palmGraphics = self.attachAsset('palm', { anchorX: 0.5, anchorY: 0.5 }); self.width = palmGraphics.width; self.height = palmGraphics.height; self.isMoving = false; self.direction = "left"; // Default direction self.lastY = 0; self.update = function () { // Animation will be handled by tweens // This just ensures we have an update method for tracking }; return self; }); var PerfectMessage = Container.expand(function () { var self = Container.call(this); var messageText = new Text2('PERFECT! +2!', { size: 120, fill: 0xFFF000, stroke: 0xFF0000, strokeThickness: 8 }); messageText.anchor.set(0.5, 0.5); self.addChild(messageText); self.alpha = 1; self.scale.set(0.1); self.lifespan = 60; // 1 second at 60fps self.initialized = false; self.creationTime = Date.now(); // Track when message was created self.init = function () { if (self.initialized) { return; } self.initialized = true; // First tween: grow from small to large tween(self.scale, { x: 1.0, y: 1.0 }, { duration: 300, easing: tween.easeOut, onComplete: function onComplete() { // Second tween: wait a bit then fade out LK.setTimeout(function () { tween(self, { alpha: 0 }, { duration: 300, easing: tween.easeIn, onComplete: function onComplete() { // Find and remove from perfectMessages array var index = perfectMessages.indexOf(self); if (index > -1) { perfectMessages.splice(index, 1); } // Remove from game first before destroying if (self.parent) { self.parent.removeChild(self); } self.destroy(); } }); }, 600); } }); }; self.update = function () { if (!self.initialized) { self.init(); } // Manual animation as fallback if tweens fail if (!self.initialized) { self.lifespan--; if (self.lifespan <= 0) { // Find and remove from perfectMessages array var index = perfectMessages.indexOf(self); if (index > -1) { perfectMessages.splice(index, 1); } self.destroy(); return; } // Animation effect if (self.lifespan > 45) { // Growing phase self.scale.set(0.1 + (1.0 - 0.1) * (1 - (self.lifespan - 45) / 15)); } else if (self.lifespan < 15) { // Fading out phase self.alpha = self.lifespan / 15; } } }; return self; }); var PlayerCar = Container.expand(function () { var self = Container.call(this); var carGraphics = self.attachAsset('playerCar', { anchorX: 0.5, anchorY: 0.5 }); self.width = carGraphics.width; self.height = carGraphics.height; self.speed = 0; self.maxSpeed = 15; self.steering = 0; self.crashed = false; self.lastX = 0; self.bubble = null; self.crash = function () { if (!self.crashed) { self.crashed = true; // 1. First, play the crash sound LK.getSound('crash').play(); // 2. Second, mirror vertically the player Car carGraphics.scaleY = -1; // Flash effect for visual feedback LK.effects.flashObject(self, 0xFF0000, 1000); // 3. Stop the game by setting speed to 0 self.speed = 0; // 4. After 2 seconds, show GameOver LK.setTimeout(function () { LK.showGameOver(); }, 2000); } }; self.update = function () { // Save last position for movement detection self.lastX = self.x; // Handle car physics if (!self.crashed) { self.speed = Math.min(self.maxSpeed, self.speed + 0.1); } // Apply steering if not crashed if (!self.crashed) { self.x += self.steering; } // Keep car within bounds if (self.x < self.width / 2) { self.x = self.width / 2; } if (self.x > 2048 - self.width / 2) { self.x = 2048 - self.width / 2; } // Check if car has moved horizontally and has a bubble if (Math.abs(self.x - self.lastX) > 0.1 && self.bubble && self.bubble.parent) { // Remove bubble immediately when car moves if (self.bubble.parent) { self.bubble.parent.removeChild(self.bubble); } self.bubble.destroy(); self.bubble = null; } }; return self; }); var RoadLine = Container.expand(function () { var self = Container.call(this); var lineGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.width = lineGraphics.width; self.height = lineGraphics.height; self.isMoving = false; self.lastY = 0; self.update = function () { // Animation will be handled by tweens // This just ensures we have an update method for tracking }; return self; }); // RoadLine class removed as requested var TrafficCar = Container.expand(function () { var self = Container.call(this); // Randomly select one of the available traffic car assets var carAssets = ['trafficCar', 'trafficCar2', 'trafficCar3', 'trafficCar4']; var selectedCarAsset = carAssets[Math.floor(Math.random() * carAssets.length)]; self.direction = Math.random() > 0.5 ? 1 : -1; // Random direction: -1 = left, 1 = right var carGraphics = self.attachAsset(selectedCarAsset, { anchorX: 0.5, anchorY: 0.5, x: self.direction == -1 ? 0 : 200 }); self.width = carGraphics.width; self.height = carGraphics.height; self.speed = 0.1 * level * SPEED_LEVEL_FACTOR; self.direction = Math.random() > 0.5 ? 1 : -1; // Random direction: -1 = left, 1 = right self.startY = 1300; // Starting Y position (further up the screen) self.targetY = 2732 + self.height; // Y position when fully off-screen (bottom) self.initialScale = 0.1; self.targetScale = 2.0; // Set target X position in one of the specified ranges self.targetX = Math.random() > 0.5 ? Math.random() * 500 + 2000 : Math.random() * 100; // Set initial scale self.scale.set(self.initialScale); self.update = function () { // Calculate progress towards target Y var progress = (self.y - self.startY) / (self.targetY - self.startY); progress = Math.max(0, Math.min(1, progress)); // Clamp between 0 and 1 // Interpolate scale based on progress var currentScale = self.initialScale + (self.targetScale - self.initialScale) * progress; self.scale.set(currentScale); // Calculate X position based on progress (linear interpolation) var startX = 2048 / 2; // Center of screen self.x = startX + (self.targetX - startX) * progress; // Apply level-based speed increase self.speed += progress * level * SPEED_LEVEL_FACTOR; // Update Y position with speed that increases as the car grows self.y += self.speed; // Destroy if off screen if (self.y > self.targetY) { // Check against targetY // Find and remove self from trafficCars array var index = trafficCars.indexOf(self); if (index > -1) { trafficCars.splice(index, 1); // Check for perfect timing - if honk was within 1 second (1000ms) before or after car disappears var currentTime = Date.now(); var timeDifference = Math.abs(currentTime - lastHonkTime); if (timeDifference <= 500 * 2 / Math.min(3, level)) { // Perfect timing! Add 2 extra points LK.setScore(LK.getScore() + 3); // +1 for normal disappearance, +2 for perfect timing // Create and display perfect message var perfectMsg = new PerfectMessage(); perfectMsg.x = centerX; perfectMsg.y = centerY; perfectMsg.creationTime = Date.now(); // Set creation time game.addChild(perfectMsg); perfectMessages.push(perfectMsg); // Explicitly call init to ensure animation starts immediately perfectMsg.init(); } else if (timeDifference > 500 * 2 / Math.min(3, level) && lastHonkTime > 0 && lastHonkTime > currentTime - 3000) { // Bad timing! Show a non-perfect message only if honk was played AFTER this car crossed // (checking if honk happened within last 3 seconds to ensure it relates to this car) var nonPerfectMsg = new NonPerfectMessage(); nonPerfectMsg.x = centerX; nonPerfectMsg.y = centerY; nonPerfectMsg.creationTime = Date.now(); // Set creation time game.addChild(nonPerfectMsg); perfectMessages.push(nonPerfectMsg); // Add to the same array for cleanup // Explicitly call init to ensure animation starts immediately nonPerfectMsg.init(); // Normal score increment LK.setScore(LK.getScore() + 1); } else { // Normal score increment LK.setScore(LK.getScore() + 1); } scoreText.setText(LK.getScore().toString()); scoreText.setStyle({ tint: 0xffffff, stroke: 0xffffff, strokeThickness: 5 }); // List of available sounds excluding crash var sounds = ['blind', 'getOff', 'holdingTraffic', 'learnToDrive', 'license', 'moveOver', 'speedUp', 'whatAreYouDoing']; // Get current last played sound from game variable or initialize it var lastPlayedSound = game.lastPlayedSound || ''; // Filter out the last played sound if it exists var availableSounds = sounds.filter(function (sound) { return sound !== lastPlayedSound; }); // Select a random sound from the filtered list var randomSound = availableSounds[Math.floor(Math.random() * availableSounds.length)]; // Store the selected sound as the last played sound game.lastPlayedSound = randomSound; // Play the selected sound LK.getSound(randomSound).play(); // Check if player already has a bubble if (player.bubble && player.bubble.parent) { player.bubble.parent.removeChild(player.bubble); player.bubble.destroy(); } // Show bubble over player car var bubble = LK.getAsset('bubble', { anchorX: 0.5, anchorY: 1.0, x: player.x, y: player.y - player.height / 2 }); game.addChild(bubble); bubble.scale.set(0); // Store reference to the bubble in player object player.bubble = bubble; // Store creation time for auto-cleanup bubble.creationTime = Date.now(); // Animate the bubble appearing and disappearing tween(bubble.scale, { x: 0.8, y: 0.8 }, { duration: 200, easing: tween.easeOut, onComplete: function onComplete() { // Wait for 0.3 seconds then fade out (this happens if no movement removed it) LK.setTimeout(function () { // Check if bubble still exists and hasn't been removed by movement if (bubble && bubble.parent) { tween(bubble, { alpha: 0 }, { duration: 200, easing: tween.easeIn, onComplete: function onComplete() { if (bubble.parent) { bubble.parent.removeChild(bubble); } bubble.destroy(); // Clear reference in player if (player.bubble === bubble) { player.bubble = null; } } }); } }, 800); // Increased to ensure bubble shows for 1 second total } }); } self.destroy(); } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // Game state variables var gameStarted = false; var gameTime = 30; // Initial time in seconds var distance = 0; var level = 1; var checkpointDistance = 500; // Distance between checkpoints var nextCheckpointAt = checkpointDistance; var trafficCars = []; var obstacles = []; var checkpoints = []; var timeBonuses = []; var difficultyTimer; var lastHonkTime = 0; // Track when player last honked var perfectMessages = []; // Array to store perfect timing messages // Create container groups var palmGroup = new Container(); var roadLinesGroup = new Container(); var SPEED_LEVEL_FACTOR = 3; // Create road lines var ROAD_LINE_COUNT = 15; var roadLines = []; var BASE_ROAD_LINE_COOLDOWN = 20; // Base spawn cooldown var roadLineSpawnCooldown = 20; // Spawn a road line every 0.33 seconds if needed var roadLineSpawnTimer = 0; // Timer to track cooldown // Create palm trees var PALM_COUNT = 40; var palms = []; var centerX = 2048 / 2; // Center of screen X var centerY = 2732 / 2; // Center of screen Y var BASE_PALM_COOLDOWN = 10; // Base spawn cooldown var palmSpawnCooldown = 10; // Spawn a palm every 0.5 seconds if needed var palmSpawnTimer = 0; // Timer to track cooldown var lastSpawnSide = "right"; // Track the last side a palm was spawned on // Sway parameters var swayCounter = 0; // Create road var road = LK.getAsset('road', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 + 50, y: 2732 / 2 - 100 // y: 1000 }); game.addChild(road); game.addChild(palmGroup); game.addChild(roadLinesGroup); // Road lines removed as requested // Create player car var player = new PlayerCar(); player.x = 2048 / 2; player.y = 2732 - 300; game.addChild(player); var scoreLabel = new Text2('Score:', { size: 80, fill: 0xFFFFFF, stroke: 0xFFFFFF, strokeThickness: 5 }); scoreLabel.anchor.set(0, 0); scoreLabel.x = -150; LK.gui.top.addChild(scoreLabel); var scoreText = new Text2('0', { size: 80, fill: 0xFFFFFF, stroke: 0xFFFFFF, strokeThickness: 5 }); scoreText.anchor.set(0, 0); scoreText.x = 100; LK.gui.top.addChild(scoreText); var levelLabel = new Text2('Level:', { size: 80, fill: 0xFFFFFF, stroke: 0xFFFFFF, strokeThickness: 5 }); levelLabel.anchor.set(0, 0); levelLabel.x = -150; levelLabel.y = 100; // Offset below time LK.gui.top.addChild(levelLabel); var levelText = new Text2('1', { size: 80, fill: 0xFFFFFF, stroke: 0xFFFFFF, strokeThickness: 5 }); levelText.anchor.set(0, 0); levelText.x = 100; levelText.y = 100; // Offset below time LK.gui.top.addChild(levelText); var startText = new Text2('TAP TO START\nTAP TO 🔊\nPERFECTLY TIMED 🔊 = +2', { size: 120, fill: 0xFFFFFF, stroke: 0xFFFFFF, strokeThickness: 8, align: 'center' }); startText.anchor.set(0.5, 0.5); LK.gui.center.addChild(startText); // Functions to spawn game elements function spawnTrafficCar() { // Only spawn a new car if there are no cars on screen if (trafficCars.length === 0) { var car = new TrafficCar(); car.x = 2048 / 2; // Start in the middle of the screen car.y = car.startY; // Start at the defined startY car.speed = 0.1 * level * SPEED_LEVEL_FACTOR; trafficCars.push(car); game.addChild(car); } // Schedule next check for spawning with faster spawns at higher levels var nextSpawnTime = Math.max(200, 1000 / level); // Faster spawns at higher levels LK.setTimeout(spawnTrafficCar, nextSpawnTime); } // Start game function function startGame() { gameStarted = true; gameTime = 30; distance = 0; level = 1; // Initial level starts at 1 nextCheckpointAt = checkpointDistance; LK.setScore(0); // Initialize road lines with pre-animated positions as if they had already been running for 10 seconds roadLines = []; for (var i = 0; i < roadLinesGroup.children.length; i++) { roadLinesGroup.children[i].destroy(); } roadLinesGroup.removeChildren(); roadLineSpawnTimer = 0; // Create pre-existing road lines as if they were already running for 10 seconds for (var i = 0; i < ROAD_LINE_COUNT; i++) { var newRoadLine = new RoadLine(); // Calculate a position along the animation path as if it had been running // Progress ranges from 0 (just started) to 1 (almost finished) var progress = i / ROAD_LINE_COUNT; // Start at center X and interpolate Y based on progress newRoadLine.x = centerX + 15; // Calculate Y position - from center-50 to beyond bottom of screen var startY = centerY - 50; var targetY = 2732 + 100; newRoadLine.y = startY + (targetY - startY) * progress; // Calculate scale based on progress (0.1 to 1.0) var startScale = 0.1; var endScale = 1.0; var currentScale = startScale + (endScale - startScale) * progress; newRoadLine.scale.set(currentScale); newRoadLine.lastY = newRoadLine.y; newRoadLine.isMoving = true; // Only add the road line and start animation if it's still on screen if (newRoadLine.y < 2732) { // Create tween animation with perspective effect - already in progress var remainingDuration = 4000 * (1 - progress) / level; tween(newRoadLine, { y: targetY, scaleX: endScale, scaleY: endScale }, { duration: remainingDuration, easing: tween.easeIn }); roadLines.push(newRoadLine); roadLinesGroup.addChild(newRoadLine); } else { newRoadLine.destroy(); } } // Update UI scoreText.setText('0'); levelText.setText(level.toString()); levelText.setStyle({ tint: 0xffffff, stroke: 0xffffff, strokeThickness: 5 }); // Remove start text LK.gui.center.removeChild(startText); // Start spawning game elements spawnTrafficCar(); // Level is now calculated based on score/10+1, no need for a timer to increase it // Play background music LK.playMusic('bgmusic'); // Palm spawning is handled in the update function } // Handle touch input game.down = function (x, y) { // Only play honk sound if game has started if (!gameStarted) { startGame(); return; } // Play honk sound when screen is clicked/tapped after game has started LK.getSound('honk').play(); // Record the time of honk for perfect timing detection lastHonkTime = Date.now(); // Remove any existing bubble when player honks if (player.bubble && player.bubble.parent) { player.bubble.parent.removeChild(player.bubble); player.bubble.destroy(); player.bubble = null; } }; game.move = function (x, y) { if (!gameStarted) { return; } // Move player car directly to mouse X position var targetX = x; // Apply smooth movement by calculating the difference var deltaX = targetX - player.x; player.steering = deltaX * 0.1; // Smooth movement factor }; game.up = function () { if (gameStarted) { // No need to reset steering as we'll continue to follow mouse } }; // Main game update function game.update = function () { if (!gameStarted) { return; } // Update road lines if (gameStarted) { // Update road line animations for (var r = roadLines.length - 1; r >= 0; r--) { var roadLine = roadLines[r]; // Check if roadLine exists before accessing its properties if (roadLine) { roadLine.lastY = roadLine.y; // Start a new roadLine animation if roadLine is not already moving if (roadLine.isMoving === false) { roadLine.isMoving = true; // Set target position - straight down the screen var targetY = 2732 + 100; // Below bottom of screen var targetScale = 1.0; // End larger // Create tween animation with perspective effect tween(roadLine, { y: targetY, scaleX: targetScale, scaleY: targetScale }, { duration: 4000 / level, easing: tween.easeIn }); } // Check if roadLine has moved significantly past the bottom of the screen and destroy it var destroyYPosition = 2732 - 300; // Check if center is below the bottom edge if (roadLine.isMoving && roadLine.y > destroyYPosition) { // RoadLine is off-screen, destroy it var index = roadLines.indexOf(roadLine); if (index > -1) { roadLinesGroup.removeChild(roadLine); roadLines.splice(index, 1); roadLine.destroy(); } // Skip to next roadLine as this one is destroyed continue; } } } // End of loop processing existing road lines // Calculate spawn cooldown based on level roadLineSpawnCooldown = Math.max(5, BASE_ROAD_LINE_COOLDOWN / level); // Update road line spawn timer and generate new road lines if needed roadLineSpawnTimer -= 1; if (roadLineSpawnTimer <= 0 && roadLines.length < ROAD_LINE_COUNT) { roadLineSpawnTimer = roadLineSpawnCooldown; // Reset timer var newRoadLine = new RoadLine(); // Start at center X and at center Y newRoadLine.x = centerX + 15; newRoadLine.y = centerY - 50; newRoadLine.scale.set(0.1); // Start small newRoadLine.lastY = newRoadLine.y; newRoadLine.isMoving = false; // Flag to track if road line is currently animating roadLines.push(newRoadLine); roadLinesGroup.addChild(newRoadLine); } } // Road lines removed as requested // Update game time gameTime -= 1 / 60; // Assuming 60 FPS // Calculate level based on score/10, but minimum of 1 level = Math.floor(LK.getScore() / 10) + 1; levelText.setText(level.toString()); // Update palm animations section if (gameStarted) { distance += 10 + level; // Don't update score based on distance anymore // Keep updating the distance for internal calculations if needed // Update palm animations for (var p = palms.length - 1; p >= 0; p--) { var palm = palms[p]; // Check if palm exists before accessing its properties if (palm) { palm.lastY = palm.y; // Start a new palm animation if palm is not already moving if (palm.isMoving === false) { palm.isMoving = true; // Set target position based on direction (left or right corner) var targetX = palm.direction === "left" ? -12000 : 14500; // Left or right edge var targetY = 2732 + 100; // Below bottom of screen var targetScale = 2.0; // End larger // Create tween animation with perspective effect - use same duration for all palms tween(palm, { x: targetX, y: targetY, scaleX: targetScale * (lastSpawnSide === "right" ? 1 : -1), scaleY: targetScale }, { duration: 7000 / level, // Duration decreases as level increases easing: tween.easeIn // Removed onFinish callback }); } // Check if palm has moved significantly past the bottom of the screen and destroy it var destroyYPosition = 2500; // Check if center is 150px below the bottom edge if (palm.isMoving && palm.y > destroyYPosition) { // Palm is off-screen, destroy it var index = palms.indexOf(palm); if (index > -1) { palmGroup.removeChild(palm); palms.splice(index, 1); palm.destroy(); } // Skip to next palm as this one is destroyed continue; } } } // End of loop processing existing palms // Calculate palm spawn cooldown based on level palmSpawnCooldown = Math.max(3, BASE_PALM_COOLDOWN / level); // Update palm spawn timer and generate new palms if needed palmSpawnTimer -= 1; if (palmSpawnTimer <= 0 && palms.length < PALM_COUNT) { palmSpawnTimer = palmSpawnCooldown; // Reset timer var newPalm = new Palm(); // Alternate spawn side based on the last spawned side lastSpawnSide = lastSpawnSide === "right" ? "left" : "right"; // Start from exact center point newPalm.x = centerX + (lastSpawnSide === "right" ? 50 : -50); newPalm.y = centerY - 100; // Start at center newPalm.scale.set(0.005); // Start small newPalm.scaleX *= lastSpawnSide === "right" ? 1 : -1; newPalm.lastY = newPalm.y; newPalm.direction = lastSpawnSide; // Use the determined side newPalm.isMoving = false; // Flag to track if palm is currently animating palms.push(newPalm); palmGroup.addChild(newPalm); } } // Game will continue indefinitely - removed game over condition gameTime = Math.max(gameTime, 0.1); // Keep time from reaching zero // Update perfect messages for (var m = perfectMessages.length - 1; m >= 0; m--) { perfectMessages[m].update(); } // Check for old perfect and non-perfect messages every 2 seconds if (LK.ticks % 60 === 0) { var currentTime = Date.now(); for (var i = perfectMessages.length - 1; i >= 0; i--) { // If message is older than 2 seconds and still exists if (perfectMessages[i] && perfectMessages[i].creationTime && currentTime - perfectMessages[i].creationTime > 500) { // Remove from game if it's still attached if (perfectMessages[i].parent) { perfectMessages[i].parent.removeChild(perfectMessages[i]); } // Destroy and remove from array perfectMessages[i].destroy(); perfectMessages.splice(i, 1); } } // Check for speech bubble timing if (player.bubble && player.bubble.creationTime && currentTime - player.bubble.creationTime > 1000) { // Remove bubble if it's been showing for more than 1 second if (player.bubble.parent) { player.bubble.parent.removeChild(player.bubble); } player.bubble.destroy(); player.bubble = null; } } // Check collisions with traffic cars for (var i = trafficCars.length - 1; i >= 0; i--) { var car = trafficCars[i]; if (player.intersects(car) && !player.crashed) { player.crash(); trafficCars.splice(i, 1); car.destroy(); // GameOver will be called after the crash sequence } } }; function endGame() { if (!gameStarted) { return; } // Just save high score without ending the game if (LK.getScore() > storage.highScore) { storage.highScore = LK.getScore(); } // Game continues - no game over screen }
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0
});
/****
* Classes
****/
var NonPerfectMessage = Container.expand(function () {
var self = Container.call(this);
var messageText = new Text2('Bad timing', {
size: 120,
fill: 0xFF0000,
stroke: 0xffffff,
strokeThickness: 8
});
messageText.anchor.set(0.5, 0.5);
self.addChild(messageText);
self.alpha = 1;
self.scale.set(0.1);
self.lifespan = 60; // 1 second at 60fps
self.initialized = false;
self.creationTime = Date.now(); // Track when message was created
self.init = function () {
if (self.initialized) {
return;
}
self.initialized = true;
// First tween: grow from small to large
tween(self.scale, {
x: 1.0,
y: 1.0
}, {
duration: 300,
easing: tween.easeOut,
onComplete: function onComplete() {
// Second tween: wait a bit then fade out
LK.setTimeout(function () {
tween(self, {
alpha: 0
}, {
duration: 300,
easing: tween.easeIn,
onComplete: function onComplete() {
// Find and remove from perfectMessages array
var index = perfectMessages.indexOf(self);
if (index > -1) {
perfectMessages.splice(index, 1);
}
// Remove from game first before destroying
if (self.parent) {
self.parent.removeChild(self);
}
self.destroy();
}
});
}, 600);
}
});
};
self.update = function () {
if (!self.initialized) {
self.init();
}
// Manual animation as fallback if tweens fail
if (!self.initialized) {
self.lifespan--;
if (self.lifespan <= 0) {
// Find and remove from perfectMessages array
var index = perfectMessages.indexOf(self);
if (index > -1) {
perfectMessages.splice(index, 1);
}
self.destroy();
return;
}
// Animation effect
if (self.lifespan > 45) {
// Growing phase
self.scale.set(0.1 + (1.0 - 0.1) * (1 - (self.lifespan - 45) / 15));
} else if (self.lifespan < 15) {
// Fading out phase
self.alpha = self.lifespan / 15;
}
}
};
return self;
});
var Palm = Container.expand(function () {
var self = Container.call(this);
var palmGraphics = self.attachAsset('palm', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = palmGraphics.width;
self.height = palmGraphics.height;
self.isMoving = false;
self.direction = "left"; // Default direction
self.lastY = 0;
self.update = function () {
// Animation will be handled by tweens
// This just ensures we have an update method for tracking
};
return self;
});
var PerfectMessage = Container.expand(function () {
var self = Container.call(this);
var messageText = new Text2('PERFECT! +2!', {
size: 120,
fill: 0xFFF000,
stroke: 0xFF0000,
strokeThickness: 8
});
messageText.anchor.set(0.5, 0.5);
self.addChild(messageText);
self.alpha = 1;
self.scale.set(0.1);
self.lifespan = 60; // 1 second at 60fps
self.initialized = false;
self.creationTime = Date.now(); // Track when message was created
self.init = function () {
if (self.initialized) {
return;
}
self.initialized = true;
// First tween: grow from small to large
tween(self.scale, {
x: 1.0,
y: 1.0
}, {
duration: 300,
easing: tween.easeOut,
onComplete: function onComplete() {
// Second tween: wait a bit then fade out
LK.setTimeout(function () {
tween(self, {
alpha: 0
}, {
duration: 300,
easing: tween.easeIn,
onComplete: function onComplete() {
// Find and remove from perfectMessages array
var index = perfectMessages.indexOf(self);
if (index > -1) {
perfectMessages.splice(index, 1);
}
// Remove from game first before destroying
if (self.parent) {
self.parent.removeChild(self);
}
self.destroy();
}
});
}, 600);
}
});
};
self.update = function () {
if (!self.initialized) {
self.init();
}
// Manual animation as fallback if tweens fail
if (!self.initialized) {
self.lifespan--;
if (self.lifespan <= 0) {
// Find and remove from perfectMessages array
var index = perfectMessages.indexOf(self);
if (index > -1) {
perfectMessages.splice(index, 1);
}
self.destroy();
return;
}
// Animation effect
if (self.lifespan > 45) {
// Growing phase
self.scale.set(0.1 + (1.0 - 0.1) * (1 - (self.lifespan - 45) / 15));
} else if (self.lifespan < 15) {
// Fading out phase
self.alpha = self.lifespan / 15;
}
}
};
return self;
});
var PlayerCar = Container.expand(function () {
var self = Container.call(this);
var carGraphics = self.attachAsset('playerCar', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = carGraphics.width;
self.height = carGraphics.height;
self.speed = 0;
self.maxSpeed = 15;
self.steering = 0;
self.crashed = false;
self.lastX = 0;
self.bubble = null;
self.crash = function () {
if (!self.crashed) {
self.crashed = true;
// 1. First, play the crash sound
LK.getSound('crash').play();
// 2. Second, mirror vertically the player Car
carGraphics.scaleY = -1;
// Flash effect for visual feedback
LK.effects.flashObject(self, 0xFF0000, 1000);
// 3. Stop the game by setting speed to 0
self.speed = 0;
// 4. After 2 seconds, show GameOver
LK.setTimeout(function () {
LK.showGameOver();
}, 2000);
}
};
self.update = function () {
// Save last position for movement detection
self.lastX = self.x;
// Handle car physics
if (!self.crashed) {
self.speed = Math.min(self.maxSpeed, self.speed + 0.1);
}
// Apply steering if not crashed
if (!self.crashed) {
self.x += self.steering;
}
// Keep car within bounds
if (self.x < self.width / 2) {
self.x = self.width / 2;
}
if (self.x > 2048 - self.width / 2) {
self.x = 2048 - self.width / 2;
}
// Check if car has moved horizontally and has a bubble
if (Math.abs(self.x - self.lastX) > 0.1 && self.bubble && self.bubble.parent) {
// Remove bubble immediately when car moves
if (self.bubble.parent) {
self.bubble.parent.removeChild(self.bubble);
}
self.bubble.destroy();
self.bubble = null;
}
};
return self;
});
var RoadLine = Container.expand(function () {
var self = Container.call(this);
var lineGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = lineGraphics.width;
self.height = lineGraphics.height;
self.isMoving = false;
self.lastY = 0;
self.update = function () {
// Animation will be handled by tweens
// This just ensures we have an update method for tracking
};
return self;
});
// RoadLine class removed as requested
var TrafficCar = Container.expand(function () {
var self = Container.call(this);
// Randomly select one of the available traffic car assets
var carAssets = ['trafficCar', 'trafficCar2', 'trafficCar3', 'trafficCar4'];
var selectedCarAsset = carAssets[Math.floor(Math.random() * carAssets.length)];
self.direction = Math.random() > 0.5 ? 1 : -1; // Random direction: -1 = left, 1 = right
var carGraphics = self.attachAsset(selectedCarAsset, {
anchorX: 0.5,
anchorY: 0.5,
x: self.direction == -1 ? 0 : 200
});
self.width = carGraphics.width;
self.height = carGraphics.height;
self.speed = 0.1 * level * SPEED_LEVEL_FACTOR;
self.direction = Math.random() > 0.5 ? 1 : -1; // Random direction: -1 = left, 1 = right
self.startY = 1300; // Starting Y position (further up the screen)
self.targetY = 2732 + self.height; // Y position when fully off-screen (bottom)
self.initialScale = 0.1;
self.targetScale = 2.0;
// Set target X position in one of the specified ranges
self.targetX = Math.random() > 0.5 ? Math.random() * 500 + 2000 : Math.random() * 100;
// Set initial scale
self.scale.set(self.initialScale);
self.update = function () {
// Calculate progress towards target Y
var progress = (self.y - self.startY) / (self.targetY - self.startY);
progress = Math.max(0, Math.min(1, progress)); // Clamp between 0 and 1
// Interpolate scale based on progress
var currentScale = self.initialScale + (self.targetScale - self.initialScale) * progress;
self.scale.set(currentScale);
// Calculate X position based on progress (linear interpolation)
var startX = 2048 / 2; // Center of screen
self.x = startX + (self.targetX - startX) * progress;
// Apply level-based speed increase
self.speed += progress * level * SPEED_LEVEL_FACTOR;
// Update Y position with speed that increases as the car grows
self.y += self.speed;
// Destroy if off screen
if (self.y > self.targetY) {
// Check against targetY
// Find and remove self from trafficCars array
var index = trafficCars.indexOf(self);
if (index > -1) {
trafficCars.splice(index, 1);
// Check for perfect timing - if honk was within 1 second (1000ms) before or after car disappears
var currentTime = Date.now();
var timeDifference = Math.abs(currentTime - lastHonkTime);
if (timeDifference <= 500 * 2 / Math.min(3, level)) {
// Perfect timing! Add 2 extra points
LK.setScore(LK.getScore() + 3); // +1 for normal disappearance, +2 for perfect timing
// Create and display perfect message
var perfectMsg = new PerfectMessage();
perfectMsg.x = centerX;
perfectMsg.y = centerY;
perfectMsg.creationTime = Date.now(); // Set creation time
game.addChild(perfectMsg);
perfectMessages.push(perfectMsg);
// Explicitly call init to ensure animation starts immediately
perfectMsg.init();
} else if (timeDifference > 500 * 2 / Math.min(3, level) && lastHonkTime > 0 && lastHonkTime > currentTime - 3000) {
// Bad timing! Show a non-perfect message only if honk was played AFTER this car crossed
// (checking if honk happened within last 3 seconds to ensure it relates to this car)
var nonPerfectMsg = new NonPerfectMessage();
nonPerfectMsg.x = centerX;
nonPerfectMsg.y = centerY;
nonPerfectMsg.creationTime = Date.now(); // Set creation time
game.addChild(nonPerfectMsg);
perfectMessages.push(nonPerfectMsg); // Add to the same array for cleanup
// Explicitly call init to ensure animation starts immediately
nonPerfectMsg.init();
// Normal score increment
LK.setScore(LK.getScore() + 1);
} else {
// Normal score increment
LK.setScore(LK.getScore() + 1);
}
scoreText.setText(LK.getScore().toString());
scoreText.setStyle({
tint: 0xffffff,
stroke: 0xffffff,
strokeThickness: 5
});
// List of available sounds excluding crash
var sounds = ['blind', 'getOff', 'holdingTraffic', 'learnToDrive', 'license', 'moveOver', 'speedUp', 'whatAreYouDoing'];
// Get current last played sound from game variable or initialize it
var lastPlayedSound = game.lastPlayedSound || '';
// Filter out the last played sound if it exists
var availableSounds = sounds.filter(function (sound) {
return sound !== lastPlayedSound;
});
// Select a random sound from the filtered list
var randomSound = availableSounds[Math.floor(Math.random() * availableSounds.length)];
// Store the selected sound as the last played sound
game.lastPlayedSound = randomSound;
// Play the selected sound
LK.getSound(randomSound).play();
// Check if player already has a bubble
if (player.bubble && player.bubble.parent) {
player.bubble.parent.removeChild(player.bubble);
player.bubble.destroy();
}
// Show bubble over player car
var bubble = LK.getAsset('bubble', {
anchorX: 0.5,
anchorY: 1.0,
x: player.x,
y: player.y - player.height / 2
});
game.addChild(bubble);
bubble.scale.set(0);
// Store reference to the bubble in player object
player.bubble = bubble;
// Store creation time for auto-cleanup
bubble.creationTime = Date.now();
// Animate the bubble appearing and disappearing
tween(bubble.scale, {
x: 0.8,
y: 0.8
}, {
duration: 200,
easing: tween.easeOut,
onComplete: function onComplete() {
// Wait for 0.3 seconds then fade out (this happens if no movement removed it)
LK.setTimeout(function () {
// Check if bubble still exists and hasn't been removed by movement
if (bubble && bubble.parent) {
tween(bubble, {
alpha: 0
}, {
duration: 200,
easing: tween.easeIn,
onComplete: function onComplete() {
if (bubble.parent) {
bubble.parent.removeChild(bubble);
}
bubble.destroy();
// Clear reference in player
if (player.bubble === bubble) {
player.bubble = null;
}
}
});
}
}, 800); // Increased to ensure bubble shows for 1 second total
}
});
}
self.destroy();
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Game state variables
var gameStarted = false;
var gameTime = 30; // Initial time in seconds
var distance = 0;
var level = 1;
var checkpointDistance = 500; // Distance between checkpoints
var nextCheckpointAt = checkpointDistance;
var trafficCars = [];
var obstacles = [];
var checkpoints = [];
var timeBonuses = [];
var difficultyTimer;
var lastHonkTime = 0; // Track when player last honked
var perfectMessages = []; // Array to store perfect timing messages
// Create container groups
var palmGroup = new Container();
var roadLinesGroup = new Container();
var SPEED_LEVEL_FACTOR = 3;
// Create road lines
var ROAD_LINE_COUNT = 15;
var roadLines = [];
var BASE_ROAD_LINE_COOLDOWN = 20; // Base spawn cooldown
var roadLineSpawnCooldown = 20; // Spawn a road line every 0.33 seconds if needed
var roadLineSpawnTimer = 0; // Timer to track cooldown
// Create palm trees
var PALM_COUNT = 40;
var palms = [];
var centerX = 2048 / 2; // Center of screen X
var centerY = 2732 / 2; // Center of screen Y
var BASE_PALM_COOLDOWN = 10; // Base spawn cooldown
var palmSpawnCooldown = 10; // Spawn a palm every 0.5 seconds if needed
var palmSpawnTimer = 0; // Timer to track cooldown
var lastSpawnSide = "right"; // Track the last side a palm was spawned on
// Sway parameters
var swayCounter = 0;
// Create road
var road = LK.getAsset('road', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 + 50,
y: 2732 / 2 - 100
// y: 1000
});
game.addChild(road);
game.addChild(palmGroup);
game.addChild(roadLinesGroup);
// Road lines removed as requested
// Create player car
var player = new PlayerCar();
player.x = 2048 / 2;
player.y = 2732 - 300;
game.addChild(player);
var scoreLabel = new Text2('Score:', {
size: 80,
fill: 0xFFFFFF,
stroke: 0xFFFFFF,
strokeThickness: 5
});
scoreLabel.anchor.set(0, 0);
scoreLabel.x = -150;
LK.gui.top.addChild(scoreLabel);
var scoreText = new Text2('0', {
size: 80,
fill: 0xFFFFFF,
stroke: 0xFFFFFF,
strokeThickness: 5
});
scoreText.anchor.set(0, 0);
scoreText.x = 100;
LK.gui.top.addChild(scoreText);
var levelLabel = new Text2('Level:', {
size: 80,
fill: 0xFFFFFF,
stroke: 0xFFFFFF,
strokeThickness: 5
});
levelLabel.anchor.set(0, 0);
levelLabel.x = -150;
levelLabel.y = 100; // Offset below time
LK.gui.top.addChild(levelLabel);
var levelText = new Text2('1', {
size: 80,
fill: 0xFFFFFF,
stroke: 0xFFFFFF,
strokeThickness: 5
});
levelText.anchor.set(0, 0);
levelText.x = 100;
levelText.y = 100; // Offset below time
LK.gui.top.addChild(levelText);
var startText = new Text2('TAP TO START\nTAP TO 🔊\nPERFECTLY TIMED 🔊 = +2', {
size: 120,
fill: 0xFFFFFF,
stroke: 0xFFFFFF,
strokeThickness: 8,
align: 'center'
});
startText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(startText);
// Functions to spawn game elements
function spawnTrafficCar() {
// Only spawn a new car if there are no cars on screen
if (trafficCars.length === 0) {
var car = new TrafficCar();
car.x = 2048 / 2; // Start in the middle of the screen
car.y = car.startY; // Start at the defined startY
car.speed = 0.1 * level * SPEED_LEVEL_FACTOR;
trafficCars.push(car);
game.addChild(car);
}
// Schedule next check for spawning with faster spawns at higher levels
var nextSpawnTime = Math.max(200, 1000 / level); // Faster spawns at higher levels
LK.setTimeout(spawnTrafficCar, nextSpawnTime);
}
// Start game function
function startGame() {
gameStarted = true;
gameTime = 30;
distance = 0;
level = 1; // Initial level starts at 1
nextCheckpointAt = checkpointDistance;
LK.setScore(0);
// Initialize road lines with pre-animated positions as if they had already been running for 10 seconds
roadLines = [];
for (var i = 0; i < roadLinesGroup.children.length; i++) {
roadLinesGroup.children[i].destroy();
}
roadLinesGroup.removeChildren();
roadLineSpawnTimer = 0;
// Create pre-existing road lines as if they were already running for 10 seconds
for (var i = 0; i < ROAD_LINE_COUNT; i++) {
var newRoadLine = new RoadLine();
// Calculate a position along the animation path as if it had been running
// Progress ranges from 0 (just started) to 1 (almost finished)
var progress = i / ROAD_LINE_COUNT;
// Start at center X and interpolate Y based on progress
newRoadLine.x = centerX + 15;
// Calculate Y position - from center-50 to beyond bottom of screen
var startY = centerY - 50;
var targetY = 2732 + 100;
newRoadLine.y = startY + (targetY - startY) * progress;
// Calculate scale based on progress (0.1 to 1.0)
var startScale = 0.1;
var endScale = 1.0;
var currentScale = startScale + (endScale - startScale) * progress;
newRoadLine.scale.set(currentScale);
newRoadLine.lastY = newRoadLine.y;
newRoadLine.isMoving = true;
// Only add the road line and start animation if it's still on screen
if (newRoadLine.y < 2732) {
// Create tween animation with perspective effect - already in progress
var remainingDuration = 4000 * (1 - progress) / level;
tween(newRoadLine, {
y: targetY,
scaleX: endScale,
scaleY: endScale
}, {
duration: remainingDuration,
easing: tween.easeIn
});
roadLines.push(newRoadLine);
roadLinesGroup.addChild(newRoadLine);
} else {
newRoadLine.destroy();
}
}
// Update UI
scoreText.setText('0');
levelText.setText(level.toString());
levelText.setStyle({
tint: 0xffffff,
stroke: 0xffffff,
strokeThickness: 5
});
// Remove start text
LK.gui.center.removeChild(startText);
// Start spawning game elements
spawnTrafficCar();
// Level is now calculated based on score/10+1, no need for a timer to increase it
// Play background music
LK.playMusic('bgmusic');
// Palm spawning is handled in the update function
}
// Handle touch input
game.down = function (x, y) {
// Only play honk sound if game has started
if (!gameStarted) {
startGame();
return;
}
// Play honk sound when screen is clicked/tapped after game has started
LK.getSound('honk').play();
// Record the time of honk for perfect timing detection
lastHonkTime = Date.now();
// Remove any existing bubble when player honks
if (player.bubble && player.bubble.parent) {
player.bubble.parent.removeChild(player.bubble);
player.bubble.destroy();
player.bubble = null;
}
};
game.move = function (x, y) {
if (!gameStarted) {
return;
}
// Move player car directly to mouse X position
var targetX = x;
// Apply smooth movement by calculating the difference
var deltaX = targetX - player.x;
player.steering = deltaX * 0.1; // Smooth movement factor
};
game.up = function () {
if (gameStarted) {
// No need to reset steering as we'll continue to follow mouse
}
};
// Main game update function
game.update = function () {
if (!gameStarted) {
return;
}
// Update road lines
if (gameStarted) {
// Update road line animations
for (var r = roadLines.length - 1; r >= 0; r--) {
var roadLine = roadLines[r];
// Check if roadLine exists before accessing its properties
if (roadLine) {
roadLine.lastY = roadLine.y;
// Start a new roadLine animation if roadLine is not already moving
if (roadLine.isMoving === false) {
roadLine.isMoving = true;
// Set target position - straight down the screen
var targetY = 2732 + 100; // Below bottom of screen
var targetScale = 1.0; // End larger
// Create tween animation with perspective effect
tween(roadLine, {
y: targetY,
scaleX: targetScale,
scaleY: targetScale
}, {
duration: 4000 / level,
easing: tween.easeIn
});
}
// Check if roadLine has moved significantly past the bottom of the screen and destroy it
var destroyYPosition = 2732 - 300; // Check if center is below the bottom edge
if (roadLine.isMoving && roadLine.y > destroyYPosition) {
// RoadLine is off-screen, destroy it
var index = roadLines.indexOf(roadLine);
if (index > -1) {
roadLinesGroup.removeChild(roadLine);
roadLines.splice(index, 1);
roadLine.destroy();
}
// Skip to next roadLine as this one is destroyed
continue;
}
}
} // End of loop processing existing road lines
// Calculate spawn cooldown based on level
roadLineSpawnCooldown = Math.max(5, BASE_ROAD_LINE_COOLDOWN / level);
// Update road line spawn timer and generate new road lines if needed
roadLineSpawnTimer -= 1;
if (roadLineSpawnTimer <= 0 && roadLines.length < ROAD_LINE_COUNT) {
roadLineSpawnTimer = roadLineSpawnCooldown; // Reset timer
var newRoadLine = new RoadLine();
// Start at center X and at center Y
newRoadLine.x = centerX + 15;
newRoadLine.y = centerY - 50;
newRoadLine.scale.set(0.1); // Start small
newRoadLine.lastY = newRoadLine.y;
newRoadLine.isMoving = false; // Flag to track if road line is currently animating
roadLines.push(newRoadLine);
roadLinesGroup.addChild(newRoadLine);
}
}
// Road lines removed as requested
// Update game time
gameTime -= 1 / 60; // Assuming 60 FPS
// Calculate level based on score/10, but minimum of 1
level = Math.floor(LK.getScore() / 10) + 1;
levelText.setText(level.toString());
// Update palm animations section
if (gameStarted) {
distance += 10 + level;
// Don't update score based on distance anymore
// Keep updating the distance for internal calculations if needed
// Update palm animations
for (var p = palms.length - 1; p >= 0; p--) {
var palm = palms[p];
// Check if palm exists before accessing its properties
if (palm) {
palm.lastY = palm.y;
// Start a new palm animation if palm is not already moving
if (palm.isMoving === false) {
palm.isMoving = true;
// Set target position based on direction (left or right corner)
var targetX = palm.direction === "left" ? -12000 : 14500; // Left or right edge
var targetY = 2732 + 100; // Below bottom of screen
var targetScale = 2.0; // End larger
// Create tween animation with perspective effect - use same duration for all palms
tween(palm, {
x: targetX,
y: targetY,
scaleX: targetScale * (lastSpawnSide === "right" ? 1 : -1),
scaleY: targetScale
}, {
duration: 7000 / level,
// Duration decreases as level increases
easing: tween.easeIn // Removed onFinish callback
});
}
// Check if palm has moved significantly past the bottom of the screen and destroy it
var destroyYPosition = 2500; // Check if center is 150px below the bottom edge
if (palm.isMoving && palm.y > destroyYPosition) {
// Palm is off-screen, destroy it
var index = palms.indexOf(palm);
if (index > -1) {
palmGroup.removeChild(palm);
palms.splice(index, 1);
palm.destroy();
}
// Skip to next palm as this one is destroyed
continue;
}
}
} // End of loop processing existing palms
// Calculate palm spawn cooldown based on level
palmSpawnCooldown = Math.max(3, BASE_PALM_COOLDOWN / level);
// Update palm spawn timer and generate new palms if needed
palmSpawnTimer -= 1;
if (palmSpawnTimer <= 0 && palms.length < PALM_COUNT) {
palmSpawnTimer = palmSpawnCooldown; // Reset timer
var newPalm = new Palm();
// Alternate spawn side based on the last spawned side
lastSpawnSide = lastSpawnSide === "right" ? "left" : "right";
// Start from exact center point
newPalm.x = centerX + (lastSpawnSide === "right" ? 50 : -50);
newPalm.y = centerY - 100; // Start at center
newPalm.scale.set(0.005); // Start small
newPalm.scaleX *= lastSpawnSide === "right" ? 1 : -1;
newPalm.lastY = newPalm.y;
newPalm.direction = lastSpawnSide; // Use the determined side
newPalm.isMoving = false; // Flag to track if palm is currently animating
palms.push(newPalm);
palmGroup.addChild(newPalm);
}
}
// Game will continue indefinitely - removed game over condition
gameTime = Math.max(gameTime, 0.1); // Keep time from reaching zero
// Update perfect messages
for (var m = perfectMessages.length - 1; m >= 0; m--) {
perfectMessages[m].update();
}
// Check for old perfect and non-perfect messages every 2 seconds
if (LK.ticks % 60 === 0) {
var currentTime = Date.now();
for (var i = perfectMessages.length - 1; i >= 0; i--) {
// If message is older than 2 seconds and still exists
if (perfectMessages[i] && perfectMessages[i].creationTime && currentTime - perfectMessages[i].creationTime > 500) {
// Remove from game if it's still attached
if (perfectMessages[i].parent) {
perfectMessages[i].parent.removeChild(perfectMessages[i]);
}
// Destroy and remove from array
perfectMessages[i].destroy();
perfectMessages.splice(i, 1);
}
}
// Check for speech bubble timing
if (player.bubble && player.bubble.creationTime && currentTime - player.bubble.creationTime > 1000) {
// Remove bubble if it's been showing for more than 1 second
if (player.bubble.parent) {
player.bubble.parent.removeChild(player.bubble);
}
player.bubble.destroy();
player.bubble = null;
}
}
// Check collisions with traffic cars
for (var i = trafficCars.length - 1; i >= 0; i--) {
var car = trafficCars[i];
if (player.intersects(car) && !player.crashed) {
player.crash();
trafficCars.splice(i, 1);
car.destroy();
// GameOver will be called after the crash sequence
}
}
};
function endGame() {
if (!gameStarted) {
return;
}
// Just save high score without ending the game
if (LK.getScore() > storage.highScore) {
storage.highScore = LK.getScore();
}
// Game continues - no game over screen
}
crash
Sound effect
bgmusic
Music
honk
Sound effect
learnToDrive
Sound effect
whatAreYouDoing
Sound effect
moveOver
Sound effect
speedUp
Sound effect
blind
Sound effect
license
Sound effect
getOff
Sound effect
holdingTraffic
Sound effect
dog
Sound effect