User prompt
Please fix the bug: 'Timeout.tick error: createScoreBackground is not defined' in or related to this line: 'createScoreBackground();' Line Number: 533
User prompt
Please fix the bug: 'Timeout.tick error: createScoreBackground is not defined' in or related to this line: 'createScoreBackground();' Line Number: 532
User prompt
Please fix the bug: 'Timeout.tick error: createScoreBackground is not defined' in or related to this line: 'createScoreBackground();' Line Number: 532
User prompt
Please fix the bug: 'Timeout.tick error: createScoreBackground is not defined' in or related to this line: 'createScoreBackground();' Line Number: 531
User prompt
show scores texts in background2 if win or lose.
User prompt
Add text of score by name of the new player, as an option : the highest score on the top and other scores below it.
User prompt
Please fix the bug: 'Timeout.tick error: saveHighscore is not defined' in or related to this line: 'saveHighscore(LK.getScore());' Line Number: 478
User prompt
Please fix the bug: 'Uncaught TypeError: storage.isAvailable is not a function' in or related to this line: 'if (!storage.isAvailable()) {' Line Number: 628 ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
Please fix the bug: 'Uncaught TypeError: storage.isAvailable is not a function' in or related to this line: 'if (!storage.isAvailable()) {' Line Number: 628
User prompt
save the new scores of the new players when they logged in to upit accounts.
User prompt
Please fix the bug: 'Timeout.tick error: saveHighscore is not defined' in or related to this line: 'saveHighscore(LK.getScore());' Line Number: 478
User prompt
Please fix the bug: 'Timeout.tick error: saveHighscore is not defined' in or related to this line: 'saveHighscore(LK.getScore());' Line Number: 478
User prompt
Please fix the bug: 'Timeout.tick error: saveHighscore is not defined' in or related to this line: 'saveHighscore(LK.getScore());' Line Number: 478
User prompt
Remove the scores of default players. ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
add intromusic1
User prompt
Please fix the bug: 'Timeout.tick error: saveHighscore is not defined' in or related to this line: 'saveHighscore(LK.getScore());' Line Number: 477
Code edit (1 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Timeout.tick error: saveHighscore is not defined' in or related to this line: 'saveHighscore(LK.getScore());' Line Number: 477
User prompt
fit background0 to the screen
User prompt
Please fix the bug: 'Timeout.tick error: saveHighscore is not defined' in or related to this line: 'saveHighscore(LK.getScore());' Line Number: 478
User prompt
Please fix the bug: 'Timeout.tick error: saveHighscore is not defined' in or related to this line: 'saveHighscore(LK.getScore());' Line Number: 478 ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
Please fix the bug: 'Timeout.tick error: saveHighscore is not defined' in or related to this line: 'saveHighscore(LK.getScore());' Line Number: 478
User prompt
Please fix the bug: 'Timeout.tick error: saveHighscore is not defined' in or related to this line: 'saveHighscore(LK.getScore());' Line Number: 477
User prompt
Please fix the bug: 'Timeout.tick error: saveHighscore is not defined' in or related to this line: 'saveHighscore(LK.getScore());' Line Number: 477
User prompt
Add the new score to the list always don't do default players names do real logged in players to upit.
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Air Enemy Class (Snake Segment - Simplified for MVP)
var AirEnemy = Container.expand(function (startX, startY) {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('airEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.x = startX;
self.y = startY;
self.speedX = 4 + Math.random() * 2; // Horizontal speed
self.amplitudeY = 100 + Math.random() * 100; // Vertical movement range
self.frequencyY = 0.01 + Math.random() * 0.01; // Vertical movement speed
self.fireRate = 150; // Ticks between shots
self.fireCooldown = Math.random() * self.fireRate;
self.update = function () {
// Basic horizontal and sinusoidal vertical movement
self.x -= self.speedX;
self.y = startY + Math.sin(LK.ticks * self.frequencyY + startX) * self.amplitudeY; // Use startX for phase offset
// Firing logic
self.fireCooldown++;
if (self.fireCooldown >= self.fireRate) {
self.fireCooldown = 0;
// Fire a single bullet straight left
var bulletSpeed = 8;
fireEnemyBullet(self.x, self.y, -bulletSpeed, 0); // vx = -speed, vy = 0
}
// Boundary check (simple respawn logic)
if (self.x < -enemyGraphics.width) {
self.x = 2048 + enemyGraphics.width; // Respawn on the right
startY = Math.random() * (2732 - 400) + 200; // Random Y position
}
};
return self;
});
// Boss Class (Basic structure for future)
var Boss = Container.expand(function () {
var self = Container.call(this);
var bossGraphics = self.attachAsset('boss', {
anchorX: 0.5,
anchorY: 0.5
});
self.x = 2048 - 300; // Position on the right
self.y = 2732 / 2; // Center vertically
self.fireRate = 180; // Ticks between laser bursts
self.fireCooldown = 0;
self.health = 100; // Example health
self.update = function () {
// Add movement logic if needed
self.fireCooldown++;
if (self.fireCooldown >= self.fireRate) {
self.fireCooldown = 0;
fireBossLasers(self.x, self.y);
}
};
return self;
});
// Boss Laser Class (Basic structure for future)
var BossLaser = Container.expand(function (startX, startY, vx, vy) {
var self = Container.call(this);
var laserGraphics = self.attachAsset('bossLaser', {
anchorX: 0.5,
anchorY: 0.5 // Anchor center
});
self.x = startX;
self.y = startY;
self.vx = vx;
self.vy = vy;
// Set rotation based on velocity direction
self.rotation = Math.atan2(vy, vx) + Math.PI / 2; // Point laser in direction of travel (+90deg adjustment needed depending on asset orientation)
self.update = function () {
self.x += self.vx;
self.y += self.vy;
};
return self;
});
// Bottom Mountain Obstacle Class
var BottomMountain = Container.expand(function () {
var self = Container.call(this);
var mountainGraphics = self.attachAsset('bottommountain', {
anchorX: 0.5,
anchorY: 0.75 // Anchor base at terrain edge
});
self.speed = 5; // Should match terrain speed
self.update = function () {
self.x -= self.speed;
};
return self;
});
// Enemy Bullet Class
var EnemyBullet = Container.expand(function (startX, startY, vx, vy) {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('enemyBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.x = startX;
self.y = startY;
self.vx = vx; // Horizontal velocity
self.vy = vy; // Vertical velocity
self.update = function () {
self.x += self.vx;
self.y += self.vy;
};
return self;
});
// Ground Enemy Class
var GroundEnemy = Container.expand(function (isTop) {
var self = Container.call(this);
var enemyGraphics = self.attachAsset(isTop ? 'groundEnemytop' : 'groundEnemybottom', {
anchorX: 0.5,
anchorY: isTop ? 0 : 1 // Anchor base at terrain edge
});
self.isTop = isTop;
self.speed = 5; // Should match terrain speed
self.fireRate = 120; // Ticks between firing sequences (2 seconds)
self.fireCooldown = Math.random() * self.fireRate; // Random initial delay
self.shotsInBurst = 3;
self.burstDelay = 10; // Ticks between shots in a burst
self.update = function () {
self.x -= self.speed;
self.fireCooldown++;
if (self.fireCooldown >= self.fireRate) {
self.fireCooldown = 0; // Reset cooldown
// Fire burst
for (var i = 0; i < self.shotsInBurst; i++) {
LK.setTimeout(function () {
// Check if enemy still exists before firing
if (!self.destroyed) {
var bulletSpeed = 8;
// Calculate direction logic is now in fireEnemyBullet, but still need to pass initial values
var vx = -bulletSpeed * 0.707; // Default direction if player not available
var vy = self.isTop ? bulletSpeed * 0.707 : -bulletSpeed * 0.707; // Default direction
fireEnemyBullet(self.x, self.y, vx, vy);
}
}, i * self.burstDelay);
}
}
};
return self;
});
// Player Bullet Class
var PlayerBullet = Container.expand(function (startX, startY) {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('playerBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.x = startX;
self.y = startY;
self.speed = 20; // Moves to the right
self.update = function () {
self.x += self.speed;
};
return self;
});
// Terrain Segment Class
var TerrainSegment = Container.expand(function (isTop) {
var self = Container.call(this);
var terrainGraphics = self.attachAsset('terrain', {
anchorX: 0,
anchorY: isTop ? 0 : 1 // Anchor at top for top terrain, bottom for bottom terrain
});
self.isTop = isTop;
self.speed = 5; // Horizontal scroll speed
self.update = function () {
self.x -= self.speed;
};
return self;
});
// Top Mountain Obstacle Class
var TopMountain = Container.expand(function () {
var self = Container.call(this);
var mountainGraphics = self.attachAsset('topmountain', {
anchorX: 0.5,
anchorY: 0.3 // Anchor base at terrain edge
});
self.speed = 5; // Should match terrain speed
self.update = function () {
self.x -= self.speed;
};
return self;
});
// Player UFO Class
var UFO = Container.expand(function () {
var self = Container.call(this);
var ufoGraphics = self.attachAsset('ufo', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 10; // Movement speed multiplier, adjust as needed
self.isDead = false;
self.invincibleUntil = 0;
// Keep UFO within game boundaries
self.clampPosition = function () {
var halfWidth = ufoGraphics.width / 2;
var halfHeight = ufoGraphics.height / 2;
if (self.x < halfWidth) {
self.x = halfWidth;
}
if (self.x > 2048 - halfWidth) {
self.x = 2048 - halfWidth;
}
if (self.y < halfHeight + 100) {
self.y = halfHeight + 100;
} // Avoid top-left menu area
if (self.y > 2732 - halfHeight) {
self.y = 2732 - halfHeight;
}
// Adjust based on terrain phase
if (gamePhase === 0) {
// Find the current terrain height at the UFO's x position (simplified)
var terrainHeight = 200; // Assuming constant terrain height for now
if (self.y < terrainHeight + halfHeight) {
self.y = terrainHeight + halfHeight;
}
if (self.y > 2732 - terrainHeight - halfHeight) {
self.y = 2732 - terrainHeight - halfHeight;
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x101030 // Dark space blue background
});
/****
* Game Code
****/
// LK.init.image('mountain', {width:150, height:250, id:'6819884bc4a0c8bae9e84ae0'}) // Removed - Replaced by top/bottom mountains
// Minimalistic tween library which should be used for animations over time
// Boss Laser Beam
// Boss
// Air Enemy (Placeholder shape)
// Enemy Bullet
// Ground Enemy
// Mountain Obstacle (simplified)
// Ground/Ceiling
// Player UFO
// Initialize assets used in this game. Scale them according to what is needed for the game.
/****
* Assets
* Assets are automatically created and loaded either dynamically during gameplay
* or via static code analysis based on their usage in the code.
****/
// Game State
var isGameStarted = false; // Flag to track if game has started
var showingHighscores = false; // Flag to track if highscore screen is shown
var gamePhase = 0; // 0: Terrain, 1: Space, 2: Boss
var phaseStartTime = LK.ticks;
var phaseDuration = 3600; // 1 minute (60 seconds * 60 fps = 3600 ticks)
var terrainSpeed = 5;
var scoreIncrementTimer = 0;
var highscoreBackground = null;
var highscoreTexts = [];
// Game Objects
var player = null;
var terrainSegmentsTop = [];
var terrainSegmentsBottom = [];
var mountains = [];
var groundEnemies = [];
var enemyBullets = [];
var airEnemies = []; // Simple air enemies for MVP phase 2
var boss = null;
var bossLasers = [];
var playerBullets = []; // Array for player bullets
// Player Control
var dragNode = null;
var playerFireRate = 15; // Ticks between shots (4 shots per second)
var playerFireCooldown = 0;
// Player Lives
var MAX_PLAYER_LIVES = 5;
var playerLives = MAX_PLAYER_LIVES;
var livesTxt;
// Score Display
var scoreTxt = new Text2('0', {
size: 100,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt); // Position score at top center
// Helper function to create terrain segments
function createTerrainSegment(isTop, xPos) {
var segment = new TerrainSegment(isTop);
segment.x = xPos;
segment.y = isTop ? 0 : 2732;
segment.speed = terrainSpeed;
game.addChild(segment);
if (isTop) {
terrainSegmentsTop.push(segment);
} else {
terrainSegmentsBottom.push(segment);
}
return segment;
}
// Helper function to spawn mountains on terrain
function spawnMountain(terrainSegment) {
var mountain;
if (terrainSegment.isTop) {
mountain = new TopMountain();
} else {
mountain = new BottomMountain();
}
// Position relative to the terrain segment
// Ensure we use the actual height of the terrain graphic for positioning
var terrainGraphic = terrainSegment.children[0]; // Assuming the graphic is the first child
mountain.x = terrainSegment.x + Math.random() * terrainGraphic.width;
// Position based on whether it's a top or bottom mountain
mountain.y = terrainSegment.isTop ? terrainGraphic.height : 2732 - terrainGraphic.height;
mountain.speed = terrainSpeed;
game.addChild(mountain);
mountains.push(mountain);
}
// Helper function to spawn ground enemies on terrain
function spawnGroundEnemy(terrainSegment) {
var enemy = new GroundEnemy(terrainSegment.isTop);
// Position relative to the terrain segment
// Ensure we use the actual height of the terrain graphic for positioning
var terrainGraphic = terrainSegment.children[0]; // Assuming the graphic is the first child
enemy.x = terrainSegment.x + Math.random() * terrainGraphic.width;
enemy.y = terrainSegment.isTop ? terrainGraphic.height : 2732 - terrainGraphic.height;
enemy.speed = terrainSpeed;
game.addChild(enemy);
groundEnemies.push(enemy);
}
// Helper function to fire enemy bullets
function fireEnemyBullet(x, y, vx, vy) {
// If player exists and is not dead, target the player instead of using fixed direction
if (player && !player.isDead) {
// Calculate direction vector to player
var dx = player.x - x;
var dy = player.y - y;
// Normalize the vector
var distance = Math.sqrt(dx * dx + dy * dy);
// Use the bullet speed provided (or calculate from vx and vy)
var bulletSpeed = Math.sqrt(vx * vx + vy * vy);
// Set velocity components to target player
vx = dx / distance * bulletSpeed;
vy = dy / distance * bulletSpeed;
}
var bullet = new EnemyBullet(x, y, vx, vy);
game.addChild(bullet);
enemyBullets.push(bullet);
LK.getSound('enemyShoot').play();
}
// Helper function to spawn air enemies (simple version)
function spawnAirEnemy() {
var startY = Math.random() * (2732 - 400) + 200; // Avoid edges
var enemy = new AirEnemy(2048 + 100, startY); // Start off-screen right
game.addChild(enemy);
airEnemies.push(enemy);
}
// Helper function to fire boss lasers
function fireBossLasers(x, y) {
var directions = 5;
var laserSpeed = 12;
var verticalSpread = 4; // Max vertical speed component
for (var i = 0; i < directions; i++) {
// Calculate vertical velocity component for spread
// Example: i=0 -> -4, i=1 -> -2, i=2 -> 0, i=3 -> 2, i=4 -> 4
var vy = -verticalSpread + verticalSpread * 2 / (directions - 1) * i;
// Keep horizontal speed constant (moving left)
var vx = -laserSpeed;
var laser = new BossLaser(x, y, vx, vy);
game.addChild(laser);
bossLasers.push(laser);
}
// Add sound effect for laser fire (consider adding one e.g., LK.getSound('bossShoot').play(); if asset exists)
}
// Initialize Game Elements
function initGame() {
// Reset state
LK.setScore(0);
scoreTxt.setText('0');
// Hide score during intro
scoreTxt.visible = !isGameStarted;
// Initialize Lives
playerLives = MAX_PLAYER_LIVES;
if (!livesTxt) {
// Create only if it doesn't exist (for game restarts)
livesTxt = new Text2("Lives: x" + playerLives, {
size: 80,
// Slightly smaller than score
fill: 0xFFFFFF
});
livesTxt.anchor.set(1, 0); // Anchor top-right
LK.gui.topRight.addChild(livesTxt);
} else {
livesTxt.setText("Lives: x" + playerLives);
}
gamePhase = 0;
phaseStartTime = LK.ticks;
dragNode = null;
playerBullets = []; // Clear player bullets
playerFireCooldown = 0; // Reset fire cooldown
// Clear existing elements from previous game (if any)
// Note: LK engine handles full reset on GameOver/YouWin, but manual cleanup might be needed if restarting mid-game (not typical)
// Let's assume full reset is handled by LK.
// Create Player
player = new UFO();
player.x = 300;
player.y = 2732 / 2;
player.isDead = false;
player.invincibleUntil = 0;
player.alpha = 1; // Ensure player is visible
game.addChild(player);
// Create initial terrain
var terrainWidth = LK.getAsset('terrain', {}).width; // Get width from asset
for (var i = 0; i < Math.ceil(2048 / terrainWidth) + 1; i++) {
createTerrainSegment(true, i * terrainWidth);
createTerrainSegment(false, i * terrainWidth);
}
// Start Phase 1 Music
LK.playMusic('phase1Music');
}
// Helper function already moved above
// Helper function to save highscore
function saveHighscore(score) {
// Get existing highscores or create empty array
var highscores = [];
try {
// Try to parse the serialized highscores data
if (storage && storage.available) {
highscores = JSON.parse(storage.highscoresData);
}
} catch (e) {
console.log("Error parsing existing highscores:", e);
highscores = [];
}
// Get player name from Upit if available
var playerName = "FRVR Player"; // Default fallback name
// Check if we have user info available through Upit
if (LK.profile && LK.profile.name) {
playerName = LK.profile.name; // Use real player name
} else if (LK.profile && LK.profile.id) {
playerName = "Player " + LK.profile.id.substring(0, 5); // Use ID fragment if no name
}
// Add new score entry (always add it, we'll sort and filter afterward)
highscores.push({
name: playerName,
score: score
});
// Sort highscores by score (descending)
highscores.sort(function (a, b) {
return b.score - a.score;
});
// Keep only top 10 scores
if (highscores.length > 10) {
highscores = highscores.slice(0, 10);
}
// Save back to storage - serialize scores to avoid complex objects
// The storage only supports literal values and simple structures
// Convert highscores array to string to ensure compatibility
try {
// Convert the complex object to a JSON string
var highscoresString = JSON.stringify(highscores);
// Store the string representation
storage.highscoresData = highscoresString;
// Save to Upit account if player is logged in
if (storage && storage.available) {
storage.save('highscores', highscoresString);
}
// When retrieving it, we'll need to parse it back - modify the retrieval in showHighscore function
} catch (e) {
console.log("Error saving highscores:", e);
}
}
// Helper function definition moved before handlePlayerDeath
// Helper function to handle player death
function handlePlayerDeath() {
if (!player || player.isDead) {
return;
}
// Event Handlers
player.isDead = true;
player.alpha = 0; // Make player invisible during death processing
LK.getSound('playerExplosion').play();
// Optional: Flash the player before making them fully invisible, or flash screen
// LK.effects.flashObject(player, 0xFF0000, 500);
LK.effects.flashScreen(0xFF0000, 200); // Short screen flash
playerLives--;
if (livesTxt) {
livesTxt.setText("Lives: x" + playerLives);
}
LK.setTimeout(function () {
if (playerLives > 0) {
respawnPlayer();
} else {
// Save highscore before showing Game Over
saveHighscore(LK.getScore());
// Create background for scores before showing game over
createScoreBackground();
LK.showGameOver();
}
}, 1000); // 1 second delay for effects and sound
}
// Helper function to respawn player at checkpoint
function respawnPlayer() {
if (!player) {
return;
}
player.x = 300;
player.y = 2732 / 2;
player.clampPosition();
player.alpha = 1; // Make player visible again
player.isDead = false;
player.invincibleUntil = LK.ticks + 120; // 2 seconds of invincibility
// Clear active threats
for (var i = enemyBullets.length - 1; i >= 0; i--) {
if (enemyBullets[i] && !enemyBullets[i].destroyed) {
enemyBullets[i].destroy();
}
}
enemyBullets = [];
if (gamePhase === 2) {
// Boss phase
for (var i = bossLasers.length - 1; i >= 0; i--) {
if (bossLasers[i] && !bossLasers[i].destroyed) {
bossLasers[i].destroy();
}
}
bossLasers = [];
}
// Player is reset, other game elements (enemies, boss health) remain.
}
// Create intro screen elements
var backgroundAsset = LK.getAsset('Background0', {});
var scaleX = 2048 / backgroundAsset.width;
var scaleY = 2732 / backgroundAsset.height;
var background = game.addChild(LK.getAsset('Background0', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: scaleX,
scaleY: scaleY
}));
// Play intro music
LK.playMusic('Intromusic1');
var startButton = game.addChild(LK.getAsset('Startgamebutton', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2 + 700,
scaleX: 1.5,
scaleY: 1.5
}));
var highscoreButton = game.addChild(LK.getAsset('Highscorebutton', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2 + 1050,
scaleX: 1.5,
scaleY: 1.5
}));
function startGame() {
// Remove intro elements
background.destroy();
startButton.destroy();
highscoreButton.destroy();
// Set game as started
isGameStarted = true;
// Show score
scoreTxt.visible = true;
// Initialize the game
initGame();
}
function showHighscore() {
// If already showing highscores, hide them and return to intro
if (showingHighscores) {
// Remove highscore display
if (highscoreBackground) {
highscoreBackground.destroy();
highscoreBackground = null;
}
// Remove all highscore texts
highscoreTexts.forEach(function (text) {
text.destroy();
});
highscoreTexts = [];
showingHighscores = false;
return;
}
// Show custom highscore screen
showingHighscores = true;
// Create background
highscoreBackground = game.addChild(LK.getAsset('Background2', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: 20.48,
scaleY: 27.32
}));
// Create highscore title
var titleText = new Text2("HIGHSCORES", {
size: 120,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0);
titleText.x = 2048 / 2;
titleText.y = 200;
game.addChild(titleText);
highscoreTexts.push(titleText);
// Get highscores from storage or use default ones if none exist
var highscores = [];
try {
// Try to load from Upit account storage if available
if (storage && storage.available) {
storage.load('highscores', function (savedScores) {
if (savedScores) {
try {
highscores = JSON.parse(savedScores);
storage.highscoresData = savedScores; // Update local reference
displayHighscores(highscores);
return;
} catch (parseErr) {
console.log("Error parsing highscores from Upit:", parseErr);
}
}
// If no Upit scores or parse error, fall back to local scores
if (storage.highscoresData) {
highscores = JSON.parse(storage.highscoresData);
}
displayHighscores(highscores);
});
return; // Exit early since we're handling async
} else {
// Try to parse the serialized highscores data
if (storage.highscoresData) {
highscores = JSON.parse(storage.highscoresData);
}
}
} catch (e) {
console.log("Error parsing highscores:", e);
}
// No default scores, use empty array if no valid highscores found
if (!highscores || !highscores.length) {
highscores = [];
}
// Display the highscores immediately if we didn't use Upit cloud storage
if (!storage || !storage.available) {
displayHighscores(highscores);
}
// Sort highscores by score (descending)
highscores.sort(function (a, b) {
return b.score - a.score;
});
// Helper function to display highscores
function displayHighscores(highscores) {
// Sort highscores by score (descending)
highscores.sort(function (a, b) {
return b.score - a.score;
});
// Display top 5 highscores
var maxToShow = Math.min(highscores.length, 5);
// Display title for scores
var titleScoreText = new Text2("Player Scores", {
size: 90,
fill: 0xFFD700
});
titleScoreText.anchor.set(0.5, 0);
titleScoreText.x = 2048 / 2;
titleScoreText.y = 350;
game.addChild(titleScoreText);
highscoreTexts.push(titleScoreText);
// Display player scores
for (var i = 0; i < maxToShow; i++) {
var scoreEntry = new Text2(i + 1 + ". " + highscores[i].name + ": " + highscores[i].score, {
size: 80,
fill: i === 0 ? "#FFD700" : "#FFFFFF" // Gold for highest score
});
scoreEntry.anchor.set(0.5, 0);
scoreEntry.x = 2048 / 2;
scoreEntry.y = 450 + i * 150;
game.addChild(scoreEntry);
highscoreTexts.push(scoreEntry);
}
}
// Helper function to create score background when game is won or lost
function createScoreBackground() {
// Get highscores from storage
var highscores = [];
try {
if (storage && storage.available && storage.highscoresData) {
highscores = JSON.parse(storage.highscoresData);
}
} catch (e) {
console.log("Error parsing highscores:", e);
highscores = [];
}
// Sort highscores by score (descending)
highscores.sort(function (a, b) {
return b.score - a.score;
});
// Create background
var scoreBackground = game.addChild(LK.getAsset('Background2', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: 20.48,
scaleY: 27.32,
alpha: 0.9
}));
// Create title
var titleText = new Text2("HIGHSCORES", {
size: 120,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0);
titleText.x = 2048 / 2;
titleText.y = 200;
game.addChild(titleText);
// Display top 5 highscores
var maxToShow = Math.min(highscores.length, 5);
if (maxToShow > 0) {
// Display title for scores
var titleScoreText = new Text2("Top Players", {
size: 90,
fill: 0xFFD700
});
titleScoreText.anchor.set(0.5, 0);
titleScoreText.x = 2048 / 2;
titleScoreText.y = 350;
game.addChild(titleScoreText);
// Display player scores
for (var i = 0; i < maxToShow; i++) {
var scoreEntry = new Text2(i + 1 + ". " + highscores[i].name + ": " + highscores[i].score, {
size: 80,
fill: i === 0 ? "#FFD700" : "#FFFFFF" // Gold for highest score
});
scoreEntry.anchor.set(0.5, 0);
scoreEntry.x = 2048 / 2;
scoreEntry.y = 450 + i * 150;
game.addChild(scoreEntry);
}
} else {
// No scores message
var noScoresText = new Text2("No scores yet!", {
size: 80,
fill: 0xFFFFFF
});
noScoresText.anchor.set(0.5, 0);
noScoresText.x = 2048 / 2;
noScoresText.y = 450;
game.addChild(noScoresText);
}
// Current score
var currentScoreText = new Text2("Your Score: " + LK.getScore(), {
size: 100,
fill: 0x00FF00
});
currentScoreText.anchor.set(0.5, 0);
currentScoreText.x = 2048 / 2;
currentScoreText.y = 1500;
game.addChild(currentScoreText);
}
// Add back button text
var backText = new Text2("TAP ANYWHERE TO RETURN", {
size: 70,
fill: 0xFFFFFF
});
backText.anchor.set(0.5, 0);
backText.x = 2048 / 2;
backText.y = 2200;
game.addChild(backText);
highscoreTexts.push(backText);
// Check if there's a LK API for showing leaderboard as backup
if (typeof LK.showLeaderboard === 'function') {
// Add fallback to standard leaderboard
LK.setTimeout(function () {
if (showingHighscores) {
// If our custom screen hasn't been dismissed after 5 seconds, show official leaderboard
showingHighscores = false;
if (highscoreBackground) {
highscoreBackground.destroy();
highscoreBackground = null;
}
highscoreTexts.forEach(function (text) {
text.destroy();
});
highscoreTexts = [];
LK.showLeaderboard();
}
}, 5000);
}
}
// Add interactive behavior to buttons
startButton.interactive = true;
startButton.buttonMode = true;
startButton.down = function () {
startGame();
};
highscoreButton.interactive = true;
highscoreButton.buttonMode = true;
highscoreButton.down = function () {
showHighscore();
};
game.down = function (x, y, obj) {
// Handle tapping on highscore screen to return to intro
if (showingHighscores) {
showHighscore(); // This will toggle off the highscore display
return;
}
// Only process player dragging if game has started
if (isGameStarted && player) {
// Check if touch is on the player UFO
var localPos = player.toLocal(game.toGlobal({
x: x,
y: y
})); // Convert game coords to player's local coords
// Use a slightly larger hit area for easier dragging
var hitWidth = player.width * 1.5;
var hitHeight = player.height * 1.5;
if (Math.abs(localPos.x) < hitWidth / 2 && Math.abs(localPos.y) < hitHeight / 2) {
dragNode = player;
// Instantly move player to touch position for responsive feel
var gamePos = game.toLocal(obj.global); // Use obj.global for precise position
player.x = gamePos.x;
player.y = gamePos.y;
player.clampPosition();
} else {
dragNode = null;
}
}
};
game.move = function (x, y, obj) {
if (dragNode) {
var gamePos = game.toLocal(obj.global); // Convert event global position to game coordinates
dragNode.x = gamePos.x;
dragNode.y = gamePos.y;
// Clamp player position within bounds immediately after move
if (dragNode === player) {
player.clampPosition();
}
}
};
game.up = function (x, y, obj) {
dragNode = null;
};
// Game Update Logic
game.update = function () {
// If game hasn't started yet or player is gone, don't process game logic
if (!isGameStarted || !player || player.destroyed) {
// Make sure score is hidden during intro
scoreTxt.visible = false;
return; // Game not started or player fully gone, nothing to do.
}
// Make sure score is visible during gameplay
scoreTxt.visible = true;
// If player is dead, stop their specific logic & wait for respawn/game over timeout
// Other game elements (enemies, bullets) might still update.
// Player input and collisions will be gated by player.isDead or invincibility.
// Player invincibility blinking
if (player && !player.isDead && LK.ticks < player.invincibleUntil) {
player.alpha = LK.ticks % 20 < 10 ? 0.5 : 1; // Blink
} else if (player && !player.isDead && player.alpha !== 1) {
player.alpha = 1; // Ensure alpha is reset
}
// --- Global Updates ---
// Player Shooting
playerFireCooldown++;
if (!player.isDead && dragNode === player && playerFireCooldown >= playerFireRate) {
// Only shoot while dragging/controlling and alive
playerFireCooldown = 0;
var bullet = new PlayerBullet(player.x + player.width / 2, player.y);
game.addChild(bullet);
playerBullets.push(bullet);
LK.getSound('playerShoot').play();
}
// Score increases over time
scoreIncrementTimer++;
if (scoreIncrementTimer >= 60) {
// Add 10 points every second
scoreIncrementTimer = 0;
LK.setScore(LK.getScore() + 10);
scoreTxt.setText(LK.getScore());
}
// --- Phase Management ---
var elapsedTicks = LK.ticks - phaseStartTime;
if (gamePhase === 0 && elapsedTicks >= phaseDuration) {
// Transition to Phase 1 (Space)
gamePhase = 1;
phaseStartTime = LK.ticks; // Reset timer for next phase if needed
console.log("Transitioning to Phase 1: Space");
// Clean up terrain-specific elements
terrainSegmentsTop.forEach(function (s) {
return s.destroy();
});
terrainSegmentsBottom.forEach(function (s) {
return s.destroy();
});
mountains.forEach(function (m) {
return m.destroy();
});
groundEnemies.forEach(function (ge) {
return ge.destroy();
});
terrainSegmentsTop = [];
terrainSegmentsBottom = [];
mountains = [];
groundEnemies = [];
// Spawn initial air enemies
for (var i = 0; i < 5; i++) {
// Start with 5 air enemies
spawnAirEnemy();
}
LK.playMusic('phase2Music'); // Switch music
} else if (gamePhase === 1 && elapsedTicks >= phaseDuration * 1.5) {
// Example: Boss after 1.5x phase duration
// Transition to Phase 2 (Boss)
if (!boss) {
// Ensure boss only spawns once
gamePhase = 2;
phaseStartTime = LK.ticks;
console.log("Transitioning to Phase 2: Boss");
// Clean up air enemies
airEnemies.forEach(function (ae) {
return ae.destroy();
});
airEnemies = [];
// Spawn Boss
boss = new Boss();
game.addChild(boss);
LK.playMusic('bossMusic'); // Boss music
}
}
// --- Phase 1: Terrain Logic ---
if (gamePhase === 0) {
// Update and manage terrain segments
var terrainWidth = LK.getAsset('terrain', {}).width;
for (var i = terrainSegmentsTop.length - 1; i >= 0; i--) {
var segment = terrainSegmentsTop[i];
if (segment.x < -terrainWidth) {
// Reposition segment to the right
var maxX = 0;
terrainSegmentsTop.forEach(function (s) {
if (s.x > maxX) {
maxX = s.x;
}
});
segment.x = maxX + terrainWidth;
// Always spawn an enemy on the reused segment
spawnGroundEnemy(segment);
// Additionally, 50% chance for a mountain
if (Math.random() < 0.5) {
spawnMountain(segment);
}
}
}
// Repeat for bottom terrain
for (var i = terrainSegmentsBottom.length - 1; i >= 0; i--) {
var segment = terrainSegmentsBottom[i];
if (segment.x < -terrainWidth) {
var maxX = 0;
terrainSegmentsBottom.forEach(function (s) {
if (s.x > maxX) {
maxX = s.x;
}
});
segment.x = maxX + terrainWidth;
// Always spawn an enemy on the reused segment
spawnGroundEnemy(segment);
// Additionally, 50% chance for a mountain
if (Math.random() < 0.5) {
spawnMountain(segment);
}
}
}
// Update mountains & check collision
for (var i = mountains.length - 1; i >= 0; i--) {
var mountain = mountains[i];
if (mountain.x < -mountain.width) {
mountain.destroy();
mountains.splice(i, 1);
} else {
if (!player.isDead && LK.ticks > player.invincibleUntil && player.intersects(mountain)) {
handlePlayerDeath();
return; // Stop update processing
}
}
}
// Update ground enemies & check collision
for (var i = groundEnemies.length - 1; i >= 0; i--) {
var enemy = groundEnemies[i];
if (enemy.x < -enemy.width) {
enemy.destroy();
groundEnemies.splice(i, 1);
} else {
// Collision check: Player vs Ground Enemy
if (!player.isDead && LK.ticks > player.invincibleUntil && player.intersects(enemy)) {
LK.getSound('enemyExplosion').play(); //{3L} // Enemy explodes
enemy.destroy(); // Destroy enemy on collision too
groundEnemies.splice(i, 1);
handlePlayerDeath();
return;
}
}
}
// Re-clamp player position based on potentially moving terrain (simple clamp for now)
player.clampPosition();
}
// --- Phase 1/2: Air Enemy Logic ---
if (gamePhase === 1) {
// Add more air enemies periodically?
if (LK.ticks % 180 === 0 && airEnemies.length < 10) {
// Spawn if less than 10, every 3 seconds
spawnAirEnemy();
}
// Update air enemies & check collision
for (var i = airEnemies.length - 1; i >= 0; i--) {
var enemy = airEnemies[i];
// Air enemies handle their own off-screen logic (respawn) in their update
// Collision check: Player vs Air Enemy
if (!player.isDead && LK.ticks > player.invincibleUntil && player.intersects(enemy)) {
LK.getSound('enemyExplosion').play(); //{41} // Enemy explodes
enemy.destroy(); // Destroy enemy on collision
airEnemies.splice(i, 1);
// LK.setScore(LK.getScore() + 50); // Score for destroying enemy - player died, maybe no score for this? Or keep it. Let's keep for now.
// scoreTxt.setText(LK.getScore());
handlePlayerDeath();
return;
}
}
}
// --- Phase 3: Boss Logic ---
if (gamePhase === 2 && boss) {
// Check collision: Player vs Boss
if (!player.isDead && LK.ticks > player.invincibleUntil && player.intersects(boss)) {
// Don't destroy boss on collision
handlePlayerDeath();
return;
}
// Update boss lasers & check collision
for (var i = bossLasers.length - 1; i >= 0; i--) {
var laser = bossLasers[i];
// Check if laser is off-screen
if (laser.x < -laser.width || laser.x > 2048 + laser.width || laser.y < -laser.height || laser.y > 2732 + laser.height) {
laser.destroy();
bossLasers.splice(i, 1);
} else {
// Collision check: Player vs Boss Laser
if (!player.isDead && LK.ticks > player.invincibleUntil && player.intersects(laser)) {
laser.destroy(); // Destroy laser
bossLasers.splice(i, 1);
handlePlayerDeath();
return;
}
}
}
// Note: Boss defeat condition is now handled within the player bullet collision check loop.
}
// --- Update Enemy Bullets & Check Collision (All Phases) ---
for (var i = enemyBullets.length - 1; i >= 0; i--) {
var bullet = enemyBullets[i];
// Check if bullet is off-screen
if (bullet.y < -bullet.height || bullet.y > 2732 + bullet.height) {
bullet.destroy();
enemyBullets.splice(i, 1);
} else {
// Collision check: Player vs Enemy Bullet
if (!player.isDead && LK.ticks > player.invincibleUntil && player.intersects(bullet)) {
bullet.destroy(); // Destroy bullet
enemyBullets.splice(i, 1);
handlePlayerDeath(); // This function now handles the delay and sound
return; // Stop update processing
}
}
}
// The saveHighscore function has been moved before handlePlayerDeath to fix the undefined error
// --- Update Player Bullets & Check Collision ---
for (var i = playerBullets.length - 1; i >= 0; i--) {
var bullet = playerBullets[i];
// Check if bullet is off-screen (right side)
if (bullet.x > 2048 + bullet.width) {
bullet.destroy();
playerBullets.splice(i, 1);
continue; // Skip collision checks if off-screen
}
// Collision Check: Player Bullet vs Ground Enemy (Phase 0)
if (gamePhase === 0) {
for (var j = groundEnemies.length - 1; j >= 0; j--) {
var enemy = groundEnemies[j];
if (bullet.intersects(enemy)) {
LK.getSound('enemyExplosion').play();
LK.effects.flashObject(enemy, 0xFFFFFF, 100); // Flash enemy white
enemy.destroy();
groundEnemies.splice(j, 1);
bullet.destroy();
playerBullets.splice(i, 1);
LK.setScore(LK.getScore() + 100); // Score for destroying ground enemy
scoreTxt.setText(LK.getScore());
break; // Bullet can only hit one enemy
}
}
if (!bullet.exists) {
continue;
} // Check if bullet was destroyed in previous loop
}
// Collision Check: Player Bullet vs Air Enemy (Phase 1)
if (gamePhase === 1) {
for (var j = airEnemies.length - 1; j >= 0; j--) {
var enemy = airEnemies[j];
if (bullet.intersects(enemy)) {
LK.getSound('enemyExplosion').play();
LK.effects.flashObject(enemy, 0xFFFFFF, 100); // Flash enemy white
enemy.destroy();
airEnemies.splice(j, 1);
bullet.destroy();
playerBullets.splice(i, 1);
LK.setScore(LK.getScore() + 150); // Score for destroying air enemy
scoreTxt.setText(LK.getScore());
break; // Bullet can only hit one enemy
}
}
if (!bullet.exists) {
continue;
} // Check if bullet was destroyed in previous loop
}
// Collision Check: Player Bullet vs Boss (Phase 2)
if (gamePhase === 2 && boss) {
if (bullet.intersects(boss)) {
LK.getSound('enemyExplosion').play(); // Use enemy explosion for hit sound
LK.effects.flashObject(boss, 0xFFFFFF, 100); // Flash boss white
bullet.destroy();
playerBullets.splice(i, 1);
boss.health -= 1; // Decrease boss health
LK.setScore(LK.getScore() + 10); // Small score for hitting boss
scoreTxt.setText(LK.getScore());
// Boss defeat check is now inside the bullet loop
if (boss.health <= 0) {
LK.getSound('bossExplosion').play(); // Play boss explosion sound
boss.destroy();
boss = null;
// Cleanup remaining lasers
bossLasers.forEach(function (l) {
return l.destroy();
});
bossLasers = [];
LK.setScore(LK.getScore() + 5000); // Big score bonus
scoreTxt.setText(LK.getScore());
// Save highscore before showing You Win
saveHighscore(LK.getScore());
// Create background for scores before showing you win
createScoreBackground();
// Delay showing YouWin slightly to allow sound to play
LK.setTimeout(function () {
LK.showYouWin();
}, 500); // Adjust delay as needed for sound length
return; // Stop update processing
}
break; // Bullet is destroyed after hitting boss
}
}
} // End of playerBullets loop
}; ===================================================================
--- original.js
+++ change.js
Alien Airships of boss, HD colors. In-Game asset. 2d. High contrast. No shadows
pack mountain, yellow, HD colors. In-Game asset. 2d. High contrast. No shadows.no black lines
shot circle. blur, light, HD colors In-Game asset. 2d. High contrast. No shadows
gunTurret aiming diagonal. yellow, HD colors. In-Game asset. 2d. High contrast. No shadows
Square & diagonal tank enemies from the future have 4 turrets on its 4 sides, HD colors. In-Game asset. 2d. High contrast. No shadows