/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Balloon (for celebration)
var Balloon = Container.expand(function () {
var self = Container.call(this);
var balloonColors = [0xff6666, 0x66ccff, 0x66ff66, 0xffcc66, 0xcc66ff];
var color = balloonColors[Math.floor(Math.random() * balloonColors.length)];
var balloonAsset = self.attachAsset('centerCircle', {
width: 80,
height: 110,
color: color,
anchorX: 0.5,
anchorY: 1
});
// Add a string (line) as a small rectangle
var stringAsset = self.attachAsset('lane', {
width: 6,
height: 40,
color: 0x888888,
anchorX: 0.5,
anchorY: 0,
x: 0,
y: 0
});
stringAsset.y = 0;
stringAsset.x = 0;
balloonAsset.y = 0;
self.addChild(stringAsset);
self.addChild(balloonAsset);
self.update = function () {
// Balloons float up a bit
self.y -= 2;
};
return self;
});
// Coin
var Coin = Container.expand(function () {
var self = Container.call(this);
var coinAsset = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0; // Will be set on spawn
self.update = function () {
self.y += self.speed;
};
return self;
});
// Obstacle Car
var ObstacleCar = Container.expand(function () {
var self = Container.call(this);
var obsAsset = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0; // Will be set on spawn
self.update = function () {
self.y += self.speed;
};
return self;
});
// Player Car
var PlayerCar = 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
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
// Road config
// Car (player)
// Obstacle (enemy car)
// Coin
// Road lane
var ROAD_WIDTH = 900;
var ROAD_X = (2048 - ROAD_WIDTH) / 2;
var ROAD_Y = 0;
var ROAD_HEIGHT = 2732;
// Lane config
var LANE_COUNT = 3;
var LANE_WIDTH = ROAD_WIDTH / LANE_COUNT;
// Player config
var PLAYER_START_LANE = 1; // 0 = left, 1 = center, 2 = right
var PLAYER_Y = 2732 - 400;
// Obstacle config
var OBSTACLE_MIN_INTERVAL = 60; // frames
var OBSTACLE_MAX_INTERVAL = 120; // frames
// Coin config
var COIN_MIN_INTERVAL = 80;
var COIN_MAX_INTERVAL = 160;
// Speed config
var BASE_SPEED = 16;
var SPEED_INCREMENT = 0.015; // per tick
var MAX_SPEED = 38;
// Game state
var playerCar;
var obstacles = [];
var coins = [];
var dragNode = null;
var dragOffsetX = 0;
var lastObstacleTick = 0;
var nextObstacleInterval = 90;
var lastCoinTick = 0;
var nextCoinInterval = 120;
var currentSpeed = BASE_SPEED;
var ticksSurvived = 0;
// Improved grass plan: Layered grass with two tones and a border for each side
// Left grass (darker base)
var leftGrassDark = LK.getAsset('lane', {
width: ROAD_X,
height: ROAD_HEIGHT,
color: 0x228B22,
// darker green
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.addChild(leftGrassDark);
// Left grass (lighter highlight, inner edge)
var leftGrassLight = LK.getAsset('lane', {
width: Math.max(ROAD_X - 40, 0),
height: ROAD_HEIGHT,
color: 0x2ecc40,
// lighter green
anchorX: 0,
anchorY: 0,
x: 20,
y: 0
});
game.addChild(leftGrassLight);
// Left border (between grass and road)
var leftBorder = LK.getAsset('lane', {
width: 16,
height: ROAD_HEIGHT,
color: 0x333333,
anchorX: 1,
anchorY: 0,
x: ROAD_X,
y: ROAD_Y
});
game.addChild(leftBorder);
// Right grass (darker base)
var rightGrassDark = LK.getAsset('lane', {
width: ROAD_X,
height: ROAD_HEIGHT,
color: 0x228B22,
anchorX: 0,
anchorY: 0,
x: ROAD_X + ROAD_WIDTH,
y: 0
});
game.addChild(rightGrassDark);
// Right grass (lighter highlight, inner edge)
var rightGrassLight = LK.getAsset('lane', {
width: Math.max(ROAD_X - 40, 0),
height: ROAD_HEIGHT,
color: 0x2ecc40,
anchorX: 0,
anchorY: 0,
x: ROAD_X + ROAD_WIDTH + 20,
y: 0
});
game.addChild(rightGrassLight);
// Right border (between grass and road)
var rightBorder = LK.getAsset('lane', {
width: 16,
height: ROAD_HEIGHT,
color: 0x333333,
anchorX: 0,
anchorY: 0,
x: ROAD_X + ROAD_WIDTH,
y: ROAD_Y
});
game.addChild(rightBorder);
// Draw road background (just a gray rectangle)
var roadBg = LK.getAsset('lane', {
width: ROAD_WIDTH,
height: ROAD_HEIGHT,
color: 0x444444,
anchorX: 0,
anchorY: 0,
x: ROAD_X,
y: ROAD_Y
});
game.addChild(roadBg);
// Draw lane markers
var laneMarkers = [];
for (var i = 1; i < LANE_COUNT; i++) {
var marker = LK.getAsset('lane', {
width: 40,
height: ROAD_HEIGHT,
color: 0xffffff,
anchorX: 0.5,
anchorY: 0,
x: ROAD_X + i * LANE_WIDTH,
y: ROAD_Y
});
marker.alpha = 0.18;
game.addChild(marker);
laneMarkers.push(marker);
}
// Score text
var scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Place player car
playerCar = new PlayerCar();
playerCar.x = ROAD_X + (PLAYER_START_LANE + 0.5) * LANE_WIDTH;
playerCar.y = PLAYER_Y;
game.addChild(playerCar);
// Helper: Clamp x to road
function clampToRoad(x) {
var minX = ROAD_X + playerCar.width / 2;
var maxX = ROAD_X + ROAD_WIDTH - playerCar.width / 2;
if (x < minX) return minX;
if (x > maxX) return maxX;
return x;
}
// Touch/drag controls
game.down = function (x, y, obj) {
// Only start drag if touch is on car
var local = playerCar.toLocal(game.toGlobal({
x: x,
y: y
}));
if (local.x >= -playerCar.width / 2 && local.x <= playerCar.width / 2 && local.y >= -playerCar.height / 2 && local.y <= playerCar.height / 2) {
dragNode = playerCar;
dragOffsetX = playerCar.x - x;
}
};
game.move = function (x, y, obj) {
if (dragNode) {
// Only allow horizontal movement, clamp to road
dragNode.x = clampToRoad(x + dragOffsetX);
}
};
game.up = function (x, y, obj) {
dragNode = null;
};
// Spawn obstacle
function spawnObstacle() {
var lane = Math.floor(Math.random() * LANE_COUNT);
var obs = new ObstacleCar();
obs.x = ROAD_X + (lane + 0.5) * LANE_WIDTH;
obs.y = -obs.height / 2;
obs.speed = currentSpeed;
obstacles.push(obs);
game.addChild(obs);
}
// Spawn coin
function spawnCoin() {
var lane = Math.floor(Math.random() * LANE_COUNT);
var coin = new Coin();
coin.x = ROAD_X + (lane + 0.5) * LANE_WIDTH;
coin.y = -coin.height / 2 - 100;
coin.speed = currentSpeed;
coins.push(coin);
game.addChild(coin);
}
// Main game update
game.update = function () {
ticksSurvived++;
// Increase speed over time
if (currentSpeed < MAX_SPEED) {
currentSpeed += SPEED_INCREMENT;
}
// Update obstacles
for (var i = obstacles.length - 1; i >= 0; i--) {
var obs = obstacles[i];
obs.speed = currentSpeed;
obs.update();
// Remove if off screen
if (obs.y - obs.height / 2 > 2732) {
obs.destroy();
obstacles.splice(i, 1);
continue;
}
// Collision with player
if (obs.intersects(playerCar)) {
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
return;
}
}
// Update coins
for (var j = coins.length - 1; j >= 0; j--) {
var coin = coins[j];
coin.speed = currentSpeed;
coin.update();
// Remove if off screen
if (coin.y - coin.height / 2 > 2732) {
coin.destroy();
coins.splice(j, 1);
continue;
}
// Collect coin
if (coin.intersects(playerCar)) {
LK.setScore(LK.getScore() + 1);
scoreTxt.setText(LK.getScore());
// Animate coin
tween(coin, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
coin.destroy();
}
});
coins.splice(j, 1);
// Show balloons if score reaches 20 (only once)
if (typeof balloonsShown === "undefined") {
balloonsShown = false;
}
if (!balloonsShown && LK.getScore() === 20) {
balloonsShown = true;
if (typeof balloons === "undefined") {
balloons = [];
}
// Spawn several balloons at random positions
var balloonCount = 7;
for (var b = 0; b < balloonCount; b++) {
var balloon = new Balloon();
// Spread balloons horizontally across the road area
balloon.x = ROAD_X + 80 + Math.random() * (ROAD_WIDTH - 160);
balloon.y = 900 + Math.random() * 600;
game.addChild(balloon);
balloons.push(balloon);
}
// Remove balloons after 2 seconds
LK.setTimeout(function () {
for (var b = 0; b < balloons.length; b++) {
if (balloons[b] && balloons[b].destroy) {
balloons[b].destroy();
}
}
balloons = [];
}, 2000);
}
continue;
}
}
// Spawn obstacles
if (LK.ticks - lastObstacleTick >= nextObstacleInterval) {
spawnObstacle();
lastObstacleTick = LK.ticks;
nextObstacleInterval = OBSTACLE_MIN_INTERVAL + Math.floor(Math.random() * (OBSTACLE_MAX_INTERVAL - OBSTACLE_MIN_INTERVAL));
}
// Spawn coins
if (LK.ticks - lastCoinTick >= nextCoinInterval) {
spawnCoin();
lastCoinTick = LK.ticks;
nextCoinInterval = COIN_MIN_INTERVAL + Math.floor(Math.random() * (COIN_MAX_INTERVAL - COIN_MIN_INTERVAL));
}
};
// Set initial score
LK.setScore(0);
scoreTxt.setText('0'); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Balloon (for celebration)
var Balloon = Container.expand(function () {
var self = Container.call(this);
var balloonColors = [0xff6666, 0x66ccff, 0x66ff66, 0xffcc66, 0xcc66ff];
var color = balloonColors[Math.floor(Math.random() * balloonColors.length)];
var balloonAsset = self.attachAsset('centerCircle', {
width: 80,
height: 110,
color: color,
anchorX: 0.5,
anchorY: 1
});
// Add a string (line) as a small rectangle
var stringAsset = self.attachAsset('lane', {
width: 6,
height: 40,
color: 0x888888,
anchorX: 0.5,
anchorY: 0,
x: 0,
y: 0
});
stringAsset.y = 0;
stringAsset.x = 0;
balloonAsset.y = 0;
self.addChild(stringAsset);
self.addChild(balloonAsset);
self.update = function () {
// Balloons float up a bit
self.y -= 2;
};
return self;
});
// Coin
var Coin = Container.expand(function () {
var self = Container.call(this);
var coinAsset = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0; // Will be set on spawn
self.update = function () {
self.y += self.speed;
};
return self;
});
// Obstacle Car
var ObstacleCar = Container.expand(function () {
var self = Container.call(this);
var obsAsset = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0; // Will be set on spawn
self.update = function () {
self.y += self.speed;
};
return self;
});
// Player Car
var PlayerCar = 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
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
// Road config
// Car (player)
// Obstacle (enemy car)
// Coin
// Road lane
var ROAD_WIDTH = 900;
var ROAD_X = (2048 - ROAD_WIDTH) / 2;
var ROAD_Y = 0;
var ROAD_HEIGHT = 2732;
// Lane config
var LANE_COUNT = 3;
var LANE_WIDTH = ROAD_WIDTH / LANE_COUNT;
// Player config
var PLAYER_START_LANE = 1; // 0 = left, 1 = center, 2 = right
var PLAYER_Y = 2732 - 400;
// Obstacle config
var OBSTACLE_MIN_INTERVAL = 60; // frames
var OBSTACLE_MAX_INTERVAL = 120; // frames
// Coin config
var COIN_MIN_INTERVAL = 80;
var COIN_MAX_INTERVAL = 160;
// Speed config
var BASE_SPEED = 16;
var SPEED_INCREMENT = 0.015; // per tick
var MAX_SPEED = 38;
// Game state
var playerCar;
var obstacles = [];
var coins = [];
var dragNode = null;
var dragOffsetX = 0;
var lastObstacleTick = 0;
var nextObstacleInterval = 90;
var lastCoinTick = 0;
var nextCoinInterval = 120;
var currentSpeed = BASE_SPEED;
var ticksSurvived = 0;
// Improved grass plan: Layered grass with two tones and a border for each side
// Left grass (darker base)
var leftGrassDark = LK.getAsset('lane', {
width: ROAD_X,
height: ROAD_HEIGHT,
color: 0x228B22,
// darker green
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.addChild(leftGrassDark);
// Left grass (lighter highlight, inner edge)
var leftGrassLight = LK.getAsset('lane', {
width: Math.max(ROAD_X - 40, 0),
height: ROAD_HEIGHT,
color: 0x2ecc40,
// lighter green
anchorX: 0,
anchorY: 0,
x: 20,
y: 0
});
game.addChild(leftGrassLight);
// Left border (between grass and road)
var leftBorder = LK.getAsset('lane', {
width: 16,
height: ROAD_HEIGHT,
color: 0x333333,
anchorX: 1,
anchorY: 0,
x: ROAD_X,
y: ROAD_Y
});
game.addChild(leftBorder);
// Right grass (darker base)
var rightGrassDark = LK.getAsset('lane', {
width: ROAD_X,
height: ROAD_HEIGHT,
color: 0x228B22,
anchorX: 0,
anchorY: 0,
x: ROAD_X + ROAD_WIDTH,
y: 0
});
game.addChild(rightGrassDark);
// Right grass (lighter highlight, inner edge)
var rightGrassLight = LK.getAsset('lane', {
width: Math.max(ROAD_X - 40, 0),
height: ROAD_HEIGHT,
color: 0x2ecc40,
anchorX: 0,
anchorY: 0,
x: ROAD_X + ROAD_WIDTH + 20,
y: 0
});
game.addChild(rightGrassLight);
// Right border (between grass and road)
var rightBorder = LK.getAsset('lane', {
width: 16,
height: ROAD_HEIGHT,
color: 0x333333,
anchorX: 0,
anchorY: 0,
x: ROAD_X + ROAD_WIDTH,
y: ROAD_Y
});
game.addChild(rightBorder);
// Draw road background (just a gray rectangle)
var roadBg = LK.getAsset('lane', {
width: ROAD_WIDTH,
height: ROAD_HEIGHT,
color: 0x444444,
anchorX: 0,
anchorY: 0,
x: ROAD_X,
y: ROAD_Y
});
game.addChild(roadBg);
// Draw lane markers
var laneMarkers = [];
for (var i = 1; i < LANE_COUNT; i++) {
var marker = LK.getAsset('lane', {
width: 40,
height: ROAD_HEIGHT,
color: 0xffffff,
anchorX: 0.5,
anchorY: 0,
x: ROAD_X + i * LANE_WIDTH,
y: ROAD_Y
});
marker.alpha = 0.18;
game.addChild(marker);
laneMarkers.push(marker);
}
// Score text
var scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Place player car
playerCar = new PlayerCar();
playerCar.x = ROAD_X + (PLAYER_START_LANE + 0.5) * LANE_WIDTH;
playerCar.y = PLAYER_Y;
game.addChild(playerCar);
// Helper: Clamp x to road
function clampToRoad(x) {
var minX = ROAD_X + playerCar.width / 2;
var maxX = ROAD_X + ROAD_WIDTH - playerCar.width / 2;
if (x < minX) return minX;
if (x > maxX) return maxX;
return x;
}
// Touch/drag controls
game.down = function (x, y, obj) {
// Only start drag if touch is on car
var local = playerCar.toLocal(game.toGlobal({
x: x,
y: y
}));
if (local.x >= -playerCar.width / 2 && local.x <= playerCar.width / 2 && local.y >= -playerCar.height / 2 && local.y <= playerCar.height / 2) {
dragNode = playerCar;
dragOffsetX = playerCar.x - x;
}
};
game.move = function (x, y, obj) {
if (dragNode) {
// Only allow horizontal movement, clamp to road
dragNode.x = clampToRoad(x + dragOffsetX);
}
};
game.up = function (x, y, obj) {
dragNode = null;
};
// Spawn obstacle
function spawnObstacle() {
var lane = Math.floor(Math.random() * LANE_COUNT);
var obs = new ObstacleCar();
obs.x = ROAD_X + (lane + 0.5) * LANE_WIDTH;
obs.y = -obs.height / 2;
obs.speed = currentSpeed;
obstacles.push(obs);
game.addChild(obs);
}
// Spawn coin
function spawnCoin() {
var lane = Math.floor(Math.random() * LANE_COUNT);
var coin = new Coin();
coin.x = ROAD_X + (lane + 0.5) * LANE_WIDTH;
coin.y = -coin.height / 2 - 100;
coin.speed = currentSpeed;
coins.push(coin);
game.addChild(coin);
}
// Main game update
game.update = function () {
ticksSurvived++;
// Increase speed over time
if (currentSpeed < MAX_SPEED) {
currentSpeed += SPEED_INCREMENT;
}
// Update obstacles
for (var i = obstacles.length - 1; i >= 0; i--) {
var obs = obstacles[i];
obs.speed = currentSpeed;
obs.update();
// Remove if off screen
if (obs.y - obs.height / 2 > 2732) {
obs.destroy();
obstacles.splice(i, 1);
continue;
}
// Collision with player
if (obs.intersects(playerCar)) {
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
return;
}
}
// Update coins
for (var j = coins.length - 1; j >= 0; j--) {
var coin = coins[j];
coin.speed = currentSpeed;
coin.update();
// Remove if off screen
if (coin.y - coin.height / 2 > 2732) {
coin.destroy();
coins.splice(j, 1);
continue;
}
// Collect coin
if (coin.intersects(playerCar)) {
LK.setScore(LK.getScore() + 1);
scoreTxt.setText(LK.getScore());
// Animate coin
tween(coin, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
coin.destroy();
}
});
coins.splice(j, 1);
// Show balloons if score reaches 20 (only once)
if (typeof balloonsShown === "undefined") {
balloonsShown = false;
}
if (!balloonsShown && LK.getScore() === 20) {
balloonsShown = true;
if (typeof balloons === "undefined") {
balloons = [];
}
// Spawn several balloons at random positions
var balloonCount = 7;
for (var b = 0; b < balloonCount; b++) {
var balloon = new Balloon();
// Spread balloons horizontally across the road area
balloon.x = ROAD_X + 80 + Math.random() * (ROAD_WIDTH - 160);
balloon.y = 900 + Math.random() * 600;
game.addChild(balloon);
balloons.push(balloon);
}
// Remove balloons after 2 seconds
LK.setTimeout(function () {
for (var b = 0; b < balloons.length; b++) {
if (balloons[b] && balloons[b].destroy) {
balloons[b].destroy();
}
}
balloons = [];
}, 2000);
}
continue;
}
}
// Spawn obstacles
if (LK.ticks - lastObstacleTick >= nextObstacleInterval) {
spawnObstacle();
lastObstacleTick = LK.ticks;
nextObstacleInterval = OBSTACLE_MIN_INTERVAL + Math.floor(Math.random() * (OBSTACLE_MAX_INTERVAL - OBSTACLE_MIN_INTERVAL));
}
// Spawn coins
if (LK.ticks - lastCoinTick >= nextCoinInterval) {
spawnCoin();
lastCoinTick = LK.ticks;
nextCoinInterval = COIN_MIN_INTERVAL + Math.floor(Math.random() * (COIN_MAX_INTERVAL - COIN_MIN_INTERVAL));
}
};
// Set initial score
LK.setScore(0);
scoreTxt.setText('0');