/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0,
coins: 0,
carSpeed: 1,
carHandling: 1
});
/****
* Classes
****/
var Coin = Container.expand(function () {
var self = Container.call(this);
var coinGraphics = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0;
self.width = coinGraphics.width;
self.height = coinGraphics.height;
// Add a little rotation animation
self.update = function () {
self.y += self.speed;
coinGraphics.rotation += 0.05;
// Reset with random position when coin goes off screen
if (self.y > 2732 + self.height) {
self.visible = false;
self.needsReset = true;
}
};
return self;
});
var EnemyCar = Container.expand(function () {
var self = Container.call(this);
var carGraphics = self.attachAsset('enemy_car', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0;
self.width = carGraphics.width;
self.height = carGraphics.height;
self.update = function () {
// Store original position if not set yet
if (self.startX === undefined) {
self.startX = self.x;
self.angle = Math.random() * Math.PI * 2; // Random starting angle
self.radius = Math.random() * 150 + 50; // Random radius between 50-200
self.rotationSpeed = (Math.random() > 0.5 ? 1 : -1) * (Math.random() * 0.03 + 0.01); // Random speed and direction
}
// Move down
self.y += self.speed;
// Apply spiral motion
self.angle += self.rotationSpeed;
self.x = self.startX + Math.cos(self.angle) * self.radius;
// Reset with random position when car goes off screen
if (self.y > 2732 + self.height) {
self.visible = false;
self.needsReset = true;
// Clear spiral motion variables for reset
self.startX = undefined;
self.angle = undefined;
self.radius = undefined;
self.rotationSpeed = undefined;
}
};
return self;
});
var InvincibilityPowerup = Container.expand(function () {
var self = Container.call(this);
var shieldGraphics = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5
});
// Change tint to purple for shield powerup
shieldGraphics.tint = 0x8A2BE2;
self.speed = 0;
self.width = shieldGraphics.width;
self.height = shieldGraphics.height;
// Add a rotation animation
self.update = function () {
self.y += self.speed;
shieldGraphics.rotation -= 0.08;
// Reset with random position when powerup goes off screen
if (self.y > 2732 + self.height) {
self.visible = false;
self.needsReset = true;
}
};
return self;
});
var Obstacle = Container.expand(function () {
var self = Container.call(this);
var obstacleGraphics = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0;
self.width = obstacleGraphics.width;
self.height = obstacleGraphics.height;
self.update = function () {
self.y += self.speed;
// Reset with random position when obstacle goes off screen
if (self.y > 2732 + self.height) {
self.visible = false;
self.needsReset = true;
}
};
return self;
});
var PlayerCar = Container.expand(function () {
var self = Container.call(this);
var carGraphics = self.attachAsset('player_car', {
anchorX: 0.5,
anchorY: 0.5
});
self.targetX = 0;
self.width = carGraphics.width;
self.height = carGraphics.height;
self.speed = 10 + storage.carSpeed * 2;
self.handling = 0.1 + storage.carHandling * 0.03;
self.update = function () {
// Move car toward target position with smooth handling
self.x += (self.targetX - self.x) * self.handling;
};
return self;
});
var RoadLine = Container.expand(function () {
var self = Container.call(this);
var lineGraphics = self.attachAsset('road_line', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0;
self.update = function () {
self.y += self.speed;
// Reset position when line goes off screen
if (self.y > 2732 + lineGraphics.height) {
self.y = -lineGraphics.height;
}
};
return self;
});
var SpeedBoost = Container.expand(function () {
var self = Container.call(this);
var boostGraphics = self.attachAsset('speedup', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0;
self.width = boostGraphics.width;
self.height = boostGraphics.height;
// Add a pulsing animation
var pulseUp = true;
self.update = function () {
self.y += self.speed;
// Pulse animation
if (pulseUp) {
boostGraphics.scale.x += 0.01;
boostGraphics.scale.y += 0.01;
if (boostGraphics.scale.x >= 1.2) {
pulseUp = false;
}
} else {
boostGraphics.scale.x -= 0.01;
boostGraphics.scale.y -= 0.01;
if (boostGraphics.scale.x <= 0.8) {
pulseUp = true;
}
}
// Reset with random position when boost goes off screen
if (self.y > 2732 + self.height) {
self.visible = false;
self.needsReset = true;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
// Game states
var STATE_READY = 0;
var STATE_PLAYING = 1;
var STATE_GAME_OVER = 2;
var gameState = STATE_READY;
// Game area constants
var ROAD_WIDTH = 1600;
var ROAD_LEFT = (2048 - ROAD_WIDTH) / 2;
var ROAD_RIGHT = ROAD_LEFT + ROAD_WIDTH;
var LANES = 4;
var LANE_WIDTH = ROAD_WIDTH / LANES;
// Game variables
var score = 0;
var coinsCollected = 0;
var gameSpeed = 8;
var baseSpeed = 8;
var speedMultiplier = 1;
var spawnRate = 0.02; // Chance to spawn an object each frame
var difficultyTimer = 0;
var boostTime = 0;
var invincibilityTime = 0;
var isInvincible = false;
// Create road background
var road = LK.getAsset('road', {
anchorX: 0.5,
anchorY: 0
});
road.x = 2048 / 2;
road.y = 0;
game.addChild(road);
// Create road lines
var roadLines = [];
var totalLines = 20;
var lineSpacing = 2732 / totalLines;
for (var i = 0; i < totalLines; i++) {
var line = new RoadLine();
line.x = 2048 / 2;
line.y = i * lineSpacing;
roadLines.push(line);
game.addChild(line);
}
// Create player car
var player = new PlayerCar();
player.x = 2048 / 2;
player.y = 2732 - 300;
game.addChild(player);
// Create enemy cars pool
var enemyCars = [];
var MAX_ENEMY_CARS = 10;
for (var i = 0; i < MAX_ENEMY_CARS; i++) {
var car = new EnemyCar();
car.visible = false;
car.needsReset = true;
enemyCars.push(car);
game.addChild(car);
}
// Create coins pool
var coins = [];
var MAX_COINS = 10;
for (var i = 0; i < MAX_COINS; i++) {
var coin = new Coin();
coin.visible = false;
coin.needsReset = true;
coins.push(coin);
game.addChild(coin);
}
// Create obstacles pool
var obstacles = [];
var MAX_OBSTACLES = 5;
for (var i = 0; i < MAX_OBSTACLES; i++) {
var obstacle = new Obstacle();
obstacle.visible = false;
obstacle.needsReset = true;
obstacles.push(obstacle);
game.addChild(obstacle);
}
// Create speed boosts pool
var speedBoosts = [];
var MAX_SPEED_BOOSTS = 3;
for (var i = 0; i < MAX_SPEED_BOOSTS; i++) {
var boost = new SpeedBoost();
boost.visible = false;
boost.needsReset = true;
speedBoosts.push(boost);
game.addChild(boost);
}
// Create shield powerups pool
var shieldPowerups = [];
var MAX_SHIELD_POWERUPS = 2;
for (var i = 0; i < MAX_SHIELD_POWERUPS; i++) {
var shield = new InvincibilityPowerup();
shield.visible = false;
shield.needsReset = true;
shieldPowerups.push(shield);
game.addChild(shield);
}
// Setup UI
var scoreTxt = new Text2('DISTANCE: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0, 0);
LK.gui.topRight.addChild(scoreTxt);
var coinsTxt = new Text2('COINS: 0', {
size: 60,
fill: 0xFFDD00
});
coinsTxt.anchor.set(1, 0);
LK.gui.top.addChild(coinsTxt);
var highScoreTxt = new Text2('BEST: ' + storage.highScore, {
size: 40,
fill: 0xAAAAAA
});
highScoreTxt.anchor.set(0, 1);
LK.gui.topRight.addChild(highScoreTxt);
var startTxt = new Text2('TAP TO START', {
size: 80,
fill: 0xFFFFFF
});
startTxt.anchor.set(0.5, 0.5);
LK.gui.center.addChild(startTxt);
// Create lane indicators
var laneIndicators = [];
for (var i = 0; i < LANES; i++) {
var indicator = LK.getAsset('road_line', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 0.5,
alpha: 0.6
});
indicator.x = getLanePosition(i);
indicator.y = player.y - 100;
indicator.tint = 0x33ff33; // Light green tint
laneIndicators.push(indicator);
game.addChild(indicator);
}
// Add current lane text
var laneTxt = new Text2('LANE: 1', {
size: 50,
fill: 0x33ff33
});
laneTxt.anchor.set(0, 0);
LK.gui.topLeft.addChild(laneTxt);
// Add shield status text
var shieldTxt = new Text2('', {
size: 50,
fill: 0x8A2BE2
});
shieldTxt.anchor.set(0, 0);
shieldTxt.y = 60; // Position below lane text
LK.gui.topLeft.addChild(shieldTxt);
// Function to get lane position
function getLanePosition(laneNum) {
return ROAD_LEFT + LANE_WIDTH * (laneNum + 0.5);
}
// Function to check which lane a position is in
function getLaneNumberFromPosition(xPos) {
if (xPos < ROAD_LEFT) return 0;
if (xPos > ROAD_RIGHT) return LANES - 1;
return Math.floor((xPos - ROAD_LEFT) / LANE_WIDTH);
}
// Function to reset and position game object
function resetObject(obj, type) {
// Choose random lane
var lane = Math.floor(Math.random() * LANES);
obj.x = getLanePosition(lane);
obj.y = -obj.height;
obj.visible = true;
obj.needsReset = false;
// Set speed based on game speed
obj.speed = gameSpeed;
// Extra setup for specific object types
if (type === 'car') {
// Random color tint variation for enemy cars
var colorVariation = Math.random() * 0x333333;
obj.children[0].tint = 0x3377ff + colorVariation;
// Clear spiral variables to reset motion
obj.startX = undefined;
obj.angle = undefined;
obj.radius = undefined;
obj.rotationSpeed = undefined;
}
}
// Function to start the game
function startGame() {
if (gameState !== STATE_READY) return;
gameState = STATE_PLAYING;
startTxt.visible = false;
// Reset game variables
score = 0;
coinsCollected = 0;
gameSpeed = baseSpeed;
speedMultiplier = 1;
difficultyTimer = 0;
boostTime = 0;
invincibilityTime = 0;
isInvincible = false;
shieldTxt.setText('');
// Update UI
scoreTxt.setText('DISTANCE: 0');
coinsTxt.setText('COINS: 0');
// Reset lane indicators
var currentLane = getLaneNumberFromPosition(player.x);
laneTxt.setText('LANE: ' + (currentLane + 1));
for (var i = 0; i < laneIndicators.length; i++) {
if (i === currentLane) {
laneIndicators[i].alpha = 0.9;
laneIndicators[i].tint = 0x33ff33;
} else {
laneIndicators[i].alpha = 0.4;
laneIndicators[i].tint = 0xcccccc;
}
laneIndicators[i].visible = true;
}
laneTxt.visible = true;
// Play background music
LK.playMusic('game_music');
}
// Handle player movement
function handleMove(x, y, obj) {
if (gameState === STATE_PLAYING) {
// Limit player movement to the road area with some buffer
var targetX = Math.max(ROAD_LEFT + player.width / 2, Math.min(ROAD_RIGHT - player.width / 2, x));
player.targetX = targetX;
// Update the lane text based on player position
var currentLane = getLaneNumberFromPosition(targetX);
laneTxt.setText('LANE: ' + (currentLane + 1));
// Highlight the current lane indicator
for (var i = 0; i < laneIndicators.length; i++) {
if (i === currentLane) {
laneIndicators[i].alpha = 0.9;
laneIndicators[i].tint = 0x33ff33; // Bright green for current lane
} else {
laneIndicators[i].alpha = 0.4;
laneIndicators[i].tint = 0xcccccc; // Gray for other lanes
}
}
}
}
// Check collisions between player and other objects
function checkCollisions() {
// Check enemy cars - skip if invincible
for (var i = 0; i < enemyCars.length; i++) {
if (enemyCars[i].visible && player.intersects(enemyCars[i])) {
if (!isInvincible) {
gameOver();
LK.getSound('crash').play();
return;
} else {
// Just make the car disappear if we have a shield
enemyCars[i].visible = false;
enemyCars[i].needsReset = true;
// Flash player to show invincibility worked
LK.effects.flashObject(player, 0x8A2BE2, 300);
// Add score for destroying car with shield
score += 100;
var bonusTxt = new Text2('+100', {
size: 60,
fill: 0x8A2BE2
});
bonusTxt.anchor.set(0.5, 0.5);
bonusTxt.x = player.x;
bonusTxt.y = player.y - 50;
game.addChild(bonusTxt);
tween(bonusTxt, {
alpha: 0,
y: bonusTxt.y - 100
}, {
duration: 1000,
onComplete: function onComplete() {
game.removeChild(bonusTxt);
}
});
}
}
}
// Check obstacles - skip if invincible
for (var i = 0; i < obstacles.length; i++) {
if (obstacles[i].visible && player.intersects(obstacles[i])) {
if (!isInvincible) {
gameOver();
LK.getSound('crash').play();
return;
} else {
// Just make the obstacle disappear if we have a shield
obstacles[i].visible = false;
obstacles[i].needsReset = true;
// Flash player to show invincibility worked
LK.effects.flashObject(player, 0x8A2BE2, 300);
// Add score for destroying obstacle with shield
score += 50;
var bonusTxt = new Text2('+50', {
size: 60,
fill: 0x8A2BE2
});
bonusTxt.anchor.set(0.5, 0.5);
bonusTxt.x = player.x;
bonusTxt.y = player.y - 50;
game.addChild(bonusTxt);
tween(bonusTxt, {
alpha: 0,
y: bonusTxt.y - 100
}, {
duration: 1000,
onComplete: function onComplete() {
game.removeChild(bonusTxt);
}
});
}
}
}
// Check coins
for (var i = 0; i < coins.length; i++) {
if (coins[i].visible && player.intersects(coins[i])) {
coins[i].visible = false;
coins[i].needsReset = true;
coinsCollected++;
coinsTxt.setText('COINS: ' + coinsCollected);
LK.getSound('coin_pickup').play();
}
}
// Check speed boosts
for (var i = 0; i < speedBoosts.length; i++) {
if (speedBoosts[i].visible && player.intersects(speedBoosts[i])) {
speedBoosts[i].visible = false;
speedBoosts[i].needsReset = true;
activateSpeedBoost();
LK.getSound('powerup').play();
}
}
// Check shield powerups
for (var i = 0; i < shieldPowerups.length; i++) {
if (shieldPowerups[i].visible && player.intersects(shieldPowerups[i])) {
shieldPowerups[i].visible = false;
shieldPowerups[i].needsReset = true;
activateInvincibility();
LK.getSound('powerup').play();
}
}
}
// Activate speed boost
function activateSpeedBoost() {
boostTime = 180; // 3 seconds at 60fps
speedMultiplier = 2;
// Flash screen green briefly
LK.effects.flashScreen(0x00ff00, 300);
}
// Activate invincibility shield
function activateInvincibility() {
invincibilityTime = 300; // 5 seconds at 60fps
isInvincible = true;
// Update shield status text
shieldTxt.setText('SHIELD: ACTIVE');
shieldTxt.visible = true;
// Flash screen purple briefly
LK.effects.flashScreen(0x8A2BE2, 300);
// Add visual effect to player
player.children[0].tint = 0x8A2BE2;
}
// Spawn game objects
function spawnObjects() {
// Increase spawn rate based on score
var currentSpawnRate = spawnRate * (1 + score / 5000);
// Enemy cars
if (Math.random() < currentSpawnRate) {
for (var i = 0; i < enemyCars.length; i++) {
if (enemyCars[i].needsReset) {
resetObject(enemyCars[i], 'car');
break;
}
}
}
// Coins (more frequent)
if (Math.random() < currentSpawnRate * 1.5) {
for (var i = 0; i < coins.length; i++) {
if (coins[i].needsReset) {
resetObject(coins[i], 'coin');
break;
}
}
}
// Obstacles (less frequent)
if (Math.random() < currentSpawnRate * 0.7) {
for (var i = 0; i < obstacles.length; i++) {
if (obstacles[i].needsReset) {
resetObject(obstacles[i], 'obstacle');
break;
}
}
}
// Speed boosts (rare)
if (Math.random() < currentSpawnRate * 0.3) {
for (var i = 0; i < speedBoosts.length; i++) {
if (speedBoosts[i].needsReset) {
resetObject(speedBoosts[i], 'boost');
break;
}
}
}
// Shield powerups (very rare)
if (Math.random() < currentSpawnRate * 0.15) {
for (var i = 0; i < shieldPowerups.length; i++) {
if (shieldPowerups[i].needsReset) {
resetObject(shieldPowerups[i], 'shield');
break;
}
}
}
}
// End the game
function gameOver() {
if (gameState !== STATE_PLAYING) return;
gameState = STATE_GAME_OVER;
// Save coins
storage.coins += coinsCollected;
// Update high score if needed
if (score > storage.highScore) {
storage.highScore = score;
highScoreTxt.setText('BEST: ' + storage.highScore);
}
// Create final score text to display
var finalScoreTxt = new Text2('FINAL SCORE: ' + score, {
size: 100,
fill: 0xFFFFFF,
stroke: 0x000000,
strokeThickness: 8
});
finalScoreTxt.anchor.set(0.5, 0.5);
LK.gui.center.addChild(finalScoreTxt);
// Flash screen red
LK.effects.flashScreen(0xff0000, 1000);
// Show game over screen after a short delay to allow score to be visible
LK.setTimeout(function () {
LK.showGameOver();
}, 2000);
}
// Main game update function
game.update = function () {
// Update lane indicators visibility based on game state
for (var i = 0; i < laneIndicators.length; i++) {
laneIndicators[i].visible = gameState === STATE_PLAYING;
}
laneTxt.visible = gameState === STATE_PLAYING;
// Handle game state
if (gameState === STATE_PLAYING) {
// Update game speed
var currentGameSpeed = gameSpeed * speedMultiplier;
// Update boost time
if (boostTime > 0) {
boostTime--;
if (boostTime === 0) {
speedMultiplier = 1;
}
}
// Update invincibility time
if (invincibilityTime > 0) {
invincibilityTime--;
// Update shield status text with countdown
shieldTxt.setText('SHIELD: ' + Math.ceil(invincibilityTime / 60) + 's');
// Blink player when shield is about to expire (last second)
if (invincibilityTime < 60) {
player.visible = LK.ticks % 10 < 5;
}
if (invincibilityTime === 0) {
isInvincible = false;
shieldTxt.setText('');
player.visible = true;
player.children[0].tint = 0xff0000; // Reset to original color
}
}
// Update road lines
for (var i = 0; i < roadLines.length; i++) {
roadLines[i].speed = currentGameSpeed;
}
// Update all game objects
for (var i = 0; i < enemyCars.length; i++) {
if (enemyCars[i].visible) enemyCars[i].speed = currentGameSpeed * 0.7;
}
for (var i = 0; i < coins.length; i++) {
if (coins[i].visible) coins[i].speed = currentGameSpeed;
}
for (var i = 0; i < obstacles.length; i++) {
if (obstacles[i].visible) obstacles[i].speed = currentGameSpeed;
}
for (var i = 0; i < speedBoosts.length; i++) {
if (speedBoosts[i].visible) speedBoosts[i].speed = currentGameSpeed;
}
for (var i = 0; i < shieldPowerups.length; i++) {
if (shieldPowerups[i].visible) shieldPowerups[i].speed = currentGameSpeed;
}
// Increment score based on speed with bonus during invincibility
var scoreMultiplier = isInvincible ? 1.5 : 1;
var scoreIncrease = Math.round(currentGameSpeed / 4 * scoreMultiplier);
score += scoreIncrease;
scoreTxt.setText('DISTANCE: ' + score);
// Visual feedback for score increases
if (LK.ticks % 15 === 0 && scoreIncrease > 0) {
var scorePopup = new Text2('+' + scoreIncrease, {
size: 40,
fill: speedMultiplier > 1 ? 0x33ff33 : isInvincible ? 0x8A2BE2 : 0xffffff
});
scorePopup.anchor.set(0.5, 0.5);
scorePopup.x = scoreTxt.x - 150;
scorePopup.y = scoreTxt.y + 50;
LK.gui.topRight.addChild(scorePopup);
// Animate the score popup
tween(scorePopup, {
alpha: 0,
y: scorePopup.y - 50
}, {
duration: 1000,
onComplete: function onComplete() {
LK.gui.topRight.removeChild(scorePopup);
}
});
}
// Increase game difficulty over time
difficultyTimer++;
if (difficultyTimer >= 300) {
// Every 5 seconds
difficultyTimer = 0;
gameSpeed += 0.2; // Gradually increase speed
}
// Check collisions
checkCollisions();
// Spawn new objects
spawnObjects();
}
};
// Game input events
game.down = function (x, y, obj) {
if (gameState === STATE_READY) {
startGame();
}
};
game.move = handleMove;
// Start with the game in ready state
gameState = STATE_READY; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0,
coins: 0,
carSpeed: 1,
carHandling: 1
});
/****
* Classes
****/
var Coin = Container.expand(function () {
var self = Container.call(this);
var coinGraphics = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0;
self.width = coinGraphics.width;
self.height = coinGraphics.height;
// Add a little rotation animation
self.update = function () {
self.y += self.speed;
coinGraphics.rotation += 0.05;
// Reset with random position when coin goes off screen
if (self.y > 2732 + self.height) {
self.visible = false;
self.needsReset = true;
}
};
return self;
});
var EnemyCar = Container.expand(function () {
var self = Container.call(this);
var carGraphics = self.attachAsset('enemy_car', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0;
self.width = carGraphics.width;
self.height = carGraphics.height;
self.update = function () {
// Store original position if not set yet
if (self.startX === undefined) {
self.startX = self.x;
self.angle = Math.random() * Math.PI * 2; // Random starting angle
self.radius = Math.random() * 150 + 50; // Random radius between 50-200
self.rotationSpeed = (Math.random() > 0.5 ? 1 : -1) * (Math.random() * 0.03 + 0.01); // Random speed and direction
}
// Move down
self.y += self.speed;
// Apply spiral motion
self.angle += self.rotationSpeed;
self.x = self.startX + Math.cos(self.angle) * self.radius;
// Reset with random position when car goes off screen
if (self.y > 2732 + self.height) {
self.visible = false;
self.needsReset = true;
// Clear spiral motion variables for reset
self.startX = undefined;
self.angle = undefined;
self.radius = undefined;
self.rotationSpeed = undefined;
}
};
return self;
});
var InvincibilityPowerup = Container.expand(function () {
var self = Container.call(this);
var shieldGraphics = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5
});
// Change tint to purple for shield powerup
shieldGraphics.tint = 0x8A2BE2;
self.speed = 0;
self.width = shieldGraphics.width;
self.height = shieldGraphics.height;
// Add a rotation animation
self.update = function () {
self.y += self.speed;
shieldGraphics.rotation -= 0.08;
// Reset with random position when powerup goes off screen
if (self.y > 2732 + self.height) {
self.visible = false;
self.needsReset = true;
}
};
return self;
});
var Obstacle = Container.expand(function () {
var self = Container.call(this);
var obstacleGraphics = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0;
self.width = obstacleGraphics.width;
self.height = obstacleGraphics.height;
self.update = function () {
self.y += self.speed;
// Reset with random position when obstacle goes off screen
if (self.y > 2732 + self.height) {
self.visible = false;
self.needsReset = true;
}
};
return self;
});
var PlayerCar = Container.expand(function () {
var self = Container.call(this);
var carGraphics = self.attachAsset('player_car', {
anchorX: 0.5,
anchorY: 0.5
});
self.targetX = 0;
self.width = carGraphics.width;
self.height = carGraphics.height;
self.speed = 10 + storage.carSpeed * 2;
self.handling = 0.1 + storage.carHandling * 0.03;
self.update = function () {
// Move car toward target position with smooth handling
self.x += (self.targetX - self.x) * self.handling;
};
return self;
});
var RoadLine = Container.expand(function () {
var self = Container.call(this);
var lineGraphics = self.attachAsset('road_line', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0;
self.update = function () {
self.y += self.speed;
// Reset position when line goes off screen
if (self.y > 2732 + lineGraphics.height) {
self.y = -lineGraphics.height;
}
};
return self;
});
var SpeedBoost = Container.expand(function () {
var self = Container.call(this);
var boostGraphics = self.attachAsset('speedup', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0;
self.width = boostGraphics.width;
self.height = boostGraphics.height;
// Add a pulsing animation
var pulseUp = true;
self.update = function () {
self.y += self.speed;
// Pulse animation
if (pulseUp) {
boostGraphics.scale.x += 0.01;
boostGraphics.scale.y += 0.01;
if (boostGraphics.scale.x >= 1.2) {
pulseUp = false;
}
} else {
boostGraphics.scale.x -= 0.01;
boostGraphics.scale.y -= 0.01;
if (boostGraphics.scale.x <= 0.8) {
pulseUp = true;
}
}
// Reset with random position when boost goes off screen
if (self.y > 2732 + self.height) {
self.visible = false;
self.needsReset = true;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
// Game states
var STATE_READY = 0;
var STATE_PLAYING = 1;
var STATE_GAME_OVER = 2;
var gameState = STATE_READY;
// Game area constants
var ROAD_WIDTH = 1600;
var ROAD_LEFT = (2048 - ROAD_WIDTH) / 2;
var ROAD_RIGHT = ROAD_LEFT + ROAD_WIDTH;
var LANES = 4;
var LANE_WIDTH = ROAD_WIDTH / LANES;
// Game variables
var score = 0;
var coinsCollected = 0;
var gameSpeed = 8;
var baseSpeed = 8;
var speedMultiplier = 1;
var spawnRate = 0.02; // Chance to spawn an object each frame
var difficultyTimer = 0;
var boostTime = 0;
var invincibilityTime = 0;
var isInvincible = false;
// Create road background
var road = LK.getAsset('road', {
anchorX: 0.5,
anchorY: 0
});
road.x = 2048 / 2;
road.y = 0;
game.addChild(road);
// Create road lines
var roadLines = [];
var totalLines = 20;
var lineSpacing = 2732 / totalLines;
for (var i = 0; i < totalLines; i++) {
var line = new RoadLine();
line.x = 2048 / 2;
line.y = i * lineSpacing;
roadLines.push(line);
game.addChild(line);
}
// Create player car
var player = new PlayerCar();
player.x = 2048 / 2;
player.y = 2732 - 300;
game.addChild(player);
// Create enemy cars pool
var enemyCars = [];
var MAX_ENEMY_CARS = 10;
for (var i = 0; i < MAX_ENEMY_CARS; i++) {
var car = new EnemyCar();
car.visible = false;
car.needsReset = true;
enemyCars.push(car);
game.addChild(car);
}
// Create coins pool
var coins = [];
var MAX_COINS = 10;
for (var i = 0; i < MAX_COINS; i++) {
var coin = new Coin();
coin.visible = false;
coin.needsReset = true;
coins.push(coin);
game.addChild(coin);
}
// Create obstacles pool
var obstacles = [];
var MAX_OBSTACLES = 5;
for (var i = 0; i < MAX_OBSTACLES; i++) {
var obstacle = new Obstacle();
obstacle.visible = false;
obstacle.needsReset = true;
obstacles.push(obstacle);
game.addChild(obstacle);
}
// Create speed boosts pool
var speedBoosts = [];
var MAX_SPEED_BOOSTS = 3;
for (var i = 0; i < MAX_SPEED_BOOSTS; i++) {
var boost = new SpeedBoost();
boost.visible = false;
boost.needsReset = true;
speedBoosts.push(boost);
game.addChild(boost);
}
// Create shield powerups pool
var shieldPowerups = [];
var MAX_SHIELD_POWERUPS = 2;
for (var i = 0; i < MAX_SHIELD_POWERUPS; i++) {
var shield = new InvincibilityPowerup();
shield.visible = false;
shield.needsReset = true;
shieldPowerups.push(shield);
game.addChild(shield);
}
// Setup UI
var scoreTxt = new Text2('DISTANCE: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0, 0);
LK.gui.topRight.addChild(scoreTxt);
var coinsTxt = new Text2('COINS: 0', {
size: 60,
fill: 0xFFDD00
});
coinsTxt.anchor.set(1, 0);
LK.gui.top.addChild(coinsTxt);
var highScoreTxt = new Text2('BEST: ' + storage.highScore, {
size: 40,
fill: 0xAAAAAA
});
highScoreTxt.anchor.set(0, 1);
LK.gui.topRight.addChild(highScoreTxt);
var startTxt = new Text2('TAP TO START', {
size: 80,
fill: 0xFFFFFF
});
startTxt.anchor.set(0.5, 0.5);
LK.gui.center.addChild(startTxt);
// Create lane indicators
var laneIndicators = [];
for (var i = 0; i < LANES; i++) {
var indicator = LK.getAsset('road_line', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 0.5,
alpha: 0.6
});
indicator.x = getLanePosition(i);
indicator.y = player.y - 100;
indicator.tint = 0x33ff33; // Light green tint
laneIndicators.push(indicator);
game.addChild(indicator);
}
// Add current lane text
var laneTxt = new Text2('LANE: 1', {
size: 50,
fill: 0x33ff33
});
laneTxt.anchor.set(0, 0);
LK.gui.topLeft.addChild(laneTxt);
// Add shield status text
var shieldTxt = new Text2('', {
size: 50,
fill: 0x8A2BE2
});
shieldTxt.anchor.set(0, 0);
shieldTxt.y = 60; // Position below lane text
LK.gui.topLeft.addChild(shieldTxt);
// Function to get lane position
function getLanePosition(laneNum) {
return ROAD_LEFT + LANE_WIDTH * (laneNum + 0.5);
}
// Function to check which lane a position is in
function getLaneNumberFromPosition(xPos) {
if (xPos < ROAD_LEFT) return 0;
if (xPos > ROAD_RIGHT) return LANES - 1;
return Math.floor((xPos - ROAD_LEFT) / LANE_WIDTH);
}
// Function to reset and position game object
function resetObject(obj, type) {
// Choose random lane
var lane = Math.floor(Math.random() * LANES);
obj.x = getLanePosition(lane);
obj.y = -obj.height;
obj.visible = true;
obj.needsReset = false;
// Set speed based on game speed
obj.speed = gameSpeed;
// Extra setup for specific object types
if (type === 'car') {
// Random color tint variation for enemy cars
var colorVariation = Math.random() * 0x333333;
obj.children[0].tint = 0x3377ff + colorVariation;
// Clear spiral variables to reset motion
obj.startX = undefined;
obj.angle = undefined;
obj.radius = undefined;
obj.rotationSpeed = undefined;
}
}
// Function to start the game
function startGame() {
if (gameState !== STATE_READY) return;
gameState = STATE_PLAYING;
startTxt.visible = false;
// Reset game variables
score = 0;
coinsCollected = 0;
gameSpeed = baseSpeed;
speedMultiplier = 1;
difficultyTimer = 0;
boostTime = 0;
invincibilityTime = 0;
isInvincible = false;
shieldTxt.setText('');
// Update UI
scoreTxt.setText('DISTANCE: 0');
coinsTxt.setText('COINS: 0');
// Reset lane indicators
var currentLane = getLaneNumberFromPosition(player.x);
laneTxt.setText('LANE: ' + (currentLane + 1));
for (var i = 0; i < laneIndicators.length; i++) {
if (i === currentLane) {
laneIndicators[i].alpha = 0.9;
laneIndicators[i].tint = 0x33ff33;
} else {
laneIndicators[i].alpha = 0.4;
laneIndicators[i].tint = 0xcccccc;
}
laneIndicators[i].visible = true;
}
laneTxt.visible = true;
// Play background music
LK.playMusic('game_music');
}
// Handle player movement
function handleMove(x, y, obj) {
if (gameState === STATE_PLAYING) {
// Limit player movement to the road area with some buffer
var targetX = Math.max(ROAD_LEFT + player.width / 2, Math.min(ROAD_RIGHT - player.width / 2, x));
player.targetX = targetX;
// Update the lane text based on player position
var currentLane = getLaneNumberFromPosition(targetX);
laneTxt.setText('LANE: ' + (currentLane + 1));
// Highlight the current lane indicator
for (var i = 0; i < laneIndicators.length; i++) {
if (i === currentLane) {
laneIndicators[i].alpha = 0.9;
laneIndicators[i].tint = 0x33ff33; // Bright green for current lane
} else {
laneIndicators[i].alpha = 0.4;
laneIndicators[i].tint = 0xcccccc; // Gray for other lanes
}
}
}
}
// Check collisions between player and other objects
function checkCollisions() {
// Check enemy cars - skip if invincible
for (var i = 0; i < enemyCars.length; i++) {
if (enemyCars[i].visible && player.intersects(enemyCars[i])) {
if (!isInvincible) {
gameOver();
LK.getSound('crash').play();
return;
} else {
// Just make the car disappear if we have a shield
enemyCars[i].visible = false;
enemyCars[i].needsReset = true;
// Flash player to show invincibility worked
LK.effects.flashObject(player, 0x8A2BE2, 300);
// Add score for destroying car with shield
score += 100;
var bonusTxt = new Text2('+100', {
size: 60,
fill: 0x8A2BE2
});
bonusTxt.anchor.set(0.5, 0.5);
bonusTxt.x = player.x;
bonusTxt.y = player.y - 50;
game.addChild(bonusTxt);
tween(bonusTxt, {
alpha: 0,
y: bonusTxt.y - 100
}, {
duration: 1000,
onComplete: function onComplete() {
game.removeChild(bonusTxt);
}
});
}
}
}
// Check obstacles - skip if invincible
for (var i = 0; i < obstacles.length; i++) {
if (obstacles[i].visible && player.intersects(obstacles[i])) {
if (!isInvincible) {
gameOver();
LK.getSound('crash').play();
return;
} else {
// Just make the obstacle disappear if we have a shield
obstacles[i].visible = false;
obstacles[i].needsReset = true;
// Flash player to show invincibility worked
LK.effects.flashObject(player, 0x8A2BE2, 300);
// Add score for destroying obstacle with shield
score += 50;
var bonusTxt = new Text2('+50', {
size: 60,
fill: 0x8A2BE2
});
bonusTxt.anchor.set(0.5, 0.5);
bonusTxt.x = player.x;
bonusTxt.y = player.y - 50;
game.addChild(bonusTxt);
tween(bonusTxt, {
alpha: 0,
y: bonusTxt.y - 100
}, {
duration: 1000,
onComplete: function onComplete() {
game.removeChild(bonusTxt);
}
});
}
}
}
// Check coins
for (var i = 0; i < coins.length; i++) {
if (coins[i].visible && player.intersects(coins[i])) {
coins[i].visible = false;
coins[i].needsReset = true;
coinsCollected++;
coinsTxt.setText('COINS: ' + coinsCollected);
LK.getSound('coin_pickup').play();
}
}
// Check speed boosts
for (var i = 0; i < speedBoosts.length; i++) {
if (speedBoosts[i].visible && player.intersects(speedBoosts[i])) {
speedBoosts[i].visible = false;
speedBoosts[i].needsReset = true;
activateSpeedBoost();
LK.getSound('powerup').play();
}
}
// Check shield powerups
for (var i = 0; i < shieldPowerups.length; i++) {
if (shieldPowerups[i].visible && player.intersects(shieldPowerups[i])) {
shieldPowerups[i].visible = false;
shieldPowerups[i].needsReset = true;
activateInvincibility();
LK.getSound('powerup').play();
}
}
}
// Activate speed boost
function activateSpeedBoost() {
boostTime = 180; // 3 seconds at 60fps
speedMultiplier = 2;
// Flash screen green briefly
LK.effects.flashScreen(0x00ff00, 300);
}
// Activate invincibility shield
function activateInvincibility() {
invincibilityTime = 300; // 5 seconds at 60fps
isInvincible = true;
// Update shield status text
shieldTxt.setText('SHIELD: ACTIVE');
shieldTxt.visible = true;
// Flash screen purple briefly
LK.effects.flashScreen(0x8A2BE2, 300);
// Add visual effect to player
player.children[0].tint = 0x8A2BE2;
}
// Spawn game objects
function spawnObjects() {
// Increase spawn rate based on score
var currentSpawnRate = spawnRate * (1 + score / 5000);
// Enemy cars
if (Math.random() < currentSpawnRate) {
for (var i = 0; i < enemyCars.length; i++) {
if (enemyCars[i].needsReset) {
resetObject(enemyCars[i], 'car');
break;
}
}
}
// Coins (more frequent)
if (Math.random() < currentSpawnRate * 1.5) {
for (var i = 0; i < coins.length; i++) {
if (coins[i].needsReset) {
resetObject(coins[i], 'coin');
break;
}
}
}
// Obstacles (less frequent)
if (Math.random() < currentSpawnRate * 0.7) {
for (var i = 0; i < obstacles.length; i++) {
if (obstacles[i].needsReset) {
resetObject(obstacles[i], 'obstacle');
break;
}
}
}
// Speed boosts (rare)
if (Math.random() < currentSpawnRate * 0.3) {
for (var i = 0; i < speedBoosts.length; i++) {
if (speedBoosts[i].needsReset) {
resetObject(speedBoosts[i], 'boost');
break;
}
}
}
// Shield powerups (very rare)
if (Math.random() < currentSpawnRate * 0.15) {
for (var i = 0; i < shieldPowerups.length; i++) {
if (shieldPowerups[i].needsReset) {
resetObject(shieldPowerups[i], 'shield');
break;
}
}
}
}
// End the game
function gameOver() {
if (gameState !== STATE_PLAYING) return;
gameState = STATE_GAME_OVER;
// Save coins
storage.coins += coinsCollected;
// Update high score if needed
if (score > storage.highScore) {
storage.highScore = score;
highScoreTxt.setText('BEST: ' + storage.highScore);
}
// Create final score text to display
var finalScoreTxt = new Text2('FINAL SCORE: ' + score, {
size: 100,
fill: 0xFFFFFF,
stroke: 0x000000,
strokeThickness: 8
});
finalScoreTxt.anchor.set(0.5, 0.5);
LK.gui.center.addChild(finalScoreTxt);
// Flash screen red
LK.effects.flashScreen(0xff0000, 1000);
// Show game over screen after a short delay to allow score to be visible
LK.setTimeout(function () {
LK.showGameOver();
}, 2000);
}
// Main game update function
game.update = function () {
// Update lane indicators visibility based on game state
for (var i = 0; i < laneIndicators.length; i++) {
laneIndicators[i].visible = gameState === STATE_PLAYING;
}
laneTxt.visible = gameState === STATE_PLAYING;
// Handle game state
if (gameState === STATE_PLAYING) {
// Update game speed
var currentGameSpeed = gameSpeed * speedMultiplier;
// Update boost time
if (boostTime > 0) {
boostTime--;
if (boostTime === 0) {
speedMultiplier = 1;
}
}
// Update invincibility time
if (invincibilityTime > 0) {
invincibilityTime--;
// Update shield status text with countdown
shieldTxt.setText('SHIELD: ' + Math.ceil(invincibilityTime / 60) + 's');
// Blink player when shield is about to expire (last second)
if (invincibilityTime < 60) {
player.visible = LK.ticks % 10 < 5;
}
if (invincibilityTime === 0) {
isInvincible = false;
shieldTxt.setText('');
player.visible = true;
player.children[0].tint = 0xff0000; // Reset to original color
}
}
// Update road lines
for (var i = 0; i < roadLines.length; i++) {
roadLines[i].speed = currentGameSpeed;
}
// Update all game objects
for (var i = 0; i < enemyCars.length; i++) {
if (enemyCars[i].visible) enemyCars[i].speed = currentGameSpeed * 0.7;
}
for (var i = 0; i < coins.length; i++) {
if (coins[i].visible) coins[i].speed = currentGameSpeed;
}
for (var i = 0; i < obstacles.length; i++) {
if (obstacles[i].visible) obstacles[i].speed = currentGameSpeed;
}
for (var i = 0; i < speedBoosts.length; i++) {
if (speedBoosts[i].visible) speedBoosts[i].speed = currentGameSpeed;
}
for (var i = 0; i < shieldPowerups.length; i++) {
if (shieldPowerups[i].visible) shieldPowerups[i].speed = currentGameSpeed;
}
// Increment score based on speed with bonus during invincibility
var scoreMultiplier = isInvincible ? 1.5 : 1;
var scoreIncrease = Math.round(currentGameSpeed / 4 * scoreMultiplier);
score += scoreIncrease;
scoreTxt.setText('DISTANCE: ' + score);
// Visual feedback for score increases
if (LK.ticks % 15 === 0 && scoreIncrease > 0) {
var scorePopup = new Text2('+' + scoreIncrease, {
size: 40,
fill: speedMultiplier > 1 ? 0x33ff33 : isInvincible ? 0x8A2BE2 : 0xffffff
});
scorePopup.anchor.set(0.5, 0.5);
scorePopup.x = scoreTxt.x - 150;
scorePopup.y = scoreTxt.y + 50;
LK.gui.topRight.addChild(scorePopup);
// Animate the score popup
tween(scorePopup, {
alpha: 0,
y: scorePopup.y - 50
}, {
duration: 1000,
onComplete: function onComplete() {
LK.gui.topRight.removeChild(scorePopup);
}
});
}
// Increase game difficulty over time
difficultyTimer++;
if (difficultyTimer >= 300) {
// Every 5 seconds
difficultyTimer = 0;
gameSpeed += 0.2; // Gradually increase speed
}
// Check collisions
checkCollisions();
// Spawn new objects
spawnObjects();
}
};
// Game input events
game.down = function (x, y, obj) {
if (gameState === STATE_READY) {
startGame();
}
};
game.move = handleMove;
// Start with the game in ready state
gameState = STATE_READY;
Coin 2d pixilated top down. In-Game asset. 2d. High contrast. No shadows
Red car top down 24 pixilated. In-Game asset. 2d. High contrast. No shadows
Blue car 2d pixilated topdown. In-Game asset. 2d. High contrast. No shadows
Box 2d pixilated topdown. In-Game asset. 2d. High contrast. No shadows
Black Road no lines 2d pixilated topdown. In-Game asset. 2d. High contrast. No shadows
Powerup speed boost 2d pixilated topdown. In-Game asset. 2d. High contrast. No shadows