/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Barrier (obstacle) var Barrier = Container.expand(function () { var self = Container.call(this); var barrierSprite = self.attachAsset('barrier', { anchorX: 0.5, anchorY: 1 }); self.width = barrierSprite.width; self.height = barrierSprite.height; self.speed = 0; // Will be set by game // Called every tick self.update = function () { self.x -= self.speed; }; return self; }); // PrintableBlock (special printable barrier) var PrintableBlock = Container.expand(function () { var self = Container.call(this); var blockSprite = self.attachAsset('barrier', { anchorX: 0.5, anchorY: 1 }); self.width = blockSprite.width; self.height = blockSprite.height; self.speed = 0; // Will be set by game // Optionally, visually distinguish printable blocks (e.g. by color) blockSprite.color = 0x27ae60; // greenish, but engine may ignore this // Called every tick self.update = function () { self.x -= self.speed; }; return self; }); // Runner (player character) var Runner = Container.expand(function () { var self = Container.call(this); var runnerSprite = self.attachAsset('runner', { anchorX: 0.5, anchorY: 1 }); self.width = runnerSprite.width; self.height = runnerSprite.height; self.groundY = 0; // Will be set after ground is created self.isJumping = false; self.jumpStartY = 0; self.jumpStartTime = 0; self.jumpDuration = 0; self.jumpHeight = 0; self.velocityY = 0; // Physics self.gravity = 1.2; // px per tick^2 (reduced for slower fall) self.jumpVelocity = -52; // px per tick (negative is up) // Called every tick self.update = function () { // If jumping, apply velocity if (self.isJumping) { self.y += self.velocityY; self.velocityY += self.gravity; // Landed if (self.y >= self.groundY) { self.y = self.groundY; self.isJumping = false; self.velocityY = 0; } } }; // Start jump self.jump = function () { if (!self.isJumping) { self.isJumping = true; self.velocityY = self.jumpVelocity; } }; // Cancel jump (for variable jump height) self.cancelJump = function () { if (self.isJumping && self.velocityY < -18) { self.velocityY = -18; } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x0a0a2a }); /**** * Game Code ****/ // Pixel starry night background var backgroundStars = []; var BG_STAR_SCROLL_SPEED = 1.2; // slower for parallax var BG_STAR_LAYERS = 3; var BG_STARS_PER_LAYER = [32, 24, 16]; // more in front, less in back var BG_STAR_COLORS = [0xffffff, // white 0xcce6ff, // blueish 0xfff6cc, // yellowish 0xffcccc, // reddish 0xb0b0ff // purple/blue ]; var BG_STAR_SIZES = [6, 10, 16]; // back, mid, front function createBackgroundStars() { for (var layer = 0; layer < BG_STAR_LAYERS; layer++) { var count = BG_STARS_PER_LAYER[layer]; var size = BG_STAR_SIZES[layer]; for (var i = 0; i < count; i++) { var color = BG_STAR_COLORS[Math.floor(Math.random() * BG_STAR_COLORS.length)]; var star = LK.getAsset('ground', { anchorX: 0.5, anchorY: 0.5, width: size + Math.floor(Math.random() * 2), height: size + Math.floor(Math.random() * 2), x: Math.floor(Math.random() * 2048), y: Math.floor(Math.random() * 2200) }); star.tint = color; // Twinkle: randomize alpha, and store twinkle params star.alpha = 0.7 + Math.random() * 0.3; star._twinkleSpeed = 0.008 + Math.random() * 0.012 + layer * 0.004; star._twinklePhase = Math.random() * Math.PI * 2; star._starLayer = layer; backgroundStars.push(star); game.addChild(star); } } } createBackgroundStars(); function updateBackgroundStars() { for (var i = 0; i < backgroundStars.length; i++) { var star = backgroundStars[i]; // Parallax: slower for further layers var speed = BG_STAR_SCROLL_SPEED * (0.5 + 0.5 * (BG_STAR_LAYERS - star._starLayer) / BG_STAR_LAYERS); star.x -= speed; // Twinkle star.alpha = 0.7 + 0.3 * Math.sin(LK.ticks * star._twinkleSpeed + star._twinklePhase); // Wrap around if (star.x < -star.width) { star.x = 2048 + Math.random() * 80; star.y = Math.floor(Math.random() * 2200); // Optionally randomize color and twinkle star.tint = BG_STAR_COLORS[Math.floor(Math.random() * BG_STAR_COLORS.length)]; star._twinkleSpeed = 0.008 + Math.random() * 0.012 + star._starLayer * 0.004; star._twinklePhase = Math.random() * Math.PI * 2; } } } // Game constants var GROUND_Y = 2200; // y position of ground top var RUNNER_START_X = 400; var BARRIER_MIN_GAP = 600; var BARRIER_MAX_GAP = 950; var BARRIER_MIN_HEIGHT = 180; var BARRIER_MAX_HEIGHT = 320; var BARRIER_WIDTH = 80; var SCROLL_START_SPEED = 13; var SCROLL_MAX_SPEED = 24; var SCROLL_ACCEL = 0.018; // px per tick^2 // Character (costume) options var CHARACTER_OPTIONS = [{ id: 'runner', color: 0x2d9cdb }, // blue { id: 'runner', color: 0xe67e22 }, // orange { id: 'runner', color: 0x27ae60 }, // green { id: 'runner', color: 0x8e44ad } // purple ]; var selectedCharacterIndex = 0; // Game state var runner = null; var ground = null; var barriers = []; var score = 0; var scrollSpeed = SCROLL_START_SPEED; var lastBarrierX = 0; var isGameOver = false; var isTouching = false; // Menu state var isMenuOpen = true; var menuContainer = null; var costumeContainer = null; // Score display var scoreTxt = new Text2('0', { size: 140, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Dash button UI var dashBtn = LK.getAsset('barrier', { anchorX: 0.5, anchorY: 0.5, width: 320, height: 160, x: 2048 - 240, y: 2732 - 220 }); dashBtn.tint = 0x2d9cdb; var dashTxt = new Text2('DASH', { size: 80, fill: 0xffffff }); dashTxt.anchor.set(0.5, 0.5); dashTxt.x = dashBtn.x; dashTxt.y = dashBtn.y; LK.gui.addChild(dashBtn); LK.gui.addChild(dashTxt); // Dash state var dashActive = false; var dashCooldown = false; var dashDuration = 32; // frames (about 0.5s) var dashCooldownTime = 90; // frames (about 1.5s) var dashTimer = 0; var dashCooldownTimer = 0; // Dash button event dashBtn.down = function (x, y, obj) { if (isMenuOpen || isGameOver) return; if (!dashActive && !dashCooldown) { dashActive = true; dashTimer = dashDuration; dashCooldown = false; dashBtn.alpha = 0.5; dashTxt.alpha = 0.5; } }; dashTxt.down = dashBtn.down; // Create ground ground = LK.getAsset('ground', { anchorX: 0, anchorY: 0, x: 0, y: GROUND_Y }); game.addChild(ground); // Create runner (with selected costume) function createRunnerWithCostume() { var costume = CHARACTER_OPTIONS[selectedCharacterIndex]; var r = new Runner(); // Set color if possible if (r.children && r.children[0]) { r.children[0].tint = costume.color; } r.x = RUNNER_START_X; r.groundY = GROUND_Y; r.y = GROUND_Y; return r; } runner = createRunnerWithCostume(); game.addChild(runner); // Menu UI function showMenu() { isMenuOpen = true; if (menuContainer) { menuContainer.visible = true; return; } menuContainer = new Container(); // Background overlay var overlay = LK.getAsset('ground', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732 }); overlay.alpha = 0.85; menuContainer.addChild(overlay); // Title var title = new Text2('Endless Runner', { size: 180, fill: 0xffffff }); title.anchor.set(0.5, 0); title.x = 2048 / 2; title.y = 400; menuContainer.addChild(title); // Play button var playBtn = LK.getAsset('barrier', { anchorX: 0.5, anchorY: 0.5, width: 500, height: 180, x: 2048 / 2, y: 900 }); playBtn.tint = 0x27ae60; menuContainer.addChild(playBtn); var playTxt = new Text2('PLAY', { size: 120, fill: 0xffffff }); playTxt.anchor.set(0.5, 0.5); playTxt.x = playBtn.x; playTxt.y = playBtn.y; menuContainer.addChild(playTxt); // Color button var colorBtn = LK.getAsset('barrier', { anchorX: 0.5, anchorY: 0.5, width: 500, height: 180, x: 2048 / 2, y: 1200 }); colorBtn.tint = 0x2980b9; menuContainer.addChild(colorBtn); var colorTxt = new Text2('COLOR', { size: 100, fill: 0xffffff }); colorTxt.anchor.set(0.5, 0.5); colorTxt.x = colorBtn.x; colorTxt.y = colorBtn.y; menuContainer.addChild(colorTxt); // Play button event playBtn.down = function (x, y, obj) { hideMenu(); startGame(); }; playTxt.down = playBtn.down; // Color button event colorBtn.down = function (x, y, obj) { showCostumeMenu(); }; colorTxt.down = colorBtn.down; game.addChild(menuContainer); } function hideMenu() { isMenuOpen = false; if (menuContainer) menuContainer.visible = false; if (costumeContainer) costumeContainer.visible = false; } function showCostumeMenu() { if (!costumeContainer) { costumeContainer = new Container(); // Overlay var overlay = LK.getAsset('ground', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732 }); overlay.alpha = 0.85; costumeContainer.addChild(overlay); // Title var title = new Text2('Select Costume', { size: 150, fill: 0xffffff }); title.anchor.set(0.5, 0); title.x = 2048 / 2; title.y = 400; costumeContainer.addChild(title); // Costume preview var preview = LK.getAsset('runner', { anchorX: 0.5, anchorY: 1, x: 2048 / 2, y: 1200, width: 300, height: 300 }); preview.tint = CHARACTER_OPTIONS[selectedCharacterIndex].color; costumeContainer.addChild(preview); // Left arrow var leftBtn = LK.getAsset('barrier', { anchorX: 0.5, anchorY: 0.5, width: 120, height: 120, x: 2048 / 2 - 250, y: 1200 }); leftBtn.tint = 0x888888; costumeContainer.addChild(leftBtn); var leftTxt = new Text2('<', { size: 120, fill: 0xffffff }); leftTxt.anchor.set(0.5, 0.5); leftTxt.x = leftBtn.x; leftTxt.y = leftBtn.y; costumeContainer.addChild(leftTxt); // Right arrow var rightBtn = LK.getAsset('barrier', { anchorX: 0.5, anchorY: 0.5, width: 120, height: 120, x: 2048 / 2 + 250, y: 1200 }); rightBtn.tint = 0x888888; costumeContainer.addChild(rightBtn); var rightTxt = new Text2('>', { size: 120, fill: 0xffffff }); rightTxt.anchor.set(0.5, 0.5); rightTxt.x = rightBtn.x; rightTxt.y = rightBtn.y; costumeContainer.addChild(rightTxt); // Select button var selectBtn = LK.getAsset('barrier', { anchorX: 0.5, anchorY: 0.5, width: 400, height: 140, x: 2048 / 2, y: 1700 }); selectBtn.tint = 0x27ae60; costumeContainer.addChild(selectBtn); var selectTxt = new Text2('SELECT', { size: 90, fill: 0xffffff }); selectTxt.anchor.set(0.5, 0.5); selectTxt.x = selectBtn.x; selectTxt.y = selectBtn.y; costumeContainer.addChild(selectTxt); // Arrow events leftBtn.down = function (x, y, obj) { selectedCharacterIndex = (selectedCharacterIndex + CHARACTER_OPTIONS.length - 1) % CHARACTER_OPTIONS.length; preview.tint = CHARACTER_OPTIONS[selectedCharacterIndex].color; }; leftTxt.down = leftBtn.down; rightBtn.down = function (x, y, obj) { selectedCharacterIndex = (selectedCharacterIndex + 1) % CHARACTER_OPTIONS.length; preview.tint = CHARACTER_OPTIONS[selectedCharacterIndex].color; }; rightTxt.down = rightBtn.down; // Select event selectBtn.down = function (x, y, obj) { if (runner) { runner.destroy(); } runner = createRunnerWithCostume(); game.addChild(runner); costumeContainer.visible = false; menuContainer.visible = true; }; selectTxt.down = selectBtn.down; game.addChild(costumeContainer); } else { // Update preview var preview = costumeContainer.children[2]; preview.tint = CHARACTER_OPTIONS[selectedCharacterIndex].color; costumeContainer.visible = true; } if (menuContainer) menuContainer.visible = false; } // Start game from menu function startGame() { hideMenu(); resetGame(); } // Show menu on load showMenu(); // Helper: spawn a barrier at x function spawnBarrier(x) { // 25% chance to spawn a PrintableBlock instead of a Barrier var isPrintable = Math.random() < 0.25; var barrier; if (isPrintable) { barrier = new PrintableBlock(); } else { barrier = new Barrier(); } barrier.x = x; // Randomize height var h = BARRIER_MIN_HEIGHT + Math.floor(Math.random() * (BARRIER_MAX_HEIGHT - BARRIER_MIN_HEIGHT + 1)); barrier.height = h; barrier.children[0].height = h; barrier.y = GROUND_Y; barrier.speed = scrollSpeed; barriers.push(barrier); game.addChild(barrier); } // Helper: reset game state function resetGame() { // Remove barriers for (var i = 0; i < barriers.length; i++) { barriers[i].destroy(); } barriers = []; score = 0; scrollSpeed = SCROLL_START_SPEED; lastBarrierX = 1400; isGameOver = false; runner.x = RUNNER_START_X; runner.y = GROUND_Y; runner.isJumping = false; runner.velocityY = 0; scoreTxt.setText('0'); // Spawn initial barriers for (var i = 0; i < 3; i++) { var gap = BARRIER_MIN_GAP + Math.floor(Math.random() * (BARRIER_MAX_GAP - BARRIER_MIN_GAP + 1)); lastBarrierX += gap; spawnBarrier(lastBarrierX); } } // Start game resetGame(); // Touch/press to jump game.down = function (x, y, obj) { if (isMenuOpen || isGameOver) return; isTouching = true; runner.jump(); }; // Release to cut jump short game.up = function (x, y, obj) { if (isMenuOpen || isGameOver) return; isTouching = false; runner.cancelJump(); }; // Main update loop game.update = function () { if (isMenuOpen || isGameOver) return; updateBackgroundStars(); // Dash logic if (dashActive) { // Temporarily boost scroll speed scrollSpeed += 13; dashTimer--; if (dashTimer <= 0) { dashActive = false; dashCooldown = true; dashCooldownTimer = dashCooldownTime; dashBtn.alpha = 0.3; dashTxt.alpha = 0.3; } } else if (dashCooldown) { dashCooldownTimer--; if (dashCooldownTimer <= 0) { dashCooldown = false; dashBtn.alpha = 1; dashTxt.alpha = 1; } } // Accelerate scroll speed if (scrollSpeed < SCROLL_MAX_SPEED) { scrollSpeed += SCROLL_ACCEL; if (scrollSpeed > SCROLL_MAX_SPEED) scrollSpeed = SCROLL_MAX_SPEED; } // Update runner runner.update(); // Update barriers for (var i = barriers.length - 1; i >= 0; i--) { var barrier = barriers[i]; barrier.speed = scrollSpeed; barrier.update(); // Passed barrier if (!barrier.passed && barrier.x + BARRIER_WIDTH / 2 < runner.x - runner.width / 2) { barrier.passed = true; score += 1; scoreTxt.setText(score + ''); LK.setScore(score); } // Remove off-screen barriers if (barrier.x < -BARRIER_WIDTH) { barrier.destroy(); barriers.splice(i, 1); } } // Spawn new barriers if (barriers.length === 0 || barriers[barriers.length - 1].x < 2048 - BARRIER_MAX_GAP) { var gap = BARRIER_MIN_GAP + Math.floor(Math.random() * (BARRIER_MAX_GAP - BARRIER_MIN_GAP + 1)); var newX = 2048 + gap; spawnBarrier(newX); } // Collision detection for (var i = 0; i < barriers.length; i++) { var barrier = barriers[i]; if (runner.intersects(barrier)) { // Game over isGameOver = true; LK.effects.flashScreen(0xff0000, 800); LK.showGameOver(); return; } } }; // On game over, reset state for next run LK.on('gameover', function () { showMenu(); }); // Prevent elements in top left 100x100 // (All elements are placed away from this area by design)
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Barrier (obstacle)
var Barrier = Container.expand(function () {
var self = Container.call(this);
var barrierSprite = self.attachAsset('barrier', {
anchorX: 0.5,
anchorY: 1
});
self.width = barrierSprite.width;
self.height = barrierSprite.height;
self.speed = 0; // Will be set by game
// Called every tick
self.update = function () {
self.x -= self.speed;
};
return self;
});
// PrintableBlock (special printable barrier)
var PrintableBlock = Container.expand(function () {
var self = Container.call(this);
var blockSprite = self.attachAsset('barrier', {
anchorX: 0.5,
anchorY: 1
});
self.width = blockSprite.width;
self.height = blockSprite.height;
self.speed = 0; // Will be set by game
// Optionally, visually distinguish printable blocks (e.g. by color)
blockSprite.color = 0x27ae60; // greenish, but engine may ignore this
// Called every tick
self.update = function () {
self.x -= self.speed;
};
return self;
});
// Runner (player character)
var Runner = Container.expand(function () {
var self = Container.call(this);
var runnerSprite = self.attachAsset('runner', {
anchorX: 0.5,
anchorY: 1
});
self.width = runnerSprite.width;
self.height = runnerSprite.height;
self.groundY = 0; // Will be set after ground is created
self.isJumping = false;
self.jumpStartY = 0;
self.jumpStartTime = 0;
self.jumpDuration = 0;
self.jumpHeight = 0;
self.velocityY = 0;
// Physics
self.gravity = 1.2; // px per tick^2 (reduced for slower fall)
self.jumpVelocity = -52; // px per tick (negative is up)
// Called every tick
self.update = function () {
// If jumping, apply velocity
if (self.isJumping) {
self.y += self.velocityY;
self.velocityY += self.gravity;
// Landed
if (self.y >= self.groundY) {
self.y = self.groundY;
self.isJumping = false;
self.velocityY = 0;
}
}
};
// Start jump
self.jump = function () {
if (!self.isJumping) {
self.isJumping = true;
self.velocityY = self.jumpVelocity;
}
};
// Cancel jump (for variable jump height)
self.cancelJump = function () {
if (self.isJumping && self.velocityY < -18) {
self.velocityY = -18;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x0a0a2a
});
/****
* Game Code
****/
// Pixel starry night background
var backgroundStars = [];
var BG_STAR_SCROLL_SPEED = 1.2; // slower for parallax
var BG_STAR_LAYERS = 3;
var BG_STARS_PER_LAYER = [32, 24, 16]; // more in front, less in back
var BG_STAR_COLORS = [0xffffff,
// white
0xcce6ff,
// blueish
0xfff6cc,
// yellowish
0xffcccc,
// reddish
0xb0b0ff // purple/blue
];
var BG_STAR_SIZES = [6, 10, 16]; // back, mid, front
function createBackgroundStars() {
for (var layer = 0; layer < BG_STAR_LAYERS; layer++) {
var count = BG_STARS_PER_LAYER[layer];
var size = BG_STAR_SIZES[layer];
for (var i = 0; i < count; i++) {
var color = BG_STAR_COLORS[Math.floor(Math.random() * BG_STAR_COLORS.length)];
var star = LK.getAsset('ground', {
anchorX: 0.5,
anchorY: 0.5,
width: size + Math.floor(Math.random() * 2),
height: size + Math.floor(Math.random() * 2),
x: Math.floor(Math.random() * 2048),
y: Math.floor(Math.random() * 2200)
});
star.tint = color;
// Twinkle: randomize alpha, and store twinkle params
star.alpha = 0.7 + Math.random() * 0.3;
star._twinkleSpeed = 0.008 + Math.random() * 0.012 + layer * 0.004;
star._twinklePhase = Math.random() * Math.PI * 2;
star._starLayer = layer;
backgroundStars.push(star);
game.addChild(star);
}
}
}
createBackgroundStars();
function updateBackgroundStars() {
for (var i = 0; i < backgroundStars.length; i++) {
var star = backgroundStars[i];
// Parallax: slower for further layers
var speed = BG_STAR_SCROLL_SPEED * (0.5 + 0.5 * (BG_STAR_LAYERS - star._starLayer) / BG_STAR_LAYERS);
star.x -= speed;
// Twinkle
star.alpha = 0.7 + 0.3 * Math.sin(LK.ticks * star._twinkleSpeed + star._twinklePhase);
// Wrap around
if (star.x < -star.width) {
star.x = 2048 + Math.random() * 80;
star.y = Math.floor(Math.random() * 2200);
// Optionally randomize color and twinkle
star.tint = BG_STAR_COLORS[Math.floor(Math.random() * BG_STAR_COLORS.length)];
star._twinkleSpeed = 0.008 + Math.random() * 0.012 + star._starLayer * 0.004;
star._twinklePhase = Math.random() * Math.PI * 2;
}
}
}
// Game constants
var GROUND_Y = 2200; // y position of ground top
var RUNNER_START_X = 400;
var BARRIER_MIN_GAP = 600;
var BARRIER_MAX_GAP = 950;
var BARRIER_MIN_HEIGHT = 180;
var BARRIER_MAX_HEIGHT = 320;
var BARRIER_WIDTH = 80;
var SCROLL_START_SPEED = 13;
var SCROLL_MAX_SPEED = 24;
var SCROLL_ACCEL = 0.018; // px per tick^2
// Character (costume) options
var CHARACTER_OPTIONS = [{
id: 'runner',
color: 0x2d9cdb
},
// blue
{
id: 'runner',
color: 0xe67e22
},
// orange
{
id: 'runner',
color: 0x27ae60
},
// green
{
id: 'runner',
color: 0x8e44ad
} // purple
];
var selectedCharacterIndex = 0;
// Game state
var runner = null;
var ground = null;
var barriers = [];
var score = 0;
var scrollSpeed = SCROLL_START_SPEED;
var lastBarrierX = 0;
var isGameOver = false;
var isTouching = false;
// Menu state
var isMenuOpen = true;
var menuContainer = null;
var costumeContainer = null;
// Score display
var scoreTxt = new Text2('0', {
size: 140,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Dash button UI
var dashBtn = LK.getAsset('barrier', {
anchorX: 0.5,
anchorY: 0.5,
width: 320,
height: 160,
x: 2048 - 240,
y: 2732 - 220
});
dashBtn.tint = 0x2d9cdb;
var dashTxt = new Text2('DASH', {
size: 80,
fill: 0xffffff
});
dashTxt.anchor.set(0.5, 0.5);
dashTxt.x = dashBtn.x;
dashTxt.y = dashBtn.y;
LK.gui.addChild(dashBtn);
LK.gui.addChild(dashTxt);
// Dash state
var dashActive = false;
var dashCooldown = false;
var dashDuration = 32; // frames (about 0.5s)
var dashCooldownTime = 90; // frames (about 1.5s)
var dashTimer = 0;
var dashCooldownTimer = 0;
// Dash button event
dashBtn.down = function (x, y, obj) {
if (isMenuOpen || isGameOver) return;
if (!dashActive && !dashCooldown) {
dashActive = true;
dashTimer = dashDuration;
dashCooldown = false;
dashBtn.alpha = 0.5;
dashTxt.alpha = 0.5;
}
};
dashTxt.down = dashBtn.down;
// Create ground
ground = LK.getAsset('ground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: GROUND_Y
});
game.addChild(ground);
// Create runner (with selected costume)
function createRunnerWithCostume() {
var costume = CHARACTER_OPTIONS[selectedCharacterIndex];
var r = new Runner();
// Set color if possible
if (r.children && r.children[0]) {
r.children[0].tint = costume.color;
}
r.x = RUNNER_START_X;
r.groundY = GROUND_Y;
r.y = GROUND_Y;
return r;
}
runner = createRunnerWithCostume();
game.addChild(runner);
// Menu UI
function showMenu() {
isMenuOpen = true;
if (menuContainer) {
menuContainer.visible = true;
return;
}
menuContainer = new Container();
// Background overlay
var overlay = LK.getAsset('ground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732
});
overlay.alpha = 0.85;
menuContainer.addChild(overlay);
// Title
var title = new Text2('Endless Runner', {
size: 180,
fill: 0xffffff
});
title.anchor.set(0.5, 0);
title.x = 2048 / 2;
title.y = 400;
menuContainer.addChild(title);
// Play button
var playBtn = LK.getAsset('barrier', {
anchorX: 0.5,
anchorY: 0.5,
width: 500,
height: 180,
x: 2048 / 2,
y: 900
});
playBtn.tint = 0x27ae60;
menuContainer.addChild(playBtn);
var playTxt = new Text2('PLAY', {
size: 120,
fill: 0xffffff
});
playTxt.anchor.set(0.5, 0.5);
playTxt.x = playBtn.x;
playTxt.y = playBtn.y;
menuContainer.addChild(playTxt);
// Color button
var colorBtn = LK.getAsset('barrier', {
anchorX: 0.5,
anchorY: 0.5,
width: 500,
height: 180,
x: 2048 / 2,
y: 1200
});
colorBtn.tint = 0x2980b9;
menuContainer.addChild(colorBtn);
var colorTxt = new Text2('COLOR', {
size: 100,
fill: 0xffffff
});
colorTxt.anchor.set(0.5, 0.5);
colorTxt.x = colorBtn.x;
colorTxt.y = colorBtn.y;
menuContainer.addChild(colorTxt);
// Play button event
playBtn.down = function (x, y, obj) {
hideMenu();
startGame();
};
playTxt.down = playBtn.down;
// Color button event
colorBtn.down = function (x, y, obj) {
showCostumeMenu();
};
colorTxt.down = colorBtn.down;
game.addChild(menuContainer);
}
function hideMenu() {
isMenuOpen = false;
if (menuContainer) menuContainer.visible = false;
if (costumeContainer) costumeContainer.visible = false;
}
function showCostumeMenu() {
if (!costumeContainer) {
costumeContainer = new Container();
// Overlay
var overlay = LK.getAsset('ground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732
});
overlay.alpha = 0.85;
costumeContainer.addChild(overlay);
// Title
var title = new Text2('Select Costume', {
size: 150,
fill: 0xffffff
});
title.anchor.set(0.5, 0);
title.x = 2048 / 2;
title.y = 400;
costumeContainer.addChild(title);
// Costume preview
var preview = LK.getAsset('runner', {
anchorX: 0.5,
anchorY: 1,
x: 2048 / 2,
y: 1200,
width: 300,
height: 300
});
preview.tint = CHARACTER_OPTIONS[selectedCharacterIndex].color;
costumeContainer.addChild(preview);
// Left arrow
var leftBtn = LK.getAsset('barrier', {
anchorX: 0.5,
anchorY: 0.5,
width: 120,
height: 120,
x: 2048 / 2 - 250,
y: 1200
});
leftBtn.tint = 0x888888;
costumeContainer.addChild(leftBtn);
var leftTxt = new Text2('<', {
size: 120,
fill: 0xffffff
});
leftTxt.anchor.set(0.5, 0.5);
leftTxt.x = leftBtn.x;
leftTxt.y = leftBtn.y;
costumeContainer.addChild(leftTxt);
// Right arrow
var rightBtn = LK.getAsset('barrier', {
anchorX: 0.5,
anchorY: 0.5,
width: 120,
height: 120,
x: 2048 / 2 + 250,
y: 1200
});
rightBtn.tint = 0x888888;
costumeContainer.addChild(rightBtn);
var rightTxt = new Text2('>', {
size: 120,
fill: 0xffffff
});
rightTxt.anchor.set(0.5, 0.5);
rightTxt.x = rightBtn.x;
rightTxt.y = rightBtn.y;
costumeContainer.addChild(rightTxt);
// Select button
var selectBtn = LK.getAsset('barrier', {
anchorX: 0.5,
anchorY: 0.5,
width: 400,
height: 140,
x: 2048 / 2,
y: 1700
});
selectBtn.tint = 0x27ae60;
costumeContainer.addChild(selectBtn);
var selectTxt = new Text2('SELECT', {
size: 90,
fill: 0xffffff
});
selectTxt.anchor.set(0.5, 0.5);
selectTxt.x = selectBtn.x;
selectTxt.y = selectBtn.y;
costumeContainer.addChild(selectTxt);
// Arrow events
leftBtn.down = function (x, y, obj) {
selectedCharacterIndex = (selectedCharacterIndex + CHARACTER_OPTIONS.length - 1) % CHARACTER_OPTIONS.length;
preview.tint = CHARACTER_OPTIONS[selectedCharacterIndex].color;
};
leftTxt.down = leftBtn.down;
rightBtn.down = function (x, y, obj) {
selectedCharacterIndex = (selectedCharacterIndex + 1) % CHARACTER_OPTIONS.length;
preview.tint = CHARACTER_OPTIONS[selectedCharacterIndex].color;
};
rightTxt.down = rightBtn.down;
// Select event
selectBtn.down = function (x, y, obj) {
if (runner) {
runner.destroy();
}
runner = createRunnerWithCostume();
game.addChild(runner);
costumeContainer.visible = false;
menuContainer.visible = true;
};
selectTxt.down = selectBtn.down;
game.addChild(costumeContainer);
} else {
// Update preview
var preview = costumeContainer.children[2];
preview.tint = CHARACTER_OPTIONS[selectedCharacterIndex].color;
costumeContainer.visible = true;
}
if (menuContainer) menuContainer.visible = false;
}
// Start game from menu
function startGame() {
hideMenu();
resetGame();
}
// Show menu on load
showMenu();
// Helper: spawn a barrier at x
function spawnBarrier(x) {
// 25% chance to spawn a PrintableBlock instead of a Barrier
var isPrintable = Math.random() < 0.25;
var barrier;
if (isPrintable) {
barrier = new PrintableBlock();
} else {
barrier = new Barrier();
}
barrier.x = x;
// Randomize height
var h = BARRIER_MIN_HEIGHT + Math.floor(Math.random() * (BARRIER_MAX_HEIGHT - BARRIER_MIN_HEIGHT + 1));
barrier.height = h;
barrier.children[0].height = h;
barrier.y = GROUND_Y;
barrier.speed = scrollSpeed;
barriers.push(barrier);
game.addChild(barrier);
}
// Helper: reset game state
function resetGame() {
// Remove barriers
for (var i = 0; i < barriers.length; i++) {
barriers[i].destroy();
}
barriers = [];
score = 0;
scrollSpeed = SCROLL_START_SPEED;
lastBarrierX = 1400;
isGameOver = false;
runner.x = RUNNER_START_X;
runner.y = GROUND_Y;
runner.isJumping = false;
runner.velocityY = 0;
scoreTxt.setText('0');
// Spawn initial barriers
for (var i = 0; i < 3; i++) {
var gap = BARRIER_MIN_GAP + Math.floor(Math.random() * (BARRIER_MAX_GAP - BARRIER_MIN_GAP + 1));
lastBarrierX += gap;
spawnBarrier(lastBarrierX);
}
}
// Start game
resetGame();
// Touch/press to jump
game.down = function (x, y, obj) {
if (isMenuOpen || isGameOver) return;
isTouching = true;
runner.jump();
};
// Release to cut jump short
game.up = function (x, y, obj) {
if (isMenuOpen || isGameOver) return;
isTouching = false;
runner.cancelJump();
};
// Main update loop
game.update = function () {
if (isMenuOpen || isGameOver) return;
updateBackgroundStars();
// Dash logic
if (dashActive) {
// Temporarily boost scroll speed
scrollSpeed += 13;
dashTimer--;
if (dashTimer <= 0) {
dashActive = false;
dashCooldown = true;
dashCooldownTimer = dashCooldownTime;
dashBtn.alpha = 0.3;
dashTxt.alpha = 0.3;
}
} else if (dashCooldown) {
dashCooldownTimer--;
if (dashCooldownTimer <= 0) {
dashCooldown = false;
dashBtn.alpha = 1;
dashTxt.alpha = 1;
}
}
// Accelerate scroll speed
if (scrollSpeed < SCROLL_MAX_SPEED) {
scrollSpeed += SCROLL_ACCEL;
if (scrollSpeed > SCROLL_MAX_SPEED) scrollSpeed = SCROLL_MAX_SPEED;
}
// Update runner
runner.update();
// Update barriers
for (var i = barriers.length - 1; i >= 0; i--) {
var barrier = barriers[i];
barrier.speed = scrollSpeed;
barrier.update();
// Passed barrier
if (!barrier.passed && barrier.x + BARRIER_WIDTH / 2 < runner.x - runner.width / 2) {
barrier.passed = true;
score += 1;
scoreTxt.setText(score + '');
LK.setScore(score);
}
// Remove off-screen barriers
if (barrier.x < -BARRIER_WIDTH) {
barrier.destroy();
barriers.splice(i, 1);
}
}
// Spawn new barriers
if (barriers.length === 0 || barriers[barriers.length - 1].x < 2048 - BARRIER_MAX_GAP) {
var gap = BARRIER_MIN_GAP + Math.floor(Math.random() * (BARRIER_MAX_GAP - BARRIER_MIN_GAP + 1));
var newX = 2048 + gap;
spawnBarrier(newX);
}
// Collision detection
for (var i = 0; i < barriers.length; i++) {
var barrier = barriers[i];
if (runner.intersects(barrier)) {
// Game over
isGameOver = true;
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
return;
}
}
};
// On game over, reset state for next run
LK.on('gameover', function () {
showMenu();
});
// Prevent elements in top left 100x100
// (All elements are placed away from this area by design)