User prompt
Write the score instantly
User prompt
Score increases faster
User prompt
Let the menu be on a different screen, and when you press start, it switches to the game
User prompt
The start button does not work
User prompt
When you press the start, the game starts, and the car is not damaged before the start.
User prompt
When starting the game, add a menu and get the start text.
User prompt
Please fix the bug: 'LK.showMenu is not a function' in or related to this line: 'LK.showMenu({' Line Number: 480
User prompt
Oyuna menü ekle
User prompt
From 400 points, people start throwing a lot of bottles.
User prompt
After 400 points, the game will be the same as on the first point, but much faster.
User prompt
After 70 points, let them throw fewer bottles.
User prompt
Let it raise both the shield and the life bar that people throw.
User prompt
Hellbox should be removed from the game entirely.
User prompt
Let the bottle thrown by people provide a shield, and let the hellbox thrown by people die.
User prompt
Let only the magic bottle that people throw provide a shield
User prompt
Don't let the hellbox that people throw give you a shield.
User prompt
Don't let the hellbox come out on the road.
User prompt
A person can either throw only a spell bottle or only a hellbox, not both at the same time.
User prompt
When people throw Hellboxes, they only get more lives.
User prompt
Hellbox and magic bottle are in the same person.
User prompt
When you throw a Hellbox, there is no shield.
User prompt
Some people throw magic bottles, some people just send hellboxes
User prompt
Let sidewalk people throw HellBox instead of spellBottle
User prompt
People throw fewer magic bottles
User prompt
People should not throw magic bottles for up to 30 points.
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ // Enemy Car Class // Note: The car sprite is always the first child (self.children[0]) for tinting purposes. var EnemyCar = Container.expand(function () { var self = Container.call(this); var car = self.attachAsset('enemyCar', { anchorX: 0.5, anchorY: 0.5 }); // --- Overlay a white rectangle for the window --- // The window is always in the same place on the enemyCar asset, so we overlay a white box. // These values are tuned for the asset's window position and size. var windowOverlay = LK.getAsset('heartIcon', { anchorX: 0.5, anchorY: 0.5, width: 70, height: 38, x: 0, y: -38, tint: 0xffffff, alpha: 1 }); self.addChild(windowOverlay); self.lane = 1; self.speed = 18; self.update = function () { self.y += self.speed * gameSpeed; }; return self; }); // Fireball Class var Fireball = Container.expand(function () { var self = Container.call(this); var fireball = self.attachAsset('Fireball', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 28; self.lane = 1; // --- Health bar for Fireball (ace) --- self.maxLife = 3; self.life = self.maxLife; // Health bar background self.lifeBarBg = self.attachAsset('lifeBarBg', { anchorX: 0.5, anchorY: 0.5, width: 220, height: 220, y: -90, alpha: 0.92 }); // Health bar border self.lifeBarBorder = self.attachAsset('lifeBarBorder', { anchorX: 0.5, anchorY: 0.5, width: 260, height: 260, y: -90, alpha: 1 }); // Health bar fill self.lifeBar = self.attachAsset('lifeBar', { anchorX: 0.0, anchorY: 0.5, width: 180, height: 60, x: -90, y: -90, alpha: 1 }); // Heart icon self.lifeBarHeart = self.attachAsset('heartIcon', { anchorX: 0.5, anchorY: 0.5, width: 70, height: 70, x: -130, y: -90, alpha: 1 }); // Helper to update fireball health bar UI self.updateLifeBar = function () { // Show only if not full and not dead var show = self.life < self.maxLife && self.life > 0; self.lifeBarBg.visible = show; self.lifeBar.visible = show; self.lifeBarBorder.visible = show; self.lifeBarHeart.visible = show; // Set width proportional to life var minWidth = 24; var w = self.life > 0 ? Math.max(minWidth, Math.round(180 * (self.life / self.maxLife))) : 0; self.lifeBar.width = w; // Hide if dead if (self.life <= 0) { self.lifeBarBg.visible = false; self.lifeBar.visible = false; self.lifeBarBorder.visible = false; self.lifeBarHeart.visible = false; } }; // Initialize bar state self.updateLifeBar(); self.update = function () { // Move fireball down the screen self.y += self.speed * gameSpeed; // Keep health bar above fireball self.lifeBarBg.y = -90; self.lifeBarBorder.y = -90; self.lifeBar.y = -90; self.lifeBarHeart.y = -90; self.lifeBar.x = -90; self.lifeBarHeart.x = -130; self.updateLifeBar(); }; return self; }); // Lane Divider Class var LaneDivider = Container.expand(function () { var self = Container.call(this); var div = self.attachAsset('laneDivider', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 18; self.update = function () { self.y += self.speed * gameSpeed; }; return self; }); // Obstacle Class var Obstacle = Container.expand(function () { var self = Container.call(this); var obs = self.attachAsset('obstacle', { anchorX: 0.5, anchorY: 0.5 }); self.lane = 1; self.speed = 18; self.update = function () { self.y += self.speed * gameSpeed; }; return self; }); // Player Car Class var PlayerCar = Container.expand(function () { var self = Container.call(this); var car = self.attachAsset('playerCar', { anchorX: 0.5, anchorY: 0.5 }); self.lane = 1; // 0: left, 1: center, 2: right self.life = playerMaxLife; // Add life property to player car self.setLane = function (laneIdx) { self.lane = laneIdx; // Animate to new lane position var targetX = lanes[self.lane]; // Always reset Y to baseY for lane change if (typeof self.baseY === "undefined") self.baseY = 2732 - 500; tween.stop(self, { scaleX: true, scaleY: true, y: true }); // Squash/stretch effect: squash horizontally, stretch vertically, then restore tween(self, { scaleX: 1.18, scaleY: 0.88 }, { duration: 60, easing: tween.cubicOut, onFinish: function onFinish() { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 80, easing: tween.cubicOut }); } }); // Move to new lane and reset Y to baseY (so bounce resumes from correct position) tween(self, { x: targetX, y: self.baseY }, { duration: 120, easing: tween.cubicOut, onUpdate: function onUpdate() { updateLifeBar(); } }); updateLifeBar(); }; // Idle bounce animation for player car self.baseY = 2732 - 500; // Store the base Y for idle bounce self.idleBounce = function () { // Cancel any previous bounce tween.stop(self, { y: true }); // Animate up tween(self, { y: self.baseY - 18 }, { duration: 320, easing: tween.sineInOut, onFinish: function onFinish() { // Animate down tween(self, { y: self.baseY + 18 }, { duration: 320, easing: tween.sineInOut, onFinish: function onFinish() { // Loop bounce self.idleBounce(); } }); } }); }; // Start idle bounce when created self.idleBounce(); // Clamp Y position to prevent slipping up self.update = function () { // If y is above the allowed minimum, clamp it back var minY = 2732 - 700; // Don't let car go above this Y (adjust as needed) if (self.y < minY) { self.y = minY; } }; return self; }); // --- PEOPLE WALKING ON SIDEWALKS --- // Person class for sidewalk walkers var SidewalkPerson = Container.expand(function () { var self = Container.call(this); // Use heartIcon as a placeholder for a person (circle/box) var person = self.attachAsset('heartIcon', { anchorX: 0.5, anchorY: 0.5, width: 60, height: 60, tint: 0x333333 + Math.floor(Math.random() * 0xCCCCCC), // random grayish color alpha: 1 }); // Add hands (arms) as rectangles var leftHand = LK.getAsset('heartIcon', { anchorX: 0.5, anchorY: 0.5, width: 16, height: 38, x: -32, y: 18, tint: 0xdeb887, // light brown, can randomize for variety alpha: 1 }); var rightHand = LK.getAsset('heartIcon', { anchorX: 0.5, anchorY: 0.5, width: 16, height: 38, x: 32, y: 18, tint: 0xdeb887, alpha: 1 }); self.addChild(leftHand); self.addChild(rightHand); // Randomize direction: 1 = down, -1 = up self.dir = Math.random() < 0.5 ? 1 : -1; // Set speed (pixels per frame) self.speed = 4 + Math.random() * 2; // Set sidewalk: 0 = left, 1 = right self.side = 0; // Set initial position if (Math.random() < 0.5) { self.side = 0; self.x = sidewalkWidth / 2; } else { self.side = 1; self.x = 2048 - sidewalkWidth / 2; } // Y: if going down, start at top; if up, start at bottom self.y = self.dir === 1 ? -40 - Math.random() * 200 : 2732 + 40 + Math.random() * 200; // Sway animation self.swayTick = Math.random() * Math.PI * 2; self.update = function () { // Move up or down self.y += self.speed * self.dir; // Sway left/right a little self.swayTick += 0.08; var sway = Math.sin(self.swayTick) * 10; self.x = (self.side === 0 ? sidewalkWidth / 2 : 2048 - sidewalkWidth / 2) + sway; // --- Magic bottle in hand logic --- if (typeof self.handBottle === "undefined") { self.handBottle = null; } if (typeof self.craveSpellBottle === "undefined") self.craveSpellBottle = Math.random() < 0.5; // 50% crave // If craving and not on cooldown, show bottle in hand if (self.craveSpellBottle && self.throwCooldown <= 0 && !self.handBottle) { // Add a bottle in the hand (side dependent) self.handBottle = LK.getAsset('spellBottle', { anchorX: 0.5, anchorY: 0.5, width: 38, height: 38, x: self.side === 0 ? -32 : 32, // left or right hand y: 18, tint: 0x2BD5E6, alpha: 1 }); self.addChild(self.handBottle); } // If not craving or on cooldown, remove bottle from hand if present if ((!self.craveSpellBottle || self.throwCooldown > 0) && self.handBottle) { self.handBottle.destroy(); self.handBottle = null; } }; return self; }); /**** * Initialize Game ****/ // Create and track sidewalk people var game = new LK.Game({ backgroundColor: 0x222222 }); /**** * Game Code ****/ // Use spellBottle as heart // Health bar assets // Spell bottle asset (example: blue bottle, 120x180) // When the player picks up the magic bottle, the car bursts into flames (flame burst effect) // Lane positions (3 lanes) // Car (player) // Enemy car // Obstacle (barrier) // Road lane divider // Sound for crash // Sound for lane change // Music (background) // Person drawing asset for sidewalk walkers (customize this for identity) // This is a simple stick-figure style: head (ellipse), body (box), arms (boxes), legs (boxes) var laneCount = 3; var laneWidth = 520; // Increased lane width for more space between lanes var lanes = [2048 / 2 - laneWidth, // left 2048 / 2, // center 2048 / 2 + laneWidth // right ]; // Game variables var playerCar; var enemyCars = []; var obstacles = []; var laneDividers = []; var score = 0; var highScore = storage.highScore || 0; var scoreTxt; var highScoreTxt; var gameSpeed = 1; var ticksSinceStart = 0; var swipeStartX = null; var swipeStartY = null; var swipeActive = false; var lastLane = 1; var spawnTick = 0; var dividerSpacing = 320; var dragNode = null; // --- LANE IDLE TIMER SYSTEM --- var laneIdleTicks = [0, 0, 0]; // Track how long player stays in each lane (in ticks) var lastPlayerLane = 1; // Track last lane for idle timer var fireballIdleTriggered = [false, false, false]; // Prevent multiple fireballs for same idle period // --- LIFE SYSTEM --- var playerMaxLife = 5; var playerLife = playerMaxLife; // Health bar UI will be handled by a new, clear, and consistent system below // Score display scoreTxt = new Text2('0', { size: 120, fill: "#fff" }); scoreTxt.anchor.set(0.5, 0.5); LK.gui.top.addChild(scoreTxt); // Shield timer display var shieldTxt = new Text2('', { size: 80, fill: 0x2BD5E6 }); shieldTxt.anchor.set(0.5, 0.5); LK.gui.top.addChild(shieldTxt); // High Score display highScoreTxt = new Text2('HI: ' + highScore, { size: 60, fill: 0xFFFF00 }); highScoreTxt.anchor.set(0.5, 0.5); LK.gui.top.addChild(highScoreTxt); // Add swipe left gesture to high score text var hiSwipeStartX = null; var hiSwipeStartY = null; var hiSwipeActive = false; highScoreTxt.down = function (x, y, obj) { hiSwipeStartX = x; hiSwipeStartY = y; hiSwipeActive = true; }; highScoreTxt.move = function (x, y, obj) { if (!hiSwipeActive) return; var dx = x - hiSwipeStartX; var dy = y - hiSwipeStartY; // Only consider horizontal swipes, ignore vertical if (Math.abs(dx) > 60 && Math.abs(dx) > Math.abs(dy)) { if (dx < 0) { // Swipe left detected on high score text // Flash the high score text white for feedback tween(highScoreTxt, { tint: 0xffffff }, { duration: 80, yoyo: true, repeat: 1, onComplete: function onComplete() { highScoreTxt.tint = 0xFFFF00; } }); hiSwipeActive = false; } } }; highScoreTxt.up = function (x, y, obj) { hiSwipeActive = false; }; // Start music LK.playMusic('bgmusic'); // Create player car playerCar = new PlayerCar(); playerCar.x = lanes[1]; playerCar.y = 2732 - 500; playerCar.setLane(1); // Shield properties playerCar.shieldActive = false; playerCar.shieldTicks = 0; // Add shield effect asset (invisible by default) var shieldEffect = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.2, scaleY: 1.2, x: playerCar.x, y: playerCar.y, alpha: 0.5, tint: 0xffffff }); shieldEffect.visible = false; game.addChild(shieldEffect); game.addChild(playerCar); // Add sidewalks to the far left and right edges of the screen (do not touch the road) var sidewalkWidth = 120; var sidewalkHeight = 2732; var sidewalkColor = 0xcccccc; // Light gray for sidewalk var leftSidewalk = LK.getAsset('heartIcon', { anchorX: 0, anchorY: 0, width: sidewalkWidth, height: sidewalkHeight, x: 0, y: 0, tint: sidewalkColor, alpha: 1 }); var rightSidewalk = LK.getAsset('heartIcon', { anchorX: 0, anchorY: 0, width: sidewalkWidth, height: sidewalkHeight, x: 2048 - sidewalkWidth, y: 0, tint: sidewalkColor, alpha: 1 }); game.addChild(leftSidewalk); game.addChild(rightSidewalk); // Create and track sidewalk people var sidewalkPeople = []; var maxPeoplePerSide = 6; for (var i = 0; i < maxPeoplePerSide * 2; i++) { var p = new SidewalkPerson(); sidewalkPeople.push(p); game.addChild(p); } // Create lane dividers (vertical lines for each lane) // For 3 lanes, we want to copy the center lane divider and place it to the right and left of the center stripe, without adding extra lines // The center divider is between lane 0 and lane 1, and between lane 1 and lane 2 // So, for 3 lanes, we want to draw the two dividers: one between lane 0 and 1, and one between lane 1 and 2 var dividerOffsets = []; // Find the X positions for the two dividers (between lanes) for (var l = 1; l < laneCount; l++) { dividerOffsets.push((lanes[l - 1] + lanes[l]) / 2); } // Now, for each divider, draw a set of stripes down the screen for (var d = 0; d < dividerOffsets.length; d++) { for (var i = 0; i < 10; i++) { // Center LaneDivider var divider = new LaneDivider(); divider.x = dividerOffsets[d]; divider.y = i * dividerSpacing; laneDividers.push(divider); game.addChild(divider); // Left copy var dividerLeft = new LaneDivider(); dividerLeft.x = dividerOffsets[d] - laneWidth; dividerLeft.y = i * dividerSpacing; laneDividers.push(dividerLeft); game.addChild(dividerLeft); // Right copy var dividerRight = new LaneDivider(); dividerRight.x = dividerOffsets[d] + laneWidth; dividerRight.y = i * dividerSpacing; laneDividers.push(dividerRight); game.addChild(dividerRight); } } // Prepare for dynamic spell bottle spawning var spellBottle = null; var spellBottleActive = false; var spellBottleLane = 1; // Add: track last spell bottle spawn tick to limit spawn rate var lastSpellBottleSpawnTick = -1000; // Helper: spawn enemy car, obstacle, or fireball function spawnObstacleOrEnemy() { var y = -300; if (score > 200) { // After 200 points, only spawn barriers (obstacles), and spawn in 2 lanes at once // Pick two different lanes var lanesToUse = []; while (lanesToUse.length < 2) { var idx = Math.floor(Math.random() * laneCount); if (lanesToUse.indexOf(idx) === -1) lanesToUse.push(idx); } for (var i = 0; i < lanesToUse.length; i++) { var obs = new Obstacle(); obs.lane = lanesToUse[i]; obs.x = lanes[lanesToUse[i]]; obs.y = y; obstacles.push(obs); game.addChild(obs); } } else if (score >= 70) { // Only spawn fireballs after 70 points if (!window.fireballs) window.fireballs = []; if (window.fireballs.length < 2) { var laneIdx = Math.floor(Math.random() * laneCount); var fireball = new Fireball(); fireball.lane = laneIdx; fireball.x = lanes[laneIdx]; fireball.y = y; window.fireballs.push(fireball); game.addChild(fireball); } } else { // Randomly decide: 60% enemy car, 25% obstacle, 15% fireball var laneIdx = Math.floor(Math.random() * laneCount); var rand = Math.random(); if (rand < 0.6) { var enemy = new EnemyCar(); enemy.lane = laneIdx; enemy.x = lanes[laneIdx]; enemy.y = y; // Assign a bold, high-contrast color tint to the enemy car for visibility // Use a set of bold, high-contrast tints for random color var enemyTints = [0xFFFFFF, // white 0xFF3333, // red 0x33CCFF, // blue 0xFFCC00, // yellow 0x00FF66, // green 0xFF66FF, // magenta 0xFF8800, // orange 0x9933FF // purple ]; var randomColor = enemyTints[Math.floor(Math.random() * enemyTints.length)]; if (enemy.children && enemy.children.length > 0) { enemy.children[0].tint = randomColor; } enemyCars.push(enemy); game.addChild(enemy); } else if (rand < 0.85) { var obs = new Obstacle(); obs.lane = laneIdx; obs.x = lanes[laneIdx]; obs.y = y; obstacles.push(obs); game.addChild(obs); } else { // Prevent fireballs from spawning before 60 points if (score >= 60) { // Limit fireballs: only spawn if fewer than 2 are on screen if (!window.fireballs) window.fireballs = []; if (window.fireballs.length < 2) { var fireball = new Fireball(); fireball.lane = laneIdx; fireball.x = lanes[laneIdx]; fireball.y = y; window.fireballs.push(fireball); game.addChild(fireball); } } } } } // Helper: update score function updateScore(val) { score = val; scoreTxt.setText(score); if (score > highScore) { highScore = score; highScoreTxt.setText('HI: ' + highScore); storage.highScore = highScore; } } // Helper: update player health bar UI (global, for use in PlayerCar.setLane and elsewhere) function updateLifeBar() { // Always show the health bar unless dead var showBar = playerLife > 0; if (window.playerHealthBarBg) window.playerHealthBarBg.visible = showBar; if (window.playerHealthBarFill) window.playerHealthBarFill.visible = showBar; if (window.playerHealthBarBorder) window.playerHealthBarBorder.visible = showBar; // Removed: if (window.playerHealthBarHeart) window.playerHealthBarHeart.visible = showBar; // Position above player car if (playerCar && window.playerHealthBarBg && window.playerHealthBarFill && window.playerHealthBarBorder) { var barY = playerCar.y - 140; var barX = playerCar.x; window.playerHealthBarBg.x = barX; window.playerHealthBarBg.y = barY; window.playerHealthBarFill.x = barX - 240; window.playerHealthBarFill.y = barY; window.playerHealthBarBorder.x = barX; window.playerHealthBarBorder.y = barY; // Removed: window.playerHealthBarHeart.x = barX - 320; // Removed: window.playerHealthBarHeart.y = barY; // Set width proportional to life, always show a minimum width if not dead var minWidth = 38; var fillW = playerLife > 0 ? Math.max(minWidth, Math.round(480 * (playerLife / playerMaxLife))) : 0; window.playerHealthBarFill.width = fillW; } // Hide if dead if (playerLife <= 0) { if (window.playerHealthBarBg) window.playerHealthBarBg.visible = false; if (window.playerHealthBarFill) window.playerHealthBarFill.visible = false; if (window.playerHealthBarBorder) window.playerHealthBarBorder.visible = false; // Removed: if (window.playerHealthBarHeart) window.playerHealthBarHeart.visible = false; } } // Touch/drag/swipe handling game.down = function (x, y, obj) { swipeStartX = x; swipeStartY = y; swipeActive = true; dragNode = playerCar; }; game.move = function (x, y, obj) { // Only handle swipe if active if (!swipeActive) return; if (!dragNode) return; var dx = x - swipeStartX; var dy = y - swipeStartY; // Only consider horizontal swipes, ignore vertical if (Math.abs(dx) > 80 && Math.abs(dx) > Math.abs(dy)) { var newLane = playerCar.lane; if (dx < 0 && playerCar.lane > 0) { newLane = playerCar.lane - 1; } else if (dx > 0 && playerCar.lane < laneCount - 1) { newLane = playerCar.lane + 1; } if (newLane !== playerCar.lane) { // Reset idle timer for previous lane, and for new lane laneIdleTicks[playerCar.lane] = 0; fireballIdleTriggered[playerCar.lane] = false; playerCar.setLane(newLane); // Also reset for new lane laneIdleTicks[newLane] = 0; fireballIdleTriggered[newLane] = false; lastPlayerLane = newLane; LK.getSound('swipe').play(); swipeActive = false; dragNode = null; } } }; game.up = function (x, y, obj) { swipeActive = false; dragNode = null; }; // Main game update loop game.update = function () { ticksSinceStart++; // --- LANE IDLE TIMER UPDATE --- // Only increment for the current lane, reset others for (var l = 0; l < laneCount; l++) { if (l === playerCar.lane) { laneIdleTicks[l]++; } else { laneIdleTicks[l] = 0; fireballIdleTriggered[l] = false; } } // If player has stayed in the same lane for >10s (600 ticks), spawn a fireball in that lane if (laneIdleTicks[playerCar.lane] > 600 && !fireballIdleTriggered[playerCar.lane]) { // Only spawn if fewer than 2 fireballs are on screen if (!window.fireballs) window.fireballs = []; if (window.fireballs.length < 2) { var fireball = new Fireball(); fireball.lane = playerCar.lane; fireball.x = lanes[playerCar.lane]; fireball.y = -300; window.fireballs.push(fireball); game.addChild(fireball); } fireballIdleTriggered[playerCar.lane] = true; // Optionally, flash a warning or effect here if desired } // Adjust acceleration: start at 20 points, increase slowly until 70 points, then ramp up if (score >= 400) { // After 400 points, decrease the speed gradually if (ticksSinceStart % 60 === 0 && gameSpeed > 1) { gameSpeed -= 0.08; if (gameSpeed < 1) gameSpeed = 1; } } else if (score >= 250) { // Between 250 and 400, slow down the speed a little (decelerate) if (ticksSinceStart % 60 === 0 && gameSpeed > 1.2) { gameSpeed -= 0.04; if (gameSpeed < 1.2) gameSpeed = 1.2; } } else if (score >= 70) { // After 70, only fireballs spawn and game returns to normal speed gameSpeed = 1; } else if (score >= 20) { // Between 20 and 70, increase very slowly if (ticksSinceStart % 90 === 0 && gameSpeed < 2) { gameSpeed += 0.04; if (gameSpeed > 2) gameSpeed = 2; } } else { gameSpeed = 1; } // --- UPDATE SIDEWALK PEOPLE --- for (var i = 0; i < sidewalkPeople.length; i++) { var p = sidewalkPeople[i]; p.update(); // --- GIVE ACETITY: random chance to get a speed boost and color flash --- if (typeof p.acetityCooldown === "undefined") p.acetityCooldown = 0; if (typeof p.acetityActive === "undefined") p.acetityActive = false; if (typeof p.baseSpeed === "undefined") p.baseSpeed = p.speed; // --- THROWING ITEMS: only crave (desire) spell bottles, and rarely throw them --- if (typeof p.throwCooldown === "undefined") p.throwCooldown = 0; if (typeof p.thrownItems === "undefined") p.thrownItems = []; if (typeof p.craveSpellBottle === "undefined") p.craveSpellBottle = Math.random() < 0.5; // 50% crave if (p.throwCooldown > 0) { p.throwCooldown--; } else if (p.craveSpellBottle && Math.random() < 0.008 && score >= 30) { // 0.8% chance per frame to throw, only if craving spell bottle (was 0.1%) // Only throw if not too close to top/bottom and not too many items already if (p.y > 200 && p.y < 2732 - 200 && (!window.thrownItems || window.thrownItems.length < 2)) { // Only throw spellBottle var itemType = 'spellBottle'; var thrownItem = LK.getAsset(itemType, { anchorX: 0.5, anchorY: 0.5, width: 80, height: 80, x: p.x + (p.side === 0 ? 60 : -60), // throw toward road y: p.y + 10, tint: 0x2BD5E6, alpha: 1 }); thrownItem._vx = (p.side === 0 ? 1 : -1) * (12 + Math.random() * 6); // horizontal speed toward road thrownItem._vy = 10 + Math.random() * 6; // vertical speed thrownItem._gravity = 0.7 + Math.random() * 0.2; thrownItem._rotSpeed = (Math.random() - 0.5) * 0.2; thrownItem._fromSide = p.side; thrownItem._active = true; if (!window.thrownItems) window.thrownItems = []; window.thrownItems.push(thrownItem); game.addChild(thrownItem); // Remove bottle from hand immediately after throw if (typeof p.handBottle !== "undefined" && p.handBottle) { p.handBottle.destroy(); p.handBottle = null; } p.throwCooldown = 360 + Math.floor(Math.random() * 180); // 6-9s cooldown, much less frequent } } // Cooldown for next acetity if (p.acetityCooldown > 0) { p.acetityCooldown--; } else if (!p.acetityActive && Math.random() < 0.008) { // 0.8% chance per frame to get acetity if not active p.acetityActive = true; p.acetityTicks = 60 + Math.floor(Math.random() * 60); // 1-2 seconds p.baseSpeed = p.speed; p.speed = p.baseSpeed * (1.7 + Math.random() * 0.7); // 1.7x-2.4x speed // Flash color: yellow or cyan if (p.children && p.children.length > 0) { var flashColor = Math.random() < 0.5 ? 0xffff00 : 0x00ffff; tween(p.children[0], { tint: flashColor }, { duration: 80, yoyo: true, repeat: 2, onComplete: function onComplete() { if (p.children && p.children.length > 0) { p.children[0].tint = 0x333333 + Math.floor(Math.random() * 0xCCCCCC); } } }); } } // If acetity is active, count down and restore speed after if (p.acetityActive) { p.acetityTicks--; // Optionally, make them slightly larger while acetity is active if (p.children && p.children.length > 0) { p.children[0].scaleX = 1.18; p.children[0].scaleY = 1.18; } if (p.acetityTicks <= 0) { p.acetityActive = false; p.speed = p.baseSpeed; p.acetityCooldown = 120 + Math.floor(Math.random() * 120); // 2-4s cooldown if (p.children && p.children.length > 0) { p.children[0].scaleX = 1; p.children[0].scaleY = 1; } } } // If off screen, respawn at the other end with new speed/direction if (p.dir === 1 && p.y > 2732 + 80 || p.dir === -1 && p.y < -80) { // Randomize direction p.dir = Math.random() < 0.5 ? 1 : -1; // Randomize sidewalk p.side = Math.random() < 0.5 ? 0 : 1; p.x = p.side === 0 ? sidewalkWidth / 2 : 2048 - sidewalkWidth / 2; // Y: if going down, start at top; if up, start at bottom p.y = p.dir === 1 ? -40 - Math.random() * 200 : 2732 + 40 + Math.random() * 200; // Randomize speed p.speed = 4 + Math.random() * 2; p.baseSpeed = p.speed; p.acetityActive = false; p.acetityCooldown = 0; // Randomize color if (p.children && p.children.length > 0) { p.children[0].tint = 0x333333 + Math.floor(Math.random() * 0xCCCCCC); p.children[0].scaleX = 1; p.children[0].scaleY = 1; } // Randomize sway phase p.swayTick = Math.random() * Math.PI * 2; } } // Move lane dividers, loop to top for (var i = 0; i < laneDividers.length; i++) { var div = laneDividers[i]; div.update(); if (div.y > 2732 + 60) { div.y -= dividerSpacing * 10; } } // --- UPDATE THROWN ITEMS (from sidewalk people) --- if (!window.thrownItems) window.thrownItems = []; for (var ti = window.thrownItems.length - 1; ti >= 0; ti--) { var item = window.thrownItems[ti]; if (!item._active) continue; // Move item item.x += item._vx; item.y += item._vy; item._vy += item._gravity; item.rotation += item._rotSpeed; // If item is off screen, destroy if (item.x < -100 || item.x > 2048 + 100 || item.y > 2732 + 100) { item.destroy(); window.thrownItems.splice(ti, 1); continue; } // If item reaches the road area (not sidewalk), allow collision with player var roadLeft = sidewalkWidth + 40; var roadRight = 2048 - sidewalkWidth - 40; if (item.x > roadLeft && item.x < roadRight && item.y > 0 && item.y < 2732) { // Check collision with playerCar if (playerCar && item.intersects(playerCar)) { // If it's a spellBottle (all thrown items are), grant shield to player if (!playerCar.shieldActive) { playerCar.shieldActive = true; playerCar.shieldTicks = 180; // 3 seconds at 60fps // Add flame burst effect: flashObject with orange/yellow color, then white var fireMagic = LK.getAsset('spellBottle', { anchorX: 0.5, anchorY: 0.5, x: playerCar.x, y: playerCar.y, scaleX: 1, scaleY: 1, alpha: 0.85, tint: 0xff6600 }); game.addChild(fireMagic); tween(fireMagic, { scaleX: 2.2, scaleY: 2.2, alpha: 0 }, { duration: 1000, easing: tween.cubicOut, onUpdate: function onUpdate() { fireMagic.x = playerCar.x; fireMagic.y = playerCar.y; }, onComplete: function onComplete() { fireMagic.destroy(); } }); LK.effects.flashObject(playerCar, 0xffa500, 350); LK.setTimeout(function () { LK.effects.flashObject(playerCar, 0xffff00, 350); }, 350); } // Remove item after hit item.destroy(); window.thrownItems.splice(ti, 1); continue; } } } // Spell bottle no longer spawns on the road spellBottle = null; spellBottleActive = false; // Spawn enemies/obstacles every 40-60 ticks, randomize spawnTick++; var spawnInterval = 40 + Math.floor(Math.random() * 20); if (spawnTick > spawnInterval) { spawnObstacleOrEnemy(); spawnTick = 0; } // Update enemy cars for (var i = enemyCars.length - 1; i >= 0; i--) { var enemy = enemyCars[i]; enemy.update(); // Off screen if (enemy.y > 2732 + 300) { enemy.destroy(); enemyCars.splice(i, 1); updateScore(score + 1); continue; } // Collision with spell bottle if (spellBottle && enemy.intersects(spellBottle)) { spellBottle.destroy(); spellBottle = null; spellBottleActive = false; } // Collision with player if (enemy.intersects(playerCar)) { if (playerCar.shieldActive) { // Ignore collision, let player pass through enemy car while shield is active continue; } else { // --- LIFE SYSTEM: lose 1 life, give 1 to enemy, show bar --- if (playerLife > 0) { playerLife--; playerCar.life = playerLife; // Give 1 life to enemy (could be used for enemy powerup, here just for demo) if (!enemy.life) enemy.life = 0; enemy.life++; // If enemy is a fireball, update its health bar if (typeof enemy.updateLifeBar === "function") { enemy.updateLifeBar(); } updateLifeBar(); LK.effects.flashObject(playerCar, 0xff0000, 400); if (playerLife <= 0) { LK.getSound('crash').play(); LK.effects.flashScreen(0xff0000, 800); // Explosion effect at player car position LK.effects.flashObject(playerCar, 0xffffff, 700); LK.showGameOver(); return; } // Remove enemy on hit enemy.destroy(); enemyCars.splice(i, 1); continue; } else { LK.getSound('crash').play(); LK.effects.flashScreen(0xff0000, 800); // Explosion effect at player car position LK.effects.flashObject(playerCar, 0xffffff, 700); LK.showGameOver(); return; } } } } // Update obstacles for (var j = obstacles.length - 1; j >= 0; j--) { var obs = obstacles[j]; obs.update(); // Off screen if (obs.y > 2732 + 200) { obs.destroy(); obstacles.splice(j, 1); updateScore(score + 1); continue; } // Collision with player if (obs.intersects(playerCar)) { if (playerCar.shieldActive) { // Ignore collision, let player pass through obstacle while shield is active continue; } else { LK.getSound('crash').play(); LK.effects.flashScreen(0xff0000, 800); // Explosion effect at player car position LK.effects.flashObject(playerCar, 0xffffff, 700); LK.showGameOver(); return; } } } // Update fireballs if (!window.fireballs) window.fireballs = []; for (var f = window.fireballs.length - 1; f >= 0; f--) { var fireball = window.fireballs[f]; fireball.update(); // Track lastY for fireball to detect when it leaves the screen if (typeof fireball.lastY === "undefined") fireball.lastY = fireball.y; // Track if player has already passed this fireball for scoring if (typeof fireball.passedByPlayer === "undefined") fireball.passedByPlayer = false; // Award points if player passes by (dodges) the fireball // Player "passes by" if fireball moves below the player car, and they did not collide if (!fireball.passedByPlayer && fireball.lastY <= playerCar.y && fireball.y > playerCar.y && fireball.life > 0 // Only if not destroyed ) { updateScore(score + 2); // Award 2 points for dodging a fireball fireball.passedByPlayer = true; } // Off screen if (fireball.y > 2732 + 200) { fireball.destroy(); window.fireballs.splice(f, 1); continue; } // Award points if fireball is destroyed (life reaches 0) if (typeof fireball.lastLife === "undefined") fireball.lastLife = fireball.life; if (fireball.lastLife > 0 && fireball.life <= 0) { // Fireball was just destroyed this frame // Always award points for destroying a fireball, even after 70 points updateScore(score + 3); // Award 3 points for destroying a fireball fireball.destroy(); window.fireballs.splice(f, 1); continue; } fireball.lastLife = fireball.life; // Collision with player if (fireball.intersects(playerCar)) { if (playerCar.shieldActive) { // Ignore collision, let player pass through fireball while shield is active // Do NOT destroy the fireball, just let it continue continue; } else { LK.getSound('crash').play(); LK.effects.flashScreen(0xff3300, 900); LK.effects.flashObject(playerCar, 0xff6600, 700); LK.showGameOver(); return; } } fireball.lastY = fireball.y; } // --- HELLBOX SPAWNING & COLLISION HANDLING --- // Find all HellBoxes in the game (if any) if (!window.hellBoxes) window.hellBoxes = []; if (typeof window.lastHellBoxSpawnTick === "undefined") window.lastHellBoxSpawnTick = -1000; if (typeof window.hellBoxActive === "undefined") window.hellBoxActive = false; if (typeof window.hellBoxObj === "undefined") window.hellBoxObj = null; // HellBox spawn logic (like spell bottle) // Prevent HellBox from spawning after 70 points if (!window.hellBoxActive && score <= 70) { // Only allow spawn if enough time has passed since last spawn var hellBoxCooldown = score >= 50 ? 400 : 800; // 6.6s if score>=50, else 13s var hellBoxChance = score >= 50 ? 1 / 90 : 1 / 180; // 1/90 per frame if score>=50, else 1/180 if (ticksSinceStart - window.lastHellBoxSpawnTick > hellBoxCooldown) { if (Math.random() < hellBoxChance) { var hellBoxLane = Math.floor(Math.random() * laneCount); var hellBoxX = lanes[hellBoxLane]; // Place the HellBox at a random Y between 700 and 1200 var hellBoxY = 700 + Math.floor(Math.random() * 500); var hellBox = LK.getAsset('HellBox', { anchorX: 0.5, anchorY: 1.0, x: hellBoxX, y: hellBoxY }); game.addChild(hellBox); window.hellBoxes.push(hellBox); window.hellBoxActive = true; window.hellBoxObj = hellBox; window.lastHellBoxSpawnTick = ticksSinceStart; } } } else if (window.hellBoxObj) { // Move the HellBox down the screen window.hellBoxObj.y += 18 * gameSpeed; // Off screen? Remove if (window.hellBoxObj.y < -200 || window.hellBoxObj.y > 2732 + 200) { window.hellBoxObj.destroy(); var idx = window.hellBoxes.indexOf(window.hellBoxObj); if (idx !== -1) window.hellBoxes.splice(idx, 1); window.hellBoxObj = null; window.hellBoxActive = false; } } // HellBox collision and cleanup for (var h = window.hellBoxes.length - 1; h >= 0; h--) { var hellBox = window.hellBoxes[h]; if (!hellBox) continue; // Update position if needed (if HellBox has .update) if (typeof hellBox.update === "function") hellBox.update(); // Off screen? Remove if (hellBox.y > 2732 + 200) { hellBox.destroy(); window.hellBoxes.splice(h, 1); if (window.hellBoxObj === hellBox) { window.hellBoxObj = null; window.hellBoxActive = false; } continue; } // Collision with player if (hellBox.intersects(playerCar)) { // Player can collect HellBox if shield is active // Always heal and destroy HellBox on collision, regardless of shield var healAmount = 1; if (playerLife < playerMaxLife) { playerLife = Math.min(playerLife + healAmount, playerMaxLife); playerCar.life = playerLife; updateLifeBar(); LK.effects.flashObject(playerCar, 0x00ff00, 400); } hellBox.destroy(); window.hellBoxes.splice(h, 1); if (window.hellBoxObj === hellBox) { window.hellBoxObj = null; window.hellBoxActive = false; } continue; } } ; // Shield timer logic if (playerCar.shieldActive) { playerCar.shieldTicks--; // Show shield seconds left (rounded up) var shieldSeconds = Math.ceil(playerCar.shieldTicks / 60); shieldTxt.setText("Shield: " + shieldSeconds + "s"); // Show shield effect shieldEffect.visible = true; // Keep shield effect centered on playerCar shieldEffect.x = playerCar.x; shieldEffect.y = playerCar.y; // Let the shield effect rotate smoothly around itself using tween if (typeof shieldEffect._tweening === "undefined" || !shieldEffect._tweening) { var _spinShield = function spinShield() { tween(shieldEffect, { rotation: shieldEffect.rotation + Math.PI * 2 }, { duration: 2200, easing: tween.linear, onFinish: _spinShield }); }; shieldEffect.rotation = 0; shieldEffect._tweening = true; _spinShield(); } if (playerCar.shieldTicks <= 0) { playerCar.shieldActive = false; playerCar.shieldTicks = 0; shieldTxt.setText(''); shieldEffect.visible = false; } } else { shieldTxt.setText(''); shieldEffect.visible = false; } // --- LIFE BAR UI update --- // Draw a simple, always-visible horizontal health bar above the player car if (!window.playerHealthBar) { // Bar background (expanded, taller) window.playerHealthBarBg = LK.getAsset('lifeBarBg', { anchorX: 0.5, anchorY: 0.5, width: 520, height: 120, alpha: 0.85 }); // Bar fill (expanded, taller) window.playerHealthBarFill = LK.getAsset('lifeBar', { anchorX: 0.0, anchorY: 0.5, width: 480, height: 90, x: -240, alpha: 1 }); // Bar border (expanded, taller) window.playerHealthBarBorder = LK.getAsset('lifeBarBorder', { anchorX: 0.5, anchorY: 0.5, width: 560, height: 130, alpha: 1 }); // Add to game game.addChild(window.playerHealthBarBg); game.addChild(window.playerHealthBarFill); game.addChild(window.playerHealthBarBorder); window.playerHealthBar = true; } // Always show the health bar unless dead var showBar = playerLife > 0; window.playerHealthBarBg.visible = showBar; window.playerHealthBarFill.visible = showBar; window.playerHealthBarBorder.visible = showBar; // Position above player car if (playerCar) { var barY = (typeof playerCar.baseY !== "undefined" ? playerCar.baseY : playerCar.y) - 140; var barX = playerCar.x; window.playerHealthBarBg.x = barX; window.playerHealthBarBg.y = barY; window.playerHealthBarFill.x = barX - 240; window.playerHealthBarFill.y = barY; window.playerHealthBarBorder.x = barX; window.playerHealthBarBorder.y = barY; // Set width proportional to life, always show a minimum width if not dead var minWidth = 38; var fillW = playerLife > 0 ? Math.max(minWidth, Math.round(480 * (playerLife / playerMaxLife))) : 0; window.playerHealthBarFill.width = fillW; } // Hide if dead if (playerLife <= 0) { window.playerHealthBarBg.visible = false; window.playerHealthBarFill.visible = false; window.playerHealthBarBorder.visible = false; } }; // Place high score text to the left of the score text, both horizontally aligned highScoreTxt.x = LK.gui.top.width / 2 - 220; highScoreTxt.y = scoreTxt.height / 2 + 10; scoreTxt.x = LK.gui.top.width / 2 - 60; scoreTxt.y = scoreTxt.height / 2 + 10; // Position shield timer text directly below score, moved a little to the left shieldTxt.x = LK.gui.top.width / 2 - 80; shieldTxt.y = scoreTxt.y + scoreTxt.height / 2 + shieldTxt.height / 2 + 10; ;
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Enemy Car Class
// Note: The car sprite is always the first child (self.children[0]) for tinting purposes.
var EnemyCar = Container.expand(function () {
var self = Container.call(this);
var car = self.attachAsset('enemyCar', {
anchorX: 0.5,
anchorY: 0.5
});
// --- Overlay a white rectangle for the window ---
// The window is always in the same place on the enemyCar asset, so we overlay a white box.
// These values are tuned for the asset's window position and size.
var windowOverlay = LK.getAsset('heartIcon', {
anchorX: 0.5,
anchorY: 0.5,
width: 70,
height: 38,
x: 0,
y: -38,
tint: 0xffffff,
alpha: 1
});
self.addChild(windowOverlay);
self.lane = 1;
self.speed = 18;
self.update = function () {
self.y += self.speed * gameSpeed;
};
return self;
});
// Fireball Class
var Fireball = Container.expand(function () {
var self = Container.call(this);
var fireball = self.attachAsset('Fireball', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 28;
self.lane = 1;
// --- Health bar for Fireball (ace) ---
self.maxLife = 3;
self.life = self.maxLife;
// Health bar background
self.lifeBarBg = self.attachAsset('lifeBarBg', {
anchorX: 0.5,
anchorY: 0.5,
width: 220,
height: 220,
y: -90,
alpha: 0.92
});
// Health bar border
self.lifeBarBorder = self.attachAsset('lifeBarBorder', {
anchorX: 0.5,
anchorY: 0.5,
width: 260,
height: 260,
y: -90,
alpha: 1
});
// Health bar fill
self.lifeBar = self.attachAsset('lifeBar', {
anchorX: 0.0,
anchorY: 0.5,
width: 180,
height: 60,
x: -90,
y: -90,
alpha: 1
});
// Heart icon
self.lifeBarHeart = self.attachAsset('heartIcon', {
anchorX: 0.5,
anchorY: 0.5,
width: 70,
height: 70,
x: -130,
y: -90,
alpha: 1
});
// Helper to update fireball health bar UI
self.updateLifeBar = function () {
// Show only if not full and not dead
var show = self.life < self.maxLife && self.life > 0;
self.lifeBarBg.visible = show;
self.lifeBar.visible = show;
self.lifeBarBorder.visible = show;
self.lifeBarHeart.visible = show;
// Set width proportional to life
var minWidth = 24;
var w = self.life > 0 ? Math.max(minWidth, Math.round(180 * (self.life / self.maxLife))) : 0;
self.lifeBar.width = w;
// Hide if dead
if (self.life <= 0) {
self.lifeBarBg.visible = false;
self.lifeBar.visible = false;
self.lifeBarBorder.visible = false;
self.lifeBarHeart.visible = false;
}
};
// Initialize bar state
self.updateLifeBar();
self.update = function () {
// Move fireball down the screen
self.y += self.speed * gameSpeed;
// Keep health bar above fireball
self.lifeBarBg.y = -90;
self.lifeBarBorder.y = -90;
self.lifeBar.y = -90;
self.lifeBarHeart.y = -90;
self.lifeBar.x = -90;
self.lifeBarHeart.x = -130;
self.updateLifeBar();
};
return self;
});
// Lane Divider Class
var LaneDivider = Container.expand(function () {
var self = Container.call(this);
var div = self.attachAsset('laneDivider', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 18;
self.update = function () {
self.y += self.speed * gameSpeed;
};
return self;
});
// Obstacle Class
var Obstacle = Container.expand(function () {
var self = Container.call(this);
var obs = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5
});
self.lane = 1;
self.speed = 18;
self.update = function () {
self.y += self.speed * gameSpeed;
};
return self;
});
// Player Car Class
var PlayerCar = Container.expand(function () {
var self = Container.call(this);
var car = self.attachAsset('playerCar', {
anchorX: 0.5,
anchorY: 0.5
});
self.lane = 1; // 0: left, 1: center, 2: right
self.life = playerMaxLife; // Add life property to player car
self.setLane = function (laneIdx) {
self.lane = laneIdx;
// Animate to new lane position
var targetX = lanes[self.lane];
// Always reset Y to baseY for lane change
if (typeof self.baseY === "undefined") self.baseY = 2732 - 500;
tween.stop(self, {
scaleX: true,
scaleY: true,
y: true
});
// Squash/stretch effect: squash horizontally, stretch vertically, then restore
tween(self, {
scaleX: 1.18,
scaleY: 0.88
}, {
duration: 60,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 80,
easing: tween.cubicOut
});
}
});
// Move to new lane and reset Y to baseY (so bounce resumes from correct position)
tween(self, {
x: targetX,
y: self.baseY
}, {
duration: 120,
easing: tween.cubicOut,
onUpdate: function onUpdate() {
updateLifeBar();
}
});
updateLifeBar();
};
// Idle bounce animation for player car
self.baseY = 2732 - 500; // Store the base Y for idle bounce
self.idleBounce = function () {
// Cancel any previous bounce
tween.stop(self, {
y: true
});
// Animate up
tween(self, {
y: self.baseY - 18
}, {
duration: 320,
easing: tween.sineInOut,
onFinish: function onFinish() {
// Animate down
tween(self, {
y: self.baseY + 18
}, {
duration: 320,
easing: tween.sineInOut,
onFinish: function onFinish() {
// Loop bounce
self.idleBounce();
}
});
}
});
};
// Start idle bounce when created
self.idleBounce();
// Clamp Y position to prevent slipping up
self.update = function () {
// If y is above the allowed minimum, clamp it back
var minY = 2732 - 700; // Don't let car go above this Y (adjust as needed)
if (self.y < minY) {
self.y = minY;
}
};
return self;
});
// --- PEOPLE WALKING ON SIDEWALKS ---
// Person class for sidewalk walkers
var SidewalkPerson = Container.expand(function () {
var self = Container.call(this);
// Use heartIcon as a placeholder for a person (circle/box)
var person = self.attachAsset('heartIcon', {
anchorX: 0.5,
anchorY: 0.5,
width: 60,
height: 60,
tint: 0x333333 + Math.floor(Math.random() * 0xCCCCCC),
// random grayish color
alpha: 1
});
// Add hands (arms) as rectangles
var leftHand = LK.getAsset('heartIcon', {
anchorX: 0.5,
anchorY: 0.5,
width: 16,
height: 38,
x: -32,
y: 18,
tint: 0xdeb887,
// light brown, can randomize for variety
alpha: 1
});
var rightHand = LK.getAsset('heartIcon', {
anchorX: 0.5,
anchorY: 0.5,
width: 16,
height: 38,
x: 32,
y: 18,
tint: 0xdeb887,
alpha: 1
});
self.addChild(leftHand);
self.addChild(rightHand);
// Randomize direction: 1 = down, -1 = up
self.dir = Math.random() < 0.5 ? 1 : -1;
// Set speed (pixels per frame)
self.speed = 4 + Math.random() * 2;
// Set sidewalk: 0 = left, 1 = right
self.side = 0;
// Set initial position
if (Math.random() < 0.5) {
self.side = 0;
self.x = sidewalkWidth / 2;
} else {
self.side = 1;
self.x = 2048 - sidewalkWidth / 2;
}
// Y: if going down, start at top; if up, start at bottom
self.y = self.dir === 1 ? -40 - Math.random() * 200 : 2732 + 40 + Math.random() * 200;
// Sway animation
self.swayTick = Math.random() * Math.PI * 2;
self.update = function () {
// Move up or down
self.y += self.speed * self.dir;
// Sway left/right a little
self.swayTick += 0.08;
var sway = Math.sin(self.swayTick) * 10;
self.x = (self.side === 0 ? sidewalkWidth / 2 : 2048 - sidewalkWidth / 2) + sway;
// --- Magic bottle in hand logic ---
if (typeof self.handBottle === "undefined") {
self.handBottle = null;
}
if (typeof self.craveSpellBottle === "undefined") self.craveSpellBottle = Math.random() < 0.5; // 50% crave
// If craving and not on cooldown, show bottle in hand
if (self.craveSpellBottle && self.throwCooldown <= 0 && !self.handBottle) {
// Add a bottle in the hand (side dependent)
self.handBottle = LK.getAsset('spellBottle', {
anchorX: 0.5,
anchorY: 0.5,
width: 38,
height: 38,
x: self.side === 0 ? -32 : 32,
// left or right hand
y: 18,
tint: 0x2BD5E6,
alpha: 1
});
self.addChild(self.handBottle);
}
// If not craving or on cooldown, remove bottle from hand if present
if ((!self.craveSpellBottle || self.throwCooldown > 0) && self.handBottle) {
self.handBottle.destroy();
self.handBottle = null;
}
};
return self;
});
/****
* Initialize Game
****/
// Create and track sidewalk people
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
// Use spellBottle as heart
// Health bar assets
// Spell bottle asset (example: blue bottle, 120x180)
// When the player picks up the magic bottle, the car bursts into flames (flame burst effect)
// Lane positions (3 lanes)
// Car (player)
// Enemy car
// Obstacle (barrier)
// Road lane divider
// Sound for crash
// Sound for lane change
// Music (background)
// Person drawing asset for sidewalk walkers (customize this for identity)
// This is a simple stick-figure style: head (ellipse), body (box), arms (boxes), legs (boxes)
var laneCount = 3;
var laneWidth = 520; // Increased lane width for more space between lanes
var lanes = [2048 / 2 - laneWidth,
// left
2048 / 2,
// center
2048 / 2 + laneWidth // right
];
// Game variables
var playerCar;
var enemyCars = [];
var obstacles = [];
var laneDividers = [];
var score = 0;
var highScore = storage.highScore || 0;
var scoreTxt;
var highScoreTxt;
var gameSpeed = 1;
var ticksSinceStart = 0;
var swipeStartX = null;
var swipeStartY = null;
var swipeActive = false;
var lastLane = 1;
var spawnTick = 0;
var dividerSpacing = 320;
var dragNode = null;
// --- LANE IDLE TIMER SYSTEM ---
var laneIdleTicks = [0, 0, 0]; // Track how long player stays in each lane (in ticks)
var lastPlayerLane = 1; // Track last lane for idle timer
var fireballIdleTriggered = [false, false, false]; // Prevent multiple fireballs for same idle period
// --- LIFE SYSTEM ---
var playerMaxLife = 5;
var playerLife = playerMaxLife;
// Health bar UI will be handled by a new, clear, and consistent system below
// Score display
scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0.5);
LK.gui.top.addChild(scoreTxt);
// Shield timer display
var shieldTxt = new Text2('', {
size: 80,
fill: 0x2BD5E6
});
shieldTxt.anchor.set(0.5, 0.5);
LK.gui.top.addChild(shieldTxt);
// High Score display
highScoreTxt = new Text2('HI: ' + highScore, {
size: 60,
fill: 0xFFFF00
});
highScoreTxt.anchor.set(0.5, 0.5);
LK.gui.top.addChild(highScoreTxt);
// Add swipe left gesture to high score text
var hiSwipeStartX = null;
var hiSwipeStartY = null;
var hiSwipeActive = false;
highScoreTxt.down = function (x, y, obj) {
hiSwipeStartX = x;
hiSwipeStartY = y;
hiSwipeActive = true;
};
highScoreTxt.move = function (x, y, obj) {
if (!hiSwipeActive) return;
var dx = x - hiSwipeStartX;
var dy = y - hiSwipeStartY;
// Only consider horizontal swipes, ignore vertical
if (Math.abs(dx) > 60 && Math.abs(dx) > Math.abs(dy)) {
if (dx < 0) {
// Swipe left detected on high score text
// Flash the high score text white for feedback
tween(highScoreTxt, {
tint: 0xffffff
}, {
duration: 80,
yoyo: true,
repeat: 1,
onComplete: function onComplete() {
highScoreTxt.tint = 0xFFFF00;
}
});
hiSwipeActive = false;
}
}
};
highScoreTxt.up = function (x, y, obj) {
hiSwipeActive = false;
};
// Start music
LK.playMusic('bgmusic');
// Create player car
playerCar = new PlayerCar();
playerCar.x = lanes[1];
playerCar.y = 2732 - 500;
playerCar.setLane(1);
// Shield properties
playerCar.shieldActive = false;
playerCar.shieldTicks = 0;
// Add shield effect asset (invisible by default)
var shieldEffect = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.2,
scaleY: 1.2,
x: playerCar.x,
y: playerCar.y,
alpha: 0.5,
tint: 0xffffff
});
shieldEffect.visible = false;
game.addChild(shieldEffect);
game.addChild(playerCar);
// Add sidewalks to the far left and right edges of the screen (do not touch the road)
var sidewalkWidth = 120;
var sidewalkHeight = 2732;
var sidewalkColor = 0xcccccc; // Light gray for sidewalk
var leftSidewalk = LK.getAsset('heartIcon', {
anchorX: 0,
anchorY: 0,
width: sidewalkWidth,
height: sidewalkHeight,
x: 0,
y: 0,
tint: sidewalkColor,
alpha: 1
});
var rightSidewalk = LK.getAsset('heartIcon', {
anchorX: 0,
anchorY: 0,
width: sidewalkWidth,
height: sidewalkHeight,
x: 2048 - sidewalkWidth,
y: 0,
tint: sidewalkColor,
alpha: 1
});
game.addChild(leftSidewalk);
game.addChild(rightSidewalk);
// Create and track sidewalk people
var sidewalkPeople = [];
var maxPeoplePerSide = 6;
for (var i = 0; i < maxPeoplePerSide * 2; i++) {
var p = new SidewalkPerson();
sidewalkPeople.push(p);
game.addChild(p);
}
// Create lane dividers (vertical lines for each lane)
// For 3 lanes, we want to copy the center lane divider and place it to the right and left of the center stripe, without adding extra lines
// The center divider is between lane 0 and lane 1, and between lane 1 and lane 2
// So, for 3 lanes, we want to draw the two dividers: one between lane 0 and 1, and one between lane 1 and 2
var dividerOffsets = [];
// Find the X positions for the two dividers (between lanes)
for (var l = 1; l < laneCount; l++) {
dividerOffsets.push((lanes[l - 1] + lanes[l]) / 2);
}
// Now, for each divider, draw a set of stripes down the screen
for (var d = 0; d < dividerOffsets.length; d++) {
for (var i = 0; i < 10; i++) {
// Center LaneDivider
var divider = new LaneDivider();
divider.x = dividerOffsets[d];
divider.y = i * dividerSpacing;
laneDividers.push(divider);
game.addChild(divider);
// Left copy
var dividerLeft = new LaneDivider();
dividerLeft.x = dividerOffsets[d] - laneWidth;
dividerLeft.y = i * dividerSpacing;
laneDividers.push(dividerLeft);
game.addChild(dividerLeft);
// Right copy
var dividerRight = new LaneDivider();
dividerRight.x = dividerOffsets[d] + laneWidth;
dividerRight.y = i * dividerSpacing;
laneDividers.push(dividerRight);
game.addChild(dividerRight);
}
}
// Prepare for dynamic spell bottle spawning
var spellBottle = null;
var spellBottleActive = false;
var spellBottleLane = 1;
// Add: track last spell bottle spawn tick to limit spawn rate
var lastSpellBottleSpawnTick = -1000;
// Helper: spawn enemy car, obstacle, or fireball
function spawnObstacleOrEnemy() {
var y = -300;
if (score > 200) {
// After 200 points, only spawn barriers (obstacles), and spawn in 2 lanes at once
// Pick two different lanes
var lanesToUse = [];
while (lanesToUse.length < 2) {
var idx = Math.floor(Math.random() * laneCount);
if (lanesToUse.indexOf(idx) === -1) lanesToUse.push(idx);
}
for (var i = 0; i < lanesToUse.length; i++) {
var obs = new Obstacle();
obs.lane = lanesToUse[i];
obs.x = lanes[lanesToUse[i]];
obs.y = y;
obstacles.push(obs);
game.addChild(obs);
}
} else if (score >= 70) {
// Only spawn fireballs after 70 points
if (!window.fireballs) window.fireballs = [];
if (window.fireballs.length < 2) {
var laneIdx = Math.floor(Math.random() * laneCount);
var fireball = new Fireball();
fireball.lane = laneIdx;
fireball.x = lanes[laneIdx];
fireball.y = y;
window.fireballs.push(fireball);
game.addChild(fireball);
}
} else {
// Randomly decide: 60% enemy car, 25% obstacle, 15% fireball
var laneIdx = Math.floor(Math.random() * laneCount);
var rand = Math.random();
if (rand < 0.6) {
var enemy = new EnemyCar();
enemy.lane = laneIdx;
enemy.x = lanes[laneIdx];
enemy.y = y;
// Assign a bold, high-contrast color tint to the enemy car for visibility
// Use a set of bold, high-contrast tints for random color
var enemyTints = [0xFFFFFF,
// white
0xFF3333,
// red
0x33CCFF,
// blue
0xFFCC00,
// yellow
0x00FF66,
// green
0xFF66FF,
// magenta
0xFF8800,
// orange
0x9933FF // purple
];
var randomColor = enemyTints[Math.floor(Math.random() * enemyTints.length)];
if (enemy.children && enemy.children.length > 0) {
enemy.children[0].tint = randomColor;
}
enemyCars.push(enemy);
game.addChild(enemy);
} else if (rand < 0.85) {
var obs = new Obstacle();
obs.lane = laneIdx;
obs.x = lanes[laneIdx];
obs.y = y;
obstacles.push(obs);
game.addChild(obs);
} else {
// Prevent fireballs from spawning before 60 points
if (score >= 60) {
// Limit fireballs: only spawn if fewer than 2 are on screen
if (!window.fireballs) window.fireballs = [];
if (window.fireballs.length < 2) {
var fireball = new Fireball();
fireball.lane = laneIdx;
fireball.x = lanes[laneIdx];
fireball.y = y;
window.fireballs.push(fireball);
game.addChild(fireball);
}
}
}
}
}
// Helper: update score
function updateScore(val) {
score = val;
scoreTxt.setText(score);
if (score > highScore) {
highScore = score;
highScoreTxt.setText('HI: ' + highScore);
storage.highScore = highScore;
}
}
// Helper: update player health bar UI (global, for use in PlayerCar.setLane and elsewhere)
function updateLifeBar() {
// Always show the health bar unless dead
var showBar = playerLife > 0;
if (window.playerHealthBarBg) window.playerHealthBarBg.visible = showBar;
if (window.playerHealthBarFill) window.playerHealthBarFill.visible = showBar;
if (window.playerHealthBarBorder) window.playerHealthBarBorder.visible = showBar;
// Removed: if (window.playerHealthBarHeart) window.playerHealthBarHeart.visible = showBar;
// Position above player car
if (playerCar && window.playerHealthBarBg && window.playerHealthBarFill && window.playerHealthBarBorder) {
var barY = playerCar.y - 140;
var barX = playerCar.x;
window.playerHealthBarBg.x = barX;
window.playerHealthBarBg.y = barY;
window.playerHealthBarFill.x = barX - 240;
window.playerHealthBarFill.y = barY;
window.playerHealthBarBorder.x = barX;
window.playerHealthBarBorder.y = barY;
// Removed: window.playerHealthBarHeart.x = barX - 320;
// Removed: window.playerHealthBarHeart.y = barY;
// Set width proportional to life, always show a minimum width if not dead
var minWidth = 38;
var fillW = playerLife > 0 ? Math.max(minWidth, Math.round(480 * (playerLife / playerMaxLife))) : 0;
window.playerHealthBarFill.width = fillW;
}
// Hide if dead
if (playerLife <= 0) {
if (window.playerHealthBarBg) window.playerHealthBarBg.visible = false;
if (window.playerHealthBarFill) window.playerHealthBarFill.visible = false;
if (window.playerHealthBarBorder) window.playerHealthBarBorder.visible = false;
// Removed: if (window.playerHealthBarHeart) window.playerHealthBarHeart.visible = false;
}
}
// Touch/drag/swipe handling
game.down = function (x, y, obj) {
swipeStartX = x;
swipeStartY = y;
swipeActive = true;
dragNode = playerCar;
};
game.move = function (x, y, obj) {
// Only handle swipe if active
if (!swipeActive) return;
if (!dragNode) return;
var dx = x - swipeStartX;
var dy = y - swipeStartY;
// Only consider horizontal swipes, ignore vertical
if (Math.abs(dx) > 80 && Math.abs(dx) > Math.abs(dy)) {
var newLane = playerCar.lane;
if (dx < 0 && playerCar.lane > 0) {
newLane = playerCar.lane - 1;
} else if (dx > 0 && playerCar.lane < laneCount - 1) {
newLane = playerCar.lane + 1;
}
if (newLane !== playerCar.lane) {
// Reset idle timer for previous lane, and for new lane
laneIdleTicks[playerCar.lane] = 0;
fireballIdleTriggered[playerCar.lane] = false;
playerCar.setLane(newLane);
// Also reset for new lane
laneIdleTicks[newLane] = 0;
fireballIdleTriggered[newLane] = false;
lastPlayerLane = newLane;
LK.getSound('swipe').play();
swipeActive = false;
dragNode = null;
}
}
};
game.up = function (x, y, obj) {
swipeActive = false;
dragNode = null;
};
// Main game update loop
game.update = function () {
ticksSinceStart++;
// --- LANE IDLE TIMER UPDATE ---
// Only increment for the current lane, reset others
for (var l = 0; l < laneCount; l++) {
if (l === playerCar.lane) {
laneIdleTicks[l]++;
} else {
laneIdleTicks[l] = 0;
fireballIdleTriggered[l] = false;
}
}
// If player has stayed in the same lane for >10s (600 ticks), spawn a fireball in that lane
if (laneIdleTicks[playerCar.lane] > 600 && !fireballIdleTriggered[playerCar.lane]) {
// Only spawn if fewer than 2 fireballs are on screen
if (!window.fireballs) window.fireballs = [];
if (window.fireballs.length < 2) {
var fireball = new Fireball();
fireball.lane = playerCar.lane;
fireball.x = lanes[playerCar.lane];
fireball.y = -300;
window.fireballs.push(fireball);
game.addChild(fireball);
}
fireballIdleTriggered[playerCar.lane] = true;
// Optionally, flash a warning or effect here if desired
}
// Adjust acceleration: start at 20 points, increase slowly until 70 points, then ramp up
if (score >= 400) {
// After 400 points, decrease the speed gradually
if (ticksSinceStart % 60 === 0 && gameSpeed > 1) {
gameSpeed -= 0.08;
if (gameSpeed < 1) gameSpeed = 1;
}
} else if (score >= 250) {
// Between 250 and 400, slow down the speed a little (decelerate)
if (ticksSinceStart % 60 === 0 && gameSpeed > 1.2) {
gameSpeed -= 0.04;
if (gameSpeed < 1.2) gameSpeed = 1.2;
}
} else if (score >= 70) {
// After 70, only fireballs spawn and game returns to normal speed
gameSpeed = 1;
} else if (score >= 20) {
// Between 20 and 70, increase very slowly
if (ticksSinceStart % 90 === 0 && gameSpeed < 2) {
gameSpeed += 0.04;
if (gameSpeed > 2) gameSpeed = 2;
}
} else {
gameSpeed = 1;
}
// --- UPDATE SIDEWALK PEOPLE ---
for (var i = 0; i < sidewalkPeople.length; i++) {
var p = sidewalkPeople[i];
p.update();
// --- GIVE ACETITY: random chance to get a speed boost and color flash ---
if (typeof p.acetityCooldown === "undefined") p.acetityCooldown = 0;
if (typeof p.acetityActive === "undefined") p.acetityActive = false;
if (typeof p.baseSpeed === "undefined") p.baseSpeed = p.speed;
// --- THROWING ITEMS: only crave (desire) spell bottles, and rarely throw them ---
if (typeof p.throwCooldown === "undefined") p.throwCooldown = 0;
if (typeof p.thrownItems === "undefined") p.thrownItems = [];
if (typeof p.craveSpellBottle === "undefined") p.craveSpellBottle = Math.random() < 0.5; // 50% crave
if (p.throwCooldown > 0) {
p.throwCooldown--;
} else if (p.craveSpellBottle && Math.random() < 0.008 && score >= 30) {
// 0.8% chance per frame to throw, only if craving spell bottle (was 0.1%)
// Only throw if not too close to top/bottom and not too many items already
if (p.y > 200 && p.y < 2732 - 200 && (!window.thrownItems || window.thrownItems.length < 2)) {
// Only throw spellBottle
var itemType = 'spellBottle';
var thrownItem = LK.getAsset(itemType, {
anchorX: 0.5,
anchorY: 0.5,
width: 80,
height: 80,
x: p.x + (p.side === 0 ? 60 : -60),
// throw toward road
y: p.y + 10,
tint: 0x2BD5E6,
alpha: 1
});
thrownItem._vx = (p.side === 0 ? 1 : -1) * (12 + Math.random() * 6); // horizontal speed toward road
thrownItem._vy = 10 + Math.random() * 6; // vertical speed
thrownItem._gravity = 0.7 + Math.random() * 0.2;
thrownItem._rotSpeed = (Math.random() - 0.5) * 0.2;
thrownItem._fromSide = p.side;
thrownItem._active = true;
if (!window.thrownItems) window.thrownItems = [];
window.thrownItems.push(thrownItem);
game.addChild(thrownItem);
// Remove bottle from hand immediately after throw
if (typeof p.handBottle !== "undefined" && p.handBottle) {
p.handBottle.destroy();
p.handBottle = null;
}
p.throwCooldown = 360 + Math.floor(Math.random() * 180); // 6-9s cooldown, much less frequent
}
}
// Cooldown for next acetity
if (p.acetityCooldown > 0) {
p.acetityCooldown--;
} else if (!p.acetityActive && Math.random() < 0.008) {
// 0.8% chance per frame to get acetity if not active
p.acetityActive = true;
p.acetityTicks = 60 + Math.floor(Math.random() * 60); // 1-2 seconds
p.baseSpeed = p.speed;
p.speed = p.baseSpeed * (1.7 + Math.random() * 0.7); // 1.7x-2.4x speed
// Flash color: yellow or cyan
if (p.children && p.children.length > 0) {
var flashColor = Math.random() < 0.5 ? 0xffff00 : 0x00ffff;
tween(p.children[0], {
tint: flashColor
}, {
duration: 80,
yoyo: true,
repeat: 2,
onComplete: function onComplete() {
if (p.children && p.children.length > 0) {
p.children[0].tint = 0x333333 + Math.floor(Math.random() * 0xCCCCCC);
}
}
});
}
}
// If acetity is active, count down and restore speed after
if (p.acetityActive) {
p.acetityTicks--;
// Optionally, make them slightly larger while acetity is active
if (p.children && p.children.length > 0) {
p.children[0].scaleX = 1.18;
p.children[0].scaleY = 1.18;
}
if (p.acetityTicks <= 0) {
p.acetityActive = false;
p.speed = p.baseSpeed;
p.acetityCooldown = 120 + Math.floor(Math.random() * 120); // 2-4s cooldown
if (p.children && p.children.length > 0) {
p.children[0].scaleX = 1;
p.children[0].scaleY = 1;
}
}
}
// If off screen, respawn at the other end with new speed/direction
if (p.dir === 1 && p.y > 2732 + 80 || p.dir === -1 && p.y < -80) {
// Randomize direction
p.dir = Math.random() < 0.5 ? 1 : -1;
// Randomize sidewalk
p.side = Math.random() < 0.5 ? 0 : 1;
p.x = p.side === 0 ? sidewalkWidth / 2 : 2048 - sidewalkWidth / 2;
// Y: if going down, start at top; if up, start at bottom
p.y = p.dir === 1 ? -40 - Math.random() * 200 : 2732 + 40 + Math.random() * 200;
// Randomize speed
p.speed = 4 + Math.random() * 2;
p.baseSpeed = p.speed;
p.acetityActive = false;
p.acetityCooldown = 0;
// Randomize color
if (p.children && p.children.length > 0) {
p.children[0].tint = 0x333333 + Math.floor(Math.random() * 0xCCCCCC);
p.children[0].scaleX = 1;
p.children[0].scaleY = 1;
}
// Randomize sway phase
p.swayTick = Math.random() * Math.PI * 2;
}
}
// Move lane dividers, loop to top
for (var i = 0; i < laneDividers.length; i++) {
var div = laneDividers[i];
div.update();
if (div.y > 2732 + 60) {
div.y -= dividerSpacing * 10;
}
}
// --- UPDATE THROWN ITEMS (from sidewalk people) ---
if (!window.thrownItems) window.thrownItems = [];
for (var ti = window.thrownItems.length - 1; ti >= 0; ti--) {
var item = window.thrownItems[ti];
if (!item._active) continue;
// Move item
item.x += item._vx;
item.y += item._vy;
item._vy += item._gravity;
item.rotation += item._rotSpeed;
// If item is off screen, destroy
if (item.x < -100 || item.x > 2048 + 100 || item.y > 2732 + 100) {
item.destroy();
window.thrownItems.splice(ti, 1);
continue;
}
// If item reaches the road area (not sidewalk), allow collision with player
var roadLeft = sidewalkWidth + 40;
var roadRight = 2048 - sidewalkWidth - 40;
if (item.x > roadLeft && item.x < roadRight && item.y > 0 && item.y < 2732) {
// Check collision with playerCar
if (playerCar && item.intersects(playerCar)) {
// If it's a spellBottle (all thrown items are), grant shield to player
if (!playerCar.shieldActive) {
playerCar.shieldActive = true;
playerCar.shieldTicks = 180; // 3 seconds at 60fps
// Add flame burst effect: flashObject with orange/yellow color, then white
var fireMagic = LK.getAsset('spellBottle', {
anchorX: 0.5,
anchorY: 0.5,
x: playerCar.x,
y: playerCar.y,
scaleX: 1,
scaleY: 1,
alpha: 0.85,
tint: 0xff6600
});
game.addChild(fireMagic);
tween(fireMagic, {
scaleX: 2.2,
scaleY: 2.2,
alpha: 0
}, {
duration: 1000,
easing: tween.cubicOut,
onUpdate: function onUpdate() {
fireMagic.x = playerCar.x;
fireMagic.y = playerCar.y;
},
onComplete: function onComplete() {
fireMagic.destroy();
}
});
LK.effects.flashObject(playerCar, 0xffa500, 350);
LK.setTimeout(function () {
LK.effects.flashObject(playerCar, 0xffff00, 350);
}, 350);
}
// Remove item after hit
item.destroy();
window.thrownItems.splice(ti, 1);
continue;
}
}
}
// Spell bottle no longer spawns on the road
spellBottle = null;
spellBottleActive = false;
// Spawn enemies/obstacles every 40-60 ticks, randomize
spawnTick++;
var spawnInterval = 40 + Math.floor(Math.random() * 20);
if (spawnTick > spawnInterval) {
spawnObstacleOrEnemy();
spawnTick = 0;
}
// Update enemy cars
for (var i = enemyCars.length - 1; i >= 0; i--) {
var enemy = enemyCars[i];
enemy.update();
// Off screen
if (enemy.y > 2732 + 300) {
enemy.destroy();
enemyCars.splice(i, 1);
updateScore(score + 1);
continue;
}
// Collision with spell bottle
if (spellBottle && enemy.intersects(spellBottle)) {
spellBottle.destroy();
spellBottle = null;
spellBottleActive = false;
}
// Collision with player
if (enemy.intersects(playerCar)) {
if (playerCar.shieldActive) {
// Ignore collision, let player pass through enemy car while shield is active
continue;
} else {
// --- LIFE SYSTEM: lose 1 life, give 1 to enemy, show bar ---
if (playerLife > 0) {
playerLife--;
playerCar.life = playerLife;
// Give 1 life to enemy (could be used for enemy powerup, here just for demo)
if (!enemy.life) enemy.life = 0;
enemy.life++;
// If enemy is a fireball, update its health bar
if (typeof enemy.updateLifeBar === "function") {
enemy.updateLifeBar();
}
updateLifeBar();
LK.effects.flashObject(playerCar, 0xff0000, 400);
if (playerLife <= 0) {
LK.getSound('crash').play();
LK.effects.flashScreen(0xff0000, 800);
// Explosion effect at player car position
LK.effects.flashObject(playerCar, 0xffffff, 700);
LK.showGameOver();
return;
}
// Remove enemy on hit
enemy.destroy();
enemyCars.splice(i, 1);
continue;
} else {
LK.getSound('crash').play();
LK.effects.flashScreen(0xff0000, 800);
// Explosion effect at player car position
LK.effects.flashObject(playerCar, 0xffffff, 700);
LK.showGameOver();
return;
}
}
}
}
// Update obstacles
for (var j = obstacles.length - 1; j >= 0; j--) {
var obs = obstacles[j];
obs.update();
// Off screen
if (obs.y > 2732 + 200) {
obs.destroy();
obstacles.splice(j, 1);
updateScore(score + 1);
continue;
}
// Collision with player
if (obs.intersects(playerCar)) {
if (playerCar.shieldActive) {
// Ignore collision, let player pass through obstacle while shield is active
continue;
} else {
LK.getSound('crash').play();
LK.effects.flashScreen(0xff0000, 800);
// Explosion effect at player car position
LK.effects.flashObject(playerCar, 0xffffff, 700);
LK.showGameOver();
return;
}
}
}
// Update fireballs
if (!window.fireballs) window.fireballs = [];
for (var f = window.fireballs.length - 1; f >= 0; f--) {
var fireball = window.fireballs[f];
fireball.update();
// Track lastY for fireball to detect when it leaves the screen
if (typeof fireball.lastY === "undefined") fireball.lastY = fireball.y;
// Track if player has already passed this fireball for scoring
if (typeof fireball.passedByPlayer === "undefined") fireball.passedByPlayer = false;
// Award points if player passes by (dodges) the fireball
// Player "passes by" if fireball moves below the player car, and they did not collide
if (!fireball.passedByPlayer && fireball.lastY <= playerCar.y && fireball.y > playerCar.y && fireball.life > 0 // Only if not destroyed
) {
updateScore(score + 2); // Award 2 points for dodging a fireball
fireball.passedByPlayer = true;
}
// Off screen
if (fireball.y > 2732 + 200) {
fireball.destroy();
window.fireballs.splice(f, 1);
continue;
}
// Award points if fireball is destroyed (life reaches 0)
if (typeof fireball.lastLife === "undefined") fireball.lastLife = fireball.life;
if (fireball.lastLife > 0 && fireball.life <= 0) {
// Fireball was just destroyed this frame
// Always award points for destroying a fireball, even after 70 points
updateScore(score + 3); // Award 3 points for destroying a fireball
fireball.destroy();
window.fireballs.splice(f, 1);
continue;
}
fireball.lastLife = fireball.life;
// Collision with player
if (fireball.intersects(playerCar)) {
if (playerCar.shieldActive) {
// Ignore collision, let player pass through fireball while shield is active
// Do NOT destroy the fireball, just let it continue
continue;
} else {
LK.getSound('crash').play();
LK.effects.flashScreen(0xff3300, 900);
LK.effects.flashObject(playerCar, 0xff6600, 700);
LK.showGameOver();
return;
}
}
fireball.lastY = fireball.y;
}
// --- HELLBOX SPAWNING & COLLISION HANDLING ---
// Find all HellBoxes in the game (if any)
if (!window.hellBoxes) window.hellBoxes = [];
if (typeof window.lastHellBoxSpawnTick === "undefined") window.lastHellBoxSpawnTick = -1000;
if (typeof window.hellBoxActive === "undefined") window.hellBoxActive = false;
if (typeof window.hellBoxObj === "undefined") window.hellBoxObj = null;
// HellBox spawn logic (like spell bottle)
// Prevent HellBox from spawning after 70 points
if (!window.hellBoxActive && score <= 70) {
// Only allow spawn if enough time has passed since last spawn
var hellBoxCooldown = score >= 50 ? 400 : 800; // 6.6s if score>=50, else 13s
var hellBoxChance = score >= 50 ? 1 / 90 : 1 / 180; // 1/90 per frame if score>=50, else 1/180
if (ticksSinceStart - window.lastHellBoxSpawnTick > hellBoxCooldown) {
if (Math.random() < hellBoxChance) {
var hellBoxLane = Math.floor(Math.random() * laneCount);
var hellBoxX = lanes[hellBoxLane];
// Place the HellBox at a random Y between 700 and 1200
var hellBoxY = 700 + Math.floor(Math.random() * 500);
var hellBox = LK.getAsset('HellBox', {
anchorX: 0.5,
anchorY: 1.0,
x: hellBoxX,
y: hellBoxY
});
game.addChild(hellBox);
window.hellBoxes.push(hellBox);
window.hellBoxActive = true;
window.hellBoxObj = hellBox;
window.lastHellBoxSpawnTick = ticksSinceStart;
}
}
} else if (window.hellBoxObj) {
// Move the HellBox down the screen
window.hellBoxObj.y += 18 * gameSpeed;
// Off screen? Remove
if (window.hellBoxObj.y < -200 || window.hellBoxObj.y > 2732 + 200) {
window.hellBoxObj.destroy();
var idx = window.hellBoxes.indexOf(window.hellBoxObj);
if (idx !== -1) window.hellBoxes.splice(idx, 1);
window.hellBoxObj = null;
window.hellBoxActive = false;
}
}
// HellBox collision and cleanup
for (var h = window.hellBoxes.length - 1; h >= 0; h--) {
var hellBox = window.hellBoxes[h];
if (!hellBox) continue;
// Update position if needed (if HellBox has .update)
if (typeof hellBox.update === "function") hellBox.update();
// Off screen? Remove
if (hellBox.y > 2732 + 200) {
hellBox.destroy();
window.hellBoxes.splice(h, 1);
if (window.hellBoxObj === hellBox) {
window.hellBoxObj = null;
window.hellBoxActive = false;
}
continue;
}
// Collision with player
if (hellBox.intersects(playerCar)) {
// Player can collect HellBox if shield is active
// Always heal and destroy HellBox on collision, regardless of shield
var healAmount = 1;
if (playerLife < playerMaxLife) {
playerLife = Math.min(playerLife + healAmount, playerMaxLife);
playerCar.life = playerLife;
updateLifeBar();
LK.effects.flashObject(playerCar, 0x00ff00, 400);
}
hellBox.destroy();
window.hellBoxes.splice(h, 1);
if (window.hellBoxObj === hellBox) {
window.hellBoxObj = null;
window.hellBoxActive = false;
}
continue;
}
}
;
// Shield timer logic
if (playerCar.shieldActive) {
playerCar.shieldTicks--;
// Show shield seconds left (rounded up)
var shieldSeconds = Math.ceil(playerCar.shieldTicks / 60);
shieldTxt.setText("Shield: " + shieldSeconds + "s");
// Show shield effect
shieldEffect.visible = true;
// Keep shield effect centered on playerCar
shieldEffect.x = playerCar.x;
shieldEffect.y = playerCar.y;
// Let the shield effect rotate smoothly around itself using tween
if (typeof shieldEffect._tweening === "undefined" || !shieldEffect._tweening) {
var _spinShield = function spinShield() {
tween(shieldEffect, {
rotation: shieldEffect.rotation + Math.PI * 2
}, {
duration: 2200,
easing: tween.linear,
onFinish: _spinShield
});
};
shieldEffect.rotation = 0;
shieldEffect._tweening = true;
_spinShield();
}
if (playerCar.shieldTicks <= 0) {
playerCar.shieldActive = false;
playerCar.shieldTicks = 0;
shieldTxt.setText('');
shieldEffect.visible = false;
}
} else {
shieldTxt.setText('');
shieldEffect.visible = false;
}
// --- LIFE BAR UI update ---
// Draw a simple, always-visible horizontal health bar above the player car
if (!window.playerHealthBar) {
// Bar background (expanded, taller)
window.playerHealthBarBg = LK.getAsset('lifeBarBg', {
anchorX: 0.5,
anchorY: 0.5,
width: 520,
height: 120,
alpha: 0.85
});
// Bar fill (expanded, taller)
window.playerHealthBarFill = LK.getAsset('lifeBar', {
anchorX: 0.0,
anchorY: 0.5,
width: 480,
height: 90,
x: -240,
alpha: 1
});
// Bar border (expanded, taller)
window.playerHealthBarBorder = LK.getAsset('lifeBarBorder', {
anchorX: 0.5,
anchorY: 0.5,
width: 560,
height: 130,
alpha: 1
});
// Add to game
game.addChild(window.playerHealthBarBg);
game.addChild(window.playerHealthBarFill);
game.addChild(window.playerHealthBarBorder);
window.playerHealthBar = true;
}
// Always show the health bar unless dead
var showBar = playerLife > 0;
window.playerHealthBarBg.visible = showBar;
window.playerHealthBarFill.visible = showBar;
window.playerHealthBarBorder.visible = showBar;
// Position above player car
if (playerCar) {
var barY = (typeof playerCar.baseY !== "undefined" ? playerCar.baseY : playerCar.y) - 140;
var barX = playerCar.x;
window.playerHealthBarBg.x = barX;
window.playerHealthBarBg.y = barY;
window.playerHealthBarFill.x = barX - 240;
window.playerHealthBarFill.y = barY;
window.playerHealthBarBorder.x = barX;
window.playerHealthBarBorder.y = barY;
// Set width proportional to life, always show a minimum width if not dead
var minWidth = 38;
var fillW = playerLife > 0 ? Math.max(minWidth, Math.round(480 * (playerLife / playerMaxLife))) : 0;
window.playerHealthBarFill.width = fillW;
}
// Hide if dead
if (playerLife <= 0) {
window.playerHealthBarBg.visible = false;
window.playerHealthBarFill.visible = false;
window.playerHealthBarBorder.visible = false;
}
};
// Place high score text to the left of the score text, both horizontally aligned
highScoreTxt.x = LK.gui.top.width / 2 - 220;
highScoreTxt.y = scoreTxt.height / 2 + 10;
scoreTxt.x = LK.gui.top.width / 2 - 60;
scoreTxt.y = scoreTxt.height / 2 + 10;
// Position shield timer text directly below score, moved a little to the left
shieldTxt.x = LK.gui.top.width / 2 - 80;
shieldTxt.y = scoreTxt.y + scoreTxt.height / 2 + shieldTxt.height / 2 + 10;
;