/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Enemy Car Class (basic) var EnemyCar = Container.expand(function () { var self = Container.call(this); var car = self.attachAsset('enemyCar', { anchorX: 0.5, anchorY: 0.5 }); self.width = car.width; self.height = car.height; self.speed = 12; // Will increase with level self.type = 1; self.hp = 1; self.crashed = false; // Damage indicator graphics (front, back, left, right) var indicatorLength = car.width * 0.7; var indicatorThickness = 22; var indicatorColor = 0xffeb3b; // Front indicator (top of car) var frontIndicator = self.attachAsset('crashEffect', { anchorX: 0.5, anchorY: 0.5, x: 0, y: -car.height / 2 + indicatorThickness / 2, scaleX: indicatorLength / 220, scaleY: indicatorThickness / 120, alpha: 0.7 }); // Back indicator (bottom of car) var backIndicator = self.attachAsset('crashEffect', { anchorX: 0.5, anchorY: 0.5, x: 0, y: car.height / 2 - indicatorThickness / 2, scaleX: indicatorLength / 220, scaleY: indicatorThickness / 120, alpha: 0.7 }); // Left indicator (left side of car) var leftIndicator = self.attachAsset('crashEffect', { anchorX: 0.5, anchorY: 0.5, x: -car.width / 2 + indicatorThickness / 2, y: 0, scaleX: indicatorThickness / 120, scaleY: indicatorLength / 220, alpha: 0.7, rotation: Math.PI / 2 }); // Right indicator (right side of car) var rightIndicator = self.attachAsset('crashEffect', { anchorX: 0.5, anchorY: 0.5, x: car.width / 2 - indicatorThickness / 2, y: 0, scaleX: indicatorThickness / 120, scaleY: indicatorLength / 220, alpha: 0.7, rotation: Math.PI / 2 }); // Hide all indicators initially frontIndicator.visible = false; backIndicator.visible = false; leftIndicator.visible = false; rightIndicator.visible = false; // Crash effect, now with hitPart argument: "front", "back", "left", "right" self.showCrash = function (hitPart) { // Only set the indicator on the first hit if (!self._firstCrashPart) { self._firstCrashPart = hitPart; } // Hide all indicators frontIndicator.visible = false; backIndicator.visible = false; leftIndicator.visible = false; rightIndicator.visible = false; // Show only the first hit part indicator var showPart = self._firstCrashPart || hitPart; if (showPart === "front") { frontIndicator.visible = true; } else if (showPart === "back") { backIndicator.visible = true; } else if (showPart === "left") { leftIndicator.visible = true; } else if (showPart === "right") { rightIndicator.visible = true; } else { frontIndicator.visible = true; // fallback } var effect = LK.getAsset('crashEffect', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, alpha: 0.8, scaleX: 0.5, scaleY: 0.5 }); self.addChild(effect); tween(effect, { scaleX: 1.5, scaleY: 1.5, alpha: 0 }, { duration: 400, easing: tween.easeOut, onFinish: function onFinish() { effect.destroy(); // Hide indicator after effect // Do NOT hide the first hit indicator, keep it visible } }); }; return self; }); // Enemy Car 2 (tougher, for higher levels) var EnemyCar2 = Container.expand(function () { var self = Container.call(this); var car = self.attachAsset('enemyCar2', { anchorX: 0.5, anchorY: 0.5 }); self.width = car.width; self.height = car.height; self.speed = 16; self.type = 2; self.hp = 2; self.crashed = false; // Damage indicator graphics (front, back, left, right) var indicatorLength = car.width * 0.7; var indicatorThickness = 22; var indicatorColor = 0xffeb3b; // Front indicator (top of car) var frontIndicator = self.attachAsset('crashEffect', { anchorX: 0.5, anchorY: 0.5, x: 0, y: -car.height / 2 + indicatorThickness / 2, scaleX: indicatorLength / 220, scaleY: indicatorThickness / 120, alpha: 0.7 }); // Back indicator (bottom of car) var backIndicator = self.attachAsset('crashEffect', { anchorX: 0.5, anchorY: 0.5, x: 0, y: car.height / 2 - indicatorThickness / 2, scaleX: indicatorLength / 220, scaleY: indicatorThickness / 120, alpha: 0.7 }); // Left indicator (left side of car) var leftIndicator = self.attachAsset('crashEffect', { anchorX: 0.5, anchorY: 0.5, x: -car.width / 2 + indicatorThickness / 2, y: 0, scaleX: indicatorThickness / 120, scaleY: indicatorLength / 220, alpha: 0.7, rotation: Math.PI / 2 }); // Right indicator (right side of car) var rightIndicator = self.attachAsset('crashEffect', { anchorX: 0.5, anchorY: 0.5, x: car.width / 2 - indicatorThickness / 2, y: 0, scaleX: indicatorThickness / 120, scaleY: indicatorLength / 220, alpha: 0.7, rotation: Math.PI / 2 }); // Hide all indicators initially frontIndicator.visible = false; backIndicator.visible = false; leftIndicator.visible = false; rightIndicator.visible = false; self.showCrash = function (hitPart) { // Only set the indicator on the first hit if (!self._firstCrashPart) { self._firstCrashPart = hitPart; } // Hide all indicators frontIndicator.visible = false; backIndicator.visible = false; leftIndicator.visible = false; rightIndicator.visible = false; // Show only the first hit part indicator var showPart = self._firstCrashPart || hitPart; if (showPart === "front") { frontIndicator.visible = true; } else if (showPart === "back") { backIndicator.visible = true; } else if (showPart === "left") { leftIndicator.visible = true; } else if (showPart === "right") { rightIndicator.visible = true; } else { frontIndicator.visible = true; // fallback } var effect = LK.getAsset('crashEffect', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, alpha: 0.8, scaleX: 0.5, scaleY: 0.5 }); self.addChild(effect); tween(effect, { scaleX: 1.5, scaleY: 1.5, alpha: 0 }, { duration: 400, easing: tween.easeOut, onFinish: function onFinish() { effect.destroy(); // Do NOT hide the first hit indicator, keep it visible } }); }; 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.width = car.width; self.height = car.height; self.hp = 1; // Upgradable self.invincible = false; self.invincibleTimer = 0; self.upgradeLevel = 0; // Flash effect for invincibility self.flashInvincible = function () { self.invincible = true; self.invincibleTimer = 60; // 1 second at 60fps tween(self, { alpha: 0.5 }, { duration: 100, easing: tween.easeInOut, onFinish: function onFinish() { tween(self, { alpha: 1 }, { duration: 100, easing: tween.easeInOut }); } }); }; // Upgrade effect self.showUpgrade = function () { var effect = LK.getAsset('upgradeEffect', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0, alpha: 0.7, scaleX: 0.5, scaleY: 0.5 }); self.addChild(effect); tween(effect, { scaleX: 1.5, scaleY: 1.5, alpha: 0 }, { duration: 500, easing: tween.easeOut, onFinish: function onFinish() { effect.destroy(); } }); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x222222 }); /**** * Game Code ****/ // Arena scroll speed (background illusion) // Player car: blue box // Enemy car: red box // Enemy car 2: green box (for future levels) // Crash effect: yellow ellipse // Upgrade effect: purple ellipse // Sound: crash // Sound: upgrade // Music: background // --- Desert background, cactuses, and dust cloud setup --- // Add a desert background image (fills the arena) // new visual for pit/hole // Stack two desert backgrounds vertically for a longer scrolling road var desertBg1 = LK.getAsset('desertBg', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732 }); var desertBg2 = LK.getAsset('desertBg', { anchorX: 0, anchorY: 0, x: 0, y: -2732, width: 2048, height: 2732 }); game.addChild(desertBg1); game.addChild(desertBg2); // Add more cactuses, only along the left and right edges (not top/bottom or middle) var cactusPerSide = 8; for (var i = 0; i < cactusPerSide; i++) { // Left edge var cactusL = LK.getAsset('cactus', { anchorX: 0.5, anchorY: 1, scaleX: 0.8 + Math.random() * 0.4, scaleY: 0.8 + Math.random() * 0.4 }); cactusL.x = 100 + cactusL.width / 2; // Avoid top/bottom 100px, and avoid the very center (leave a gap in the middle) var minY = 200; var maxY = 2732 - 200; var gapStart = 2732 / 2 - 350; var gapEnd = 2732 / 2 + 350; var yVal = minY + Math.random() * (maxY - minY); // If in the center gap, push up or down if (yVal > gapStart && yVal < gapEnd) { if (Math.random() < 0.5) { yVal = minY + Math.random() * (gapStart - minY); } else { yVal = gapEnd + Math.random() * (maxY - gapEnd); } } cactusL.y = yVal; game.addChild(cactusL); // Right edge var cactusR = LK.getAsset('cactus', { anchorX: 0.5, anchorY: 1, scaleX: 0.8 + Math.random() * 0.4, scaleY: 0.8 + Math.random() * 0.4 }); cactusR.x = 2048 - 100 - cactusR.width / 2; // Use same y logic as left var yValR = minY + Math.random() * (maxY - minY); if (yValR > gapStart && yValR < gapEnd) { if (Math.random() < 0.5) { yValR = minY + Math.random() * (gapStart - minY); } else { yValR = gapEnd + Math.random() * (maxY - gapEnd); } } cactusR.y = yValR; game.addChild(cactusR); } // Dust clouds spawn from all screen edges and move in random directions var dustClouds = []; var dustCloudCount = 8; for (var i = 0; i < dustCloudCount; i++) { // Randomly pick a spawn edge: 0=top, 1=bottom, 2=left, 3=right var edge = Math.floor(Math.random() * 4); var x, y, dx, dy; var margin = 100; var speed = 2 + Math.random() * 2; if (edge === 0) { // top x = margin + Math.random() * (2048 - 2 * margin); y = -80; dx = (Math.random() - 0.5) * 2; dy = speed; } else if (edge === 1) { // bottom x = margin + Math.random() * (2048 - 2 * margin); y = 2732 + 80; dx = (Math.random() - 0.5) * 2; dy = -speed; } else if (edge === 2) { // left x = -80; y = margin + Math.random() * (2732 - 2 * margin); dx = speed; dy = (Math.random() - 0.5) * 2; } else { // right x = 2048 + 80; y = margin + Math.random() * (2732 - 2 * margin); dx = -speed; dy = (Math.random() - 0.5) * 2; } var dustCloud = LK.getAsset('dustCloud', { anchorX: 0.5, anchorY: 0.5, x: x, y: y, scaleX: 2.2, scaleY: 1.2, alpha: 0.5 }); dustCloud._dx = dx; dustCloud._dy = dy; dustCloud._spawnEdge = edge; game.addChild(dustCloud); dustClouds.push(dustCloud); } var scrollSpeed = 10; // Player var player = new PlayerCar(); player.x = 2048 / 2; player.y = 2732 - 500; game.addChild(player); // --- Heart (life) system --- var maxHearts = 3; var hearts = 0; // Start with 0 var heartIcons = []; // Create all heart icons at once, hide them initially for (var i = 0; i < maxHearts; i++) { var heart = LK.getAsset('heart', { x: 60 + i * 80, y: 60, anchorX: 0.5, anchorY: 0.5, scaleX: 0.25, scaleY: 0.25 }); heart.visible = false; game.addChild(heart); heartIcons.push(heart); } function updateHeartsDisplay() { for (var i = 0; i < maxHearts; i++) { heartIcons[i].visible = i < hearts; } } updateHeartsDisplay(); // Score var score = 0; var scoreTxt = new Text2('0', { size: 120, fill: "#fff" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Level var level = 1; var levelTxt = new Text2('Lv.1', { size: 70, fill: "#fff" }); levelTxt.anchor.set(0.5, 0); LK.gui.top.addChild(levelTxt); levelTxt.y = 120; // Upgrades var upgradeTxt = new Text2('', { size: 70, fill: 0xFFD600 }); upgradeTxt.anchor.set(0.5, 0); LK.gui.top.addChild(upgradeTxt); upgradeTxt.y = 200; // Enemies var enemies = []; var enemySpawnTimer = 0; var enemySpawnInterval = 60; // frames // Dragging var dragNode = null; // Invincibility after crash var invincibleFrames = 0; // Arena bounds (leave 100px at top for menu) var arenaLeft = 100; var arenaRight = 2048 - 100; var arenaTop = 100; var arenaBottom = 2732 - 100; // Music LK.playMusic('arenaMusic'); // Helper: spawn enemy function spawnEnemy() { var enemy; if (level < 3 || Math.random() < 0.7) { enemy = new EnemyCar(); } else { enemy = new EnemyCar2(); } // Spawn from center and off-center lanes (not far left/right) // Thorns and pits will not slide to the far left and right of the screen var laneCenters = [arenaLeft + (arenaRight - arenaLeft) * 0.28, // left off-center arenaLeft + (arenaRight - arenaLeft) * 0.38, // left of center (arenaLeft + arenaRight) / 2, // center arenaLeft + (arenaRight - arenaLeft) * 0.62, // right of center arenaLeft + (arenaRight - arenaLeft) * 0.72 // right off-center ]; var lane = Math.floor(Math.random() * laneCenters.length); enemy.x = laneCenters[lane]; enemy.y = -enemy.height / 2; // Speed up with level if (enemy.type === 1) { enemy.speed = 12 + level * 1.5; } else { enemy.speed = 16 + level * 1.2; } enemies.push(enemy); game.addChild(enemy); } // Helper: upgrade player function upgradePlayer() { player.upgradeLevel += 1; player.hp += 1; player.showUpgrade(); upgradeTxt.setText('Upgrade! HP: ' + player.hp); LK.getSound('upgrade').play(); LK.setTimeout(function () { upgradeTxt.setText(''); }, 1200); } // Handle move (dragging player) function handleMove(x, y, obj) { if (dragNode === player) { // Clamp to arena var px = x; var py = y; var halfW = player.width / 2; var halfH = player.height / 2; if (px < arenaLeft + halfW) px = arenaLeft + halfW; if (px > arenaRight - halfW) px = arenaRight - halfW; if (py < arenaTop + halfH) py = arenaTop + halfH; if (py > arenaBottom - halfH) py = arenaBottom - halfH; player.x = px; player.y = py; } } // Touch/drag events game.down = function (x, y, obj) { // Only start drag if touch is on player var dx = x - player.x; var dy = y - player.y; if (dx * dx + dy * dy < player.width / 2 * (player.width / 2)) { dragNode = player; handleMove(x, y, obj); } }; game.move = function (x, y, obj) { handleMove(x, y, obj); }; game.up = function (x, y, obj) { dragNode = null; }; // Main update loop game.update = function () { // Scroll both desert backgrounds for a seamless, infinite road effect desertBg1.y += scrollSpeed; desertBg2.y += scrollSpeed; // If a background moves off the bottom, move it to the top of the other if (desertBg1.y >= 2732) { desertBg1.y = desertBg2.y - 2732; } if (desertBg2.y >= 2732) { desertBg2.y = desertBg1.y - 2732; } // Animate dust clouds for movement illusion (from all edges) if (dustClouds && dustClouds.length) { for (var d = 0; d < dustClouds.length; d++) { var dc = dustClouds[d]; dc.x += dc._dx; dc.y += dc._dy; // If cloud is out of bounds, respawn from a random edge var outOfBounds = dc.x < -200 || dc.x > 2048 + 200 || dc.y < -200 || dc.y > 2732 + 200; if (outOfBounds) { // Pick a new random edge and direction var edge = Math.floor(Math.random() * 4); var margin = 100; var speed = 2 + Math.random() * 2; if (edge === 0) { // top dc.x = margin + Math.random() * (2048 - 2 * margin); dc.y = -80; dc._dx = (Math.random() - 0.5) * 2; dc._dy = speed; } else if (edge === 1) { // bottom dc.x = margin + Math.random() * (2048 - 2 * margin); dc.y = 2732 + 80; dc._dx = (Math.random() - 0.5) * 2; dc._dy = -speed; } else if (edge === 2) { // left dc.x = -80; dc.y = margin + Math.random() * (2732 - 2 * margin); dc._dx = speed; dc._dy = (Math.random() - 0.5) * 2; } else { // right dc.x = 2048 + 80; dc.y = margin + Math.random() * (2732 - 2 * margin); dc._dx = -speed; dc._dy = (Math.random() - 0.5) * 2; } } } } // --- Pits: spawn, animate, and collision --- // Pit setup: global arrays and spawn timer if (typeof pits === "undefined") { pits = []; pitSpawnTimer = 0; pitSpawnInterval = 90; // frames, can be tuned // Pit lanes: center and off-center, not far left/right pitLanes = [arenaLeft + (arenaRight - arenaLeft) * 0.28, arenaLeft + (arenaRight - arenaLeft) * 0.38, (arenaLeft + arenaRight) / 2, arenaLeft + (arenaRight - arenaLeft) * 0.62, arenaLeft + (arenaRight - arenaLeft) * 0.72]; } // Animate pits for (var p = pits.length - 1; p >= 0; p--) { var pit = pits[p]; if (pit.lastY === undefined) pit.lastY = pit.y; pit.y += scrollSpeed + 14 + level * 0.7; // pits move slightly faster than scroll // Remove pit if off screen if (pit.lastY <= 2732 + 200 && pit.y > 2732 + 200) { pit.destroy(); pits.splice(p, 1); continue; } pit.lastY = pit.y; } // Spawn pits pitSpawnTimer++; var pitInt = Math.max(50, pitSpawnInterval - level * 2); if (pitSpawnTimer >= pitInt) { // Only spawn in center and off-center lanes, not far left/right var lane = Math.floor(Math.random() * pitLanes.length); var pit = LK.getAsset('pitHole', { anchorX: 0.5, anchorY: 0.5, x: pitLanes[lane], y: -60, scaleX: 1.2 + Math.random() * 0.3, scaleY: 0.7 + Math.random() * 0.2, alpha: 0.95 }); pit.width = 180 + Math.random() * 80; pit.height = 80 + Math.random() * 30; pits.push(pit); game.addChild(pit); pit.lastY = pit.y; pit.lastWasIntersecting = false; pit.isPit = true; pit.isDeadly = true; pit._type = "pit"; pit._alreadyHit = false; pit._id = "pit_" + Date.now() + "_" + Math.floor(Math.random() * 10000); pitSpawnTimer = 0; } // Pit collision with player (kill instantly) for (var p = pits.length - 1; p >= 0; p--) { var pit = pits[p]; if (pit._alreadyHit) continue; var isIntersecting = player.intersects(pit); if (pit.lastWasIntersecting === false && isIntersecting) { pit._alreadyHit = true; if (hearts > 0 && !player.invincible) { // Use a heart, grant invincibility, do not die hearts--; if (hearts >= 0 && hearts < heartIcons.length) { heartIcons[hearts].visible = false; } updateHeartsDisplay(); player.flashInvincible(); LK.effects.flashScreen(0x000000, 600); } else { LK.effects.flashScreen(0x000000, 1000); LK.showGameOver(); return; } } pit.lastWasIntersecting = isIntersecting; } // Arena scroll illusion: move all enemies down for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; enemy.y += enemy.speed + scrollSpeed; // End game if enemy reaches the end of the screen (bottom) if (enemy.lastY === undefined) { enemy.lastY = enemy.y; } var screenEndY = 2732 + 100; if (enemy.lastY <= screenEndY && enemy.y > screenEndY) { // Lose a heart if enemy reaches the end hearts--; if (hearts >= 0 && hearts < heartIcons.length) { heartIcons[hearts].visible = false; } updateHeartsDisplay(); LK.effects.flashScreen(0xff0000, 1000); // Remove the enemy from the game enemy.destroy(); enemies.splice(i, 1); // Game over if no hearts left if (hearts <= 0) { LK.showGameOver(); return; } // Otherwise, continue the game continue; } enemy.lastY = enemy.y; } // Spawn enemies enemySpawnTimer++; var spawnInt = Math.max(30, enemySpawnInterval - level * 2); if (enemySpawnTimer >= spawnInt) { spawnEnemy(); enemySpawnTimer = 0; } // Collision: player vs enemies for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; if (enemy.crashed) continue; if (player.invincible) continue; if (player.intersects(enemy)) { // Determine which side of the enemy was hit by the player var dx = player.x - enemy.x; var dy = player.y - enemy.y; var absDx = Math.abs(dx); var absDy = Math.abs(dy); var hitPart = "front"; if (absDx > absDy) { // Side hit if (dx < 0) { hitPart = "left"; } else { hitPart = "right"; } } else { // Vertical hit if (dy < 0) { hitPart = "front"; } else { hitPart = "back"; } } // Only allow crash and destroy if hitPart is "back" if (hitPart === "back") { // Crash! // For EnemyCar2, require 2 hits to destroy if (enemy.type === 2) { if (typeof enemy._hitCount === "undefined") enemy._hitCount = 0; enemy._hitCount++; enemy.showCrash(hitPart); LK.getSound('crash').play(); // Only destroy and score after 2 hits if (enemy._hitCount >= 2) { enemy.crashed = true; score += 10; LK.setScore(score); scoreTxt.setText(score); // Remove enemy after effect LK.setTimeout(function (e, idx) { return function () { if (enemies[idx] === e) { e.destroy(); enemies.splice(idx, 1); } }; }(enemy, i), 400); } // Player does not take damage, only enemy is destroyed } else { enemy.crashed = true; enemy.showCrash(hitPart); LK.getSound('crash').play(); score += 10; LK.setScore(score); scoreTxt.setText(score); // Remove enemy after effect LK.setTimeout(function (e, idx) { return function () { if (enemies[idx] === e) { e.destroy(); enemies.splice(idx, 1); } }; }(enemy, i), 400); // Player does not take damage, only enemy is destroyed } } } } // --- Blur effect when player collides with dust cloud --- if (typeof game._blurActive === "undefined") { game._blurActive = false; game._blurLastIntersecting = false; } // Check intersection with any dust cloud var dustCloudIntersecting = false; if (dustClouds && dustClouds.length) { for (var d = 0; d < dustClouds.length; d++) { if (player.intersects(dustClouds[d])) { dustCloudIntersecting = true; break; } } } if (!game._blurLastIntersecting && dustCloudIntersecting && !game._blurActive) { game._blurActive = true; // Darken the screen more when colliding with a dust cloud LK.effects.flashScreen(0x000000, 1200, { alpha: 0.55 }); // More darkness, longer duration // Optionally, you can still use blur if you want both effects: // if (LK.effects && typeof LK.effects.blurScreen === "function") { // LK.effects.blurScreen(8, 2000); // } LK.setTimeout(function () { game._blurActive = false; }, 1200); } game._blurLastIntersecting = dustCloudIntersecting; // Invincibility timer if (player.invincible) { player.invincibleTimer--; if (player.invincibleTimer <= 0) { player.invincible = false; player.alpha = 1; } } // Level up every 100 points var newLevel = Math.floor(score / 100) + 1; if (newLevel > level) { level = newLevel; levelTxt.setText('Lv.' + level); // Every 2 levels, upgrade player if (level % 2 === 0) { upgradePlayer(); } // Every 5 levels, gain a heart (up to 3), starting from 0 var newHearts = Math.min(Math.floor(level / 5), maxHearts); if (newHearts > hearts) { hearts = newHearts; updateHeartsDisplay(); } } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Enemy Car Class (basic)
var EnemyCar = Container.expand(function () {
var self = Container.call(this);
var car = self.attachAsset('enemyCar', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = car.width;
self.height = car.height;
self.speed = 12; // Will increase with level
self.type = 1;
self.hp = 1;
self.crashed = false;
// Damage indicator graphics (front, back, left, right)
var indicatorLength = car.width * 0.7;
var indicatorThickness = 22;
var indicatorColor = 0xffeb3b;
// Front indicator (top of car)
var frontIndicator = self.attachAsset('crashEffect', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -car.height / 2 + indicatorThickness / 2,
scaleX: indicatorLength / 220,
scaleY: indicatorThickness / 120,
alpha: 0.7
});
// Back indicator (bottom of car)
var backIndicator = self.attachAsset('crashEffect', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: car.height / 2 - indicatorThickness / 2,
scaleX: indicatorLength / 220,
scaleY: indicatorThickness / 120,
alpha: 0.7
});
// Left indicator (left side of car)
var leftIndicator = self.attachAsset('crashEffect', {
anchorX: 0.5,
anchorY: 0.5,
x: -car.width / 2 + indicatorThickness / 2,
y: 0,
scaleX: indicatorThickness / 120,
scaleY: indicatorLength / 220,
alpha: 0.7,
rotation: Math.PI / 2
});
// Right indicator (right side of car)
var rightIndicator = self.attachAsset('crashEffect', {
anchorX: 0.5,
anchorY: 0.5,
x: car.width / 2 - indicatorThickness / 2,
y: 0,
scaleX: indicatorThickness / 120,
scaleY: indicatorLength / 220,
alpha: 0.7,
rotation: Math.PI / 2
});
// Hide all indicators initially
frontIndicator.visible = false;
backIndicator.visible = false;
leftIndicator.visible = false;
rightIndicator.visible = false;
// Crash effect, now with hitPart argument: "front", "back", "left", "right"
self.showCrash = function (hitPart) {
// Only set the indicator on the first hit
if (!self._firstCrashPart) {
self._firstCrashPart = hitPart;
}
// Hide all indicators
frontIndicator.visible = false;
backIndicator.visible = false;
leftIndicator.visible = false;
rightIndicator.visible = false;
// Show only the first hit part indicator
var showPart = self._firstCrashPart || hitPart;
if (showPart === "front") {
frontIndicator.visible = true;
} else if (showPart === "back") {
backIndicator.visible = true;
} else if (showPart === "left") {
leftIndicator.visible = true;
} else if (showPart === "right") {
rightIndicator.visible = true;
} else {
frontIndicator.visible = true; // fallback
}
var effect = LK.getAsset('crashEffect', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0,
alpha: 0.8,
scaleX: 0.5,
scaleY: 0.5
});
self.addChild(effect);
tween(effect, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
effect.destroy();
// Hide indicator after effect
// Do NOT hide the first hit indicator, keep it visible
}
});
};
return self;
});
// Enemy Car 2 (tougher, for higher levels)
var EnemyCar2 = Container.expand(function () {
var self = Container.call(this);
var car = self.attachAsset('enemyCar2', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = car.width;
self.height = car.height;
self.speed = 16;
self.type = 2;
self.hp = 2;
self.crashed = false;
// Damage indicator graphics (front, back, left, right)
var indicatorLength = car.width * 0.7;
var indicatorThickness = 22;
var indicatorColor = 0xffeb3b;
// Front indicator (top of car)
var frontIndicator = self.attachAsset('crashEffect', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -car.height / 2 + indicatorThickness / 2,
scaleX: indicatorLength / 220,
scaleY: indicatorThickness / 120,
alpha: 0.7
});
// Back indicator (bottom of car)
var backIndicator = self.attachAsset('crashEffect', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: car.height / 2 - indicatorThickness / 2,
scaleX: indicatorLength / 220,
scaleY: indicatorThickness / 120,
alpha: 0.7
});
// Left indicator (left side of car)
var leftIndicator = self.attachAsset('crashEffect', {
anchorX: 0.5,
anchorY: 0.5,
x: -car.width / 2 + indicatorThickness / 2,
y: 0,
scaleX: indicatorThickness / 120,
scaleY: indicatorLength / 220,
alpha: 0.7,
rotation: Math.PI / 2
});
// Right indicator (right side of car)
var rightIndicator = self.attachAsset('crashEffect', {
anchorX: 0.5,
anchorY: 0.5,
x: car.width / 2 - indicatorThickness / 2,
y: 0,
scaleX: indicatorThickness / 120,
scaleY: indicatorLength / 220,
alpha: 0.7,
rotation: Math.PI / 2
});
// Hide all indicators initially
frontIndicator.visible = false;
backIndicator.visible = false;
leftIndicator.visible = false;
rightIndicator.visible = false;
self.showCrash = function (hitPart) {
// Only set the indicator on the first hit
if (!self._firstCrashPart) {
self._firstCrashPart = hitPart;
}
// Hide all indicators
frontIndicator.visible = false;
backIndicator.visible = false;
leftIndicator.visible = false;
rightIndicator.visible = false;
// Show only the first hit part indicator
var showPart = self._firstCrashPart || hitPart;
if (showPart === "front") {
frontIndicator.visible = true;
} else if (showPart === "back") {
backIndicator.visible = true;
} else if (showPart === "left") {
leftIndicator.visible = true;
} else if (showPart === "right") {
rightIndicator.visible = true;
} else {
frontIndicator.visible = true; // fallback
}
var effect = LK.getAsset('crashEffect', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0,
alpha: 0.8,
scaleX: 0.5,
scaleY: 0.5
});
self.addChild(effect);
tween(effect, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
effect.destroy();
// Do NOT hide the first hit indicator, keep it visible
}
});
};
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.width = car.width;
self.height = car.height;
self.hp = 1; // Upgradable
self.invincible = false;
self.invincibleTimer = 0;
self.upgradeLevel = 0;
// Flash effect for invincibility
self.flashInvincible = function () {
self.invincible = true;
self.invincibleTimer = 60; // 1 second at 60fps
tween(self, {
alpha: 0.5
}, {
duration: 100,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(self, {
alpha: 1
}, {
duration: 100,
easing: tween.easeInOut
});
}
});
};
// Upgrade effect
self.showUpgrade = function () {
var effect = LK.getAsset('upgradeEffect', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0,
alpha: 0.7,
scaleX: 0.5,
scaleY: 0.5
});
self.addChild(effect);
tween(effect, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
effect.destroy();
}
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
// Arena scroll speed (background illusion)
// Player car: blue box
// Enemy car: red box
// Enemy car 2: green box (for future levels)
// Crash effect: yellow ellipse
// Upgrade effect: purple ellipse
// Sound: crash
// Sound: upgrade
// Music: background
// --- Desert background, cactuses, and dust cloud setup ---
// Add a desert background image (fills the arena)
// new visual for pit/hole
// Stack two desert backgrounds vertically for a longer scrolling road
var desertBg1 = LK.getAsset('desertBg', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732
});
var desertBg2 = LK.getAsset('desertBg', {
anchorX: 0,
anchorY: 0,
x: 0,
y: -2732,
width: 2048,
height: 2732
});
game.addChild(desertBg1);
game.addChild(desertBg2);
// Add more cactuses, only along the left and right edges (not top/bottom or middle)
var cactusPerSide = 8;
for (var i = 0; i < cactusPerSide; i++) {
// Left edge
var cactusL = LK.getAsset('cactus', {
anchorX: 0.5,
anchorY: 1,
scaleX: 0.8 + Math.random() * 0.4,
scaleY: 0.8 + Math.random() * 0.4
});
cactusL.x = 100 + cactusL.width / 2;
// Avoid top/bottom 100px, and avoid the very center (leave a gap in the middle)
var minY = 200;
var maxY = 2732 - 200;
var gapStart = 2732 / 2 - 350;
var gapEnd = 2732 / 2 + 350;
var yVal = minY + Math.random() * (maxY - minY);
// If in the center gap, push up or down
if (yVal > gapStart && yVal < gapEnd) {
if (Math.random() < 0.5) {
yVal = minY + Math.random() * (gapStart - minY);
} else {
yVal = gapEnd + Math.random() * (maxY - gapEnd);
}
}
cactusL.y = yVal;
game.addChild(cactusL);
// Right edge
var cactusR = LK.getAsset('cactus', {
anchorX: 0.5,
anchorY: 1,
scaleX: 0.8 + Math.random() * 0.4,
scaleY: 0.8 + Math.random() * 0.4
});
cactusR.x = 2048 - 100 - cactusR.width / 2;
// Use same y logic as left
var yValR = minY + Math.random() * (maxY - minY);
if (yValR > gapStart && yValR < gapEnd) {
if (Math.random() < 0.5) {
yValR = minY + Math.random() * (gapStart - minY);
} else {
yValR = gapEnd + Math.random() * (maxY - gapEnd);
}
}
cactusR.y = yValR;
game.addChild(cactusR);
}
// Dust clouds spawn from all screen edges and move in random directions
var dustClouds = [];
var dustCloudCount = 8;
for (var i = 0; i < dustCloudCount; i++) {
// Randomly pick a spawn edge: 0=top, 1=bottom, 2=left, 3=right
var edge = Math.floor(Math.random() * 4);
var x, y, dx, dy;
var margin = 100;
var speed = 2 + Math.random() * 2;
if (edge === 0) {
// top
x = margin + Math.random() * (2048 - 2 * margin);
y = -80;
dx = (Math.random() - 0.5) * 2;
dy = speed;
} else if (edge === 1) {
// bottom
x = margin + Math.random() * (2048 - 2 * margin);
y = 2732 + 80;
dx = (Math.random() - 0.5) * 2;
dy = -speed;
} else if (edge === 2) {
// left
x = -80;
y = margin + Math.random() * (2732 - 2 * margin);
dx = speed;
dy = (Math.random() - 0.5) * 2;
} else {
// right
x = 2048 + 80;
y = margin + Math.random() * (2732 - 2 * margin);
dx = -speed;
dy = (Math.random() - 0.5) * 2;
}
var dustCloud = LK.getAsset('dustCloud', {
anchorX: 0.5,
anchorY: 0.5,
x: x,
y: y,
scaleX: 2.2,
scaleY: 1.2,
alpha: 0.5
});
dustCloud._dx = dx;
dustCloud._dy = dy;
dustCloud._spawnEdge = edge;
game.addChild(dustCloud);
dustClouds.push(dustCloud);
}
var scrollSpeed = 10;
// Player
var player = new PlayerCar();
player.x = 2048 / 2;
player.y = 2732 - 500;
game.addChild(player);
// --- Heart (life) system ---
var maxHearts = 3;
var hearts = 0; // Start with 0
var heartIcons = [];
// Create all heart icons at once, hide them initially
for (var i = 0; i < maxHearts; i++) {
var heart = LK.getAsset('heart', {
x: 60 + i * 80,
y: 60,
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.25,
scaleY: 0.25
});
heart.visible = false;
game.addChild(heart);
heartIcons.push(heart);
}
function updateHeartsDisplay() {
for (var i = 0; i < maxHearts; i++) {
heartIcons[i].visible = i < hearts;
}
}
updateHeartsDisplay();
// Score
var score = 0;
var scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Level
var level = 1;
var levelTxt = new Text2('Lv.1', {
size: 70,
fill: "#fff"
});
levelTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(levelTxt);
levelTxt.y = 120;
// Upgrades
var upgradeTxt = new Text2('', {
size: 70,
fill: 0xFFD600
});
upgradeTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(upgradeTxt);
upgradeTxt.y = 200;
// Enemies
var enemies = [];
var enemySpawnTimer = 0;
var enemySpawnInterval = 60; // frames
// Dragging
var dragNode = null;
// Invincibility after crash
var invincibleFrames = 0;
// Arena bounds (leave 100px at top for menu)
var arenaLeft = 100;
var arenaRight = 2048 - 100;
var arenaTop = 100;
var arenaBottom = 2732 - 100;
// Music
LK.playMusic('arenaMusic');
// Helper: spawn enemy
function spawnEnemy() {
var enemy;
if (level < 3 || Math.random() < 0.7) {
enemy = new EnemyCar();
} else {
enemy = new EnemyCar2();
}
// Spawn from center and off-center lanes (not far left/right)
// Thorns and pits will not slide to the far left and right of the screen
var laneCenters = [arenaLeft + (arenaRight - arenaLeft) * 0.28,
// left off-center
arenaLeft + (arenaRight - arenaLeft) * 0.38,
// left of center
(arenaLeft + arenaRight) / 2,
// center
arenaLeft + (arenaRight - arenaLeft) * 0.62,
// right of center
arenaLeft + (arenaRight - arenaLeft) * 0.72 // right off-center
];
var lane = Math.floor(Math.random() * laneCenters.length);
enemy.x = laneCenters[lane];
enemy.y = -enemy.height / 2;
// Speed up with level
if (enemy.type === 1) {
enemy.speed = 12 + level * 1.5;
} else {
enemy.speed = 16 + level * 1.2;
}
enemies.push(enemy);
game.addChild(enemy);
}
// Helper: upgrade player
function upgradePlayer() {
player.upgradeLevel += 1;
player.hp += 1;
player.showUpgrade();
upgradeTxt.setText('Upgrade! HP: ' + player.hp);
LK.getSound('upgrade').play();
LK.setTimeout(function () {
upgradeTxt.setText('');
}, 1200);
}
// Handle move (dragging player)
function handleMove(x, y, obj) {
if (dragNode === player) {
// Clamp to arena
var px = x;
var py = y;
var halfW = player.width / 2;
var halfH = player.height / 2;
if (px < arenaLeft + halfW) px = arenaLeft + halfW;
if (px > arenaRight - halfW) px = arenaRight - halfW;
if (py < arenaTop + halfH) py = arenaTop + halfH;
if (py > arenaBottom - halfH) py = arenaBottom - halfH;
player.x = px;
player.y = py;
}
}
// Touch/drag events
game.down = function (x, y, obj) {
// Only start drag if touch is on player
var dx = x - player.x;
var dy = y - player.y;
if (dx * dx + dy * dy < player.width / 2 * (player.width / 2)) {
dragNode = player;
handleMove(x, y, obj);
}
};
game.move = function (x, y, obj) {
handleMove(x, y, obj);
};
game.up = function (x, y, obj) {
dragNode = null;
};
// Main update loop
game.update = function () {
// Scroll both desert backgrounds for a seamless, infinite road effect
desertBg1.y += scrollSpeed;
desertBg2.y += scrollSpeed;
// If a background moves off the bottom, move it to the top of the other
if (desertBg1.y >= 2732) {
desertBg1.y = desertBg2.y - 2732;
}
if (desertBg2.y >= 2732) {
desertBg2.y = desertBg1.y - 2732;
}
// Animate dust clouds for movement illusion (from all edges)
if (dustClouds && dustClouds.length) {
for (var d = 0; d < dustClouds.length; d++) {
var dc = dustClouds[d];
dc.x += dc._dx;
dc.y += dc._dy;
// If cloud is out of bounds, respawn from a random edge
var outOfBounds = dc.x < -200 || dc.x > 2048 + 200 || dc.y < -200 || dc.y > 2732 + 200;
if (outOfBounds) {
// Pick a new random edge and direction
var edge = Math.floor(Math.random() * 4);
var margin = 100;
var speed = 2 + Math.random() * 2;
if (edge === 0) {
// top
dc.x = margin + Math.random() * (2048 - 2 * margin);
dc.y = -80;
dc._dx = (Math.random() - 0.5) * 2;
dc._dy = speed;
} else if (edge === 1) {
// bottom
dc.x = margin + Math.random() * (2048 - 2 * margin);
dc.y = 2732 + 80;
dc._dx = (Math.random() - 0.5) * 2;
dc._dy = -speed;
} else if (edge === 2) {
// left
dc.x = -80;
dc.y = margin + Math.random() * (2732 - 2 * margin);
dc._dx = speed;
dc._dy = (Math.random() - 0.5) * 2;
} else {
// right
dc.x = 2048 + 80;
dc.y = margin + Math.random() * (2732 - 2 * margin);
dc._dx = -speed;
dc._dy = (Math.random() - 0.5) * 2;
}
}
}
}
// --- Pits: spawn, animate, and collision ---
// Pit setup: global arrays and spawn timer
if (typeof pits === "undefined") {
pits = [];
pitSpawnTimer = 0;
pitSpawnInterval = 90; // frames, can be tuned
// Pit lanes: center and off-center, not far left/right
pitLanes = [arenaLeft + (arenaRight - arenaLeft) * 0.28, arenaLeft + (arenaRight - arenaLeft) * 0.38, (arenaLeft + arenaRight) / 2, arenaLeft + (arenaRight - arenaLeft) * 0.62, arenaLeft + (arenaRight - arenaLeft) * 0.72];
}
// Animate pits
for (var p = pits.length - 1; p >= 0; p--) {
var pit = pits[p];
if (pit.lastY === undefined) pit.lastY = pit.y;
pit.y += scrollSpeed + 14 + level * 0.7; // pits move slightly faster than scroll
// Remove pit if off screen
if (pit.lastY <= 2732 + 200 && pit.y > 2732 + 200) {
pit.destroy();
pits.splice(p, 1);
continue;
}
pit.lastY = pit.y;
}
// Spawn pits
pitSpawnTimer++;
var pitInt = Math.max(50, pitSpawnInterval - level * 2);
if (pitSpawnTimer >= pitInt) {
// Only spawn in center and off-center lanes, not far left/right
var lane = Math.floor(Math.random() * pitLanes.length);
var pit = LK.getAsset('pitHole', {
anchorX: 0.5,
anchorY: 0.5,
x: pitLanes[lane],
y: -60,
scaleX: 1.2 + Math.random() * 0.3,
scaleY: 0.7 + Math.random() * 0.2,
alpha: 0.95
});
pit.width = 180 + Math.random() * 80;
pit.height = 80 + Math.random() * 30;
pits.push(pit);
game.addChild(pit);
pit.lastY = pit.y;
pit.lastWasIntersecting = false;
pit.isPit = true;
pit.isDeadly = true;
pit._type = "pit";
pit._alreadyHit = false;
pit._id = "pit_" + Date.now() + "_" + Math.floor(Math.random() * 10000);
pitSpawnTimer = 0;
}
// Pit collision with player (kill instantly)
for (var p = pits.length - 1; p >= 0; p--) {
var pit = pits[p];
if (pit._alreadyHit) continue;
var isIntersecting = player.intersects(pit);
if (pit.lastWasIntersecting === false && isIntersecting) {
pit._alreadyHit = true;
if (hearts > 0 && !player.invincible) {
// Use a heart, grant invincibility, do not die
hearts--;
if (hearts >= 0 && hearts < heartIcons.length) {
heartIcons[hearts].visible = false;
}
updateHeartsDisplay();
player.flashInvincible();
LK.effects.flashScreen(0x000000, 600);
} else {
LK.effects.flashScreen(0x000000, 1000);
LK.showGameOver();
return;
}
}
pit.lastWasIntersecting = isIntersecting;
}
// Arena scroll illusion: move all enemies down
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
enemy.y += enemy.speed + scrollSpeed;
// End game if enemy reaches the end of the screen (bottom)
if (enemy.lastY === undefined) {
enemy.lastY = enemy.y;
}
var screenEndY = 2732 + 100;
if (enemy.lastY <= screenEndY && enemy.y > screenEndY) {
// Lose a heart if enemy reaches the end
hearts--;
if (hearts >= 0 && hearts < heartIcons.length) {
heartIcons[hearts].visible = false;
}
updateHeartsDisplay();
LK.effects.flashScreen(0xff0000, 1000);
// Remove the enemy from the game
enemy.destroy();
enemies.splice(i, 1);
// Game over if no hearts left
if (hearts <= 0) {
LK.showGameOver();
return;
}
// Otherwise, continue the game
continue;
}
enemy.lastY = enemy.y;
}
// Spawn enemies
enemySpawnTimer++;
var spawnInt = Math.max(30, enemySpawnInterval - level * 2);
if (enemySpawnTimer >= spawnInt) {
spawnEnemy();
enemySpawnTimer = 0;
}
// Collision: player vs enemies
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
if (enemy.crashed) continue;
if (player.invincible) continue;
if (player.intersects(enemy)) {
// Determine which side of the enemy was hit by the player
var dx = player.x - enemy.x;
var dy = player.y - enemy.y;
var absDx = Math.abs(dx);
var absDy = Math.abs(dy);
var hitPart = "front";
if (absDx > absDy) {
// Side hit
if (dx < 0) {
hitPart = "left";
} else {
hitPart = "right";
}
} else {
// Vertical hit
if (dy < 0) {
hitPart = "front";
} else {
hitPart = "back";
}
}
// Only allow crash and destroy if hitPart is "back"
if (hitPart === "back") {
// Crash!
// For EnemyCar2, require 2 hits to destroy
if (enemy.type === 2) {
if (typeof enemy._hitCount === "undefined") enemy._hitCount = 0;
enemy._hitCount++;
enemy.showCrash(hitPart);
LK.getSound('crash').play();
// Only destroy and score after 2 hits
if (enemy._hitCount >= 2) {
enemy.crashed = true;
score += 10;
LK.setScore(score);
scoreTxt.setText(score);
// Remove enemy after effect
LK.setTimeout(function (e, idx) {
return function () {
if (enemies[idx] === e) {
e.destroy();
enemies.splice(idx, 1);
}
};
}(enemy, i), 400);
}
// Player does not take damage, only enemy is destroyed
} else {
enemy.crashed = true;
enemy.showCrash(hitPart);
LK.getSound('crash').play();
score += 10;
LK.setScore(score);
scoreTxt.setText(score);
// Remove enemy after effect
LK.setTimeout(function (e, idx) {
return function () {
if (enemies[idx] === e) {
e.destroy();
enemies.splice(idx, 1);
}
};
}(enemy, i), 400);
// Player does not take damage, only enemy is destroyed
}
}
}
}
// --- Blur effect when player collides with dust cloud ---
if (typeof game._blurActive === "undefined") {
game._blurActive = false;
game._blurLastIntersecting = false;
}
// Check intersection with any dust cloud
var dustCloudIntersecting = false;
if (dustClouds && dustClouds.length) {
for (var d = 0; d < dustClouds.length; d++) {
if (player.intersects(dustClouds[d])) {
dustCloudIntersecting = true;
break;
}
}
}
if (!game._blurLastIntersecting && dustCloudIntersecting && !game._blurActive) {
game._blurActive = true;
// Darken the screen more when colliding with a dust cloud
LK.effects.flashScreen(0x000000, 1200, {
alpha: 0.55
}); // More darkness, longer duration
// Optionally, you can still use blur if you want both effects:
// if (LK.effects && typeof LK.effects.blurScreen === "function") {
// LK.effects.blurScreen(8, 2000);
// }
LK.setTimeout(function () {
game._blurActive = false;
}, 1200);
}
game._blurLastIntersecting = dustCloudIntersecting;
// Invincibility timer
if (player.invincible) {
player.invincibleTimer--;
if (player.invincibleTimer <= 0) {
player.invincible = false;
player.alpha = 1;
}
}
// Level up every 100 points
var newLevel = Math.floor(score / 100) + 1;
if (newLevel > level) {
level = newLevel;
levelTxt.setText('Lv.' + level);
// Every 2 levels, upgrade player
if (level % 2 === 0) {
upgradePlayer();
}
// Every 5 levels, gain a heart (up to 3), starting from 0
var newHearts = Math.min(Math.floor(level / 5), maxHearts);
if (newHearts > hearts) {
hearts = newHearts;
updateHeartsDisplay();
}
}
};
A Mad Max style car will be old and will have a metal spiked plate on the front and will have a top view of the car
It will be in the style of a Mad Max truck and will have a top view. There will be a thorn plate in the front and the back will be smoother.
cactus 2d. In-Game asset. 2d. High contrast. No shadows
The asphalt road will be a little wider
dust cloud. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
It will be a small car in the style of Madmax. It will have a top view. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
middle lava
The hearts will be in a slightly old Mad Max style look and half will be inside the leather case. In-Game asset. 2d. High contrast. No shadows
scatter light. In-Game asset. 2d. High contrast. No shadows