/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Bird class var Bird = Container.expand(function () { var self = Container.call(this); // Attach bird asset (ellipse, yellow) var birdAsset = self.attachAsset('bird', { anchorX: 0.5, anchorY: 0.5 }); // Physics properties self.velocityY = 0; self.gravity = 0.5; // Lower gravity per frame for slower fall self.flapStrength = -20; // Lower flap strength for gentler jump // Bird size for collision self.radius = birdAsset.width * 0.45; // Flap method self.flap = function () { self.velocityY = self.flapStrength; }; // Update method (called every tick) self.update = function () { // Only apply gravity and velocity if gameStarted is true if (typeof gameStarted !== "undefined" && gameStarted) { self.velocityY += self.gravity; self.y += self.velocityY; } // Clamp rotation for visual feedback (optional) var maxAngle = Math.PI / 4; var minAngle = -Math.PI / 6; var angle = self.velocityY / 40 * maxAngle; if (angle > maxAngle) { angle = maxAngle; } if (angle < minAngle) { angle = minAngle; } birdAsset.rotation = angle; }; return self; }); // PipePair class (top and bottom pipes as a pair) var PipePair = Container.expand(function () { var self = Container.call(this); // Pipe properties self.pipeWidth = 80; // Start very thin, will be set wider on spawn self.gapHeight = 520; // Will be randomized a bit self.speed = 12; // Speed at which pipes move left // Top pipe (cloud) self.topPipe = self.attachAsset('cloud', { anchorX: 0, anchorY: 1, width: self.pipeWidth, height: 300, // cloud height y: 0, x: 0 }); // Bottom pipe (mountain) self.bottomPipe = self.attachAsset('mountain', { anchorX: 0, anchorY: 0, width: self.pipeWidth, height: 600, // mountain height y: 0, x: 0 }); // Used to track if score was already given for this pipe self.passed = false; // Set pipes' vertical positions self.setGap = function (gapY, gapHeight) { self.gapHeight = gapHeight; // Top cloud: position at gapY, stretch to fill from top to gapY self.topPipe.y = gapY; self.topPipe.height = Math.max(1, gapY); // cloud height = gapY, min 1 self.topPipe.width = self.pipeWidth; // Bottom mountain: position at gapY+gapHeight, stretch to fill to bottom self.bottomPipe.y = gapY + gapHeight; self.bottomPipe.height = Math.max(1, 2732 - (gapY + gapHeight)); // mountain height self.bottomPipe.width = self.pipeWidth; }; // Update method self.update = function () { self.x -= self.speed; }; // Collision check with bird self.collidesWith = function (bird) { // Bird's bounding circle var bx = bird.x; var by = bird.y; var r = bird.radius; // Top pipe rectangle var topRect = { x: self.x, y: 0, w: self.pipeWidth, h: self.topPipe.y }; // Bottom pipe rectangle var bottomRect = { x: self.x, y: self.bottomPipe.y, w: self.pipeWidth, h: 2732 - self.bottomPipe.y }; // Helper: circle-rect collision function circleRectCollide(cx, cy, cr, rx, ry, rw, rh) { var closestX = Math.max(rx, Math.min(cx, rx + rw)); var closestY = Math.max(ry, Math.min(cy, ry + rh)); var dx = cx - closestX; var dy = cy - closestY; return dx * dx + dy * dy < cr * cr; } if (circleRectCollide(bx, by, r, topRect.x, topRect.y, topRect.w, topRect.h)) { return true; } if (circleRectCollide(bx, by, r, bottomRect.x, bottomRect.y, bottomRect.w, bottomRect.h)) { return true; } return false; }; // Has bird passed this pipe? (for scoring) self.isPassedBy = function (bird) { return !self.passed && self.x + self.pipeWidth < bird.x - bird.radius; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // --- No dynamic ground/upground height/width/distance here; handled in update below -- var background = LK.getAsset('background', { anchorX: 0, anchorY: 0, x: 0, y: 0, width: 2048, height: 2732 }); game.addChild(background); // --- Cloud (top pipe) - white, wide, ellipse // Mountain (bottom pipe) - green, wide, triangle-like // --- Game Variables --- // Tween plugin for animations (not strictly needed for MVP, but included for future use) // --- Asset Initialization (shapes) --- var bird; var pipes = []; var pipeSpawnTimer = 0; var pipeInterval = 90; // Frames between pipes (1.5s at 60fps) var score = 0; var scoreTxt; var ground; var gameStarted = false; var gameOver = false; // --- Tap to Start Overlay --- var tapToStartTxt = new Text2('Tap to Start', { size: 140, fill: 0xffffff }); tapToStartTxt.anchor.set(0.5, 1); // anchor bottom center tapToStartTxt.x = 2048 / 2; // Move down so the layer sits on the ground, with a gap above ground var tapToStartGroundGap = 60; // distance above ground var tapToStartMaxY = 2732 - 920 - tapToStartGroundGap; // 120 is base ground height tapToStartTxt.y = tapToStartMaxY; tapToStartTxt.visible = false; // Add "How to play" instructions below the Tap to Start text var howToPlayTxt = new Text2('Tap to power up and guide your UFO through doors.\nAvoid galactic doors and see how far you can go!', { size: 70, fill: 0xffffff }); howToPlayTxt.anchor.set(0.5, 0); // anchor top center howToPlayTxt.x = 2048 / 2; howToPlayTxt.y = tapToStartTxt.y + 40; // 40px below tapToStartTxt // Remove from game if already added, then add to top if (tapToStartTxt.parent) { tapToStartTxt.parent.removeChild(tapToStartTxt); } game.addChild(tapToStartTxt); game.setChildIndex(tapToStartTxt, game.children.length - 1); // Always on top if (howToPlayTxt.parent) { howToPlayTxt.parent.removeChild(howToPlayTxt); } game.addChild(howToPlayTxt); game.setChildIndex(howToPlayTxt, game.children.length - 1); // --- GUI Score Display --- if (typeof scoreTxt !== "undefined" && scoreTxt && scoreTxt.parent) { scoreTxt.parent.removeChild(scoreTxt); scoreTxt.destroy(); } if (typeof scoreCircleBg !== "undefined" && scoreCircleBg && scoreCircleBg.parent) { scoreCircleBg.parent.removeChild(scoreCircleBg); scoreCircleBg.destroy(); } // Create a perfect circle background for the score and center it scoreCircleBg = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, x: LK.gui.width / 2, y: 80 + 45, // 45 is half of 90 (score text size), so circle is centered behind text scaleX: 0.8, scaleY: 0.8 }); LK.gui.addChild(scoreCircleBg); scoreTxt = new Text2('0', { size: 90, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0.5); // Center score horizontally at the top, vertically centered in the circle scoreTxt.x = LK.gui.width / 2; scoreTxt.y = 80 + 45; LK.gui.addChild(scoreTxt); // Add game name text under the score in big font, but on overlay layer (with tapToStartTxt) var gameNameTxt = new Text2('Galactic Doors', { size: 220, fill: 0xffffff, font: "'Orbitron', 'Audiowide', 'Space Mono', 'GillSans-Bold', Impact, 'Arial Black', Tahoma" }); gameNameTxt.anchor.set(0.5, 0); // Place it under the score circle, with a gap, but on overlay gameNameTxt.x = 2048 / 2; gameNameTxt.y = tapToStartTxt.y - 1000; // 1000px above tapToStartTxt (visually grouped) gameNameTxt.visible = false; // Remove from game if already added, then add to top if (gameNameTxt.parent) { gameNameTxt.parent.removeChild(gameNameTxt); } game.addChild(gameNameTxt); game.setChildIndex(gameNameTxt, game.children.length - 1); // Move howToPlayTxt to overlay and hide initially howToPlayTxt.x = 2048 / 2; howToPlayTxt.y = tapToStartTxt.y + 40; howToPlayTxt.visible = false; if (howToPlayTxt.parent) { howToPlayTxt.parent.removeChild(howToPlayTxt); } game.addChild(howToPlayTxt); game.setChildIndex(howToPlayTxt, game.children.length - 1); // --- Ground (for collision) --- ground = LK.getAsset('ground', { anchorX: 0, anchorY: 0, x: 0, y: 2732 - 120 }); game.addChild(ground); // --- Mirrored ground at top (now called upground) --- var upground = LK.getAsset('ground', { anchorX: 0, anchorY: 1, x: 0, y: 0, scaleY: -1 }); game.addChild(upground); // --- Minimum ground width (never narrower than mountain/cloud asset) --- var minGroundWidth = 800; // same as mountain/cloud asset width var maxGroundWidth = 2048; // full width // --- Bird --- bird = new Bird(); game.addChild(bird); // Start position: horizontally 30% from left, vertically centered bird.x = 2048 * 0.3; bird.y = 2732 / 2; // --- Reset function --- function resetGame() { // Remove pipes for (var i = 0; i < pipes.length; i++) { pipes[i].destroy(); } pipes = []; // Ensure background is always at the back if (background && background.parent) { background.parent.setChildIndex(background, 0); } // Reset bird bird.x = 2048 * 0.3; bird.y = 2732 / 2; bird.velocityY = 0; // Reset score score = 0; scoreTxt.setText(score); // Reset timers pipeSpawnTimer = 0; gameStarted = false; gameOver = false; if (tapToStartTxt) { tapToStartTxt.visible = true; // Remove and re-add to ensure it's on top if (tapToStartTxt.parent) { tapToStartTxt.parent.removeChild(tapToStartTxt); } game.addChild(tapToStartTxt); game.setChildIndex(tapToStartTxt, game.children.length - 1); } if (gameNameTxt) { gameNameTxt.visible = true; if (gameNameTxt.parent) { gameNameTxt.parent.removeChild(gameNameTxt); } game.addChild(gameNameTxt); game.setChildIndex(gameNameTxt, game.children.length - 1); } if (howToPlayTxt) { howToPlayTxt.visible = true; if (howToPlayTxt.parent) { howToPlayTxt.parent.removeChild(howToPlayTxt); } game.addChild(howToPlayTxt); game.setChildIndex(howToPlayTxt, game.children.length - 1); } } // --- Start game on first tap --- game.down = function (x, y, obj) { if (gameOver) { return; } if (!gameStarted) { gameStarted = true; if (tapToStartTxt) { tapToStartTxt.visible = false; } if (gameNameTxt) { gameNameTxt.visible = false; } if (howToPlayTxt) { howToPlayTxt.visible = false; } bird.flap(); return; } bird.flap(); }; // --- Main update loop --- game.update = function () { if (gameOver) { return; } // Bird physics if (gameStarted) { bird.update(); } // Clamp bird to top of screen if (bird.y - bird.radius < 0) { bird.y = bird.radius; bird.velocityY = 0; } // Pipe spawning if (gameStarted) { pipeSpawnTimer++; if (pipeSpawnTimer >= pipeInterval) { pipeSpawnTimer = 0; // Mountain-like curve for gapY using a sine wave var minGapY = 350; var maxGapY = 2732 - 120 - 350 - 700; var t = (score + pipes.length) * 0.5; // t increases as more pipes spawn var amplitude = (maxGapY - minGapY) / 2; var centerY = minGapY + amplitude; var gapY = centerY + Math.sin(t) * amplitude; gapY = Math.max(minGapY, Math.min(maxGapY, gapY)); var gapHeight = 650 + Math.floor(Math.random() * 120); // 650-770 px (increased gap) // Calculate pipe width based on score: start thin, widen much more slowly var minPipeWidth = 80; var maxPipeWidth = 880; // Use a slower growth curve (e.g. score/120 instead of score/40) var pipeWidth = minPipeWidth + Math.floor((maxPipeWidth - minPipeWidth) * Math.min(1, score / 120)); // Clamp to max if (pipeWidth > maxPipeWidth) { pipeWidth = maxPipeWidth; } if (pipeWidth < minPipeWidth) { pipeWidth = minPipeWidth; } var pipePair = new PipePair(); pipePair.pipeWidth = pipeWidth; pipePair.topPipe.width = pipeWidth; pipePair.bottomPipe.width = pipeWidth; pipePair.x = 2048; pipePair.setGap(gapY, gapHeight); pipes.push(pipePair); game.addChild(pipePair); } } // Update pipes, check for collisions and scoring for (var i = pipes.length - 1; i >= 0; i--) { var pipe = pipes[i]; if (gameStarted) { pipe.update(); } // Remove pipes that are off screen if (pipe.x + pipe.pipeWidth < 0) { pipe.destroy(); pipes.splice(i, 1); continue; } // Collision if (gameStarted && pipe.collidesWith(bird)) { endGame(); return; } // Scoring if (gameStarted && pipe.isPassedBy(bird)) { score += 1; scoreTxt.setText(score); LK.getSound('score').play(); pipe.passed = true; } } // --- Move upground to upper edge and ground to lower edge, fixed positions --- if (ground && upground) { // Increase ground height as score increases, but clamp to a max (e.g. 600) var baseGroundHeight = 120; var maxGroundHeight = 600; // For every 5 points, increase by 12px (tune as needed) var groundHeight = baseGroundHeight + Math.floor(score / 5) * 12; if (groundHeight > maxGroundHeight) { groundHeight = maxGroundHeight; } // Always full width, always at x=0 ground.x = 0; upground.x = 0; ground.width = 2048; upground.width = 2048; ground.height = groundHeight; upground.height = groundHeight; // upground at very top edge upground.y = 0; // ground at very bottom edge ground.y = 2732 - groundHeight; } // Ground collision if (gameStarted && bird.y + bird.radius >= ground.y) { bird.y = ground.y - bird.radius; endGame(); return; } // Upground (top ground) collision if (gameStarted && bird.y - bird.radius <= upground.y + upground.height) { bird.y = upground.y + upground.height + bird.radius; endGame(); return; } }; // --- End game --- function endGame() { if (gameOver) { return; } gameOver = true; bird.velocityY = 0; // Stop bird movement after death // Add a black circle under the blackhole layer on death, fade in like a blackhole var blackCircle = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2, scaleX: 2.5, scaleY: 2.5, tint: 0x000000 }); blackCircle.alpha = 0; var birdIndex = game.children.indexOf(bird); if (birdIndex > -1) { game.addChildAt(blackCircle, birdIndex); } else { game.addChild(blackCircle); } // Fade in the black circle slowly (like a blackhole) tween(blackCircle, { alpha: 1 }, { duration: 900, easing: tween.cubicInOut }); // Show blackhole image at center of screen, under the bird layer var blackhole = LK.getAsset('blackhole', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); blackhole.alpha = 0; // Start invisible var blackholeIndex = game.children.indexOf(bird); if (blackholeIndex > -1) { game.addChildAt(blackhole, blackholeIndex); } else { game.addChild(blackhole); } // Removed red flash effect on death // Fade in blackhole slowly, then spiral-pull bird to center and fix position tween(blackhole, { alpha: 1 }, { duration: 900, easing: tween.cubicInOut, onFinish: function onFinish() { // Spiral parameters var spiralSteps = 60; // 60 frames for spiral var spiralDuration = 1200; // ms // Capture bird's position at spiral start to avoid vibration var spiralStartX = bird.x; var spiralStartY = bird.y; var centerX = 2048 / 2; var centerY = 2732 / 2; var startAngle = Math.atan2(spiralStartY - centerY, spiralStartX - centerX); var radius = Math.sqrt((spiralStartX - centerX) * (spiralStartX - centerX) + (spiralStartY - centerY) * (spiralStartY - centerY)); var spiralTick = 0; // Stop any previous tweens on bird tween.stop(bird); // Spiral animation using manual tweening (only update position, do not tween other sprite properties) function spiralStep() { // Use t in [0,1] var t = spiralTick / spiralSteps; // Easing for spiral progress var easeT = tween.cubicInOut(t); // Spiral angle increases as t increases, start from startAngle var spiralAngle = startAngle + 4 * Math.PI * easeT; // 2 full turns // Radius shrinks to 0 var r = radius * (1 - easeT); // Calculate new position along spiral var x = centerX + r * Math.cos(spiralAngle); var y = centerY + r * Math.sin(spiralAngle); // Only update position, do not tween scale/rotation/etc bird.x = x; bird.y = y; spiralTick++; if (spiralTick <= spiralSteps) { LK.setTimeout(spiralStep, spiralDuration / spiralSteps); } else { // Snap to exact center bird.x = centerX; bird.y = centerY; LK.showGameOver(); } } spiralStep(); } }); } // --- Reset on game over (handled by LK) --- LK.on('gameover', function () { resetGame(); }); // --- Initial reset --- resetGame(); // --- Pause Button --- // Place pause button at right, same vertical as scoreTxt var pauseBtn = new Text2('II', { size: 120, fill: 0xffffff }); pauseBtn.anchor.set(1, 0); // Place at top right, just below top edge (keep 80px from top for visibility) pauseBtn.x = 2048 - 60; // 60px from right edge pauseBtn.y = 80; LK.gui.addChild(pauseBtn); // Pause event (optional: you may want to hook this up to LK.pauseGame if available) // pauseBtn.down = function(x, y, obj) { // LK.pauseGame(); // };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Bird class
var Bird = Container.expand(function () {
var self = Container.call(this);
// Attach bird asset (ellipse, yellow)
var birdAsset = self.attachAsset('bird', {
anchorX: 0.5,
anchorY: 0.5
});
// Physics properties
self.velocityY = 0;
self.gravity = 0.5; // Lower gravity per frame for slower fall
self.flapStrength = -20; // Lower flap strength for gentler jump
// Bird size for collision
self.radius = birdAsset.width * 0.45;
// Flap method
self.flap = function () {
self.velocityY = self.flapStrength;
};
// Update method (called every tick)
self.update = function () {
// Only apply gravity and velocity if gameStarted is true
if (typeof gameStarted !== "undefined" && gameStarted) {
self.velocityY += self.gravity;
self.y += self.velocityY;
}
// Clamp rotation for visual feedback (optional)
var maxAngle = Math.PI / 4;
var minAngle = -Math.PI / 6;
var angle = self.velocityY / 40 * maxAngle;
if (angle > maxAngle) {
angle = maxAngle;
}
if (angle < minAngle) {
angle = minAngle;
}
birdAsset.rotation = angle;
};
return self;
});
// PipePair class (top and bottom pipes as a pair)
var PipePair = Container.expand(function () {
var self = Container.call(this);
// Pipe properties
self.pipeWidth = 80; // Start very thin, will be set wider on spawn
self.gapHeight = 520; // Will be randomized a bit
self.speed = 12; // Speed at which pipes move left
// Top pipe (cloud)
self.topPipe = self.attachAsset('cloud', {
anchorX: 0,
anchorY: 1,
width: self.pipeWidth,
height: 300,
// cloud height
y: 0,
x: 0
});
// Bottom pipe (mountain)
self.bottomPipe = self.attachAsset('mountain', {
anchorX: 0,
anchorY: 0,
width: self.pipeWidth,
height: 600,
// mountain height
y: 0,
x: 0
});
// Used to track if score was already given for this pipe
self.passed = false;
// Set pipes' vertical positions
self.setGap = function (gapY, gapHeight) {
self.gapHeight = gapHeight;
// Top cloud: position at gapY, stretch to fill from top to gapY
self.topPipe.y = gapY;
self.topPipe.height = Math.max(1, gapY); // cloud height = gapY, min 1
self.topPipe.width = self.pipeWidth;
// Bottom mountain: position at gapY+gapHeight, stretch to fill to bottom
self.bottomPipe.y = gapY + gapHeight;
self.bottomPipe.height = Math.max(1, 2732 - (gapY + gapHeight)); // mountain height
self.bottomPipe.width = self.pipeWidth;
};
// Update method
self.update = function () {
self.x -= self.speed;
};
// Collision check with bird
self.collidesWith = function (bird) {
// Bird's bounding circle
var bx = bird.x;
var by = bird.y;
var r = bird.radius;
// Top pipe rectangle
var topRect = {
x: self.x,
y: 0,
w: self.pipeWidth,
h: self.topPipe.y
};
// Bottom pipe rectangle
var bottomRect = {
x: self.x,
y: self.bottomPipe.y,
w: self.pipeWidth,
h: 2732 - self.bottomPipe.y
};
// Helper: circle-rect collision
function circleRectCollide(cx, cy, cr, rx, ry, rw, rh) {
var closestX = Math.max(rx, Math.min(cx, rx + rw));
var closestY = Math.max(ry, Math.min(cy, ry + rh));
var dx = cx - closestX;
var dy = cy - closestY;
return dx * dx + dy * dy < cr * cr;
}
if (circleRectCollide(bx, by, r, topRect.x, topRect.y, topRect.w, topRect.h)) {
return true;
}
if (circleRectCollide(bx, by, r, bottomRect.x, bottomRect.y, bottomRect.w, bottomRect.h)) {
return true;
}
return false;
};
// Has bird passed this pipe? (for scoring)
self.isPassedBy = function (bird) {
return !self.passed && self.x + self.pipeWidth < bird.x - bird.radius;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// --- No dynamic ground/upground height/width/distance here; handled in update below --
var background = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732
});
game.addChild(background);
// --- Cloud (top pipe) - white, wide, ellipse
// Mountain (bottom pipe) - green, wide, triangle-like
// --- Game Variables ---
// Tween plugin for animations (not strictly needed for MVP, but included for future use)
// --- Asset Initialization (shapes) ---
var bird;
var pipes = [];
var pipeSpawnTimer = 0;
var pipeInterval = 90; // Frames between pipes (1.5s at 60fps)
var score = 0;
var scoreTxt;
var ground;
var gameStarted = false;
var gameOver = false;
// --- Tap to Start Overlay ---
var tapToStartTxt = new Text2('Tap to Start', {
size: 140,
fill: 0xffffff
});
tapToStartTxt.anchor.set(0.5, 1); // anchor bottom center
tapToStartTxt.x = 2048 / 2;
// Move down so the layer sits on the ground, with a gap above ground
var tapToStartGroundGap = 60; // distance above ground
var tapToStartMaxY = 2732 - 920 - tapToStartGroundGap; // 120 is base ground height
tapToStartTxt.y = tapToStartMaxY;
tapToStartTxt.visible = false;
// Add "How to play" instructions below the Tap to Start text
var howToPlayTxt = new Text2('Tap to power up and guide your UFO through doors.\nAvoid galactic doors and see how far you can go!', {
size: 70,
fill: 0xffffff
});
howToPlayTxt.anchor.set(0.5, 0); // anchor top center
howToPlayTxt.x = 2048 / 2;
howToPlayTxt.y = tapToStartTxt.y + 40; // 40px below tapToStartTxt
// Remove from game if already added, then add to top
if (tapToStartTxt.parent) {
tapToStartTxt.parent.removeChild(tapToStartTxt);
}
game.addChild(tapToStartTxt);
game.setChildIndex(tapToStartTxt, game.children.length - 1); // Always on top
if (howToPlayTxt.parent) {
howToPlayTxt.parent.removeChild(howToPlayTxt);
}
game.addChild(howToPlayTxt);
game.setChildIndex(howToPlayTxt, game.children.length - 1);
// --- GUI Score Display ---
if (typeof scoreTxt !== "undefined" && scoreTxt && scoreTxt.parent) {
scoreTxt.parent.removeChild(scoreTxt);
scoreTxt.destroy();
}
if (typeof scoreCircleBg !== "undefined" && scoreCircleBg && scoreCircleBg.parent) {
scoreCircleBg.parent.removeChild(scoreCircleBg);
scoreCircleBg.destroy();
}
// Create a perfect circle background for the score and center it
scoreCircleBg = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
x: LK.gui.width / 2,
y: 80 + 45,
// 45 is half of 90 (score text size), so circle is centered behind text
scaleX: 0.8,
scaleY: 0.8
});
LK.gui.addChild(scoreCircleBg);
scoreTxt = new Text2('0', {
size: 90,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0.5);
// Center score horizontally at the top, vertically centered in the circle
scoreTxt.x = LK.gui.width / 2;
scoreTxt.y = 80 + 45;
LK.gui.addChild(scoreTxt);
// Add game name text under the score in big font, but on overlay layer (with tapToStartTxt)
var gameNameTxt = new Text2('Galactic Doors', {
size: 220,
fill: 0xffffff,
font: "'Orbitron', 'Audiowide', 'Space Mono', 'GillSans-Bold', Impact, 'Arial Black', Tahoma"
});
gameNameTxt.anchor.set(0.5, 0);
// Place it under the score circle, with a gap, but on overlay
gameNameTxt.x = 2048 / 2;
gameNameTxt.y = tapToStartTxt.y - 1000; // 1000px above tapToStartTxt (visually grouped)
gameNameTxt.visible = false;
// Remove from game if already added, then add to top
if (gameNameTxt.parent) {
gameNameTxt.parent.removeChild(gameNameTxt);
}
game.addChild(gameNameTxt);
game.setChildIndex(gameNameTxt, game.children.length - 1);
// Move howToPlayTxt to overlay and hide initially
howToPlayTxt.x = 2048 / 2;
howToPlayTxt.y = tapToStartTxt.y + 40;
howToPlayTxt.visible = false;
if (howToPlayTxt.parent) {
howToPlayTxt.parent.removeChild(howToPlayTxt);
}
game.addChild(howToPlayTxt);
game.setChildIndex(howToPlayTxt, game.children.length - 1);
// --- Ground (for collision) ---
ground = LK.getAsset('ground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 2732 - 120
});
game.addChild(ground);
// --- Mirrored ground at top (now called upground) ---
var upground = LK.getAsset('ground', {
anchorX: 0,
anchorY: 1,
x: 0,
y: 0,
scaleY: -1
});
game.addChild(upground);
// --- Minimum ground width (never narrower than mountain/cloud asset) ---
var minGroundWidth = 800; // same as mountain/cloud asset width
var maxGroundWidth = 2048; // full width
// --- Bird ---
bird = new Bird();
game.addChild(bird);
// Start position: horizontally 30% from left, vertically centered
bird.x = 2048 * 0.3;
bird.y = 2732 / 2;
// --- Reset function ---
function resetGame() {
// Remove pipes
for (var i = 0; i < pipes.length; i++) {
pipes[i].destroy();
}
pipes = [];
// Ensure background is always at the back
if (background && background.parent) {
background.parent.setChildIndex(background, 0);
}
// Reset bird
bird.x = 2048 * 0.3;
bird.y = 2732 / 2;
bird.velocityY = 0;
// Reset score
score = 0;
scoreTxt.setText(score);
// Reset timers
pipeSpawnTimer = 0;
gameStarted = false;
gameOver = false;
if (tapToStartTxt) {
tapToStartTxt.visible = true;
// Remove and re-add to ensure it's on top
if (tapToStartTxt.parent) {
tapToStartTxt.parent.removeChild(tapToStartTxt);
}
game.addChild(tapToStartTxt);
game.setChildIndex(tapToStartTxt, game.children.length - 1);
}
if (gameNameTxt) {
gameNameTxt.visible = true;
if (gameNameTxt.parent) {
gameNameTxt.parent.removeChild(gameNameTxt);
}
game.addChild(gameNameTxt);
game.setChildIndex(gameNameTxt, game.children.length - 1);
}
if (howToPlayTxt) {
howToPlayTxt.visible = true;
if (howToPlayTxt.parent) {
howToPlayTxt.parent.removeChild(howToPlayTxt);
}
game.addChild(howToPlayTxt);
game.setChildIndex(howToPlayTxt, game.children.length - 1);
}
}
// --- Start game on first tap ---
game.down = function (x, y, obj) {
if (gameOver) {
return;
}
if (!gameStarted) {
gameStarted = true;
if (tapToStartTxt) {
tapToStartTxt.visible = false;
}
if (gameNameTxt) {
gameNameTxt.visible = false;
}
if (howToPlayTxt) {
howToPlayTxt.visible = false;
}
bird.flap();
return;
}
bird.flap();
};
// --- Main update loop ---
game.update = function () {
if (gameOver) {
return;
}
// Bird physics
if (gameStarted) {
bird.update();
}
// Clamp bird to top of screen
if (bird.y - bird.radius < 0) {
bird.y = bird.radius;
bird.velocityY = 0;
}
// Pipe spawning
if (gameStarted) {
pipeSpawnTimer++;
if (pipeSpawnTimer >= pipeInterval) {
pipeSpawnTimer = 0;
// Mountain-like curve for gapY using a sine wave
var minGapY = 350;
var maxGapY = 2732 - 120 - 350 - 700;
var t = (score + pipes.length) * 0.5; // t increases as more pipes spawn
var amplitude = (maxGapY - minGapY) / 2;
var centerY = minGapY + amplitude;
var gapY = centerY + Math.sin(t) * amplitude;
gapY = Math.max(minGapY, Math.min(maxGapY, gapY));
var gapHeight = 650 + Math.floor(Math.random() * 120); // 650-770 px (increased gap)
// Calculate pipe width based on score: start thin, widen much more slowly
var minPipeWidth = 80;
var maxPipeWidth = 880;
// Use a slower growth curve (e.g. score/120 instead of score/40)
var pipeWidth = minPipeWidth + Math.floor((maxPipeWidth - minPipeWidth) * Math.min(1, score / 120));
// Clamp to max
if (pipeWidth > maxPipeWidth) {
pipeWidth = maxPipeWidth;
}
if (pipeWidth < minPipeWidth) {
pipeWidth = minPipeWidth;
}
var pipePair = new PipePair();
pipePair.pipeWidth = pipeWidth;
pipePair.topPipe.width = pipeWidth;
pipePair.bottomPipe.width = pipeWidth;
pipePair.x = 2048;
pipePair.setGap(gapY, gapHeight);
pipes.push(pipePair);
game.addChild(pipePair);
}
}
// Update pipes, check for collisions and scoring
for (var i = pipes.length - 1; i >= 0; i--) {
var pipe = pipes[i];
if (gameStarted) {
pipe.update();
}
// Remove pipes that are off screen
if (pipe.x + pipe.pipeWidth < 0) {
pipe.destroy();
pipes.splice(i, 1);
continue;
}
// Collision
if (gameStarted && pipe.collidesWith(bird)) {
endGame();
return;
}
// Scoring
if (gameStarted && pipe.isPassedBy(bird)) {
score += 1;
scoreTxt.setText(score);
LK.getSound('score').play();
pipe.passed = true;
}
}
// --- Move upground to upper edge and ground to lower edge, fixed positions ---
if (ground && upground) {
// Increase ground height as score increases, but clamp to a max (e.g. 600)
var baseGroundHeight = 120;
var maxGroundHeight = 600;
// For every 5 points, increase by 12px (tune as needed)
var groundHeight = baseGroundHeight + Math.floor(score / 5) * 12;
if (groundHeight > maxGroundHeight) {
groundHeight = maxGroundHeight;
}
// Always full width, always at x=0
ground.x = 0;
upground.x = 0;
ground.width = 2048;
upground.width = 2048;
ground.height = groundHeight;
upground.height = groundHeight;
// upground at very top edge
upground.y = 0;
// ground at very bottom edge
ground.y = 2732 - groundHeight;
}
// Ground collision
if (gameStarted && bird.y + bird.radius >= ground.y) {
bird.y = ground.y - bird.radius;
endGame();
return;
}
// Upground (top ground) collision
if (gameStarted && bird.y - bird.radius <= upground.y + upground.height) {
bird.y = upground.y + upground.height + bird.radius;
endGame();
return;
}
};
// --- End game ---
function endGame() {
if (gameOver) {
return;
}
gameOver = true;
bird.velocityY = 0; // Stop bird movement after death
// Add a black circle under the blackhole layer on death, fade in like a blackhole
var blackCircle = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: 2.5,
scaleY: 2.5,
tint: 0x000000
});
blackCircle.alpha = 0;
var birdIndex = game.children.indexOf(bird);
if (birdIndex > -1) {
game.addChildAt(blackCircle, birdIndex);
} else {
game.addChild(blackCircle);
}
// Fade in the black circle slowly (like a blackhole)
tween(blackCircle, {
alpha: 1
}, {
duration: 900,
easing: tween.cubicInOut
});
// Show blackhole image at center of screen, under the bird layer
var blackhole = LK.getAsset('blackhole', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
blackhole.alpha = 0; // Start invisible
var blackholeIndex = game.children.indexOf(bird);
if (blackholeIndex > -1) {
game.addChildAt(blackhole, blackholeIndex);
} else {
game.addChild(blackhole);
}
// Removed red flash effect on death
// Fade in blackhole slowly, then spiral-pull bird to center and fix position
tween(blackhole, {
alpha: 1
}, {
duration: 900,
easing: tween.cubicInOut,
onFinish: function onFinish() {
// Spiral parameters
var spiralSteps = 60; // 60 frames for spiral
var spiralDuration = 1200; // ms
// Capture bird's position at spiral start to avoid vibration
var spiralStartX = bird.x;
var spiralStartY = bird.y;
var centerX = 2048 / 2;
var centerY = 2732 / 2;
var startAngle = Math.atan2(spiralStartY - centerY, spiralStartX - centerX);
var radius = Math.sqrt((spiralStartX - centerX) * (spiralStartX - centerX) + (spiralStartY - centerY) * (spiralStartY - centerY));
var spiralTick = 0;
// Stop any previous tweens on bird
tween.stop(bird);
// Spiral animation using manual tweening (only update position, do not tween other sprite properties)
function spiralStep() {
// Use t in [0,1]
var t = spiralTick / spiralSteps;
// Easing for spiral progress
var easeT = tween.cubicInOut(t);
// Spiral angle increases as t increases, start from startAngle
var spiralAngle = startAngle + 4 * Math.PI * easeT; // 2 full turns
// Radius shrinks to 0
var r = radius * (1 - easeT);
// Calculate new position along spiral
var x = centerX + r * Math.cos(spiralAngle);
var y = centerY + r * Math.sin(spiralAngle);
// Only update position, do not tween scale/rotation/etc
bird.x = x;
bird.y = y;
spiralTick++;
if (spiralTick <= spiralSteps) {
LK.setTimeout(spiralStep, spiralDuration / spiralSteps);
} else {
// Snap to exact center
bird.x = centerX;
bird.y = centerY;
LK.showGameOver();
}
}
spiralStep();
}
});
}
// --- Reset on game over (handled by LK) ---
LK.on('gameover', function () {
resetGame();
});
// --- Initial reset ---
resetGame();
// --- Pause Button ---
// Place pause button at right, same vertical as scoreTxt
var pauseBtn = new Text2('II', {
size: 120,
fill: 0xffffff
});
pauseBtn.anchor.set(1, 0);
// Place at top right, just below top edge (keep 80px from top for visibility)
pauseBtn.x = 2048 - 60; // 60px from right edge
pauseBtn.y = 80;
LK.gui.addChild(pauseBtn);
// Pause event (optional: you may want to hook this up to LK.pauseGame if available)
// pauseBtn.down = function(x, y, obj) {
// LK.pauseGame();
// };
galaxy theme background. In-Game asset. 2d. High contrast. No shadows
only black hole simple no background. In-Game asset. 2d. High contrast. No shadows
galactic portals looks like a black hole. In-Game asset. 2d. High contrast. No shadows
galactic portals looks like a long straight pillar . In-Game asset. 2d. High contrast. No shadows
create an ufo without legs only frizby shape. In-Game asset. 2d. High contrast. No shadows