/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // PipePair class (top and bottom pipes) var PipePair = Container.expand(function () { var self = Container.call(this); // Gap size and vertical position self.gap = 600; self.pipeWidth = 220; self.pipeHeight = 900; // Top pipe self.topPipe = self.attachAsset('pipe', { anchorX: 0.5, anchorY: 1.0, x: 0, y: 0 }); // Bottom pipe self.bottomPipe = self.attachAsset('pipe', { anchorX: 0.5, anchorY: 0.0, x: 0, y: self.gap + self.pipeHeight }); // Used to check if player has passed this pipe self.passed = false; // Set vertical gap position self.setGapY = function (gapY) { // gapY is the y of the center of the gap self.topPipe.y = gapY - self.gap / 2; self.bottomPipe.y = gapY + self.gap / 2; }; // Update method self.update = function () { self.x -= pipeSpeed; }; // Get bounds for collision self.getTopPipeBounds = function () { return { x: self.x - self.pipeWidth / 2, y: self.topPipe.y - self.pipeHeight, width: self.pipeWidth, height: self.pipeHeight }; }; self.getBottomPipeBounds = function () { return { x: self.x - self.pipeWidth / 2, y: self.bottomPipe.y, width: self.pipeWidth, height: self.pipeHeight }; }; return self; }); // Player class var Player = Container.expand(function () { var self = Container.call(this); var playerGfx = self.attachAsset('playerSquare', { anchorX: 0.5, anchorY: 0.5 }); self.vy = 0; // vertical velocity self.gravity = 0; // gravity per frame (slower fall) self.flapStrength = -28; // negative = up (slower flap) // Flap method self.flap = function () { self.vy = self.flapStrength; // Animate a quick scale for feedback tween(playerGfx, { scaleY: 0.7 }, { duration: 60, easing: tween.cubicOut, onFinish: function onFinish() { tween(playerGfx, { scaleY: 1 }, { duration: 120, easing: tween.cubicOut }); } }); }; // Update method self.update = function () { self.vy += self.gravity; self.y += self.vy; // Clamp rotation for visual feedback var maxAngle = Math.PI / 5; var minAngle = -Math.PI / 6; var targetRot = Math.max(minAngle, Math.min(maxAngle, self.vy / 60)); playerGfx.rotation = targetRot; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x87ceeb // Sky blue }); /**** * Game Code ****/ // Game constants // Square player // Pipe (top and bottom, same asset, different y/flip) // Ground var GROUND_HEIGHT = 120; var PLAYER_START_X = 500; var PLAYER_START_Y = 1200; var PIPE_INTERVAL = 900; // px between pipes horizontally var PIPE_MIN_Y = 500; var PIPE_MAX_Y = 2732 - GROUND_HEIGHT - 500; var pipeSpeed = 10; // Game state var player; var pipes = []; var ground; var score = 0; var scoreTxt; var gameStarted = true; var gameOver = false; var lastPipeX = 0; // Player speed factor: starts at 0.5 (half speed), becomes 1 after first movement var playerSpeedFactor = 0.5; var playerHasMoved = false; // Start menu overlay var startMenu = new Container(); var startBg = LK.getAsset('ground', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, width: 900, height: 600 }); startBg.alpha = 0.92; startMenu.addChild(startBg); var startText = new Text2("Tap to Start", { size: 180, fill: 0xffffff }); startText.anchor.set(0.5, 0.5); startText.x = 1024; startText.y = 1366 - 60; startMenu.addChild(startText); var howToText = new Text2("Touch and drag to move the square", { size: 80, fill: 0xffffff }); howToText.anchor.set(0.5, 0.5); howToText.x = 1024; howToText.y = 1366 + 120; startMenu.addChild(howToText); // Start menu overlay is not shown since game starts immediately // LK.gui.center.addChild(startMenu); // Add cloudy background image (behind everything) var background = LK.getAsset('cloudyBackground', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732 }); game.addChild(background); // Track background offset for parallax effect var backgroundOffsetY = 0; // Add ground ground = LK.getAsset('ground', { anchorX: 0, anchorY: 0, x: 0, y: 2732 - GROUND_HEIGHT }); game.addChild(ground); // Add player player = new Player(); player.x = PLAYER_START_X; player.y = PLAYER_START_Y; game.addChild(player); // Add score text scoreTxt = new Text2('0', { size: 150, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Helper: reset game state function resetGame() { // Remove pipes for (var i = 0; i < pipes.length; i++) { pipes[i].destroy(); } pipes = []; // Reset player player.x = PLAYER_START_X; player.y = PLAYER_START_Y; player.vy = 0; // Reset score score = 0; scoreTxt.setText(score); // Reset state gameStarted = false; gameOver = false; lastPipeX = 0; playerSpeedFactor = 0.5; playerHasMoved = false; // Show start menu again if (startMenu && !startMenu.parent) { LK.gui.center.addChild(startMenu); } } // Helper: spawn a new pipe pair function spawnPipePair(x) { var pipe = new PipePair(); // Randomize gap center var gapY = PIPE_MIN_Y + Math.floor(Math.random() * (PIPE_MAX_Y - PIPE_MIN_Y)); pipe.x = x; pipe.setGapY(gapY); pipes.push(pipe); game.addChild(pipe); } // Start with 3 pipes offscreen to the right for (var i = 0; i < 3; i++) { spawnPipePair(2048 + i * PIPE_INTERVAL); lastPipeX = 2048 + i * PIPE_INTERVAL; } // --- Add up and down control buttons --- var btnUp = LK.getAsset('btnUp', { anchorX: 0.5, anchorY: 0.5, x: 2048 - 200, y: 2732 - 500 }); var btnDown = LK.getAsset('btnDown', { anchorX: 0.5, anchorY: 0.5, x: 2048 - 200, y: 2732 - 250 }); btnUp.alpha = 0.85; btnDown.alpha = 0.85; game.addChild(btnUp); game.addChild(btnDown); // Button state var btnUpPressed = false; var btnDownPressed = false; // Helper to check if a point is inside a button function isInsideBtn(btn, x, y) { var bx = btn.x, by = btn.y; var bw = btn.width, bh = btn.height; return x >= bx - bw / 2 && x <= bx + bw / 2 && y >= by - bh / 2 && y <= by + bh / 2; } // Input: touch and drag to control the square directly or press buttons game.down = function (x, y, obj) { if (gameOver) { return; } // No need to handle start menu or gameStarted here since game starts immediately // Check if up or down button pressed if (isInsideBtn(btnUp, x, y)) { btnUpPressed = true; btnDownPressed = false; return; } if (isInsideBtn(btnDown, x, y)) { btnDownPressed = true; btnUpPressed = false; return; } // No direct touch control of the square }; game.move = function (x, y, obj) { if (gameOver || !gameStarted) return; // If holding up or down button, ignore drag if (btnUpPressed || btnDownPressed) return; if (!game._touching) return; // No direct touch drag control of the square }; game.up = function (x, y, obj) { btnUpPressed = false; btnDownPressed = false; game._touching = false; }; // Main update loop game.update = function () { if (gameOver) { return; } // Only update if started if (gameStarted) { // Button control: move player up/down if button held if (btnUpPressed) { var oldY = player.y; player.y = Math.max(60, player.y - 22 * playerSpeedFactor); player.vy = 0; // Move background at half the speed of the player var deltaY = player.y - oldY; backgroundOffsetY += deltaY * 0.5; // Clamp backgroundOffsetY to [0, 2732 - background.height] backgroundOffsetY = Math.max(0, Math.min(2732 - background.height, backgroundOffsetY)); background.y = -backgroundOffsetY; // On first movement, restore speed to 1 if (!playerHasMoved) { playerSpeedFactor = 1; playerHasMoved = true; } } if (btnDownPressed) { var oldY = player.y; player.y = Math.min(2732 - GROUND_HEIGHT - 60, player.y + 22 * playerSpeedFactor); player.vy = 0; // Move background at half the speed of the player var deltaY = player.y - oldY; backgroundOffsetY += deltaY * 0.5; // Clamp backgroundOffsetY to [0, 2732 - background.height] backgroundOffsetY = Math.max(0, Math.min(2732 - background.height, backgroundOffsetY)); background.y = -backgroundOffsetY; // On first movement, restore speed to 1 if (!playerHasMoved) { playerSpeedFactor = 1; playerHasMoved = true; } } player.update(); // Move pipes and check for offscreen for (var i = pipes.length - 1; i >= 0; i--) { var pipe = pipes[i]; pipe.update(); // Remove pipes that are offscreen left if (pipe.x < -pipe.pipeWidth) { pipe.destroy(); pipes.splice(i, 1); continue; } // Check for passing pipe (score) if (!pipe.passed && pipe.x + pipe.pipeWidth / 2 < player.x) { pipe.passed = true; score += 1; scoreTxt.setText(score); } } // Spawn new pipes as needed if (pipes.length === 0 || lastPipeX - pipes[pipes.length - 1].x >= PIPE_INTERVAL) { lastPipeX += PIPE_INTERVAL; spawnPipePair(lastPipeX); } // Collision detection: pipes for (var i = 0; i < pipes.length; i++) { var pipe = pipes[i]; // Top pipe var topB = pipe.getTopPipeBounds(); if (rectsIntersect(player, topB)) { endGame(); return; } // Bottom pipe var botB = pipe.getBottomPipeBounds(); if (rectsIntersect(player, botB)) { endGame(); return; } } // Collision detection: ground if (player.y + 60 > 2732 - GROUND_HEIGHT) { player.y = 2732 - GROUND_HEIGHT - 60; endGame(); return; } // Collision detection: ceiling if (player.y - 60 < 0) { player.y = 60; player.vy = 0; } } }; // Rectangle intersection helper (player is a square, pipes are rectangles) function rectsIntersect(playerObj, rect) { // Player bounds var px = playerObj.x - 60; var py = playerObj.y - 60; var pw = 120; var ph = 120; // Rect bounds var rx = rect.x; var ry = rect.y; var rw = rect.width; var rh = rect.height; // Check overlap return !(px + pw < rx || px > rx + rw || py + ph < ry || py > ry + rh); } // End game function endGame() { gameOver = true; // Flash red LK.effects.flashScreen(0xff0000, 800); // Show game over (auto resets game) LK.setScore(score); LK.showGameOver(); } // Reset game on game over LK.on('gameover', function () { resetGame(); }); // Also reset on you win (not used, but for completeness) LK.on('youwin', function () { resetGame(); });
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// PipePair class (top and bottom pipes)
var PipePair = Container.expand(function () {
var self = Container.call(this);
// Gap size and vertical position
self.gap = 600;
self.pipeWidth = 220;
self.pipeHeight = 900;
// Top pipe
self.topPipe = self.attachAsset('pipe', {
anchorX: 0.5,
anchorY: 1.0,
x: 0,
y: 0
});
// Bottom pipe
self.bottomPipe = self.attachAsset('pipe', {
anchorX: 0.5,
anchorY: 0.0,
x: 0,
y: self.gap + self.pipeHeight
});
// Used to check if player has passed this pipe
self.passed = false;
// Set vertical gap position
self.setGapY = function (gapY) {
// gapY is the y of the center of the gap
self.topPipe.y = gapY - self.gap / 2;
self.bottomPipe.y = gapY + self.gap / 2;
};
// Update method
self.update = function () {
self.x -= pipeSpeed;
};
// Get bounds for collision
self.getTopPipeBounds = function () {
return {
x: self.x - self.pipeWidth / 2,
y: self.topPipe.y - self.pipeHeight,
width: self.pipeWidth,
height: self.pipeHeight
};
};
self.getBottomPipeBounds = function () {
return {
x: self.x - self.pipeWidth / 2,
y: self.bottomPipe.y,
width: self.pipeWidth,
height: self.pipeHeight
};
};
return self;
});
// Player class
var Player = Container.expand(function () {
var self = Container.call(this);
var playerGfx = self.attachAsset('playerSquare', {
anchorX: 0.5,
anchorY: 0.5
});
self.vy = 0; // vertical velocity
self.gravity = 0; // gravity per frame (slower fall)
self.flapStrength = -28; // negative = up (slower flap)
// Flap method
self.flap = function () {
self.vy = self.flapStrength;
// Animate a quick scale for feedback
tween(playerGfx, {
scaleY: 0.7
}, {
duration: 60,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(playerGfx, {
scaleY: 1
}, {
duration: 120,
easing: tween.cubicOut
});
}
});
};
// Update method
self.update = function () {
self.vy += self.gravity;
self.y += self.vy;
// Clamp rotation for visual feedback
var maxAngle = Math.PI / 5;
var minAngle = -Math.PI / 6;
var targetRot = Math.max(minAngle, Math.min(maxAngle, self.vy / 60));
playerGfx.rotation = targetRot;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87ceeb // Sky blue
});
/****
* Game Code
****/
// Game constants
// Square player
// Pipe (top and bottom, same asset, different y/flip)
// Ground
var GROUND_HEIGHT = 120;
var PLAYER_START_X = 500;
var PLAYER_START_Y = 1200;
var PIPE_INTERVAL = 900; // px between pipes horizontally
var PIPE_MIN_Y = 500;
var PIPE_MAX_Y = 2732 - GROUND_HEIGHT - 500;
var pipeSpeed = 10;
// Game state
var player;
var pipes = [];
var ground;
var score = 0;
var scoreTxt;
var gameStarted = true;
var gameOver = false;
var lastPipeX = 0;
// Player speed factor: starts at 0.5 (half speed), becomes 1 after first movement
var playerSpeedFactor = 0.5;
var playerHasMoved = false;
// Start menu overlay
var startMenu = new Container();
var startBg = LK.getAsset('ground', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
width: 900,
height: 600
});
startBg.alpha = 0.92;
startMenu.addChild(startBg);
var startText = new Text2("Tap to Start", {
size: 180,
fill: 0xffffff
});
startText.anchor.set(0.5, 0.5);
startText.x = 1024;
startText.y = 1366 - 60;
startMenu.addChild(startText);
var howToText = new Text2("Touch and drag to move the square", {
size: 80,
fill: 0xffffff
});
howToText.anchor.set(0.5, 0.5);
howToText.x = 1024;
howToText.y = 1366 + 120;
startMenu.addChild(howToText);
// Start menu overlay is not shown since game starts immediately
// LK.gui.center.addChild(startMenu);
// Add cloudy background image (behind everything)
var background = LK.getAsset('cloudyBackground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732
});
game.addChild(background);
// Track background offset for parallax effect
var backgroundOffsetY = 0;
// Add ground
ground = LK.getAsset('ground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 2732 - GROUND_HEIGHT
});
game.addChild(ground);
// Add player
player = new Player();
player.x = PLAYER_START_X;
player.y = PLAYER_START_Y;
game.addChild(player);
// Add score text
scoreTxt = new Text2('0', {
size: 150,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Helper: reset game state
function resetGame() {
// Remove pipes
for (var i = 0; i < pipes.length; i++) {
pipes[i].destroy();
}
pipes = [];
// Reset player
player.x = PLAYER_START_X;
player.y = PLAYER_START_Y;
player.vy = 0;
// Reset score
score = 0;
scoreTxt.setText(score);
// Reset state
gameStarted = false;
gameOver = false;
lastPipeX = 0;
playerSpeedFactor = 0.5;
playerHasMoved = false;
// Show start menu again
if (startMenu && !startMenu.parent) {
LK.gui.center.addChild(startMenu);
}
}
// Helper: spawn a new pipe pair
function spawnPipePair(x) {
var pipe = new PipePair();
// Randomize gap center
var gapY = PIPE_MIN_Y + Math.floor(Math.random() * (PIPE_MAX_Y - PIPE_MIN_Y));
pipe.x = x;
pipe.setGapY(gapY);
pipes.push(pipe);
game.addChild(pipe);
}
// Start with 3 pipes offscreen to the right
for (var i = 0; i < 3; i++) {
spawnPipePair(2048 + i * PIPE_INTERVAL);
lastPipeX = 2048 + i * PIPE_INTERVAL;
}
// --- Add up and down control buttons ---
var btnUp = LK.getAsset('btnUp', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 - 200,
y: 2732 - 500
});
var btnDown = LK.getAsset('btnDown', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 - 200,
y: 2732 - 250
});
btnUp.alpha = 0.85;
btnDown.alpha = 0.85;
game.addChild(btnUp);
game.addChild(btnDown);
// Button state
var btnUpPressed = false;
var btnDownPressed = false;
// Helper to check if a point is inside a button
function isInsideBtn(btn, x, y) {
var bx = btn.x,
by = btn.y;
var bw = btn.width,
bh = btn.height;
return x >= bx - bw / 2 && x <= bx + bw / 2 && y >= by - bh / 2 && y <= by + bh / 2;
}
// Input: touch and drag to control the square directly or press buttons
game.down = function (x, y, obj) {
if (gameOver) {
return;
}
// No need to handle start menu or gameStarted here since game starts immediately
// Check if up or down button pressed
if (isInsideBtn(btnUp, x, y)) {
btnUpPressed = true;
btnDownPressed = false;
return;
}
if (isInsideBtn(btnDown, x, y)) {
btnDownPressed = true;
btnUpPressed = false;
return;
}
// No direct touch control of the square
};
game.move = function (x, y, obj) {
if (gameOver || !gameStarted) return;
// If holding up or down button, ignore drag
if (btnUpPressed || btnDownPressed) return;
if (!game._touching) return;
// No direct touch drag control of the square
};
game.up = function (x, y, obj) {
btnUpPressed = false;
btnDownPressed = false;
game._touching = false;
};
// Main update loop
game.update = function () {
if (gameOver) {
return;
}
// Only update if started
if (gameStarted) {
// Button control: move player up/down if button held
if (btnUpPressed) {
var oldY = player.y;
player.y = Math.max(60, player.y - 22 * playerSpeedFactor);
player.vy = 0;
// Move background at half the speed of the player
var deltaY = player.y - oldY;
backgroundOffsetY += deltaY * 0.5;
// Clamp backgroundOffsetY to [0, 2732 - background.height]
backgroundOffsetY = Math.max(0, Math.min(2732 - background.height, backgroundOffsetY));
background.y = -backgroundOffsetY;
// On first movement, restore speed to 1
if (!playerHasMoved) {
playerSpeedFactor = 1;
playerHasMoved = true;
}
}
if (btnDownPressed) {
var oldY = player.y;
player.y = Math.min(2732 - GROUND_HEIGHT - 60, player.y + 22 * playerSpeedFactor);
player.vy = 0;
// Move background at half the speed of the player
var deltaY = player.y - oldY;
backgroundOffsetY += deltaY * 0.5;
// Clamp backgroundOffsetY to [0, 2732 - background.height]
backgroundOffsetY = Math.max(0, Math.min(2732 - background.height, backgroundOffsetY));
background.y = -backgroundOffsetY;
// On first movement, restore speed to 1
if (!playerHasMoved) {
playerSpeedFactor = 1;
playerHasMoved = true;
}
}
player.update();
// Move pipes and check for offscreen
for (var i = pipes.length - 1; i >= 0; i--) {
var pipe = pipes[i];
pipe.update();
// Remove pipes that are offscreen left
if (pipe.x < -pipe.pipeWidth) {
pipe.destroy();
pipes.splice(i, 1);
continue;
}
// Check for passing pipe (score)
if (!pipe.passed && pipe.x + pipe.pipeWidth / 2 < player.x) {
pipe.passed = true;
score += 1;
scoreTxt.setText(score);
}
}
// Spawn new pipes as needed
if (pipes.length === 0 || lastPipeX - pipes[pipes.length - 1].x >= PIPE_INTERVAL) {
lastPipeX += PIPE_INTERVAL;
spawnPipePair(lastPipeX);
}
// Collision detection: pipes
for (var i = 0; i < pipes.length; i++) {
var pipe = pipes[i];
// Top pipe
var topB = pipe.getTopPipeBounds();
if (rectsIntersect(player, topB)) {
endGame();
return;
}
// Bottom pipe
var botB = pipe.getBottomPipeBounds();
if (rectsIntersect(player, botB)) {
endGame();
return;
}
}
// Collision detection: ground
if (player.y + 60 > 2732 - GROUND_HEIGHT) {
player.y = 2732 - GROUND_HEIGHT - 60;
endGame();
return;
}
// Collision detection: ceiling
if (player.y - 60 < 0) {
player.y = 60;
player.vy = 0;
}
}
};
// Rectangle intersection helper (player is a square, pipes are rectangles)
function rectsIntersect(playerObj, rect) {
// Player bounds
var px = playerObj.x - 60;
var py = playerObj.y - 60;
var pw = 120;
var ph = 120;
// Rect bounds
var rx = rect.x;
var ry = rect.y;
var rw = rect.width;
var rh = rect.height;
// Check overlap
return !(px + pw < rx || px > rx + rw || py + ph < ry || py > ry + rh);
}
// End game
function endGame() {
gameOver = true;
// Flash red
LK.effects.flashScreen(0xff0000, 800);
// Show game over (auto resets game)
LK.setScore(score);
LK.showGameOver();
}
// Reset game on game over
LK.on('gameover', function () {
resetGame();
});
// Also reset on you win (not used, but for completeness)
LK.on('youwin', function () {
resetGame();
});