/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var Asteroid = Container.expand(function (size) { var self = Container.call(this); // Size can be 0 (small), 1 (medium), or 2 (large) self.size = size || 2; // Create a container for the asteroid var asteroidGraphics = new Container(); self.addChild(asteroidGraphics); // Determine asteroid size var radius; if (self.size === 0) { radius = 16; // small } else if (self.size === 1) { radius = 32; // medium } else { radius = 64; // large } // Create a pixel art circle (asteroid) var pixelSize = 4; // Size of each "pixel" var pixelGap = 1; // Gap between pixels for hollow effect var steps = 16; // Number of points around the circle // Draw a circle of pixels for (var i = 0; i < steps; i++) { var angle = i / steps * Math.PI * 2; var px = Math.cos(angle) * radius; var py = Math.sin(angle) * radius; var pixel = LK.getAsset('asteroidLarge', { anchorX: 0.5, anchorY: 0.5, width: pixelSize - pixelGap, height: pixelSize - pixelGap, x: px, y: py }); asteroidGraphics.addChild(pixel); } // Random velocity var speed = (3 - self.size) * 0.8 + 0.5; // Smaller asteroids move faster var angle = Math.random() * Math.PI * 2; self.velocity = { x: Math.cos(angle) * speed, y: Math.sin(angle) * speed }; // Random rotation self.rotationSpeed = (Math.random() - 0.5) * 0.05; self.update = function () { // Move self.x += self.velocity.x; self.y += self.velocity.y; // Rotate asteroidGraphics.rotation += self.rotationSpeed; // Wrap around screen edges if (self.x < -50) { self.x = 2098; } if (self.x > 2098) { self.x = -50; } if (self.y < -50) { self.y = 2782; } if (self.y > 2782) { self.y = -50; } }; self.getPoints = function () { return (3 - self.size) * 100; // Small: 300, Medium: 200, Large: 100 }; return self; }); var Bullet = Container.expand(function () { var self = Container.call(this); // Create a container for pixel art bullet var bulletGraphics = new Container(); self.addChild(bulletGraphics); // Create a small pixel art square (bullet) var bulletSize = 8; var pixelSize = 2; // Size of each "pixel" var pixelGap = 1; // Gap between pixels for hollow effect // Draw the outline of the square var points = [{ x: -bulletSize / 2, y: -bulletSize / 2 }, // Top left { x: bulletSize / 2, y: -bulletSize / 2 }, // Top right { x: bulletSize / 2, y: bulletSize / 2 }, // Bottom right { x: -bulletSize / 2, y: bulletSize / 2 } // Bottom left ]; // Draw the square outline for (var i = 0; i < 4; i++) { var startPoint = points[i]; var endPoint = points[(i + 1) % 4]; // Calculate steps var dx = endPoint.x - startPoint.x; var dy = endPoint.y - startPoint.y; var steps = Math.max(Math.abs(dx), Math.abs(dy)) / pixelSize; // Draw pixels along the line for (var step = 0; step <= steps; step++) { var px = startPoint.x + dx * (step / steps); var py = startPoint.y + dy * (step / steps); var pixel = LK.getAsset('bulletShape', { anchorX: 0.5, anchorY: 0.5, width: pixelSize - pixelGap, height: pixelSize - pixelGap, x: px, y: py }); bulletGraphics.addChild(pixel); } } self.speed = 10; self.velocity = { x: 0, y: 0 }; self.lifespan = 60; // 1 second at 60fps self.age = 0; self.update = function () { self.x += self.velocity.x; self.y += self.velocity.y; self.age++; // Wrap around screen edges if (self.x < 0) { self.x = 2048; } if (self.x > 2048) { self.x = 0; } if (self.y < 0) { self.y = 2732; } if (self.y > 2732) { self.y = 0; } }; return self; }); var Button = Container.expand(function (assetId) { var self = Container.call(this); // Create button as a solid shape var buttonGraphics = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5, scaleX: 2, scaleY: 2 }); buttonGraphics.alpha = 0.5; // Determine button shape based on assetId var isCircle = assetId === 'fireButton' || assetId === 'forwardButton'; var buttonSize = 60; // Radius or half-width self.isPressed = false; self.down = function (x, y, obj) { self.isPressed = true; buttonGraphics.alpha = 0.8; }; self.up = function (x, y, obj) { self.isPressed = false; buttonGraphics.alpha = 0.5; }; return self; }); var Ship = Container.expand(function () { var self = Container.call(this); // Create a custom triangle ship var shipGraphics = new Container(); self.addChild(shipGraphics); // Create a pixel art triangle var triangleSize = 48; var pixelSize = 4; // Size of each "pixel" var pixelGap = 1; // Gap between pixels for hollow effect // Create triangle points - pointing right by default (0 degrees = right) var points = [{ x: triangleSize, y: 0 }, // Front tip (pointing right) { x: -triangleSize / 2, y: -triangleSize / 2 }, // Top left { x: -triangleSize / 2, y: triangleSize / 2 } // Bottom left ]; // Draw the outline with pixel art style for (var i = 0; i < 3; i++) { var startPoint = points[i]; var endPoint = points[(i + 1) % 3]; // Calculate step count based on distance var dx = endPoint.x - startPoint.x; var dy = endPoint.y - startPoint.y; var steps = Math.max(Math.abs(dx), Math.abs(dy)) / pixelSize; // Draw pixels along the line for (var step = 0; step <= steps; step++) { var px = startPoint.x + dx * (step / steps); var py = startPoint.y + dy * (step / steps); var pixel = LK.getAsset('shipShape', { anchorX: 0.5, anchorY: 0.5, width: pixelSize - pixelGap, height: pixelSize - pixelGap, x: px, y: py }); shipGraphics.addChild(pixel); } } shipGraphics.x = 0; shipGraphics.y = 0; // Ship properties self.rot = 0; // Start pointing right (0 radians) self.rotationSpeed = 0.05; // Reduced rotation speed for less sensitive steering self.isRotatingLeft = false; self.isRotatingRight = false; self.isFiring = false; self.fireDelay = 15; // frames between shots self.fireTimer = 0; self.invulnerable = false; self.invulnerableTime = 0; // Physics-based movement properties - much simpler direct movement self.thrustPower = 0.15; // Reduced thrust power for slower acceleration self.dragFactor = 0.97; // Slightly increased drag to slow down faster self.maxSpeed = 6; // Lower maximum speed self.velocity = { x: 0, y: 0 }; // Track previous position for movement calculations self.lastX = 0; self.lastY = 0; // Track turning state for gradual braking self.wasTurning = false; self.brakingFromVelocity = { x: 0, y: 0 }; // Apply rotation to ship graphics shipGraphics.rotation = self.rot; self.update = function () { // Store last position self.lastX = self.x; self.lastY = self.y; // Handle rotation var isTurning = false; if (self.isRotatingLeft) { self.rot -= self.rotationSpeed; isTurning = true; } if (self.isRotatingRight) { self.rot += self.rotationSpeed; isTurning = true; } // Apply rotation to ship visual shipGraphics.rotation = self.rot; // Calculate direction vector from rotation // 0 = right, PI/2 = down, PI = left, -PI/2 = up var dirX = Math.cos(self.rot); var dirY = Math.sin(self.rot); // Apply different thrust based on whether turning or not var currentThrustPower = self.thrustPower; if (isTurning) { // Reduce thrust when turning (braking effect) currentThrustPower = self.thrustPower * 0.3; // Start gradual braking if we weren't turning before if (!self.wasTurning) { self.wasTurning = true; // Store current velocity to brake from self.brakingFromVelocity = { x: self.velocity.x, y: self.velocity.y }; // Calculate target velocity (30% of current) var targetVelX = self.velocity.x * 0.3; var targetVelY = self.velocity.y * 0.3; // Gradually reduce velocity over 800ms tween(self.velocity, { x: targetVelX, y: targetVelY }, { duration: 800, easing: tween.easeOut }); } } else { // Normal thrust when going straight currentThrustPower = self.thrustPower; // Start gradual acceleration if we were turning before if (self.wasTurning) { self.wasTurning = false; // Stop any ongoing braking tween tween.stop(self.velocity); // Gradually restore velocity over 600ms tween(self.velocity, { x: self.velocity.x / 0.3, // Restore to pre-braking level y: self.velocity.y / 0.3 }, { duration: 600, easing: tween.easeInOut }); } // Normal drag when not turning self.velocity.x *= self.dragFactor; self.velocity.y *= self.dragFactor; } // Apply thrust force in direction ship is facing self.velocity.x += dirX * currentThrustPower; self.velocity.y += dirY * currentThrustPower; // Calculate current speed var speed = Math.sqrt(self.velocity.x * self.velocity.x + self.velocity.y * self.velocity.y); // Apply speed limit if necessary if (speed > self.maxSpeed) { self.velocity.x = self.velocity.x / speed * self.maxSpeed; self.velocity.y = self.velocity.y / speed * self.maxSpeed; } // Create thrust particles if (speed > 0.5 && LK.ticks % 3 === 0) { var particle = new ThrustParticle(); // Position at the back of the ship (opposite of direction) var backX = self.x - dirX * triangleSize * 0.5; var backY = self.y - dirY * triangleSize * 0.5; // Add some randomness backX += (Math.random() - 0.5) * 10; backY += (Math.random() - 0.5) * 10; particle.x = backX; particle.y = backY; // Set velocity opposite to ship direction with some randomness particle.velocity.x = -dirX * (1 + Math.random()) + (Math.random() - 0.5) * 0.5; particle.velocity.y = -dirY * (1 + Math.random()) + (Math.random() - 0.5) * 0.5; // Add to game via event if (typeof game.addThrustParticle === 'function') { game.addThrustParticle(particle); } } // Update position based on velocity self.x += self.velocity.x; self.y += self.velocity.y; // Wrap around screen edges if (self.x < 0) { self.x = 2048; } if (self.x > 2048) { self.x = 0; } if (self.y < 0) { self.y = 2732; } if (self.y > 2732) { self.y = 0; } // Handle firing if (self.isFiring) { if (self.fireTimer <= 0) { self.fireTimer = self.fireDelay; return true; // Signal to create a bullet } } if (self.fireTimer > 0) { self.fireTimer--; } // Handle invulnerability if (self.invulnerable) { self.invulnerableTime--; shipGraphics.alpha = Math.sin(LK.ticks * 0.5) * 0.5 + 0.5; if (self.invulnerableTime <= 0) { self.invulnerable = false; shipGraphics.alpha = 1; } } return false; }; self.makeInvulnerable = function (frames) { self.invulnerable = true; self.invulnerableTime = frames; }; return self; }); var ThrustParticle = Container.expand(function () { var self = Container.call(this); // Create particle visual - larger and more visible var particleGraphics = LK.getAsset('bulletShape', { anchorX: 0.5, anchorY: 0.5, width: 8, height: 8 }); particleGraphics.alpha = 0.9; self.addChild(particleGraphics); // Particle properties self.velocity = { x: 0, y: 0 }; self.lifespan = 25; // Longer lifespan for better visibility self.age = 0; self.update = function () { // Move according to velocity self.x += self.velocity.x; self.y += self.velocity.y; // Age the particle self.age++; // Fade out as it ages, with a faster tail-end fade var lifeRatio = self.age / self.lifespan; particleGraphics.alpha = 0.9 * (1 - lifeRatio * lifeRatio); // Return true if particle should be removed return self.age >= self.lifespan; }; return self; }); /**** * Initialize Game ****/ // Function to add thrust particles - called from Ship update var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // Game variables var ship; var bullets = []; var asteroids = []; var thrustParticles = []; // Array to store thrust particles var score = 0; var lives = 3; var level = 1; var gameStarted = false; var gameOver = false; var triangleSize = 48; // Ship triangle size for bullet positioning // UI elements var leftButton; var rightButton; var fireButton; var scoreTxt; var livesTxt; var levelTxt; var startText; // Initialize the game function initGame() { // Create ship ship = new Ship(); ship.x = 2048 / 2; ship.y = 200; // Position at top middle ship.makeInvulnerable(120); // 2 seconds // Add visual effect to indicate ship is ready for movement tween(ship, { alpha: 0.5 }, { duration: 500, easing: tween.easeInOut, onFinish: function onFinish() { tween(ship, { alpha: 1 }, { duration: 500, easing: tween.easeInOut }); } }); game.addChild(ship); // Create control buttons leftButton = new Button('rotateLeftButton'); leftButton.x = 300; // Position on the left side leftButton.y = 2732 - 200; // Position at the bottom leftButton.scale.set(2, 2); // Make button bigger game.addChild(leftButton); // Fire button in the middle fireButton = new Button('fireButton'); fireButton.x = 2048 / 2; // Position in middle fireButton.y = 2732 - 200; // Position at the bottom fireButton.scale.set(2, 2); // Make button bigger game.addChild(fireButton); // No forward button needed - ship will constantly move forward // Right rotation button rightButton = new Button('rotateRightButton'); rightButton.x = 2048 - 300; // Position on the right side rightButton.y = 2732 - 200; // Position at the bottom rightButton.scale.set(2, 2); // Make button bigger game.addChild(rightButton); // Create UI text scoreTxt = new Text2('SCORE: 0', { size: 40, fill: 0xFFFFFF }); scoreTxt.anchor.set(0, 0); LK.gui.topRight.addChild(scoreTxt); livesTxt = new Text2('LIVES: 3', { size: 40, fill: 0xFFFFFF }); livesTxt.anchor.set(0, 0); LK.gui.top.addChild(livesTxt); levelTxt = new Text2('LEVEL: 1', { size: 40, fill: 0xFFFFFF }); levelTxt.anchor.set(1, 0); LK.gui.topLeft.addChild(levelTxt); // Start screen text if (!gameStarted) { startText = new Text2('TAP TO START', { size: 80, fill: 0xFFFFFF }); startText.anchor.set(0.5, 0.5); LK.gui.center.addChild(startText); } // Clear any existing game objects bullets = []; asteroids = []; thrustParticles = []; // Initialize asteroids for the first level createAsteroidsForLevel(level); // Start game music LK.playMusic('gameMusic', { loop: true }); } function createAsteroidsForLevel(level) { // Number of asteroids based on level var numAsteroids = Math.min(4 + level, 12); for (var i = 0; i < numAsteroids; i++) { var asteroid = new Asteroid(2); // Start with large asteroids // Position the asteroid away from the player do { asteroid.x = Math.random() * 2048; asteroid.y = Math.random() * 2732; } while (distance(asteroid.x, asteroid.y, ship.x, ship.y) < 300); asteroids.push(asteroid); game.addChild(asteroid); } } function distance(x1, y1, x2, y2) { return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); } function createBullet() { var bullet = new Bullet(); // Use ship's rotation directly var angle = ship.rot; // Calculate the position at the tip of the ship based on the triangle geometry // For the redesigned ship (pointing up at 0 rotation), the tip is -triangleSize units in Y var offsetX = Math.cos(angle) * triangleSize; var offsetY = Math.sin(angle) * triangleSize; // Position bullet at the tip of the ship bullet.x = ship.x + offsetX; bullet.y = ship.y + offsetY; // Set bullet velocity to match ship's exact facing direction bullet.velocity.x = Math.cos(angle) * bullet.speed; bullet.velocity.y = Math.sin(angle) * bullet.speed; // Add bullet to game bullets.push(bullet); game.addChild(bullet); // Play shoot sound LK.getSound('shoot').play(); } function updateAsteroids() { for (var i = asteroids.length - 1; i >= 0; i--) { var asteroid = asteroids[i]; asteroid.update(); // Check for collision with the ship if (!ship.invulnerable && asteroid.intersects(ship)) { // Player loses a life lives--; livesTxt.setText('LIVES: ' + lives); // Play explosion sound LK.getSound('explosion').play(); // Flash screen LK.effects.flashScreen(0xFF0000, 500); // Make ship invulnerable for a few seconds ship.makeInvulnerable(180); // Game over if no lives left if (lives <= 0) { LK.setScore(score); LK.showGameOver(); gameOver = true; return; } } } } function updateBullets() { for (var i = bullets.length - 1; i >= 0; i--) { var bullet = bullets[i]; bullet.update(); // Remove bullets that have lived too long if (bullet.age > bullet.lifespan) { game.removeChild(bullet); bullets.splice(i, 1); continue; } // Check for collisions with asteroids var hitAsteroid = false; for (var j = asteroids.length - 1; j >= 0; j--) { var asteroid = asteroids[j]; if (bullet.intersects(asteroid)) { hitAsteroid = true; // Add score based on asteroid size score += asteroid.getPoints(); scoreTxt.setText('SCORE: ' + score); // Play explosion sound LK.getSound('explosion').play(); // Break asteroid into smaller pieces if it's not the smallest size // Small asteroids (size 0) will just disappear if (asteroid.size > 1) { for (var k = 0; k < 2; k++) { var newAsteroid = new Asteroid(asteroid.size - 1); newAsteroid.x = asteroid.x; newAsteroid.y = asteroid.y; asteroids.push(newAsteroid); game.addChild(newAsteroid); } } // Remove the asteroid game.removeChild(asteroid); asteroids.splice(j, 1); break; } } if (hitAsteroid) { // Remove the bullet game.removeChild(bullet); bullets.splice(i, 1); } } // Check if all asteroids are destroyed if (asteroids.length === 0) { // Next level level++; levelTxt.setText('LEVEL: ' + level); // Create new asteroids for the next level createAsteroidsForLevel(level); } } // Main game update function game.update = function () { if (!gameStarted) { return; } // Update ship controls based on button state ship.isRotatingLeft = leftButton.isPressed; ship.isRotatingRight = rightButton.isPressed; ship.isFiring = fireButton.isPressed; // Ship constantly moves forward, no need for isMoving toggle // Update ship if (ship.update()) { createBullet(); } // Update bullets and check for collisions updateBullets(); // Update asteroids and check for collisions updateAsteroids(); // Update thrust particles updateThrustParticles(); }; // Event handlers game.down = function (x, y, obj) { if (!gameStarted) { gameStarted = true; if (startText) { LK.gui.center.removeChild(startText); startText = null; } } // Ensure buttons can detect touch events when pressed var local; // Check left button local = leftButton.toLocal({ x: x, y: y }); if (local.x >= -leftButton.width / 2 && local.x <= leftButton.width / 2 && local.y >= -leftButton.height / 2 && local.y <= leftButton.height / 2) { leftButton.down(local.x, local.y, {}); } // Check fire button local = fireButton.toLocal({ x: x, y: y }); if (local.x >= -fireButton.width / 2 && local.x <= fireButton.width / 2 && local.y >= -fireButton.height / 2 && local.y <= fireButton.height / 2) { fireButton.down(local.x, local.y, {}); } // Forward button removed - ship moves constantly // Check right button local = rightButton.toLocal({ x: x, y: y }); if (local.x >= -rightButton.width / 2 && local.x <= rightButton.width / 2 && local.y >= -rightButton.height / 2 && local.y <= rightButton.height / 2) { rightButton.down(local.x, local.y, {}); } }; // Add up handler to handle releasing buttons game.up = function (x, y, obj) { // Reset all button states when touch/click is released leftButton.up(0, 0, {}); fireButton.up(0, 0, {}); rightButton.up(0, 0, {}); }; // Initialize the game when this script runs initGame(); // Function to add thrust particles - called from Ship update game.addThrustParticle = function (particle) { thrustParticles.push(particle); game.addChild(particle); }; // Function to update thrust particles function updateThrustParticles() { for (var i = thrustParticles.length - 1; i >= 0; i--) { var particle = thrustParticles[i]; // If particle update returns true, it means the particle should be removed if (particle.update()) { game.removeChild(particle); thrustParticles.splice(i, 1); } } }
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Asteroid = Container.expand(function (size) {
var self = Container.call(this);
// Size can be 0 (small), 1 (medium), or 2 (large)
self.size = size || 2;
// Create a container for the asteroid
var asteroidGraphics = new Container();
self.addChild(asteroidGraphics);
// Determine asteroid size
var radius;
if (self.size === 0) {
radius = 16; // small
} else if (self.size === 1) {
radius = 32; // medium
} else {
radius = 64; // large
}
// Create a pixel art circle (asteroid)
var pixelSize = 4; // Size of each "pixel"
var pixelGap = 1; // Gap between pixels for hollow effect
var steps = 16; // Number of points around the circle
// Draw a circle of pixels
for (var i = 0; i < steps; i++) {
var angle = i / steps * Math.PI * 2;
var px = Math.cos(angle) * radius;
var py = Math.sin(angle) * radius;
var pixel = LK.getAsset('asteroidLarge', {
anchorX: 0.5,
anchorY: 0.5,
width: pixelSize - pixelGap,
height: pixelSize - pixelGap,
x: px,
y: py
});
asteroidGraphics.addChild(pixel);
}
// Random velocity
var speed = (3 - self.size) * 0.8 + 0.5; // Smaller asteroids move faster
var angle = Math.random() * Math.PI * 2;
self.velocity = {
x: Math.cos(angle) * speed,
y: Math.sin(angle) * speed
};
// Random rotation
self.rotationSpeed = (Math.random() - 0.5) * 0.05;
self.update = function () {
// Move
self.x += self.velocity.x;
self.y += self.velocity.y;
// Rotate
asteroidGraphics.rotation += self.rotationSpeed;
// Wrap around screen edges
if (self.x < -50) {
self.x = 2098;
}
if (self.x > 2098) {
self.x = -50;
}
if (self.y < -50) {
self.y = 2782;
}
if (self.y > 2782) {
self.y = -50;
}
};
self.getPoints = function () {
return (3 - self.size) * 100; // Small: 300, Medium: 200, Large: 100
};
return self;
});
var Bullet = Container.expand(function () {
var self = Container.call(this);
// Create a container for pixel art bullet
var bulletGraphics = new Container();
self.addChild(bulletGraphics);
// Create a small pixel art square (bullet)
var bulletSize = 8;
var pixelSize = 2; // Size of each "pixel"
var pixelGap = 1; // Gap between pixels for hollow effect
// Draw the outline of the square
var points = [{
x: -bulletSize / 2,
y: -bulletSize / 2
},
// Top left
{
x: bulletSize / 2,
y: -bulletSize / 2
},
// Top right
{
x: bulletSize / 2,
y: bulletSize / 2
},
// Bottom right
{
x: -bulletSize / 2,
y: bulletSize / 2
} // Bottom left
];
// Draw the square outline
for (var i = 0; i < 4; i++) {
var startPoint = points[i];
var endPoint = points[(i + 1) % 4];
// Calculate steps
var dx = endPoint.x - startPoint.x;
var dy = endPoint.y - startPoint.y;
var steps = Math.max(Math.abs(dx), Math.abs(dy)) / pixelSize;
// Draw pixels along the line
for (var step = 0; step <= steps; step++) {
var px = startPoint.x + dx * (step / steps);
var py = startPoint.y + dy * (step / steps);
var pixel = LK.getAsset('bulletShape', {
anchorX: 0.5,
anchorY: 0.5,
width: pixelSize - pixelGap,
height: pixelSize - pixelGap,
x: px,
y: py
});
bulletGraphics.addChild(pixel);
}
}
self.speed = 10;
self.velocity = {
x: 0,
y: 0
};
self.lifespan = 60; // 1 second at 60fps
self.age = 0;
self.update = function () {
self.x += self.velocity.x;
self.y += self.velocity.y;
self.age++;
// Wrap around screen edges
if (self.x < 0) {
self.x = 2048;
}
if (self.x > 2048) {
self.x = 0;
}
if (self.y < 0) {
self.y = 2732;
}
if (self.y > 2732) {
self.y = 0;
}
};
return self;
});
var Button = Container.expand(function (assetId) {
var self = Container.call(this);
// Create button as a solid shape
var buttonGraphics = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 2
});
buttonGraphics.alpha = 0.5;
// Determine button shape based on assetId
var isCircle = assetId === 'fireButton' || assetId === 'forwardButton';
var buttonSize = 60; // Radius or half-width
self.isPressed = false;
self.down = function (x, y, obj) {
self.isPressed = true;
buttonGraphics.alpha = 0.8;
};
self.up = function (x, y, obj) {
self.isPressed = false;
buttonGraphics.alpha = 0.5;
};
return self;
});
var Ship = Container.expand(function () {
var self = Container.call(this);
// Create a custom triangle ship
var shipGraphics = new Container();
self.addChild(shipGraphics);
// Create a pixel art triangle
var triangleSize = 48;
var pixelSize = 4; // Size of each "pixel"
var pixelGap = 1; // Gap between pixels for hollow effect
// Create triangle points - pointing right by default (0 degrees = right)
var points = [{
x: triangleSize,
y: 0
},
// Front tip (pointing right)
{
x: -triangleSize / 2,
y: -triangleSize / 2
},
// Top left
{
x: -triangleSize / 2,
y: triangleSize / 2
} // Bottom left
];
// Draw the outline with pixel art style
for (var i = 0; i < 3; i++) {
var startPoint = points[i];
var endPoint = points[(i + 1) % 3];
// Calculate step count based on distance
var dx = endPoint.x - startPoint.x;
var dy = endPoint.y - startPoint.y;
var steps = Math.max(Math.abs(dx), Math.abs(dy)) / pixelSize;
// Draw pixels along the line
for (var step = 0; step <= steps; step++) {
var px = startPoint.x + dx * (step / steps);
var py = startPoint.y + dy * (step / steps);
var pixel = LK.getAsset('shipShape', {
anchorX: 0.5,
anchorY: 0.5,
width: pixelSize - pixelGap,
height: pixelSize - pixelGap,
x: px,
y: py
});
shipGraphics.addChild(pixel);
}
}
shipGraphics.x = 0;
shipGraphics.y = 0;
// Ship properties
self.rot = 0; // Start pointing right (0 radians)
self.rotationSpeed = 0.05; // Reduced rotation speed for less sensitive steering
self.isRotatingLeft = false;
self.isRotatingRight = false;
self.isFiring = false;
self.fireDelay = 15; // frames between shots
self.fireTimer = 0;
self.invulnerable = false;
self.invulnerableTime = 0;
// Physics-based movement properties - much simpler direct movement
self.thrustPower = 0.15; // Reduced thrust power for slower acceleration
self.dragFactor = 0.97; // Slightly increased drag to slow down faster
self.maxSpeed = 6; // Lower maximum speed
self.velocity = {
x: 0,
y: 0
};
// Track previous position for movement calculations
self.lastX = 0;
self.lastY = 0;
// Track turning state for gradual braking
self.wasTurning = false;
self.brakingFromVelocity = {
x: 0,
y: 0
};
// Apply rotation to ship graphics
shipGraphics.rotation = self.rot;
self.update = function () {
// Store last position
self.lastX = self.x;
self.lastY = self.y;
// Handle rotation
var isTurning = false;
if (self.isRotatingLeft) {
self.rot -= self.rotationSpeed;
isTurning = true;
}
if (self.isRotatingRight) {
self.rot += self.rotationSpeed;
isTurning = true;
}
// Apply rotation to ship visual
shipGraphics.rotation = self.rot;
// Calculate direction vector from rotation
// 0 = right, PI/2 = down, PI = left, -PI/2 = up
var dirX = Math.cos(self.rot);
var dirY = Math.sin(self.rot);
// Apply different thrust based on whether turning or not
var currentThrustPower = self.thrustPower;
if (isTurning) {
// Reduce thrust when turning (braking effect)
currentThrustPower = self.thrustPower * 0.3;
// Start gradual braking if we weren't turning before
if (!self.wasTurning) {
self.wasTurning = true;
// Store current velocity to brake from
self.brakingFromVelocity = {
x: self.velocity.x,
y: self.velocity.y
};
// Calculate target velocity (30% of current)
var targetVelX = self.velocity.x * 0.3;
var targetVelY = self.velocity.y * 0.3;
// Gradually reduce velocity over 800ms
tween(self.velocity, {
x: targetVelX,
y: targetVelY
}, {
duration: 800,
easing: tween.easeOut
});
}
} else {
// Normal thrust when going straight
currentThrustPower = self.thrustPower;
// Start gradual acceleration if we were turning before
if (self.wasTurning) {
self.wasTurning = false;
// Stop any ongoing braking tween
tween.stop(self.velocity);
// Gradually restore velocity over 600ms
tween(self.velocity, {
x: self.velocity.x / 0.3,
// Restore to pre-braking level
y: self.velocity.y / 0.3
}, {
duration: 600,
easing: tween.easeInOut
});
}
// Normal drag when not turning
self.velocity.x *= self.dragFactor;
self.velocity.y *= self.dragFactor;
}
// Apply thrust force in direction ship is facing
self.velocity.x += dirX * currentThrustPower;
self.velocity.y += dirY * currentThrustPower;
// Calculate current speed
var speed = Math.sqrt(self.velocity.x * self.velocity.x + self.velocity.y * self.velocity.y);
// Apply speed limit if necessary
if (speed > self.maxSpeed) {
self.velocity.x = self.velocity.x / speed * self.maxSpeed;
self.velocity.y = self.velocity.y / speed * self.maxSpeed;
}
// Create thrust particles
if (speed > 0.5 && LK.ticks % 3 === 0) {
var particle = new ThrustParticle();
// Position at the back of the ship (opposite of direction)
var backX = self.x - dirX * triangleSize * 0.5;
var backY = self.y - dirY * triangleSize * 0.5;
// Add some randomness
backX += (Math.random() - 0.5) * 10;
backY += (Math.random() - 0.5) * 10;
particle.x = backX;
particle.y = backY;
// Set velocity opposite to ship direction with some randomness
particle.velocity.x = -dirX * (1 + Math.random()) + (Math.random() - 0.5) * 0.5;
particle.velocity.y = -dirY * (1 + Math.random()) + (Math.random() - 0.5) * 0.5;
// Add to game via event
if (typeof game.addThrustParticle === 'function') {
game.addThrustParticle(particle);
}
}
// Update position based on velocity
self.x += self.velocity.x;
self.y += self.velocity.y;
// Wrap around screen edges
if (self.x < 0) {
self.x = 2048;
}
if (self.x > 2048) {
self.x = 0;
}
if (self.y < 0) {
self.y = 2732;
}
if (self.y > 2732) {
self.y = 0;
}
// Handle firing
if (self.isFiring) {
if (self.fireTimer <= 0) {
self.fireTimer = self.fireDelay;
return true; // Signal to create a bullet
}
}
if (self.fireTimer > 0) {
self.fireTimer--;
}
// Handle invulnerability
if (self.invulnerable) {
self.invulnerableTime--;
shipGraphics.alpha = Math.sin(LK.ticks * 0.5) * 0.5 + 0.5;
if (self.invulnerableTime <= 0) {
self.invulnerable = false;
shipGraphics.alpha = 1;
}
}
return false;
};
self.makeInvulnerable = function (frames) {
self.invulnerable = true;
self.invulnerableTime = frames;
};
return self;
});
var ThrustParticle = Container.expand(function () {
var self = Container.call(this);
// Create particle visual - larger and more visible
var particleGraphics = LK.getAsset('bulletShape', {
anchorX: 0.5,
anchorY: 0.5,
width: 8,
height: 8
});
particleGraphics.alpha = 0.9;
self.addChild(particleGraphics);
// Particle properties
self.velocity = {
x: 0,
y: 0
};
self.lifespan = 25; // Longer lifespan for better visibility
self.age = 0;
self.update = function () {
// Move according to velocity
self.x += self.velocity.x;
self.y += self.velocity.y;
// Age the particle
self.age++;
// Fade out as it ages, with a faster tail-end fade
var lifeRatio = self.age / self.lifespan;
particleGraphics.alpha = 0.9 * (1 - lifeRatio * lifeRatio);
// Return true if particle should be removed
return self.age >= self.lifespan;
};
return self;
});
/****
* Initialize Game
****/
// Function to add thrust particles - called from Ship update
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Game variables
var ship;
var bullets = [];
var asteroids = [];
var thrustParticles = []; // Array to store thrust particles
var score = 0;
var lives = 3;
var level = 1;
var gameStarted = false;
var gameOver = false;
var triangleSize = 48; // Ship triangle size for bullet positioning
// UI elements
var leftButton;
var rightButton;
var fireButton;
var scoreTxt;
var livesTxt;
var levelTxt;
var startText;
// Initialize the game
function initGame() {
// Create ship
ship = new Ship();
ship.x = 2048 / 2;
ship.y = 200; // Position at top middle
ship.makeInvulnerable(120); // 2 seconds
// Add visual effect to indicate ship is ready for movement
tween(ship, {
alpha: 0.5
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(ship, {
alpha: 1
}, {
duration: 500,
easing: tween.easeInOut
});
}
});
game.addChild(ship);
// Create control buttons
leftButton = new Button('rotateLeftButton');
leftButton.x = 300; // Position on the left side
leftButton.y = 2732 - 200; // Position at the bottom
leftButton.scale.set(2, 2); // Make button bigger
game.addChild(leftButton);
// Fire button in the middle
fireButton = new Button('fireButton');
fireButton.x = 2048 / 2; // Position in middle
fireButton.y = 2732 - 200; // Position at the bottom
fireButton.scale.set(2, 2); // Make button bigger
game.addChild(fireButton);
// No forward button needed - ship will constantly move forward
// Right rotation button
rightButton = new Button('rotateRightButton');
rightButton.x = 2048 - 300; // Position on the right side
rightButton.y = 2732 - 200; // Position at the bottom
rightButton.scale.set(2, 2); // Make button bigger
game.addChild(rightButton);
// Create UI text
scoreTxt = new Text2('SCORE: 0', {
size: 40,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0, 0);
LK.gui.topRight.addChild(scoreTxt);
livesTxt = new Text2('LIVES: 3', {
size: 40,
fill: 0xFFFFFF
});
livesTxt.anchor.set(0, 0);
LK.gui.top.addChild(livesTxt);
levelTxt = new Text2('LEVEL: 1', {
size: 40,
fill: 0xFFFFFF
});
levelTxt.anchor.set(1, 0);
LK.gui.topLeft.addChild(levelTxt);
// Start screen text
if (!gameStarted) {
startText = new Text2('TAP TO START', {
size: 80,
fill: 0xFFFFFF
});
startText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(startText);
}
// Clear any existing game objects
bullets = [];
asteroids = [];
thrustParticles = [];
// Initialize asteroids for the first level
createAsteroidsForLevel(level);
// Start game music
LK.playMusic('gameMusic', {
loop: true
});
}
function createAsteroidsForLevel(level) {
// Number of asteroids based on level
var numAsteroids = Math.min(4 + level, 12);
for (var i = 0; i < numAsteroids; i++) {
var asteroid = new Asteroid(2); // Start with large asteroids
// Position the asteroid away from the player
do {
asteroid.x = Math.random() * 2048;
asteroid.y = Math.random() * 2732;
} while (distance(asteroid.x, asteroid.y, ship.x, ship.y) < 300);
asteroids.push(asteroid);
game.addChild(asteroid);
}
}
function distance(x1, y1, x2, y2) {
return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
}
function createBullet() {
var bullet = new Bullet();
// Use ship's rotation directly
var angle = ship.rot;
// Calculate the position at the tip of the ship based on the triangle geometry
// For the redesigned ship (pointing up at 0 rotation), the tip is -triangleSize units in Y
var offsetX = Math.cos(angle) * triangleSize;
var offsetY = Math.sin(angle) * triangleSize;
// Position bullet at the tip of the ship
bullet.x = ship.x + offsetX;
bullet.y = ship.y + offsetY;
// Set bullet velocity to match ship's exact facing direction
bullet.velocity.x = Math.cos(angle) * bullet.speed;
bullet.velocity.y = Math.sin(angle) * bullet.speed;
// Add bullet to game
bullets.push(bullet);
game.addChild(bullet);
// Play shoot sound
LK.getSound('shoot').play();
}
function updateAsteroids() {
for (var i = asteroids.length - 1; i >= 0; i--) {
var asteroid = asteroids[i];
asteroid.update();
// Check for collision with the ship
if (!ship.invulnerable && asteroid.intersects(ship)) {
// Player loses a life
lives--;
livesTxt.setText('LIVES: ' + lives);
// Play explosion sound
LK.getSound('explosion').play();
// Flash screen
LK.effects.flashScreen(0xFF0000, 500);
// Make ship invulnerable for a few seconds
ship.makeInvulnerable(180);
// Game over if no lives left
if (lives <= 0) {
LK.setScore(score);
LK.showGameOver();
gameOver = true;
return;
}
}
}
}
function updateBullets() {
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
bullet.update();
// Remove bullets that have lived too long
if (bullet.age > bullet.lifespan) {
game.removeChild(bullet);
bullets.splice(i, 1);
continue;
}
// Check for collisions with asteroids
var hitAsteroid = false;
for (var j = asteroids.length - 1; j >= 0; j--) {
var asteroid = asteroids[j];
if (bullet.intersects(asteroid)) {
hitAsteroid = true;
// Add score based on asteroid size
score += asteroid.getPoints();
scoreTxt.setText('SCORE: ' + score);
// Play explosion sound
LK.getSound('explosion').play();
// Break asteroid into smaller pieces if it's not the smallest size
// Small asteroids (size 0) will just disappear
if (asteroid.size > 1) {
for (var k = 0; k < 2; k++) {
var newAsteroid = new Asteroid(asteroid.size - 1);
newAsteroid.x = asteroid.x;
newAsteroid.y = asteroid.y;
asteroids.push(newAsteroid);
game.addChild(newAsteroid);
}
}
// Remove the asteroid
game.removeChild(asteroid);
asteroids.splice(j, 1);
break;
}
}
if (hitAsteroid) {
// Remove the bullet
game.removeChild(bullet);
bullets.splice(i, 1);
}
}
// Check if all asteroids are destroyed
if (asteroids.length === 0) {
// Next level
level++;
levelTxt.setText('LEVEL: ' + level);
// Create new asteroids for the next level
createAsteroidsForLevel(level);
}
}
// Main game update function
game.update = function () {
if (!gameStarted) {
return;
}
// Update ship controls based on button state
ship.isRotatingLeft = leftButton.isPressed;
ship.isRotatingRight = rightButton.isPressed;
ship.isFiring = fireButton.isPressed;
// Ship constantly moves forward, no need for isMoving toggle
// Update ship
if (ship.update()) {
createBullet();
}
// Update bullets and check for collisions
updateBullets();
// Update asteroids and check for collisions
updateAsteroids();
// Update thrust particles
updateThrustParticles();
};
// Event handlers
game.down = function (x, y, obj) {
if (!gameStarted) {
gameStarted = true;
if (startText) {
LK.gui.center.removeChild(startText);
startText = null;
}
}
// Ensure buttons can detect touch events when pressed
var local;
// Check left button
local = leftButton.toLocal({
x: x,
y: y
});
if (local.x >= -leftButton.width / 2 && local.x <= leftButton.width / 2 && local.y >= -leftButton.height / 2 && local.y <= leftButton.height / 2) {
leftButton.down(local.x, local.y, {});
}
// Check fire button
local = fireButton.toLocal({
x: x,
y: y
});
if (local.x >= -fireButton.width / 2 && local.x <= fireButton.width / 2 && local.y >= -fireButton.height / 2 && local.y <= fireButton.height / 2) {
fireButton.down(local.x, local.y, {});
}
// Forward button removed - ship moves constantly
// Check right button
local = rightButton.toLocal({
x: x,
y: y
});
if (local.x >= -rightButton.width / 2 && local.x <= rightButton.width / 2 && local.y >= -rightButton.height / 2 && local.y <= rightButton.height / 2) {
rightButton.down(local.x, local.y, {});
}
};
// Add up handler to handle releasing buttons
game.up = function (x, y, obj) {
// Reset all button states when touch/click is released
leftButton.up(0, 0, {});
fireButton.up(0, 0, {});
rightButton.up(0, 0, {});
};
// Initialize the game when this script runs
initGame();
// Function to add thrust particles - called from Ship update
game.addThrustParticle = function (particle) {
thrustParticles.push(particle);
game.addChild(particle);
};
// Function to update thrust particles
function updateThrustParticles() {
for (var i = thrustParticles.length - 1; i >= 0; i--) {
var particle = thrustParticles[i];
// If particle update returns true, it means the particle should be removed
if (particle.update()) {
game.removeChild(particle);
thrustParticles.splice(i, 1);
}
}
}