/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Distraction Icon (obstacle) var DistractionIcon = Container.expand(function () { var self = Container.call(this); var icon = self.attachAsset('distraction_icon', { anchorX: 0.5, anchorY: 0.5 }); self.type = 'distraction'; self.lane = 1; self.speed = 16; // Will be set by game, but default here return self; }); // Productivity Icon (collectible) var ProdIcon = Container.expand(function () { var self = Container.call(this); var icon = self.attachAsset('prod_icon', { anchorX: 0.5, anchorY: 0.5 }); self.type = 'prod'; self.lane = 1; self.speed = 16; // Will be set by game, but default here return self; }); // Runner class var Runner = Container.expand(function () { var self = Container.call(this); var runnerSprite = self.attachAsset('runner', { anchorX: 0.5, anchorY: 1 }); self.lane = 1; // Start in center lane (0: left, 1: center, 2: right) self.y = 2200; // Near bottom of screen // Move to lane with tween self.moveToLane = function (targetLane) { if (targetLane < 0 || targetLane >= laneCount) return; self.lane = targetLane; tween(self, { x: laneX[targetLane] }, { duration: 120, easing: tween.cubicOut }); }; // Set initial position self.x = laneX[self.lane]; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // Lane positions (global, so all classes can access) // Runner character // Distraction icons (obstacles) // Productivity icons (collectibles) // Score display var laneCount = 3; var laneX = [2048 / 2 - 300, // Left lane 2048 / 2, // Center lane 2048 / 2 + 300 // Right lane ]; var scoreTxt = new Text2('0', { size: 120, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Game variables var runner; var objects = []; // All collectibles and obstacles var spawnTimer = 0; var spawnInterval = 60; // Frames between spawns (will decrease) var objectSpeed = 16; // Initial speed (will increase) var minSpawnInterval = 24; var maxObjectSpeed = 38; var score = 0; var lastMoveX = null; var lastMoveY = null; var dragStartX = null; var dragStartY = null; var dragActive = false; // Initialize runner runner = new Runner(); game.addChild(runner); // Helper: spawn a new object (randomly prod or distraction) function spawnObject() { // Randomly choose lane (0, 1, or 2) var lane = Math.floor(Math.random() * laneCount); // Randomly choose type (60% prod, 40% distraction) var isProd = Math.random() < 0.6; var obj; if (isProd) { obj = new ProdIcon(); } else { obj = new DistractionIcon(); } // Ensure object spawns only in a lane, never between lanes obj.lane = lane; obj.x = laneX[lane]; obj.y = -100; // Start just above screen // For distraction (obstacle), randomly assign a higher speed to some for reflex challenge if (obj.type === 'distraction' && Math.random() < 0.4) { // 40% of obstacles are fast (reflex test) obj.speed = objectSpeed + 10 + Math.floor(Math.random() * 6); // +10~+15 px/frame } else { obj.speed = objectSpeed; } // Track lastY and lastWasIntersecting for event detection obj.lastY = obj.y; obj.lastWasIntersecting = false; objects.push(obj); game.addChild(obj); } // Touch/drag/swipe controls game.down = function (x, y, obj) { dragStartX = x; dragStartY = y; dragActive = true; lastMoveX = x; lastMoveY = y; }; game.move = function (x, y, obj) { if (!dragActive) return; var dx = x - dragStartX; var dy = y - dragStartY; // Only consider horizontal swipes if (Math.abs(dx) > 80 && Math.abs(dx) > Math.abs(dy)) { if (dx > 0 && runner.lane < laneCount - 1) { runner.moveToLane(runner.lane + 1); dragActive = false; } else if (dx < 0 && runner.lane > 0) { runner.moveToLane(runner.lane - 1); dragActive = false; } } lastMoveX = x; lastMoveY = y; }; game.up = function (x, y, obj) { // If tap (not swipe), move to lane based on tap position if (dragActive && Math.abs(x - dragStartX) < 40 && Math.abs(y - dragStartY) < 40) { // Tap: move to nearest lane var tapLane = 0; var minDist = Math.abs(x - laneX[0]); for (var i = 1; i < laneCount; i++) { var d = Math.abs(x - laneX[i]); if (d < minDist) { minDist = d; tapLane = i; } } runner.moveToLane(tapLane); } dragActive = false; dragStartX = null; dragStartY = null; }; // Main game loop game.update = function () { // Increase difficulty over time if (LK.ticks % 180 == 0) { // Every 3 seconds if (spawnInterval > minSpawnInterval) spawnInterval -= 2; if (objectSpeed < maxObjectSpeed) objectSpeed += 1; } // Spawn objects spawnTimer++; if (spawnTimer >= spawnInterval) { spawnObject(); spawnTimer = 0; } // Move objects and check collisions for (var i = objects.length - 1; i >= 0; i--) { var obj = objects[i]; obj.y += obj.speed; // Remove if off screen (use lastY for transition detection) if (obj.lastY <= 2800 && obj.y > 2800) { obj.destroy(); objects.splice(i, 1); continue; } // Collision with runner (use lastWasIntersecting for precise event) // Only check for collision if in the same lane and vertical overlap var isIntersecting = obj.lane === runner.lane && Math.abs(obj.y - runner.y + 40) < 120; if (!obj.lastWasIntersecting && isIntersecting) { if (obj.type === 'prod') { // Collect score += 1; LK.setScore(score); scoreTxt.setText(LK.getScore()); LK.effects.flashObject(runner, 0x4caf50, 200); obj.destroy(); objects.splice(i, 1); } else if (obj.type === 'distraction') { // Hit distraction: game over LK.effects.flashScreen(0xf44336, 800); LK.showGameOver(); return; } } // Update lastY and lastWasIntersecting for next frame obj.lastY = obj.y; obj.lastWasIntersecting = isIntersecting; } }; // Center runner vertically (bottom 1/5th of screen) runner.y = 2732 - 350; // Initial score score = 0; LK.setScore(score); scoreTxt.setText(LK.getScore()); // Play background music at game start LK.playMusic('Mozart');
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Distraction Icon (obstacle)
var DistractionIcon = Container.expand(function () {
var self = Container.call(this);
var icon = self.attachAsset('distraction_icon', {
anchorX: 0.5,
anchorY: 0.5
});
self.type = 'distraction';
self.lane = 1;
self.speed = 16; // Will be set by game, but default here
return self;
});
// Productivity Icon (collectible)
var ProdIcon = Container.expand(function () {
var self = Container.call(this);
var icon = self.attachAsset('prod_icon', {
anchorX: 0.5,
anchorY: 0.5
});
self.type = 'prod';
self.lane = 1;
self.speed = 16; // Will be set by game, but default here
return self;
});
// Runner class
var Runner = Container.expand(function () {
var self = Container.call(this);
var runnerSprite = self.attachAsset('runner', {
anchorX: 0.5,
anchorY: 1
});
self.lane = 1; // Start in center lane (0: left, 1: center, 2: right)
self.y = 2200; // Near bottom of screen
// Move to lane with tween
self.moveToLane = function (targetLane) {
if (targetLane < 0 || targetLane >= laneCount) return;
self.lane = targetLane;
tween(self, {
x: laneX[targetLane]
}, {
duration: 120,
easing: tween.cubicOut
});
};
// Set initial position
self.x = laneX[self.lane];
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Lane positions (global, so all classes can access)
// Runner character
// Distraction icons (obstacles)
// Productivity icons (collectibles)
// Score display
var laneCount = 3;
var laneX = [2048 / 2 - 300,
// Left lane
2048 / 2,
// Center lane
2048 / 2 + 300 // Right lane
];
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Game variables
var runner;
var objects = []; // All collectibles and obstacles
var spawnTimer = 0;
var spawnInterval = 60; // Frames between spawns (will decrease)
var objectSpeed = 16; // Initial speed (will increase)
var minSpawnInterval = 24;
var maxObjectSpeed = 38;
var score = 0;
var lastMoveX = null;
var lastMoveY = null;
var dragStartX = null;
var dragStartY = null;
var dragActive = false;
// Initialize runner
runner = new Runner();
game.addChild(runner);
// Helper: spawn a new object (randomly prod or distraction)
function spawnObject() {
// Randomly choose lane (0, 1, or 2)
var lane = Math.floor(Math.random() * laneCount);
// Randomly choose type (60% prod, 40% distraction)
var isProd = Math.random() < 0.6;
var obj;
if (isProd) {
obj = new ProdIcon();
} else {
obj = new DistractionIcon();
}
// Ensure object spawns only in a lane, never between lanes
obj.lane = lane;
obj.x = laneX[lane];
obj.y = -100; // Start just above screen
// For distraction (obstacle), randomly assign a higher speed to some for reflex challenge
if (obj.type === 'distraction' && Math.random() < 0.4) {
// 40% of obstacles are fast (reflex test)
obj.speed = objectSpeed + 10 + Math.floor(Math.random() * 6); // +10~+15 px/frame
} else {
obj.speed = objectSpeed;
}
// Track lastY and lastWasIntersecting for event detection
obj.lastY = obj.y;
obj.lastWasIntersecting = false;
objects.push(obj);
game.addChild(obj);
}
// Touch/drag/swipe controls
game.down = function (x, y, obj) {
dragStartX = x;
dragStartY = y;
dragActive = true;
lastMoveX = x;
lastMoveY = y;
};
game.move = function (x, y, obj) {
if (!dragActive) return;
var dx = x - dragStartX;
var dy = y - dragStartY;
// Only consider horizontal swipes
if (Math.abs(dx) > 80 && Math.abs(dx) > Math.abs(dy)) {
if (dx > 0 && runner.lane < laneCount - 1) {
runner.moveToLane(runner.lane + 1);
dragActive = false;
} else if (dx < 0 && runner.lane > 0) {
runner.moveToLane(runner.lane - 1);
dragActive = false;
}
}
lastMoveX = x;
lastMoveY = y;
};
game.up = function (x, y, obj) {
// If tap (not swipe), move to lane based on tap position
if (dragActive && Math.abs(x - dragStartX) < 40 && Math.abs(y - dragStartY) < 40) {
// Tap: move to nearest lane
var tapLane = 0;
var minDist = Math.abs(x - laneX[0]);
for (var i = 1; i < laneCount; i++) {
var d = Math.abs(x - laneX[i]);
if (d < minDist) {
minDist = d;
tapLane = i;
}
}
runner.moveToLane(tapLane);
}
dragActive = false;
dragStartX = null;
dragStartY = null;
};
// Main game loop
game.update = function () {
// Increase difficulty over time
if (LK.ticks % 180 == 0) {
// Every 3 seconds
if (spawnInterval > minSpawnInterval) spawnInterval -= 2;
if (objectSpeed < maxObjectSpeed) objectSpeed += 1;
}
// Spawn objects
spawnTimer++;
if (spawnTimer >= spawnInterval) {
spawnObject();
spawnTimer = 0;
}
// Move objects and check collisions
for (var i = objects.length - 1; i >= 0; i--) {
var obj = objects[i];
obj.y += obj.speed;
// Remove if off screen (use lastY for transition detection)
if (obj.lastY <= 2800 && obj.y > 2800) {
obj.destroy();
objects.splice(i, 1);
continue;
}
// Collision with runner (use lastWasIntersecting for precise event)
// Only check for collision if in the same lane and vertical overlap
var isIntersecting = obj.lane === runner.lane && Math.abs(obj.y - runner.y + 40) < 120;
if (!obj.lastWasIntersecting && isIntersecting) {
if (obj.type === 'prod') {
// Collect
score += 1;
LK.setScore(score);
scoreTxt.setText(LK.getScore());
LK.effects.flashObject(runner, 0x4caf50, 200);
obj.destroy();
objects.splice(i, 1);
} else if (obj.type === 'distraction') {
// Hit distraction: game over
LK.effects.flashScreen(0xf44336, 800);
LK.showGameOver();
return;
}
}
// Update lastY and lastWasIntersecting for next frame
obj.lastY = obj.y;
obj.lastWasIntersecting = isIntersecting;
}
};
// Center runner vertically (bottom 1/5th of screen)
runner.y = 2732 - 350;
// Initial score
score = 0;
LK.setScore(score);
scoreTxt.setText(LK.getScore());
// Play background music at game start
LK.playMusic('Mozart');