/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Background = Container.expand(function () { var self = Container.call(this); // Properties for the stars self.stars = []; self.starCount = 100; self.speed = 0; self.init = function (speed) { self.speed = speed; // Create stars for (var i = 0; i < self.starCount; i++) { var star = new Container(); // Create star graphic var starGraphic = star.attachAsset('engineTrail', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.5 + Math.random() * 0.5, scaleY: 0.5 + Math.random() * 0.5, alpha: 0.3 + Math.random() * 0.7 }); // Position star randomly star.x = Math.random() * 2048; star.y = Math.random() * 2732; // Star speed depends on size (parallax effect) star.speed = 0.5 + starGraphic.scaleX; // White color tint for stars starGraphic.tint = 0xFFFFFF; // Add star to container self.addChild(star); self.stars.push(star); } }; self.update = function () { // Update star positions for (var i = 0; i < self.stars.length; i++) { var star = self.stars[i]; // Move star based on its speed star.x -= star.speed * self.speed; // Wrap stars when they go off screen if (star.x < -20) { star.x = 2048 + 20; star.y = Math.random() * 2732; } } }; return self; }); var Collectible = Container.expand(function () { var self = Container.call(this); // Type determines appearance and effect self.type = ''; self.speed = 0; self.lastX = 0; self.lastIsColliding = false; self.graphic = null; self.init = function (type, speed) { self.type = type; self.speed = speed; // Create different visuals based on type if (type === 'forceToken') { self.graphic = self.attachAsset('forceToken', { anchorX: 0.5, anchorY: 0.5 }); } else if (type === 'shield') { self.graphic = self.attachAsset('shield', { anchorX: 0.5, anchorY: 0.5 }); } // Record initial position self.lastX = self.x; }; // Float animation self.update = function () { // Save last position self.lastX = self.x; // Move left self.x -= self.speed; // Floating animation self.y += Math.sin(LK.ticks * 0.1) * 0.5; self.graphic.rotation += 0.02; }; return self; }); // DottedLine class removed var HeroLaser = Container.expand(function () { var self = Container.call(this); self.speed = 0; self.lastX = 0; self.lastY = 0; self.lastIsColliding = false; // Create laser graphic var laserGraphic = self.attachAsset('laser', { anchorX: 0.5, anchorY: 0.5 }); // Red laser for hero laserGraphic.tint = 0xFF0000; self.init = function (speed) { self.speed = speed; self.lastX = self.x; self.lastY = self.y; }; self.update = function () { // Save last position self.lastX = self.x; self.lastY = self.y; // Move right (player fires forward) self.x += self.speed; }; return self; }); var Obstacle = Container.expand(function () { var self = Container.call(this); // Type determines appearance and behavior self.type = ''; self.speed = 0; self.lastX = 0; self.lastY = 0; self.lastIsColliding = false; self.graphic = null; self.init = function (type, speed) { self.type = type; self.speed = speed; // Create different visuals based on type if (type === 'wall') { self.graphic = self.attachAsset('obstacle', { anchorX: 0.5, anchorY: 0.5 }); } else if (type === 'laser') { self.graphic = self.attachAsset('laser', { anchorX: 0.5, anchorY: 0.5 }); // No rotation - lasers are straight } else if (type === 'tieFighter') { self.graphic = self.attachAsset('tieFighter', { anchorX: 0.5, anchorY: 0.5 }); } // Record initial position self.lastX = self.x; self.lastY = self.y; }; // Move obstacle self.update = function () { // Save last position self.lastX = self.x; self.lastY = self.y; // Move left self.x -= self.speed; // Special movement patterns based on type if (self.type === 'tieFighter') { // TIE fighters move in a slight wave pattern self.y += Math.sin(self.x * 0.01) * 2; } }; return self; }); var Player = Container.expand(function () { var self = Container.call(this); // Create X-wing graphic var xwingGraphic = self.attachAsset('xwing', { anchorX: 0.5, anchorY: 0.5 }); // Physics properties self.velocityY = 0; self.gravity = 0.5; self.jumpForce = -12; self.maxVelocity = 15; self.isJumping = false; self.isShielded = false; self.shieldTimer = 0; // Track last position for collision detection self.lastY = 0; self.lastIsColliding = false; // Shield visual effect var shieldGraphic = self.attachAsset('shield', { anchorX: 0.5, anchorY: 0.5, alpha: 0 }); // Laser firing properties self.canFire = true; self.fireDelay = 30; // frames between shots self.fireTimer = 0; // Fire method for shooting lasers self.fire = function () { if (!self.canFire) return; // Create hero laser var laser = new HeroLaser(); laser.x = self.x + 60; laser.y = self.y; laser.init(gameSpeed + 10); game.addChild(laser); heroLasers.push(laser); // Play laser sound LK.getSound('laser').play(); // Set cooldown self.canFire = false; self.fireTimer = self.fireDelay; }; // Apply physics and update position self.update = function () { // Save last position for collision detection self.lastY = self.y; // Apply gravity self.velocityY += self.gravity; // Cap terminal velocity if (self.velocityY > self.maxVelocity) { self.velocityY = self.maxVelocity; } // Update position self.y += self.velocityY; // Floor boundary if (self.y > game.trenchBottom - xwingGraphic.height / 2) { self.y = game.trenchBottom - xwingGraphic.height / 2; self.velocityY = 0; self.isJumping = false; } // Ceiling boundary if (self.y < game.trenchTop + xwingGraphic.height / 2) { self.y = game.trenchTop + xwingGraphic.height / 2; self.velocityY = 1; } // Visual tilt based on velocity (diving down = nose down, rising up = nose up) xwingGraphic.rotation = self.velocityY * 0.05; // Shield timer if (self.isShielded) { self.shieldTimer--; if (self.shieldTimer <= 0) { self.isShielded = false; shieldGraphic.alpha = 0; } } // Weapon cooldown if (!self.canFire) { self.fireTimer--; if (self.fireTimer <= 0) { self.canFire = true; } } }; // Jump method when screen is tapped self.jump = function () { self.velocityY = self.jumpForce; self.isJumping = true; }; // Activate shield power-up self.activateShield = function (duration) { self.isShielded = true; self.shieldTimer = duration; shieldGraphic.alpha = 0.6; }; return self; }); var TrenchSegment = Container.expand(function () { var self = Container.call(this); self.speed = 0; self.lastX = 0; self.init = function (speed) { self.speed = speed; // Add walls var topWall = self.attachAsset('trenchWall', { anchorX: 0, anchorY: 0 }); var bottomWall = self.attachAsset('trenchWall', { anchorX: 0, anchorY: 1 }); // Position walls topWall.y = game.trenchTop; bottomWall.y = game.trenchBottom; self.lastX = self.x; }; self.update = function () { self.lastX = self.x; self.x -= self.speed; }; return self; }); /**** * Initialize Game ****/ // Game constants var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // Star Wars themed game assets // Game constants //Minimalistic tween library which should be used for animations over time // Initialize game assets var GAME_SPEED_INITIAL = 8; var GAME_SPEED_INCREMENT = 0.001; var GAME_SPEED_FORCE_BOOST = 0.5; // Speed boost per force token var OBSTACLE_SPAWN_RATE_INITIAL = 120; // frames var OBSTACLE_SPAWN_RATE_MIN = 40; // minimum frames between obstacles var OBSTACLE_SPAWN_RATE_DECREASE = 0.05; // how quickly spawn rate decreases var COLLECTIBLE_SPAWN_RATE = 180; // frames var TRENCH_WIDTH = 1600; var TRENCH_HEIGHT = 1400; var ENEMY_INCREASE_TIME = 1000; // time before adding more enemy types // Game variables var gameSpeed = GAME_SPEED_INITIAL; var baseGameSpeed = GAME_SPEED_INITIAL; // Base speed without force boost var obstacleSpawnRate = OBSTACLE_SPAWN_RATE_INITIAL; var obstacleTimer = 0; var collectibleTimer = 0; var distance = 0; var lives = 3; var forceTokens = 0; var gameTime = 0; // track game time in frames var enemyDensity = 1; // multiplier for enemy spawn chance var maxEnemiesAtOnce = 3; // maximum number of enemies that can spawn at once var background = null; var trenchSegments = []; var obstacles = []; var collectibles = []; var heroLasers = []; var player = null; var isGameActive = true; // Set up trench boundaries game.trenchTop = (2732 - TRENCH_HEIGHT) / 2; game.trenchBottom = game.trenchTop + TRENCH_HEIGHT; game.trenchLeft = (2048 - TRENCH_WIDTH) / 2; game.trenchRight = game.trenchLeft + TRENCH_WIDTH; // Create background with stars background = new Background(); background.init(1); game.addChild(background); // Initialize trench floor var trenchFloor = LK.getAsset('trenchFloor', { anchorX: 0.5, anchorY: 0 }); trenchFloor.x = 2048 / 2; trenchFloor.y = game.trenchBottom; trenchFloor.height = 2732 - game.trenchBottom; // Stretch height to fill from trench bottom to screen bottom game.addChild(trenchFloor); // Create GUI elements var distanceText = new Text2('DISTANCE: 0', { size: 70, fill: 0xFFFFFF }); distanceText.anchor.set(0, 0); distanceText.x = 120; distanceText.y = 50; LK.gui.topLeft.addChild(distanceText); var livesText = new Text2('LIVES: 3', { size: 70, fill: 0xFFFFFF }); livesText.anchor.set(1, 0); livesText.x = -120; livesText.y = 50; LK.gui.topRight.addChild(livesText); var forceTokenText = new Text2('FORCE: 0', { size: 70, fill: 0xFFFFFF }); forceTokenText.anchor.set(0, 0); forceTokenText.x = 120; forceTokenText.y = 130; LK.gui.topLeft.addChild(forceTokenText); // Add text labels for tap areas var fireText = new Text2('TAP TO FIRE', { size: 60, fill: 0xFFFFFF }); fireText.anchor.set(0.5, 0.5); fireText.x = 2048 / 4; fireText.y = 2732 / 4; LK.gui.center.addChild(fireText); var jumpText = new Text2('TAP TO JUMP', { size: 60, fill: 0xFFFFFF }); jumpText.anchor.set(0.5, 0.5); jumpText.x = 2048 / 4; jumpText.y = 2732 - 2732 / 4; LK.gui.center.addChild(jumpText); // Dotted line removed // Create player (X-wing) player = new Player(); player.x = 400; player.y = (game.trenchTop + game.trenchBottom) / 2; game.addChild(player); // Initialize trench segments function createInitialTrench() { var segments = 3; // Number of initial trench segments var segmentWidth = 2048; // Width of each segment for (var i = 0; i < segments; i++) { var segment = new TrenchSegment(); segment.x = i * segmentWidth; segment.init(gameSpeed); trenchSegments.push(segment); game.addChild(segment); } } // Create a new obstacle function spawnObstacle() { // Determine how many obstacles to spawn based on game progression var spawnCount = Math.min(maxEnemiesAtOnce, 1 + Math.floor(enemyDensity * Math.random())); // Create multiple obstacles if needed for (var i = 0; i < spawnCount; i++) { var obstacle = new Obstacle(); // Random obstacle type var types = ['wall', 'laser', 'tieFighter']; var type = types[Math.floor(Math.random() * types.length)]; // Initialize the obstacle first to create the graphic obstacle.init(type, gameSpeed); // Position at right side of screen obstacle.x = 2048 + 100 + i * 150; // Space out multiple obstacles // Random vertical position within trench if (type === 'wall' && obstacle.graphic) { // Walls randomly appear at top or bottom obstacle.y = Math.random() < 0.5 ? game.trenchTop + obstacle.graphic.height / 2 : game.trenchBottom - obstacle.graphic.height / 2; } else { // Other obstacles can appear anywhere in trench obstacle.y = game.trenchTop + 150 + Math.random() * (TRENCH_HEIGHT - 300); } obstacles.push(obstacle); game.addChild(obstacle); } } // Create a new collectible function spawnCollectible() { var collectible = new Collectible(); // Random collectible type (force tokens more common than shields) var type = Math.random() < 0.8 ? 'forceToken' : 'shield'; // Position at right side of screen collectible.x = 2048 + 100; // Random vertical position within trench collectible.y = game.trenchTop + 150 + Math.random() * (TRENCH_HEIGHT - 300); collectible.init(type, gameSpeed); collectibles.push(collectible); game.addChild(collectible); } // Update score and distance display function updateHUD() { var distanceKm = Math.floor(distance / 100); distanceText.setText('DISTANCE: ' + distanceKm + ' KM'); livesText.setText('LIVES: ' + lives); forceTokenText.setText('FORCE: ' + forceTokens); } // Handle player hit function playerHit() { if (player.isShielded) return; // Shield protects from damage lives--; livesText.setText('LIVES: ' + lives); // Flash player to indicate damage LK.effects.flashObject(player, 0xFF0000, 500); if (lives <= 0) { gameOver(); } } // Game over function gameOver() { isGameActive = false; LK.setScore(Math.floor(distance / 10)); LK.showGameOver(); } // Handle screen tap/click input game.down = function (x, y, obj) { if (isGameActive) { // Jump if tap is in the bottom half of the screen if (y > 2732 / 2) { player.jump(); } else { // Fire if tap is in the top half player.fire(); } } }; // Play theme music with fade in LK.playMusic('theme', { fade: { start: 0, end: 0.4, duration: 2000 } }); // Main game update loop createInitialTrench(); game.update = function () { if (!isGameActive) return; // Increase game time counter gameTime++; // Increase base game speed over time baseGameSpeed += GAME_SPEED_INCREMENT; // Calculate total game speed (base + force boost) gameSpeed = baseGameSpeed + forceTokens * GAME_SPEED_FORCE_BOOST; // Update background stars background.speed = gameSpeed / 5; background.update(); // Update distance traveled distance += gameSpeed; // Increase enemy density based on game time if (gameTime % ENEMY_INCREASE_TIME === 0) { enemyDensity = Math.min(5, enemyDensity + 0.1); maxEnemiesAtOnce = Math.min(5, maxEnemiesAtOnce + 0.1); } // Update player player.update(); // Manage trench segments for (var i = trenchSegments.length - 1; i >= 0; i--) { var segment = trenchSegments[i]; segment.speed = gameSpeed; segment.update(); // If segment moves off screen, remove and create new one at the end if (segment.x + 2048 < 0) { segment.destroy(); trenchSegments.splice(i, 1); var newSegment = new TrenchSegment(); newSegment.x = trenchSegments[trenchSegments.length - 1].x + 2048; newSegment.init(gameSpeed); trenchSegments.push(newSegment); game.addChild(newSegment); } } // Manage obstacles obstacleTimer++; if (obstacleTimer >= obstacleSpawnRate) { spawnObstacle(); obstacleTimer = 0; // Calculate spawn rate based on game time AND distance // This makes enemies spawn more frequently the longer you play obstacleSpawnRate = Math.max(OBSTACLE_SPAWN_RATE_MIN, OBSTACLE_SPAWN_RATE_INITIAL - Math.floor(distance / 1000) - gameTime * OBSTACLE_SPAWN_RATE_DECREASE / 1000); } for (var j = obstacles.length - 1; j >= 0; j--) { var obstacle = obstacles[j]; obstacle.speed = gameSpeed; obstacle.update(); // Check collision with player var isCollidingNow = obstacle.intersects(player); if (!obstacle.lastIsColliding && isCollidingNow) { playerHit(); LK.getSound('explosion').play(); // Remove obstacle after collision obstacle.destroy(); obstacles.splice(j, 1); continue; } obstacle.lastIsColliding = isCollidingNow; // Remove if off screen if (obstacle.x + 100 < 0) { obstacle.destroy(); obstacles.splice(j, 1); } } // Manage collectibles collectibleTimer++; if (collectibleTimer >= COLLECTIBLE_SPAWN_RATE) { spawnCollectible(); collectibleTimer = 0; } for (var k = collectibles.length - 1; k >= 0; k--) { var collectible = collectibles[k]; collectible.speed = gameSpeed; collectible.update(); // Check collision with player var isCollectingNow = collectible.intersects(player); if (!collectible.lastIsColliding && isCollectingNow) { // Apply effect based on type if (collectible.type === 'forceToken') { // Increase score LK.setScore(LK.getScore() + 10); // Increment force tokens forceTokens++; // Recalculate game speed with new force token count gameSpeed = baseGameSpeed + forceTokens * GAME_SPEED_FORCE_BOOST; // Update force tokens display forceTokenText.setText('FORCE: ' + forceTokens); tween(collectible.graphic, { alpha: 0 }, { duration: 300 }); LK.getSound('collect').play(); } else if (collectible.type === 'shield') { // Activate shield player.activateShield(600); // Shield lasts 10 seconds (600 frames) tween(collectible.graphic, { alpha: 0 }, { duration: 300 }); LK.getSound('collect').play(); } // Remove collectible collectible.destroy(); collectibles.splice(k, 1); continue; } collectible.lastIsColliding = isCollectingNow; // Remove if off screen if (collectible.x + 50 < 0) { collectible.destroy(); collectibles.splice(k, 1); } } // Manage hero lasers for (var l = heroLasers.length - 1; l >= 0; l--) { var heroLaser = heroLasers[l]; heroLaser.update(); // Check collision with obstacles var hitObstacle = false; for (var m = obstacles.length - 1; m >= 0; m--) { var targetObstacle = obstacles[m]; if (heroLaser.intersects(targetObstacle) && targetObstacle.type === 'tieFighter') { // Destroy TIE fighter tween(targetObstacle, { alpha: 0 }, { duration: 300 }); // Add explosion effect var explosion = LK.getAsset('explosion', { anchorX: 0.5, anchorY: 0.5, x: targetObstacle.x, y: targetObstacle.y }); game.addChild(explosion); // Remove after animation tween(explosion, { alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 500, onComplete: function onComplete() { game.removeChild(explosion); } }); // Play explosion sound LK.getSound('explosion').play(); // Increase score LK.setScore(LK.getScore() + 50); // Remove obstacle targetObstacle.destroy(); obstacles.splice(m, 1); // Flag laser hit hitObstacle = true; break; } } // Remove laser if it hit something or went off screen if (hitObstacle || heroLaser.x > 2048 + 100) { heroLaser.destroy(); heroLasers.splice(l, 1); } } // Dotted line update removed // Update HUD updateHUD(); };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Background = Container.expand(function () {
var self = Container.call(this);
// Properties for the stars
self.stars = [];
self.starCount = 100;
self.speed = 0;
self.init = function (speed) {
self.speed = speed;
// Create stars
for (var i = 0; i < self.starCount; i++) {
var star = new Container();
// Create star graphic
var starGraphic = star.attachAsset('engineTrail', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5 + Math.random() * 0.5,
scaleY: 0.5 + Math.random() * 0.5,
alpha: 0.3 + Math.random() * 0.7
});
// Position star randomly
star.x = Math.random() * 2048;
star.y = Math.random() * 2732;
// Star speed depends on size (parallax effect)
star.speed = 0.5 + starGraphic.scaleX;
// White color tint for stars
starGraphic.tint = 0xFFFFFF;
// Add star to container
self.addChild(star);
self.stars.push(star);
}
};
self.update = function () {
// Update star positions
for (var i = 0; i < self.stars.length; i++) {
var star = self.stars[i];
// Move star based on its speed
star.x -= star.speed * self.speed;
// Wrap stars when they go off screen
if (star.x < -20) {
star.x = 2048 + 20;
star.y = Math.random() * 2732;
}
}
};
return self;
});
var Collectible = Container.expand(function () {
var self = Container.call(this);
// Type determines appearance and effect
self.type = '';
self.speed = 0;
self.lastX = 0;
self.lastIsColliding = false;
self.graphic = null;
self.init = function (type, speed) {
self.type = type;
self.speed = speed;
// Create different visuals based on type
if (type === 'forceToken') {
self.graphic = self.attachAsset('forceToken', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (type === 'shield') {
self.graphic = self.attachAsset('shield', {
anchorX: 0.5,
anchorY: 0.5
});
}
// Record initial position
self.lastX = self.x;
};
// Float animation
self.update = function () {
// Save last position
self.lastX = self.x;
// Move left
self.x -= self.speed;
// Floating animation
self.y += Math.sin(LK.ticks * 0.1) * 0.5;
self.graphic.rotation += 0.02;
};
return self;
});
// DottedLine class removed
var HeroLaser = Container.expand(function () {
var self = Container.call(this);
self.speed = 0;
self.lastX = 0;
self.lastY = 0;
self.lastIsColliding = false;
// Create laser graphic
var laserGraphic = self.attachAsset('laser', {
anchorX: 0.5,
anchorY: 0.5
});
// Red laser for hero
laserGraphic.tint = 0xFF0000;
self.init = function (speed) {
self.speed = speed;
self.lastX = self.x;
self.lastY = self.y;
};
self.update = function () {
// Save last position
self.lastX = self.x;
self.lastY = self.y;
// Move right (player fires forward)
self.x += self.speed;
};
return self;
});
var Obstacle = Container.expand(function () {
var self = Container.call(this);
// Type determines appearance and behavior
self.type = '';
self.speed = 0;
self.lastX = 0;
self.lastY = 0;
self.lastIsColliding = false;
self.graphic = null;
self.init = function (type, speed) {
self.type = type;
self.speed = speed;
// Create different visuals based on type
if (type === 'wall') {
self.graphic = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (type === 'laser') {
self.graphic = self.attachAsset('laser', {
anchorX: 0.5,
anchorY: 0.5
});
// No rotation - lasers are straight
} else if (type === 'tieFighter') {
self.graphic = self.attachAsset('tieFighter', {
anchorX: 0.5,
anchorY: 0.5
});
}
// Record initial position
self.lastX = self.x;
self.lastY = self.y;
};
// Move obstacle
self.update = function () {
// Save last position
self.lastX = self.x;
self.lastY = self.y;
// Move left
self.x -= self.speed;
// Special movement patterns based on type
if (self.type === 'tieFighter') {
// TIE fighters move in a slight wave pattern
self.y += Math.sin(self.x * 0.01) * 2;
}
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
// Create X-wing graphic
var xwingGraphic = self.attachAsset('xwing', {
anchorX: 0.5,
anchorY: 0.5
});
// Physics properties
self.velocityY = 0;
self.gravity = 0.5;
self.jumpForce = -12;
self.maxVelocity = 15;
self.isJumping = false;
self.isShielded = false;
self.shieldTimer = 0;
// Track last position for collision detection
self.lastY = 0;
self.lastIsColliding = false;
// Shield visual effect
var shieldGraphic = self.attachAsset('shield', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
// Laser firing properties
self.canFire = true;
self.fireDelay = 30; // frames between shots
self.fireTimer = 0;
// Fire method for shooting lasers
self.fire = function () {
if (!self.canFire) return;
// Create hero laser
var laser = new HeroLaser();
laser.x = self.x + 60;
laser.y = self.y;
laser.init(gameSpeed + 10);
game.addChild(laser);
heroLasers.push(laser);
// Play laser sound
LK.getSound('laser').play();
// Set cooldown
self.canFire = false;
self.fireTimer = self.fireDelay;
};
// Apply physics and update position
self.update = function () {
// Save last position for collision detection
self.lastY = self.y;
// Apply gravity
self.velocityY += self.gravity;
// Cap terminal velocity
if (self.velocityY > self.maxVelocity) {
self.velocityY = self.maxVelocity;
}
// Update position
self.y += self.velocityY;
// Floor boundary
if (self.y > game.trenchBottom - xwingGraphic.height / 2) {
self.y = game.trenchBottom - xwingGraphic.height / 2;
self.velocityY = 0;
self.isJumping = false;
}
// Ceiling boundary
if (self.y < game.trenchTop + xwingGraphic.height / 2) {
self.y = game.trenchTop + xwingGraphic.height / 2;
self.velocityY = 1;
}
// Visual tilt based on velocity (diving down = nose down, rising up = nose up)
xwingGraphic.rotation = self.velocityY * 0.05;
// Shield timer
if (self.isShielded) {
self.shieldTimer--;
if (self.shieldTimer <= 0) {
self.isShielded = false;
shieldGraphic.alpha = 0;
}
}
// Weapon cooldown
if (!self.canFire) {
self.fireTimer--;
if (self.fireTimer <= 0) {
self.canFire = true;
}
}
};
// Jump method when screen is tapped
self.jump = function () {
self.velocityY = self.jumpForce;
self.isJumping = true;
};
// Activate shield power-up
self.activateShield = function (duration) {
self.isShielded = true;
self.shieldTimer = duration;
shieldGraphic.alpha = 0.6;
};
return self;
});
var TrenchSegment = Container.expand(function () {
var self = Container.call(this);
self.speed = 0;
self.lastX = 0;
self.init = function (speed) {
self.speed = speed;
// Add walls
var topWall = self.attachAsset('trenchWall', {
anchorX: 0,
anchorY: 0
});
var bottomWall = self.attachAsset('trenchWall', {
anchorX: 0,
anchorY: 1
});
// Position walls
topWall.y = game.trenchTop;
bottomWall.y = game.trenchBottom;
self.lastX = self.x;
};
self.update = function () {
self.lastX = self.x;
self.x -= self.speed;
};
return self;
});
/****
* Initialize Game
****/
// Game constants
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Star Wars themed game assets
// Game constants
//Minimalistic tween library which should be used for animations over time
// Initialize game assets
var GAME_SPEED_INITIAL = 8;
var GAME_SPEED_INCREMENT = 0.001;
var GAME_SPEED_FORCE_BOOST = 0.5; // Speed boost per force token
var OBSTACLE_SPAWN_RATE_INITIAL = 120; // frames
var OBSTACLE_SPAWN_RATE_MIN = 40; // minimum frames between obstacles
var OBSTACLE_SPAWN_RATE_DECREASE = 0.05; // how quickly spawn rate decreases
var COLLECTIBLE_SPAWN_RATE = 180; // frames
var TRENCH_WIDTH = 1600;
var TRENCH_HEIGHT = 1400;
var ENEMY_INCREASE_TIME = 1000; // time before adding more enemy types
// Game variables
var gameSpeed = GAME_SPEED_INITIAL;
var baseGameSpeed = GAME_SPEED_INITIAL; // Base speed without force boost
var obstacleSpawnRate = OBSTACLE_SPAWN_RATE_INITIAL;
var obstacleTimer = 0;
var collectibleTimer = 0;
var distance = 0;
var lives = 3;
var forceTokens = 0;
var gameTime = 0; // track game time in frames
var enemyDensity = 1; // multiplier for enemy spawn chance
var maxEnemiesAtOnce = 3; // maximum number of enemies that can spawn at once
var background = null;
var trenchSegments = [];
var obstacles = [];
var collectibles = [];
var heroLasers = [];
var player = null;
var isGameActive = true;
// Set up trench boundaries
game.trenchTop = (2732 - TRENCH_HEIGHT) / 2;
game.trenchBottom = game.trenchTop + TRENCH_HEIGHT;
game.trenchLeft = (2048 - TRENCH_WIDTH) / 2;
game.trenchRight = game.trenchLeft + TRENCH_WIDTH;
// Create background with stars
background = new Background();
background.init(1);
game.addChild(background);
// Initialize trench floor
var trenchFloor = LK.getAsset('trenchFloor', {
anchorX: 0.5,
anchorY: 0
});
trenchFloor.x = 2048 / 2;
trenchFloor.y = game.trenchBottom;
trenchFloor.height = 2732 - game.trenchBottom; // Stretch height to fill from trench bottom to screen bottom
game.addChild(trenchFloor);
// Create GUI elements
var distanceText = new Text2('DISTANCE: 0', {
size: 70,
fill: 0xFFFFFF
});
distanceText.anchor.set(0, 0);
distanceText.x = 120;
distanceText.y = 50;
LK.gui.topLeft.addChild(distanceText);
var livesText = new Text2('LIVES: 3', {
size: 70,
fill: 0xFFFFFF
});
livesText.anchor.set(1, 0);
livesText.x = -120;
livesText.y = 50;
LK.gui.topRight.addChild(livesText);
var forceTokenText = new Text2('FORCE: 0', {
size: 70,
fill: 0xFFFFFF
});
forceTokenText.anchor.set(0, 0);
forceTokenText.x = 120;
forceTokenText.y = 130;
LK.gui.topLeft.addChild(forceTokenText);
// Add text labels for tap areas
var fireText = new Text2('TAP TO FIRE', {
size: 60,
fill: 0xFFFFFF
});
fireText.anchor.set(0.5, 0.5);
fireText.x = 2048 / 4;
fireText.y = 2732 / 4;
LK.gui.center.addChild(fireText);
var jumpText = new Text2('TAP TO JUMP', {
size: 60,
fill: 0xFFFFFF
});
jumpText.anchor.set(0.5, 0.5);
jumpText.x = 2048 / 4;
jumpText.y = 2732 - 2732 / 4;
LK.gui.center.addChild(jumpText);
// Dotted line removed
// Create player (X-wing)
player = new Player();
player.x = 400;
player.y = (game.trenchTop + game.trenchBottom) / 2;
game.addChild(player);
// Initialize trench segments
function createInitialTrench() {
var segments = 3; // Number of initial trench segments
var segmentWidth = 2048; // Width of each segment
for (var i = 0; i < segments; i++) {
var segment = new TrenchSegment();
segment.x = i * segmentWidth;
segment.init(gameSpeed);
trenchSegments.push(segment);
game.addChild(segment);
}
}
// Create a new obstacle
function spawnObstacle() {
// Determine how many obstacles to spawn based on game progression
var spawnCount = Math.min(maxEnemiesAtOnce, 1 + Math.floor(enemyDensity * Math.random()));
// Create multiple obstacles if needed
for (var i = 0; i < spawnCount; i++) {
var obstacle = new Obstacle();
// Random obstacle type
var types = ['wall', 'laser', 'tieFighter'];
var type = types[Math.floor(Math.random() * types.length)];
// Initialize the obstacle first to create the graphic
obstacle.init(type, gameSpeed);
// Position at right side of screen
obstacle.x = 2048 + 100 + i * 150; // Space out multiple obstacles
// Random vertical position within trench
if (type === 'wall' && obstacle.graphic) {
// Walls randomly appear at top or bottom
obstacle.y = Math.random() < 0.5 ? game.trenchTop + obstacle.graphic.height / 2 : game.trenchBottom - obstacle.graphic.height / 2;
} else {
// Other obstacles can appear anywhere in trench
obstacle.y = game.trenchTop + 150 + Math.random() * (TRENCH_HEIGHT - 300);
}
obstacles.push(obstacle);
game.addChild(obstacle);
}
}
// Create a new collectible
function spawnCollectible() {
var collectible = new Collectible();
// Random collectible type (force tokens more common than shields)
var type = Math.random() < 0.8 ? 'forceToken' : 'shield';
// Position at right side of screen
collectible.x = 2048 + 100;
// Random vertical position within trench
collectible.y = game.trenchTop + 150 + Math.random() * (TRENCH_HEIGHT - 300);
collectible.init(type, gameSpeed);
collectibles.push(collectible);
game.addChild(collectible);
}
// Update score and distance display
function updateHUD() {
var distanceKm = Math.floor(distance / 100);
distanceText.setText('DISTANCE: ' + distanceKm + ' KM');
livesText.setText('LIVES: ' + lives);
forceTokenText.setText('FORCE: ' + forceTokens);
}
// Handle player hit
function playerHit() {
if (player.isShielded) return; // Shield protects from damage
lives--;
livesText.setText('LIVES: ' + lives);
// Flash player to indicate damage
LK.effects.flashObject(player, 0xFF0000, 500);
if (lives <= 0) {
gameOver();
}
}
// Game over
function gameOver() {
isGameActive = false;
LK.setScore(Math.floor(distance / 10));
LK.showGameOver();
}
// Handle screen tap/click input
game.down = function (x, y, obj) {
if (isGameActive) {
// Jump if tap is in the bottom half of the screen
if (y > 2732 / 2) {
player.jump();
} else {
// Fire if tap is in the top half
player.fire();
}
}
};
// Play theme music with fade in
LK.playMusic('theme', {
fade: {
start: 0,
end: 0.4,
duration: 2000
}
});
// Main game update loop
createInitialTrench();
game.update = function () {
if (!isGameActive) return;
// Increase game time counter
gameTime++;
// Increase base game speed over time
baseGameSpeed += GAME_SPEED_INCREMENT;
// Calculate total game speed (base + force boost)
gameSpeed = baseGameSpeed + forceTokens * GAME_SPEED_FORCE_BOOST;
// Update background stars
background.speed = gameSpeed / 5;
background.update();
// Update distance traveled
distance += gameSpeed;
// Increase enemy density based on game time
if (gameTime % ENEMY_INCREASE_TIME === 0) {
enemyDensity = Math.min(5, enemyDensity + 0.1);
maxEnemiesAtOnce = Math.min(5, maxEnemiesAtOnce + 0.1);
}
// Update player
player.update();
// Manage trench segments
for (var i = trenchSegments.length - 1; i >= 0; i--) {
var segment = trenchSegments[i];
segment.speed = gameSpeed;
segment.update();
// If segment moves off screen, remove and create new one at the end
if (segment.x + 2048 < 0) {
segment.destroy();
trenchSegments.splice(i, 1);
var newSegment = new TrenchSegment();
newSegment.x = trenchSegments[trenchSegments.length - 1].x + 2048;
newSegment.init(gameSpeed);
trenchSegments.push(newSegment);
game.addChild(newSegment);
}
}
// Manage obstacles
obstacleTimer++;
if (obstacleTimer >= obstacleSpawnRate) {
spawnObstacle();
obstacleTimer = 0;
// Calculate spawn rate based on game time AND distance
// This makes enemies spawn more frequently the longer you play
obstacleSpawnRate = Math.max(OBSTACLE_SPAWN_RATE_MIN, OBSTACLE_SPAWN_RATE_INITIAL - Math.floor(distance / 1000) - gameTime * OBSTACLE_SPAWN_RATE_DECREASE / 1000);
}
for (var j = obstacles.length - 1; j >= 0; j--) {
var obstacle = obstacles[j];
obstacle.speed = gameSpeed;
obstacle.update();
// Check collision with player
var isCollidingNow = obstacle.intersects(player);
if (!obstacle.lastIsColliding && isCollidingNow) {
playerHit();
LK.getSound('explosion').play();
// Remove obstacle after collision
obstacle.destroy();
obstacles.splice(j, 1);
continue;
}
obstacle.lastIsColliding = isCollidingNow;
// Remove if off screen
if (obstacle.x + 100 < 0) {
obstacle.destroy();
obstacles.splice(j, 1);
}
}
// Manage collectibles
collectibleTimer++;
if (collectibleTimer >= COLLECTIBLE_SPAWN_RATE) {
spawnCollectible();
collectibleTimer = 0;
}
for (var k = collectibles.length - 1; k >= 0; k--) {
var collectible = collectibles[k];
collectible.speed = gameSpeed;
collectible.update();
// Check collision with player
var isCollectingNow = collectible.intersects(player);
if (!collectible.lastIsColliding && isCollectingNow) {
// Apply effect based on type
if (collectible.type === 'forceToken') {
// Increase score
LK.setScore(LK.getScore() + 10);
// Increment force tokens
forceTokens++;
// Recalculate game speed with new force token count
gameSpeed = baseGameSpeed + forceTokens * GAME_SPEED_FORCE_BOOST;
// Update force tokens display
forceTokenText.setText('FORCE: ' + forceTokens);
tween(collectible.graphic, {
alpha: 0
}, {
duration: 300
});
LK.getSound('collect').play();
} else if (collectible.type === 'shield') {
// Activate shield
player.activateShield(600); // Shield lasts 10 seconds (600 frames)
tween(collectible.graphic, {
alpha: 0
}, {
duration: 300
});
LK.getSound('collect').play();
}
// Remove collectible
collectible.destroy();
collectibles.splice(k, 1);
continue;
}
collectible.lastIsColliding = isCollectingNow;
// Remove if off screen
if (collectible.x + 50 < 0) {
collectible.destroy();
collectibles.splice(k, 1);
}
}
// Manage hero lasers
for (var l = heroLasers.length - 1; l >= 0; l--) {
var heroLaser = heroLasers[l];
heroLaser.update();
// Check collision with obstacles
var hitObstacle = false;
for (var m = obstacles.length - 1; m >= 0; m--) {
var targetObstacle = obstacles[m];
if (heroLaser.intersects(targetObstacle) && targetObstacle.type === 'tieFighter') {
// Destroy TIE fighter
tween(targetObstacle, {
alpha: 0
}, {
duration: 300
});
// Add explosion effect
var explosion = LK.getAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
x: targetObstacle.x,
y: targetObstacle.y
});
game.addChild(explosion);
// Remove after animation
tween(explosion, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 500,
onComplete: function onComplete() {
game.removeChild(explosion);
}
});
// Play explosion sound
LK.getSound('explosion').play();
// Increase score
LK.setScore(LK.getScore() + 50);
// Remove obstacle
targetObstacle.destroy();
obstacles.splice(m, 1);
// Flag laser hit
hitObstacle = true;
break;
}
}
// Remove laser if it hit something or went off screen
if (hitObstacle || heroLaser.x > 2048 + 100) {
heroLaser.destroy();
heroLasers.splice(l, 1);
}
}
// Dotted line update removed
// Update HUD
updateHUD();
};