/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Define a class for scrolling backgrounds
var Background = Container.expand(function () {
var self = Container.call(this);
self.speed = 3;
self.asset = null;
self.init = function (assetName, startX) {
self.asset = self.attachAsset(assetName, {
anchorX: 0,
anchorY: 0
});
self.x = startX;
self.y = 0;
};
self.update = function () {
self.x -= gameSpeed;
// If background has moved completely off screen to the left, reposition it to the right
if (self.x <= -1366) {
self.x = 1366 * 2;
}
};
return self;
});
// Define a class for bombs dropped by player
var Bomb = Container.expand(function () {
var self = Container.call(this);
// Create bomb graphic
var bombGraphics = self.attachAsset('bomb', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.2,
scaleY: 2.2
});
// Track last position and explosion state
self.lastY = 0;
self.hasExploded = false;
self.velocityY = 4; // Reduced further to make bombs drop even slower
// Update bomb position and check for ground collision
self.update = function () {
// Store last position before updating
self.lastY = self.y;
// Apply gravity
self.velocityY += 0.2; // Reduced from 0.5 for even slower falling
self.y += self.velocityY;
// Make bomb pulse in size as it falls (rotation removed)
// Pulse size between 1.0 and 1.4 based on sine wave with faster frequency
var pulseScale = 1.0 + 0.2 * Math.sin(LK.ticks * 0.25); // Increased from 0.1 to 0.25 for faster pulse
bombGraphics.scale.set(pulseScale, pulseScale);
// Check for collision with missiles
var children = game.children.slice();
for (var i = 0; i < children.length; i++) {
if (children[i] instanceof Missile && self.intersects(children[i])) {
// Create individual missile explosion at the hit missile's position
var hitMissile = children[i];
var missileExplosion = new Container();
var missileExplosionGraphic = missileExplosion.attachAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.9,
scaleX: 0.1,
scaleY: 0.1
});
missileExplosion.x = hitMissile.x;
missileExplosion.y = hitMissile.y;
missileExplosion.scrollSpeed = gameSpeed;
missileExplosion.update = function () {
missileExplosion.x -= missileExplosion.scrollSpeed;
};
game.addChild(missileExplosion);
// Animate explosion
tween(missileExplosionGraphic, {
alpha: 0,
scaleX: 1.8,
scaleY: 1.8
}, {
duration: 1000,
onFinish: function onFinish() {
game.removeChild(missileExplosion);
}
});
LK.effects.flashScreen(0xFFA500, 500); // Orange flash for 500ms
player.lives = Math.min(3, player.lives + 1);
player.setHeartStatus(player.lives, true);
// Remove the specific missile that was hit
game.removeChild(hitMissile);
// Give the player 1000 points
LK.setScore(LK.getScore() + 1000);
self.explode();
return;
}
}
// Check if bomb hits the ground
if (self.lastY < floorLevel && self.y >= floorLevel) {
self.explode();
}
};
// Handle explosion effect and enemy destruction
self.explode = function () {
if (self.hasExploded) {
return;
}
self.hasExploded = true;
// Play explosion sound
LK.getSound('explosion').play();
// Create explosion graphic
var explosion = LK.getAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8
});
explosion.x = self.x;
explosion.y = self.y;
game.addChild(explosion);
// Check for enemies and missiles in explosion radius
var enemyHit = false;
var missileHit = false;
var children = game.children.slice();
var missilesToRemove = [];
var enemiesToRemove = [];
for (var i = 0; i < children.length; i++) {
// Check for enemy hits
if (children[i] instanceof Enemy) {
var enemy = children[i];
// Simple distance check for explosion radius
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 150) {
// Explosion radius
// Create player hit explosion at enemy's position using playerHitExplosion asset
var enemyExplosion = new Container();
var enemyExplosionGraphic = enemyExplosion.attachAsset('playerHitExplosion', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.9,
scaleX: 0.1,
scaleY: 0.1
});
enemyExplosion.x = enemy.x;
enemyExplosion.y = enemy.y;
enemyExplosion.scrollSpeed = gameSpeed; // Store the current game speed
enemyExplosion.update = function () {
// Make explosion scroll with background
enemyExplosion.x -= enemyExplosion.scrollSpeed;
};
game.addChild(enemyExplosion);
// Animate explosion growing and fading out
tween(enemyExplosionGraphic, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 1000,
onFinish: function onFinish() {
game.removeChild(enemyExplosion);
}
});
// Add enemy to removal list
enemiesToRemove.push(enemy);
enemyHit = true;
}
} else if (children[i] instanceof Missile) {
var missile = children[i];
// Check if missile is in explosion radius
var mDx = missile.x - self.x;
var mDy = missile.y - self.y;
var mDistance = Math.sqrt(mDx * mDx + mDy * mDy);
if (mDistance < 150) {
// Create explosion at missile position
var missileExplosion = new Container();
var missileExplosionGraphic = missileExplosion.attachAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.9,
scaleX: 0.1,
scaleY: 0.1
});
missileExplosion.x = missile.x;
missileExplosion.y = missile.y;
missileExplosion.scrollSpeed = gameSpeed;
missileExplosion.update = function () {
missileExplosion.x -= missileExplosion.scrollSpeed;
};
game.addChild(missileExplosion);
// Animate explosion
tween(missileExplosionGraphic, {
alpha: 0,
scaleX: 1.8,
scaleY: 1.8
}, {
duration: 1000,
onFinish: function onFinish() {
game.removeChild(missileExplosion);
}
});
// Restore heart using setHeartStatus function
player.setHeartStatus(player.lives, true);
// Flash the heart to highlight it was restored
LK.effects.flashObject(player.hearts[player.lives - 1], 0x00FF00, 500);
// Add missile to removal list
missilesToRemove.push(missile);
}
}
}
if (enemyHit) {
// If no missile was hit but an enemy was, give normal points
// Play enemy hit sound when enemy is destroyed
LK.getSound('enemyHit').play();
// Add 500 points for destroying enemy with bomb
LK.setScore(LK.getScore() + 500);
// Score text is updated in game.update animation
}
// Remove all collected enemies and missiles
for (var k = 0; k < enemiesToRemove.length; k++) {
game.removeChild(enemiesToRemove[k]);
}
for (var l = 0; l < missilesToRemove.length; l++) {
game.removeChild(missilesToRemove[l]);
}
// Remove bomb
game.removeChild(self);
// Fade out and remove explosion after 500ms
tween(explosion, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 500,
onFinish: function onFinish() {
game.removeChild(explosion);
}
});
};
return self;
});
// Define a class for enemies
var Enemy = Container.expand(function () {
var self = Container.call(this);
// Create enemy using proper enemy sprite
var enemyGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5
});
// Track last position and collision state
self.lastX = 0;
self.lastCollided = false;
self.update = function () {
// Store last position before updating
self.lastX = self.x;
// Move at the same speed as background
self.x -= gameSpeed;
// Check if enemy hits left edge of screen
if (self.lastX > 0 && self.x <= 0) {
// Add 100 to score
LK.setScore(LK.getScore() + 100);
// Score text is updated in game.update animation
// Remove this enemy
self.parent.removeChild(self);
}
// Check collision with player if not previously collided
if (!self.lastCollided && self.intersects(player)) {
// Set collision flag to prevent multiple hits
self.lastCollided = true;
// Remove tutorial message if it exists
if (!game.jumpTutorialShown && game.jumpTutorialMessage) {
game.removeChild(game.jumpTutorialMessage);
game.jumpTutorialShown = true;
}
// Create player hit explosion at enemy's position using different asset
var explosion = new Container();
var explosionGraphic = explosion.attachAsset('playerHitExplosion', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.9,
scaleX: 0.1,
scaleY: 0.1
});
explosion.x = self.x;
explosion.y = self.y;
explosion.scrollSpeed = gameSpeed; // Store the current game speed
explosion.update = function () {
// Make explosion scroll with background
explosion.x -= explosion.scrollSpeed;
};
game.addChild(explosion);
// Play explosion sound when player is hit
LK.getSound('explosion').play();
// Animate explosion growing and fading out
tween(explosionGraphic, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 1000,
onFinish: function onFinish() {
game.removeChild(explosion);
}
});
// Remove enemy from game
self.parent.removeChild(self);
// Reduce player lives
if (player.lives > 0) {
player.lives--;
// Play sound when life is lost
LK.getSound('lifeLost').play();
// Update heart display
if (player.hearts.length > 0) {
// Make the heart 10% opacity instead of removing it
player.setHeartStatus(player.lives, false);
}
// Flash player to indicate damage
LK.effects.flashObject(player, 0xFF0000, 500);
// Game over if no lives left
if (player.lives <= 0) {
// Hide player
player.visible = false;
// Create explosion where player was
var finalExplosion = new Container();
var finalExplosionGraphic = finalExplosion.attachAsset('playerExplosion', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 1.0,
scaleX: 1.0,
scaleY: 1.0
});
finalExplosion.x = player.x;
finalExplosion.y = player.y;
game.addChild(finalExplosion);
// Stop game scrolling
gameSpeed = 0;
// Animate explosion growing and fading out over 1.5 seconds
tween(finalExplosionGraphic, {
alpha: 0,
scaleX: 3.0,
scaleY: 3.0
}, {
duration: 1500,
onFinish: function onFinish() {
game.removeChild(finalExplosion);
LK.showGameOver();
}
});
}
}
}
};
return self;
});
// Define a class for missiles that launch at player
var Missile = Container.expand(function () {
var self = Container.call(this);
// Create missile graphic using dedicated missile asset
var missileGraphics = self.attachAsset('missile', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 2.2
});
// Track missile properties
self.warningTime = 60; // 1 second warning at 60fps
self.isWarning = true; // Start in warning state
self.scrollSpeed = 0; // Will be set when missile is created
// Create warning indicator
self.warningIndicator = new Container();
var warningGraphic = self.warningIndicator.attachAsset('missile', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.0,
tint: 0xFF0000 // Red color for warning
});
// Add warning text
self.warningText = new Text2('!', {
size: 60,
fill: 0xFFFFFF,
stroke: 0x000000,
strokeThickness: 3
});
self.warningText.anchor.set(0.5, 0.5);
self.warningIndicator.addChild(self.warningText);
// Update method for missile behavior
self.update = function () {
if (self.isWarning) {
// During warning phase, just count down
self.warningTime--;
// Make warning indicator blink
self.warningIndicator.alpha = Math.floor(self.warningTime / 10) % 2 === 0 ? 1.0 : 0.3;
// When warning time ends, start moving missile
if (self.warningTime <= 0) {
self.isWarning = false;
game.removeChild(self.warningIndicator);
// Flash missile red-white-red-white when it first launches
LK.effects.flashObject(missileGraphics, 0xFF0000, 500, 4);
// Play whoosh sound when missile starts moving
LK.getSound('whoosh').play();
}
} else {
// Move missile from right to left at 1.5x scroll speed
self.x -= self.scrollSpeed;
// Check if missile is off screen
if (self.x < -50) {
if (self.parent) {
// Add 50 points when missile reaches left edge
LK.setScore(LK.getScore() + 50);
self.parent.removeChild(self);
}
return;
}
// Check for collision with player
if (self.intersects(player) && player.lives > 0) {
// Create explosion
var explosion = new Container();
var explosionGraphic = explosion.attachAsset('playerHitExplosion', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.9,
scaleX: 0.1,
scaleY: 0.1
});
explosion.x = self.x;
explosion.y = self.y;
explosion.scrollSpeed = gameSpeed;
explosion.update = function () {
explosion.x -= explosion.scrollSpeed;
};
game.addChild(explosion);
// Play explosion sound when player is hit by missile
LK.getSound('explosion').play();
// Animate explosion
tween(explosionGraphic, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 1000,
onFinish: function onFinish() {
game.removeChild(explosion);
}
});
// Reduce player lives
player.lives--;
// Play sound when life is lost
LK.getSound('lifeLost').play();
// Update heart display
if (player.hearts.length > 0) {
// Make the heart 10% opacity instead of removing it
player.setHeartStatus(player.lives, false);
}
// Flash player to indicate damage
LK.effects.flashObject(player, 0xFF0000, 500);
// Game over if no lives left
if (player.lives <= 0) {
// Hide player
player.visible = false;
// Create explosion where player was
var finalExplosion = new Container();
var finalExplosionGraphic = finalExplosion.attachAsset('playerExplosion', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 1.0,
scaleX: 1.0,
scaleY: 1.0
});
finalExplosion.x = player.x;
finalExplosion.y = player.y;
game.addChild(finalExplosion);
// Stop game scrolling
gameSpeed = 0;
// Animate explosion growing and fading out over 1.5 seconds
tween(finalExplosionGraphic, {
alpha: 0,
scaleX: 3.0,
scaleY: 3.0
}, {
duration: 1500,
onFinish: function onFinish() {
game.removeChild(finalExplosion);
LK.showGameOver();
}
});
}
// Remove missile
if (self.parent) {
self.parent.removeChild(self);
}
}
}
};
// Initialize missile with warning at the specified position
self.init = function (yPosition) {
// Position missile at right edge of screen
self.x = 2048;
self.y = yPosition;
// Set missile speed to 1.5x the current game speed
self.scrollSpeed = gameSpeed * 1.5;
// Position warning indicator at the right edge
self.warningIndicator.x = 2048;
self.warningIndicator.y = yPosition;
// Add warning indicator to game
game.addChild(self.warningIndicator);
};
return self;
});
//<Assets used in the game will automatically appear here>
// Define a class for the player character
var Player = Container.expand(function () {
var self = Container.call(this);
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
self.speed = 5;
self.jumpHeight = 30; // Reduced from 35 to 30 for slower, lower jumps
self.isJumping = false;
self.velocityY = 0;
self.lives = 3; // Initialize player with 3 lives
self.hearts = []; // Array to store heart icons
// Function to set heart visibility
self.setHeartStatus = function (livesCount, active) {
// Loop through all hearts
for (var i = 0; i < self.hearts.length; i++) {
// If lives count is less than or equal to the heart index, set to inactive
// Otherwise set to active
if (i < livesCount) {
self.hearts[i].alpha = 1.0;
} else {
self.hearts[i].alpha = 0.2;
}
}
};
self.canDropBomb = false; // Start with bomb unready
self.activeBomb = null; // Reference to current active bomb
self.bombCooldown = 240; // Start with full cooldown
self.bombReloadTime = 240; // 4 seconds at 60fps
self.createHearts = function () {
// Always create three hearts in the middle of the screen near the bottom
for (var i = 0; i < 3; i++) {
var heart = new Container();
// Create a heart shape using heart asset
var heartShape = heart.attachAsset('heart', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
// Calculate position in the middle of the screen
heart.x = 2048 / 2 + (i - 1) * 200; // Center horizontally with even wider spacing
heart.y = 2532; // Position 50 pixels up from previous position
// Set inactive hearts to 10% opacity if beyond current lives
if (i >= self.lives) {
heart.alpha = 0.1;
}
// Add heart to array and game so it stays fixed
self.hearts.push(heart);
game.addChild(heart);
}
};
// Initialize hearts when player is created
self.createHearts();
// Create bomb indicator
self.bombIndicator = new Container();
var bombGraphic = self.bombIndicator.attachAsset('bomb', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.7,
scaleY: 1.7
});
// No cooldown overlay needed
// Add text to show reload progress
self.bombReadyText = new Text2('READY', {
size: 16,
// Slightly bigger text size
fill: 0x4B0082,
// Dark purple
stroke: 0x000000,
strokeThickness: 2,
fontWeight: 'bold' // Make text bold
});
// Initialize style explicitly to avoid "Cannot set properties of undefined" error
self.bombReadyText.style = {
fill: 0x4B0082,
// Dark purple
fontWeight: 'bold' // Make text bold
};
self.bombReadyText.anchor.set(0.5, 0.5); // Center it on the bomb
self.bombReadyText.y = 30; // Position 30 pixels below the center (moved up 10px)
self.bombIndicator.addChild(self.bombReadyText);
// Position bomb indicator at bottom of screen, doubled in size
self.bombIndicator.x = 2048 / 2;
self.bombIndicator.y = 2732 - 515; // Position 60 pixels down from previous position
self.bombIndicator.scale.set(2, 2); // Double the size
game.addChild(self.bombIndicator);
self.update = function () {
if (self.isJumping) {
// If there's an active bomb, hover in place until bomb explodes
if (self.activeBomb && self.activeBomb.parent) {
// Apply very slight hover effect
self.y += Math.sin(LK.ticks * 0.1) * 0.5;
// Remove any upward momentum
if (self.velocityY < 0) {
self.velocityY = 0;
}
} else {
self.y += self.velocityY;
self.velocityY += 0.3; // Even further decreased gravity effect for slower, floatier jumps
if (self.y >= floorLevel) {
// Return to floor level
self.y = floorLevel;
self.isJumping = false;
self.velocityY = 0;
self.activeBomb = null; // Clear reference to active bomb
}
}
}
// Handle bomb cooldown
if (self.bombCooldown > 0) {
self.bombCooldown--;
// Calculate progress for new animation
var progress = self.bombCooldown / self.bombReloadTime;
// If it's the first frame of cooldown, set up initial state
if (self.bombCooldown === self.bombReloadTime - 1) {
// Stop any existing tweens
tween.stop(bombGraphic);
// Reset to 50% scale and transparent
bombGraphic.scale.set(0.5, 0.5);
bombGraphic.alpha = 0;
// Start tween to scale up and increase opacity
tween(bombGraphic, {
alpha: 1.0,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: self.bombReloadTime * (1000 / 60),
easing: tween.easeInOut
});
}
// Update text to show progress
if (self.bombCooldown <= 0) {
self.canDropBomb = true;
self.bombReadyText.setText('READY');
self.bombReadyText.style.fill = 0x00FF00;
// Play bomb ready sound when bomb becomes available
LK.getSound('bombReady').play();
} else {
var secondsLeft = Math.ceil(self.bombCooldown / 60);
self.bombReadyText.setText(secondsLeft + 's');
self.bombReadyText.style.fill = 0xFF0000;
}
}
};
self.jump = function () {
if (!self.isJumping) {
self.isJumping = true;
self.velocityY = -self.jumpHeight;
// Play jump sound when player jumps
LK.getSound('jump').play();
// Remove tutorial message on first jump if it exists
if (!game.jumpTutorialShown && game.jumpTutorialMessage) {
game.removeChild(game.jumpTutorialMessage);
game.jumpTutorialShown = true;
// Start bomb tutorial timer after first jump
if (!game.bombMessageDisplayed) {
game.bombTutorialTimer = 30; // 0.5 seconds at 60fps
}
}
} else if (self.canDropBomb) {
// Drop a bomb if in mid-air and haven't dropped one yet
self.canDropBomb = false;
self.bombCooldown = self.bombReloadTime; // Start cooldown timer (4 seconds)
// Reset bomb graphic to 50% size and transparent
bombGraphic.scale.set(0.5, 0.5);
bombGraphic.alpha = 0;
// Create new bomb
var bomb = new Bomb();
bomb.x = self.x;
bomb.y = self.y + 50; // Position slightly below player
bomb.lastY = bomb.y;
// Add bomb to game at the right layer - above background but below player
game.addChildAt(bomb, 6);
self.activeBomb = bomb;
// Remove bomb tutorial message if first bomb dropped
if (!game.bombTutorialShown && game.bombTutorialMessage) {
game.removeChild(game.bombTutorialMessage);
game.bombTutorialShown = true;
}
}
};
});
// Define a class for street backgrounds (bottom part)
var StreetBackground = Container.expand(function () {
var self = Container.call(this);
self.speed = 3;
self.asset = null;
self.init = function (assetName, startX) {
self.asset = self.attachAsset(assetName, {
anchorX: 0,
anchorY: 0,
scaleY: 0.5 // Make the background half height
});
self.x = startX;
self.y = 2732 * 0.5; // Position in the bottom half of the screen
};
self.update = function () {
self.x -= gameSpeed;
// If background has moved completely off screen to the left, reposition it to the right
if (self.x <= -2048) {
self.x = 2048 * 2 - gameSpeed;
}
};
return self;
});
// UI Panel class
var UIPanel = Container.expand(function () {
var self = Container.call(this);
// Create a panel using a brand new UI panel image
var panel = self.attachAsset('uiPanelBackground', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.9
});
// Method to update panel content (kept for compatibility)
self.updateContent = function (status) {
// No text to update anymore
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1A0933 // Dark purple background
});
/****
* Game Code
****/
// Game speed control variable
var gameSpeed = 7; // Increased from 5 to 7 for faster initial scrolling
// Floor level variable (Y position for player and enemies)
var floorLevel = 2732 * 0.55 + 400; // 55% of screen height + 400px down
// Track enemies
var enemies = [];
// Tutorial message tracking
game.jumpTutorialShown = false;
game.jumpMessageDisplayed = false;
game.bombTutorialShown = false;
game.bombMessageDisplayed = false;
game.bombTutorialTimer = 0;
// Create three scrolling backgrounds for the top part
var backgrounds = [];
// Create three scrolling backgrounds for the bottom part (street)
var streetBackgrounds = [];
// Create each background and position them
for (var i = 0; i < 3; i++) {
var bg = new Background();
bg.init('background' + (i + 1), i * 1366);
backgrounds.push(bg);
game.addChild(bg);
// Create street backgrounds using different assets
var streetBg = new StreetBackground();
streetBg.init('street' + (i + 1), i * 2048);
streetBackgrounds.push(streetBg);
game.addChild(streetBg);
}
// Initialize player and add at a high layer to ensure it's above all game elements
var player = new Player();
player.x = 2048 / 2 - 500; // Move player back 500 pixels
player.y = floorLevel; // Position player at the floor level
// Add player at a layer that ensures it's above bombs but below UI
var playerLayerIndex = Math.min(game.children.length, 10); // Use a valid index based on current children
game.addChildAt(player, playerLayerIndex);
// Create and position UI panel
var uiPanel = new UIPanel();
uiPanel.x = 2048 / 2; // Center horizontally
uiPanel.y = 2732 - 300; // Position 50 pixels up from previous position
uiPanel.scale.set(1.2, 1.2); // Make the UI panel 20% bigger
// Make sure UI panel is at the right display level (moved up six levels)
game.addChildAt(uiPanel, 6);
// Create a new Text2 object to display the score with cyberpunk styling
var scoreText = new Text2('0', {
size: 120,
fill: 0x00FFFF,
// Cyan color for cyberpunk neon effect
stroke: 0xFF00FF,
// Magenta stroke for contrast
strokeThickness: 5,
// Thick stroke for glow effect
dropShadow: true,
dropShadowColor: 0x00FFFF,
// Cyan shadow
dropShadowBlur: 10,
dropShadowAngle: Math.PI / 4,
dropShadowDistance: 6,
letterSpacing: 3 // Spacing between letters for futuristic look
});
// Add the score text to the game GUI at the top center of the screen
LK.gui.top.addChild(scoreText);
// Center the text
scoreText.anchor.set(0.5, 0);
// Add padding from the top
scoreText.y = 80;
// Lives text has been removed
// Keep track of displayed score for animation
var displayedScore = 0;
// Create a pulsating glow effect for the cyberpunk score display
function pulseScoreText() {
tween(scoreText, {
strokeThickness: 8,
dropShadowBlur: 15
}, {
duration: 800,
onFinish: function onFinish() {
tween(scoreText, {
strokeThickness: 5,
dropShadowBlur: 10
}, {
duration: 800,
onFinish: pulseScoreText
});
}
});
}
pulseScoreText();
// Handle game updates
game.update = function () {
// Increase game speed as score increases for difficulty progression
gameSpeed = 5 + Math.min(LK.getScore() / 10, 5); // Cap at max 10 speed
// Handle bomb tutorial timer and display
if (game.jumpTutorialShown && !game.bombMessageDisplayed && game.bombTutorialTimer > 0) {
game.bombTutorialTimer--;
if (game.bombTutorialTimer === 0) {
// Create bomb tutorial message
game.bombMessageDisplayed = true;
var bombMessage = new Container();
var bombSprite = bombMessage.attachAsset('tap_to_bomb', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.0,
scaleY: 2.0
});
// Position the message in the center of the screen
bombMessage.x = 2048 / 2;
bombMessage.y = 2732 / 2; // Centered on screen
// Add the message directly to game
game.addChild(bombMessage);
// Make the message pulse using tween
var _pulseBombMessage = function pulseBombMessage() {
tween(bombMessage.scale, {
x: 1.2,
y: 1.2
}, {
duration: 500,
onFinish: function onFinish() {
tween(bombMessage.scale, {
x: 1.0,
y: 1.0
}, {
duration: 500,
onFinish: _pulseBombMessage
});
}
});
};
_pulseBombMessage();
// Store reference to message
game.bombTutorialMessage = bombMessage;
}
}
// Check if we need to show the jump tutorial message
if (!game.jumpTutorialShown) {
// Check for enemies approaching the player
var children = game.children.slice();
var _loop = function _loop() {
if (children[t] instanceof Enemy) {
enemy = children[t]; // Check if this is the first enemy and hasn't had a message attached yet
if (!game.jumpMessageDisplayed) {
// Make the message pulse using tween
var _pulseMessage = function pulseMessage() {
tween(jumpMessage.scale, {
x: 1.2,
y: 1.2
}, {
duration: 500,
onFinish: function onFinish() {
tween(jumpMessage.scale, {
x: 1.0,
y: 1.0
}, {
duration: 500,
onFinish: _pulseMessage
});
}
});
};
game.jumpMessageDisplayed = true;
// Create jump tutorial message as a sprite in center of screen
jumpMessage = new Container();
var jumpSprite = jumpMessage.attachAsset('tap_to_jump', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.0,
scaleY: 2.0
});
// Position the message in the center of the screen
jumpMessage.x = 2048 / 2;
jumpMessage.y = 2732 / 2;
// Add the message directly to game so it's centered on screen
game.addChild(jumpMessage);
_pulseMessage();
// Variable to store tutorial message reference
game.jumpTutorialMessage = jumpMessage;
// Create a function to check distance and remove the message
enemy.checkJumpTutorialDistance = function () {
var distance = Math.abs(this.x - player.x);
if (distance <= 150) {
game.removeChild(game.jumpTutorialMessage);
game.jumpTutorialShown = true;
// Remove the check function from enemy's update
this.checkJumpTutorialDistance = null;
}
};
// Modify the enemy's update method to include the distance check
originalUpdate = enemy.update;
enemy.update = function () {
originalUpdate.call(this);
if (this.checkJumpTutorialDistance) {
this.checkJumpTutorialDistance();
}
};
return 1; // break
}
}
},
enemy,
jumpMessage,
originalUpdate;
for (var t = 0; t < children.length; t++) {
if (_loop()) break;
}
}
// Check if score reaches 3000, increase game speed and missile spawn rate
if (LK.getScore() >= 20000) {
// Boost speed significantly at 20000 points
gameSpeed = 5 + Math.min(LK.getScore() / 10, 12); // Cap at max 17 speed
} else if (LK.getScore() >= 3000) {
// Boost speed further when score hits 3000
gameSpeed = 5 + Math.min(LK.getScore() / 10, 8); // Cap at max 13 speed
}
// Update scrolling backgrounds
for (var i = 0; i < backgrounds.length; i++) {
backgrounds[i].update();
}
// Update street backgrounds
for (var i = 0; i < streetBackgrounds.length; i++) {
streetBackgrounds[i].update();
}
player.update();
// Spawn enemies (wait 5 seconds at the start - 300 frames at 60fps)
if (LK.ticks > 300) {
// Determine enemy spawn interval based on score
var enemySpawnInterval = 180; // Default: every 3 seconds
if (LK.getScore() >= 20000) {
enemySpawnInterval = 90; // Every 1.5 seconds at 20000+
} else if (LK.getScore() >= 3000) {
enemySpawnInterval = 150; // Every 2.5 seconds at 3000+
}
if (LK.ticks % enemySpawnInterval === 0) {
// Spawn enemy
var enemy = new Enemy();
enemy.x = 2048; // Start at right edge of screen
enemy.y = floorLevel; // Position at floor level
enemy.lastX = enemy.x; // Initialize lastX
game.addChild(enemy);
}
}
// Spawn missiles when player score reaches 1500
if (LK.getScore() >= 1500) {
// Determine spawn interval based on score thresholds
var missileSpawnInterval = 120; // Default interval
if (LK.getScore() >= 20000) {
missileSpawnInterval = 30; // Four times as often at 20000+
} else if (LK.getScore() >= 3000) {
missileSpawnInterval = 60; // Twice as often at 3000+
}
if (LK.ticks % missileSpawnInterval === 0) {
// Calculate a y-position for the missile
// Should be at least 500px above player but no higher than max jump height
var playerMaxHeight = floorLevel - player.jumpHeight * 6; // Approximate max jump height
var minMissileHeight = floorLevel - 2800; // At least 500px above player (increased from 300px)
var maxMissileHeight = floorLevel - 800; // Don't go too high
// Calculate the valid range for missile spawn positions
var validRangeMin = Math.max(maxMissileHeight, minMissileHeight);
var validRangeMax = floorLevel - 50;
// Generate a random position within this range
var missileY = validRangeMin + Math.random() * (validRangeMax - validRangeMin);
// Ensure missile is within screen bounds
missileY = Math.max(100, Math.min(missileY, floorLevel - 50));
// Create missile with warning
var missile = new Missile();
missile.init(missileY);
game.addChild(missile);
}
}
// Update all enemies and bombs
var children = game.children.slice();
for (var i = 0; i < children.length; i++) {
if (children[i] instanceof Enemy) {
children[i].update();
} else if (children[i] instanceof Bomb) {
children[i].update();
} else if (children[i] instanceof Missile) {
children[i].update();
} else if (children[i].update && children[i].scrollSpeed !== undefined) {
// Update any container with scrollSpeed (like our player hit explosions)
children[i].update();
}
}
// Animate score count up
var actualScore = LK.getScore();
if (displayedScore < actualScore) {
// Increment displayed score by a percentage of the difference
displayedScore = Math.min(displayedScore + Math.ceil((actualScore - displayedScore) * 0.1), actualScore);
scoreText.setText(displayedScore.toString());
}
// Lives text display has been removed
// Update UI panel is no longer needed - using player's bomb display
// Play background music if not already playing
if (LK.ticks === 1) {
LK.playMusic('MeanStreets');
}
};
// Handle player jump
game.down = function (x, y, obj) {
player.jump();
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Define a class for scrolling backgrounds
var Background = Container.expand(function () {
var self = Container.call(this);
self.speed = 3;
self.asset = null;
self.init = function (assetName, startX) {
self.asset = self.attachAsset(assetName, {
anchorX: 0,
anchorY: 0
});
self.x = startX;
self.y = 0;
};
self.update = function () {
self.x -= gameSpeed;
// If background has moved completely off screen to the left, reposition it to the right
if (self.x <= -1366) {
self.x = 1366 * 2;
}
};
return self;
});
// Define a class for bombs dropped by player
var Bomb = Container.expand(function () {
var self = Container.call(this);
// Create bomb graphic
var bombGraphics = self.attachAsset('bomb', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.2,
scaleY: 2.2
});
// Track last position and explosion state
self.lastY = 0;
self.hasExploded = false;
self.velocityY = 4; // Reduced further to make bombs drop even slower
// Update bomb position and check for ground collision
self.update = function () {
// Store last position before updating
self.lastY = self.y;
// Apply gravity
self.velocityY += 0.2; // Reduced from 0.5 for even slower falling
self.y += self.velocityY;
// Make bomb pulse in size as it falls (rotation removed)
// Pulse size between 1.0 and 1.4 based on sine wave with faster frequency
var pulseScale = 1.0 + 0.2 * Math.sin(LK.ticks * 0.25); // Increased from 0.1 to 0.25 for faster pulse
bombGraphics.scale.set(pulseScale, pulseScale);
// Check for collision with missiles
var children = game.children.slice();
for (var i = 0; i < children.length; i++) {
if (children[i] instanceof Missile && self.intersects(children[i])) {
// Create individual missile explosion at the hit missile's position
var hitMissile = children[i];
var missileExplosion = new Container();
var missileExplosionGraphic = missileExplosion.attachAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.9,
scaleX: 0.1,
scaleY: 0.1
});
missileExplosion.x = hitMissile.x;
missileExplosion.y = hitMissile.y;
missileExplosion.scrollSpeed = gameSpeed;
missileExplosion.update = function () {
missileExplosion.x -= missileExplosion.scrollSpeed;
};
game.addChild(missileExplosion);
// Animate explosion
tween(missileExplosionGraphic, {
alpha: 0,
scaleX: 1.8,
scaleY: 1.8
}, {
duration: 1000,
onFinish: function onFinish() {
game.removeChild(missileExplosion);
}
});
LK.effects.flashScreen(0xFFA500, 500); // Orange flash for 500ms
player.lives = Math.min(3, player.lives + 1);
player.setHeartStatus(player.lives, true);
// Remove the specific missile that was hit
game.removeChild(hitMissile);
// Give the player 1000 points
LK.setScore(LK.getScore() + 1000);
self.explode();
return;
}
}
// Check if bomb hits the ground
if (self.lastY < floorLevel && self.y >= floorLevel) {
self.explode();
}
};
// Handle explosion effect and enemy destruction
self.explode = function () {
if (self.hasExploded) {
return;
}
self.hasExploded = true;
// Play explosion sound
LK.getSound('explosion').play();
// Create explosion graphic
var explosion = LK.getAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8
});
explosion.x = self.x;
explosion.y = self.y;
game.addChild(explosion);
// Check for enemies and missiles in explosion radius
var enemyHit = false;
var missileHit = false;
var children = game.children.slice();
var missilesToRemove = [];
var enemiesToRemove = [];
for (var i = 0; i < children.length; i++) {
// Check for enemy hits
if (children[i] instanceof Enemy) {
var enemy = children[i];
// Simple distance check for explosion radius
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 150) {
// Explosion radius
// Create player hit explosion at enemy's position using playerHitExplosion asset
var enemyExplosion = new Container();
var enemyExplosionGraphic = enemyExplosion.attachAsset('playerHitExplosion', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.9,
scaleX: 0.1,
scaleY: 0.1
});
enemyExplosion.x = enemy.x;
enemyExplosion.y = enemy.y;
enemyExplosion.scrollSpeed = gameSpeed; // Store the current game speed
enemyExplosion.update = function () {
// Make explosion scroll with background
enemyExplosion.x -= enemyExplosion.scrollSpeed;
};
game.addChild(enemyExplosion);
// Animate explosion growing and fading out
tween(enemyExplosionGraphic, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 1000,
onFinish: function onFinish() {
game.removeChild(enemyExplosion);
}
});
// Add enemy to removal list
enemiesToRemove.push(enemy);
enemyHit = true;
}
} else if (children[i] instanceof Missile) {
var missile = children[i];
// Check if missile is in explosion radius
var mDx = missile.x - self.x;
var mDy = missile.y - self.y;
var mDistance = Math.sqrt(mDx * mDx + mDy * mDy);
if (mDistance < 150) {
// Create explosion at missile position
var missileExplosion = new Container();
var missileExplosionGraphic = missileExplosion.attachAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.9,
scaleX: 0.1,
scaleY: 0.1
});
missileExplosion.x = missile.x;
missileExplosion.y = missile.y;
missileExplosion.scrollSpeed = gameSpeed;
missileExplosion.update = function () {
missileExplosion.x -= missileExplosion.scrollSpeed;
};
game.addChild(missileExplosion);
// Animate explosion
tween(missileExplosionGraphic, {
alpha: 0,
scaleX: 1.8,
scaleY: 1.8
}, {
duration: 1000,
onFinish: function onFinish() {
game.removeChild(missileExplosion);
}
});
// Restore heart using setHeartStatus function
player.setHeartStatus(player.lives, true);
// Flash the heart to highlight it was restored
LK.effects.flashObject(player.hearts[player.lives - 1], 0x00FF00, 500);
// Add missile to removal list
missilesToRemove.push(missile);
}
}
}
if (enemyHit) {
// If no missile was hit but an enemy was, give normal points
// Play enemy hit sound when enemy is destroyed
LK.getSound('enemyHit').play();
// Add 500 points for destroying enemy with bomb
LK.setScore(LK.getScore() + 500);
// Score text is updated in game.update animation
}
// Remove all collected enemies and missiles
for (var k = 0; k < enemiesToRemove.length; k++) {
game.removeChild(enemiesToRemove[k]);
}
for (var l = 0; l < missilesToRemove.length; l++) {
game.removeChild(missilesToRemove[l]);
}
// Remove bomb
game.removeChild(self);
// Fade out and remove explosion after 500ms
tween(explosion, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 500,
onFinish: function onFinish() {
game.removeChild(explosion);
}
});
};
return self;
});
// Define a class for enemies
var Enemy = Container.expand(function () {
var self = Container.call(this);
// Create enemy using proper enemy sprite
var enemyGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5
});
// Track last position and collision state
self.lastX = 0;
self.lastCollided = false;
self.update = function () {
// Store last position before updating
self.lastX = self.x;
// Move at the same speed as background
self.x -= gameSpeed;
// Check if enemy hits left edge of screen
if (self.lastX > 0 && self.x <= 0) {
// Add 100 to score
LK.setScore(LK.getScore() + 100);
// Score text is updated in game.update animation
// Remove this enemy
self.parent.removeChild(self);
}
// Check collision with player if not previously collided
if (!self.lastCollided && self.intersects(player)) {
// Set collision flag to prevent multiple hits
self.lastCollided = true;
// Remove tutorial message if it exists
if (!game.jumpTutorialShown && game.jumpTutorialMessage) {
game.removeChild(game.jumpTutorialMessage);
game.jumpTutorialShown = true;
}
// Create player hit explosion at enemy's position using different asset
var explosion = new Container();
var explosionGraphic = explosion.attachAsset('playerHitExplosion', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.9,
scaleX: 0.1,
scaleY: 0.1
});
explosion.x = self.x;
explosion.y = self.y;
explosion.scrollSpeed = gameSpeed; // Store the current game speed
explosion.update = function () {
// Make explosion scroll with background
explosion.x -= explosion.scrollSpeed;
};
game.addChild(explosion);
// Play explosion sound when player is hit
LK.getSound('explosion').play();
// Animate explosion growing and fading out
tween(explosionGraphic, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 1000,
onFinish: function onFinish() {
game.removeChild(explosion);
}
});
// Remove enemy from game
self.parent.removeChild(self);
// Reduce player lives
if (player.lives > 0) {
player.lives--;
// Play sound when life is lost
LK.getSound('lifeLost').play();
// Update heart display
if (player.hearts.length > 0) {
// Make the heart 10% opacity instead of removing it
player.setHeartStatus(player.lives, false);
}
// Flash player to indicate damage
LK.effects.flashObject(player, 0xFF0000, 500);
// Game over if no lives left
if (player.lives <= 0) {
// Hide player
player.visible = false;
// Create explosion where player was
var finalExplosion = new Container();
var finalExplosionGraphic = finalExplosion.attachAsset('playerExplosion', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 1.0,
scaleX: 1.0,
scaleY: 1.0
});
finalExplosion.x = player.x;
finalExplosion.y = player.y;
game.addChild(finalExplosion);
// Stop game scrolling
gameSpeed = 0;
// Animate explosion growing and fading out over 1.5 seconds
tween(finalExplosionGraphic, {
alpha: 0,
scaleX: 3.0,
scaleY: 3.0
}, {
duration: 1500,
onFinish: function onFinish() {
game.removeChild(finalExplosion);
LK.showGameOver();
}
});
}
}
}
};
return self;
});
// Define a class for missiles that launch at player
var Missile = Container.expand(function () {
var self = Container.call(this);
// Create missile graphic using dedicated missile asset
var missileGraphics = self.attachAsset('missile', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 2.2
});
// Track missile properties
self.warningTime = 60; // 1 second warning at 60fps
self.isWarning = true; // Start in warning state
self.scrollSpeed = 0; // Will be set when missile is created
// Create warning indicator
self.warningIndicator = new Container();
var warningGraphic = self.warningIndicator.attachAsset('missile', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.0,
tint: 0xFF0000 // Red color for warning
});
// Add warning text
self.warningText = new Text2('!', {
size: 60,
fill: 0xFFFFFF,
stroke: 0x000000,
strokeThickness: 3
});
self.warningText.anchor.set(0.5, 0.5);
self.warningIndicator.addChild(self.warningText);
// Update method for missile behavior
self.update = function () {
if (self.isWarning) {
// During warning phase, just count down
self.warningTime--;
// Make warning indicator blink
self.warningIndicator.alpha = Math.floor(self.warningTime / 10) % 2 === 0 ? 1.0 : 0.3;
// When warning time ends, start moving missile
if (self.warningTime <= 0) {
self.isWarning = false;
game.removeChild(self.warningIndicator);
// Flash missile red-white-red-white when it first launches
LK.effects.flashObject(missileGraphics, 0xFF0000, 500, 4);
// Play whoosh sound when missile starts moving
LK.getSound('whoosh').play();
}
} else {
// Move missile from right to left at 1.5x scroll speed
self.x -= self.scrollSpeed;
// Check if missile is off screen
if (self.x < -50) {
if (self.parent) {
// Add 50 points when missile reaches left edge
LK.setScore(LK.getScore() + 50);
self.parent.removeChild(self);
}
return;
}
// Check for collision with player
if (self.intersects(player) && player.lives > 0) {
// Create explosion
var explosion = new Container();
var explosionGraphic = explosion.attachAsset('playerHitExplosion', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.9,
scaleX: 0.1,
scaleY: 0.1
});
explosion.x = self.x;
explosion.y = self.y;
explosion.scrollSpeed = gameSpeed;
explosion.update = function () {
explosion.x -= explosion.scrollSpeed;
};
game.addChild(explosion);
// Play explosion sound when player is hit by missile
LK.getSound('explosion').play();
// Animate explosion
tween(explosionGraphic, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 1000,
onFinish: function onFinish() {
game.removeChild(explosion);
}
});
// Reduce player lives
player.lives--;
// Play sound when life is lost
LK.getSound('lifeLost').play();
// Update heart display
if (player.hearts.length > 0) {
// Make the heart 10% opacity instead of removing it
player.setHeartStatus(player.lives, false);
}
// Flash player to indicate damage
LK.effects.flashObject(player, 0xFF0000, 500);
// Game over if no lives left
if (player.lives <= 0) {
// Hide player
player.visible = false;
// Create explosion where player was
var finalExplosion = new Container();
var finalExplosionGraphic = finalExplosion.attachAsset('playerExplosion', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 1.0,
scaleX: 1.0,
scaleY: 1.0
});
finalExplosion.x = player.x;
finalExplosion.y = player.y;
game.addChild(finalExplosion);
// Stop game scrolling
gameSpeed = 0;
// Animate explosion growing and fading out over 1.5 seconds
tween(finalExplosionGraphic, {
alpha: 0,
scaleX: 3.0,
scaleY: 3.0
}, {
duration: 1500,
onFinish: function onFinish() {
game.removeChild(finalExplosion);
LK.showGameOver();
}
});
}
// Remove missile
if (self.parent) {
self.parent.removeChild(self);
}
}
}
};
// Initialize missile with warning at the specified position
self.init = function (yPosition) {
// Position missile at right edge of screen
self.x = 2048;
self.y = yPosition;
// Set missile speed to 1.5x the current game speed
self.scrollSpeed = gameSpeed * 1.5;
// Position warning indicator at the right edge
self.warningIndicator.x = 2048;
self.warningIndicator.y = yPosition;
// Add warning indicator to game
game.addChild(self.warningIndicator);
};
return self;
});
//<Assets used in the game will automatically appear here>
// Define a class for the player character
var Player = Container.expand(function () {
var self = Container.call(this);
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
self.speed = 5;
self.jumpHeight = 30; // Reduced from 35 to 30 for slower, lower jumps
self.isJumping = false;
self.velocityY = 0;
self.lives = 3; // Initialize player with 3 lives
self.hearts = []; // Array to store heart icons
// Function to set heart visibility
self.setHeartStatus = function (livesCount, active) {
// Loop through all hearts
for (var i = 0; i < self.hearts.length; i++) {
// If lives count is less than or equal to the heart index, set to inactive
// Otherwise set to active
if (i < livesCount) {
self.hearts[i].alpha = 1.0;
} else {
self.hearts[i].alpha = 0.2;
}
}
};
self.canDropBomb = false; // Start with bomb unready
self.activeBomb = null; // Reference to current active bomb
self.bombCooldown = 240; // Start with full cooldown
self.bombReloadTime = 240; // 4 seconds at 60fps
self.createHearts = function () {
// Always create three hearts in the middle of the screen near the bottom
for (var i = 0; i < 3; i++) {
var heart = new Container();
// Create a heart shape using heart asset
var heartShape = heart.attachAsset('heart', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
// Calculate position in the middle of the screen
heart.x = 2048 / 2 + (i - 1) * 200; // Center horizontally with even wider spacing
heart.y = 2532; // Position 50 pixels up from previous position
// Set inactive hearts to 10% opacity if beyond current lives
if (i >= self.lives) {
heart.alpha = 0.1;
}
// Add heart to array and game so it stays fixed
self.hearts.push(heart);
game.addChild(heart);
}
};
// Initialize hearts when player is created
self.createHearts();
// Create bomb indicator
self.bombIndicator = new Container();
var bombGraphic = self.bombIndicator.attachAsset('bomb', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.7,
scaleY: 1.7
});
// No cooldown overlay needed
// Add text to show reload progress
self.bombReadyText = new Text2('READY', {
size: 16,
// Slightly bigger text size
fill: 0x4B0082,
// Dark purple
stroke: 0x000000,
strokeThickness: 2,
fontWeight: 'bold' // Make text bold
});
// Initialize style explicitly to avoid "Cannot set properties of undefined" error
self.bombReadyText.style = {
fill: 0x4B0082,
// Dark purple
fontWeight: 'bold' // Make text bold
};
self.bombReadyText.anchor.set(0.5, 0.5); // Center it on the bomb
self.bombReadyText.y = 30; // Position 30 pixels below the center (moved up 10px)
self.bombIndicator.addChild(self.bombReadyText);
// Position bomb indicator at bottom of screen, doubled in size
self.bombIndicator.x = 2048 / 2;
self.bombIndicator.y = 2732 - 515; // Position 60 pixels down from previous position
self.bombIndicator.scale.set(2, 2); // Double the size
game.addChild(self.bombIndicator);
self.update = function () {
if (self.isJumping) {
// If there's an active bomb, hover in place until bomb explodes
if (self.activeBomb && self.activeBomb.parent) {
// Apply very slight hover effect
self.y += Math.sin(LK.ticks * 0.1) * 0.5;
// Remove any upward momentum
if (self.velocityY < 0) {
self.velocityY = 0;
}
} else {
self.y += self.velocityY;
self.velocityY += 0.3; // Even further decreased gravity effect for slower, floatier jumps
if (self.y >= floorLevel) {
// Return to floor level
self.y = floorLevel;
self.isJumping = false;
self.velocityY = 0;
self.activeBomb = null; // Clear reference to active bomb
}
}
}
// Handle bomb cooldown
if (self.bombCooldown > 0) {
self.bombCooldown--;
// Calculate progress for new animation
var progress = self.bombCooldown / self.bombReloadTime;
// If it's the first frame of cooldown, set up initial state
if (self.bombCooldown === self.bombReloadTime - 1) {
// Stop any existing tweens
tween.stop(bombGraphic);
// Reset to 50% scale and transparent
bombGraphic.scale.set(0.5, 0.5);
bombGraphic.alpha = 0;
// Start tween to scale up and increase opacity
tween(bombGraphic, {
alpha: 1.0,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: self.bombReloadTime * (1000 / 60),
easing: tween.easeInOut
});
}
// Update text to show progress
if (self.bombCooldown <= 0) {
self.canDropBomb = true;
self.bombReadyText.setText('READY');
self.bombReadyText.style.fill = 0x00FF00;
// Play bomb ready sound when bomb becomes available
LK.getSound('bombReady').play();
} else {
var secondsLeft = Math.ceil(self.bombCooldown / 60);
self.bombReadyText.setText(secondsLeft + 's');
self.bombReadyText.style.fill = 0xFF0000;
}
}
};
self.jump = function () {
if (!self.isJumping) {
self.isJumping = true;
self.velocityY = -self.jumpHeight;
// Play jump sound when player jumps
LK.getSound('jump').play();
// Remove tutorial message on first jump if it exists
if (!game.jumpTutorialShown && game.jumpTutorialMessage) {
game.removeChild(game.jumpTutorialMessage);
game.jumpTutorialShown = true;
// Start bomb tutorial timer after first jump
if (!game.bombMessageDisplayed) {
game.bombTutorialTimer = 30; // 0.5 seconds at 60fps
}
}
} else if (self.canDropBomb) {
// Drop a bomb if in mid-air and haven't dropped one yet
self.canDropBomb = false;
self.bombCooldown = self.bombReloadTime; // Start cooldown timer (4 seconds)
// Reset bomb graphic to 50% size and transparent
bombGraphic.scale.set(0.5, 0.5);
bombGraphic.alpha = 0;
// Create new bomb
var bomb = new Bomb();
bomb.x = self.x;
bomb.y = self.y + 50; // Position slightly below player
bomb.lastY = bomb.y;
// Add bomb to game at the right layer - above background but below player
game.addChildAt(bomb, 6);
self.activeBomb = bomb;
// Remove bomb tutorial message if first bomb dropped
if (!game.bombTutorialShown && game.bombTutorialMessage) {
game.removeChild(game.bombTutorialMessage);
game.bombTutorialShown = true;
}
}
};
});
// Define a class for street backgrounds (bottom part)
var StreetBackground = Container.expand(function () {
var self = Container.call(this);
self.speed = 3;
self.asset = null;
self.init = function (assetName, startX) {
self.asset = self.attachAsset(assetName, {
anchorX: 0,
anchorY: 0,
scaleY: 0.5 // Make the background half height
});
self.x = startX;
self.y = 2732 * 0.5; // Position in the bottom half of the screen
};
self.update = function () {
self.x -= gameSpeed;
// If background has moved completely off screen to the left, reposition it to the right
if (self.x <= -2048) {
self.x = 2048 * 2 - gameSpeed;
}
};
return self;
});
// UI Panel class
var UIPanel = Container.expand(function () {
var self = Container.call(this);
// Create a panel using a brand new UI panel image
var panel = self.attachAsset('uiPanelBackground', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.9
});
// Method to update panel content (kept for compatibility)
self.updateContent = function (status) {
// No text to update anymore
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1A0933 // Dark purple background
});
/****
* Game Code
****/
// Game speed control variable
var gameSpeed = 7; // Increased from 5 to 7 for faster initial scrolling
// Floor level variable (Y position for player and enemies)
var floorLevel = 2732 * 0.55 + 400; // 55% of screen height + 400px down
// Track enemies
var enemies = [];
// Tutorial message tracking
game.jumpTutorialShown = false;
game.jumpMessageDisplayed = false;
game.bombTutorialShown = false;
game.bombMessageDisplayed = false;
game.bombTutorialTimer = 0;
// Create three scrolling backgrounds for the top part
var backgrounds = [];
// Create three scrolling backgrounds for the bottom part (street)
var streetBackgrounds = [];
// Create each background and position them
for (var i = 0; i < 3; i++) {
var bg = new Background();
bg.init('background' + (i + 1), i * 1366);
backgrounds.push(bg);
game.addChild(bg);
// Create street backgrounds using different assets
var streetBg = new StreetBackground();
streetBg.init('street' + (i + 1), i * 2048);
streetBackgrounds.push(streetBg);
game.addChild(streetBg);
}
// Initialize player and add at a high layer to ensure it's above all game elements
var player = new Player();
player.x = 2048 / 2 - 500; // Move player back 500 pixels
player.y = floorLevel; // Position player at the floor level
// Add player at a layer that ensures it's above bombs but below UI
var playerLayerIndex = Math.min(game.children.length, 10); // Use a valid index based on current children
game.addChildAt(player, playerLayerIndex);
// Create and position UI panel
var uiPanel = new UIPanel();
uiPanel.x = 2048 / 2; // Center horizontally
uiPanel.y = 2732 - 300; // Position 50 pixels up from previous position
uiPanel.scale.set(1.2, 1.2); // Make the UI panel 20% bigger
// Make sure UI panel is at the right display level (moved up six levels)
game.addChildAt(uiPanel, 6);
// Create a new Text2 object to display the score with cyberpunk styling
var scoreText = new Text2('0', {
size: 120,
fill: 0x00FFFF,
// Cyan color for cyberpunk neon effect
stroke: 0xFF00FF,
// Magenta stroke for contrast
strokeThickness: 5,
// Thick stroke for glow effect
dropShadow: true,
dropShadowColor: 0x00FFFF,
// Cyan shadow
dropShadowBlur: 10,
dropShadowAngle: Math.PI / 4,
dropShadowDistance: 6,
letterSpacing: 3 // Spacing between letters for futuristic look
});
// Add the score text to the game GUI at the top center of the screen
LK.gui.top.addChild(scoreText);
// Center the text
scoreText.anchor.set(0.5, 0);
// Add padding from the top
scoreText.y = 80;
// Lives text has been removed
// Keep track of displayed score for animation
var displayedScore = 0;
// Create a pulsating glow effect for the cyberpunk score display
function pulseScoreText() {
tween(scoreText, {
strokeThickness: 8,
dropShadowBlur: 15
}, {
duration: 800,
onFinish: function onFinish() {
tween(scoreText, {
strokeThickness: 5,
dropShadowBlur: 10
}, {
duration: 800,
onFinish: pulseScoreText
});
}
});
}
pulseScoreText();
// Handle game updates
game.update = function () {
// Increase game speed as score increases for difficulty progression
gameSpeed = 5 + Math.min(LK.getScore() / 10, 5); // Cap at max 10 speed
// Handle bomb tutorial timer and display
if (game.jumpTutorialShown && !game.bombMessageDisplayed && game.bombTutorialTimer > 0) {
game.bombTutorialTimer--;
if (game.bombTutorialTimer === 0) {
// Create bomb tutorial message
game.bombMessageDisplayed = true;
var bombMessage = new Container();
var bombSprite = bombMessage.attachAsset('tap_to_bomb', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.0,
scaleY: 2.0
});
// Position the message in the center of the screen
bombMessage.x = 2048 / 2;
bombMessage.y = 2732 / 2; // Centered on screen
// Add the message directly to game
game.addChild(bombMessage);
// Make the message pulse using tween
var _pulseBombMessage = function pulseBombMessage() {
tween(bombMessage.scale, {
x: 1.2,
y: 1.2
}, {
duration: 500,
onFinish: function onFinish() {
tween(bombMessage.scale, {
x: 1.0,
y: 1.0
}, {
duration: 500,
onFinish: _pulseBombMessage
});
}
});
};
_pulseBombMessage();
// Store reference to message
game.bombTutorialMessage = bombMessage;
}
}
// Check if we need to show the jump tutorial message
if (!game.jumpTutorialShown) {
// Check for enemies approaching the player
var children = game.children.slice();
var _loop = function _loop() {
if (children[t] instanceof Enemy) {
enemy = children[t]; // Check if this is the first enemy and hasn't had a message attached yet
if (!game.jumpMessageDisplayed) {
// Make the message pulse using tween
var _pulseMessage = function pulseMessage() {
tween(jumpMessage.scale, {
x: 1.2,
y: 1.2
}, {
duration: 500,
onFinish: function onFinish() {
tween(jumpMessage.scale, {
x: 1.0,
y: 1.0
}, {
duration: 500,
onFinish: _pulseMessage
});
}
});
};
game.jumpMessageDisplayed = true;
// Create jump tutorial message as a sprite in center of screen
jumpMessage = new Container();
var jumpSprite = jumpMessage.attachAsset('tap_to_jump', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.0,
scaleY: 2.0
});
// Position the message in the center of the screen
jumpMessage.x = 2048 / 2;
jumpMessage.y = 2732 / 2;
// Add the message directly to game so it's centered on screen
game.addChild(jumpMessage);
_pulseMessage();
// Variable to store tutorial message reference
game.jumpTutorialMessage = jumpMessage;
// Create a function to check distance and remove the message
enemy.checkJumpTutorialDistance = function () {
var distance = Math.abs(this.x - player.x);
if (distance <= 150) {
game.removeChild(game.jumpTutorialMessage);
game.jumpTutorialShown = true;
// Remove the check function from enemy's update
this.checkJumpTutorialDistance = null;
}
};
// Modify the enemy's update method to include the distance check
originalUpdate = enemy.update;
enemy.update = function () {
originalUpdate.call(this);
if (this.checkJumpTutorialDistance) {
this.checkJumpTutorialDistance();
}
};
return 1; // break
}
}
},
enemy,
jumpMessage,
originalUpdate;
for (var t = 0; t < children.length; t++) {
if (_loop()) break;
}
}
// Check if score reaches 3000, increase game speed and missile spawn rate
if (LK.getScore() >= 20000) {
// Boost speed significantly at 20000 points
gameSpeed = 5 + Math.min(LK.getScore() / 10, 12); // Cap at max 17 speed
} else if (LK.getScore() >= 3000) {
// Boost speed further when score hits 3000
gameSpeed = 5 + Math.min(LK.getScore() / 10, 8); // Cap at max 13 speed
}
// Update scrolling backgrounds
for (var i = 0; i < backgrounds.length; i++) {
backgrounds[i].update();
}
// Update street backgrounds
for (var i = 0; i < streetBackgrounds.length; i++) {
streetBackgrounds[i].update();
}
player.update();
// Spawn enemies (wait 5 seconds at the start - 300 frames at 60fps)
if (LK.ticks > 300) {
// Determine enemy spawn interval based on score
var enemySpawnInterval = 180; // Default: every 3 seconds
if (LK.getScore() >= 20000) {
enemySpawnInterval = 90; // Every 1.5 seconds at 20000+
} else if (LK.getScore() >= 3000) {
enemySpawnInterval = 150; // Every 2.5 seconds at 3000+
}
if (LK.ticks % enemySpawnInterval === 0) {
// Spawn enemy
var enemy = new Enemy();
enemy.x = 2048; // Start at right edge of screen
enemy.y = floorLevel; // Position at floor level
enemy.lastX = enemy.x; // Initialize lastX
game.addChild(enemy);
}
}
// Spawn missiles when player score reaches 1500
if (LK.getScore() >= 1500) {
// Determine spawn interval based on score thresholds
var missileSpawnInterval = 120; // Default interval
if (LK.getScore() >= 20000) {
missileSpawnInterval = 30; // Four times as often at 20000+
} else if (LK.getScore() >= 3000) {
missileSpawnInterval = 60; // Twice as often at 3000+
}
if (LK.ticks % missileSpawnInterval === 0) {
// Calculate a y-position for the missile
// Should be at least 500px above player but no higher than max jump height
var playerMaxHeight = floorLevel - player.jumpHeight * 6; // Approximate max jump height
var minMissileHeight = floorLevel - 2800; // At least 500px above player (increased from 300px)
var maxMissileHeight = floorLevel - 800; // Don't go too high
// Calculate the valid range for missile spawn positions
var validRangeMin = Math.max(maxMissileHeight, minMissileHeight);
var validRangeMax = floorLevel - 50;
// Generate a random position within this range
var missileY = validRangeMin + Math.random() * (validRangeMax - validRangeMin);
// Ensure missile is within screen bounds
missileY = Math.max(100, Math.min(missileY, floorLevel - 50));
// Create missile with warning
var missile = new Missile();
missile.init(missileY);
game.addChild(missile);
}
}
// Update all enemies and bombs
var children = game.children.slice();
for (var i = 0; i < children.length; i++) {
if (children[i] instanceof Enemy) {
children[i].update();
} else if (children[i] instanceof Bomb) {
children[i].update();
} else if (children[i] instanceof Missile) {
children[i].update();
} else if (children[i].update && children[i].scrollSpeed !== undefined) {
// Update any container with scrollSpeed (like our player hit explosions)
children[i].update();
}
}
// Animate score count up
var actualScore = LK.getScore();
if (displayedScore < actualScore) {
// Increment displayed score by a percentage of the difference
displayedScore = Math.min(displayedScore + Math.ceil((actualScore - displayedScore) * 0.1), actualScore);
scoreText.setText(displayedScore.toString());
}
// Lives text display has been removed
// Update UI panel is no longer needed - using player's bomb display
// Play background music if not already playing
if (LK.ticks === 1) {
LK.playMusic('MeanStreets');
}
};
// Handle player jump
game.down = function (x, y, obj) {
player.jump();
};
cyberpunk pixel art asphalt street. In-Game asset. 2d. High contrast. No shadows, street debris
cyberpunk pixel art asphalt street. In-Game asset. 2d. High contrast. No shadows, street debris
digital explosion, burnt orange neon blue, pixels, sparks. In-Game asset. 2d. High contrast. No shadows
digital explosion. squares. pixels. chaos. neon. sparks. In-Game asset. 2d. High contrast. No shadows
Make it pixel art style
Make it pixel art style
Make this pixel art style
Make this pixel art style and give it a bit of a tail on the top as if it is flying downwards.
Make this look like pixel art
Neon pink heart. Pixel art style. Slight neon blue outer glow. In-Game asset. 2d. High contrast. No shadows
Pixel font in yellow that says "Tap to Jump". In-Game asset. 2d. High contrast. No shadows
Pixel font in yellow that says "Tap Again to Attack". In-Game asset. 2d. High contrast. No shadows