User prompt
araçalar hangi yöne gidiyorsa o tarafa önleri baksın
User prompt
araçlar taşıtlar iç içe girmesin
Code edit (1 edits merged)
Please save this source code
User prompt
Crossy Critters: Day & Night Dash
Initial prompt
Create a 2D top-down road crossing game in portrait orientation with the following specifications: 🎮 GAME STRUCTURE: - The player must cross 20 consecutive road sections (levels). - Each section includes either traffic-filled roads or safe grass areas. - The level ends when the player reaches the 20th row. 🚗 VEHICLE TRAFFIC: - Vehicles spawn endlessly from left or right sides on road lanes. - Vehicle types: - Small cars - Pickup trucks - Trucks - TIR (semi-trailers) - Motorcycles - Traffic flows continuously and never stops. - Vehicles **must never overlap** — a minimum distance is maintained between vehicles on each lane. - Each lane has its own traffic speed and spacing logic. - Traffic intensity and speed increase slightly with each level. 👤 PLAYER FEATURES: - The player controls an animal character (chicken, dog, or cat). - Movement is grid-based: one tile per move (tap, swipe, or arrow keys). - Coins appear randomly on the map and can be collected. - Collected coins are used to unlock new characters in a shop. 📱 UI & VISUALS: - Portrait mode (720x1280). - Bright, cartoon-style or pixel-art 2D graphics. - Score counter (number of rows crossed) and coin counter always visible. - Game Over screen on collision. - Victory screen after 20th row. 🌗 DAY/NIGHT CYCLE: - The game includes a smooth day/night cycle during gameplay. - The background gradually changes from day to night and back. - Visual changes include: - Sky color transition - Soft lighting effects - Optional light glows from vehicles at night - The cycle runs continuously and does not pause. 🔊 SOUND & FEEDBACK: - Vehicles have movement and engine sounds. - Coin collection triggers sound effects. - Day/night cycle may include ambient audio changes (e.g. crickets at night). - Smooth animations for character movement, death, and level transitions. The game should be mobile-optimized, visually dynamic, and casual-friendly while offering fun progression with an engaging atmosphere through lighting and time-of-day changes.
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Animal class (player) var Animal = Container.expand(function () { var self = Container.call(this); // Default to chicken self.type = 'chicken'; self.sprite = self.attachAsset(self.type, { anchorX: 0.5, anchorY: 0.5 }); // Set a more accurate hitbox for the animal (chicken, cat, dog) function setAnimalHitArea(type) { var w = self.sprite.width; var h = self.sprite.height; // Shrink hitbox for more fair collisions if (type === 'chicken') { // chicken: 60% width, 60% height self.hitArea = new Rectangle(-w * 0.3, -h * 0.3, w * 0.6, h * 0.6); } else if (type === 'cat' || type === 'dog') { // cat/dog: 62% width, 62% height self.hitArea = new Rectangle(-w * 0.31, -h * 0.31, w * 0.62, h * 0.62); } else { // fallback: 60% width, 60% height self.hitArea = new Rectangle(-w * 0.3, -h * 0.3, w * 0.6, h * 0.6); } } setAnimalHitArea(self.type); self.setType = function (type) { if (self.sprite) self.sprite.destroy(); self.type = type; self.sprite = self.attachAsset(type, { anchorX: 0.5, anchorY: 0.5 }); setAnimalHitArea(type); }; // Animate hop self.hop = function (targetX, targetY, _onFinish) { tween(self, { x: targetX, y: targetY, scaleY: 1.2 }, { duration: 80, easing: tween.cubicOut, onFinish: function onFinish() { tween(self, { scaleY: 1 }, { duration: 60, easing: tween.cubicIn, onFinish: _onFinish }); } }); }; return self; }); // Coin class var Coin = Container.expand(function () { var self = Container.call(this); self.sprite = self.attachAsset('coin', { anchorX: 0.5, anchorY: 0.5 }); // Animate spin self.update = function () { self.sprite.rotation += 0.15; }; return self; }); // Vehicle class var Vehicle = Container.expand(function () { var self = Container.call(this); // type: 'car1', 'car2', 'truck' self.type = 'car1'; self.sprite = self.attachAsset(self.type, { anchorX: 0.5, anchorY: 0.5 }); // Headlights for night effect self.headlights = null; function addHeadlights() { // Remove old headlights if any if (self.headlights) { self.headlights.destroy(); self.headlights = null; } // Only add headlights for cars and truck if (self.type === 'car1' || self.type === 'car2' || self.type === 'truck') { // Attach headlight asset, anchor at left for right-to-left, right for left-to-right self.headlights = self.attachAsset('car_headlight', { anchorX: self.dir === 1 ? 0 : 1, anchorY: 0.5 }); // Remove hitbox from headlights so they do not participate in collision self.headlights.hitArea = null; // Place headlights further inside the car for a more realistic look var headlightInset = self.sprite.width * 0.22; // Move headlights inside by 22% of car width var offset = (self.sprite.width / 2 - headlightInset) * (self.dir === 1 ? 1 : -1); self.headlights.x = offset; self.headlights.y = 0; // Scale headlights to match car width var scale = self.sprite.width / self.headlights.width; self.headlights.scaleX = scale * (self.dir === 1 ? 1 : -1); self.headlights.scaleY = self.sprite.height / self.headlights.height; // Start hidden, will be shown at night self.headlights.alpha = 0; } } // Set a much smaller hitbox for the vehicle (extremely forgiving) function setVehicleHitArea(type) { var w = self.sprite.width; var h = self.sprite.height; // Make hitbox ultra tiny (almost no damage at all) if (type === 'car1') { // car1: 2% width, 2% height self.hitArea = new Rectangle(-w * 0.01, -h * 0.01, w * 0.02, h * 0.02); } else if (type === 'car2') { // car2: 2% width, 2% height self.hitArea = new Rectangle(-w * 0.01, -h * 0.01, w * 0.02, h * 0.02); } else if (type === 'truck') { // truck: 2.5% width, 2% height self.hitArea = new Rectangle(-w * 0.0125, -h * 0.01, w * 0.025, h * 0.02); } else { // fallback: 2% width, 2% height self.hitArea = new Rectangle(-w * 0.01, -h * 0.01, w * 0.02, h * 0.02); } } setVehicleHitArea(self.type); self.setType = function (type) { if (self.sprite) self.sprite.destroy(); self.type = type; self.sprite = self.attachAsset(type, { anchorX: 0.5, anchorY: 0.5 }); setVehicleHitArea(type); // Set sprite orientation to always face movement direction, regardless of asset's default orientation if (type === 'car2') { // Car2 asset default faces right, so: if (self.dir === 1) { // Left-to-right: no flip needed self.sprite.scaleX = Math.abs(self.sprite.scaleX); self.sprite.rotation = 0; } else { // Right-to-left: flip horizontally self.sprite.scaleX = -Math.abs(self.sprite.scaleX); self.sprite.rotation = 0; } } else { // Other vehicles: keep existing logic if (self.dir === 1) { // Always face right for left-to-right movement self.sprite.scaleX = Math.abs(self.sprite.scaleX); } else { // Always face left for right-to-left movement self.sprite.scaleX = -Math.abs(self.sprite.scaleX); } } // Add or update headlights addHeadlights(); }; // Lane index (0-19) self.lane = 0; // Direction: 1 = left to right, -1 = right to left self.dir = 1; // Speed in px per frame self.speed = 6; self.update = function () { if (typeof self.lastX === "undefined") self.lastX = self.x; self.x += self.speed * self.dir; // Ensure sprite faces the correct direction, always facing movement direction if (self.type === 'car2') { if (self.dir === 1) { self.sprite.scaleX = Math.abs(self.sprite.scaleX); self.sprite.rotation = 0; } else { self.sprite.scaleX = -Math.abs(self.sprite.scaleX); self.sprite.rotation = 0; } } else { if (self.dir === 1) { self.sprite.scaleX = Math.abs(self.sprite.scaleX); self.sprite.rotation = 0; } else { self.sprite.scaleX = -Math.abs(self.sprite.scaleX); self.sprite.rotation = 0; } } self.lastX = self.x; // Update headlights orientation, scale, and visibility for night if (self.headlights) { // Update position and scale in case car was resized or direction changed var headlightInset = self.sprite.width * 0.22; // Move headlights inside by 22% of car width var offset = (self.sprite.width / 2 - headlightInset) * (self.dir === 1 ? 1 : -1); self.headlights.x = offset; self.headlights.y = 0; var scale = self.sprite.width / self.headlights.width; self.headlights.scaleX = scale * (self.dir === 1 ? 1 : -1); self.headlights.scaleY = self.sprite.height / self.headlights.height; // Show headlights only at night (dayNight.t >= 0.5), with semi-transparency self.headlights.alpha = typeof dayNight !== "undefined" && dayNight.t >= 0.5 ? 0.45 : 0; } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x87ceeb // Day sky blue }); /**** * Game Code ****/ // Music // Sounds // Coin // Vehicles // Lane types // Animal characters // --- Game constants --- var NUM_LANES = 20; var LANE_HEIGHT = 120; var LANE_TYPES = []; // 'grass' or 'road' var LANE_Y = []; // y positions of each lane (0 = bottom) var LANE_CONFIG = []; // {type, y, speed, dir, spacing, vehicleTypes} var LANE_TOP = 2732 - NUM_LANES * LANE_HEIGHT; // Top y of first lane // --- Day/Night cycle --- var dayNight = { t: 0, // 0 = day, 1 = night speed: 0.00025, // How fast day/night cycles (per tick) colorDay: 0x87ceeb, colorNight: 0x23234a }; // --- Game state --- var player = null; var vehicles = []; var coins = []; var score = 0; var coinsCollected = 0; var isMoving = false; var dragStart = null; var dragTarget = null; var lastLane = 0; var gameOver = false; // --- Level state --- var currentLevel = 1; var maxLevel = 5; var flag = null; // --- UI --- var scoreTxt = new Text2('0', { size: 100, fill: "#fff" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); var coinTxt = new Text2('0', { size: 80, fill: 0xFFD700 }); coinTxt.anchor.set(0.5, 0); LK.gui.topRight.addChild(coinTxt); // --- Lane setup --- function setupLanes() { LANE_TYPES.length = 0; LANE_Y.length = 0; LANE_CONFIG.length = 0; for (var i = 0; i < NUM_LANES; i++) { // Alternate: grass every 3rd lane, rest are road var type = i % 3 === 0 ? 'grass' : 'road'; LANE_TYPES.push(type); var y = 2732 - (i + 0.5) * LANE_HEIGHT; LANE_Y.push(y); // Lane config if (type === 'road') { // Assign direction: even lanes go left-to-right, odd lanes go right-to-left (never mixed) var dir = i % 2 === 0 ? 1 : -1; var speed = 4 + i % 4 * 1.5; // 4, 5.5, 7, 8.5 var spacing = 350 + i % 3 * 80; // 350, 430, 510 var vehicleTypes = i % 4 === 0 ? ['truck'] : ['car1', 'car2']; LANE_CONFIG.push({ type: type, y: y, speed: speed, dir: dir, spacing: spacing, vehicleTypes: vehicleTypes }); } else { LANE_CONFIG.push({ type: type, y: y }); } } } // --- Draw lanes --- function drawLanes() { for (var i = 0; i < NUM_LANES; i++) { var laneType = LANE_TYPES[i]; var y = LANE_Y[i]; var lane = LK.getAsset(laneType, { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: y }); game.addChild(lane); // Add direction arrows for road lanes // (Removed small car images used as arrows) } } // --- Spawn player --- function spawnPlayer() { player = new Animal(); player.setType('chicken'); // Start at bottom grass lane, center player.x = 2048 / 2; player.y = LANE_Y[0]; lastLane = 0; game.addChild(player); } // --- Spawn flag at the end of the level --- function spawnFlag() { if (flag) { flag.destroy(); flag = null; } // Place flag at the topmost grass lane var topLaneIdx = NUM_LANES - 1; var flagY = LANE_Y[topLaneIdx]; flag = LK.getAsset('flag', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: flagY }); game.addChild(flag); } // --- Spawn vehicles --- function spawnVehicle(laneIdx) { var config = LANE_CONFIG[laneIdx]; if (!config || config.type !== 'road') return; // Check for minimum distance to last vehicle in this lane var lastVehicle = null; var lastDist = 99999; for (var i = 0; i < vehicles.length; i++) { if (vehicles[i].lane === laneIdx) { var dist = Math.abs(vehicles[i].x - 2048 / 2); if (dist < lastDist) { lastDist = dist; lastVehicle = vehicles[i]; } } } // Only spawn if no vehicle or far enough from last vehicle in this lane if (!lastVehicle || lastDist > config.spacing) { var v = new Vehicle(); // Random vehicle type var vtype = config.vehicleTypes[Math.floor(Math.random() * config.vehicleTypes.length)]; v.setType(vtype); v.lane = laneIdx; v.dir = config.dir; v.speed = config.speed + Math.random() * 1.5; // Y position v.y = config.y; // X position: offscreen left or right if (v.dir === 1) { v.x = -v.sprite.width / 2 - 20; // Car2: always face right for left-to-right movement if (v.type === 'car2') { v.sprite.scaleX = Math.abs(v.sprite.scaleX); v.sprite.rotation = 0; } else { // Always face right for left-to-right movement v.sprite.scaleX = Math.abs(v.sprite.scaleX); v.sprite.rotation = 0; } } else { v.x = 2048 + v.sprite.width / 2 + 20; // Car2: always face left for right-to-left movement if (v.type === 'car2') { v.sprite.scaleX = -Math.abs(v.sprite.scaleX); v.sprite.rotation = 0; } else { // Always face left for right-to-left movement v.sprite.scaleX = -Math.abs(v.sprite.scaleX); v.sprite.rotation = 0; } } // Update headlights after direction and type are set if (typeof v.addHeadlights === "function") v.addHeadlights && v.addHeadlights(); vehicles.push(v); game.addChild(v); } } // --- Spawn coin --- function spawnCoin(laneIdx) { var config = LANE_CONFIG[laneIdx]; if (!config || config.type !== 'grass') return; // Only one coin per grass lane at a time for (var i = 0; i < coins.length; i++) { if (Math.abs(coins[i].y - config.y) < 10) return; } var c = new Coin(); c.x = 200 + Math.random() * (2048 - 400); c.y = config.y; coins.push(c); game.addChild(c); } // --- Remove vehicle --- function removeVehicle(idx) { vehicles[idx].destroy(); vehicles.splice(idx, 1); } // --- Remove coin --- function removeCoin(idx) { coins[idx].destroy(); coins.splice(idx, 1); } // --- Update UI --- function updateUI() { scoreTxt.setText(score); coinTxt.setText(coinsCollected); } // --- Sun and Moon sprites --- var sunSprite = null; var moonSprite = null; var skySpriteLayer = new Container(); game.addChild(skySpriteLayer); // --- Day/Night cycle update --- function updateDayNight() { dayNight.t += dayNight.speed; if (dayNight.t > 1) dayNight.t = 0; // Interpolate color var t = dayNight.t; var c1 = dayNight.colorDay, c2 = dayNight.colorNight; var r = (c1 >> 16) * (1 - t) + (c2 >> 16) * t & 0xff; var g = (c1 >> 8 & 0xff) * (1 - t) + (c2 >> 8 & 0xff) * t & 0xff; var b = (c1 & 0xff) * (1 - t) + (c2 & 0xff) * t & 0xff; var color = r << 16 | g << 8 | b; game.setBackgroundColor(color); // Sun and Moon movement // Sun: visible during t in [0,0.5), Moon: visible during t in [0.5,1) // Both move in an arc from left to right var skyY = 350; var skyRadius = 800; var centerX = 2048 / 2; var centerY = skyY + 200; // Sun if (!sunSprite) { sunSprite = LK.getAsset('sun', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 }); skySpriteLayer.addChild(sunSprite); } // Moon if (!moonSprite) { moonSprite = LK.getAsset('ay', { anchorX: 0.5, anchorY: 0.5, x: 0, y: 0 }); skySpriteLayer.addChild(moonSprite); } // Sun position: t in [0,0.5) → left to right, high arc var sunT = t < 0.5 ? t / 0.5 : 0; var sunAngle = Math.PI * (1 - sunT); // from left (π) to right (0) sunSprite.x = centerX + skyRadius * Math.cos(sunAngle); sunSprite.y = centerY - skyRadius * Math.sin(sunAngle); sunSprite.alpha = t < 0.5 ? 1 : 0; // Moon position: t in [0.5,1) → left to right, high arc var moonT = t >= 0.5 ? (t - 0.5) / 0.5 : 0; var moonAngle = Math.PI * (1 - moonT); // from left (π) to right (0) moonSprite.x = centerX + skyRadius * Math.cos(moonAngle); moonSprite.y = centerY - skyRadius * Math.sin(moonAngle); moonSprite.alpha = t >= 0.5 ? 1 : 0; } // --- Collision detection --- function checkCollisions() { // Vehicles for (var i = 0; i < vehicles.length; i++) { if (player.intersects(vehicles[i])) { // Crash! LK.effects.flashScreen(0xff0000, 800); LK.getSound('crash').play(); gameOver = true; LK.showGameOver(); return; } } // Coins for (var i = coins.length - 1; i >= 0; i--) { if (player.intersects(coins[i])) { coinsCollected++; updateUI(); LK.getSound('coin').play(); removeCoin(i); } } } // --- Move player (grid) --- function tryMove(dx, dy) { if (isMoving || gameOver) return; var newLane = lastLane + dy; if (newLane < 0 || newLane >= NUM_LANES) return; var newX = player.x + dx * 220; if (newX < 80 || newX > 2048 - 80) return; var newY = LANE_Y[newLane]; isMoving = true; player.hop(newX, newY, function () { isMoving = false; lastLane = newLane; // Score: advance only if moving up if (dy > 0) { score++; updateUI(); if (score >= NUM_LANES - 1) { // Win! LK.effects.flashScreen(0x00ffcc, 1000); LK.showYouWin(); gameOver = true; } } }); LK.getSound('hop').play(); } // --- Touch controls (swipe) --- var touchStart = null; game.down = function (x, y, obj) { if (gameOver) return; touchStart = { x: x, y: y }; }; game.up = function (x, y, obj) { if (gameOver || !touchStart) return; var dx = x - touchStart.x; var dy = y - touchStart.y; var absX = Math.abs(dx), absY = Math.abs(dy); if (absX < 40 && absY < 40) { // Tap: move up tryMove(0, 1); } else if (absY > absX) { if (dy < -40) tryMove(0, -1); // Down else if (dy > 40) tryMove(0, 1); // Up } else { if (dx < -40) tryMove(-1, 0); // Left else if (dx > 40) tryMove(1, 0); // Right } touchStart = null; }; // --- Main update loop --- game.update = function () { if (gameOver) return; // Day/Night updateDayNight(); // Vehicles for (var i = vehicles.length - 1; i >= 0; i--) { vehicles[i].update(); // Remove if offscreen if (vehicles[i].dir === 1 && vehicles[i].x > 2048 + vehicles[i].sprite.width / 2 + 40 || vehicles[i].dir === -1 && vehicles[i].x < -vehicles[i].sprite.width / 2 - 40) { removeVehicle(i); } } // Coins for (var i = 0; i < coins.length; i++) { coins[i].update(); } // Spawn vehicles for (var i = 0; i < NUM_LANES; i++) { var config = LANE_CONFIG[i]; if (!config || config.type !== 'road') continue; // Try to spawn a vehicle with a small probability if (Math.random() < 0.025) { // ~1 every 40 frames spawnVehicle(i); } } // Spawn coins for (var i = 0; i < NUM_LANES; i++) { var config = LANE_CONFIG[i]; if (!config || config.type !== 'grass') continue; // 1% chance per frame if (Math.random() < 0.01) { spawnCoin(i); } } // Remove coins if offscreen (shouldn't happen, but safety) for (var i = coins.length - 1; i >= 0; i--) { if (coins[i].y < LANE_TOP - 100 || coins[i].y > 2732 + 100) { removeCoin(i); } } // Collisions checkCollisions(); // Check for flag collision and level progression if (flag && player.intersects(flag)) { // Advance to next level if possible if (currentLevel < maxLevel) { currentLevel++; // Increase difficulty: more/faster vehicles, less grass, etc. NUM_LANES = 20 + currentLevel * 2; LANE_HEIGHT = Math.max(80, 120 - currentLevel * 5); // Remove all game objects for (var i = vehicles.length - 1; i >= 0; i--) removeVehicle(i); for (var i = coins.length - 1; i >= 0; i--) removeCoin(i); if (player) { player.destroy(); player = null; } if (flag) { flag.destroy(); flag = null; } // Re-setup lanes and redraw setupLanes(); drawLanes(); spawnPlayer(); spawnFlag(); updateUI(); gameOver = false; } else { // Last level: show win LK.effects.flashScreen(0x00ffcc, 1000); LK.showYouWin(); gameOver = true; } } }; // --- Start Menu Overlay --- var startMenuOverlay = new Container(); var startMenuBg = LK.getAsset('StartMenu', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, scaleX: 4, scaleY: 4 }); startMenuOverlay.addChild(startMenuBg); // Play button (image) var playBtn = LK.getAsset('start', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 + 250, scaleX: 2.2, scaleY: 2.2 }); startMenuOverlay.addChild(playBtn); // Options button (image) var optionsBtn = LK.getAsset('options', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 + 450, scaleX: 2.2, scaleY: 2.2 }); startMenuOverlay.addChild(optionsBtn); // Exit button (image) var exitBtn = LK.getAsset('exit', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 + 600, scaleX: 2.2, scaleY: 2.2 }); startMenuOverlay.addChild(exitBtn); var startMenuActive = true; game.addChild(startMenuOverlay); // Helper for button hit detection (works for both Text2 and image buttons) function isInsideBtn(btn, x, y) { // Use anchorX/anchorY for images, anchor for Text2 var anchorX = typeof btn.anchorX === "number" ? btn.anchorX : btn.anchor && btn.anchor.x ? btn.anchor.x : 0.5; var anchorY = typeof btn.anchorY === "number" ? btn.anchorY : btn.anchor && btn.anchor.y ? btn.anchor.y : 0.5; var bx = btn.x - btn.width * anchorX; var by = btn.y - btn.height * anchorY; return x >= bx && x <= bx + btn.width && y >= by && y <= by + btn.height; } // Block input/gameplay until play is pressed function startGame() { if (!startMenuActive) return; startMenuActive = false; startMenuOverlay.visible = false; // Now start the game setupLanes(); drawLanes(); spawnPlayer(); spawnFlag(); updateUI(); LK.playMusic('bgmusic', { fade: { start: 0, end: 0.7, duration: 1200 } }); } // --- Options Overlay --- var optionsOverlay = new Container(); optionsOverlay.visible = false; // Background for options var optionsBg = LK.getAsset('StartMenu', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, scaleX: 3.5, scaleY: 3.5 }); optionsOverlay.addChild(optionsBg); // Title var optionsTitle = new Text2("OPTIONS", { size: 140, fill: "#fff" }); optionsTitle.anchor.set(0.5, 0.5); optionsTitle.x = 2048 / 2; optionsTitle.y = 2732 / 2 - 350; optionsOverlay.addChild(optionsTitle); // --- Sound Volume Controls --- var soundLabel = new Text2("Sound Volume", { size: 90, fill: "#fff" }); soundLabel.anchor.set(0.5, 0.5); soundLabel.x = 2048 / 2; soundLabel.y = 2732 / 2 - 150; optionsOverlay.addChild(soundLabel); var soundDownBtn = new Text2("-", { size: 120, fill: "#fff" }); soundDownBtn.anchor.set(0.5, 0.5); soundDownBtn.x = 2048 / 2 - 200; soundDownBtn.y = 2732 / 2 - 50; optionsOverlay.addChild(soundDownBtn); var soundUpBtn = new Text2("+", { size: 120, fill: "#fff" }); soundUpBtn.anchor.set(0.5, 0.5); soundUpBtn.x = 2048 / 2 + 200; soundUpBtn.y = 2732 / 2 - 50; optionsOverlay.addChild(soundUpBtn); var soundVolTxt = new Text2("100%", { size: 90, fill: "#fff" }); soundVolTxt.anchor.set(0.5, 0.5); soundVolTxt.x = 2048 / 2; soundVolTxt.y = 2732 / 2 - 50; optionsOverlay.addChild(soundVolTxt); // --- Music Controls --- var musicLabel = new Text2("Music", { size: 90, fill: "#fff" }); musicLabel.anchor.set(0.5, 0.5); musicLabel.x = 2048 / 2; musicLabel.y = 2732 / 2 + 80; optionsOverlay.addChild(musicLabel); var musicToggleBtn = new Text2("Mute", { size: 100, fill: "#fff" }); musicToggleBtn.anchor.set(0.5, 0.5); musicToggleBtn.x = 2048 / 2; musicToggleBtn.y = 2732 / 2 + 180; optionsOverlay.addChild(musicToggleBtn); // --- Close Button --- var optionsCloseBtn = new Text2("CLOSE", { size: 100, fill: "#fff" }); optionsCloseBtn.anchor.set(0.5, 0.5); optionsCloseBtn.x = 2048 / 2; optionsCloseBtn.y = 2732 / 2 + 350; optionsOverlay.addChild(optionsCloseBtn); game.addChild(optionsOverlay); // --- Sound/Music State --- var soundVolume = 1.0; // 0.0 - 1.0 var musicMuted = false; // Helper to update UI and apply sound/music settings function updateOptionsUI() { soundVolTxt.setText(Math.round(soundVolume * 100) + "%"); // Set all sound volumes LK.getSound('coin').volume = soundVolume; LK.getSound('crash').volume = soundVolume; LK.getSound('hop').volume = soundVolume; // Set music volume or mute if (musicMuted) { LK.setMusicVolume && LK.setMusicVolume(0); musicToggleBtn.setText("Unmute"); } else { LK.setMusicVolume && LK.setMusicVolume(soundVolume); musicToggleBtn.setText("Mute"); } } // --- Options Button Handlers --- // Show options overlay function showOptions() { optionsOverlay.visible = true; startMenuOverlay.visible = false; updateOptionsUI(); } // Hide options overlay function closeOptions() { optionsOverlay.visible = false; if (startMenuActive) startMenuOverlay.visible = true; } // Touch handler for options overlay optionsOverlay.down = function (x, y, obj) { if (isInsideBtn(soundDownBtn, x, y)) { soundVolume = Math.max(0, soundVolume - 0.1); updateOptionsUI(); return; } if (isInsideBtn(soundUpBtn, x, y)) { soundVolume = Math.min(1, soundVolume + 0.1); updateOptionsUI(); return; } if (isInsideBtn(musicToggleBtn, x, y)) { musicMuted = !musicMuted; updateOptionsUI(); return; } if (isInsideBtn(optionsCloseBtn, x, y)) { closeOptions(); return; } }; // Forward touch events to options overlay if visible var origGameDown = game.down; game.down = function (x, y, obj) { if (optionsOverlay.visible) { optionsOverlay.down(x, y, obj); return; } origGameDown(x, y, obj); }; function exitGame() { LK.effects.flashScreen(0x222222, 400); } // Touch handler for menu game.down = function (x, y, obj) { if (startMenuActive) { if (isInsideBtn(playBtn, x, y)) { startGame(); return; } else if (isInsideBtn(optionsBtn, x, y)) { showOptions(); return; } else if (isInsideBtn(exitBtn, x, y)) { exitGame(); return; } // Ignore touches outside buttons while menu is active return; } if (gameOver) return; touchStart = { x: x, y: y }; }; game.up = function (x, y, obj) { if (startMenuActive) return; if (gameOver || !touchStart) return; var dx = x - touchStart.x; var dy = y - touchStart.y; var absX = Math.abs(dx), absY = Math.abs(dy); if (absX < 40 && absY < 40) { // Tap: move up tryMove(0, 1); } else if (absY > absX) { if (dy < -40) tryMove(0, -1); // Down else if (dy > 40) tryMove(0, 1); // Up } else { if (dx < -40) tryMove(-1, 0); // Left else if (dx > 40) tryMove(1, 0); // Right } touchStart = null; };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Animal class (player)
var Animal = Container.expand(function () {
var self = Container.call(this);
// Default to chicken
self.type = 'chicken';
self.sprite = self.attachAsset(self.type, {
anchorX: 0.5,
anchorY: 0.5
});
// Set a more accurate hitbox for the animal (chicken, cat, dog)
function setAnimalHitArea(type) {
var w = self.sprite.width;
var h = self.sprite.height;
// Shrink hitbox for more fair collisions
if (type === 'chicken') {
// chicken: 60% width, 60% height
self.hitArea = new Rectangle(-w * 0.3, -h * 0.3, w * 0.6, h * 0.6);
} else if (type === 'cat' || type === 'dog') {
// cat/dog: 62% width, 62% height
self.hitArea = new Rectangle(-w * 0.31, -h * 0.31, w * 0.62, h * 0.62);
} else {
// fallback: 60% width, 60% height
self.hitArea = new Rectangle(-w * 0.3, -h * 0.3, w * 0.6, h * 0.6);
}
}
setAnimalHitArea(self.type);
self.setType = function (type) {
if (self.sprite) self.sprite.destroy();
self.type = type;
self.sprite = self.attachAsset(type, {
anchorX: 0.5,
anchorY: 0.5
});
setAnimalHitArea(type);
};
// Animate hop
self.hop = function (targetX, targetY, _onFinish) {
tween(self, {
x: targetX,
y: targetY,
scaleY: 1.2
}, {
duration: 80,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(self, {
scaleY: 1
}, {
duration: 60,
easing: tween.cubicIn,
onFinish: _onFinish
});
}
});
};
return self;
});
// Coin class
var Coin = Container.expand(function () {
var self = Container.call(this);
self.sprite = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5
});
// Animate spin
self.update = function () {
self.sprite.rotation += 0.15;
};
return self;
});
// Vehicle class
var Vehicle = Container.expand(function () {
var self = Container.call(this);
// type: 'car1', 'car2', 'truck'
self.type = 'car1';
self.sprite = self.attachAsset(self.type, {
anchorX: 0.5,
anchorY: 0.5
});
// Headlights for night effect
self.headlights = null;
function addHeadlights() {
// Remove old headlights if any
if (self.headlights) {
self.headlights.destroy();
self.headlights = null;
}
// Only add headlights for cars and truck
if (self.type === 'car1' || self.type === 'car2' || self.type === 'truck') {
// Attach headlight asset, anchor at left for right-to-left, right for left-to-right
self.headlights = self.attachAsset('car_headlight', {
anchorX: self.dir === 1 ? 0 : 1,
anchorY: 0.5
});
// Remove hitbox from headlights so they do not participate in collision
self.headlights.hitArea = null;
// Place headlights further inside the car for a more realistic look
var headlightInset = self.sprite.width * 0.22; // Move headlights inside by 22% of car width
var offset = (self.sprite.width / 2 - headlightInset) * (self.dir === 1 ? 1 : -1);
self.headlights.x = offset;
self.headlights.y = 0;
// Scale headlights to match car width
var scale = self.sprite.width / self.headlights.width;
self.headlights.scaleX = scale * (self.dir === 1 ? 1 : -1);
self.headlights.scaleY = self.sprite.height / self.headlights.height;
// Start hidden, will be shown at night
self.headlights.alpha = 0;
}
}
// Set a much smaller hitbox for the vehicle (extremely forgiving)
function setVehicleHitArea(type) {
var w = self.sprite.width;
var h = self.sprite.height;
// Make hitbox ultra tiny (almost no damage at all)
if (type === 'car1') {
// car1: 2% width, 2% height
self.hitArea = new Rectangle(-w * 0.01, -h * 0.01, w * 0.02, h * 0.02);
} else if (type === 'car2') {
// car2: 2% width, 2% height
self.hitArea = new Rectangle(-w * 0.01, -h * 0.01, w * 0.02, h * 0.02);
} else if (type === 'truck') {
// truck: 2.5% width, 2% height
self.hitArea = new Rectangle(-w * 0.0125, -h * 0.01, w * 0.025, h * 0.02);
} else {
// fallback: 2% width, 2% height
self.hitArea = new Rectangle(-w * 0.01, -h * 0.01, w * 0.02, h * 0.02);
}
}
setVehicleHitArea(self.type);
self.setType = function (type) {
if (self.sprite) self.sprite.destroy();
self.type = type;
self.sprite = self.attachAsset(type, {
anchorX: 0.5,
anchorY: 0.5
});
setVehicleHitArea(type);
// Set sprite orientation to always face movement direction, regardless of asset's default orientation
if (type === 'car2') {
// Car2 asset default faces right, so:
if (self.dir === 1) {
// Left-to-right: no flip needed
self.sprite.scaleX = Math.abs(self.sprite.scaleX);
self.sprite.rotation = 0;
} else {
// Right-to-left: flip horizontally
self.sprite.scaleX = -Math.abs(self.sprite.scaleX);
self.sprite.rotation = 0;
}
} else {
// Other vehicles: keep existing logic
if (self.dir === 1) {
// Always face right for left-to-right movement
self.sprite.scaleX = Math.abs(self.sprite.scaleX);
} else {
// Always face left for right-to-left movement
self.sprite.scaleX = -Math.abs(self.sprite.scaleX);
}
}
// Add or update headlights
addHeadlights();
};
// Lane index (0-19)
self.lane = 0;
// Direction: 1 = left to right, -1 = right to left
self.dir = 1;
// Speed in px per frame
self.speed = 6;
self.update = function () {
if (typeof self.lastX === "undefined") self.lastX = self.x;
self.x += self.speed * self.dir;
// Ensure sprite faces the correct direction, always facing movement direction
if (self.type === 'car2') {
if (self.dir === 1) {
self.sprite.scaleX = Math.abs(self.sprite.scaleX);
self.sprite.rotation = 0;
} else {
self.sprite.scaleX = -Math.abs(self.sprite.scaleX);
self.sprite.rotation = 0;
}
} else {
if (self.dir === 1) {
self.sprite.scaleX = Math.abs(self.sprite.scaleX);
self.sprite.rotation = 0;
} else {
self.sprite.scaleX = -Math.abs(self.sprite.scaleX);
self.sprite.rotation = 0;
}
}
self.lastX = self.x;
// Update headlights orientation, scale, and visibility for night
if (self.headlights) {
// Update position and scale in case car was resized or direction changed
var headlightInset = self.sprite.width * 0.22; // Move headlights inside by 22% of car width
var offset = (self.sprite.width / 2 - headlightInset) * (self.dir === 1 ? 1 : -1);
self.headlights.x = offset;
self.headlights.y = 0;
var scale = self.sprite.width / self.headlights.width;
self.headlights.scaleX = scale * (self.dir === 1 ? 1 : -1);
self.headlights.scaleY = self.sprite.height / self.headlights.height;
// Show headlights only at night (dayNight.t >= 0.5), with semi-transparency
self.headlights.alpha = typeof dayNight !== "undefined" && dayNight.t >= 0.5 ? 0.45 : 0;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87ceeb // Day sky blue
});
/****
* Game Code
****/
// Music
// Sounds
// Coin
// Vehicles
// Lane types
// Animal characters
// --- Game constants ---
var NUM_LANES = 20;
var LANE_HEIGHT = 120;
var LANE_TYPES = []; // 'grass' or 'road'
var LANE_Y = []; // y positions of each lane (0 = bottom)
var LANE_CONFIG = []; // {type, y, speed, dir, spacing, vehicleTypes}
var LANE_TOP = 2732 - NUM_LANES * LANE_HEIGHT; // Top y of first lane
// --- Day/Night cycle ---
var dayNight = {
t: 0,
// 0 = day, 1 = night
speed: 0.00025,
// How fast day/night cycles (per tick)
colorDay: 0x87ceeb,
colorNight: 0x23234a
};
// --- Game state ---
var player = null;
var vehicles = [];
var coins = [];
var score = 0;
var coinsCollected = 0;
var isMoving = false;
var dragStart = null;
var dragTarget = null;
var lastLane = 0;
var gameOver = false;
// --- Level state ---
var currentLevel = 1;
var maxLevel = 5;
var flag = null;
// --- UI ---
var scoreTxt = new Text2('0', {
size: 100,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var coinTxt = new Text2('0', {
size: 80,
fill: 0xFFD700
});
coinTxt.anchor.set(0.5, 0);
LK.gui.topRight.addChild(coinTxt);
// --- Lane setup ---
function setupLanes() {
LANE_TYPES.length = 0;
LANE_Y.length = 0;
LANE_CONFIG.length = 0;
for (var i = 0; i < NUM_LANES; i++) {
// Alternate: grass every 3rd lane, rest are road
var type = i % 3 === 0 ? 'grass' : 'road';
LANE_TYPES.push(type);
var y = 2732 - (i + 0.5) * LANE_HEIGHT;
LANE_Y.push(y);
// Lane config
if (type === 'road') {
// Assign direction: even lanes go left-to-right, odd lanes go right-to-left (never mixed)
var dir = i % 2 === 0 ? 1 : -1;
var speed = 4 + i % 4 * 1.5; // 4, 5.5, 7, 8.5
var spacing = 350 + i % 3 * 80; // 350, 430, 510
var vehicleTypes = i % 4 === 0 ? ['truck'] : ['car1', 'car2'];
LANE_CONFIG.push({
type: type,
y: y,
speed: speed,
dir: dir,
spacing: spacing,
vehicleTypes: vehicleTypes
});
} else {
LANE_CONFIG.push({
type: type,
y: y
});
}
}
}
// --- Draw lanes ---
function drawLanes() {
for (var i = 0; i < NUM_LANES; i++) {
var laneType = LANE_TYPES[i];
var y = LANE_Y[i];
var lane = LK.getAsset(laneType, {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: y
});
game.addChild(lane);
// Add direction arrows for road lanes
// (Removed small car images used as arrows)
}
}
// --- Spawn player ---
function spawnPlayer() {
player = new Animal();
player.setType('chicken');
// Start at bottom grass lane, center
player.x = 2048 / 2;
player.y = LANE_Y[0];
lastLane = 0;
game.addChild(player);
}
// --- Spawn flag at the end of the level ---
function spawnFlag() {
if (flag) {
flag.destroy();
flag = null;
}
// Place flag at the topmost grass lane
var topLaneIdx = NUM_LANES - 1;
var flagY = LANE_Y[topLaneIdx];
flag = LK.getAsset('flag', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: flagY
});
game.addChild(flag);
}
// --- Spawn vehicles ---
function spawnVehicle(laneIdx) {
var config = LANE_CONFIG[laneIdx];
if (!config || config.type !== 'road') return;
// Check for minimum distance to last vehicle in this lane
var lastVehicle = null;
var lastDist = 99999;
for (var i = 0; i < vehicles.length; i++) {
if (vehicles[i].lane === laneIdx) {
var dist = Math.abs(vehicles[i].x - 2048 / 2);
if (dist < lastDist) {
lastDist = dist;
lastVehicle = vehicles[i];
}
}
}
// Only spawn if no vehicle or far enough from last vehicle in this lane
if (!lastVehicle || lastDist > config.spacing) {
var v = new Vehicle();
// Random vehicle type
var vtype = config.vehicleTypes[Math.floor(Math.random() * config.vehicleTypes.length)];
v.setType(vtype);
v.lane = laneIdx;
v.dir = config.dir;
v.speed = config.speed + Math.random() * 1.5;
// Y position
v.y = config.y;
// X position: offscreen left or right
if (v.dir === 1) {
v.x = -v.sprite.width / 2 - 20;
// Car2: always face right for left-to-right movement
if (v.type === 'car2') {
v.sprite.scaleX = Math.abs(v.sprite.scaleX);
v.sprite.rotation = 0;
} else {
// Always face right for left-to-right movement
v.sprite.scaleX = Math.abs(v.sprite.scaleX);
v.sprite.rotation = 0;
}
} else {
v.x = 2048 + v.sprite.width / 2 + 20;
// Car2: always face left for right-to-left movement
if (v.type === 'car2') {
v.sprite.scaleX = -Math.abs(v.sprite.scaleX);
v.sprite.rotation = 0;
} else {
// Always face left for right-to-left movement
v.sprite.scaleX = -Math.abs(v.sprite.scaleX);
v.sprite.rotation = 0;
}
}
// Update headlights after direction and type are set
if (typeof v.addHeadlights === "function") v.addHeadlights && v.addHeadlights();
vehicles.push(v);
game.addChild(v);
}
}
// --- Spawn coin ---
function spawnCoin(laneIdx) {
var config = LANE_CONFIG[laneIdx];
if (!config || config.type !== 'grass') return;
// Only one coin per grass lane at a time
for (var i = 0; i < coins.length; i++) {
if (Math.abs(coins[i].y - config.y) < 10) return;
}
var c = new Coin();
c.x = 200 + Math.random() * (2048 - 400);
c.y = config.y;
coins.push(c);
game.addChild(c);
}
// --- Remove vehicle ---
function removeVehicle(idx) {
vehicles[idx].destroy();
vehicles.splice(idx, 1);
}
// --- Remove coin ---
function removeCoin(idx) {
coins[idx].destroy();
coins.splice(idx, 1);
}
// --- Update UI ---
function updateUI() {
scoreTxt.setText(score);
coinTxt.setText(coinsCollected);
}
// --- Sun and Moon sprites ---
var sunSprite = null;
var moonSprite = null;
var skySpriteLayer = new Container();
game.addChild(skySpriteLayer);
// --- Day/Night cycle update ---
function updateDayNight() {
dayNight.t += dayNight.speed;
if (dayNight.t > 1) dayNight.t = 0;
// Interpolate color
var t = dayNight.t;
var c1 = dayNight.colorDay,
c2 = dayNight.colorNight;
var r = (c1 >> 16) * (1 - t) + (c2 >> 16) * t & 0xff;
var g = (c1 >> 8 & 0xff) * (1 - t) + (c2 >> 8 & 0xff) * t & 0xff;
var b = (c1 & 0xff) * (1 - t) + (c2 & 0xff) * t & 0xff;
var color = r << 16 | g << 8 | b;
game.setBackgroundColor(color);
// Sun and Moon movement
// Sun: visible during t in [0,0.5), Moon: visible during t in [0.5,1)
// Both move in an arc from left to right
var skyY = 350;
var skyRadius = 800;
var centerX = 2048 / 2;
var centerY = skyY + 200;
// Sun
if (!sunSprite) {
sunSprite = LK.getAsset('sun', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0
});
skySpriteLayer.addChild(sunSprite);
}
// Moon
if (!moonSprite) {
moonSprite = LK.getAsset('ay', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0
});
skySpriteLayer.addChild(moonSprite);
}
// Sun position: t in [0,0.5) → left to right, high arc
var sunT = t < 0.5 ? t / 0.5 : 0;
var sunAngle = Math.PI * (1 - sunT); // from left (π) to right (0)
sunSprite.x = centerX + skyRadius * Math.cos(sunAngle);
sunSprite.y = centerY - skyRadius * Math.sin(sunAngle);
sunSprite.alpha = t < 0.5 ? 1 : 0;
// Moon position: t in [0.5,1) → left to right, high arc
var moonT = t >= 0.5 ? (t - 0.5) / 0.5 : 0;
var moonAngle = Math.PI * (1 - moonT); // from left (π) to right (0)
moonSprite.x = centerX + skyRadius * Math.cos(moonAngle);
moonSprite.y = centerY - skyRadius * Math.sin(moonAngle);
moonSprite.alpha = t >= 0.5 ? 1 : 0;
}
// --- Collision detection ---
function checkCollisions() {
// Vehicles
for (var i = 0; i < vehicles.length; i++) {
if (player.intersects(vehicles[i])) {
// Crash!
LK.effects.flashScreen(0xff0000, 800);
LK.getSound('crash').play();
gameOver = true;
LK.showGameOver();
return;
}
}
// Coins
for (var i = coins.length - 1; i >= 0; i--) {
if (player.intersects(coins[i])) {
coinsCollected++;
updateUI();
LK.getSound('coin').play();
removeCoin(i);
}
}
}
// --- Move player (grid) ---
function tryMove(dx, dy) {
if (isMoving || gameOver) return;
var newLane = lastLane + dy;
if (newLane < 0 || newLane >= NUM_LANES) return;
var newX = player.x + dx * 220;
if (newX < 80 || newX > 2048 - 80) return;
var newY = LANE_Y[newLane];
isMoving = true;
player.hop(newX, newY, function () {
isMoving = false;
lastLane = newLane;
// Score: advance only if moving up
if (dy > 0) {
score++;
updateUI();
if (score >= NUM_LANES - 1) {
// Win!
LK.effects.flashScreen(0x00ffcc, 1000);
LK.showYouWin();
gameOver = true;
}
}
});
LK.getSound('hop').play();
}
// --- Touch controls (swipe) ---
var touchStart = null;
game.down = function (x, y, obj) {
if (gameOver) return;
touchStart = {
x: x,
y: y
};
};
game.up = function (x, y, obj) {
if (gameOver || !touchStart) return;
var dx = x - touchStart.x;
var dy = y - touchStart.y;
var absX = Math.abs(dx),
absY = Math.abs(dy);
if (absX < 40 && absY < 40) {
// Tap: move up
tryMove(0, 1);
} else if (absY > absX) {
if (dy < -40) tryMove(0, -1); // Down
else if (dy > 40) tryMove(0, 1); // Up
} else {
if (dx < -40) tryMove(-1, 0); // Left
else if (dx > 40) tryMove(1, 0); // Right
}
touchStart = null;
};
// --- Main update loop ---
game.update = function () {
if (gameOver) return;
// Day/Night
updateDayNight();
// Vehicles
for (var i = vehicles.length - 1; i >= 0; i--) {
vehicles[i].update();
// Remove if offscreen
if (vehicles[i].dir === 1 && vehicles[i].x > 2048 + vehicles[i].sprite.width / 2 + 40 || vehicles[i].dir === -1 && vehicles[i].x < -vehicles[i].sprite.width / 2 - 40) {
removeVehicle(i);
}
}
// Coins
for (var i = 0; i < coins.length; i++) {
coins[i].update();
}
// Spawn vehicles
for (var i = 0; i < NUM_LANES; i++) {
var config = LANE_CONFIG[i];
if (!config || config.type !== 'road') continue;
// Try to spawn a vehicle with a small probability
if (Math.random() < 0.025) {
// ~1 every 40 frames
spawnVehicle(i);
}
}
// Spawn coins
for (var i = 0; i < NUM_LANES; i++) {
var config = LANE_CONFIG[i];
if (!config || config.type !== 'grass') continue;
// 1% chance per frame
if (Math.random() < 0.01) {
spawnCoin(i);
}
}
// Remove coins if offscreen (shouldn't happen, but safety)
for (var i = coins.length - 1; i >= 0; i--) {
if (coins[i].y < LANE_TOP - 100 || coins[i].y > 2732 + 100) {
removeCoin(i);
}
}
// Collisions
checkCollisions();
// Check for flag collision and level progression
if (flag && player.intersects(flag)) {
// Advance to next level if possible
if (currentLevel < maxLevel) {
currentLevel++;
// Increase difficulty: more/faster vehicles, less grass, etc.
NUM_LANES = 20 + currentLevel * 2;
LANE_HEIGHT = Math.max(80, 120 - currentLevel * 5);
// Remove all game objects
for (var i = vehicles.length - 1; i >= 0; i--) removeVehicle(i);
for (var i = coins.length - 1; i >= 0; i--) removeCoin(i);
if (player) {
player.destroy();
player = null;
}
if (flag) {
flag.destroy();
flag = null;
}
// Re-setup lanes and redraw
setupLanes();
drawLanes();
spawnPlayer();
spawnFlag();
updateUI();
gameOver = false;
} else {
// Last level: show win
LK.effects.flashScreen(0x00ffcc, 1000);
LK.showYouWin();
gameOver = true;
}
}
};
// --- Start Menu Overlay ---
var startMenuOverlay = new Container();
var startMenuBg = LK.getAsset('StartMenu', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: 4,
scaleY: 4
});
startMenuOverlay.addChild(startMenuBg);
// Play button (image)
var playBtn = LK.getAsset('start', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2 + 250,
scaleX: 2.2,
scaleY: 2.2
});
startMenuOverlay.addChild(playBtn);
// Options button (image)
var optionsBtn = LK.getAsset('options', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2 + 450,
scaleX: 2.2,
scaleY: 2.2
});
startMenuOverlay.addChild(optionsBtn);
// Exit button (image)
var exitBtn = LK.getAsset('exit', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2 + 600,
scaleX: 2.2,
scaleY: 2.2
});
startMenuOverlay.addChild(exitBtn);
var startMenuActive = true;
game.addChild(startMenuOverlay);
// Helper for button hit detection (works for both Text2 and image buttons)
function isInsideBtn(btn, x, y) {
// Use anchorX/anchorY for images, anchor for Text2
var anchorX = typeof btn.anchorX === "number" ? btn.anchorX : btn.anchor && btn.anchor.x ? btn.anchor.x : 0.5;
var anchorY = typeof btn.anchorY === "number" ? btn.anchorY : btn.anchor && btn.anchor.y ? btn.anchor.y : 0.5;
var bx = btn.x - btn.width * anchorX;
var by = btn.y - btn.height * anchorY;
return x >= bx && x <= bx + btn.width && y >= by && y <= by + btn.height;
}
// Block input/gameplay until play is pressed
function startGame() {
if (!startMenuActive) return;
startMenuActive = false;
startMenuOverlay.visible = false;
// Now start the game
setupLanes();
drawLanes();
spawnPlayer();
spawnFlag();
updateUI();
LK.playMusic('bgmusic', {
fade: {
start: 0,
end: 0.7,
duration: 1200
}
});
}
// --- Options Overlay ---
var optionsOverlay = new Container();
optionsOverlay.visible = false;
// Background for options
var optionsBg = LK.getAsset('StartMenu', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: 3.5,
scaleY: 3.5
});
optionsOverlay.addChild(optionsBg);
// Title
var optionsTitle = new Text2("OPTIONS", {
size: 140,
fill: "#fff"
});
optionsTitle.anchor.set(0.5, 0.5);
optionsTitle.x = 2048 / 2;
optionsTitle.y = 2732 / 2 - 350;
optionsOverlay.addChild(optionsTitle);
// --- Sound Volume Controls ---
var soundLabel = new Text2("Sound Volume", {
size: 90,
fill: "#fff"
});
soundLabel.anchor.set(0.5, 0.5);
soundLabel.x = 2048 / 2;
soundLabel.y = 2732 / 2 - 150;
optionsOverlay.addChild(soundLabel);
var soundDownBtn = new Text2("-", {
size: 120,
fill: "#fff"
});
soundDownBtn.anchor.set(0.5, 0.5);
soundDownBtn.x = 2048 / 2 - 200;
soundDownBtn.y = 2732 / 2 - 50;
optionsOverlay.addChild(soundDownBtn);
var soundUpBtn = new Text2("+", {
size: 120,
fill: "#fff"
});
soundUpBtn.anchor.set(0.5, 0.5);
soundUpBtn.x = 2048 / 2 + 200;
soundUpBtn.y = 2732 / 2 - 50;
optionsOverlay.addChild(soundUpBtn);
var soundVolTxt = new Text2("100%", {
size: 90,
fill: "#fff"
});
soundVolTxt.anchor.set(0.5, 0.5);
soundVolTxt.x = 2048 / 2;
soundVolTxt.y = 2732 / 2 - 50;
optionsOverlay.addChild(soundVolTxt);
// --- Music Controls ---
var musicLabel = new Text2("Music", {
size: 90,
fill: "#fff"
});
musicLabel.anchor.set(0.5, 0.5);
musicLabel.x = 2048 / 2;
musicLabel.y = 2732 / 2 + 80;
optionsOverlay.addChild(musicLabel);
var musicToggleBtn = new Text2("Mute", {
size: 100,
fill: "#fff"
});
musicToggleBtn.anchor.set(0.5, 0.5);
musicToggleBtn.x = 2048 / 2;
musicToggleBtn.y = 2732 / 2 + 180;
optionsOverlay.addChild(musicToggleBtn);
// --- Close Button ---
var optionsCloseBtn = new Text2("CLOSE", {
size: 100,
fill: "#fff"
});
optionsCloseBtn.anchor.set(0.5, 0.5);
optionsCloseBtn.x = 2048 / 2;
optionsCloseBtn.y = 2732 / 2 + 350;
optionsOverlay.addChild(optionsCloseBtn);
game.addChild(optionsOverlay);
// --- Sound/Music State ---
var soundVolume = 1.0; // 0.0 - 1.0
var musicMuted = false;
// Helper to update UI and apply sound/music settings
function updateOptionsUI() {
soundVolTxt.setText(Math.round(soundVolume * 100) + "%");
// Set all sound volumes
LK.getSound('coin').volume = soundVolume;
LK.getSound('crash').volume = soundVolume;
LK.getSound('hop').volume = soundVolume;
// Set music volume or mute
if (musicMuted) {
LK.setMusicVolume && LK.setMusicVolume(0);
musicToggleBtn.setText("Unmute");
} else {
LK.setMusicVolume && LK.setMusicVolume(soundVolume);
musicToggleBtn.setText("Mute");
}
}
// --- Options Button Handlers ---
// Show options overlay
function showOptions() {
optionsOverlay.visible = true;
startMenuOverlay.visible = false;
updateOptionsUI();
}
// Hide options overlay
function closeOptions() {
optionsOverlay.visible = false;
if (startMenuActive) startMenuOverlay.visible = true;
}
// Touch handler for options overlay
optionsOverlay.down = function (x, y, obj) {
if (isInsideBtn(soundDownBtn, x, y)) {
soundVolume = Math.max(0, soundVolume - 0.1);
updateOptionsUI();
return;
}
if (isInsideBtn(soundUpBtn, x, y)) {
soundVolume = Math.min(1, soundVolume + 0.1);
updateOptionsUI();
return;
}
if (isInsideBtn(musicToggleBtn, x, y)) {
musicMuted = !musicMuted;
updateOptionsUI();
return;
}
if (isInsideBtn(optionsCloseBtn, x, y)) {
closeOptions();
return;
}
};
// Forward touch events to options overlay if visible
var origGameDown = game.down;
game.down = function (x, y, obj) {
if (optionsOverlay.visible) {
optionsOverlay.down(x, y, obj);
return;
}
origGameDown(x, y, obj);
};
function exitGame() {
LK.effects.flashScreen(0x222222, 400);
}
// Touch handler for menu
game.down = function (x, y, obj) {
if (startMenuActive) {
if (isInsideBtn(playBtn, x, y)) {
startGame();
return;
} else if (isInsideBtn(optionsBtn, x, y)) {
showOptions();
return;
} else if (isInsideBtn(exitBtn, x, y)) {
exitGame();
return;
}
// Ignore touches outside buttons while menu is active
return;
}
if (gameOver) return;
touchStart = {
x: x,
y: y
};
};
game.up = function (x, y, obj) {
if (startMenuActive) return;
if (gameOver || !touchStart) return;
var dx = x - touchStart.x;
var dy = y - touchStart.y;
var absX = Math.abs(dx),
absY = Math.abs(dy);
if (absX < 40 && absY < 40) {
// Tap: move up
tryMove(0, 1);
} else if (absY > absX) {
if (dy < -40) tryMove(0, -1); // Down
else if (dy > 40) tryMove(0, 1); // Up
} else {
if (dx < -40) tryMove(-1, 0); // Left
else if (dx > 40) tryMove(1, 0); // Right
}
touchStart = null;
};
chicken. In-Game asset. 2d. High contrast. No shadows
road çif şeritli düz yol. In-Game asset. 2d. High contrast. No shadows
car. In-Game asset. 2d. High contrast. No shadows
kamyon. In-Game asset. 2d. High contrast. No shadows
truck. In-Game asset. 2d. High contrast. No shadows
grass çim zemim kısa çimli zemin. In-Game asset. 2d. High contrast. No shadows
flag. In-Game asset. 2d. High contrast. No shadows
sun. In-Game asset. 2d. High contrast. No shadows
moon. In-Game asset. 2d. High contrast. No shadows
far ışığı yansıması. In-Game asset. 2d. High contrast. No shadows