/****
* 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)