User prompt
Add the floor back
User prompt
Stretch the floor to the bottom of the screen
User prompt
Add a scrolling backround
User prompt
Delete the doted line
User prompt
Make a dotted line between the two places you can tap
User prompt
Make it so that the longer you play for, the more enemies spawn
User prompt
Make it so that the more force you get the faster it goes
User prompt
Change the name of the shields counter to lives
User prompt
Move the force token counter to below the distance counter
User prompt
Add a force token counter
User prompt
Make the x wing’s lasers red and make the other lasers straight ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Fix it
User prompt
Please fix the bug: 'TypeError: null is not an object (evaluating 'obstacle.graphic.height')' in or related to this line: 'obstacle.y = Math.random() < 0.5 ? game.trenchTop + obstacle.graphic.height / 2 : game.trenchBottom - obstacle.graphic.height / 2;' Line Number: 360
User prompt
Add the assets
User prompt
Add the assets
User prompt
Add the assets
User prompt
Implement the gameplay to the assets ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Rebel Runner
Initial prompt
Star Wars game that only requires one input
/**** * 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();
};