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