/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { bestScore: 0 }); /**** * Classes ****/ // Player Car var Car = Container.expand(function () { var self = Container.call(this); var carAsset = self.attachAsset('car', { anchorX: 0.5, anchorY: 0.5 // width/height default }); // For collision box, use the car asset's size self.width = carAsset.width; self.height = carAsset.height; return self; }); // Obstacle (legacy, not used anymore) var Obstacle = Container.expand(function () { var self = Container.call(this); var obsAsset = self.attachAsset('obstacle', { anchorX: 0.5, anchorY: 0.5 }); self.width = obsAsset.width; self.height = obsAsset.height; // For collision return self; }); // Road Stripe var Stripe = Container.expand(function () { var self = Container.call(this); var stripeAsset = self.attachAsset('stripe', { anchorX: 0.5, anchorY: 0.5 }); self.width = stripeAsset.width; self.height = stripeAsset.height; return self; }); // Traffic Light var TrafficLight = Container.expand(function () { var self = Container.call(this); // State: 0 = red, 1 = yellow, 2 = green self.state = 0; self.timer = 0; self.stateDurations = [120, 40, 120]; // red, yellow, green (in ticks) // Light containers self.redLight = self.attachAsset('stripe', { anchorX: 0.5, anchorY: 0.5, width: 80, height: 80, color: 0xff2222 }); self.yellowLight = self.attachAsset('stripe', { anchorX: 0.5, anchorY: 0.5, width: 80, height: 80, color: 0xffe066 }); self.greenLight = self.attachAsset('stripe', { anchorX: 0.5, anchorY: 0.5, width: 80, height: 80, color: 0x44ff44 }); // Arrange vertically self.redLight.y = -90; self.yellowLight.y = 0; self.greenLight.y = 90; // Initial visibility self.redLight.alpha = 1; self.yellowLight.alpha = 0.2; self.greenLight.alpha = 0.2; self.setState = function (state) { self.state = state; self.redLight.alpha = state === 0 ? 1 : 0.2; self.yellowLight.alpha = state === 1 ? 1 : 0.2; self.greenLight.alpha = state === 2 ? 1 : 0.2; }; self.update = function () { self.timer++; if (self.timer > self.stateDurations[self.state]) { self.timer = 0; self.setState((self.state + 1) % 3); } }; return self; }); // Vehicle (moving obstacle) var Vehicle = Container.expand(function () { var self = Container.call(this); // Use obstacle asset for vehicles var vehicleAsset = self.attachAsset('obstacle', { anchorX: 0.5, anchorY: 0.5 }); self.width = vehicleAsset.width; self.height = vehicleAsset.height; // Vehicle speed (relative to road speed, always positive so vehicles never drive backwards) // Obstacles always move forward (downwards), in the same direction as the player's bike self.relativeSpeed = 12 + Math.random() * 10; // 12 to 22, always forward (downwards) // Track lastY for event triggers self.lastY = self.y; // Track if already collided self.lastWasIntersecting = false; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x222222 }); /**** * Game Code ****/ // --- Menu icon click: show tokens popup --- LK.on('menu', function () { // Show a simple popup with the player's tokens LK.showPopup({ title: "Your Tokens", message: "You have " + (tokens || 0) + " tokens.", buttons: [{ text: "OK", action: function action() { LK.closePopup(); } }] }); }); // Road stripes // Coin // Obstacle // Car (player) // Game area // LK.init.shape('coin', {width:100, height:100, color:0xffeb3b, shape:'ellipse'}) var GAME_WIDTH = 2048; var GAME_HEIGHT = 2732; var ROAD_WIDTH = 1200; var ROAD_X = (GAME_WIDTH - ROAD_WIDTH) / 2; var ROAD_Y = 0; var ROAD_HEIGHT = GAME_HEIGHT; // Player car var car = new Car(); car.x = GAME_WIDTH / 2; car.y = GAME_HEIGHT - 500; game.addChild(car); // Road stripes var stripes = []; var stripeSpacing = 400; for (var i = 0; i < 8; i++) { var stripe = new Stripe(); stripe.x = GAME_WIDTH / 2; stripe.y = i * stripeSpacing + 200; stripes.push(stripe); game.addChild(stripe); } // --- Traffic Light --- var trafficLight = new TrafficLight(); trafficLight.x = GAME_WIDTH / 2; trafficLight.y = 220; game.addChild(trafficLight); // Vehicles var vehicles = []; // --- Token system and token icon display only --- var tokens = 0; if (typeof storage.tokens !== "undefined") { tokens = storage.tokens; } else { tokens = 0; storage.tokens = 0; } // --- Token icon and display in top GUI --- var tokenIconContainer = new Container(); var tokenIconAsset = tokenIconContainer.attachAsset('Token', { anchorX: 0.5, anchorY: 0.5, width: 80, height: 80 }); tokenIconContainer.x = GAME_WIDTH - 200; tokenIconContainer.y = 80; LK.gui.top.addChild(tokenIconContainer); var tokensGuiTxt = new Text2('', { size: 60, fill: "#fff" }); tokensGuiTxt.anchor.set(0, 0.5); tokensGuiTxt.x = tokenIconContainer.x + 60; tokensGuiTxt.y = tokenIconContainer.y; LK.gui.top.addChild(tokensGuiTxt); // Score var score = 0; var scoreTxt = new Text2('0', { size: 120, fill: "#fff" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Best score var bestScore = storage.bestScore || 0; var bestScoreTxt = new Text2('Best: ' + bestScore, { size: 60, fill: "#fff" }); bestScoreTxt.anchor.set(0.5, 0); bestScoreTxt.y = 120; LK.gui.top.addChild(bestScoreTxt); // Game speed var baseSpeed = 18; var speed = baseSpeed; var speedIncreaseEvery = 600; // ticks var maxSpeed = 300; // --- Speed Cursor UI --- // Speed cursor bar background var speedCursorBg = new Container(); var speedCursorBgAsset = speedCursorBg.attachAsset('Speed', { anchorX: 0, anchorY: 0.5, width: 32, height: 420, color: 0x333333 }); speedCursorBg.x = 100; speedCursorBg.y = GAME_HEIGHT / 2; game.addChild(speedCursorBg); // Speed cursor fill var speedCursorFill = new Container(); var speedCursorFillAsset = speedCursorFill.attachAsset('Speed', { anchorX: 0, anchorY: 1, width: 32, height: 400, color: 0xd6602c }); speedCursorFill.x = 100; speedCursorFill.y = GAME_HEIGHT / 2 + 200; game.addChild(speedCursorFill); // Speed cursor text var speedCursorTxt = new Text2('SPEED', { size: 48, fill: "#fff" }); speedCursorTxt.anchor.set(0.5, 0.5); speedCursorTxt.x = 116; speedCursorTxt.y = GAME_HEIGHT / 2 + 240; game.addChild(speedCursorTxt); // Gear system var gear = 1; var maxGear = 7; var minGear = 1; // index 0 unused, gears 1-7 var gearSpeeds = [0, 18, 26, 34, 42, 48, 60, 80]; // Dragging var dragCar = false; var dragOffsetX = 0; // Gas and brake button state var gasPressed = false; var brakePressed = false; // --- Handbrake state --- var handbrakePressed = false; // --- Gas System --- var maxGas = 100; var gas = maxGas; var gasConsumptionRate = 0.08; // per tick when moving var gasBarBg = new Container(); var gasBarBgAsset = gasBarBg.attachAsset('Speed', { anchorX: 0, anchorY: 0.5, width: 32, height: 420, color: 0x333333 }); gasBarBg.x = GAME_WIDTH - 132; gasBarBg.y = GAME_HEIGHT / 2; game.addChild(gasBarBg); var gasBarFill = new Container(); var gasBarFillAsset = gasBarFill.attachAsset('Speed', { anchorX: 0, anchorY: 1, width: 32, height: 400, color: 0x43a047 }); gasBarFill.x = GAME_WIDTH - 132; gasBarFill.y = GAME_HEIGHT / 2 + 200; game.addChild(gasBarFill); var gasBarTxt = new Text2('GAS', { size: 48, fill: "#fff" }); gasBarTxt.anchor.set(0.5, 0.5); gasBarTxt.x = GAME_WIDTH - 116; gasBarTxt.y = GAME_HEIGHT / 2 + 240; game.addChild(gasBarTxt); // Touch controls function clamp(val, min, max) { return val < min ? min : val > max ? max : val; } function handleMove(x, y, obj) { // storeOpen check removed (store UI is deleted) if (dragCar) { // Clamp car within road var minX = ROAD_X + car.width / 2 + 20; var maxX = ROAD_X + ROAD_WIDTH - car.width / 2 - 20; var prevX = car.x; car.x = clamp(x - dragOffsetX, minX, maxX); } } game.move = handleMove; // --- Gas and Brake Buttons --- var buttonSize = 260; var buttonMargin = 80; // Gas button (bottom right) var gasBtn = new Container(); var gasAsset = gasBtn.attachAsset('Gas', { anchorX: 0.5, anchorY: 0.5, width: buttonSize, height: buttonSize, color: 0x43a047 }); // gasAsset.tint = 0x43a047; // Tinting removed gasBtn.x = GAME_WIDTH - buttonSize / 2 - buttonMargin; gasBtn.y = GAME_HEIGHT - buttonSize / 2 - buttonMargin; game.addChild(gasBtn); var gasTxt = new Text2('GAS', { size: 70, fill: "#fff" }); gasTxt.anchor.set(0.5, 0.5); gasBtn.addChild(gasTxt); // Brake button (bottom left) var brakeBtn = new Container(); var brakeAsset = brakeBtn.attachAsset('Gas', { anchorX: 0.5, anchorY: 0.5, width: buttonSize, height: buttonSize, color: 0xfbc02d }); // brakeAsset.tint = 0xfbc02d; // Tinting removed brakeBtn.x = buttonSize / 2 + buttonMargin; brakeBtn.y = GAME_HEIGHT - buttonSize / 2 - buttonMargin; game.addChild(brakeBtn); var brakeTxt = new Text2('BRAKE', { size: 70, fill: "#fff" }); brakeTxt.anchor.set(0.5, 0.5); brakeBtn.addChild(brakeTxt); // Gear Up button (bottom center right) var gearUpBtn = new Container(); var gearUpAsset = gearUpBtn.attachAsset('Gear', { anchorX: 0.5, anchorY: 0.5, width: buttonSize * 0.7, height: buttonSize * 0.7, color: 0x90caf9 }); // gearUpAsset.tint = 0x90caf9; // Tinting removed gearUpBtn.x = GAME_WIDTH / 2 + buttonSize + buttonMargin; gearUpBtn.y = GAME_HEIGHT - buttonSize / 2 - buttonMargin; game.addChild(gearUpBtn); var gearUpTxt = new Text2('GEAR+', { size: 60, fill: "#fff" }); gearUpTxt.anchor.set(0.5, 0.5); gearUpBtn.addChild(gearUpTxt); // Gear Down button (bottom center left) var gearDownBtn = new Container(); var gearDownAsset = gearDownBtn.attachAsset('Gear', { anchorX: 0.5, anchorY: 0.5, width: buttonSize * 0.7, height: buttonSize * 0.7, color: 0x90caf9 }); // gearDownAsset.tint = 0x90caf9; // Tinting removed gearDownBtn.x = GAME_WIDTH / 2 - buttonSize - buttonMargin; gearDownBtn.y = GAME_HEIGHT - buttonSize / 2 - buttonMargin; game.addChild(gearDownBtn); var gearDownTxt = new Text2('GEAR-', { size: 60, fill: "#fff" }); gearDownTxt.anchor.set(0.5, 0.5); gearDownBtn.addChild(gearDownTxt); // Gear display (center bottom) var gearTxt = new Text2('GEAR: 1', { size: 80, fill: "#fff" }); gearTxt.anchor.set(0.5, 0.5); gearTxt.x = GAME_WIDTH / 2; gearTxt.y = GAME_HEIGHT - buttonSize - buttonMargin - 60; game.addChild(gearTxt); // --- Handbrake Button (center bottom, between brake and gear down) --- var handbrakeBtn = new Container(); var handbrakeAsset = handbrakeBtn.attachAsset('Gas', { anchorX: 0.5, anchorY: 0.5, width: buttonSize * 0.8, height: buttonSize * 0.8, color: 0x607d8b }); handbrakeBtn.x = buttonSize + buttonMargin * 1.5; handbrakeBtn.y = GAME_HEIGHT - buttonSize / 2 - buttonMargin; game.addChild(handbrakeBtn); var handbrakeTxt = new Text2('HANDBRAKE', { size: 48, fill: "#fff" }); handbrakeTxt.anchor.set(0.5, 0.5); handbrakeBtn.addChild(handbrakeTxt); // Helper to check if a point is inside a button function isInsideBtn(btn, x, y) { var bx = btn.x, by = btn.y; var hw = buttonSize / 2, hh = buttonSize / 2; return x >= bx - hw && x <= bx + hw && y >= by - hh && y <= by + hh; } // Down event game.down = function (x, y, obj) { // No store or skin UI to handle // Only start drag if touch is on car var local = car.toLocal(game.toGlobal({ x: x, y: y })); if (local.x >= -car.width / 2 && local.x <= car.width / 2 && local.y >= -car.height / 2 && local.y <= car.height / 2) { dragCar = true; dragOffsetX = x - car.x; } // Gas/brake buttons if (isInsideBtn(gasBtn, x, y)) { gasPressed = true; gasAsset.alpha = 0.7; } if (isInsideBtn(brakeBtn, x, y)) { brakePressed = true; brakeAsset.alpha = 0.7; } // Gear up button if (isInsideBtn(gearUpBtn, x, y)) { if (gear < maxGear) { gear++; gearTxt.setText('GEAR: ' + gear); // Snap speed to new gear's minimum if below if (speed < gearSpeeds[gear]) speed = gearSpeeds[gear]; } gearUpAsset.alpha = 0.7; } // Gear down button if (isInsideBtn(gearDownBtn, x, y)) { if (gear > minGear) { gear--; gearTxt.setText('GEAR: ' + gear); // Snap speed to new gear's max if above if (speed > gearSpeeds[gear]) speed = gearSpeeds[gear]; } gearDownAsset.alpha = 0.7; } // Handbrake button if (isInsideBtn(handbrakeBtn, x, y)) { handbrakePressed = true; handbrakeAsset.alpha = 0.7; } }; // Up event game.up = function (x, y, obj) { // storeOpen check removed (store UI is deleted) dragCar = false; gasPressed = false; brakePressed = false; handbrakePressed = false; gasAsset.alpha = 1; brakeAsset.alpha = 1; gearUpAsset.alpha = 1; gearDownAsset.alpha = 1; handbrakeAsset.alpha = 1; }; // Spawning var obstacleTimer = 0; var coinTimer = 0; function spawnVehicle() { var lanes = 4; var laneWidth = ROAD_WIDTH / lanes; var lane = Math.floor(Math.random() * lanes); var vehicle = new Vehicle(); vehicle.x = ROAD_X + laneWidth * (lane + 0.5); vehicle.y = -vehicle.height; vehicle.lane = lane; vehicles.push(vehicle); game.addChild(vehicle); } // Main update loop game.update = function () { // --- Car stay in middle at start, slow after gear shift, require gas to accelerate --- // State: 0 = normal, 1 = holding in middle, 2 = slowing after gear shift, 3 = waiting for gas if (typeof carStartState === "undefined") { carStartState = 1; // 1 = holding in middle carStartTicks = 0; carSlowed = false; carWaitGas = false; } if (carStartState === 1) { // Hold car in middle for 1.2 seconds (72 ticks) car.x = GAME_WIDTH / 2; carStartTicks++; if (carStartTicks > 72) { carStartState = 2; carStartTicks = 0; } } else if (carStartState === 2) { // Wait for gear shift, then slow down if (gear > 1 && !carSlowed) { speed = Math.max(gearSpeeds[gear] * 0.55, gearSpeeds[1]); carSlowed = true; carStartTicks = 0; carStartState = 3; } } else if (carStartState === 3) { // Wait for gas press to allow acceleration if (gasPressed) { carStartState = 0; } else { // Gradually slow down if not pressing gas speed = Math.max(speed - 0.7, gearSpeeds[1]); } } // Gas/brake logic if (carStartState === 0) { // Only consume gas if car is moving if (speed > 0.5) { gas -= gasConsumptionRate * (gasPressed ? 1.2 : 0.7); if (gas < 0) gas = 0; } if (gas > 0) { if (handbrakePressed) { // Handbrake: strong deceleration, can go to 0 speed = Math.max(speed - 6, 0); } else if (gasPressed) { speed = Math.min(speed + 0.5, gearSpeeds[gear]); } else if (brakePressed) { // Brake: slow down speed gradually to 0 speed = Math.max(speed - 2.5, 0); } else { // Speed up over time if (LK.ticks % speedIncreaseEvery === 0 && speed < gearSpeeds[gear]) { speed += 2; } } } else { // Out of gas: car stops speed = Math.max(speed - 2.5, 0); if (speed < 0.5) speed = 0; } } // Clamp speed to current gear range, but if out of gas, allow speed to go to 0 if (gas > 0) { if (handbrakePressed) { if (speed < 0) speed = 0; } else { if (speed > gearSpeeds[gear]) speed = gearSpeeds[gear]; if (speed < (gearSpeeds[gear - 1] ? gearSpeeds[gear - 1] : baseSpeed * 0.5)) speed = gearSpeeds[gear - 1] ? gearSpeeds[gear - 1] : baseSpeed * 0.5; } } else { if (speed < 0) speed = 0; } gearTxt.setText('GEAR: ' + gear); // --- Gas Bar UI Update --- if (typeof gasBarFillAsset !== "undefined") { var pctGas = gas / maxGas; if (pctGas < 0) pctGas = 0; if (pctGas > 1) pctGas = 1; gasBarFillAsset.height = 400 * pctGas; gasBarFill.y = GAME_HEIGHT / 2 + 200; } if (typeof gasBarTxt !== "undefined") { gasBarTxt.setText('GAS\n' + Math.round(gas) + ' / ' + maxGas); } // Move stripes for (var i = 0; i < stripes.length; i++) { stripes[i].y += speed; if (stripes[i].y > GAME_HEIGHT + stripes[i].height / 2) { stripes[i].y -= stripeSpacing * stripes.length; } } // Spawn vehicles obstacleTimer += 1; if (obstacleTimer > 32 + Math.floor(40 * baseSpeed / speed)) { // Spawn 1-2 vehicles per spawn, never in the same lane var lanes = 4; var usedLanes = []; var numVehicles = 1 + (Math.random() < 0.5 ? 1 : 0); // 1 or 2 for (var i = 0; i < numVehicles; i++) { var lane; do { lane = Math.floor(Math.random() * lanes); } while (usedLanes.indexOf(lane) !== -1 && usedLanes.length < lanes); usedLanes.push(lane); var vehicle = new Vehicle(); var laneWidth = ROAD_WIDTH / lanes; vehicle.x = ROAD_X + laneWidth * (lane + 0.5); vehicle.y = -vehicle.height; vehicle.lane = lane; vehicles.push(vehicle); game.addChild(vehicle); } obstacleTimer = 0; } // Move vehicles for (var i = vehicles.length - 1; i >= 0; i--) { var vehicle = vehicles[i]; // Move vehicle at road speed + its relative speed vehicle.lastY = vehicle.y; vehicle.y += speed + vehicle.relativeSpeed; // Remove if off screen if (vehicle.y - vehicle.height / 2 > GAME_HEIGHT + 100 || vehicle.y + vehicle.height / 2 < -200) { vehicle.destroy(); vehicles.splice(i, 1); continue; } // Collision with car (only if visible) var isIntersecting = car.intersects(vehicle); if (!vehicle.lastWasIntersecting && isIntersecting) { // Flash and game over LK.effects.flashScreen(0xff0000, 800); if (score > bestScore) { storage.bestScore = score; } LK.showGameOver(); return; } vehicle.lastWasIntersecting = isIntersecting; } score += Math.floor(speed / 4); // Slow down score growth by 75% scoreTxt.setText(score); // Token system: Give 1 token for every 1000 distance if (typeof lastDistanceForToken === "undefined") { lastDistanceForToken = 0; } while (score - lastDistanceForToken >= 1000) { tokens += 1; lastDistanceForToken += 1000; storage.tokens = tokens; } // Update top GUI token display if (typeof tokensGuiTxt !== "undefined") { tokensGuiTxt.setText('' + (tokens || 0)); } // Speed up by 1% for every 2000 score points if (typeof lastScoreForSpeed === "undefined") { lastScoreForSpeed = 0; } while (score - lastScoreForSpeed >= 2000) { speed = Math.min(Math.round(speed * 1.01), maxSpeed); lastScoreForSpeed += 2000; } if (score > bestScore) { bestScore = score; bestScoreTxt.setText('Best: ' + bestScore); storage.bestScore = bestScore; } // --- Speed Cursor Update --- // Fill height is proportional to speed (min: gearSpeeds[1], max: 300) if (typeof speedCursorFillAsset !== "undefined") { var minSpd = gearSpeeds[1]; var maxSpd = 300; var pct = (speed - minSpd) / (maxSpd - minSpd); pct = pct < 0 ? 0 : pct > 1 ? 1 : pct; speedCursorFillAsset.height = 400 * pct; speedCursorFill.y = GAME_HEIGHT / 2 + 200; // always anchor at bottom } if (typeof speedCursorTxt !== "undefined") { speedCursorTxt.setText('SPEED\n' + Math.round(speed) + ' / 300'); } // --- Traffic Light Update --- if (typeof trafficLight !== "undefined" && trafficLight.update) { trafficLight.update(); } }; // Reset score on new game score = 0; scoreTxt.setText(score); bestScore = storage.bestScore || 0; bestScoreTxt.setText('Best: ' + bestScore); // Reset token system state for this run (do not reset tokens, just the counter for new tokens) lastDistanceForToken = 0; // Reset top GUI token display if (typeof tokensGuiTxt !== "undefined") { tokensGuiTxt.setText('' + (tokens || 0)); } // Reset vehicles for (var i = vehicles.length - 1; i >= 0; i--) { if (vehicles[i] && vehicles[i].destroy) vehicles[i].destroy(); } vehicles = []; // Reset gas gas = maxGas; if (typeof gasBarFillAsset !== "undefined") { gasBarFillAsset.height = 400; gasBarFill.y = GAME_HEIGHT / 2 + 200; } if (typeof gasBarTxt !== "undefined") { gasBarTxt.setText('GAS\n' + Math.round(gas) + ' / ' + maxGas); } // Recreate car if (typeof car !== "undefined" && car.destroy) car.destroy(); car = new Car(); car.x = GAME_WIDTH / 2; car.y = GAME_HEIGHT - 500; game.addChild(car);
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
bestScore: 0
});
/****
* Classes
****/
// Player Car
var Car = Container.expand(function () {
var self = Container.call(this);
var carAsset = self.attachAsset('car', {
anchorX: 0.5,
anchorY: 0.5
// width/height default
});
// For collision box, use the car asset's size
self.width = carAsset.width;
self.height = carAsset.height;
return self;
});
// Obstacle (legacy, not used anymore)
var Obstacle = Container.expand(function () {
var self = Container.call(this);
var obsAsset = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = obsAsset.width;
self.height = obsAsset.height;
// For collision
return self;
});
// Road Stripe
var Stripe = Container.expand(function () {
var self = Container.call(this);
var stripeAsset = self.attachAsset('stripe', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = stripeAsset.width;
self.height = stripeAsset.height;
return self;
});
// Traffic Light
var TrafficLight = Container.expand(function () {
var self = Container.call(this);
// State: 0 = red, 1 = yellow, 2 = green
self.state = 0;
self.timer = 0;
self.stateDurations = [120, 40, 120]; // red, yellow, green (in ticks)
// Light containers
self.redLight = self.attachAsset('stripe', {
anchorX: 0.5,
anchorY: 0.5,
width: 80,
height: 80,
color: 0xff2222
});
self.yellowLight = self.attachAsset('stripe', {
anchorX: 0.5,
anchorY: 0.5,
width: 80,
height: 80,
color: 0xffe066
});
self.greenLight = self.attachAsset('stripe', {
anchorX: 0.5,
anchorY: 0.5,
width: 80,
height: 80,
color: 0x44ff44
});
// Arrange vertically
self.redLight.y = -90;
self.yellowLight.y = 0;
self.greenLight.y = 90;
// Initial visibility
self.redLight.alpha = 1;
self.yellowLight.alpha = 0.2;
self.greenLight.alpha = 0.2;
self.setState = function (state) {
self.state = state;
self.redLight.alpha = state === 0 ? 1 : 0.2;
self.yellowLight.alpha = state === 1 ? 1 : 0.2;
self.greenLight.alpha = state === 2 ? 1 : 0.2;
};
self.update = function () {
self.timer++;
if (self.timer > self.stateDurations[self.state]) {
self.timer = 0;
self.setState((self.state + 1) % 3);
}
};
return self;
});
// Vehicle (moving obstacle)
var Vehicle = Container.expand(function () {
var self = Container.call(this);
// Use obstacle asset for vehicles
var vehicleAsset = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = vehicleAsset.width;
self.height = vehicleAsset.height;
// Vehicle speed (relative to road speed, always positive so vehicles never drive backwards)
// Obstacles always move forward (downwards), in the same direction as the player's bike
self.relativeSpeed = 12 + Math.random() * 10; // 12 to 22, always forward (downwards)
// Track lastY for event triggers
self.lastY = self.y;
// Track if already collided
self.lastWasIntersecting = false;
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
// --- Menu icon click: show tokens popup ---
LK.on('menu', function () {
// Show a simple popup with the player's tokens
LK.showPopup({
title: "Your Tokens",
message: "You have " + (tokens || 0) + " tokens.",
buttons: [{
text: "OK",
action: function action() {
LK.closePopup();
}
}]
});
});
// Road stripes
// Coin
// Obstacle
// Car (player)
// Game area
// LK.init.shape('coin', {width:100, height:100, color:0xffeb3b, shape:'ellipse'})
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var ROAD_WIDTH = 1200;
var ROAD_X = (GAME_WIDTH - ROAD_WIDTH) / 2;
var ROAD_Y = 0;
var ROAD_HEIGHT = GAME_HEIGHT;
// Player car
var car = new Car();
car.x = GAME_WIDTH / 2;
car.y = GAME_HEIGHT - 500;
game.addChild(car);
// Road stripes
var stripes = [];
var stripeSpacing = 400;
for (var i = 0; i < 8; i++) {
var stripe = new Stripe();
stripe.x = GAME_WIDTH / 2;
stripe.y = i * stripeSpacing + 200;
stripes.push(stripe);
game.addChild(stripe);
}
// --- Traffic Light ---
var trafficLight = new TrafficLight();
trafficLight.x = GAME_WIDTH / 2;
trafficLight.y = 220;
game.addChild(trafficLight);
// Vehicles
var vehicles = [];
// --- Token system and token icon display only ---
var tokens = 0;
if (typeof storage.tokens !== "undefined") {
tokens = storage.tokens;
} else {
tokens = 0;
storage.tokens = 0;
}
// --- Token icon and display in top GUI ---
var tokenIconContainer = new Container();
var tokenIconAsset = tokenIconContainer.attachAsset('Token', {
anchorX: 0.5,
anchorY: 0.5,
width: 80,
height: 80
});
tokenIconContainer.x = GAME_WIDTH - 200;
tokenIconContainer.y = 80;
LK.gui.top.addChild(tokenIconContainer);
var tokensGuiTxt = new Text2('', {
size: 60,
fill: "#fff"
});
tokensGuiTxt.anchor.set(0, 0.5);
tokensGuiTxt.x = tokenIconContainer.x + 60;
tokensGuiTxt.y = tokenIconContainer.y;
LK.gui.top.addChild(tokensGuiTxt);
// Score
var score = 0;
var scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Best score
var bestScore = storage.bestScore || 0;
var bestScoreTxt = new Text2('Best: ' + bestScore, {
size: 60,
fill: "#fff"
});
bestScoreTxt.anchor.set(0.5, 0);
bestScoreTxt.y = 120;
LK.gui.top.addChild(bestScoreTxt);
// Game speed
var baseSpeed = 18;
var speed = baseSpeed;
var speedIncreaseEvery = 600; // ticks
var maxSpeed = 300;
// --- Speed Cursor UI ---
// Speed cursor bar background
var speedCursorBg = new Container();
var speedCursorBgAsset = speedCursorBg.attachAsset('Speed', {
anchorX: 0,
anchorY: 0.5,
width: 32,
height: 420,
color: 0x333333
});
speedCursorBg.x = 100;
speedCursorBg.y = GAME_HEIGHT / 2;
game.addChild(speedCursorBg);
// Speed cursor fill
var speedCursorFill = new Container();
var speedCursorFillAsset = speedCursorFill.attachAsset('Speed', {
anchorX: 0,
anchorY: 1,
width: 32,
height: 400,
color: 0xd6602c
});
speedCursorFill.x = 100;
speedCursorFill.y = GAME_HEIGHT / 2 + 200;
game.addChild(speedCursorFill);
// Speed cursor text
var speedCursorTxt = new Text2('SPEED', {
size: 48,
fill: "#fff"
});
speedCursorTxt.anchor.set(0.5, 0.5);
speedCursorTxt.x = 116;
speedCursorTxt.y = GAME_HEIGHT / 2 + 240;
game.addChild(speedCursorTxt);
// Gear system
var gear = 1;
var maxGear = 7;
var minGear = 1;
// index 0 unused, gears 1-7
var gearSpeeds = [0, 18, 26, 34, 42, 48, 60, 80];
// Dragging
var dragCar = false;
var dragOffsetX = 0;
// Gas and brake button state
var gasPressed = false;
var brakePressed = false;
// --- Handbrake state ---
var handbrakePressed = false;
// --- Gas System ---
var maxGas = 100;
var gas = maxGas;
var gasConsumptionRate = 0.08; // per tick when moving
var gasBarBg = new Container();
var gasBarBgAsset = gasBarBg.attachAsset('Speed', {
anchorX: 0,
anchorY: 0.5,
width: 32,
height: 420,
color: 0x333333
});
gasBarBg.x = GAME_WIDTH - 132;
gasBarBg.y = GAME_HEIGHT / 2;
game.addChild(gasBarBg);
var gasBarFill = new Container();
var gasBarFillAsset = gasBarFill.attachAsset('Speed', {
anchorX: 0,
anchorY: 1,
width: 32,
height: 400,
color: 0x43a047
});
gasBarFill.x = GAME_WIDTH - 132;
gasBarFill.y = GAME_HEIGHT / 2 + 200;
game.addChild(gasBarFill);
var gasBarTxt = new Text2('GAS', {
size: 48,
fill: "#fff"
});
gasBarTxt.anchor.set(0.5, 0.5);
gasBarTxt.x = GAME_WIDTH - 116;
gasBarTxt.y = GAME_HEIGHT / 2 + 240;
game.addChild(gasBarTxt);
// Touch controls
function clamp(val, min, max) {
return val < min ? min : val > max ? max : val;
}
function handleMove(x, y, obj) {
// storeOpen check removed (store UI is deleted)
if (dragCar) {
// Clamp car within road
var minX = ROAD_X + car.width / 2 + 20;
var maxX = ROAD_X + ROAD_WIDTH - car.width / 2 - 20;
var prevX = car.x;
car.x = clamp(x - dragOffsetX, minX, maxX);
}
}
game.move = handleMove;
// --- Gas and Brake Buttons ---
var buttonSize = 260;
var buttonMargin = 80;
// Gas button (bottom right)
var gasBtn = new Container();
var gasAsset = gasBtn.attachAsset('Gas', {
anchorX: 0.5,
anchorY: 0.5,
width: buttonSize,
height: buttonSize,
color: 0x43a047
});
// gasAsset.tint = 0x43a047; // Tinting removed
gasBtn.x = GAME_WIDTH - buttonSize / 2 - buttonMargin;
gasBtn.y = GAME_HEIGHT - buttonSize / 2 - buttonMargin;
game.addChild(gasBtn);
var gasTxt = new Text2('GAS', {
size: 70,
fill: "#fff"
});
gasTxt.anchor.set(0.5, 0.5);
gasBtn.addChild(gasTxt);
// Brake button (bottom left)
var brakeBtn = new Container();
var brakeAsset = brakeBtn.attachAsset('Gas', {
anchorX: 0.5,
anchorY: 0.5,
width: buttonSize,
height: buttonSize,
color: 0xfbc02d
});
// brakeAsset.tint = 0xfbc02d; // Tinting removed
brakeBtn.x = buttonSize / 2 + buttonMargin;
brakeBtn.y = GAME_HEIGHT - buttonSize / 2 - buttonMargin;
game.addChild(brakeBtn);
var brakeTxt = new Text2('BRAKE', {
size: 70,
fill: "#fff"
});
brakeTxt.anchor.set(0.5, 0.5);
brakeBtn.addChild(brakeTxt);
// Gear Up button (bottom center right)
var gearUpBtn = new Container();
var gearUpAsset = gearUpBtn.attachAsset('Gear', {
anchorX: 0.5,
anchorY: 0.5,
width: buttonSize * 0.7,
height: buttonSize * 0.7,
color: 0x90caf9
});
// gearUpAsset.tint = 0x90caf9; // Tinting removed
gearUpBtn.x = GAME_WIDTH / 2 + buttonSize + buttonMargin;
gearUpBtn.y = GAME_HEIGHT - buttonSize / 2 - buttonMargin;
game.addChild(gearUpBtn);
var gearUpTxt = new Text2('GEAR+', {
size: 60,
fill: "#fff"
});
gearUpTxt.anchor.set(0.5, 0.5);
gearUpBtn.addChild(gearUpTxt);
// Gear Down button (bottom center left)
var gearDownBtn = new Container();
var gearDownAsset = gearDownBtn.attachAsset('Gear', {
anchorX: 0.5,
anchorY: 0.5,
width: buttonSize * 0.7,
height: buttonSize * 0.7,
color: 0x90caf9
});
// gearDownAsset.tint = 0x90caf9; // Tinting removed
gearDownBtn.x = GAME_WIDTH / 2 - buttonSize - buttonMargin;
gearDownBtn.y = GAME_HEIGHT - buttonSize / 2 - buttonMargin;
game.addChild(gearDownBtn);
var gearDownTxt = new Text2('GEAR-', {
size: 60,
fill: "#fff"
});
gearDownTxt.anchor.set(0.5, 0.5);
gearDownBtn.addChild(gearDownTxt);
// Gear display (center bottom)
var gearTxt = new Text2('GEAR: 1', {
size: 80,
fill: "#fff"
});
gearTxt.anchor.set(0.5, 0.5);
gearTxt.x = GAME_WIDTH / 2;
gearTxt.y = GAME_HEIGHT - buttonSize - buttonMargin - 60;
game.addChild(gearTxt);
// --- Handbrake Button (center bottom, between brake and gear down) ---
var handbrakeBtn = new Container();
var handbrakeAsset = handbrakeBtn.attachAsset('Gas', {
anchorX: 0.5,
anchorY: 0.5,
width: buttonSize * 0.8,
height: buttonSize * 0.8,
color: 0x607d8b
});
handbrakeBtn.x = buttonSize + buttonMargin * 1.5;
handbrakeBtn.y = GAME_HEIGHT - buttonSize / 2 - buttonMargin;
game.addChild(handbrakeBtn);
var handbrakeTxt = new Text2('HANDBRAKE', {
size: 48,
fill: "#fff"
});
handbrakeTxt.anchor.set(0.5, 0.5);
handbrakeBtn.addChild(handbrakeTxt);
// Helper to check if a point is inside a button
function isInsideBtn(btn, x, y) {
var bx = btn.x,
by = btn.y;
var hw = buttonSize / 2,
hh = buttonSize / 2;
return x >= bx - hw && x <= bx + hw && y >= by - hh && y <= by + hh;
}
// Down event
game.down = function (x, y, obj) {
// No store or skin UI to handle
// Only start drag if touch is on car
var local = car.toLocal(game.toGlobal({
x: x,
y: y
}));
if (local.x >= -car.width / 2 && local.x <= car.width / 2 && local.y >= -car.height / 2 && local.y <= car.height / 2) {
dragCar = true;
dragOffsetX = x - car.x;
}
// Gas/brake buttons
if (isInsideBtn(gasBtn, x, y)) {
gasPressed = true;
gasAsset.alpha = 0.7;
}
if (isInsideBtn(brakeBtn, x, y)) {
brakePressed = true;
brakeAsset.alpha = 0.7;
}
// Gear up button
if (isInsideBtn(gearUpBtn, x, y)) {
if (gear < maxGear) {
gear++;
gearTxt.setText('GEAR: ' + gear);
// Snap speed to new gear's minimum if below
if (speed < gearSpeeds[gear]) speed = gearSpeeds[gear];
}
gearUpAsset.alpha = 0.7;
}
// Gear down button
if (isInsideBtn(gearDownBtn, x, y)) {
if (gear > minGear) {
gear--;
gearTxt.setText('GEAR: ' + gear);
// Snap speed to new gear's max if above
if (speed > gearSpeeds[gear]) speed = gearSpeeds[gear];
}
gearDownAsset.alpha = 0.7;
}
// Handbrake button
if (isInsideBtn(handbrakeBtn, x, y)) {
handbrakePressed = true;
handbrakeAsset.alpha = 0.7;
}
};
// Up event
game.up = function (x, y, obj) {
// storeOpen check removed (store UI is deleted)
dragCar = false;
gasPressed = false;
brakePressed = false;
handbrakePressed = false;
gasAsset.alpha = 1;
brakeAsset.alpha = 1;
gearUpAsset.alpha = 1;
gearDownAsset.alpha = 1;
handbrakeAsset.alpha = 1;
};
// Spawning
var obstacleTimer = 0;
var coinTimer = 0;
function spawnVehicle() {
var lanes = 4;
var laneWidth = ROAD_WIDTH / lanes;
var lane = Math.floor(Math.random() * lanes);
var vehicle = new Vehicle();
vehicle.x = ROAD_X + laneWidth * (lane + 0.5);
vehicle.y = -vehicle.height;
vehicle.lane = lane;
vehicles.push(vehicle);
game.addChild(vehicle);
}
// Main update loop
game.update = function () {
// --- Car stay in middle at start, slow after gear shift, require gas to accelerate ---
// State: 0 = normal, 1 = holding in middle, 2 = slowing after gear shift, 3 = waiting for gas
if (typeof carStartState === "undefined") {
carStartState = 1; // 1 = holding in middle
carStartTicks = 0;
carSlowed = false;
carWaitGas = false;
}
if (carStartState === 1) {
// Hold car in middle for 1.2 seconds (72 ticks)
car.x = GAME_WIDTH / 2;
carStartTicks++;
if (carStartTicks > 72) {
carStartState = 2;
carStartTicks = 0;
}
} else if (carStartState === 2) {
// Wait for gear shift, then slow down
if (gear > 1 && !carSlowed) {
speed = Math.max(gearSpeeds[gear] * 0.55, gearSpeeds[1]);
carSlowed = true;
carStartTicks = 0;
carStartState = 3;
}
} else if (carStartState === 3) {
// Wait for gas press to allow acceleration
if (gasPressed) {
carStartState = 0;
} else {
// Gradually slow down if not pressing gas
speed = Math.max(speed - 0.7, gearSpeeds[1]);
}
}
// Gas/brake logic
if (carStartState === 0) {
// Only consume gas if car is moving
if (speed > 0.5) {
gas -= gasConsumptionRate * (gasPressed ? 1.2 : 0.7);
if (gas < 0) gas = 0;
}
if (gas > 0) {
if (handbrakePressed) {
// Handbrake: strong deceleration, can go to 0
speed = Math.max(speed - 6, 0);
} else if (gasPressed) {
speed = Math.min(speed + 0.5, gearSpeeds[gear]);
} else if (brakePressed) {
// Brake: slow down speed gradually to 0
speed = Math.max(speed - 2.5, 0);
} else {
// Speed up over time
if (LK.ticks % speedIncreaseEvery === 0 && speed < gearSpeeds[gear]) {
speed += 2;
}
}
} else {
// Out of gas: car stops
speed = Math.max(speed - 2.5, 0);
if (speed < 0.5) speed = 0;
}
}
// Clamp speed to current gear range, but if out of gas, allow speed to go to 0
if (gas > 0) {
if (handbrakePressed) {
if (speed < 0) speed = 0;
} else {
if (speed > gearSpeeds[gear]) speed = gearSpeeds[gear];
if (speed < (gearSpeeds[gear - 1] ? gearSpeeds[gear - 1] : baseSpeed * 0.5)) speed = gearSpeeds[gear - 1] ? gearSpeeds[gear - 1] : baseSpeed * 0.5;
}
} else {
if (speed < 0) speed = 0;
}
gearTxt.setText('GEAR: ' + gear);
// --- Gas Bar UI Update ---
if (typeof gasBarFillAsset !== "undefined") {
var pctGas = gas / maxGas;
if (pctGas < 0) pctGas = 0;
if (pctGas > 1) pctGas = 1;
gasBarFillAsset.height = 400 * pctGas;
gasBarFill.y = GAME_HEIGHT / 2 + 200;
}
if (typeof gasBarTxt !== "undefined") {
gasBarTxt.setText('GAS\n' + Math.round(gas) + ' / ' + maxGas);
}
// Move stripes
for (var i = 0; i < stripes.length; i++) {
stripes[i].y += speed;
if (stripes[i].y > GAME_HEIGHT + stripes[i].height / 2) {
stripes[i].y -= stripeSpacing * stripes.length;
}
}
// Spawn vehicles
obstacleTimer += 1;
if (obstacleTimer > 32 + Math.floor(40 * baseSpeed / speed)) {
// Spawn 1-2 vehicles per spawn, never in the same lane
var lanes = 4;
var usedLanes = [];
var numVehicles = 1 + (Math.random() < 0.5 ? 1 : 0); // 1 or 2
for (var i = 0; i < numVehicles; i++) {
var lane;
do {
lane = Math.floor(Math.random() * lanes);
} while (usedLanes.indexOf(lane) !== -1 && usedLanes.length < lanes);
usedLanes.push(lane);
var vehicle = new Vehicle();
var laneWidth = ROAD_WIDTH / lanes;
vehicle.x = ROAD_X + laneWidth * (lane + 0.5);
vehicle.y = -vehicle.height;
vehicle.lane = lane;
vehicles.push(vehicle);
game.addChild(vehicle);
}
obstacleTimer = 0;
}
// Move vehicles
for (var i = vehicles.length - 1; i >= 0; i--) {
var vehicle = vehicles[i];
// Move vehicle at road speed + its relative speed
vehicle.lastY = vehicle.y;
vehicle.y += speed + vehicle.relativeSpeed;
// Remove if off screen
if (vehicle.y - vehicle.height / 2 > GAME_HEIGHT + 100 || vehicle.y + vehicle.height / 2 < -200) {
vehicle.destroy();
vehicles.splice(i, 1);
continue;
}
// Collision with car (only if visible)
var isIntersecting = car.intersects(vehicle);
if (!vehicle.lastWasIntersecting && isIntersecting) {
// Flash and game over
LK.effects.flashScreen(0xff0000, 800);
if (score > bestScore) {
storage.bestScore = score;
}
LK.showGameOver();
return;
}
vehicle.lastWasIntersecting = isIntersecting;
}
score += Math.floor(speed / 4); // Slow down score growth by 75%
scoreTxt.setText(score);
// Token system: Give 1 token for every 1000 distance
if (typeof lastDistanceForToken === "undefined") {
lastDistanceForToken = 0;
}
while (score - lastDistanceForToken >= 1000) {
tokens += 1;
lastDistanceForToken += 1000;
storage.tokens = tokens;
}
// Update top GUI token display
if (typeof tokensGuiTxt !== "undefined") {
tokensGuiTxt.setText('' + (tokens || 0));
}
// Speed up by 1% for every 2000 score points
if (typeof lastScoreForSpeed === "undefined") {
lastScoreForSpeed = 0;
}
while (score - lastScoreForSpeed >= 2000) {
speed = Math.min(Math.round(speed * 1.01), maxSpeed);
lastScoreForSpeed += 2000;
}
if (score > bestScore) {
bestScore = score;
bestScoreTxt.setText('Best: ' + bestScore);
storage.bestScore = bestScore;
}
// --- Speed Cursor Update ---
// Fill height is proportional to speed (min: gearSpeeds[1], max: 300)
if (typeof speedCursorFillAsset !== "undefined") {
var minSpd = gearSpeeds[1];
var maxSpd = 300;
var pct = (speed - minSpd) / (maxSpd - minSpd);
pct = pct < 0 ? 0 : pct > 1 ? 1 : pct;
speedCursorFillAsset.height = 400 * pct;
speedCursorFill.y = GAME_HEIGHT / 2 + 200; // always anchor at bottom
}
if (typeof speedCursorTxt !== "undefined") {
speedCursorTxt.setText('SPEED\n' + Math.round(speed) + ' / 300');
}
// --- Traffic Light Update ---
if (typeof trafficLight !== "undefined" && trafficLight.update) {
trafficLight.update();
}
};
// Reset score on new game
score = 0;
scoreTxt.setText(score);
bestScore = storage.bestScore || 0;
bestScoreTxt.setText('Best: ' + bestScore);
// Reset token system state for this run (do not reset tokens, just the counter for new tokens)
lastDistanceForToken = 0;
// Reset top GUI token display
if (typeof tokensGuiTxt !== "undefined") {
tokensGuiTxt.setText('' + (tokens || 0));
}
// Reset vehicles
for (var i = vehicles.length - 1; i >= 0; i--) {
if (vehicles[i] && vehicles[i].destroy) vehicles[i].destroy();
}
vehicles = [];
// Reset gas
gas = maxGas;
if (typeof gasBarFillAsset !== "undefined") {
gasBarFillAsset.height = 400;
gasBarFill.y = GAME_HEIGHT / 2 + 200;
}
if (typeof gasBarTxt !== "undefined") {
gasBarTxt.setText('GAS\n' + Math.round(gas) + ' / ' + maxGas);
}
// Recreate car
if (typeof car !== "undefined" && car.destroy) car.destroy();
car = new Car();
car.x = GAME_WIDTH / 2;
car.y = GAME_HEIGHT - 500;
game.addChild(car);
Vertical sports car. In-Game asset. 2d. High contrast. No shadows
Accelerator pedal vertical. In-Game asset. 2d. High contrast. No shadows
Car gear-. In-Game asset. 2d. High contrast. No shadows
Standart Car vertical. In-Game asset. 2d. High contrast. No shadows
Token. In-Game asset. 2d. High contrast. No shadows