/****
* 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