/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Car class
var Car = Container.expand(function () {
var self = Container.call(this);
// --- Car body (main) ---
var carBody = self.attachAsset('car', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.1
});
// --- Car detail elements removed ---
// Direction: 1 = up-right (45deg), -1 = up-left (135deg)
self.direction = 1;
self.speed = 32; // initial speed in px per tick
self.angleRight = Math.PI / 4; // 45deg (up-right)
self.angleLeft = 3 * Math.PI / 4; // 135deg (up-left)
// Only allow up-left and up-right, always moving up at an angle
self.setDirection = function (dir) {
// Only allow -1 (up-left) or 1 (up-right)
if (dir !== 1 && dir !== -1) return;
if (self.direction === dir) return; // Already in this direction, do nothing
// Animate rotation (body, roof, windows, lights, tires)
var targetRot = dir === 1 ? self.angleRight : self.angleLeft;
var allParts = [carBody];
for (var i = 0; i < allParts.length; ++i) {
tween(allParts[i], {
rotation: targetRot
}, {
duration: 120,
easing: tween.cubicOut
});
}
// Animate lane change (x position)
var roadLineCenter = 2048 / 2;
var roadLineWidth = 1200;
var obsMargin = 40;
var obsWidth = 160;
var carTravelEdge = roadLineWidth / 2 - obsWidth / 2 - obsMargin;
var targetX = roadLineCenter + dir * carTravelEdge;
// Stop any previous x tweens
tween.stop(self, {
x: true
});
tween(self, {
x: targetX
}, {
duration: 220,
easing: tween.cubicOut
});
self.direction = dir;
};
// Move car visually left/right (simulate up-left/up-right)
self.moveForward = function () {
// Car moves sideways along the gray road line based on direction
var roadLineCenter = 2048 / 2;
var roadLineWidth = 1200;
var roadWidth = 2400;
var carHalfW = self.width / 2;
var obsMargin = 40;
var obsWidth = 160;
var carTravelEdge = roadLineWidth / 2 - obsWidth / 2 - obsMargin;
// -1 = left, 1 = right
var targetX = roadLineCenter + self.direction * carTravelEdge;
// Smoothly move car toward targetX
var moveSpeed = 0.22;
self.x += (targetX - self.x) * moveSpeed;
// Make all car parts match obstacle's rotation (90deg, PI/2 radians)
var allParts = [carBody];
for (var i = 0; i < allParts.length; ++i) {
allParts[i].rotation = Math.PI / 2;
}
self.lastX = self.x;
self.lastX = self.x;
};
// Reset to center bottom
self.resetPosition = function () {
self.x = 2048 / 2;
self.y = 2732 - 350;
self.setDirection(1);
};
return self;
});
// Coin class
var Coin = Container.expand(function () {
var self = Container.call(this);
// Use a yellow ellipse for the coin
var coinAsset = self.attachAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.4,
scaleY: 1.4
});
coinAsset.color = 0xffd700; // gold/yellow
self.collected = false;
return self;
});
// White dotted line class
var DottedLine = Container.expand(function () {
var self = Container.call(this);
// Each dotted line is a white rectangle (box)
var dotWidth = 40;
var dotHeight = 100;
var dotAsset = self.attachAsset('dottedLine', {
anchorX: 0.5,
anchorY: 0.5
});
// Set size and color
dotAsset.width = dotWidth;
dotAsset.height = dotHeight;
dotAsset.color = 0xffffff;
// Center on road
self.x = 2048 / 2;
// y is set on spawn
self.update = function () {
// Move down at car speed
if (typeof car !== "undefined" && car && typeof car.speed === "number") {
self.y += car.speed;
}
// Remove if offscreen (handled in game.update)
};
return self;
});
// Obstacle class
var Obstacle = Container.expand(function () {
var self = Container.call(this);
// Randomly pick a color for the obstacle: green, blue, red, or black
var colors = [0x2ecc40, 0x1877f7, 0xd83318, 0x000000];
var colorIdx = Math.floor(Math.random() * colors.length);
// Use a box shape for the obstacle, colored accordingly
var obsAsset = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5
});
obsAsset.shape = 'box';
obsAsset.width = 320;
obsAsset.height = 200;
obsAsset.color = colors[colorIdx];
// Rotate 90 degrees (PI/2 radians) so the rectangle is horizontal
obsAsset.rotation = Math.PI / 2;
// side: -1 = left, 1 = right
self.side = 1;
self.passed = false;
// setSide is no longer needed, obstacles are placed directly on the gray line in spawnObstacle
// Set y position
self.setY = function (y) {
self.y = y;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
// Road
// Car: red box, 180x120
// Obstacle: yellow ellipse, 160x100
// Road: dark gray box, 2400x2732 (full height, 4x wide)
var road = LK.getAsset('road', {
anchorX: 0.5,
anchorY: 0,
x: 2048 / 2,
y: 0
});
road.width = 2400;
road.height = 2732;
game.addChild(road);
// Add a vertical gray line in the center of the road
var roadLine;
var roadLineAsset = LK.getAsset('roadLine', {
anchorX: 0.5,
anchorY: 0,
x: 2048 / 2,
y: 0
});
roadLineAsset.width = 1200;
roadLineAsset.height = 2732;
game.addChild(roadLineAsset);
// Car
// (Drifting line removed)
var car = new Car();
game.addChild(car);
car.resetPosition();
// Obstacles array
var obstacles = [];
// Score
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 1);
LK.gui.bottom.addChild(scoreTxt);
// Dotted lines (white, moving down the center of the road)
var dottedLines = [];
var dottedLineHeight = 100;
var dottedLineSpacing = 300;
var numDottedLines = Math.ceil(2732 / dottedLineSpacing) + 2; // enough to cover screen + buffer
for (var i = 0; i < numDottedLines; ++i) {
var dot = new DottedLine();
// Always spawn at the top middle, spaced downwards
dot.x = 2048 / 2;
dot.y = -dottedLineHeight / 2 + i * dottedLineSpacing;
dottedLines.push(dot);
game.addChild(dot);
}
// Game state
var gameOver = false;
var passedObstacles = 0;
var ticksSinceLastObstacle = 0;
var obstacleInterval = 45; // ticks between obstacles (start)
var minObstacleInterval = 18; // minimum interval (faster)
var speedIncreaseEvery = 10; // every 10 obstacles, speed up
var carBaseSpeed = 32;
var carMaxSpeed = 72;
// Night/Day cycle variables
var isNight = false;
var lastNightDayScore = 0;
// Overlay for night effect (covers the whole game area, fades in/out)
var nightOverlay = LK.getAsset('road', {
anchorX: 0.5,
anchorY: 0,
x: 2048 / 2,
y: 0
});
nightOverlay.width = 2400;
nightOverlay.height = 2732;
nightOverlay.color = 0x000000;
nightOverlay.alpha = 0; // Start transparent (day)
game.addChild(nightOverlay);
// Helper: spawn obstacle
function spawnObstacle() {
// Always spawn obstacles on the gray road line, not on the dotted line
// The gray road line is centered at 2048/2, width 1200 (4x)
var roadLineCenter = 2048 / 2;
var roadLineWidth = 1200;
var obsMargin = 40;
var obsWidth = 320;
// Move obstacles closer to the OUTER side of the road, but not touching the edge
// The outer edge for obstacle center is: roadLineCenter ± (roadLineWidth/2 - obsMargin - obsWidth/2)
var outerXOffset = roadLineWidth / 2 - obsMargin - obsWidth / 2;
// Randomly left or right of the center gray line
var side = Math.random() < 0.5 ? -1 : 1;
// Main obstacle: place as close as possible to the outer edge, but not touching
var obs1 = new Obstacle();
obs1.x = roadLineCenter + side * outerXOffset;
obs1.y = -100;
obstacles.push(obs1);
game.addChild(obs1);
// Second obstacle, spawn to the correct side based on lane, but ensure it doesn't touch the outer side of the road
var obs2 = new Obstacle();
var gap = 8; // gap between obstacles (original smaller gap)
// Calculate the max X for the second obstacle so it doesn't touch the outer side of the road
var roadOuterEdgeLeft = roadLineCenter - roadLineWidth / 2 + obsMargin + obsWidth / 2;
var roadOuterEdgeRight = roadLineCenter + roadLineWidth / 2 - obsMargin - obsWidth / 2;
if (side === 1) {
// Right lane: spawn another obstacle to the right, but clamp so it doesn't touch the outer side of the road
var intendedX = obs1.x - obsWidth - gap;
// If intendedX would be inside the road, use it, otherwise clamp to the minimum allowed (center line + margin + half width)
obs2.x = Math.max(intendedX, roadLineCenter + obsWidth / 2 + gap);
} else {
// Left lane: spawn another obstacle to the left, but clamp so it doesn't touch the outer side of the road
var intendedX = obs1.x + obsWidth + gap;
obs2.x = Math.min(intendedX, roadLineCenter - obsWidth / 2 - gap);
}
obs2.y = -100;
obstacles.push(obs2);
game.addChild(obs2);
// Place coins further in front of each obstacle (not between obstacles)
var coinDistance = 340; // px in front of obstacle (increased distance)
var coin1 = new Coin();
coin1.x = obs1.x;
coin1.y = obs1.y + coinDistance;
game.addChild(coin1);
if (typeof coins === "undefined") coins = [];
coins.push(coin1);
var coin2 = new Coin();
coin2.x = obs2.x;
coin2.y = obs2.y + coinDistance;
game.addChild(coin2);
coins.push(coin2);
}
// Helper: update score
function updateScore() {
scoreTxt.setText(LK.getScore());
}
// Helper: reset game state
function resetGame() {
// Remove all obstacles
for (var i = 0; i < obstacles.length; ++i) {
obstacles[i].destroy();
}
obstacles = [];
// Remove all coins
if (typeof coins !== "undefined") {
for (var i = 0; i < coins.length; ++i) {
coins[i].destroy();
}
}
coins = [];
car.resetPosition();
car.speed = carBaseSpeed;
obstacleInterval = 90;
passedObstacles = 0;
ticksSinceLastObstacle = 0;
// Reset dotted lines to initial positions
for (var i = 0; i < dottedLines.length; ++i) {
dottedLines[i].x = 2048 / 2;
dottedLines[i].y = -dottedLineHeight / 2 + i * 300;
}
LK.setScore(0);
updateScore();
gameOver = false;
autoMode = false;
if (typeof updateAutoBtn === "function" && typeof autoBtn !== "undefined") updateAutoBtn();
// Reset night/day cycle
isNight = false;
lastNightDayScore = 0;
tween.stop(nightOverlay, {
alpha: true
});
nightOverlay.alpha = 0;
}
// Touch/tap: (no car outside logic)
game.down = function (x, y, obj) {
if (gameOver) return;
if (autoMode) return;
// Tap to switch lane: flip car direction
car.setDirection(-car.direction);
};
// Main game loop
game.update = function () {
if (gameOver) return;
// Move car
if (autoMode && !gameOver) {
// Find the closest obstacle ahead of the car
var closestObs = null;
var minDist = Infinity;
for (var i = 0; i < obstacles.length; ++i) {
var obs = obstacles[i];
// Only consider obstacles in front of the car and within a certain Y range
if (obs.y > car.y - 200 && obs.y < car.y + 400) {
var dist = Math.abs(obs.y - car.y);
if (dist < minDist) {
minDist = dist;
closestObs = obs;
}
}
}
if (closestObs) {
// If the obstacle is on the same side as the car, switch direction
var carSide = car.x > 2048 / 2 ? 1 : -1;
var obsSide = closestObs.x > 2048 / 2 ? 1 : -1;
if (carSide === obsSide) {
car.setDirection(-car.direction);
}
}
}
// If not autoMode, move car toward center X (normal centering, not wide)
if (!autoMode && !gameOver) {
var centerX = 2048 / 2;
var moveSpeed = 0.12; // slightly slower for normal effect
car.x += (centerX - car.x) * moveSpeed;
}
car.moveForward();
// (Drifting line update removed)
// Keep car inside road bounds, unless carOutside is true
var roadCenter = 2048 / 2;
var roadWidth = 2400;
var carHalfW = car.width / 2;
// Always clamp car inside road bounds
if (car.x < roadCenter - roadWidth / 2 + carHalfW) {
car.x = roadCenter - roadWidth / 2 + carHalfW;
}
if (car.x > roadCenter + roadWidth / 2 - carHalfW) {
car.x = roadCenter + roadWidth / 2 - carHalfW;
}
// Move dotted lines down and handle despawn/spawn
for (var i = dottedLines.length - 1; i >= 0; --i) {
var dot = dottedLines[i];
dot.update(); // Move the dotted line at car.speed
// If offscreen at bottom, despawn and spawn a new one at the top
if (dot.y > 2732 + dottedLineHeight / 2) {
// Remove from game and array
dot.destroy();
dottedLines.splice(i, 1);
// Find minimum y among all remaining dots
var minY = Infinity;
for (var j = 0; j < dottedLines.length; ++j) {
if (dottedLines[j].y < minY) minY = dottedLines[j].y;
}
// Spawn new dot at top middle, spaced above the highest
var newDot = new DottedLine();
newDot.x = 2048 / 2;
// If there are no dots left, just place at top
if (dottedLines.length === 0) {
newDot.y = -dottedLineHeight / 2;
} else {
newDot.y = minY - dottedLineSpacing;
}
dottedLines.push(newDot);
game.addChild(newDot);
}
}
// Move coins down and check for collection
if (typeof coins === "undefined") coins = [];
for (var i = coins.length - 1; i >= 0; --i) {
var coin = coins[i];
coin.y += car.speed;
// Check for collection
if (!coin.collected && car.intersects(coin)) {
coin.collected = true;
LK.setScore(LK.getScore() + 10);
updateScore();
coin.destroy();
coins.splice(i, 1);
continue;
}
// Remove if offscreen
if (coin.y > 2732 + 80) {
coin.destroy();
coins.splice(i, 1);
}
}
// Move obstacles straight down
for (var i = obstacles.length - 1; i >= 0; --i) {
var obs = obstacles[i];
obs.y += car.speed;
// Check for collision
if (!gameOver && car.intersects(obs)) {
// Show crash asset in front of car
var crashAsset = LK.getAsset('Crash', {
anchorX: 0.5,
anchorY: 0.5,
x: car.x,
y: car.y - car.height * 0.35,
// slightly in front of car
scaleX: 3,
scaleY: 3
});
game.addChild(crashAsset);
LK.effects.flashScreen(0xff0000, 800);
gameOver = true;
LK.showGameOver();
return;
}
// Check if passed (obstacle is below car, and not already counted)
if (!obs.passed && obs.y > car.y) {
obs.passed = true;
passedObstacles += 1;
if (passedObstacles % 2 === 0) {
LK.setScore(LK.getScore() + 5);
updateScore();
}
// Speed up every N obstacles
if (passedObstacles % speedIncreaseEvery === 0 && car.speed < carMaxSpeed) {
car.speed += 2;
if (obstacleInterval > minObstacleInterval) {
obstacleInterval -= 6;
if (obstacleInterval < minObstacleInterval) obstacleInterval = minObstacleInterval;
}
}
// --- Night/Day cycle logic ---
var score = LK.getScore();
if (Math.floor(score / 250) !== Math.floor(lastNightDayScore / 250)) {
isNight = !isNight;
lastNightDayScore = score;
// Animate overlay alpha: 0.6 for night, 0 for day
tween.stop(nightOverlay, {
alpha: true
});
tween(nightOverlay, {
alpha: isNight ? 0.6 : 0
}, {
duration: 1200,
easing: tween.cubicInOut
});
}
// --- End Night/Day cycle logic ---
}
// Remove if offscreen
if (obs.y > 2732 + 120 || obs.x < 0 - 200 || obs.x > 2048 + 200) {
obs.destroy();
obstacles.splice(i, 1);
}
}
// Spawn new obstacles
ticksSinceLastObstacle += 1;
if (ticksSinceLastObstacle >= obstacleInterval) {
spawnObstacle();
ticksSinceLastObstacle = 0;
}
};
// Reset game on game over
game.on('reset', function () {
resetGame();
});
// Initial state
resetGame();
// --- Auto Button UI ---
var autoMode = false;
var autoBtn = new Text2('AUTO', {
size: 90,
fill: 0xFFFFFF,
fontWeight: "bold",
background: 0x008080
});
autoBtn.anchor.set(1, 1); // bottom right
autoBtn.x = LK.gui.width - 40;
autoBtn.y = LK.gui.height - 40;
autoBtn.alpha = 0.85;
autoBtn.interactive = true;
autoBtn.buttonMode = true;
LK.gui.bottomRight.addChild(autoBtn);
// Update button appearance
function updateAutoBtn() {
if (typeof autoBtn !== "undefined" && autoBtn && typeof autoBtn.setText === "function") {
autoBtn.setText(autoMode ? "AUTO ON" : "AUTO");
autoBtn.alpha = autoMode ? 1 : 0.85;
autoBtn.fill = autoMode ? "#00ff00" : "#ffffff";
}
}
updateAutoBtn();
// Toggle auto mode on button press
autoBtn.down = function (x, y, obj) {
autoMode = !autoMode;
updateAutoBtn();
};
// --- End Auto Button UI --- /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Car class
var Car = Container.expand(function () {
var self = Container.call(this);
// --- Car body (main) ---
var carBody = self.attachAsset('car', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.1
});
// --- Car detail elements removed ---
// Direction: 1 = up-right (45deg), -1 = up-left (135deg)
self.direction = 1;
self.speed = 32; // initial speed in px per tick
self.angleRight = Math.PI / 4; // 45deg (up-right)
self.angleLeft = 3 * Math.PI / 4; // 135deg (up-left)
// Only allow up-left and up-right, always moving up at an angle
self.setDirection = function (dir) {
// Only allow -1 (up-left) or 1 (up-right)
if (dir !== 1 && dir !== -1) return;
if (self.direction === dir) return; // Already in this direction, do nothing
// Animate rotation (body, roof, windows, lights, tires)
var targetRot = dir === 1 ? self.angleRight : self.angleLeft;
var allParts = [carBody];
for (var i = 0; i < allParts.length; ++i) {
tween(allParts[i], {
rotation: targetRot
}, {
duration: 120,
easing: tween.cubicOut
});
}
// Animate lane change (x position)
var roadLineCenter = 2048 / 2;
var roadLineWidth = 1200;
var obsMargin = 40;
var obsWidth = 160;
var carTravelEdge = roadLineWidth / 2 - obsWidth / 2 - obsMargin;
var targetX = roadLineCenter + dir * carTravelEdge;
// Stop any previous x tweens
tween.stop(self, {
x: true
});
tween(self, {
x: targetX
}, {
duration: 220,
easing: tween.cubicOut
});
self.direction = dir;
};
// Move car visually left/right (simulate up-left/up-right)
self.moveForward = function () {
// Car moves sideways along the gray road line based on direction
var roadLineCenter = 2048 / 2;
var roadLineWidth = 1200;
var roadWidth = 2400;
var carHalfW = self.width / 2;
var obsMargin = 40;
var obsWidth = 160;
var carTravelEdge = roadLineWidth / 2 - obsWidth / 2 - obsMargin;
// -1 = left, 1 = right
var targetX = roadLineCenter + self.direction * carTravelEdge;
// Smoothly move car toward targetX
var moveSpeed = 0.22;
self.x += (targetX - self.x) * moveSpeed;
// Make all car parts match obstacle's rotation (90deg, PI/2 radians)
var allParts = [carBody];
for (var i = 0; i < allParts.length; ++i) {
allParts[i].rotation = Math.PI / 2;
}
self.lastX = self.x;
self.lastX = self.x;
};
// Reset to center bottom
self.resetPosition = function () {
self.x = 2048 / 2;
self.y = 2732 - 350;
self.setDirection(1);
};
return self;
});
// Coin class
var Coin = Container.expand(function () {
var self = Container.call(this);
// Use a yellow ellipse for the coin
var coinAsset = self.attachAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.4,
scaleY: 1.4
});
coinAsset.color = 0xffd700; // gold/yellow
self.collected = false;
return self;
});
// White dotted line class
var DottedLine = Container.expand(function () {
var self = Container.call(this);
// Each dotted line is a white rectangle (box)
var dotWidth = 40;
var dotHeight = 100;
var dotAsset = self.attachAsset('dottedLine', {
anchorX: 0.5,
anchorY: 0.5
});
// Set size and color
dotAsset.width = dotWidth;
dotAsset.height = dotHeight;
dotAsset.color = 0xffffff;
// Center on road
self.x = 2048 / 2;
// y is set on spawn
self.update = function () {
// Move down at car speed
if (typeof car !== "undefined" && car && typeof car.speed === "number") {
self.y += car.speed;
}
// Remove if offscreen (handled in game.update)
};
return self;
});
// Obstacle class
var Obstacle = Container.expand(function () {
var self = Container.call(this);
// Randomly pick a color for the obstacle: green, blue, red, or black
var colors = [0x2ecc40, 0x1877f7, 0xd83318, 0x000000];
var colorIdx = Math.floor(Math.random() * colors.length);
// Use a box shape for the obstacle, colored accordingly
var obsAsset = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5
});
obsAsset.shape = 'box';
obsAsset.width = 320;
obsAsset.height = 200;
obsAsset.color = colors[colorIdx];
// Rotate 90 degrees (PI/2 radians) so the rectangle is horizontal
obsAsset.rotation = Math.PI / 2;
// side: -1 = left, 1 = right
self.side = 1;
self.passed = false;
// setSide is no longer needed, obstacles are placed directly on the gray line in spawnObstacle
// Set y position
self.setY = function (y) {
self.y = y;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
// Road
// Car: red box, 180x120
// Obstacle: yellow ellipse, 160x100
// Road: dark gray box, 2400x2732 (full height, 4x wide)
var road = LK.getAsset('road', {
anchorX: 0.5,
anchorY: 0,
x: 2048 / 2,
y: 0
});
road.width = 2400;
road.height = 2732;
game.addChild(road);
// Add a vertical gray line in the center of the road
var roadLine;
var roadLineAsset = LK.getAsset('roadLine', {
anchorX: 0.5,
anchorY: 0,
x: 2048 / 2,
y: 0
});
roadLineAsset.width = 1200;
roadLineAsset.height = 2732;
game.addChild(roadLineAsset);
// Car
// (Drifting line removed)
var car = new Car();
game.addChild(car);
car.resetPosition();
// Obstacles array
var obstacles = [];
// Score
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 1);
LK.gui.bottom.addChild(scoreTxt);
// Dotted lines (white, moving down the center of the road)
var dottedLines = [];
var dottedLineHeight = 100;
var dottedLineSpacing = 300;
var numDottedLines = Math.ceil(2732 / dottedLineSpacing) + 2; // enough to cover screen + buffer
for (var i = 0; i < numDottedLines; ++i) {
var dot = new DottedLine();
// Always spawn at the top middle, spaced downwards
dot.x = 2048 / 2;
dot.y = -dottedLineHeight / 2 + i * dottedLineSpacing;
dottedLines.push(dot);
game.addChild(dot);
}
// Game state
var gameOver = false;
var passedObstacles = 0;
var ticksSinceLastObstacle = 0;
var obstacleInterval = 45; // ticks between obstacles (start)
var minObstacleInterval = 18; // minimum interval (faster)
var speedIncreaseEvery = 10; // every 10 obstacles, speed up
var carBaseSpeed = 32;
var carMaxSpeed = 72;
// Night/Day cycle variables
var isNight = false;
var lastNightDayScore = 0;
// Overlay for night effect (covers the whole game area, fades in/out)
var nightOverlay = LK.getAsset('road', {
anchorX: 0.5,
anchorY: 0,
x: 2048 / 2,
y: 0
});
nightOverlay.width = 2400;
nightOverlay.height = 2732;
nightOverlay.color = 0x000000;
nightOverlay.alpha = 0; // Start transparent (day)
game.addChild(nightOverlay);
// Helper: spawn obstacle
function spawnObstacle() {
// Always spawn obstacles on the gray road line, not on the dotted line
// The gray road line is centered at 2048/2, width 1200 (4x)
var roadLineCenter = 2048 / 2;
var roadLineWidth = 1200;
var obsMargin = 40;
var obsWidth = 320;
// Move obstacles closer to the OUTER side of the road, but not touching the edge
// The outer edge for obstacle center is: roadLineCenter ± (roadLineWidth/2 - obsMargin - obsWidth/2)
var outerXOffset = roadLineWidth / 2 - obsMargin - obsWidth / 2;
// Randomly left or right of the center gray line
var side = Math.random() < 0.5 ? -1 : 1;
// Main obstacle: place as close as possible to the outer edge, but not touching
var obs1 = new Obstacle();
obs1.x = roadLineCenter + side * outerXOffset;
obs1.y = -100;
obstacles.push(obs1);
game.addChild(obs1);
// Second obstacle, spawn to the correct side based on lane, but ensure it doesn't touch the outer side of the road
var obs2 = new Obstacle();
var gap = 8; // gap between obstacles (original smaller gap)
// Calculate the max X for the second obstacle so it doesn't touch the outer side of the road
var roadOuterEdgeLeft = roadLineCenter - roadLineWidth / 2 + obsMargin + obsWidth / 2;
var roadOuterEdgeRight = roadLineCenter + roadLineWidth / 2 - obsMargin - obsWidth / 2;
if (side === 1) {
// Right lane: spawn another obstacle to the right, but clamp so it doesn't touch the outer side of the road
var intendedX = obs1.x - obsWidth - gap;
// If intendedX would be inside the road, use it, otherwise clamp to the minimum allowed (center line + margin + half width)
obs2.x = Math.max(intendedX, roadLineCenter + obsWidth / 2 + gap);
} else {
// Left lane: spawn another obstacle to the left, but clamp so it doesn't touch the outer side of the road
var intendedX = obs1.x + obsWidth + gap;
obs2.x = Math.min(intendedX, roadLineCenter - obsWidth / 2 - gap);
}
obs2.y = -100;
obstacles.push(obs2);
game.addChild(obs2);
// Place coins further in front of each obstacle (not between obstacles)
var coinDistance = 340; // px in front of obstacle (increased distance)
var coin1 = new Coin();
coin1.x = obs1.x;
coin1.y = obs1.y + coinDistance;
game.addChild(coin1);
if (typeof coins === "undefined") coins = [];
coins.push(coin1);
var coin2 = new Coin();
coin2.x = obs2.x;
coin2.y = obs2.y + coinDistance;
game.addChild(coin2);
coins.push(coin2);
}
// Helper: update score
function updateScore() {
scoreTxt.setText(LK.getScore());
}
// Helper: reset game state
function resetGame() {
// Remove all obstacles
for (var i = 0; i < obstacles.length; ++i) {
obstacles[i].destroy();
}
obstacles = [];
// Remove all coins
if (typeof coins !== "undefined") {
for (var i = 0; i < coins.length; ++i) {
coins[i].destroy();
}
}
coins = [];
car.resetPosition();
car.speed = carBaseSpeed;
obstacleInterval = 90;
passedObstacles = 0;
ticksSinceLastObstacle = 0;
// Reset dotted lines to initial positions
for (var i = 0; i < dottedLines.length; ++i) {
dottedLines[i].x = 2048 / 2;
dottedLines[i].y = -dottedLineHeight / 2 + i * 300;
}
LK.setScore(0);
updateScore();
gameOver = false;
autoMode = false;
if (typeof updateAutoBtn === "function" && typeof autoBtn !== "undefined") updateAutoBtn();
// Reset night/day cycle
isNight = false;
lastNightDayScore = 0;
tween.stop(nightOverlay, {
alpha: true
});
nightOverlay.alpha = 0;
}
// Touch/tap: (no car outside logic)
game.down = function (x, y, obj) {
if (gameOver) return;
if (autoMode) return;
// Tap to switch lane: flip car direction
car.setDirection(-car.direction);
};
// Main game loop
game.update = function () {
if (gameOver) return;
// Move car
if (autoMode && !gameOver) {
// Find the closest obstacle ahead of the car
var closestObs = null;
var minDist = Infinity;
for (var i = 0; i < obstacles.length; ++i) {
var obs = obstacles[i];
// Only consider obstacles in front of the car and within a certain Y range
if (obs.y > car.y - 200 && obs.y < car.y + 400) {
var dist = Math.abs(obs.y - car.y);
if (dist < minDist) {
minDist = dist;
closestObs = obs;
}
}
}
if (closestObs) {
// If the obstacle is on the same side as the car, switch direction
var carSide = car.x > 2048 / 2 ? 1 : -1;
var obsSide = closestObs.x > 2048 / 2 ? 1 : -1;
if (carSide === obsSide) {
car.setDirection(-car.direction);
}
}
}
// If not autoMode, move car toward center X (normal centering, not wide)
if (!autoMode && !gameOver) {
var centerX = 2048 / 2;
var moveSpeed = 0.12; // slightly slower for normal effect
car.x += (centerX - car.x) * moveSpeed;
}
car.moveForward();
// (Drifting line update removed)
// Keep car inside road bounds, unless carOutside is true
var roadCenter = 2048 / 2;
var roadWidth = 2400;
var carHalfW = car.width / 2;
// Always clamp car inside road bounds
if (car.x < roadCenter - roadWidth / 2 + carHalfW) {
car.x = roadCenter - roadWidth / 2 + carHalfW;
}
if (car.x > roadCenter + roadWidth / 2 - carHalfW) {
car.x = roadCenter + roadWidth / 2 - carHalfW;
}
// Move dotted lines down and handle despawn/spawn
for (var i = dottedLines.length - 1; i >= 0; --i) {
var dot = dottedLines[i];
dot.update(); // Move the dotted line at car.speed
// If offscreen at bottom, despawn and spawn a new one at the top
if (dot.y > 2732 + dottedLineHeight / 2) {
// Remove from game and array
dot.destroy();
dottedLines.splice(i, 1);
// Find minimum y among all remaining dots
var minY = Infinity;
for (var j = 0; j < dottedLines.length; ++j) {
if (dottedLines[j].y < minY) minY = dottedLines[j].y;
}
// Spawn new dot at top middle, spaced above the highest
var newDot = new DottedLine();
newDot.x = 2048 / 2;
// If there are no dots left, just place at top
if (dottedLines.length === 0) {
newDot.y = -dottedLineHeight / 2;
} else {
newDot.y = minY - dottedLineSpacing;
}
dottedLines.push(newDot);
game.addChild(newDot);
}
}
// Move coins down and check for collection
if (typeof coins === "undefined") coins = [];
for (var i = coins.length - 1; i >= 0; --i) {
var coin = coins[i];
coin.y += car.speed;
// Check for collection
if (!coin.collected && car.intersects(coin)) {
coin.collected = true;
LK.setScore(LK.getScore() + 10);
updateScore();
coin.destroy();
coins.splice(i, 1);
continue;
}
// Remove if offscreen
if (coin.y > 2732 + 80) {
coin.destroy();
coins.splice(i, 1);
}
}
// Move obstacles straight down
for (var i = obstacles.length - 1; i >= 0; --i) {
var obs = obstacles[i];
obs.y += car.speed;
// Check for collision
if (!gameOver && car.intersects(obs)) {
// Show crash asset in front of car
var crashAsset = LK.getAsset('Crash', {
anchorX: 0.5,
anchorY: 0.5,
x: car.x,
y: car.y - car.height * 0.35,
// slightly in front of car
scaleX: 3,
scaleY: 3
});
game.addChild(crashAsset);
LK.effects.flashScreen(0xff0000, 800);
gameOver = true;
LK.showGameOver();
return;
}
// Check if passed (obstacle is below car, and not already counted)
if (!obs.passed && obs.y > car.y) {
obs.passed = true;
passedObstacles += 1;
if (passedObstacles % 2 === 0) {
LK.setScore(LK.getScore() + 5);
updateScore();
}
// Speed up every N obstacles
if (passedObstacles % speedIncreaseEvery === 0 && car.speed < carMaxSpeed) {
car.speed += 2;
if (obstacleInterval > minObstacleInterval) {
obstacleInterval -= 6;
if (obstacleInterval < minObstacleInterval) obstacleInterval = minObstacleInterval;
}
}
// --- Night/Day cycle logic ---
var score = LK.getScore();
if (Math.floor(score / 250) !== Math.floor(lastNightDayScore / 250)) {
isNight = !isNight;
lastNightDayScore = score;
// Animate overlay alpha: 0.6 for night, 0 for day
tween.stop(nightOverlay, {
alpha: true
});
tween(nightOverlay, {
alpha: isNight ? 0.6 : 0
}, {
duration: 1200,
easing: tween.cubicInOut
});
}
// --- End Night/Day cycle logic ---
}
// Remove if offscreen
if (obs.y > 2732 + 120 || obs.x < 0 - 200 || obs.x > 2048 + 200) {
obs.destroy();
obstacles.splice(i, 1);
}
}
// Spawn new obstacles
ticksSinceLastObstacle += 1;
if (ticksSinceLastObstacle >= obstacleInterval) {
spawnObstacle();
ticksSinceLastObstacle = 0;
}
};
// Reset game on game over
game.on('reset', function () {
resetGame();
});
// Initial state
resetGame();
// --- Auto Button UI ---
var autoMode = false;
var autoBtn = new Text2('AUTO', {
size: 90,
fill: 0xFFFFFF,
fontWeight: "bold",
background: 0x008080
});
autoBtn.anchor.set(1, 1); // bottom right
autoBtn.x = LK.gui.width - 40;
autoBtn.y = LK.gui.height - 40;
autoBtn.alpha = 0.85;
autoBtn.interactive = true;
autoBtn.buttonMode = true;
LK.gui.bottomRight.addChild(autoBtn);
// Update button appearance
function updateAutoBtn() {
if (typeof autoBtn !== "undefined" && autoBtn && typeof autoBtn.setText === "function") {
autoBtn.setText(autoMode ? "AUTO ON" : "AUTO");
autoBtn.alpha = autoMode ? 1 : 0.85;
autoBtn.fill = autoMode ? "#00ff00" : "#ffffff";
}
}
updateAutoBtn();
// Toggle auto mode on button press
autoBtn.down = function (x, y, obj) {
autoMode = !autoMode;
updateAutoBtn();
};
// --- End Auto Button UI ---
2d red car. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat,upper profile
A truck,2d,upper profile. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat.upper looking
A car crash effect,red,yellow and orange colors,put the outer side black line. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
Coin with 10 on it . No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat