User prompt
make jump logic to "hold for longer jump"
User prompt
add a jump system where you can jump above obstacles if player wants to
User prompt
make the game's background a road
User prompt
allow player to use all of the game's screen
User prompt
still some coins are combined into obstacles fix it
User prompt
some coins are combined into obstacles fix it
Code edit (1 edits merged)
Please save this source code
User prompt
Infinite Dash
Initial prompt
make a endless runner game
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Coin var Coin = Container.expand(function () { var self = Container.call(this); var coinGfx = self.attachAsset('coin', { anchorX: 0.5, anchorY: 0.5 }); // Lane: 0,1,2 self.lane = 1; // For collision detection self.isCoin = true; return self; }); // UI Elements class for start menu and instructions var GameUI = Container.expand(function () { var self = Container.call(this); // (Title removed) // Create start button self.startBtn = new Container(); var startBtnText = new Text2('START GAME', { size: 100, fill: 0xffffff }); startBtnText.anchor.set(0.5, 0.5); self.startBtn.addChild(startBtnText); self.startBtn.x = 2048 / 2; self.startBtn.y = 1200; self.startBtn.interactive = true; self.addChild(self.startBtn); // Create how to play button self.instructionsBtn = new Container(); var instBtnText = new Text2('HOW TO PLAY', { size: 100, fill: 0xffffff }); instBtnText.anchor.set(0.5, 0.5); self.instructionsBtn.addChild(instBtnText); self.instructionsBtn.x = 2048 / 2; self.instructionsBtn.y = 1400; self.instructionsBtn.interactive = true; self.addChild(self.instructionsBtn); // Create instructions screen (initially hidden) self.instructions = new Container(); self.instructions.visible = false; var instTitle = new Text2('HOW TO PLAY', { size: 120, fill: 0xFFFFFF }); instTitle.anchor.set(0.5, 0); instTitle.x = 2048 / 2; instTitle.y = 600; self.instructions.addChild(instTitle); var instText1 = new Text2('• SWIPE LEFT/RIGHT to change lanes', { size: 80, fill: 0xFFFFFF }); instText1.anchor.set(0.5, 0); instText1.x = 2048 / 2; instText1.y = 800; self.instructions.addChild(instText1); var instText2 = new Text2('• Avoid obstacles by changing lanes', { size: 80, fill: 0xFFFFFF }); instText2.anchor.set(0.5, 0); instText2.x = 2048 / 2; instText2.y = 900; self.instructions.addChild(instText2); var instText4 = new Text2('• Collect coins for bonus points', { size: 80, fill: 0xFFFFFF }); instText4.anchor.set(0.5, 0); instText4.x = 2048 / 2; instText4.y = 1000; self.instructions.addChild(instText4); // Back button for instructions self.backBtn = new Container(); var backText = new Text2('BACK', { size: 80, fill: 0xFFFFFF }); backText.anchor.set(0.5, 0.5); self.backBtn.addChild(backText); self.backBtn.x = 2048 / 2; self.backBtn.y = 1400; self.backBtn.interactive = true; self.instructions.addChild(self.backBtn); self.addChild(self.instructions); // Button press handlers self.startBtn.down = function () { tween(self.startBtn, { scaleX: 0.95, scaleY: 0.95 }, { duration: 100 }); }; self.startBtn.up = function () { tween(self.startBtn, { scaleX: 1, scaleY: 1 }, { duration: 100, onFinish: function onFinish() { if (typeof self.onStartClick === 'function') { self.onStartClick(); } } }); }; self.instructionsBtn.down = function () { tween(self.instructionsBtn, { scaleX: 0.95, scaleY: 0.95 }, { duration: 100 }); }; self.instructionsBtn.up = function () { tween(self.instructionsBtn, { scaleX: 1, scaleY: 1 }, { duration: 100, onFinish: function onFinish() { if (typeof self.onInstructionsClick === 'function') { self.onInstructionsClick(); } } }); }; self.backBtn.down = function () { tween(self.backBtn, { scaleX: 0.95, scaleY: 0.95 }, { duration: 100 }); }; self.backBtn.up = function () { tween(self.backBtn, { scaleX: 1, scaleY: 1 }, { duration: 100, onFinish: function onFinish() { if (typeof self.onBackClick === 'function') { self.onBackClick(); } } }); }; // Set show/hide methods self.showMenu = function () { self.visible = true; self.startBtn.visible = true; self.instructionsBtn.visible = true; self.instructions.visible = false; }; self.showInstructions = function () { self.visible = true; self.startBtn.visible = false; self.instructionsBtn.visible = false; self.instructions.visible = true; }; self.hide = function () { self.visible = false; }; return self; }); // Obstacle var Obstacle = Container.expand(function () { var self = Container.call(this); var obsGfx = self.attachAsset('obstacle', { anchorX: 0.5, anchorY: 0.5 }); // Lane: 0,1,2 self.lane = 1; // For collision detection self.isObstacle = true; return self; }); // Runner (player character) var Runner = Container.expand(function () { var self = Container.call(this); var runnerGfx = self.attachAsset('runner', { anchorX: 0.5, anchorY: 0.5 }); // Current lane: 0 (left), 1 (center), 2 (right) self.lane = 1; self.moveToLane = function (lane, duration) { self.lane = lane; var targetX = laneToX(lane); tween(self, { x: targetX }, { duration: duration || 120, easing: tween.cubicOut, onFinish: function onFinish() { // (jumpIndicator logic removed) } }); }; // Called every frame by game.update self.update = function () {}; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // Button image assets // --- ROAD BACKGROUND --- // Character: Red box // Obstacle: Gray box // Coin: Yellow ellipse var roadBg1 = LK.getAsset('road', { anchorX: 0.5, anchorY: 0 }); roadBg1.x = 2048 / 2; roadBg1.y = 0; game.addChild(roadBg1); var roadBg2 = LK.getAsset('road', { anchorX: 0.5, anchorY: 0 }); roadBg2.x = 2048 / 2; roadBg2.y = -2732; game.addChild(roadBg2); // --- LANE SYSTEM --- var NUM_LANES = 3; var LANE_WIDTH = 2048 / NUM_LANES; // Use full width var LANE_START_X = LANE_WIDTH / 2; // Center of first lane at left edge function laneToX(lane) { // lane: 0 (left), 1 (center), 2 (right) return LANE_START_X + lane * LANE_WIDTH; } // Game state manager var GameState = { MENU: 0, INSTRUCTIONS: 1, PLAYING: 2 }; // --- GAMEPLAY VARIABLES --- var runner; var obstacles = []; var coinObjects = []; // Array for coin instances var speed = 18; // Initial speed (pixels per frame) var speedIncrease = 0.012; // Speed up per tick var spawnTimer = 0; var coinSpawnTimer = 0; var minObstacleGap = 420; // Minimum vertical gap between obstacles var lastObstacleY = 0; var isGameOver = false; // Coin system variables var coinCombo = 0; var coinComboTimer = 0; var coinComboTimeout = 60; // frames (1 second) to maintain combo var coinComboText = null; // Game state management var currentState = GameState.MENU; var gameUI = new GameUI(); game.addChild(gameUI); // Setup UI event handlers gameUI.onStartClick = function () { currentState = GameState.PLAYING; gameUI.hide(); resetGame(); // Show score and coin counter scoreTxt.visible = true; showCoinCounterAnimated(); }; gameUI.onInstructionsClick = function () { currentState = GameState.INSTRUCTIONS; gameUI.showInstructions(); }; gameUI.onBackClick = function () { currentState = GameState.MENU; gameUI.showMenu(); coinCounter.visible = false; }; // Initial setup gameUI.showMenu(); // --- SCORE AND COINS --- var score = 0; var coins = 0; // Count of collected coins var scoreTxt = new Text2('0', { size: 120, fill: "#fff" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Hide score initially scoreTxt.visible = false; // Coin counter display var coinCounter = new Container(); var coinIcon = LK.getAsset('coin', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8 }); coinIcon.x = 60; coinCounter.addChild(coinIcon); var coinsTxt = new Text2('0', { size: 100, fill: 0xFFCC00 }); coinsTxt.anchor.set(0, 0.5); coinsTxt.x = coinIcon.x + 80; coinsTxt.y = coinIcon.y; coinCounter.addChild(coinsTxt); // Position the coin counter at the top left (avoid 0,0 for menu icon, so use x=120) coinCounter.x = 120; coinCounter.y = 120; LK.gui.topLeft.addChild(coinCounter); // Hide coin counter initially coinCounter.visible = false; // Animate coin counter entrance when shown function showCoinCounterAnimated() { coinCounter.visible = true; coinCounter.alpha = 0; coinCounter.y = 60; // Start slightly above tween(coinCounter, { alpha: 1, y: 120 }, { duration: 400, easing: tween.bounceOut }); } // Function to reset game state function resetGame() { // Clean up arrays for (var i = 0; i < obstacles.length; i++) { obstacles[i].destroy(); } for (var j = 0; j < coinObjects.length; j++) { coinObjects[j].destroy(); } obstacles = []; coinObjects = []; isGameOver = false; speed = 18; score = 0; coins = 0; coinCombo = 0; coinComboTimer = 0; if (coinComboText) { coinComboText.destroy(); coinComboText = null; } LK.setScore(0); scoreTxt.setText('0'); coinsTxt.setText('0'); runner.x = laneToX(1); runner.lane = 1; runner.y = 2732 - 400; runner.isJumping = false; spawnTimer = 0; coinSpawnTimer = 0; roadBg1.y = 0; roadBg2.y = -2732; } // --- INIT RUNNER --- runner = new Runner(); runner.y = 2732 - 400; runner.x = laneToX(1); runner.lane = 1; game.addChild(runner); // --- INPUT HANDLING --- var dragStartX = null; var dragStartLane = null; var dragActive = false; // For jump hold logic function handleMove(x, y, obj) { if (currentState !== GameState.PLAYING) return; if (dragActive && dragStartX !== null) { var dx = x - dragStartX; if (Math.abs(dx) > 120) { // Swipe detected if (dx > 0 && runner.lane < NUM_LANES - 1) { runner.moveToLane(runner.lane + 1); dragStartX = x; } else if (dx < 0 && runner.lane > 0) { runner.moveToLane(runner.lane - 1); dragStartX = x; } } } } game.move = handleMove; game.down = function (x, y, obj) { if (currentState !== GameState.PLAYING) return; dragActive = true; dragStartX = x; dragStartLane = runner.lane; }; game.up = function (x, y, obj) { if (currentState !== GameState.PLAYING) return; dragActive = false; dragStartX = null; dragStartLane = null; }; // --- TAP TO MOVE (for single taps) --- game.tap = function (x, y, obj) { // Not supported by LK, so handled via down/up quick tap // (No implementation needed) }; // --- SPAWN OBSTACLES & COINS --- function spawnObstacle() { var lane = Math.floor(Math.random() * NUM_LANES); var obs = new Obstacle(); obs.lane = lane; obs.x = laneToX(lane); obs.y = -180; obstacles.push(obs); game.addChild(obs); lastObstacleY = obs.y; } function spawnCoin() { // Prevent coins from spawning in the same lane and too close to obstacles, and at the same Y as obstacles var availableLanes = [0, 1, 2]; var coinSpawnY = -120; // Remove lanes with obstacles too close to spawn Y or at the same Y for (var i = 0; i < obstacles.length; i++) { var obs = obstacles[i]; // If obstacle is in the same lane and within 320px vertically of coin spawn Y (-120) if (Math.abs(obs.y - coinSpawnY) < 320) { var idx = availableLanes.indexOf(obs.lane); if (idx !== -1) availableLanes.splice(idx, 1); } // Prevent coin from spawning at the same Y as any obstacle (even in other lanes) if (Math.abs(obs.y - coinSpawnY) < 1) { // If any obstacle is at the same Y, skip coin spawn entirely return; } } if (availableLanes.length === 0) return; // No safe lane, skip coin spawn var lane = availableLanes[Math.floor(Math.random() * availableLanes.length)]; var coin = new Coin(); coin.lane = lane; coin.x = laneToX(lane); coin.y = coinSpawnY; coinObjects.push(coin); game.addChild(coin); } // --- COLLISION DETECTION --- function checkCollision(a, b) { // Simple AABB var dx = Math.abs(a.x - b.x); var dy = Math.abs(a.y - b.y); var aw = a.width || 180, ah = a.height || 180; var bw = b.width || 180, bh = b.height || 180; return dx < aw / 2 + bw / 2 - 20 && dy < ah / 2 + bh / 2 - 20; } // --- GAME LOOP --- game.update = function () { if (isGameOver || currentState !== GameState.PLAYING) return; // --- Runner jump update --- if (runner && typeof runner.update === "function") { runner.update(); } // Scroll road background roadBg1.y += speed; roadBg2.y += speed; // Loop backgrounds if (roadBg1.y >= 2732) { roadBg1.y = roadBg2.y - 2732; } if (roadBg2.y >= 2732) { roadBg2.y = roadBg1.y - 2732; } // Increase speed speed += speedIncrease; // Handle coin combo timer if (coinCombo > 0) { coinComboTimer--; if (coinComboTimer <= 0) { // Reset combo when timer expires coinCombo = 0; if (coinComboText) { coinComboText.destroy(); coinComboText = null; } } } // Move obstacles for (var i = obstacles.length - 1; i >= 0; i--) { var obs = obstacles[i]; obs.y += speed; if (obs.y > 2732 + 200) { obs.destroy(); obstacles.splice(i, 1); continue; } // Collision with runner if (checkCollision(obs, runner) && !runner.isJumping) { triggerGameOver(); return; } } // Move coins for (var j = coinObjects.length - 1; j >= 0; j--) { var coin = coinObjects[j]; coin.y += speed; if (coin.y > 2732 + 120) { coin.destroy(); coinObjects.splice(j, 1); continue; } // Collision with runner if (checkCollision(coin, runner)) { // Increase combo counter coinCombo++; coinComboTimer = coinComboTimeout; // Calculate bonus based on combo var bonus = Math.min(coinCombo, 5); // Cap bonus at 5x var coinValue = 10 * bonus; // Collect coin coins += 1; score += coinValue; LK.setScore(score); scoreTxt.setText(score); coinsTxt.setText(coins); // Show combo text if (coinComboText) { coinComboText.destroy(); } if (coinCombo > 1) { coinComboText = new Text2('x' + coinCombo + ' COMBO!', { size: 80, fill: 0xFFCC00 }); coinComboText.anchor.set(0.5, 0.5); coinComboText.x = coin.x; coinComboText.y = coin.y - 100; game.addChild(coinComboText); // Animate combo text tween(coinComboText, { y: coinComboText.y - 100, alpha: 0 }, { duration: 1000, easing: tween.cubicOut, onFinish: function onFinish() { if (coinComboText) { coinComboText.destroy(); coinComboText = null; } } }); } // Add sparkle effect LK.effects.flashObject(coin, 0xffff00, 200); // Play coin sound LK.getSound('coin_collect').play(); // Play a bounce animation on the coin counter tween(coinIcon, { scaleX: 1.2, scaleY: 1.2 }, { duration: 150, easing: tween.bounceOut, onFinish: function onFinish() { tween(coinIcon, { scaleX: 0.8, scaleY: 0.8 }, { duration: 150, easing: tween.easeOut }); } }); coin.destroy(); coinObjects.splice(j, 1); continue; } } // Spawn obstacles spawnTimer -= speed; if (spawnTimer <= 0) { spawnObstacle(); // Next spawn in 700-1100 px spawnTimer = minObstacleGap + Math.floor(Math.random() * 400); } // Spawn coins coinSpawnTimer -= speed; if (coinSpawnTimer <= 0) { spawnCoin(); // Next coin in 400-900 px coinSpawnTimer = 400 + Math.floor(Math.random() * 500); } }; // --- CUSTOM GAME OVER SCREEN --- var gameOverScreen = new Container(); gameOverScreen.visible = false; // Dimmed background var gameOverBg = LK.getAsset('road', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.2, scaleY: 1.2 }); gameOverBg.x = 2048 / 2; gameOverBg.y = 2732 / 2; gameOverBg.alpha = 0.7; gameOverBg.tint = 0x222222; gameOverScreen.addChild(gameOverBg); // "Game Over" text var gameOverText = new Text2('GAME OVER', { size: 180, fill: 0xFF4444 }); gameOverText.anchor.set(0.5, 0.5); gameOverText.x = 2048 / 2; gameOverText.y = 800; gameOverScreen.addChild(gameOverText); // Score label var finalScoreText = new Text2('SCORE: 0', { size: 120, fill: 0xffffff }); finalScoreText.anchor.set(0.5, 0.5); finalScoreText.x = 2048 / 2; finalScoreText.y = 1100; gameOverScreen.addChild(finalScoreText); // Coins label var finalCoinsText = new Text2('COINS: 0', { size: 100, fill: 0xFFCC00 }); finalCoinsText.anchor.set(0.5, 0.5); finalCoinsText.x = 2048 / 2; finalCoinsText.y = 1250; gameOverScreen.addChild(finalCoinsText); // Restart button var restartBtn = new Container(); var restartBtnText = new Text2('RESTART', { size: 140, fill: 0xffffff }); restartBtnText.anchor.set(0.5, 0.5); restartBtn.addChild(restartBtnText); restartBtn.x = 2048 / 2; restartBtn.y = 1500; restartBtn.interactive = true; gameOverScreen.addChild(restartBtn); game.addChild(gameOverScreen); // Animate in the game over screen function showGameOverScreen() { finalScoreText.setText('SCORE: ' + score); finalCoinsText.setText('COINS: ' + coins); gameOverScreen.visible = true; gameOverScreen.alpha = 0; // Hide gameplay elements runner.visible = false; for (var i = 0; i < obstacles.length; i++) { obstacles[i].visible = false; } for (var j = 0; j < coinObjects.length; j++) { coinObjects[j].visible = false; } roadBg1.visible = false; roadBg2.visible = false; scoreTxt.visible = false; coinCounter.visible = false; tween(gameOverScreen, { alpha: 1 }, { duration: 400, easing: tween.cubicOut }); } // Hide the game over screen function hideGameOverScreen() { gameOverScreen.visible = false; // Restore gameplay elements runner.visible = true; for (var i = 0; i < obstacles.length; i++) { obstacles[i].visible = true; } for (var j = 0; j < coinObjects.length; j++) { coinObjects[j].visible = true; } roadBg1.visible = true; roadBg2.visible = true; } // Restart button handlers restartBtn.down = function () { tween(restartBtn, { scaleX: 0.95, scaleY: 0.95 }, { duration: 100 }); }; restartBtn.up = function () { tween(restartBtn, { scaleX: 1, scaleY: 1 }, { duration: 100, onFinish: function onFinish() { hideGameOverScreen(); currentState = GameState.PLAYING; gameUI.hide(); resetGame(); scoreTxt.visible = true; showCoinCounterAnimated(); } }); }; // --- RESET ON GAME OVER --- game.on('destroy', function () { // Clean up arrays for (var i = 0; i < obstacles.length; i++) { obstacles[i].destroy(); } for (var j = 0; j < coinObjects.length; j++) { coinObjects[j].destroy(); } obstacles = []; coinObjects = []; isGameOver = false; speed = 18; score = 0; coins = 0; coinCombo = 0; coinComboTimer = 0; if (coinComboText) { coinComboText.destroy(); coinComboText = null; } LK.setScore(0); scoreTxt.setText('0'); coinsTxt.setText('0'); runner.x = laneToX(1); runner.lane = 1; runner.y = 2732 - 400; spawnTimer = 0; coinSpawnTimer = 0; roadBg1.y = 0; roadBg2.y = -2732; // Return to menu currentState = GameState.MENU; gameUI.showMenu(); // Hide game UI scoreTxt.visible = false; coinCounter.visible = false; hideGameOverScreen(); }); // Show custom game over screen instead of default function triggerGameOver() { isGameOver = true; LK.effects.flashScreen(0xff0000, 800); showGameOverScreen(); }
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Coin
var Coin = Container.expand(function () {
var self = Container.call(this);
var coinGfx = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5
});
// Lane: 0,1,2
self.lane = 1;
// For collision detection
self.isCoin = true;
return self;
});
// UI Elements class for start menu and instructions
var GameUI = Container.expand(function () {
var self = Container.call(this);
// (Title removed)
// Create start button
self.startBtn = new Container();
var startBtnText = new Text2('START GAME', {
size: 100,
fill: 0xffffff
});
startBtnText.anchor.set(0.5, 0.5);
self.startBtn.addChild(startBtnText);
self.startBtn.x = 2048 / 2;
self.startBtn.y = 1200;
self.startBtn.interactive = true;
self.addChild(self.startBtn);
// Create how to play button
self.instructionsBtn = new Container();
var instBtnText = new Text2('HOW TO PLAY', {
size: 100,
fill: 0xffffff
});
instBtnText.anchor.set(0.5, 0.5);
self.instructionsBtn.addChild(instBtnText);
self.instructionsBtn.x = 2048 / 2;
self.instructionsBtn.y = 1400;
self.instructionsBtn.interactive = true;
self.addChild(self.instructionsBtn);
// Create instructions screen (initially hidden)
self.instructions = new Container();
self.instructions.visible = false;
var instTitle = new Text2('HOW TO PLAY', {
size: 120,
fill: 0xFFFFFF
});
instTitle.anchor.set(0.5, 0);
instTitle.x = 2048 / 2;
instTitle.y = 600;
self.instructions.addChild(instTitle);
var instText1 = new Text2('• SWIPE LEFT/RIGHT to change lanes', {
size: 80,
fill: 0xFFFFFF
});
instText1.anchor.set(0.5, 0);
instText1.x = 2048 / 2;
instText1.y = 800;
self.instructions.addChild(instText1);
var instText2 = new Text2('• Avoid obstacles by changing lanes', {
size: 80,
fill: 0xFFFFFF
});
instText2.anchor.set(0.5, 0);
instText2.x = 2048 / 2;
instText2.y = 900;
self.instructions.addChild(instText2);
var instText4 = new Text2('• Collect coins for bonus points', {
size: 80,
fill: 0xFFFFFF
});
instText4.anchor.set(0.5, 0);
instText4.x = 2048 / 2;
instText4.y = 1000;
self.instructions.addChild(instText4);
// Back button for instructions
self.backBtn = new Container();
var backText = new Text2('BACK', {
size: 80,
fill: 0xFFFFFF
});
backText.anchor.set(0.5, 0.5);
self.backBtn.addChild(backText);
self.backBtn.x = 2048 / 2;
self.backBtn.y = 1400;
self.backBtn.interactive = true;
self.instructions.addChild(self.backBtn);
self.addChild(self.instructions);
// Button press handlers
self.startBtn.down = function () {
tween(self.startBtn, {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 100
});
};
self.startBtn.up = function () {
tween(self.startBtn, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
onFinish: function onFinish() {
if (typeof self.onStartClick === 'function') {
self.onStartClick();
}
}
});
};
self.instructionsBtn.down = function () {
tween(self.instructionsBtn, {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 100
});
};
self.instructionsBtn.up = function () {
tween(self.instructionsBtn, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
onFinish: function onFinish() {
if (typeof self.onInstructionsClick === 'function') {
self.onInstructionsClick();
}
}
});
};
self.backBtn.down = function () {
tween(self.backBtn, {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 100
});
};
self.backBtn.up = function () {
tween(self.backBtn, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
onFinish: function onFinish() {
if (typeof self.onBackClick === 'function') {
self.onBackClick();
}
}
});
};
// Set show/hide methods
self.showMenu = function () {
self.visible = true;
self.startBtn.visible = true;
self.instructionsBtn.visible = true;
self.instructions.visible = false;
};
self.showInstructions = function () {
self.visible = true;
self.startBtn.visible = false;
self.instructionsBtn.visible = false;
self.instructions.visible = true;
};
self.hide = function () {
self.visible = false;
};
return self;
});
// Obstacle
var Obstacle = Container.expand(function () {
var self = Container.call(this);
var obsGfx = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5
});
// Lane: 0,1,2
self.lane = 1;
// For collision detection
self.isObstacle = true;
return self;
});
// Runner (player character)
var Runner = Container.expand(function () {
var self = Container.call(this);
var runnerGfx = self.attachAsset('runner', {
anchorX: 0.5,
anchorY: 0.5
});
// Current lane: 0 (left), 1 (center), 2 (right)
self.lane = 1;
self.moveToLane = function (lane, duration) {
self.lane = lane;
var targetX = laneToX(lane);
tween(self, {
x: targetX
}, {
duration: duration || 120,
easing: tween.cubicOut,
onFinish: function onFinish() {
// (jumpIndicator logic removed)
}
});
};
// Called every frame by game.update
self.update = function () {};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Button image assets
// --- ROAD BACKGROUND ---
// Character: Red box
// Obstacle: Gray box
// Coin: Yellow ellipse
var roadBg1 = LK.getAsset('road', {
anchorX: 0.5,
anchorY: 0
});
roadBg1.x = 2048 / 2;
roadBg1.y = 0;
game.addChild(roadBg1);
var roadBg2 = LK.getAsset('road', {
anchorX: 0.5,
anchorY: 0
});
roadBg2.x = 2048 / 2;
roadBg2.y = -2732;
game.addChild(roadBg2);
// --- LANE SYSTEM ---
var NUM_LANES = 3;
var LANE_WIDTH = 2048 / NUM_LANES; // Use full width
var LANE_START_X = LANE_WIDTH / 2; // Center of first lane at left edge
function laneToX(lane) {
// lane: 0 (left), 1 (center), 2 (right)
return LANE_START_X + lane * LANE_WIDTH;
}
// Game state manager
var GameState = {
MENU: 0,
INSTRUCTIONS: 1,
PLAYING: 2
};
// --- GAMEPLAY VARIABLES ---
var runner;
var obstacles = [];
var coinObjects = []; // Array for coin instances
var speed = 18; // Initial speed (pixels per frame)
var speedIncrease = 0.012; // Speed up per tick
var spawnTimer = 0;
var coinSpawnTimer = 0;
var minObstacleGap = 420; // Minimum vertical gap between obstacles
var lastObstacleY = 0;
var isGameOver = false;
// Coin system variables
var coinCombo = 0;
var coinComboTimer = 0;
var coinComboTimeout = 60; // frames (1 second) to maintain combo
var coinComboText = null;
// Game state management
var currentState = GameState.MENU;
var gameUI = new GameUI();
game.addChild(gameUI);
// Setup UI event handlers
gameUI.onStartClick = function () {
currentState = GameState.PLAYING;
gameUI.hide();
resetGame();
// Show score and coin counter
scoreTxt.visible = true;
showCoinCounterAnimated();
};
gameUI.onInstructionsClick = function () {
currentState = GameState.INSTRUCTIONS;
gameUI.showInstructions();
};
gameUI.onBackClick = function () {
currentState = GameState.MENU;
gameUI.showMenu();
coinCounter.visible = false;
};
// Initial setup
gameUI.showMenu();
// --- SCORE AND COINS ---
var score = 0;
var coins = 0; // Count of collected coins
var scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Hide score initially
scoreTxt.visible = false;
// Coin counter display
var coinCounter = new Container();
var coinIcon = LK.getAsset('coin', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
coinIcon.x = 60;
coinCounter.addChild(coinIcon);
var coinsTxt = new Text2('0', {
size: 100,
fill: 0xFFCC00
});
coinsTxt.anchor.set(0, 0.5);
coinsTxt.x = coinIcon.x + 80;
coinsTxt.y = coinIcon.y;
coinCounter.addChild(coinsTxt);
// Position the coin counter at the top left (avoid 0,0 for menu icon, so use x=120)
coinCounter.x = 120;
coinCounter.y = 120;
LK.gui.topLeft.addChild(coinCounter);
// Hide coin counter initially
coinCounter.visible = false;
// Animate coin counter entrance when shown
function showCoinCounterAnimated() {
coinCounter.visible = true;
coinCounter.alpha = 0;
coinCounter.y = 60; // Start slightly above
tween(coinCounter, {
alpha: 1,
y: 120
}, {
duration: 400,
easing: tween.bounceOut
});
}
// Function to reset game state
function resetGame() {
// Clean up arrays
for (var i = 0; i < obstacles.length; i++) {
obstacles[i].destroy();
}
for (var j = 0; j < coinObjects.length; j++) {
coinObjects[j].destroy();
}
obstacles = [];
coinObjects = [];
isGameOver = false;
speed = 18;
score = 0;
coins = 0;
coinCombo = 0;
coinComboTimer = 0;
if (coinComboText) {
coinComboText.destroy();
coinComboText = null;
}
LK.setScore(0);
scoreTxt.setText('0');
coinsTxt.setText('0');
runner.x = laneToX(1);
runner.lane = 1;
runner.y = 2732 - 400;
runner.isJumping = false;
spawnTimer = 0;
coinSpawnTimer = 0;
roadBg1.y = 0;
roadBg2.y = -2732;
}
// --- INIT RUNNER ---
runner = new Runner();
runner.y = 2732 - 400;
runner.x = laneToX(1);
runner.lane = 1;
game.addChild(runner);
// --- INPUT HANDLING ---
var dragStartX = null;
var dragStartLane = null;
var dragActive = false;
// For jump hold logic
function handleMove(x, y, obj) {
if (currentState !== GameState.PLAYING) return;
if (dragActive && dragStartX !== null) {
var dx = x - dragStartX;
if (Math.abs(dx) > 120) {
// Swipe detected
if (dx > 0 && runner.lane < NUM_LANES - 1) {
runner.moveToLane(runner.lane + 1);
dragStartX = x;
} else if (dx < 0 && runner.lane > 0) {
runner.moveToLane(runner.lane - 1);
dragStartX = x;
}
}
}
}
game.move = handleMove;
game.down = function (x, y, obj) {
if (currentState !== GameState.PLAYING) return;
dragActive = true;
dragStartX = x;
dragStartLane = runner.lane;
};
game.up = function (x, y, obj) {
if (currentState !== GameState.PLAYING) return;
dragActive = false;
dragStartX = null;
dragStartLane = null;
};
// --- TAP TO MOVE (for single taps) ---
game.tap = function (x, y, obj) {
// Not supported by LK, so handled via down/up quick tap
// (No implementation needed)
};
// --- SPAWN OBSTACLES & COINS ---
function spawnObstacle() {
var lane = Math.floor(Math.random() * NUM_LANES);
var obs = new Obstacle();
obs.lane = lane;
obs.x = laneToX(lane);
obs.y = -180;
obstacles.push(obs);
game.addChild(obs);
lastObstacleY = obs.y;
}
function spawnCoin() {
// Prevent coins from spawning in the same lane and too close to obstacles, and at the same Y as obstacles
var availableLanes = [0, 1, 2];
var coinSpawnY = -120;
// Remove lanes with obstacles too close to spawn Y or at the same Y
for (var i = 0; i < obstacles.length; i++) {
var obs = obstacles[i];
// If obstacle is in the same lane and within 320px vertically of coin spawn Y (-120)
if (Math.abs(obs.y - coinSpawnY) < 320) {
var idx = availableLanes.indexOf(obs.lane);
if (idx !== -1) availableLanes.splice(idx, 1);
}
// Prevent coin from spawning at the same Y as any obstacle (even in other lanes)
if (Math.abs(obs.y - coinSpawnY) < 1) {
// If any obstacle is at the same Y, skip coin spawn entirely
return;
}
}
if (availableLanes.length === 0) return; // No safe lane, skip coin spawn
var lane = availableLanes[Math.floor(Math.random() * availableLanes.length)];
var coin = new Coin();
coin.lane = lane;
coin.x = laneToX(lane);
coin.y = coinSpawnY;
coinObjects.push(coin);
game.addChild(coin);
}
// --- COLLISION DETECTION ---
function checkCollision(a, b) {
// Simple AABB
var dx = Math.abs(a.x - b.x);
var dy = Math.abs(a.y - b.y);
var aw = a.width || 180,
ah = a.height || 180;
var bw = b.width || 180,
bh = b.height || 180;
return dx < aw / 2 + bw / 2 - 20 && dy < ah / 2 + bh / 2 - 20;
}
// --- GAME LOOP ---
game.update = function () {
if (isGameOver || currentState !== GameState.PLAYING) return;
// --- Runner jump update ---
if (runner && typeof runner.update === "function") {
runner.update();
}
// Scroll road background
roadBg1.y += speed;
roadBg2.y += speed;
// Loop backgrounds
if (roadBg1.y >= 2732) {
roadBg1.y = roadBg2.y - 2732;
}
if (roadBg2.y >= 2732) {
roadBg2.y = roadBg1.y - 2732;
}
// Increase speed
speed += speedIncrease;
// Handle coin combo timer
if (coinCombo > 0) {
coinComboTimer--;
if (coinComboTimer <= 0) {
// Reset combo when timer expires
coinCombo = 0;
if (coinComboText) {
coinComboText.destroy();
coinComboText = null;
}
}
}
// Move obstacles
for (var i = obstacles.length - 1; i >= 0; i--) {
var obs = obstacles[i];
obs.y += speed;
if (obs.y > 2732 + 200) {
obs.destroy();
obstacles.splice(i, 1);
continue;
}
// Collision with runner
if (checkCollision(obs, runner) && !runner.isJumping) {
triggerGameOver();
return;
}
}
// Move coins
for (var j = coinObjects.length - 1; j >= 0; j--) {
var coin = coinObjects[j];
coin.y += speed;
if (coin.y > 2732 + 120) {
coin.destroy();
coinObjects.splice(j, 1);
continue;
}
// Collision with runner
if (checkCollision(coin, runner)) {
// Increase combo counter
coinCombo++;
coinComboTimer = coinComboTimeout;
// Calculate bonus based on combo
var bonus = Math.min(coinCombo, 5); // Cap bonus at 5x
var coinValue = 10 * bonus;
// Collect coin
coins += 1;
score += coinValue;
LK.setScore(score);
scoreTxt.setText(score);
coinsTxt.setText(coins);
// Show combo text
if (coinComboText) {
coinComboText.destroy();
}
if (coinCombo > 1) {
coinComboText = new Text2('x' + coinCombo + ' COMBO!', {
size: 80,
fill: 0xFFCC00
});
coinComboText.anchor.set(0.5, 0.5);
coinComboText.x = coin.x;
coinComboText.y = coin.y - 100;
game.addChild(coinComboText);
// Animate combo text
tween(coinComboText, {
y: coinComboText.y - 100,
alpha: 0
}, {
duration: 1000,
easing: tween.cubicOut,
onFinish: function onFinish() {
if (coinComboText) {
coinComboText.destroy();
coinComboText = null;
}
}
});
}
// Add sparkle effect
LK.effects.flashObject(coin, 0xffff00, 200);
// Play coin sound
LK.getSound('coin_collect').play();
// Play a bounce animation on the coin counter
tween(coinIcon, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 150,
easing: tween.bounceOut,
onFinish: function onFinish() {
tween(coinIcon, {
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 150,
easing: tween.easeOut
});
}
});
coin.destroy();
coinObjects.splice(j, 1);
continue;
}
}
// Spawn obstacles
spawnTimer -= speed;
if (spawnTimer <= 0) {
spawnObstacle();
// Next spawn in 700-1100 px
spawnTimer = minObstacleGap + Math.floor(Math.random() * 400);
}
// Spawn coins
coinSpawnTimer -= speed;
if (coinSpawnTimer <= 0) {
spawnCoin();
// Next coin in 400-900 px
coinSpawnTimer = 400 + Math.floor(Math.random() * 500);
}
};
// --- CUSTOM GAME OVER SCREEN ---
var gameOverScreen = new Container();
gameOverScreen.visible = false;
// Dimmed background
var gameOverBg = LK.getAsset('road', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
gameOverBg.x = 2048 / 2;
gameOverBg.y = 2732 / 2;
gameOverBg.alpha = 0.7;
gameOverBg.tint = 0x222222;
gameOverScreen.addChild(gameOverBg);
// "Game Over" text
var gameOverText = new Text2('GAME OVER', {
size: 180,
fill: 0xFF4444
});
gameOverText.anchor.set(0.5, 0.5);
gameOverText.x = 2048 / 2;
gameOverText.y = 800;
gameOverScreen.addChild(gameOverText);
// Score label
var finalScoreText = new Text2('SCORE: 0', {
size: 120,
fill: 0xffffff
});
finalScoreText.anchor.set(0.5, 0.5);
finalScoreText.x = 2048 / 2;
finalScoreText.y = 1100;
gameOverScreen.addChild(finalScoreText);
// Coins label
var finalCoinsText = new Text2('COINS: 0', {
size: 100,
fill: 0xFFCC00
});
finalCoinsText.anchor.set(0.5, 0.5);
finalCoinsText.x = 2048 / 2;
finalCoinsText.y = 1250;
gameOverScreen.addChild(finalCoinsText);
// Restart button
var restartBtn = new Container();
var restartBtnText = new Text2('RESTART', {
size: 140,
fill: 0xffffff
});
restartBtnText.anchor.set(0.5, 0.5);
restartBtn.addChild(restartBtnText);
restartBtn.x = 2048 / 2;
restartBtn.y = 1500;
restartBtn.interactive = true;
gameOverScreen.addChild(restartBtn);
game.addChild(gameOverScreen);
// Animate in the game over screen
function showGameOverScreen() {
finalScoreText.setText('SCORE: ' + score);
finalCoinsText.setText('COINS: ' + coins);
gameOverScreen.visible = true;
gameOverScreen.alpha = 0;
// Hide gameplay elements
runner.visible = false;
for (var i = 0; i < obstacles.length; i++) {
obstacles[i].visible = false;
}
for (var j = 0; j < coinObjects.length; j++) {
coinObjects[j].visible = false;
}
roadBg1.visible = false;
roadBg2.visible = false;
scoreTxt.visible = false;
coinCounter.visible = false;
tween(gameOverScreen, {
alpha: 1
}, {
duration: 400,
easing: tween.cubicOut
});
}
// Hide the game over screen
function hideGameOverScreen() {
gameOverScreen.visible = false;
// Restore gameplay elements
runner.visible = true;
for (var i = 0; i < obstacles.length; i++) {
obstacles[i].visible = true;
}
for (var j = 0; j < coinObjects.length; j++) {
coinObjects[j].visible = true;
}
roadBg1.visible = true;
roadBg2.visible = true;
}
// Restart button handlers
restartBtn.down = function () {
tween(restartBtn, {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 100
});
};
restartBtn.up = function () {
tween(restartBtn, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
onFinish: function onFinish() {
hideGameOverScreen();
currentState = GameState.PLAYING;
gameUI.hide();
resetGame();
scoreTxt.visible = true;
showCoinCounterAnimated();
}
});
};
// --- RESET ON GAME OVER ---
game.on('destroy', function () {
// Clean up arrays
for (var i = 0; i < obstacles.length; i++) {
obstacles[i].destroy();
}
for (var j = 0; j < coinObjects.length; j++) {
coinObjects[j].destroy();
}
obstacles = [];
coinObjects = [];
isGameOver = false;
speed = 18;
score = 0;
coins = 0;
coinCombo = 0;
coinComboTimer = 0;
if (coinComboText) {
coinComboText.destroy();
coinComboText = null;
}
LK.setScore(0);
scoreTxt.setText('0');
coinsTxt.setText('0');
runner.x = laneToX(1);
runner.lane = 1;
runner.y = 2732 - 400;
spawnTimer = 0;
coinSpawnTimer = 0;
roadBg1.y = 0;
roadBg2.y = -2732;
// Return to menu
currentState = GameState.MENU;
gameUI.showMenu();
// Hide game UI
scoreTxt.visible = false;
coinCounter.visible = false;
hideGameOverScreen();
});
// Show custom game over screen instead of default
function triggerGameOver() {
isGameOver = true;
LK.effects.flashScreen(0xff0000, 800);
showGameOverScreen();
}
Pixel, 2d, Coin. In-Game asset. 2d. High contrast. No shadows
container, 2d, pixel. In-Game asset. 2d. High contrast. No shadows
character with a hat, 2d, pixel,. 2d. High contrast. No shadows
road, pixel, 2d. In-Game asset. 2d. High contrast. No shadows. In-Game asset
indicator, pixel, 2d. In-Game asset. 2d. High contrast. No shadows
Start button, pixel art. In-Game asset. 2d. High contrast. No shadows
how to play button, orange outline, white text, pixel art. In-Game asset. 2d. High contrast. No shadows
restart button, orange outline, white text, pixel art.. In-Game asset. 2d. High contrast. No shadows