/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Car class
var Car = Container.expand(function () {
var self = Container.call(this);
var carSprite = self.attachAsset('car', {
anchorX: 0.5,
anchorY: 0.5
});
// For possible future effects
self.flash = function () {
LK.effects.flashObject(self, 0xffffff, 200);
};
return self;
});
// LaneLine class (for visual effect)
var LaneLine = Container.expand(function () {
var self = Container.call(this);
var lineSprite = self.attachAsset('laneLine', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
// Note class
var Note = Container.expand(function () {
var self = Container.call(this);
// Pick a random note asset
var assetIdx = Math.floor(Math.random() * NOTE_ASSET_IDS.length);
var noteSprite = self.attachAsset(NOTE_ASSET_IDS[assetIdx], {
anchorX: 0.5,
anchorY: 0.5
});
// Lane index (0,1,2)
self.lane = 1;
// For state tracking
self.lastY = undefined;
self.lastIntersecting = false;
return self;
});
// Obstacle class
var Obstacle = Container.expand(function () {
var self = Container.call(this);
var obsSprite = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5
});
// Lane index (0,1,2)
self.lane = 1;
// For state tracking
self.lastY = undefined;
self.lastIntersecting = false;
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x0a0020 // Deep synthwave night
});
/****
* Game Code
****/
// unique id
// unique id
// unique id
// unique id
// unique id
// unique id
// unique id
// Sounds and music (IDs are placeholders, engine will load as used)
// Neon synthwave colors
// --- Game constants ---
// Note class
var NOTE_ASSET_IDS = ['note',
// 0
'note1',
// 1
'note2',
// 2
'note3',
// 3
'note4',
// 4
'note5',
// 5
'note6',
// 6
'note7',
// 7
'note8' // 8
];
var NUM_LANES = 3;
var LANE_WIDTH = 410; // 2048 / 3 ≈ 682, but leave margins for neon effect
var ROAD_TOP = 400;
var ROAD_BOTTOM = 2732 - 200;
var CAR_Y = 2200;
var BASE_OBSTACLE_SPEED = 22; // px per frame
var BASE_NOTE_SPEED = 22;
var OBSTACLE_SPEED = BASE_OBSTACLE_SPEED;
var NOTE_SPEED = BASE_NOTE_SPEED;
var LANE_X = [410,
// left
1024,
// center
1638 // right
];
// --- Music BPM and spawn sync ---
var BPM = 120; // Example BPM, can be changed if music changes
var BASE_BEAT_INTERVAL = Math.round(60 / BPM * 60); // frames per beat (60fps)
var BEAT_INTERVAL = BASE_BEAT_INTERVAL;
var lastBeatTick = 0;
// --- Game state ---
var car = null;
var carLane = 1; // 0: left, 1: center, 2: right
var obstacles = [];
var notes = [];
var laneLines = [];
var score = 0;
var scoreTxt = null;
var dragSide = null; // 'left' or 'right' for touch controls
// --- Background music ---
LK.playMusic('neonTrack');
// --- Draw neon road lanes ---
function createLaneLines() {
// Remove old lines
for (var i = 0; i < laneLines.length; i++) {
laneLines[i].destroy();
}
laneLines = [];
// Draw vertical lines between lanes
for (var i = 1; i < NUM_LANES; i++) {
var x = (LANE_X[i - 1] + LANE_X[i]) / 2;
for (var y = ROAD_TOP; y < ROAD_BOTTOM; y += 400) {
var line = new LaneLine();
line.x = x;
line.y = y;
line.alpha = 0.25;
game.addChild(line);
laneLines.push(line);
}
}
}
createLaneLines();
// --- Create car ---
car = new Car();
car.x = LANE_X[carLane];
car.y = CAR_Y;
game.addChild(car);
// --- Score display ---
scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFF200,
font: "Impact"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// --- Touch controls ---
// Touch/click left or right half of screen to move car
function handleDown(x, y, obj) {
// Only respond to touches below the top 200px (avoid menu)
if (y < 200) return;
// Expand touch logic: if tap is left of car center, go left; if right, go right
// If tap is very close to car center (±80px), do nothing (prevents accidental moves)
var carCenterX = car.x;
if (x < carCenterX - 80) {
dragSide = 'left';
moveCar(-1);
} else if (x > carCenterX + 80) {
dragSide = 'right';
moveCar(1);
} else {
dragSide = null;
}
}
function handleUp(x, y, obj) {
dragSide = null;
}
function handleMove(x, y, obj) {
// Optional: swipe to move, but for now, tap only
}
game.down = handleDown;
game.up = handleUp;
game.move = handleMove;
// --- Move car between lanes ---
function moveCar(dir) {
var newLane = carLane + dir;
if (newLane < 0) newLane = 0;
if (newLane > 2) newLane = 2;
if (newLane !== carLane) {
carLane = newLane;
// Animate car to new lane
tween(car, {
x: LANE_X[carLane]
}, {
duration: 120,
easing: tween.cubicOut
});
}
}
// --- Spawn obstacles and notes in sync with music ---
function spawnBeatObjects() {
// Randomly decide: 1 or 2 obstacles, and 0 or 1 note
var availableLanes = [0, 1, 2];
// Place 1 or 2 obstacles
var numObstacles = Math.random() < 0.5 ? 1 : 2;
for (var i = 0; i < numObstacles; i++) {
if (availableLanes.length === 0) break;
var idx = Math.floor(Math.random() * availableLanes.length);
var lane = availableLanes[idx];
availableLanes.splice(idx, 1);
var obs = new Obstacle();
obs.lane = lane;
obs.x = LANE_X[lane];
obs.y = ROAD_TOP - 100;
obs.lastY = obs.y;
obs.lastIntersecting = false;
obstacles.push(obs);
game.addChild(obs);
}
// Place a note in a random free lane (if any)
if (availableLanes.length > 0 && Math.random() < 0.7) {
var idx = Math.floor(Math.random() * availableLanes.length);
var lane = availableLanes[idx];
var note = new Note();
note.lane = lane;
note.x = LANE_X[lane];
note.y = ROAD_TOP - 100;
note.lastY = note.y;
note.lastIntersecting = false;
notes.push(note);
game.addChild(note);
}
}
// --- Main game update loop ---
game.update = function () {
// --- Dynamic difficulty: first 20 points easy, then increase every 20 points ---
var currentScore = LK.getScore();
var difficultyLevel = 0;
if (currentScore < 20) {
difficultyLevel = 0;
} else {
difficultyLevel = Math.floor((currentScore - 20) / 20) + 1;
}
OBSTACLE_SPEED = BASE_OBSTACLE_SPEED + difficultyLevel * 4;
NOTE_SPEED = BASE_NOTE_SPEED + difficultyLevel * 4;
// Minimum beat interval: don't go below 12 frames (~0.2s at 60fps)
BEAT_INTERVAL = Math.max(BASE_BEAT_INTERVAL - difficultyLevel * 8, 12);
// Animate lane lines for neon effect
for (var i = 0; i < laneLines.length; i++) {
laneLines[i].y += OBSTACLE_SPEED;
if (laneLines[i].y > ROAD_BOTTOM + 200) {
laneLines[i].y = ROAD_TOP - 200;
}
}
// Spawn obstacles/notes on beat
if (LK.ticks - lastBeatTick >= BEAT_INTERVAL) {
spawnBeatObjects();
lastBeatTick = LK.ticks;
}
// Move obstacles
for (var i = obstacles.length - 1; i >= 0; i--) {
var obs = obstacles[i];
obs.y += OBSTACLE_SPEED;
// Off-screen removal
if (obs.lastY < 2800 && obs.y >= 2800) {
obs.destroy();
obstacles.splice(i, 1);
continue;
}
// Collision detection
var isIntersecting = obs.intersects(car);
if (!obs.lastIntersecting && isIntersecting) {
// Crash!
LK.effects.flashScreen(0xff2a6d, 800);
car.flash();
LK.showGameOver();
return;
}
obs.lastY = obs.y;
obs.lastIntersecting = isIntersecting;
}
// Move notes
for (var i = notes.length - 1; i >= 0; i--) {
var note = notes[i];
note.y += NOTE_SPEED;
// Off-screen removal
if (note.lastY < 2800 && note.y >= 2800) {
note.destroy();
notes.splice(i, 1);
continue;
}
// Collect note
var isIntersecting = note.intersects(car);
if (!note.lastIntersecting && isIntersecting) {
// Collect!
LK.setScore(LK.getScore() + 1);
scoreTxt.setText(LK.getScore());
LK.getSound('noteCollect').play();
// Neon flash
LK.effects.flashObject(car, 0xfff200, 200);
note.destroy();
notes.splice(i, 1);
continue;
}
note.lastY = note.y;
note.lastIntersecting = isIntersecting;
}
};
// --- Set initial score ---
LK.setScore(0);
scoreTxt.setText('0'); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Car class
var Car = Container.expand(function () {
var self = Container.call(this);
var carSprite = self.attachAsset('car', {
anchorX: 0.5,
anchorY: 0.5
});
// For possible future effects
self.flash = function () {
LK.effects.flashObject(self, 0xffffff, 200);
};
return self;
});
// LaneLine class (for visual effect)
var LaneLine = Container.expand(function () {
var self = Container.call(this);
var lineSprite = self.attachAsset('laneLine', {
anchorX: 0.5,
anchorY: 0.5
});
return self;
});
// Note class
var Note = Container.expand(function () {
var self = Container.call(this);
// Pick a random note asset
var assetIdx = Math.floor(Math.random() * NOTE_ASSET_IDS.length);
var noteSprite = self.attachAsset(NOTE_ASSET_IDS[assetIdx], {
anchorX: 0.5,
anchorY: 0.5
});
// Lane index (0,1,2)
self.lane = 1;
// For state tracking
self.lastY = undefined;
self.lastIntersecting = false;
return self;
});
// Obstacle class
var Obstacle = Container.expand(function () {
var self = Container.call(this);
var obsSprite = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5
});
// Lane index (0,1,2)
self.lane = 1;
// For state tracking
self.lastY = undefined;
self.lastIntersecting = false;
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x0a0020 // Deep synthwave night
});
/****
* Game Code
****/
// unique id
// unique id
// unique id
// unique id
// unique id
// unique id
// unique id
// Sounds and music (IDs are placeholders, engine will load as used)
// Neon synthwave colors
// --- Game constants ---
// Note class
var NOTE_ASSET_IDS = ['note',
// 0
'note1',
// 1
'note2',
// 2
'note3',
// 3
'note4',
// 4
'note5',
// 5
'note6',
// 6
'note7',
// 7
'note8' // 8
];
var NUM_LANES = 3;
var LANE_WIDTH = 410; // 2048 / 3 ≈ 682, but leave margins for neon effect
var ROAD_TOP = 400;
var ROAD_BOTTOM = 2732 - 200;
var CAR_Y = 2200;
var BASE_OBSTACLE_SPEED = 22; // px per frame
var BASE_NOTE_SPEED = 22;
var OBSTACLE_SPEED = BASE_OBSTACLE_SPEED;
var NOTE_SPEED = BASE_NOTE_SPEED;
var LANE_X = [410,
// left
1024,
// center
1638 // right
];
// --- Music BPM and spawn sync ---
var BPM = 120; // Example BPM, can be changed if music changes
var BASE_BEAT_INTERVAL = Math.round(60 / BPM * 60); // frames per beat (60fps)
var BEAT_INTERVAL = BASE_BEAT_INTERVAL;
var lastBeatTick = 0;
// --- Game state ---
var car = null;
var carLane = 1; // 0: left, 1: center, 2: right
var obstacles = [];
var notes = [];
var laneLines = [];
var score = 0;
var scoreTxt = null;
var dragSide = null; // 'left' or 'right' for touch controls
// --- Background music ---
LK.playMusic('neonTrack');
// --- Draw neon road lanes ---
function createLaneLines() {
// Remove old lines
for (var i = 0; i < laneLines.length; i++) {
laneLines[i].destroy();
}
laneLines = [];
// Draw vertical lines between lanes
for (var i = 1; i < NUM_LANES; i++) {
var x = (LANE_X[i - 1] + LANE_X[i]) / 2;
for (var y = ROAD_TOP; y < ROAD_BOTTOM; y += 400) {
var line = new LaneLine();
line.x = x;
line.y = y;
line.alpha = 0.25;
game.addChild(line);
laneLines.push(line);
}
}
}
createLaneLines();
// --- Create car ---
car = new Car();
car.x = LANE_X[carLane];
car.y = CAR_Y;
game.addChild(car);
// --- Score display ---
scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFF200,
font: "Impact"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// --- Touch controls ---
// Touch/click left or right half of screen to move car
function handleDown(x, y, obj) {
// Only respond to touches below the top 200px (avoid menu)
if (y < 200) return;
// Expand touch logic: if tap is left of car center, go left; if right, go right
// If tap is very close to car center (±80px), do nothing (prevents accidental moves)
var carCenterX = car.x;
if (x < carCenterX - 80) {
dragSide = 'left';
moveCar(-1);
} else if (x > carCenterX + 80) {
dragSide = 'right';
moveCar(1);
} else {
dragSide = null;
}
}
function handleUp(x, y, obj) {
dragSide = null;
}
function handleMove(x, y, obj) {
// Optional: swipe to move, but for now, tap only
}
game.down = handleDown;
game.up = handleUp;
game.move = handleMove;
// --- Move car between lanes ---
function moveCar(dir) {
var newLane = carLane + dir;
if (newLane < 0) newLane = 0;
if (newLane > 2) newLane = 2;
if (newLane !== carLane) {
carLane = newLane;
// Animate car to new lane
tween(car, {
x: LANE_X[carLane]
}, {
duration: 120,
easing: tween.cubicOut
});
}
}
// --- Spawn obstacles and notes in sync with music ---
function spawnBeatObjects() {
// Randomly decide: 1 or 2 obstacles, and 0 or 1 note
var availableLanes = [0, 1, 2];
// Place 1 or 2 obstacles
var numObstacles = Math.random() < 0.5 ? 1 : 2;
for (var i = 0; i < numObstacles; i++) {
if (availableLanes.length === 0) break;
var idx = Math.floor(Math.random() * availableLanes.length);
var lane = availableLanes[idx];
availableLanes.splice(idx, 1);
var obs = new Obstacle();
obs.lane = lane;
obs.x = LANE_X[lane];
obs.y = ROAD_TOP - 100;
obs.lastY = obs.y;
obs.lastIntersecting = false;
obstacles.push(obs);
game.addChild(obs);
}
// Place a note in a random free lane (if any)
if (availableLanes.length > 0 && Math.random() < 0.7) {
var idx = Math.floor(Math.random() * availableLanes.length);
var lane = availableLanes[idx];
var note = new Note();
note.lane = lane;
note.x = LANE_X[lane];
note.y = ROAD_TOP - 100;
note.lastY = note.y;
note.lastIntersecting = false;
notes.push(note);
game.addChild(note);
}
}
// --- Main game update loop ---
game.update = function () {
// --- Dynamic difficulty: first 20 points easy, then increase every 20 points ---
var currentScore = LK.getScore();
var difficultyLevel = 0;
if (currentScore < 20) {
difficultyLevel = 0;
} else {
difficultyLevel = Math.floor((currentScore - 20) / 20) + 1;
}
OBSTACLE_SPEED = BASE_OBSTACLE_SPEED + difficultyLevel * 4;
NOTE_SPEED = BASE_NOTE_SPEED + difficultyLevel * 4;
// Minimum beat interval: don't go below 12 frames (~0.2s at 60fps)
BEAT_INTERVAL = Math.max(BASE_BEAT_INTERVAL - difficultyLevel * 8, 12);
// Animate lane lines for neon effect
for (var i = 0; i < laneLines.length; i++) {
laneLines[i].y += OBSTACLE_SPEED;
if (laneLines[i].y > ROAD_BOTTOM + 200) {
laneLines[i].y = ROAD_TOP - 200;
}
}
// Spawn obstacles/notes on beat
if (LK.ticks - lastBeatTick >= BEAT_INTERVAL) {
spawnBeatObjects();
lastBeatTick = LK.ticks;
}
// Move obstacles
for (var i = obstacles.length - 1; i >= 0; i--) {
var obs = obstacles[i];
obs.y += OBSTACLE_SPEED;
// Off-screen removal
if (obs.lastY < 2800 && obs.y >= 2800) {
obs.destroy();
obstacles.splice(i, 1);
continue;
}
// Collision detection
var isIntersecting = obs.intersects(car);
if (!obs.lastIntersecting && isIntersecting) {
// Crash!
LK.effects.flashScreen(0xff2a6d, 800);
car.flash();
LK.showGameOver();
return;
}
obs.lastY = obs.y;
obs.lastIntersecting = isIntersecting;
}
// Move notes
for (var i = notes.length - 1; i >= 0; i--) {
var note = notes[i];
note.y += NOTE_SPEED;
// Off-screen removal
if (note.lastY < 2800 && note.y >= 2800) {
note.destroy();
notes.splice(i, 1);
continue;
}
// Collect note
var isIntersecting = note.intersects(car);
if (!note.lastIntersecting && isIntersecting) {
// Collect!
LK.setScore(LK.getScore() + 1);
scoreTxt.setText(LK.getScore());
LK.getSound('noteCollect').play();
// Neon flash
LK.effects.flashObject(car, 0xfff200, 200);
note.destroy();
notes.splice(i, 1);
continue;
}
note.lastY = note.y;
note.lastIntersecting = isIntersecting;
}
};
// --- Set initial score ---
LK.setScore(0);
scoreTxt.setText('0');