/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Boost class var Boost = Container.expand(function () { var self = Container.call(this); var boostAsset = self.attachAsset('boost', { anchorX: 0.5, anchorY: 0.5 }); self.width = boostAsset.width; self.height = boostAsset.height; self.update = function () { self.y += roadSpeed; }; return self; }); // Car class var Car = Container.expand(function () { var self = Container.call(this); var carAsset = self.attachAsset('car', { anchorX: 0.5, anchorY: 0.5 }); // For collision, use the container itself self.width = carAsset.width; self.height = carAsset.height; // Car speed (pixels per tick) self.speed = 10; // Lateral speed (for smooth steering) self.lateralSpeed = 0; // Max lateral speed self.maxLateralSpeed = 32; // Steering target (x position) self.targetX = self.x; // Update method self.update = function () { // Smoothly move towards targetX var dx = self.targetX - self.x; if (Math.abs(dx) > 2) { self.lateralSpeed = Math.max(-self.maxLateralSpeed, Math.min(self.maxLateralSpeed, dx * 0.25)); self.x += self.lateralSpeed; } else { self.lateralSpeed = 0; self.x = self.targetX; } }; return self; }); // Lane marker class var Lane = Container.expand(function () { var self = Container.call(this); var laneAsset = self.attachAsset('lane', { anchorX: 0.5, anchorY: 0.5 }); self.width = laneAsset.width; self.height = laneAsset.height; self.update = function () { self.y += roadSpeed; }; return self; }); // Obstacle class (default car) 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; // Update method: move down by road speed self.update = function () { // Track lastY for scoring if (self.lastY === undefined) self.lastY = self.y; self.y += roadSpeed; // No score for passing obstacles; scoring is handled by collecting boosts. self.lastY = self.y; }; return self; }); // Motorcycle obstacle var ObstacleMotorcycle = Container.expand(function () { var self = Container.call(this); var obsAsset = self.attachAsset('obstacle_motorcycle', { anchorX: 0.5, anchorY: 0.5 }); self.width = obsAsset.width; self.height = obsAsset.height; self.update = function () { if (self.lastY === undefined) self.lastY = self.y; self.y += roadSpeed; self.lastY = self.y; }; return self; }); // Sports car obstacle var ObstacleSportsCar = Container.expand(function () { var self = Container.call(this); var obsAsset = self.attachAsset('obstacle_sportscar', { anchorX: 0.5, anchorY: 0.5 }); self.width = obsAsset.width; self.height = obsAsset.height; self.update = function () { if (self.lastY === undefined) self.lastY = self.y; self.y += roadSpeed; self.lastY = self.y; }; return self; }); // Truck obstacle var ObstacleTruck = Container.expand(function () { var self = Container.call(this); var obsAsset = self.attachAsset('obstacle_truck', { anchorX: 0.5, anchorY: 0.5 }); self.width = obsAsset.width; self.height = obsAsset.height; self.update = function () { if (self.lastY === undefined) self.lastY = self.y; self.y += roadSpeed; self.lastY = self.y; }; return self; }); // Van obstacle var ObstacleVan = Container.expand(function () { var self = Container.call(this); var obsAsset = self.attachAsset('obstacle_van', { anchorX: 0.5, anchorY: 0.5 }); self.width = obsAsset.width; self.height = obsAsset.height; self.update = function () { if (self.lastY === undefined) self.lastY = self.y; self.y += roadSpeed; self.lastY = self.y; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x222222 }); /**** * Game Code ****/ // Game constants // Car (player) // Obstacle // Speed boost // Road // Lane marker var GAME_WIDTH = 2048; var GAME_HEIGHT = 2732; var ROAD_WIDTH = 900; var ROAD_X = (GAME_WIDTH - ROAD_WIDTH) / 2; var ROAD_Y = 0; var LANE_COUNT = 3; var LANE_WIDTH = ROAD_WIDTH / LANE_COUNT; var CAR_START_X = GAME_WIDTH / 2; var CAR_START_Y = GAME_HEIGHT - 500; var OBSTACLE_MIN_GAP = 600; var OBSTACLE_MAX_GAP = 1100; var BOOST_CHANCE = 0.25; // 25% chance to spawn a boost with an obstacle // Road speed (pixels per tick) var roadSpeed = 10; var roadSpeedBase = 10; var roadSpeedBoosted = 24; var boostDuration = 90; // ticks (1.5 seconds) var boostTicks = 0; // Score var score = 0; // Arrays for game objects var obstacles = []; var boosts = []; var lanes = []; // Road background var road = LK.getAsset('road', { anchorX: 0, anchorY: 0, x: ROAD_X, y: ROAD_Y }); game.addChild(road); // Lane markers function spawnLaneMarkers() { // Remove old lanes for (var i = 0; i < lanes.length; i++) { lanes[i].destroy(); } lanes = []; // For each lane (except the leftmost), draw a marker for (var l = 1; l < LANE_COUNT; l++) { for (var y = -200; y < GAME_HEIGHT + 200; y += 400) { var lane = new Lane(); lane.x = ROAD_X + l * LANE_WIDTH; lane.y = y; lanes.push(lane); game.addChild(lane); } } } spawnLaneMarkers(); // Car (player) var car = new Car(); car.x = CAR_START_X; car.y = CAR_START_Y; car.targetX = car.x; game.addChild(car); // Score text var scoreTxt = new Text2('0', { size: 120, fill: "#fff" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Helper: get lane center x function getLaneCenter(laneIdx) { return ROAD_X + LANE_WIDTH * (laneIdx + 0.5); } // Helper: spawn an obstacle (and maybe a boost) function spawnObstacle() { // Random lane var laneIdx = Math.floor(Math.random() * LANE_COUNT); // Randomly select one of five obstacle types var type = Math.floor(Math.random() * 5); var obs; if (type === 0) { obs = new Obstacle(); // default car } else if (type === 1) { obs = new ObstacleTruck(); } else if (type === 2) { obs = new ObstacleMotorcycle(); } else if (type === 3) { obs = new ObstacleSportsCar(); } else { obs = new ObstacleVan(); } obs.x = getLaneCenter(laneIdx); obs.y = -obs.height / 2; obstacles.push(obs); game.addChild(obs); // Maybe spawn a boost in a different lane if (Math.random() < BOOST_CHANCE) { var boostLane = laneIdx; // Try to pick a different lane if (LANE_COUNT > 1) { while (boostLane === laneIdx) { boostLane = Math.floor(Math.random() * LANE_COUNT); } } var boost = new Boost(); boost.x = getLaneCenter(boostLane); boost.y = obs.y; boosts.push(boost); game.addChild(boost); } } // Helper: spawn lane markers as they scroll function updateLaneMarkers() { for (var i = lanes.length - 1; i >= 0; i--) { var lane = lanes[i]; lane.update(); if (lane.y > GAME_HEIGHT + 200) { // Recycle to top lane.y -= GAME_HEIGHT + 400; } } } // Dragging var dragging = false; var dragOffsetX = 0; // Touch/mouse controls game.down = function (x, y, obj) { // Only allow drag if touch is on car or on road if (x > ROAD_X && x < ROAD_X + ROAD_WIDTH && y > 0 && y < GAME_HEIGHT) { dragging = true; dragOffsetX = x - car.x; // Set targetX immediately car.targetX = Math.max(ROAD_X + car.width / 2, Math.min(ROAD_X + ROAD_WIDTH - car.width / 2, x - dragOffsetX)); } }; game.up = function (x, y, obj) { dragging = false; }; game.move = function (x, y, obj) { if (dragging) { // Clamp to road var tx = Math.max(ROAD_X + car.width / 2, Math.min(ROAD_X + ROAD_WIDTH - car.width / 2, x - dragOffsetX)); car.targetX = tx; } }; // Game update var lastObstacleY = -OBSTACLE_MIN_GAP; game.update = function () { // Update car car.update(); // Update lane markers updateLaneMarkers(); // Update obstacles for (var i = obstacles.length - 1; i >= 0; i--) { var obs = obstacles[i]; obs.update(); // Remove if off screen if (obs.y - obs.height / 2 > GAME_HEIGHT + 100) { obs.destroy(); obstacles.splice(i, 1); continue; } // Collision with car if (car.intersects(obs)) { // Flash screen red, game over LK.effects.flashScreen(0xff0000, 800); LK.showGameOver(); return; } } // Update boosts for (var i = boosts.length - 1; i >= 0; i--) { var boost = boosts[i]; boost.update(); // Remove if off screen if (boost.y - boost.height / 2 > GAME_HEIGHT + 100) { boost.destroy(); boosts.splice(i, 1); continue; } // Collect boost if (car.intersects(boost)) { boost.destroy(); boosts.splice(i, 1); // Add score for collecting boost score += 100; scoreTxt.setText(score); // Flash car yellow LK.effects.flashObject(car, 0xffeb3b, 400); } } // Handle boost timer if (boostTicks > 0) { boostTicks--; if (boostTicks === 0) { roadSpeed = roadSpeedBase; } } // Spawn new obstacles if (obstacles.length === 0 || obstacles[obstacles.length - 1].y > OBSTACLE_MIN_GAP + Math.random() * (OBSTACLE_MAX_GAP - OBSTACLE_MIN_GAP)) { spawnObstacle(); } // Score is only updated by collecting boosts. // Check if car is off the road if (car.x - car.width / 2 < ROAD_X || car.x + car.width / 2 > ROAD_X + ROAD_WIDTH) { LK.effects.flashScreen(0xff0000, 800); LK.showGameOver(); return; } }; // Reset game state on new game LK.on('gameStart', function () { // Remove all obstacles and boosts for (var i = 0; i < obstacles.length; i++) obstacles[i].destroy(); for (var i = 0; i < boosts.length; i++) boosts[i].destroy(); obstacles = []; boosts = []; // Reset car position car.x = CAR_START_X; car.y = CAR_START_Y; car.targetX = car.x; // Reset score and speed score = 0; scoreTxt.setText(score); roadSpeed = roadSpeedBase; boostTicks = 0; // Reset lane markers spawnLaneMarkers(); });
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Boost class
var Boost = Container.expand(function () {
var self = Container.call(this);
var boostAsset = self.attachAsset('boost', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = boostAsset.width;
self.height = boostAsset.height;
self.update = function () {
self.y += roadSpeed;
};
return self;
});
// Car class
var Car = Container.expand(function () {
var self = Container.call(this);
var carAsset = self.attachAsset('car', {
anchorX: 0.5,
anchorY: 0.5
});
// For collision, use the container itself
self.width = carAsset.width;
self.height = carAsset.height;
// Car speed (pixels per tick)
self.speed = 10;
// Lateral speed (for smooth steering)
self.lateralSpeed = 0;
// Max lateral speed
self.maxLateralSpeed = 32;
// Steering target (x position)
self.targetX = self.x;
// Update method
self.update = function () {
// Smoothly move towards targetX
var dx = self.targetX - self.x;
if (Math.abs(dx) > 2) {
self.lateralSpeed = Math.max(-self.maxLateralSpeed, Math.min(self.maxLateralSpeed, dx * 0.25));
self.x += self.lateralSpeed;
} else {
self.lateralSpeed = 0;
self.x = self.targetX;
}
};
return self;
});
// Lane marker class
var Lane = Container.expand(function () {
var self = Container.call(this);
var laneAsset = self.attachAsset('lane', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = laneAsset.width;
self.height = laneAsset.height;
self.update = function () {
self.y += roadSpeed;
};
return self;
});
// Obstacle class (default car)
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;
// Update method: move down by road speed
self.update = function () {
// Track lastY for scoring
if (self.lastY === undefined) self.lastY = self.y;
self.y += roadSpeed;
// No score for passing obstacles; scoring is handled by collecting boosts.
self.lastY = self.y;
};
return self;
});
// Motorcycle obstacle
var ObstacleMotorcycle = Container.expand(function () {
var self = Container.call(this);
var obsAsset = self.attachAsset('obstacle_motorcycle', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = obsAsset.width;
self.height = obsAsset.height;
self.update = function () {
if (self.lastY === undefined) self.lastY = self.y;
self.y += roadSpeed;
self.lastY = self.y;
};
return self;
});
// Sports car obstacle
var ObstacleSportsCar = Container.expand(function () {
var self = Container.call(this);
var obsAsset = self.attachAsset('obstacle_sportscar', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = obsAsset.width;
self.height = obsAsset.height;
self.update = function () {
if (self.lastY === undefined) self.lastY = self.y;
self.y += roadSpeed;
self.lastY = self.y;
};
return self;
});
// Truck obstacle
var ObstacleTruck = Container.expand(function () {
var self = Container.call(this);
var obsAsset = self.attachAsset('obstacle_truck', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = obsAsset.width;
self.height = obsAsset.height;
self.update = function () {
if (self.lastY === undefined) self.lastY = self.y;
self.y += roadSpeed;
self.lastY = self.y;
};
return self;
});
// Van obstacle
var ObstacleVan = Container.expand(function () {
var self = Container.call(this);
var obsAsset = self.attachAsset('obstacle_van', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = obsAsset.width;
self.height = obsAsset.height;
self.update = function () {
if (self.lastY === undefined) self.lastY = self.y;
self.y += roadSpeed;
self.lastY = self.y;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
// Game constants
// Car (player)
// Obstacle
// Speed boost
// Road
// Lane marker
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var ROAD_WIDTH = 900;
var ROAD_X = (GAME_WIDTH - ROAD_WIDTH) / 2;
var ROAD_Y = 0;
var LANE_COUNT = 3;
var LANE_WIDTH = ROAD_WIDTH / LANE_COUNT;
var CAR_START_X = GAME_WIDTH / 2;
var CAR_START_Y = GAME_HEIGHT - 500;
var OBSTACLE_MIN_GAP = 600;
var OBSTACLE_MAX_GAP = 1100;
var BOOST_CHANCE = 0.25; // 25% chance to spawn a boost with an obstacle
// Road speed (pixels per tick)
var roadSpeed = 10;
var roadSpeedBase = 10;
var roadSpeedBoosted = 24;
var boostDuration = 90; // ticks (1.5 seconds)
var boostTicks = 0;
// Score
var score = 0;
// Arrays for game objects
var obstacles = [];
var boosts = [];
var lanes = [];
// Road background
var road = LK.getAsset('road', {
anchorX: 0,
anchorY: 0,
x: ROAD_X,
y: ROAD_Y
});
game.addChild(road);
// Lane markers
function spawnLaneMarkers() {
// Remove old lanes
for (var i = 0; i < lanes.length; i++) {
lanes[i].destroy();
}
lanes = [];
// For each lane (except the leftmost), draw a marker
for (var l = 1; l < LANE_COUNT; l++) {
for (var y = -200; y < GAME_HEIGHT + 200; y += 400) {
var lane = new Lane();
lane.x = ROAD_X + l * LANE_WIDTH;
lane.y = y;
lanes.push(lane);
game.addChild(lane);
}
}
}
spawnLaneMarkers();
// Car (player)
var car = new Car();
car.x = CAR_START_X;
car.y = CAR_START_Y;
car.targetX = car.x;
game.addChild(car);
// Score text
var scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Helper: get lane center x
function getLaneCenter(laneIdx) {
return ROAD_X + LANE_WIDTH * (laneIdx + 0.5);
}
// Helper: spawn an obstacle (and maybe a boost)
function spawnObstacle() {
// Random lane
var laneIdx = Math.floor(Math.random() * LANE_COUNT);
// Randomly select one of five obstacle types
var type = Math.floor(Math.random() * 5);
var obs;
if (type === 0) {
obs = new Obstacle(); // default car
} else if (type === 1) {
obs = new ObstacleTruck();
} else if (type === 2) {
obs = new ObstacleMotorcycle();
} else if (type === 3) {
obs = new ObstacleSportsCar();
} else {
obs = new ObstacleVan();
}
obs.x = getLaneCenter(laneIdx);
obs.y = -obs.height / 2;
obstacles.push(obs);
game.addChild(obs);
// Maybe spawn a boost in a different lane
if (Math.random() < BOOST_CHANCE) {
var boostLane = laneIdx;
// Try to pick a different lane
if (LANE_COUNT > 1) {
while (boostLane === laneIdx) {
boostLane = Math.floor(Math.random() * LANE_COUNT);
}
}
var boost = new Boost();
boost.x = getLaneCenter(boostLane);
boost.y = obs.y;
boosts.push(boost);
game.addChild(boost);
}
}
// Helper: spawn lane markers as they scroll
function updateLaneMarkers() {
for (var i = lanes.length - 1; i >= 0; i--) {
var lane = lanes[i];
lane.update();
if (lane.y > GAME_HEIGHT + 200) {
// Recycle to top
lane.y -= GAME_HEIGHT + 400;
}
}
}
// Dragging
var dragging = false;
var dragOffsetX = 0;
// Touch/mouse controls
game.down = function (x, y, obj) {
// Only allow drag if touch is on car or on road
if (x > ROAD_X && x < ROAD_X + ROAD_WIDTH && y > 0 && y < GAME_HEIGHT) {
dragging = true;
dragOffsetX = x - car.x;
// Set targetX immediately
car.targetX = Math.max(ROAD_X + car.width / 2, Math.min(ROAD_X + ROAD_WIDTH - car.width / 2, x - dragOffsetX));
}
};
game.up = function (x, y, obj) {
dragging = false;
};
game.move = function (x, y, obj) {
if (dragging) {
// Clamp to road
var tx = Math.max(ROAD_X + car.width / 2, Math.min(ROAD_X + ROAD_WIDTH - car.width / 2, x - dragOffsetX));
car.targetX = tx;
}
};
// Game update
var lastObstacleY = -OBSTACLE_MIN_GAP;
game.update = function () {
// Update car
car.update();
// Update lane markers
updateLaneMarkers();
// Update obstacles
for (var i = obstacles.length - 1; i >= 0; i--) {
var obs = obstacles[i];
obs.update();
// Remove if off screen
if (obs.y - obs.height / 2 > GAME_HEIGHT + 100) {
obs.destroy();
obstacles.splice(i, 1);
continue;
}
// Collision with car
if (car.intersects(obs)) {
// Flash screen red, game over
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
return;
}
}
// Update boosts
for (var i = boosts.length - 1; i >= 0; i--) {
var boost = boosts[i];
boost.update();
// Remove if off screen
if (boost.y - boost.height / 2 > GAME_HEIGHT + 100) {
boost.destroy();
boosts.splice(i, 1);
continue;
}
// Collect boost
if (car.intersects(boost)) {
boost.destroy();
boosts.splice(i, 1);
// Add score for collecting boost
score += 100;
scoreTxt.setText(score);
// Flash car yellow
LK.effects.flashObject(car, 0xffeb3b, 400);
}
}
// Handle boost timer
if (boostTicks > 0) {
boostTicks--;
if (boostTicks === 0) {
roadSpeed = roadSpeedBase;
}
}
// Spawn new obstacles
if (obstacles.length === 0 || obstacles[obstacles.length - 1].y > OBSTACLE_MIN_GAP + Math.random() * (OBSTACLE_MAX_GAP - OBSTACLE_MIN_GAP)) {
spawnObstacle();
}
// Score is only updated by collecting boosts.
// Check if car is off the road
if (car.x - car.width / 2 < ROAD_X || car.x + car.width / 2 > ROAD_X + ROAD_WIDTH) {
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
return;
}
};
// Reset game state on new game
LK.on('gameStart', function () {
// Remove all obstacles and boosts
for (var i = 0; i < obstacles.length; i++) obstacles[i].destroy();
for (var i = 0; i < boosts.length; i++) boosts[i].destroy();
obstacles = [];
boosts = [];
// Reset car position
car.x = CAR_START_X;
car.y = CAR_START_Y;
car.targetX = car.x;
// Reset score and speed
score = 0;
scoreTxt.setText(score);
roadSpeed = roadSpeedBase;
boostTicks = 0;
// Reset lane markers
spawnLaneMarkers();
});