/**** * Plugins ****/ var storage = LK.import("@upit/storage.v1"); var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var CircularObstacle = Container.expand(function (starX, starY, radius, angleSpeed, initialAngle) { var self = Container.call(this); var obstacleGraphics = self.attachAsset('obstacle', { anchorX: 0.5, anchorY: 0.5 }); self.angle = initialAngle || 0; self.radius = radius; self.angleSpeed = angleSpeed * 0.75; self.starX = starX; self.starY = starY; self.updatePosition = function () { self.x = self.starX + Math.cos(self.angle) * self.radius; self.y = self.starY + Math.sin(self.angle) * self.radius; self.angle += self.angleSpeed * 0.35; obstacleGraphics.rotation += self.angleSpeed * 0.35; }; self.moveDown = function (distance, dotY) { var speedMultiplier = dotY < 1200 ? 3 : dotY < 1400 ? 2 : dotY < 2000 ? 1.3 : 1.2; self.starY += distance * speedMultiplier; }; self.updatePosition(); }); var Dot = Container.expand(function () { var self = Container.call(this); var dotGraphics = self.attachAsset('dot', { anchorX: 0.5, anchorY: 0.5 }); self.destroyed = false; self.velocityY = 0; self.gravity = 0.7; self.bounce = -15; self._update_migrated = function () { var previousY = self.y; self.velocityY += self.gravity; self.y += self.velocityY; if (!self.firstTouch && !self.intersects(hand)) { self.y = 2732 - self.height / 2 - 500; } else if (self.intersects(hand)) { self.bounceUp(); } else if (self.y > 2232 - self.height / 2 && self.firstTouch && !self.firstStarDestroyed) { self.velocityY = self.bounce; } else if (self.y > 2832 && !self.destroyed) { createDotParticleEffect(self.x, self.y); self.destroyed = true; self.destroy(); LK.getSound('death').play(); // Play death sound LK.setTimeout(function () { triggerGameOver(); }, 1000); } self.movedDistance = self.y - previousY; }; self.bounceUp = function () { if (!self.destroyed) { self.velocityY = self.bounce; self.firstTouch = true; if (!self.firstStarDestroyed) { self.firstStarDestroyed = true; } } }; }); var DotParticleEffect = Container.expand(function (x, y) { var self = Container.call(this); self.x = x; self.y = y; var particles = []; var angleIncrement = Math.PI * 2 / 20; var speed = 30; for (var i = 0; i < 20; i++) { var particle = self.attachAsset('dot', { anchorX: 0.5, anchorY: 0.5 }); particle.scaleX = particle.scaleY = 0.5; particle.alpha = 1; var angle = i * angleIncrement; particle.vx = Math.cos(angle) * speed; particle.vy = Math.sin(angle) * speed; particles.push(particle); } self._update_migrated = function () { for (var i = particles.length - 1; i >= 0; i--) { particles[i].x += particles[i].vx; particles[i].y += particles[i].vy; particles[i].alpha -= 0.0167; if (particles[i].alpha <= 0) { particles[i].destroy(); particles.splice(i, 1); } } if (particles.length === 0) { self.destroy(); } }; game.addChild(self); }); // Add Hand asset below the dot var Hand = Container.expand(function () { var self = Container.call(this); var handGraphics = self.attachAsset('hand', { anchorX: 0.5, anchorY: 0, alpha: 0.9 }); self._update_migrated = function (distance) { if (distance) { self.y += distance; } }; }); var OrbitLine = Container.expand(function (starX, starY, radius) { var self = Container.call(this); self.starX = starX; self.starY = starY; self.radius = radius; var segments = 50; var angleIncrement = Math.PI * 2 / segments; for (var i = 0; i < segments; i++) { var angle = i * angleIncrement; var dot = self.attachAsset('smallObstacle', { x: starX + Math.cos(angle) * radius, y: starY + Math.sin(angle) * radius, anchorX: 0.5, anchorY: 0.5 }); if (i % 2 === 0) { dot.alpha = 0; } } self.moveDown = function (distance, dotY) { var speedMultiplier = dotY < 1200 ? 3 : dotY < 1400 ? 2 : dotY < 2000 ? 1.3 : dotY < 2300 ? 1.2 : 1; self.y += distance * speedMultiplier; }; }); var ParticleEffect = Container.expand(function (x, y) { var self = Container.call(this); self.x = x; self.y = y; var particles = []; for (var i = 0; i < 20; i++) { var particle = self.attachAsset('star', { anchorX: 0.5, anchorY: 0.5 }); particle.scaleX = particle.scaleY = Math.random() * 0.5 + 0.5; particle.alpha = 0.7; particle.vx = (Math.random() - 0.5) * 10; particle.vy = (Math.random() - 0.5) * 10; particles.push(particle); } self._update_migrated = function () { for (var i = particles.length - 1; i >= 0; i--) { particles[i].x += particles[i].vx; particles[i].y += particles[i].vy; particles[i].alpha -= 0.02; if (particles[i].alpha <= 0) { particles[i].destroy(); particles.splice(i, 1); } } if (particles.length === 0) { self.destroy(); } }; game.addChild(self); }); var ScorePopup = Container.expand(function (x, y) { var self = Container.call(this); self.x = x; self.y = y; var scoreText = new Text2('+1', { size: 100, fill: 0xFFFFFF, anchorX: 0.5, anchorY: 0.5 }); scoreText.x = -scoreText.width / 2; scoreText.y = -scoreText.height / 2; self.addChild(scoreText); self.alpha = 1; var duration = 120; self._update_migrated = function () { duration--; self.y -= 2; self.alpha -= 1 / 120; if (duration <= 0) { self.destroy(); } }; game.addChild(self); }); var Star = Container.expand(function () { var self = Container.call(this); var starGraphics = self.attachAsset('star', { anchorX: 0.5, anchorY: 0.5 }); self.scaleDirection = 1; self.scaleSpeed = 0.005; self.minScale = 1; self.maxScale = 1.2; self.moveDown = function (distance, dotY) { var speedMultiplier = dotY < 1200 ? 3 : dotY < 1400 ? 2 : dotY < 2000 ? 1.3 : 1.2; self.y += distance * speedMultiplier; if (self.y > 2732 - self.height / 2) { self.y = -self.height / 2; } }; self.updateScale = function () { if (self.scaleDirection === 1 && starGraphics.scale.x < self.maxScale) { starGraphics.scale.x += self.scaleSpeed; starGraphics.scale.y += self.scaleSpeed; } else if (self.scaleDirection === -1 && starGraphics.scale.x > self.minScale) { starGraphics.scale.x -= self.scaleSpeed; starGraphics.scale.y -= self.scaleSpeed; } if (starGraphics.scale.x >= self.maxScale || starGraphics.scale.x <= self.minScale) { self.scaleDirection *= -1; } }; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ LK.playMusic('Backgroundmusic'); var gameState = 'title'; // 'title', 'playing', 'gameOver' var gameTitleAsset; var highScoreTextDisplay; var tapToStartText; var blinkIntervalId = null; // For "Tap to Start" blinking // Game elements that will be initialized later or need visibility control var dot; var stars = []; var obstacles = []; var orbitLine; var hand; var handMoved = false; var titleDot; // Declare titleDot here var scoreTxt; // Declare scoreTxt here so it's globally accessible for title/game states var highScoreGameTxt; // New: high score text for in-game display var menuBackground; // For managing the title screen background overlay // Function to setup the title screen function setupTitleScreen() { gameState = 'title'; LK.setScore(0); // Reset score for the title screen phase // Add a black background overlay that covers the entire game area menuBackground = LK.getAsset('smallObstacle', { anchorX: 0.5, anchorY: 0.5, width: 2048, height: 2732, tint: 0x000000, alpha: 1.0 }); menuBackground.width = 2048; menuBackground.height = 2732; menuBackground.x = 2048 / 2; menuBackground.y = 2732 / 2; game.addChild(menuBackground); gameTitleAsset = game.addChild(LK.getAsset('gameTitle', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 3.5 // Position title a bit up })); // Add a large dot below the game title titleDot = game.addChild(LK.getAsset('dot', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: gameTitleAsset.y + gameTitleAsset.height / 2 + 600, // Position below the title, moved 400px down // Position below the title scaleX: 4, // Make it bigger scaleY: 4 // Make it bigger })); // Add a soft up and down bounce animation to the titleDot that always returns to the same position (function bounceTitleDot() { // Defensive: Only animate if titleDot exists, is not null, and is not destroyed if (!(gameState === 'title' && titleDot && titleDot.parent && typeof titleDot.y === 'number')) return; var originalY = titleDot.y; // Always tween from originalY to originalY-60 and back, so the dot never drifts tween(titleDot, { y: originalY - 60 }, { duration: 700, easing: tween.sineInOut, onFinish: function onFinish() { // Defensive: Check again before next tween if (!(gameState === 'title' && titleDot && titleDot.parent && typeof titleDot.y === 'number')) return; tween(titleDot, { y: originalY }, { duration: 700, easing: tween.sineInOut, onFinish: function onFinish() { // Loop the bounce as long as we're on the title screen and titleDot is valid bounceTitleDot(); } }); } }); })(); // Remove "TAP TO START" text from inside the titleDot (do not add it to the dot) // Center "Tap to Start" horizontally and place it above the high score (if present), otherwise below the title tapToStartText = new Text2('TAP TO START', { size: 120, fill: '#FFFFFF', // White text stroke: '#000000', // Black outline for contrast strokeThickness: 8, alpha: 0 // Start invisible for soft fade animation // anchorX and anchorY will be set via .anchor.set() for precise centering }); tapToStartText.anchor.set(0.5, 0.5); tapToStartText.x = 2048 / 2; var tapTextYPosition; if (highScoreTextDisplay && highScoreTextDisplay.parent) { // Place "Tap to Start" above the high score tapTextYPosition = highScoreTextDisplay.y - highScoreTextDisplay.height / 2 - 100; } else if (gameTitleAsset && gameTitleAsset.parent) { tapTextYPosition = gameTitleAsset.y + gameTitleAsset.height / 2 + 150; } else { tapTextYPosition = 2732 / 2 + 100; // Fallback y position } tapToStartText.y = tapTextYPosition + 1000; game.addChild(tapToStartText); if (blinkIntervalId) LK.clearInterval(blinkIntervalId); // Soft fade in/out animation using tween var fadeIn = true; function animateTapToStart() { if (!(gameState === 'title' && tapToStartText && tapToStartText.parent)) { if (blinkIntervalId) { LK.clearInterval(blinkIntervalId); blinkIntervalId = null; } return; } tween(tapToStartText, { alpha: fadeIn ? 1 : 0.3 }, { duration: 700, onFinish: function onFinish() { fadeIn = !fadeIn; animateTapToStart(); } }); } animateTapToStart(); if (scoreTxt && scoreTxt.parent) { scoreTxt.visible = false; } // Hide all game elements when title screen is up - ensure they're properly hidden if (dot) { dot.visible = false; } stars.forEach(function (s) { if (s) s.visible = false; }); obstacles.forEach(function (o) { if (o) o.visible = false; }); if (orbitLine) orbitLine.visible = false; if (hand) hand.visible = false; } // Function to initialize and start the actual game function startGamePlay() { if (gameState !== 'title') return; // gameState = 'playing'; // Moved into completeGameSetupAfterFade if (blinkIntervalId) { LK.clearInterval(blinkIntervalId); blinkIntervalId = null; } var elementsToFadeOut = []; if (gameTitleAsset && gameTitleAsset.parent) elementsToFadeOut.push(gameTitleAsset); if (highScoreTextDisplay && highScoreTextDisplay.parent) elementsToFadeOut.push(highScoreTextDisplay); if (tapToStartText && tapToStartText.parent) elementsToFadeOut.push(tapToStartText); if (menuBackground && menuBackground.parent) elementsToFadeOut.push(menuBackground); if (titleDot && titleDot.parent) elementsToFadeOut.push(titleDot); var fadeDuration = 500; // Duration of the fade in milliseconds var tweensCompletedCount = 0; function completeGameSetupAfterFade() { // Destroy elements now that they are faded and nullify global references if (gameTitleAsset && gameTitleAsset.parent) gameTitleAsset.destroy(); gameTitleAsset = null; if (highScoreTextDisplay && highScoreTextDisplay.parent) highScoreTextDisplay.destroy(); highScoreTextDisplay = null; if (tapToStartText && tapToStartText.parent) tapToStartText.destroy(); tapToStartText = null; if (menuBackground && menuBackground.parent) { menuBackground.destroy(); menuBackground = null; } if (titleDot && titleDot.parent) { titleDot.destroy(); } titleDot = null; // Nullify titleDot as well for good practice // This is the logic that was originally AFTER the destroyed elements if (!scoreTxt || !scoreTxt.parent) { if (scoreTxt) scoreTxt.destroy(); // Ensure old one is gone if somehow exists scoreTxt = new Text2('0', { size: 120, // Adjusted size for top-right placement fill: 0xFFFFFF }); //{2R} // Line identifier preserved scoreTxt.anchor.set(1, 0); // Anchor to top-right of the text scoreTxt.x = -20; // Position 20px from the right edge of the gui.topRight container scoreTxt.y = 20; // Position 20px from the top edge of the gui.topRight container LK.gui.topRight.addChild(scoreTxt); } scoreTxt.setText('0'); scoreTxt.alpha = 0; // Prepare scoreTxt for fade-in scoreTxt.visible = true; // Add high score below the score if (highScoreGameTxt && highScoreGameTxt.parent) { highScoreGameTxt.destroy(); highScoreGameTxt = null; } var currentHighScore = storage.highScore || 0; if (currentHighScore > 0) { highScoreGameTxt = new Text2('BEST: ' + currentHighScore, { size: 40, // Reduced size for the 'BEST' text // Adjusted size for better visibility below score fill: '#FFFFFF' }); //{3i} // Preserving line identifier for the Text2 options object highScoreGameTxt.anchor.set(1, 0); // Anchor to its top-right highScoreGameTxt.x = -20; // Align with scoreTxt's x-position (20px from right edge of gui.topRight) // Position highScoreGameTxt directly below scoreTxt if (scoreTxt && scoreTxt.parent) { // Ensure scoreTxt is available highScoreGameTxt.y = scoreTxt.y + scoreTxt.height + 10; // Position 10px below scoreTxt } else { // Fallback if scoreTxt isn't ready (scoreTxt.y is 20, assuming its height ~120) highScoreGameTxt.y = 20 + 120 + 10; } LK.gui.topRight.addChild(highScoreGameTxt); // Add to top right GUI container highScoreGameTxt.alpha = 0; // Prepare for fade-in, similar to scoreTxt } LK.setScore(0); initializeGameElements(); gameState = 'playing'; // Set gameState to 'playing' only after all setup is complete } if (elementsToFadeOut.length === 0) { completeGameSetupAfterFade(); // No elements to fade, proceed directly } else { elementsToFadeOut.forEach(function (element) { tween(element, { alpha: 0 }, { duration: fadeDuration, onFinish: function onFinish() { tweensCompletedCount++; if (tweensCompletedCount === elementsToFadeOut.length) { completeGameSetupAfterFade(); } } }); }); } } // Function to initialize and fade in game elements function initializeGameElements() { var elementsToFadeIn = []; var fadeDuration = 500; // Duration for game elements fade-in in milliseconds // Collect scoreTxt if it's ready (alpha is preset by completeGameSetupAfterFade) if (scoreTxt && scoreTxt.parent) { elementsToFadeIn.push(scoreTxt); } // Collect highScoreGameTxt if it exists and is part of the GUI if (highScoreGameTxt && highScoreGameTxt.parent) { elementsToFadeIn.push(highScoreGameTxt); } // Collect dot if (dot) { elementsToFadeIn.push(dot); } // Collect stars stars.forEach(function (s) { if (s) { elementsToFadeIn.push(s); } }); // Collect obstacles (this includes the initial orbitLine and CircularObstacles) obstacles.forEach(function (o) { if (o) { elementsToFadeIn.push(o); } }); // Collect hand if (hand) { elementsToFadeIn.push(hand); } if (elementsToFadeIn.length === 0) { return; // No elements to fade } // Set initial properties and start tween for each element elementsToFadeIn.forEach(function (element) { // For elements other than scoreTxt, set initial alpha. // scoreTxt's alpha is set to 0 by the calling function completeGameSetupAfterFade. if (element !== scoreTxt) { element.alpha = 0; } element.visible = true; // Ensure element is visible for alpha tween to have an effect tween(element, { // Tween to fade in alpha: 1 }, { duration: fadeDuration // No specific onFinish needed here as gameState is handled by the caller }); }); } // Initialize game elements before setting up the title screen. // This ensures they exist for the hiding logic in setupTitleScreen and are layered correctly. // Dot (needed for Hand positioning) dot = game.addChild(new Dot()); dot.x = 2048 / 2; dot.y = 2732 - dot.height / 2 - 200; // Stars // global 'stars' array is already initialized as [] in variable declarations star = game.addChild(new Star()); star.x = 2048 / 2; star.y = 2732 / 2 - 500; stars.push(star); // Hand // Global 'hand' is assigned within createHand. createHand function definition is hoisted. // Global 'handMoved' is initialized at declaration; ensure it's correctly 'false' for a new game start. createHand(); handMoved = false; // Explicitly set for game start logic // Obstacles and OrbitLine // global 'obstacles' array is already initialized as [] in variable declarations // orbitLine needs 'star' to be defined. orbitLine = game.addChild(new OrbitLine(star.x, star.y, 300)); obstacles.push(orbitLine); // Call to spawnInitialObstacles moved to earlier consolidated block spawnInitialObstacles(); setupTitleScreen(); // Show title screen on game load // Initialize the offscreen threshold for destroying obstacles var offscreenThreshold = 2732 + 1000; function createParticleEffect(x, y) { var effect = new ParticleEffect(x, y); LK.on('tick', function () { effect._update_migrated(); }); } function createDotParticleEffect(x, y) { var effect = new DotParticleEffect(x, y); LK.on('tick', function () { effect._update_migrated(); }); } function updateObstacles() { var obstacleCount = 5 + LK.getScore(); while (obstacles.length < obstacleCount) { var obstacle = game.addChild(new Obstacle()); obstacle.x = obstacle.direction === 1 ? -obstacle.width / 2 : 2048 + obstacle.width / 2; obstacle.y = Math.random() * (2732 - obstacle.height) + obstacle.height / 2; obstacles.push(obstacle); } } // scoreTxt is now declared globally as 'var scoreTxt;' // It is initialized and added to LK.gui.top within the 'startGamePlay' function. // This removes the original duplicate initialization and addition. game.on('down', function (x, y, obj) { if (gameState === 'title') { startGamePlay(); LK.getSound('bounce').play(); // Play a sound on game start } else if (gameState === 'playing' && dot && !dot.destroyed) { dot.bounceUp(); LK.getSound('bounce').play(); } }); // Star creation and stars array population moved to earlier consolidated block // OrbitLine creation and addition to obstacles moved to earlier consolidated block function spawnInitialObstacles() { var starX = 2048 / 2; var starY = 2732 / 2 - 500; var radius = 300; var angleSpeed = 0.05; for (var i = 0; i < 20; i++) { var obstacle = game.addChild(new CircularObstacle(starX, starY, radius, angleSpeed)); obstacles.push(obstacle); } } function triggerGameOver() { if (gameState === 'gameOver') return; // Already processing game over gameState = 'gameOver'; var currentScore = LK.getScore(); var highScore = storage.highScore || 0; if (currentScore > highScore) { storage.highScore = currentScore; } LK.showGameOver(); // This will reset the game instance } LK.on('tick', function () { if (gameState !== 'playing') { // Title screen animations (like blinking text) are handled by their own LK.setInterval. // No main game logic runs if not in 'playing' state. return; } // Ensure dot exists and is not destroyed before trying to update it. // Game over logic should handle the transition away from 'playing' state. if (!dot || dot.destroyed) { return; } dot._update_migrated(); for (var i = 0; i < obstacles.length; i++) { if (obstacles[i] instanceof CircularObstacle) { obstacles[i].updatePosition(); } else { if (typeof obstacles[i]._move_migrated === 'function') { obstacles[i]._move_migrated(); } } if (dot.y < 2300 && dot.movedDistance < 0) { obstacles[i].moveDown(-dot.movedDistance, dot.y); } if (dot.intersects(obstacles[i]) && !(obstacles[i] instanceof OrbitLine)) { if (!dot.destroyed) { createDotParticleEffect(dot.x, dot.y); dot.destroyed = true; LK.getSound('death').play(); // Play death sound } dot.destroy(); LK.setTimeout(function () { triggerGameOver(); }, 1000); } else if (obstacles[i].y > offscreenThreshold) { obstacles[i].destroy(); obstacles.splice(i, 1); } } for (var j = stars.length - 1; j >= 0; j--) { stars[j].updateScale(); if (dot.intersects(stars[j])) { LK.getSound('star').play(); // Play star sound LK.setScore(LK.getScore() + 1); scoreTxt.setText(LK.getScore().toString()); scoreTxt.alpha = 0; var originalY = scoreTxt.y; LK.on('tick', function updateScoreTextFadeIn() { if (!scoreTxt || !scoreTxt.parent) { // Defensive check for scoreTxt LK.off('tick', updateScoreTextFadeIn); return; } scoreTxt.alpha += 0.05; if (scoreTxt.alpha >= 1) { scoreTxt.alpha = 1; // Cap alpha at 1 scoreTxt.y = originalY; // Explicitly reset Y to its original position for this animation cycle LK.off('tick', updateScoreTextFadeIn); // Remove this animation listener } else { // Update Y position only while the animation is in progress scoreTxt.y = originalY - (1 - scoreTxt.alpha) * 50; } }); var oldStarY = stars[j].y; createParticleEffect(stars[j].x, stars[j].y); var scorePopup = new ScorePopup(stars[j].x, stars[j].y); LK.on('tick', function () { scorePopup._update_migrated(); }); stars[j].destroy(); stars.splice(j, 1); var newStar = game.addChild(new Star()); newStar.x = 2048 / 2; var additionalYOffset = LK.getScore() > 1 ? (LK.getScore() - 1) * 200 : 0; newStar.y = oldStarY - 2000 - additionalYOffset; stars.push(newStar); if (!handMoved && hand) { var handMoveDistance = 0; var handMoveInterval = LK.setInterval(function () { handMoveDistance += 1; hand._update_migrated(handMoveDistance); if (handMoveDistance >= 1000) { LK.clearInterval(handMoveInterval); } }, 10); handMoved = true; } // Increase the offscreen threshold for destroying obstacles offscreenThreshold += 300; // Add a cumulative number of CircularObstacles at random positions around the new star var cumulativeObstacles = 1 + LK.getScore(); for (var k = 0; k < cumulativeObstacles; k++) { var randomAngle = Math.random() * Math.PI * 2; // Random angle in radians var radiusOffset = k * 100; var circularObstacle = game.addChild(new CircularObstacle(newStar.x, newStar.y, 300 + radiusOffset, 0.05 * (k % 2 === 0 ? 1 : -1), randomAngle)); obstacles.push(circularObstacle); } // Add orbit line after creating all obstacles var orbitLine = game.addChild(new OrbitLine(newStar.x, newStar.y, 300 + radiusOffset)); obstacles.push(orbitLine); } else if (dot.y < 2300 && dot.movedDistance < 0) { stars[j].moveDown(-dot.movedDistance, dot.y); } } }); // Dot creation moved to earlier consolidated block // Redundant 'hand' and 'handMoved' declarations removed. // createHand() call and handMoved reset moved to earlier consolidated block. // createHand function definition remains and is hoisted. function createHand() { hand = game.addChild(new Hand()); hand.x = dot.x; hand.y = dot.y + dot.height / 2; }
/****
* Plugins
****/
var storage = LK.import("@upit/storage.v1");
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var CircularObstacle = Container.expand(function (starX, starY, radius, angleSpeed, initialAngle) {
var self = Container.call(this);
var obstacleGraphics = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5
});
self.angle = initialAngle || 0;
self.radius = radius;
self.angleSpeed = angleSpeed * 0.75;
self.starX = starX;
self.starY = starY;
self.updatePosition = function () {
self.x = self.starX + Math.cos(self.angle) * self.radius;
self.y = self.starY + Math.sin(self.angle) * self.radius;
self.angle += self.angleSpeed * 0.35;
obstacleGraphics.rotation += self.angleSpeed * 0.35;
};
self.moveDown = function (distance, dotY) {
var speedMultiplier = dotY < 1200 ? 3 : dotY < 1400 ? 2 : dotY < 2000 ? 1.3 : 1.2;
self.starY += distance * speedMultiplier;
};
self.updatePosition();
});
var Dot = Container.expand(function () {
var self = Container.call(this);
var dotGraphics = self.attachAsset('dot', {
anchorX: 0.5,
anchorY: 0.5
});
self.destroyed = false;
self.velocityY = 0;
self.gravity = 0.7;
self.bounce = -15;
self._update_migrated = function () {
var previousY = self.y;
self.velocityY += self.gravity;
self.y += self.velocityY;
if (!self.firstTouch && !self.intersects(hand)) {
self.y = 2732 - self.height / 2 - 500;
} else if (self.intersects(hand)) {
self.bounceUp();
} else if (self.y > 2232 - self.height / 2 && self.firstTouch && !self.firstStarDestroyed) {
self.velocityY = self.bounce;
} else if (self.y > 2832 && !self.destroyed) {
createDotParticleEffect(self.x, self.y);
self.destroyed = true;
self.destroy();
LK.getSound('death').play(); // Play death sound
LK.setTimeout(function () {
triggerGameOver();
}, 1000);
}
self.movedDistance = self.y - previousY;
};
self.bounceUp = function () {
if (!self.destroyed) {
self.velocityY = self.bounce;
self.firstTouch = true;
if (!self.firstStarDestroyed) {
self.firstStarDestroyed = true;
}
}
};
});
var DotParticleEffect = Container.expand(function (x, y) {
var self = Container.call(this);
self.x = x;
self.y = y;
var particles = [];
var angleIncrement = Math.PI * 2 / 20;
var speed = 30;
for (var i = 0; i < 20; i++) {
var particle = self.attachAsset('dot', {
anchorX: 0.5,
anchorY: 0.5
});
particle.scaleX = particle.scaleY = 0.5;
particle.alpha = 1;
var angle = i * angleIncrement;
particle.vx = Math.cos(angle) * speed;
particle.vy = Math.sin(angle) * speed;
particles.push(particle);
}
self._update_migrated = function () {
for (var i = particles.length - 1; i >= 0; i--) {
particles[i].x += particles[i].vx;
particles[i].y += particles[i].vy;
particles[i].alpha -= 0.0167;
if (particles[i].alpha <= 0) {
particles[i].destroy();
particles.splice(i, 1);
}
}
if (particles.length === 0) {
self.destroy();
}
};
game.addChild(self);
});
// Add Hand asset below the dot
var Hand = Container.expand(function () {
var self = Container.call(this);
var handGraphics = self.attachAsset('hand', {
anchorX: 0.5,
anchorY: 0,
alpha: 0.9
});
self._update_migrated = function (distance) {
if (distance) {
self.y += distance;
}
};
});
var OrbitLine = Container.expand(function (starX, starY, radius) {
var self = Container.call(this);
self.starX = starX;
self.starY = starY;
self.radius = radius;
var segments = 50;
var angleIncrement = Math.PI * 2 / segments;
for (var i = 0; i < segments; i++) {
var angle = i * angleIncrement;
var dot = self.attachAsset('smallObstacle', {
x: starX + Math.cos(angle) * radius,
y: starY + Math.sin(angle) * radius,
anchorX: 0.5,
anchorY: 0.5
});
if (i % 2 === 0) {
dot.alpha = 0;
}
}
self.moveDown = function (distance, dotY) {
var speedMultiplier = dotY < 1200 ? 3 : dotY < 1400 ? 2 : dotY < 2000 ? 1.3 : dotY < 2300 ? 1.2 : 1;
self.y += distance * speedMultiplier;
};
});
var ParticleEffect = Container.expand(function (x, y) {
var self = Container.call(this);
self.x = x;
self.y = y;
var particles = [];
for (var i = 0; i < 20; i++) {
var particle = self.attachAsset('star', {
anchorX: 0.5,
anchorY: 0.5
});
particle.scaleX = particle.scaleY = Math.random() * 0.5 + 0.5;
particle.alpha = 0.7;
particle.vx = (Math.random() - 0.5) * 10;
particle.vy = (Math.random() - 0.5) * 10;
particles.push(particle);
}
self._update_migrated = function () {
for (var i = particles.length - 1; i >= 0; i--) {
particles[i].x += particles[i].vx;
particles[i].y += particles[i].vy;
particles[i].alpha -= 0.02;
if (particles[i].alpha <= 0) {
particles[i].destroy();
particles.splice(i, 1);
}
}
if (particles.length === 0) {
self.destroy();
}
};
game.addChild(self);
});
var ScorePopup = Container.expand(function (x, y) {
var self = Container.call(this);
self.x = x;
self.y = y;
var scoreText = new Text2('+1', {
size: 100,
fill: 0xFFFFFF,
anchorX: 0.5,
anchorY: 0.5
});
scoreText.x = -scoreText.width / 2;
scoreText.y = -scoreText.height / 2;
self.addChild(scoreText);
self.alpha = 1;
var duration = 120;
self._update_migrated = function () {
duration--;
self.y -= 2;
self.alpha -= 1 / 120;
if (duration <= 0) {
self.destroy();
}
};
game.addChild(self);
});
var Star = Container.expand(function () {
var self = Container.call(this);
var starGraphics = self.attachAsset('star', {
anchorX: 0.5,
anchorY: 0.5
});
self.scaleDirection = 1;
self.scaleSpeed = 0.005;
self.minScale = 1;
self.maxScale = 1.2;
self.moveDown = function (distance, dotY) {
var speedMultiplier = dotY < 1200 ? 3 : dotY < 1400 ? 2 : dotY < 2000 ? 1.3 : 1.2;
self.y += distance * speedMultiplier;
if (self.y > 2732 - self.height / 2) {
self.y = -self.height / 2;
}
};
self.updateScale = function () {
if (self.scaleDirection === 1 && starGraphics.scale.x < self.maxScale) {
starGraphics.scale.x += self.scaleSpeed;
starGraphics.scale.y += self.scaleSpeed;
} else if (self.scaleDirection === -1 && starGraphics.scale.x > self.minScale) {
starGraphics.scale.x -= self.scaleSpeed;
starGraphics.scale.y -= self.scaleSpeed;
}
if (starGraphics.scale.x >= self.maxScale || starGraphics.scale.x <= self.minScale) {
self.scaleDirection *= -1;
}
};
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
LK.playMusic('Backgroundmusic');
var gameState = 'title'; // 'title', 'playing', 'gameOver'
var gameTitleAsset;
var highScoreTextDisplay;
var tapToStartText;
var blinkIntervalId = null; // For "Tap to Start" blinking
// Game elements that will be initialized later or need visibility control
var dot;
var stars = [];
var obstacles = [];
var orbitLine;
var hand;
var handMoved = false;
var titleDot; // Declare titleDot here
var scoreTxt; // Declare scoreTxt here so it's globally accessible for title/game states
var highScoreGameTxt; // New: high score text for in-game display
var menuBackground; // For managing the title screen background overlay
// Function to setup the title screen
function setupTitleScreen() {
gameState = 'title';
LK.setScore(0); // Reset score for the title screen phase
// Add a black background overlay that covers the entire game area
menuBackground = LK.getAsset('smallObstacle', {
anchorX: 0.5,
anchorY: 0.5,
width: 2048,
height: 2732,
tint: 0x000000,
alpha: 1.0
});
menuBackground.width = 2048;
menuBackground.height = 2732;
menuBackground.x = 2048 / 2;
menuBackground.y = 2732 / 2;
game.addChild(menuBackground);
gameTitleAsset = game.addChild(LK.getAsset('gameTitle', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 3.5 // Position title a bit up
}));
// Add a large dot below the game title
titleDot = game.addChild(LK.getAsset('dot', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: gameTitleAsset.y + gameTitleAsset.height / 2 + 600,
// Position below the title, moved 400px down
// Position below the title
scaleX: 4,
// Make it bigger
scaleY: 4 // Make it bigger
}));
// Add a soft up and down bounce animation to the titleDot that always returns to the same position
(function bounceTitleDot() {
// Defensive: Only animate if titleDot exists, is not null, and is not destroyed
if (!(gameState === 'title' && titleDot && titleDot.parent && typeof titleDot.y === 'number')) return;
var originalY = titleDot.y;
// Always tween from originalY to originalY-60 and back, so the dot never drifts
tween(titleDot, {
y: originalY - 60
}, {
duration: 700,
easing: tween.sineInOut,
onFinish: function onFinish() {
// Defensive: Check again before next tween
if (!(gameState === 'title' && titleDot && titleDot.parent && typeof titleDot.y === 'number')) return;
tween(titleDot, {
y: originalY
}, {
duration: 700,
easing: tween.sineInOut,
onFinish: function onFinish() {
// Loop the bounce as long as we're on the title screen and titleDot is valid
bounceTitleDot();
}
});
}
});
})();
// Remove "TAP TO START" text from inside the titleDot (do not add it to the dot)
// Center "Tap to Start" horizontally and place it above the high score (if present), otherwise below the title
tapToStartText = new Text2('TAP TO START', {
size: 120,
fill: '#FFFFFF',
// White text
stroke: '#000000',
// Black outline for contrast
strokeThickness: 8,
alpha: 0 // Start invisible for soft fade animation
// anchorX and anchorY will be set via .anchor.set() for precise centering
});
tapToStartText.anchor.set(0.5, 0.5);
tapToStartText.x = 2048 / 2;
var tapTextYPosition;
if (highScoreTextDisplay && highScoreTextDisplay.parent) {
// Place "Tap to Start" above the high score
tapTextYPosition = highScoreTextDisplay.y - highScoreTextDisplay.height / 2 - 100;
} else if (gameTitleAsset && gameTitleAsset.parent) {
tapTextYPosition = gameTitleAsset.y + gameTitleAsset.height / 2 + 150;
} else {
tapTextYPosition = 2732 / 2 + 100; // Fallback y position
}
tapToStartText.y = tapTextYPosition + 1000;
game.addChild(tapToStartText);
if (blinkIntervalId) LK.clearInterval(blinkIntervalId);
// Soft fade in/out animation using tween
var fadeIn = true;
function animateTapToStart() {
if (!(gameState === 'title' && tapToStartText && tapToStartText.parent)) {
if (blinkIntervalId) {
LK.clearInterval(blinkIntervalId);
blinkIntervalId = null;
}
return;
}
tween(tapToStartText, {
alpha: fadeIn ? 1 : 0.3
}, {
duration: 700,
onFinish: function onFinish() {
fadeIn = !fadeIn;
animateTapToStart();
}
});
}
animateTapToStart();
if (scoreTxt && scoreTxt.parent) {
scoreTxt.visible = false;
}
// Hide all game elements when title screen is up - ensure they're properly hidden
if (dot) {
dot.visible = false;
}
stars.forEach(function (s) {
if (s) s.visible = false;
});
obstacles.forEach(function (o) {
if (o) o.visible = false;
});
if (orbitLine) orbitLine.visible = false;
if (hand) hand.visible = false;
}
// Function to initialize and start the actual game
function startGamePlay() {
if (gameState !== 'title') return;
// gameState = 'playing'; // Moved into completeGameSetupAfterFade
if (blinkIntervalId) {
LK.clearInterval(blinkIntervalId);
blinkIntervalId = null;
}
var elementsToFadeOut = [];
if (gameTitleAsset && gameTitleAsset.parent) elementsToFadeOut.push(gameTitleAsset);
if (highScoreTextDisplay && highScoreTextDisplay.parent) elementsToFadeOut.push(highScoreTextDisplay);
if (tapToStartText && tapToStartText.parent) elementsToFadeOut.push(tapToStartText);
if (menuBackground && menuBackground.parent) elementsToFadeOut.push(menuBackground);
if (titleDot && titleDot.parent) elementsToFadeOut.push(titleDot);
var fadeDuration = 500; // Duration of the fade in milliseconds
var tweensCompletedCount = 0;
function completeGameSetupAfterFade() {
// Destroy elements now that they are faded and nullify global references
if (gameTitleAsset && gameTitleAsset.parent) gameTitleAsset.destroy();
gameTitleAsset = null;
if (highScoreTextDisplay && highScoreTextDisplay.parent) highScoreTextDisplay.destroy();
highScoreTextDisplay = null;
if (tapToStartText && tapToStartText.parent) tapToStartText.destroy();
tapToStartText = null;
if (menuBackground && menuBackground.parent) {
menuBackground.destroy();
menuBackground = null;
}
if (titleDot && titleDot.parent) {
titleDot.destroy();
}
titleDot = null; // Nullify titleDot as well for good practice
// This is the logic that was originally AFTER the destroyed elements
if (!scoreTxt || !scoreTxt.parent) {
if (scoreTxt) scoreTxt.destroy(); // Ensure old one is gone if somehow exists
scoreTxt = new Text2('0', {
size: 120,
// Adjusted size for top-right placement
fill: 0xFFFFFF
}); //{2R} // Line identifier preserved
scoreTxt.anchor.set(1, 0); // Anchor to top-right of the text
scoreTxt.x = -20; // Position 20px from the right edge of the gui.topRight container
scoreTxt.y = 20; // Position 20px from the top edge of the gui.topRight container
LK.gui.topRight.addChild(scoreTxt);
}
scoreTxt.setText('0');
scoreTxt.alpha = 0; // Prepare scoreTxt for fade-in
scoreTxt.visible = true;
// Add high score below the score
if (highScoreGameTxt && highScoreGameTxt.parent) {
highScoreGameTxt.destroy();
highScoreGameTxt = null;
}
var currentHighScore = storage.highScore || 0;
if (currentHighScore > 0) {
highScoreGameTxt = new Text2('BEST: ' + currentHighScore, {
size: 40,
// Reduced size for the 'BEST' text
// Adjusted size for better visibility below score
fill: '#FFFFFF'
}); //{3i} // Preserving line identifier for the Text2 options object
highScoreGameTxt.anchor.set(1, 0); // Anchor to its top-right
highScoreGameTxt.x = -20; // Align with scoreTxt's x-position (20px from right edge of gui.topRight)
// Position highScoreGameTxt directly below scoreTxt
if (scoreTxt && scoreTxt.parent) {
// Ensure scoreTxt is available
highScoreGameTxt.y = scoreTxt.y + scoreTxt.height + 10; // Position 10px below scoreTxt
} else {
// Fallback if scoreTxt isn't ready (scoreTxt.y is 20, assuming its height ~120)
highScoreGameTxt.y = 20 + 120 + 10;
}
LK.gui.topRight.addChild(highScoreGameTxt); // Add to top right GUI container
highScoreGameTxt.alpha = 0; // Prepare for fade-in, similar to scoreTxt
}
LK.setScore(0);
initializeGameElements();
gameState = 'playing'; // Set gameState to 'playing' only after all setup is complete
}
if (elementsToFadeOut.length === 0) {
completeGameSetupAfterFade(); // No elements to fade, proceed directly
} else {
elementsToFadeOut.forEach(function (element) {
tween(element, {
alpha: 0
}, {
duration: fadeDuration,
onFinish: function onFinish() {
tweensCompletedCount++;
if (tweensCompletedCount === elementsToFadeOut.length) {
completeGameSetupAfterFade();
}
}
});
});
}
}
// Function to initialize and fade in game elements
function initializeGameElements() {
var elementsToFadeIn = [];
var fadeDuration = 500; // Duration for game elements fade-in in milliseconds
// Collect scoreTxt if it's ready (alpha is preset by completeGameSetupAfterFade)
if (scoreTxt && scoreTxt.parent) {
elementsToFadeIn.push(scoreTxt);
}
// Collect highScoreGameTxt if it exists and is part of the GUI
if (highScoreGameTxt && highScoreGameTxt.parent) {
elementsToFadeIn.push(highScoreGameTxt);
}
// Collect dot
if (dot) {
elementsToFadeIn.push(dot);
}
// Collect stars
stars.forEach(function (s) {
if (s) {
elementsToFadeIn.push(s);
}
});
// Collect obstacles (this includes the initial orbitLine and CircularObstacles)
obstacles.forEach(function (o) {
if (o) {
elementsToFadeIn.push(o);
}
});
// Collect hand
if (hand) {
elementsToFadeIn.push(hand);
}
if (elementsToFadeIn.length === 0) {
return; // No elements to fade
}
// Set initial properties and start tween for each element
elementsToFadeIn.forEach(function (element) {
// For elements other than scoreTxt, set initial alpha.
// scoreTxt's alpha is set to 0 by the calling function completeGameSetupAfterFade.
if (element !== scoreTxt) {
element.alpha = 0;
}
element.visible = true; // Ensure element is visible for alpha tween to have an effect
tween(element, {
// Tween to fade in
alpha: 1
}, {
duration: fadeDuration
// No specific onFinish needed here as gameState is handled by the caller
});
});
}
// Initialize game elements before setting up the title screen.
// This ensures they exist for the hiding logic in setupTitleScreen and are layered correctly.
// Dot (needed for Hand positioning)
dot = game.addChild(new Dot());
dot.x = 2048 / 2;
dot.y = 2732 - dot.height / 2 - 200;
// Stars
// global 'stars' array is already initialized as [] in variable declarations
star = game.addChild(new Star());
star.x = 2048 / 2;
star.y = 2732 / 2 - 500;
stars.push(star);
// Hand
// Global 'hand' is assigned within createHand. createHand function definition is hoisted.
// Global 'handMoved' is initialized at declaration; ensure it's correctly 'false' for a new game start.
createHand();
handMoved = false; // Explicitly set for game start logic
// Obstacles and OrbitLine
// global 'obstacles' array is already initialized as [] in variable declarations
// orbitLine needs 'star' to be defined.
orbitLine = game.addChild(new OrbitLine(star.x, star.y, 300));
obstacles.push(orbitLine);
// Call to spawnInitialObstacles moved to earlier consolidated block
spawnInitialObstacles();
setupTitleScreen(); // Show title screen on game load
// Initialize the offscreen threshold for destroying obstacles
var offscreenThreshold = 2732 + 1000;
function createParticleEffect(x, y) {
var effect = new ParticleEffect(x, y);
LK.on('tick', function () {
effect._update_migrated();
});
}
function createDotParticleEffect(x, y) {
var effect = new DotParticleEffect(x, y);
LK.on('tick', function () {
effect._update_migrated();
});
}
function updateObstacles() {
var obstacleCount = 5 + LK.getScore();
while (obstacles.length < obstacleCount) {
var obstacle = game.addChild(new Obstacle());
obstacle.x = obstacle.direction === 1 ? -obstacle.width / 2 : 2048 + obstacle.width / 2;
obstacle.y = Math.random() * (2732 - obstacle.height) + obstacle.height / 2;
obstacles.push(obstacle);
}
}
// scoreTxt is now declared globally as 'var scoreTxt;'
// It is initialized and added to LK.gui.top within the 'startGamePlay' function.
// This removes the original duplicate initialization and addition.
game.on('down', function (x, y, obj) {
if (gameState === 'title') {
startGamePlay();
LK.getSound('bounce').play(); // Play a sound on game start
} else if (gameState === 'playing' && dot && !dot.destroyed) {
dot.bounceUp();
LK.getSound('bounce').play();
}
});
// Star creation and stars array population moved to earlier consolidated block
// OrbitLine creation and addition to obstacles moved to earlier consolidated block
function spawnInitialObstacles() {
var starX = 2048 / 2;
var starY = 2732 / 2 - 500;
var radius = 300;
var angleSpeed = 0.05;
for (var i = 0; i < 20; i++) {
var obstacle = game.addChild(new CircularObstacle(starX, starY, radius, angleSpeed));
obstacles.push(obstacle);
}
}
function triggerGameOver() {
if (gameState === 'gameOver') return; // Already processing game over
gameState = 'gameOver';
var currentScore = LK.getScore();
var highScore = storage.highScore || 0;
if (currentScore > highScore) {
storage.highScore = currentScore;
}
LK.showGameOver(); // This will reset the game instance
}
LK.on('tick', function () {
if (gameState !== 'playing') {
// Title screen animations (like blinking text) are handled by their own LK.setInterval.
// No main game logic runs if not in 'playing' state.
return;
}
// Ensure dot exists and is not destroyed before trying to update it.
// Game over logic should handle the transition away from 'playing' state.
if (!dot || dot.destroyed) {
return;
}
dot._update_migrated();
for (var i = 0; i < obstacles.length; i++) {
if (obstacles[i] instanceof CircularObstacle) {
obstacles[i].updatePosition();
} else {
if (typeof obstacles[i]._move_migrated === 'function') {
obstacles[i]._move_migrated();
}
}
if (dot.y < 2300 && dot.movedDistance < 0) {
obstacles[i].moveDown(-dot.movedDistance, dot.y);
}
if (dot.intersects(obstacles[i]) && !(obstacles[i] instanceof OrbitLine)) {
if (!dot.destroyed) {
createDotParticleEffect(dot.x, dot.y);
dot.destroyed = true;
LK.getSound('death').play(); // Play death sound
}
dot.destroy();
LK.setTimeout(function () {
triggerGameOver();
}, 1000);
} else if (obstacles[i].y > offscreenThreshold) {
obstacles[i].destroy();
obstacles.splice(i, 1);
}
}
for (var j = stars.length - 1; j >= 0; j--) {
stars[j].updateScale();
if (dot.intersects(stars[j])) {
LK.getSound('star').play(); // Play star sound
LK.setScore(LK.getScore() + 1);
scoreTxt.setText(LK.getScore().toString());
scoreTxt.alpha = 0;
var originalY = scoreTxt.y;
LK.on('tick', function updateScoreTextFadeIn() {
if (!scoreTxt || !scoreTxt.parent) {
// Defensive check for scoreTxt
LK.off('tick', updateScoreTextFadeIn);
return;
}
scoreTxt.alpha += 0.05;
if (scoreTxt.alpha >= 1) {
scoreTxt.alpha = 1; // Cap alpha at 1
scoreTxt.y = originalY; // Explicitly reset Y to its original position for this animation cycle
LK.off('tick', updateScoreTextFadeIn); // Remove this animation listener
} else {
// Update Y position only while the animation is in progress
scoreTxt.y = originalY - (1 - scoreTxt.alpha) * 50;
}
});
var oldStarY = stars[j].y;
createParticleEffect(stars[j].x, stars[j].y);
var scorePopup = new ScorePopup(stars[j].x, stars[j].y);
LK.on('tick', function () {
scorePopup._update_migrated();
});
stars[j].destroy();
stars.splice(j, 1);
var newStar = game.addChild(new Star());
newStar.x = 2048 / 2;
var additionalYOffset = LK.getScore() > 1 ? (LK.getScore() - 1) * 200 : 0;
newStar.y = oldStarY - 2000 - additionalYOffset;
stars.push(newStar);
if (!handMoved && hand) {
var handMoveDistance = 0;
var handMoveInterval = LK.setInterval(function () {
handMoveDistance += 1;
hand._update_migrated(handMoveDistance);
if (handMoveDistance >= 1000) {
LK.clearInterval(handMoveInterval);
}
}, 10);
handMoved = true;
}
// Increase the offscreen threshold for destroying obstacles
offscreenThreshold += 300;
// Add a cumulative number of CircularObstacles at random positions around the new star
var cumulativeObstacles = 1 + LK.getScore();
for (var k = 0; k < cumulativeObstacles; k++) {
var randomAngle = Math.random() * Math.PI * 2; // Random angle in radians
var radiusOffset = k * 100;
var circularObstacle = game.addChild(new CircularObstacle(newStar.x, newStar.y, 300 + radiusOffset, 0.05 * (k % 2 === 0 ? 1 : -1), randomAngle));
obstacles.push(circularObstacle);
}
// Add orbit line after creating all obstacles
var orbitLine = game.addChild(new OrbitLine(newStar.x, newStar.y, 300 + radiusOffset));
obstacles.push(orbitLine);
} else if (dot.y < 2300 && dot.movedDistance < 0) {
stars[j].moveDown(-dot.movedDistance, dot.y);
}
}
});
// Dot creation moved to earlier consolidated block
// Redundant 'hand' and 'handMoved' declarations removed.
// createHand() call and handMoved reset moved to earlier consolidated block.
// createHand function definition remains and is hoisted.
function createHand() {
hand = game.addChild(new Hand());
hand.x = dot.x;
hand.y = dot.y + dot.height / 2;
}