/****
* 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
});
// Store sprite reference for external access
self.sprite = carSprite;
// For possible future effects
self.flash = function () {
LK.effects.flashObject(self, 0xffffff, 200);
};
// Camera shake animation
self.shake = function () {
var originalX = self.x;
var originalY = self.y;
// Quick shake sequence
tween(self, {
x: originalX - 15,
y: originalY - 10
}, {
duration: 50,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
x: originalX + 15,
y: originalY + 10
}, {
duration: 50,
easing: tween.easeIn,
onFinish: function onFinish() {
tween(self, {
x: originalX - 10,
y: originalY - 5
}, {
duration: 50,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
x: originalX,
y: originalY
}, {
duration: 50,
easing: tween.easeIn
});
}
});
}
});
}
});
};
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
});
// Store sprite reference
self.sprite = lineSprite;
// Add initial glow effect
self.glowPhase = Math.random() * Math.PI * 2;
// Pulse animation
self.update = function () {
self.glowPhase += 0.08;
var glowIntensity = 0.7 + Math.sin(self.glowPhase) * 0.3;
self.alpha = glowIntensity;
};
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;
// Store sprite reference for effects
self.sprite = noteSprite;
// Collection effect
self.collect = function () {
// Scale up and fade out with rotation
tween(self, {
scaleX: 2.5,
scaleY: 2.5,
alpha: 0,
rotation: Math.PI * 2
}, {
duration: 400,
easing: tween.easeOut
});
// Move up slightly
tween(self, {
y: self.y - 150
}, {
duration: 400,
easing: tween.easeOut
});
};
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
****/
// Note class
// --- Game constants ---
// Neon synthwave colors
// Sounds and music (IDs are placeholders, engine will load as used)
// unique id
// unique id
// unique id
// unique id
// unique id
// unique id
// unique id
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 * 2.5); // frames per beat (60fps) - increased spacing
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
var isPowerUpActive = false;
var powerUpEndTime = 0;
var powerUpTimerText = null;
var speedMultiplier = 1;
var powerUpCooldownEndTime = 0;
// --- 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;
// Create dashed lines with varying opacity for depth
for (var y = ROAD_TOP - 200; y < ROAD_BOTTOM + 400; y += 120) {
var line = new LaneLine();
line.x = x;
line.y = y;
// Create gradient effect with opacity based on position
var distanceFromCenter = Math.abs(y - (ROAD_TOP + ROAD_BOTTOM) / 2);
var maxDistance = (ROAD_BOTTOM - ROAD_TOP) / 2;
var baseAlpha = 1.0 - distanceFromCenter / maxDistance * 0.3;
line.alpha = baseAlpha;
// Add slight scale variation for perspective
var scaleFactor = 1.0 - distanceFromCenter / maxDistance * 0.3;
line.scaleX = scaleFactor;
line.scaleY = scaleFactor * 1.5; // Make lines taller for better visibility
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);
// --- Power-up timer display ---
powerUpTimerText = new Text2('', {
size: 80,
fill: 0xff00ff,
font: "Impact"
});
powerUpTimerText.anchor.set(0.5, 0);
powerUpTimerText.y = 150;
powerUpTimerText.visible = false;
LK.gui.top.addChild(powerUpTimerText);
// --- 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) * speedMultiplier;
NOTE_SPEED = (BASE_NOTE_SPEED + difficultyLevel * 4) * speedMultiplier;
// Handle power-up timer
if (isPowerUpActive) {
var remainingTime = Math.max(0, powerUpEndTime - LK.ticks);
var remainingSeconds = Math.ceil(remainingTime / 60);
powerUpTimerText.setText('POWER: ' + remainingSeconds + 's');
// End power-up
if (remainingTime <= 0) {
isPowerUpActive = false;
speedMultiplier = 1;
car.sprite.tint = 0xffffff;
tween(car, {
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.easeIn
});
// Start cooldown timer (15 seconds)
powerUpCooldownEndTime = LK.ticks + 900; // 15 seconds at 60fps
}
}
// Handle cooldown timer
if (powerUpCooldownEndTime > LK.ticks) {
var cooldownRemaining = Math.max(0, powerUpCooldownEndTime - LK.ticks);
var cooldownSeconds = Math.ceil(cooldownRemaining / 60);
powerUpTimerText.setText('COOLDOWN: ' + cooldownSeconds + 's');
powerUpTimerText.visible = true;
// Keep button disabled during cooldown
glaudButton.alpha = 0.3;
} else if (!isPowerUpActive && powerUpTimerText.visible) {
// Hide timer and re-enable button when cooldown ends
powerUpTimerText.visible = false;
// Re-enable button with wing expansion
tween(glaudButton, {
alpha: 1
}, {
duration: 300
});
// Expand wings back to normal
tween(leftWing, {
scaleX: 2,
x: -100,
alpha: 0.8
}, {
duration: 600,
easing: tween.elasticOut
});
tween(rightWing, {
scaleX: 2,
x: 100,
alpha: 0.8
}, {
duration: 600,
easing: tween.elasticOut
});
// Flash edges to indicate ready
LK.effects.flashObject(leftEdge, 0xffffff, 400);
LK.effects.flashObject(rightEdge, 0xffffff, 400);
}
// Minimum beat interval: don't go below 30 frames (~0.5s at 60fps) for wider spacing
BEAT_INTERVAL = Math.max(BASE_BEAT_INTERVAL - difficultyLevel * 8, 30);
// Animate lane lines for neon effect
for (var i = 0; i < laneLines.length; i++) {
var line = laneLines[i];
line.y += OBSTACLE_SPEED;
// Recycle lines that go off screen
if (line.y > ROAD_BOTTOM + 400) {
line.y = ROAD_TOP - 400;
}
// Update gradient effect based on new position
var distanceFromCenter = Math.abs(line.y - (ROAD_TOP + ROAD_BOTTOM) / 2);
var maxDistance = (ROAD_BOTTOM - ROAD_TOP) / 2;
var baseAlpha = 1.0 - distanceFromCenter / maxDistance * 0.3;
// Calculate distance to car for glow effect
var distanceToCar = Math.abs(line.y - car.y);
var glowRadius = 300; // Pixels around car where glow effect happens
var glowIntensity = 0;
if (distanceToCar < glowRadius) {
// Calculate glow based on proximity (closer = brighter)
glowIntensity = 1.0 - distanceToCar / glowRadius;
// Add extra brightness
baseAlpha = Math.min(1.0, baseAlpha + glowIntensity * 0.5);
// Scale up slightly when glowing
var glowScale = 1.0 + glowIntensity * 0.3;
line.scaleX = line.scaleX * glowScale;
line.scaleY = line.scaleY * glowScale;
// Add tint effect for extra brightness
var tintValue = Math.floor(glowIntensity * 100);
var tintColor = 0xff << 16 | tintValue << 8 | 0xff; // Purple-ish white glow
line.sprite.tint = tintColor;
} else {
// Reset tint when not glowing
line.sprite.tint = 0xffffff;
}
// Apply base alpha but let the pulse effect in update() modulate it
line.sprite.alpha = baseAlpha;
// Update perspective scaling
var scaleFactor = 1.0 - distanceFromCenter / maxDistance * 0.3;
line.scaleX = scaleFactor;
line.scaleY = scaleFactor * 1.5;
}
// 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) {
// If power-up is active, pass through obstacle
if (isPowerUpActive) {
// Flash the obstacle and make it semi-transparent
LK.effects.flashObject(obs, 0xff00ff, 300);
tween(obs, {
alpha: 0.3,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 300,
easing: tween.easeOut
});
} else {
// 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);
// Camera shake on car
car.shake();
// Note collection effect
note.collect();
// Remove from array but destroy later after animation
notes.splice(i, 1);
// Destroy after animation completes
LK.setTimeout(function () {
note.destroy();
}, 450);
continue;
}
note.lastY = note.y;
note.lastIntersecting = isIntersecting;
}
};
// --- Glaud Effect Button ---
var glaudButton = new Container();
// Create expanding side elements for futuristic effect
var leftWing = glaudButton.attachAsset('laneLine', {
anchorX: 1,
anchorY: 0.5,
scaleX: 2,
scaleY: 1.4,
tint: 0x9400d3,
alpha: 0.8,
x: -100
});
var rightWing = glaudButton.attachAsset('laneLine', {
anchorX: 0,
anchorY: 0.5,
scaleX: 2,
scaleY: 1.4,
tint: 0x9400d3,
alpha: 0.8,
x: 100
});
// Create multi-layer button core for depth
var buttonBgOuter = glaudButton.attachAsset('laneLine', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 5,
scaleY: 1.5,
tint: 0x4a0080,
alpha: 0.4
});
var buttonBgMiddle = glaudButton.attachAsset('laneLine', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 4.5,
scaleY: 1.3,
tint: 0x6a0dad,
alpha: 0.6
});
var buttonBgInner = glaudButton.attachAsset('laneLine', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 4,
scaleY: 1.1,
tint: 0x9400d3,
alpha: 0.8
});
var buttonCore = glaudButton.attachAsset('laneLine', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3.5,
scaleY: 0.9,
tint: 0xda70d6
});
// Create glowing edges
var leftEdge = glaudButton.attachAsset('laneLine', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 1.6,
tint: 0xff00ff,
alpha: 0.8,
x: -140
});
var rightEdge = glaudButton.attachAsset('laneLine', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 1.6,
tint: 0xff00ff,
alpha: 0.8,
x: 140
});
var buttonText = new Text2('GLAUD EFFECT', {
size: 48,
fill: 0xffffff,
font: "Impact"
});
buttonText.anchor.set(0.5, 0.5);
glaudButton.addChild(buttonText);
// Add complex animation to button
glaudButton.glowPhase = 0;
glaudButton.expansionPhase = 0;
glaudButton.update = function () {
glaudButton.glowPhase += 0.05;
glaudButton.expansionPhase += 0.03;
// Glow pulsing
var glow = 0.7 + Math.sin(glaudButton.glowPhase) * 0.3;
buttonBgOuter.alpha = glow * 0.4;
buttonBgMiddle.alpha = glow * 0.6;
buttonBgInner.alpha = glow * 0.8;
leftEdge.alpha = glow * 0.8 + 0.2;
rightEdge.alpha = glow * 0.8 + 0.2;
// Wing expansion animation
var expansion = Math.sin(glaudButton.expansionPhase) * 0.2 + 1;
leftWing.scaleX = 2 * expansion;
rightWing.scaleX = 2 * expansion;
leftWing.x = -100 - (expansion - 1) * 50;
rightWing.x = 100 + (expansion - 1) * 50;
// Edge glow animation
var edgeGlow = Math.sin(glaudButton.glowPhase * 2) * 0.5 + 0.5;
leftEdge.scaleY = 1.6 + edgeGlow * 0.4;
rightEdge.scaleY = 1.6 + edgeGlow * 0.4;
leftEdge.alpha = 0.5 + edgeGlow * 0.5;
rightEdge.alpha = 0.5 + edgeGlow * 0.5;
};
// Position button at bottom right
glaudButton.x = -200;
glaudButton.y = -120;
LK.gui.bottomRight.addChild(glaudButton);
// Initial animation when button appears
tween(leftWing, {
scaleX: 2,
x: -100
}, {
duration: 800,
easing: tween.elasticOut
});
tween(rightWing, {
scaleX: 2,
x: 100
}, {
duration: 800,
easing: tween.elasticOut
});
// Button interaction
glaudButton.down = function (x, y, obj) {
// Don't activate if already active or on cooldown
if (isPowerUpActive || powerUpCooldownEndTime > LK.ticks) {
return;
}
// Flash effect when pressed
LK.effects.flashObject(glaudButton, 0xffffff, 200);
// Trigger glaud effect - flash screen with purple color
LK.effects.flashScreen(0x9400d3, 1000);
// Activate power-up
isPowerUpActive = true;
powerUpEndTime = LK.ticks + 600; // 10 seconds at 60fps
speedMultiplier = 2; // Double speed
powerUpTimerText.visible = true;
// Make car glow continuously
car.sprite.tint = 0xff00ff;
tween(car, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 300,
easing: tween.elasticOut
});
// Enhanced button press animation
// Expand wings outward
tween(leftWing, {
scaleX: 3,
x: -150,
alpha: 1
}, {
duration: 200,
easing: tween.easeOut
});
tween(rightWing, {
scaleX: 3,
x: 150,
alpha: 1
}, {
duration: 200,
easing: tween.easeOut
});
// Pulse core
tween(buttonCore, {
scaleX: 4,
scaleY: 1.1
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(buttonCore, {
scaleX: 3.5,
scaleY: 0.9
}, {
duration: 150,
easing: tween.easeIn
});
}
});
// Animate button press and disable
tween(glaudButton, {
scaleX: 1.1,
scaleY: 1.1,
alpha: 0.3
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(glaudButton, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
easing: tween.easeIn
});
// Retract wings during cooldown
tween(leftWing, {
scaleX: 1,
x: -80,
alpha: 0.3
}, {
duration: 400,
easing: tween.easeIn
});
tween(rightWing, {
scaleX: 1,
x: 80,
alpha: 0.3
}, {
duration: 400,
easing: tween.easeIn
});
}
});
};
// --- Set initial score ---
LK.setScore(0);
scoreTxt.setText('0');
; ===================================================================
--- original.js
+++ change.js