User prompt
let’s create a title screen. the title can be Invader Blits 2 But with a delay. the delay can be 3 seconds. so it will be Invader Blits (wait 3 seconds) 2 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
not like start fresh to a ball game. I’m talking about just a black screen with all codes and all sprites removed.
User prompt
let’s start fresh.
User prompt
please fix it :)
User prompt
Please fix the bug: 'TypeError: undefined is not an object (evaluating 'self.children[0].tint = c[0] << 16 | c[1] << 8 | c[2]')' in or related to this line: 'self.children[0].tint = c[0] << 16 | c[1] << 8 | c[2];' Line Number: 214
User prompt
Please fix the bug: 'undefined is not an object (evaluating 'self.children[0].tint = 0xff2222')' in or related to this line: 'self.children[0].tint = 0xff2222;' Line Number: 225
User prompt
make big invaders. they split when ever hit. and make red invaders. red invaders are 2x faster than the green invaders. make some of the invaders rainbow too.
User prompt
when I’m not by the screen edge. even when it’s not by the screen edge. it still acts like it. FIX THIS!!
User prompt
make the bomb appear every 10 seconds and moves by itself and bounces off the screen.
User prompt
make the lines blink when shooting every 0.7 seconds.
User prompt
make the lines blink every 0.3 seconds when shooting
User prompt
make the lines blink every 0.1 second when shooting.
User prompt
you made the stuff correct. it’s just the invaders isn’t dieing when target is on them and shooting.
User prompt
Please fix the bug: 'ReferenceError: Can't find variable: player' in or related to this line: 'if (!player.invincible && b.intersects(player)) {' Line Number: 446
User prompt
that third one is still in the middle. REMOVE THAT THIRD ONE. and the target isn’t shooting anything. you also forgot the blue lines.
User prompt
Please fix the bug: 'undefined is not an object (evaluating 'player.x = GAME_WIDTH / 2')' in or related to this line: 'player.x = GAME_WIDTH / 2;' Line Number: 330
User prompt
there’s 3 cannons. remove that third cannon. and the target only moves when moving hand.
User prompt
can you make the target? the target always moves but the blue block (the cannon) is always in the corner. make another one and that other one goes in a different corner (it only has to be on the bottom.) and when shooting. Blue lines go to the target and blink when ever shooting.
User prompt
I can’t shoot. how do I fix this?
Code edit (1 edits merged)
Please save this source code
User prompt
Invader Blitz
Initial prompt
we are making a game similar to space invaders frenzy.
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Invader class for enemy invaders
var Invader = Container.expand(function () {
var self = Container.call(this);
// Use the 'character' asset for invader for now, but tint it green
var invaderSprite = self.attachAsset('character', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x00ff00,
scaleX: 0.7,
scaleY: 0.7
});
self.speedX = 4; // Horizontal speed
self.speedY = 0; // Vertical speed (drops down on edge)
self.lastX = self.x;
self.lastY = self.y;
self.update = function () {
// Move horizontally
self.x += self.speedX;
// Bounce off left edge
if (self.lastX >= 60 && self.x < 60) {
self.x = 60; // Clamp to edge
self.speedX = -self.speedX * 1.12; // Increase speed and reverse
// Cap speedX to a maximum of 12 (preserve sign)
if (self.speedX > 12) self.speedX = 12;
if (self.speedX < -12) self.speedX = -12;
self.y += 40; // Move down on bounce
}
// Bounce off right edge
if (self.lastX <= 2048 - 60 && self.x > 2048 - 60) {
self.x = 2048 - 60; // Clamp to edge
self.speedX = -self.speedX * 1.12; // Increase speed and reverse
// Cap speedX to a maximum of 12 (preserve sign)
if (self.speedX > 12) self.speedX = 12;
if (self.speedX < -12) self.speedX = -12;
self.y += 40; // Move down on bounce
}
self.lastX = self.x;
self.lastY = self.y;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Game state variables
var p1Score = 0;
var p2Score = 0;
// High score tracking (persistent for session)
var highScore = 0;
// Score display Text2 objects
var p1ScoreDisplay, p2ScoreDisplay, highScoreDisplay;
// Helper to show score displays at top
function showScoreDisplays() {
// Remove previous if any
if (p1ScoreDisplay) p1ScoreDisplay.alpha = 0;
if (p2ScoreDisplay) p2ScoreDisplay.alpha = 0;
if (highScoreDisplay) highScoreDisplay.alpha = 0;
// High Score
highScoreDisplay = new Text2('HIGH: ' + highScore, {
size: 60,
fill: 0xFFD700 // Gold
});
highScoreDisplay.anchor.set(0.5, 0);
highScoreDisplay.x = 2048 / 2;
highScoreDisplay.y = 20;
LK.gui.top.addChild(highScoreDisplay);
// P1 Score (left)
p1ScoreDisplay = new Text2('P1: ' + p1Score, {
size: 50,
fill: 0xFF4444
});
p1ScoreDisplay.anchor.set(0, 0);
p1ScoreDisplay.x = 120;
p1ScoreDisplay.y = 20;
LK.gui.top.addChild(p1ScoreDisplay);
// P2 Score (right)
p2ScoreDisplay = new Text2('P2: ' + p2Score, {
size: 50,
fill: 0x4444FF
});
p2ScoreDisplay.anchor.set(1, 0);
p2ScoreDisplay.x = 2048 - 120;
p2ScoreDisplay.y = 20;
LK.gui.top.addChild(p2ScoreDisplay);
}
// Helper to update score displays
function updateScoreDisplays() {
if (p1ScoreDisplay) p1ScoreDisplay.setText('P1: ' + p1Score);
if (p2ScoreDisplay) p2ScoreDisplay.setText('P2: ' + p2Score);
// High score is always the highest of P1 and P2
highScore = Math.max(p1Score, p2Score, highScore);
if (highScoreDisplay) highScoreDisplay.setText('HIGH: ' + highScore);
}
// Red ship for P1
// Blue ship for P2
// Yellow target
var coins = 0;
var maxCoins = 2; // Require 2 credits for 2 players
var gameStarted = false;
var insertCoinText;
var creditsText;
var pressStartText;
var holdStartTime = 0; // Only used for hold-to-skip in ready screen
var isHolding = false; // Only used for hold-to-skip in ready screen
var ship1;
var ship2;
var countdownText;
var countdownTimer;
var gameplayShips = [];
var target;
var target2; // Second target for P2
var cannons = [];
var cannonLines = [];
// Track dragging for both targets independently
var draggingTarget1 = false;
var draggingTarget2 = false;
var isShooting = false;
var isInReadyScreen = false;
// Create main title text "Invader Blits"
var titleText = new Text2('Invader Blits', {
size: 120,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 2048 / 2;
titleText.y = 2732 / 2 - 50;
game.addChild(titleText);
// Create the "2" text (initially hidden)
var numberText = new Text2('2', {
size: 120,
fill: 0xFFFFFF
});
numberText.anchor.set(0.5, 0.5);
numberText.x = 2048 / 2 + titleText.width / 2 + 20; // Position it after "Invader Blits"
numberText.y = 2732 / 2 - 50;
numberText.alpha = 0; // Start invisible
game.addChild(numberText);
// Position the "2" below the screen initially for slide-up animation
numberText.y = 2732 + 100; // Start below screen
numberText.alpha = 1; // Make it visible but positioned off-screen
// Show the "2" after 3 seconds with a slide-up animation
LK.setTimeout(function () {
tween(numberText, {
y: 2732 / 2 - 50 // Slide up to final position next to title
}, {
duration: 800,
easing: tween.easeOut
});
// After the "2" animation completes, show the INSERT COIN! text
LK.setTimeout(function () {
// Create "INSERT COIN!" text
insertCoinText = new Text2('INSERT COIN!', {
size: 60,
fill: 0xFFFF00 // Yellow color
});
insertCoinText.anchor.set(0.5, 0.5);
insertCoinText.x = 2048 / 2;
insertCoinText.y = 300; // Top of screen
game.addChild(insertCoinText);
// Create "0/1 CREDITS" text
creditsText = new Text2('0/1 CREDITS', {
size: 40,
fill: 0xFFFFFF // White color
});
creditsText.anchor.set(0.5, 0.5);
creditsText.x = 2048 / 2;
creditsText.y = 380; // Below INSERT COIN!
game.addChild(creditsText);
}, 800); // Wait for the "2" animation to complete
}, 3000);
// Function to update credits display
function updateCreditsDisplay() {
if (creditsText) {
creditsText.setText(coins + '/' + maxCoins + ' CREDITS');
}
// (Removed 'PRESS START' text creation for gameplay)
// Handle target dragging
game.move = function (x, y, obj) {
// Allow both targets to be dragged at the same time
// Use draggingTarget1 and draggingTarget2 to track each target's drag state
// Move target1 if dragging
if (draggingTarget1 && target) {
target.x = x;
target.y = y;
// Only update lines for P1 (first two lines)
for (var i = 0; i < 2 && i < cannons.length; i++) {
var cannon = cannons[i];
var line = cannonLines[i];
var dx = target.x - cannon.x;
var dy = target.y - cannon.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
line.x = cannon.x;
line.y = cannon.y;
line.rotation = angle;
line.scaleX = distance / 100;
}
}
// Move target2 if dragging
if (draggingTarget2 && typeof target2 !== "undefined" && target2) {
target2.x = x;
target2.y = y;
// Only update lines for P2 (last two lines)
for (var i = 2; i < cannons.length; i++) {
var cannon = cannons[i];
var line = cannonLines[i];
var dx = target2.x - cannon.x;
var dy = target2.y - cannon.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
line.x = cannon.x;
line.y = cannon.y;
line.rotation = angle;
line.scaleX = distance / 100;
}
}
};
// Update down handler to include target dragging
var originalDown = game.down;
game.down = function (x, y, obj) {
// Call original down handler first
originalDown.call(this, x, y, obj);
// Allow both targets to be picked up for dragging at the same time
if (gameStarted) {
// Check if clicking on target1
if (target) {
var dx = x - target.x;
var dy = y - target.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 60) {
draggingTarget1 = true;
// Show lines for P1 cannons only
for (var i = 0; i < 2 && i < cannonLines.length; i++) {
cannonLines[i].alpha = 0.8;
}
}
}
// Check if clicking on target2
if (typeof target2 !== "undefined" && target2) {
var dx2 = x - target2.x;
var dy2 = y - target2.y;
var distance2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);
if (distance2 < 60) {
draggingTarget2 = true;
// Show lines for P2 cannons only
for (var i = 2; i < cannonLines.length; i++) {
cannonLines[i].alpha = 0.8;
}
}
}
}
};
// Update up handler to stop target dragging
var originalUp = game.up;
game.up = function (x, y, obj) {
// Call original up handler first
originalUp.call(this, x, y, obj);
// Stop dragging for both targets independently
if (draggingTarget1) {
// Hide lines for P1 cannons only
for (var i = 0; i < 2 && i < cannonLines.length; i++) {
cannonLines[i].alpha = 0;
}
draggingTarget1 = false;
}
if (draggingTarget2) {
// Hide lines for P2 cannons only
for (var i = 2; i < cannonLines.length; i++) {
cannonLines[i].alpha = 0;
}
draggingTarget2 = false;
}
};
// Function to update cannon lines to point at target
function updateCannonLines() {
if (!target && !target2 || !isShooting) return;
for (var i = 0; i < cannons.length; i++) {
var cannon = cannons[i];
var line = cannonLines[i];
var dx, dy;
// First two cannons/lines (P1) point to target, last two (P2) to target2
if (i < 2) {
dx = target.x - cannon.x;
dy = target.y - cannon.y;
} else {
dx = target2.x - cannon.x;
dy = target2.y - cannon.y;
}
var distance = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
line.x = cannon.x;
line.y = cannon.y;
line.rotation = angle;
line.scaleX = distance / 100; // Adjust line length
}
}
// Add shooting functionality
game.update = function () {
// Lines are now controlled by dragging, not automatic shooting
};
}
// Touch down event for inserting coins and starting game
game.down = function (x, y, obj) {
if (gameStarted) {
// (Handled by new drag logic in the other game.down handler)
return;
}
// If in ready screen (countdown), allow tap to insert coin
if (isInReadyScreen) {
// Allow inserting coin during ready screen
if (coins < maxCoins) {
coins++;
updateCreditsDisplay();
// Hide INSERT COIN text when max coins reached
if (coins >= maxCoins && insertCoinText) {
insertCoinText.alpha = 0.3; // Dim it
}
}
// Do NOT skip countdown on tap
return;
}
// Insert coin with tap
if (coins < maxCoins) {
coins++;
updateCreditsDisplay();
// Hide INSERT COIN text when max coins reached
if (coins >= maxCoins && insertCoinText) {
insertCoinText.alpha = 0.3; // Dim it
}
// Automatically go to ready screen when credit is added
if (!isInReadyScreen) {
startReadyScreen();
}
return;
}
// Start ready screen immediately when pressing start (no hold)
// Only allow starting when 2 credits are inserted
if (coins === maxCoins && !isInReadyScreen) {
startReadyScreen();
}
};
// Touch up event to stop holding
game.up = function (x, y, obj) {
if (gameStarted) {
// Only allow hold-to-skip during ready screen countdown
if (isInReadyScreen && countdownTimer && Date.now() - holdStartTime >= 1000) {
isHolding = false;
// Skip countdown
LK.clearInterval(countdownTimer);
countdownText.setText('GO!');
LK.setTimeout(function () {
if (countdownText) countdownText.alpha = 0;
isInReadyScreen = false;
// Show player ships after countdown
showPlayerShips();
// Start gameplay sequence
startGameplaySequence();
}, 500);
}
return;
}
isHolding = false;
// No hold-to-start for game; do nothing here except for hold-to-skip in ready screen
};
// Function to start ready screen
function startReadyScreen() {
gameStarted = true;
isInReadyScreen = true;
// Consume one credit when starting
coins--;
updateCreditsDisplay();
// Hide all title screen elements except creditsText (keep creditsText visible in ready screen)
if (titleText) titleText.alpha = 0;
if (numberText) numberText.alpha = 0;
if (insertCoinText) insertCoinText.alpha = 0;
if (creditsText) creditsText.alpha = 1; // Show creditsText during ready screen
if (pressStartText) pressStartText.alpha = 0;
// Make ship2 fully visible (unfade it) only if ship2 exists
if (ship2) {
tween(ship2, {
alpha: 1
}, {
duration: 500,
easing: tween.easeOut
});
}
// Show 15 second countdown
countdownText = new Text2('15', {
size: 80,
fill: 0xFF0000 // Red color
});
countdownText.anchor.set(0.5, 0.5);
countdownText.x = 2048 / 2;
countdownText.y = 400;
game.addChild(countdownText);
// Start countdown timer
var timeLeft = 15;
countdownTimer = LK.setInterval(function () {
timeLeft--;
if (timeLeft <= 0) {
LK.clearInterval(countdownTimer);
countdownText.setText('GO!');
LK.setTimeout(function () {
if (countdownText) countdownText.alpha = 0;
isInReadyScreen = false;
// Show player ships after countdown
showPlayerShips();
// Start gameplay sequence
startGameplaySequence();
}, 1000);
} else {
countdownText.setText(timeLeft.toString());
}
}, 1000);
// Game would start here - for now just clear screen
game.setBackgroundColor(0x000020); // Dark blue to show game started
}
// Function to show player ships on ready screen
function showPlayerShips() {
// Hide the hold-to-start ships
if (ship1) ship1.alpha = 0;
if (ship2) ship2.alpha = 0;
// Always show 2-player setup (force 2 players)
var shipPositions = [{
x: 100,
y: 2732 - 100
}, {
x: 100,
y: 2732 - 200
}, {
x: 2048 - 100,
y: 2732 - 100
}, {
x: 2048 - 100,
y: 2732 - 200
}];
// Create ships
for (var i = 0; i < shipPositions.length; i++) {
var ship = LK.getAsset('character', {
anchorX: 0.5,
anchorY: 0.5,
x: shipPositions[i].x,
y: shipPositions[i].y,
scaleX: 0.6,
scaleY: 0.6
});
game.addChild(ship);
}
}
// Function to start the gameplay sequence
function startGameplaySequence() {
// Show "Shoot Enemy Ships" text
var shootText = new Text2('Shoot Enemy Ships', {
size: 80,
fill: 0xFFFF00 // Yellow
});
shootText.anchor.set(0.5, 0.5);
shootText.x = 2048 / 2;
shootText.y = 2732 / 2 - 200;
game.addChild(shootText);
// After 2 seconds, show "Ready?"
LK.setTimeout(function () {
shootText.alpha = 0;
var readyText = new Text2('Ready?', {
size: 100,
fill: 0x00FF00 // Green
});
readyText.anchor.set(0.5, 0.5);
readyText.x = 2048 / 2;
readyText.y = 2732 / 2;
game.addChild(readyText);
// After 1 more second, show "Go!" and start game
LK.setTimeout(function () {
readyText.alpha = 0;
var goText = new Text2('Go!', {
size: 120,
fill: 0xFF0000 // Red
});
goText.anchor.set(0.5, 0.5);
goText.x = 2048 / 2;
goText.y = 2732 / 2;
game.addChild(goText);
// Show "Protect Cannons" with arrows at bottom
var protectText = new Text2('Protect Cannons', {
size: 60,
fill: 0xFFFFFF // White
});
protectText.anchor.set(0.5, 0.5);
protectText.x = 2048 / 2;
protectText.y = 2732 - 100;
game.addChild(protectText);
// Create left arrow
var leftArrow = new Text2('←', {
size: 80,
fill: 0xFFFFFF // White
});
leftArrow.anchor.set(0.5, 0.5);
leftArrow.x = 2048 / 2 - 300;
leftArrow.y = 2732 - 100;
game.addChild(leftArrow);
// Create right arrow
var rightArrow = new Text2('→', {
size: 80,
fill: 0xFFFFFF // White
});
rightArrow.anchor.set(0.5, 0.5);
rightArrow.x = 2048 / 2 + 300;
rightArrow.y = 2732 - 100;
game.addChild(rightArrow);
// Fade out "Go!" after a moment
LK.setTimeout(function () {
goText.alpha = 0;
// Gameplay starts here
// Always create 2-player ships (force 2 players)
// P1 ships (red) - left corners
var p1Ship1 = LK.getAsset('p1Ship', {
anchorX: 0.5,
anchorY: 0.5,
x: 100,
y: 100,
scaleX: 1,
scaleY: 1
});
game.addChild(p1Ship1);
gameplayShips.push(p1Ship1);
var p1Ship2 = LK.getAsset('p1Ship', {
anchorX: 0.5,
anchorY: 0.5,
x: 100,
y: 2732 - 100,
scaleX: 1,
scaleY: 1
});
game.addChild(p1Ship2);
gameplayShips.push(p1Ship2);
// P2 ships (blue) - right corners
var p2Ship1 = LK.getAsset('p2Ship', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 - 100,
y: 100,
scaleX: 1,
scaleY: 1
});
game.addChild(p2Ship1);
gameplayShips.push(p2Ship1);
var p2Ship2 = LK.getAsset('p2Ship', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 - 100,
y: 2732 - 100,
scaleX: 1,
scaleY: 1
});
game.addChild(p2Ship2);
gameplayShips.push(p2Ship2);
// Create target in center
target = LK.getAsset('target', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: 1,
scaleY: 1
});
game.addChild(target);
// Create a second target for P2, positioned near the right side
target2 = LK.getAsset('target', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 - 300,
y: 2732 / 2,
scaleX: 1,
scaleY: 1
});
game.addChild(target2);
// Always use 2-player: 4 cannons
var cannonPositions = [{
x: 100,
y: 2732 - 100
}, {
x: 200,
y: 2732 - 100
}, {
x: 2048 - 200,
y: 2732 - 100
}, {
x: 2048 - 100,
y: 2732 - 100
}];
for (var i = 0; i < cannonPositions.length; i++) {
var cannon = LK.getAsset('character', {
anchorX: 0.5,
anchorY: 0.5,
x: cannonPositions[i].x,
y: cannonPositions[i].y,
scaleX: 0.5,
scaleY: 0.5,
tint: 0x808080 // Gray color for cannons
});
game.addChild(cannon);
cannons.push(cannon);
}
// Create lines from cannons to target (initially hidden)
for (var i = 0; i < cannons.length; i++) {
var line = LK.getAsset('character', {
anchorX: 0,
anchorY: 0.5,
x: cannons[i].x,
y: cannons[i].y,
scaleX: 1,
scaleY: 0.02,
// Very thin to simulate a line
tint: 0xFF0000,
// Red laser color
alpha: 0 // Initially hidden
});
game.addChild(line);
cannonLines.push(line);
}
// Show score displays at the start of gameplay
showScoreDisplays();
updateScoreDisplays();
// --- INVADER WAVE SETUP ---
var invaders = [];
var invaderRows = 3;
var invaderCols = 8;
var invaderSpacingX = 180;
var invaderSpacingY = 120;
var invaderStartX = 2048 / 2 - (invaderCols - 1) * invaderSpacingX / 2;
var invaderStartY = 300;
for (var row = 0; row < invaderRows; row++) {
for (var col = 0; col < invaderCols; col++) {
var invader = new Invader();
invader.x = invaderStartX + col * invaderSpacingX;
invader.y = invaderStartY + row * invaderSpacingY;
invader.lastX = invader.x;
invader.lastY = invader.y;
game.addChild(invader);
invaders.push(invader);
}
}
// Add invader update logic to game.update
function invaderUpdateHandler() {
// Keep score displays visible and updated
if (typeof showScoreDisplays === "function" && (!p1ScoreDisplay || !p2ScoreDisplay || !highScoreDisplay)) {
showScoreDisplays();
}
updateScoreDisplays();
// Keep score displays visible and updated
if (typeof showScoreDisplays === "function" && (!p1ScoreDisplay || !p2ScoreDisplay || !highScoreDisplay)) {
showScoreDisplays();
}
updateScoreDisplays();
// Move invaders and check for collisions
for (var i = invaders.length - 1; i >= 0; i--) {
var invader = invaders[i];
invader.update();
// Check collision with target1 ONLY if P1 lines are visible (shooting)
if (target && invader.intersects(target) && cannonLines.length >= 2 && (cannonLines[0].alpha > 0 || cannonLines[1].alpha > 0)) {
invader.destroy();
invaders.splice(i, 1);
// Add 50 points to P1 score only
p1Score += 50;
if (p1Score + p2Score > highScore) highScore = p1Score + p2Score;
if (p1Score + p2Score > highScore) highScore = p1Score + p2Score;
LK.setScore(p1Score + p2Score); // Update total score for compatibility
updateScoreDisplays();
updateScoreDisplays();
continue;
}
// Check collision with target2 ONLY if P2 lines are visible (shooting)
if (typeof target2 !== "undefined" && target2 && invader.intersects(target2) && cannonLines.length >= 4 && (cannonLines[2].alpha > 0 || cannonLines[3].alpha > 0)) {
invader.destroy();
invaders.splice(i, 1);
// Add 50 points to P2 score only
p2Score += 50;
if (p1Score + p2Score > highScore) highScore = p1Score + p2Score;
if (p1Score + p2Score > highScore) highScore = p1Score + p2Score;
LK.setScore(p1Score + p2Score); // Update total score for compatibility
updateScoreDisplays();
updateScoreDisplays();
continue;
}
// Game over if invader reaches bottom
if (invader.lastY <= 2732 - 60 && invader.y > 2732 - 60) {
LK.showGameOver();
return;
}
}
// Check if all invaders from the current level are killed
if (invaders.length > 0) {
var allKilled = true;
for (var i = 0; i < invaders.length; i++) {
// Only count invaders that are visible and not destroyed
if (!invaders[i].destroyed && invaders[i].alpha !== 0) {
allKilled = false;
break;
}
}
if (allKilled) {
// Remove all old invaders before starting new level
for (var i = invaders.length - 1; i >= 0; i--) {
if (invaders[i] && typeof invaders[i].destroy === "function") {
invaders[i].destroy();
}
invaders.splice(i, 1);
}
// Add new invaders for the new level
var invaderRows = 3;
var invaderCols = 8;
var invaderSpacingX = 180;
var invaderSpacingY = 120;
var invaderStartX = 2048 / 2 - (invaderCols - 1) * invaderSpacingX / 2;
var invaderStartY = 300;
for (var row = 0; row < invaderRows; row++) {
for (var col = 0; col < invaderCols; col++) {
var invader = new Invader();
invader.x = invaderStartX + col * invaderSpacingX;
invader.y = invaderStartY + row * invaderSpacingY;
invader.lastX = invader.x;
invader.lastY = invader.y;
// Ensure invader is killable and active for this level
invader.alpha = 1;
invader.visible = true;
invader.destroyed = false;
game.addChild(invader);
invaders.push(invader);
}
}
// No stage complete popup, just continue to next level
return;
}
}
}
// Always call invaderUpdateHandler every frame
game.update = invaderUpdateHandler;
}, 1000);
}, 1000);
}, 2000);
} /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Invader class for enemy invaders
var Invader = Container.expand(function () {
var self = Container.call(this);
// Use the 'character' asset for invader for now, but tint it green
var invaderSprite = self.attachAsset('character', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0x00ff00,
scaleX: 0.7,
scaleY: 0.7
});
self.speedX = 4; // Horizontal speed
self.speedY = 0; // Vertical speed (drops down on edge)
self.lastX = self.x;
self.lastY = self.y;
self.update = function () {
// Move horizontally
self.x += self.speedX;
// Bounce off left edge
if (self.lastX >= 60 && self.x < 60) {
self.x = 60; // Clamp to edge
self.speedX = -self.speedX * 1.12; // Increase speed and reverse
// Cap speedX to a maximum of 12 (preserve sign)
if (self.speedX > 12) self.speedX = 12;
if (self.speedX < -12) self.speedX = -12;
self.y += 40; // Move down on bounce
}
// Bounce off right edge
if (self.lastX <= 2048 - 60 && self.x > 2048 - 60) {
self.x = 2048 - 60; // Clamp to edge
self.speedX = -self.speedX * 1.12; // Increase speed and reverse
// Cap speedX to a maximum of 12 (preserve sign)
if (self.speedX > 12) self.speedX = 12;
if (self.speedX < -12) self.speedX = -12;
self.y += 40; // Move down on bounce
}
self.lastX = self.x;
self.lastY = self.y;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Game state variables
var p1Score = 0;
var p2Score = 0;
// High score tracking (persistent for session)
var highScore = 0;
// Score display Text2 objects
var p1ScoreDisplay, p2ScoreDisplay, highScoreDisplay;
// Helper to show score displays at top
function showScoreDisplays() {
// Remove previous if any
if (p1ScoreDisplay) p1ScoreDisplay.alpha = 0;
if (p2ScoreDisplay) p2ScoreDisplay.alpha = 0;
if (highScoreDisplay) highScoreDisplay.alpha = 0;
// High Score
highScoreDisplay = new Text2('HIGH: ' + highScore, {
size: 60,
fill: 0xFFD700 // Gold
});
highScoreDisplay.anchor.set(0.5, 0);
highScoreDisplay.x = 2048 / 2;
highScoreDisplay.y = 20;
LK.gui.top.addChild(highScoreDisplay);
// P1 Score (left)
p1ScoreDisplay = new Text2('P1: ' + p1Score, {
size: 50,
fill: 0xFF4444
});
p1ScoreDisplay.anchor.set(0, 0);
p1ScoreDisplay.x = 120;
p1ScoreDisplay.y = 20;
LK.gui.top.addChild(p1ScoreDisplay);
// P2 Score (right)
p2ScoreDisplay = new Text2('P2: ' + p2Score, {
size: 50,
fill: 0x4444FF
});
p2ScoreDisplay.anchor.set(1, 0);
p2ScoreDisplay.x = 2048 - 120;
p2ScoreDisplay.y = 20;
LK.gui.top.addChild(p2ScoreDisplay);
}
// Helper to update score displays
function updateScoreDisplays() {
if (p1ScoreDisplay) p1ScoreDisplay.setText('P1: ' + p1Score);
if (p2ScoreDisplay) p2ScoreDisplay.setText('P2: ' + p2Score);
// High score is always the highest of P1 and P2
highScore = Math.max(p1Score, p2Score, highScore);
if (highScoreDisplay) highScoreDisplay.setText('HIGH: ' + highScore);
}
// Red ship for P1
// Blue ship for P2
// Yellow target
var coins = 0;
var maxCoins = 2; // Require 2 credits for 2 players
var gameStarted = false;
var insertCoinText;
var creditsText;
var pressStartText;
var holdStartTime = 0; // Only used for hold-to-skip in ready screen
var isHolding = false; // Only used for hold-to-skip in ready screen
var ship1;
var ship2;
var countdownText;
var countdownTimer;
var gameplayShips = [];
var target;
var target2; // Second target for P2
var cannons = [];
var cannonLines = [];
// Track dragging for both targets independently
var draggingTarget1 = false;
var draggingTarget2 = false;
var isShooting = false;
var isInReadyScreen = false;
// Create main title text "Invader Blits"
var titleText = new Text2('Invader Blits', {
size: 120,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 2048 / 2;
titleText.y = 2732 / 2 - 50;
game.addChild(titleText);
// Create the "2" text (initially hidden)
var numberText = new Text2('2', {
size: 120,
fill: 0xFFFFFF
});
numberText.anchor.set(0.5, 0.5);
numberText.x = 2048 / 2 + titleText.width / 2 + 20; // Position it after "Invader Blits"
numberText.y = 2732 / 2 - 50;
numberText.alpha = 0; // Start invisible
game.addChild(numberText);
// Position the "2" below the screen initially for slide-up animation
numberText.y = 2732 + 100; // Start below screen
numberText.alpha = 1; // Make it visible but positioned off-screen
// Show the "2" after 3 seconds with a slide-up animation
LK.setTimeout(function () {
tween(numberText, {
y: 2732 / 2 - 50 // Slide up to final position next to title
}, {
duration: 800,
easing: tween.easeOut
});
// After the "2" animation completes, show the INSERT COIN! text
LK.setTimeout(function () {
// Create "INSERT COIN!" text
insertCoinText = new Text2('INSERT COIN!', {
size: 60,
fill: 0xFFFF00 // Yellow color
});
insertCoinText.anchor.set(0.5, 0.5);
insertCoinText.x = 2048 / 2;
insertCoinText.y = 300; // Top of screen
game.addChild(insertCoinText);
// Create "0/1 CREDITS" text
creditsText = new Text2('0/1 CREDITS', {
size: 40,
fill: 0xFFFFFF // White color
});
creditsText.anchor.set(0.5, 0.5);
creditsText.x = 2048 / 2;
creditsText.y = 380; // Below INSERT COIN!
game.addChild(creditsText);
}, 800); // Wait for the "2" animation to complete
}, 3000);
// Function to update credits display
function updateCreditsDisplay() {
if (creditsText) {
creditsText.setText(coins + '/' + maxCoins + ' CREDITS');
}
// (Removed 'PRESS START' text creation for gameplay)
// Handle target dragging
game.move = function (x, y, obj) {
// Allow both targets to be dragged at the same time
// Use draggingTarget1 and draggingTarget2 to track each target's drag state
// Move target1 if dragging
if (draggingTarget1 && target) {
target.x = x;
target.y = y;
// Only update lines for P1 (first two lines)
for (var i = 0; i < 2 && i < cannons.length; i++) {
var cannon = cannons[i];
var line = cannonLines[i];
var dx = target.x - cannon.x;
var dy = target.y - cannon.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
line.x = cannon.x;
line.y = cannon.y;
line.rotation = angle;
line.scaleX = distance / 100;
}
}
// Move target2 if dragging
if (draggingTarget2 && typeof target2 !== "undefined" && target2) {
target2.x = x;
target2.y = y;
// Only update lines for P2 (last two lines)
for (var i = 2; i < cannons.length; i++) {
var cannon = cannons[i];
var line = cannonLines[i];
var dx = target2.x - cannon.x;
var dy = target2.y - cannon.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
line.x = cannon.x;
line.y = cannon.y;
line.rotation = angle;
line.scaleX = distance / 100;
}
}
};
// Update down handler to include target dragging
var originalDown = game.down;
game.down = function (x, y, obj) {
// Call original down handler first
originalDown.call(this, x, y, obj);
// Allow both targets to be picked up for dragging at the same time
if (gameStarted) {
// Check if clicking on target1
if (target) {
var dx = x - target.x;
var dy = y - target.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 60) {
draggingTarget1 = true;
// Show lines for P1 cannons only
for (var i = 0; i < 2 && i < cannonLines.length; i++) {
cannonLines[i].alpha = 0.8;
}
}
}
// Check if clicking on target2
if (typeof target2 !== "undefined" && target2) {
var dx2 = x - target2.x;
var dy2 = y - target2.y;
var distance2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);
if (distance2 < 60) {
draggingTarget2 = true;
// Show lines for P2 cannons only
for (var i = 2; i < cannonLines.length; i++) {
cannonLines[i].alpha = 0.8;
}
}
}
}
};
// Update up handler to stop target dragging
var originalUp = game.up;
game.up = function (x, y, obj) {
// Call original up handler first
originalUp.call(this, x, y, obj);
// Stop dragging for both targets independently
if (draggingTarget1) {
// Hide lines for P1 cannons only
for (var i = 0; i < 2 && i < cannonLines.length; i++) {
cannonLines[i].alpha = 0;
}
draggingTarget1 = false;
}
if (draggingTarget2) {
// Hide lines for P2 cannons only
for (var i = 2; i < cannonLines.length; i++) {
cannonLines[i].alpha = 0;
}
draggingTarget2 = false;
}
};
// Function to update cannon lines to point at target
function updateCannonLines() {
if (!target && !target2 || !isShooting) return;
for (var i = 0; i < cannons.length; i++) {
var cannon = cannons[i];
var line = cannonLines[i];
var dx, dy;
// First two cannons/lines (P1) point to target, last two (P2) to target2
if (i < 2) {
dx = target.x - cannon.x;
dy = target.y - cannon.y;
} else {
dx = target2.x - cannon.x;
dy = target2.y - cannon.y;
}
var distance = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
line.x = cannon.x;
line.y = cannon.y;
line.rotation = angle;
line.scaleX = distance / 100; // Adjust line length
}
}
// Add shooting functionality
game.update = function () {
// Lines are now controlled by dragging, not automatic shooting
};
}
// Touch down event for inserting coins and starting game
game.down = function (x, y, obj) {
if (gameStarted) {
// (Handled by new drag logic in the other game.down handler)
return;
}
// If in ready screen (countdown), allow tap to insert coin
if (isInReadyScreen) {
// Allow inserting coin during ready screen
if (coins < maxCoins) {
coins++;
updateCreditsDisplay();
// Hide INSERT COIN text when max coins reached
if (coins >= maxCoins && insertCoinText) {
insertCoinText.alpha = 0.3; // Dim it
}
}
// Do NOT skip countdown on tap
return;
}
// Insert coin with tap
if (coins < maxCoins) {
coins++;
updateCreditsDisplay();
// Hide INSERT COIN text when max coins reached
if (coins >= maxCoins && insertCoinText) {
insertCoinText.alpha = 0.3; // Dim it
}
// Automatically go to ready screen when credit is added
if (!isInReadyScreen) {
startReadyScreen();
}
return;
}
// Start ready screen immediately when pressing start (no hold)
// Only allow starting when 2 credits are inserted
if (coins === maxCoins && !isInReadyScreen) {
startReadyScreen();
}
};
// Touch up event to stop holding
game.up = function (x, y, obj) {
if (gameStarted) {
// Only allow hold-to-skip during ready screen countdown
if (isInReadyScreen && countdownTimer && Date.now() - holdStartTime >= 1000) {
isHolding = false;
// Skip countdown
LK.clearInterval(countdownTimer);
countdownText.setText('GO!');
LK.setTimeout(function () {
if (countdownText) countdownText.alpha = 0;
isInReadyScreen = false;
// Show player ships after countdown
showPlayerShips();
// Start gameplay sequence
startGameplaySequence();
}, 500);
}
return;
}
isHolding = false;
// No hold-to-start for game; do nothing here except for hold-to-skip in ready screen
};
// Function to start ready screen
function startReadyScreen() {
gameStarted = true;
isInReadyScreen = true;
// Consume one credit when starting
coins--;
updateCreditsDisplay();
// Hide all title screen elements except creditsText (keep creditsText visible in ready screen)
if (titleText) titleText.alpha = 0;
if (numberText) numberText.alpha = 0;
if (insertCoinText) insertCoinText.alpha = 0;
if (creditsText) creditsText.alpha = 1; // Show creditsText during ready screen
if (pressStartText) pressStartText.alpha = 0;
// Make ship2 fully visible (unfade it) only if ship2 exists
if (ship2) {
tween(ship2, {
alpha: 1
}, {
duration: 500,
easing: tween.easeOut
});
}
// Show 15 second countdown
countdownText = new Text2('15', {
size: 80,
fill: 0xFF0000 // Red color
});
countdownText.anchor.set(0.5, 0.5);
countdownText.x = 2048 / 2;
countdownText.y = 400;
game.addChild(countdownText);
// Start countdown timer
var timeLeft = 15;
countdownTimer = LK.setInterval(function () {
timeLeft--;
if (timeLeft <= 0) {
LK.clearInterval(countdownTimer);
countdownText.setText('GO!');
LK.setTimeout(function () {
if (countdownText) countdownText.alpha = 0;
isInReadyScreen = false;
// Show player ships after countdown
showPlayerShips();
// Start gameplay sequence
startGameplaySequence();
}, 1000);
} else {
countdownText.setText(timeLeft.toString());
}
}, 1000);
// Game would start here - for now just clear screen
game.setBackgroundColor(0x000020); // Dark blue to show game started
}
// Function to show player ships on ready screen
function showPlayerShips() {
// Hide the hold-to-start ships
if (ship1) ship1.alpha = 0;
if (ship2) ship2.alpha = 0;
// Always show 2-player setup (force 2 players)
var shipPositions = [{
x: 100,
y: 2732 - 100
}, {
x: 100,
y: 2732 - 200
}, {
x: 2048 - 100,
y: 2732 - 100
}, {
x: 2048 - 100,
y: 2732 - 200
}];
// Create ships
for (var i = 0; i < shipPositions.length; i++) {
var ship = LK.getAsset('character', {
anchorX: 0.5,
anchorY: 0.5,
x: shipPositions[i].x,
y: shipPositions[i].y,
scaleX: 0.6,
scaleY: 0.6
});
game.addChild(ship);
}
}
// Function to start the gameplay sequence
function startGameplaySequence() {
// Show "Shoot Enemy Ships" text
var shootText = new Text2('Shoot Enemy Ships', {
size: 80,
fill: 0xFFFF00 // Yellow
});
shootText.anchor.set(0.5, 0.5);
shootText.x = 2048 / 2;
shootText.y = 2732 / 2 - 200;
game.addChild(shootText);
// After 2 seconds, show "Ready?"
LK.setTimeout(function () {
shootText.alpha = 0;
var readyText = new Text2('Ready?', {
size: 100,
fill: 0x00FF00 // Green
});
readyText.anchor.set(0.5, 0.5);
readyText.x = 2048 / 2;
readyText.y = 2732 / 2;
game.addChild(readyText);
// After 1 more second, show "Go!" and start game
LK.setTimeout(function () {
readyText.alpha = 0;
var goText = new Text2('Go!', {
size: 120,
fill: 0xFF0000 // Red
});
goText.anchor.set(0.5, 0.5);
goText.x = 2048 / 2;
goText.y = 2732 / 2;
game.addChild(goText);
// Show "Protect Cannons" with arrows at bottom
var protectText = new Text2('Protect Cannons', {
size: 60,
fill: 0xFFFFFF // White
});
protectText.anchor.set(0.5, 0.5);
protectText.x = 2048 / 2;
protectText.y = 2732 - 100;
game.addChild(protectText);
// Create left arrow
var leftArrow = new Text2('←', {
size: 80,
fill: 0xFFFFFF // White
});
leftArrow.anchor.set(0.5, 0.5);
leftArrow.x = 2048 / 2 - 300;
leftArrow.y = 2732 - 100;
game.addChild(leftArrow);
// Create right arrow
var rightArrow = new Text2('→', {
size: 80,
fill: 0xFFFFFF // White
});
rightArrow.anchor.set(0.5, 0.5);
rightArrow.x = 2048 / 2 + 300;
rightArrow.y = 2732 - 100;
game.addChild(rightArrow);
// Fade out "Go!" after a moment
LK.setTimeout(function () {
goText.alpha = 0;
// Gameplay starts here
// Always create 2-player ships (force 2 players)
// P1 ships (red) - left corners
var p1Ship1 = LK.getAsset('p1Ship', {
anchorX: 0.5,
anchorY: 0.5,
x: 100,
y: 100,
scaleX: 1,
scaleY: 1
});
game.addChild(p1Ship1);
gameplayShips.push(p1Ship1);
var p1Ship2 = LK.getAsset('p1Ship', {
anchorX: 0.5,
anchorY: 0.5,
x: 100,
y: 2732 - 100,
scaleX: 1,
scaleY: 1
});
game.addChild(p1Ship2);
gameplayShips.push(p1Ship2);
// P2 ships (blue) - right corners
var p2Ship1 = LK.getAsset('p2Ship', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 - 100,
y: 100,
scaleX: 1,
scaleY: 1
});
game.addChild(p2Ship1);
gameplayShips.push(p2Ship1);
var p2Ship2 = LK.getAsset('p2Ship', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 - 100,
y: 2732 - 100,
scaleX: 1,
scaleY: 1
});
game.addChild(p2Ship2);
gameplayShips.push(p2Ship2);
// Create target in center
target = LK.getAsset('target', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
scaleX: 1,
scaleY: 1
});
game.addChild(target);
// Create a second target for P2, positioned near the right side
target2 = LK.getAsset('target', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 - 300,
y: 2732 / 2,
scaleX: 1,
scaleY: 1
});
game.addChild(target2);
// Always use 2-player: 4 cannons
var cannonPositions = [{
x: 100,
y: 2732 - 100
}, {
x: 200,
y: 2732 - 100
}, {
x: 2048 - 200,
y: 2732 - 100
}, {
x: 2048 - 100,
y: 2732 - 100
}];
for (var i = 0; i < cannonPositions.length; i++) {
var cannon = LK.getAsset('character', {
anchorX: 0.5,
anchorY: 0.5,
x: cannonPositions[i].x,
y: cannonPositions[i].y,
scaleX: 0.5,
scaleY: 0.5,
tint: 0x808080 // Gray color for cannons
});
game.addChild(cannon);
cannons.push(cannon);
}
// Create lines from cannons to target (initially hidden)
for (var i = 0; i < cannons.length; i++) {
var line = LK.getAsset('character', {
anchorX: 0,
anchorY: 0.5,
x: cannons[i].x,
y: cannons[i].y,
scaleX: 1,
scaleY: 0.02,
// Very thin to simulate a line
tint: 0xFF0000,
// Red laser color
alpha: 0 // Initially hidden
});
game.addChild(line);
cannonLines.push(line);
}
// Show score displays at the start of gameplay
showScoreDisplays();
updateScoreDisplays();
// --- INVADER WAVE SETUP ---
var invaders = [];
var invaderRows = 3;
var invaderCols = 8;
var invaderSpacingX = 180;
var invaderSpacingY = 120;
var invaderStartX = 2048 / 2 - (invaderCols - 1) * invaderSpacingX / 2;
var invaderStartY = 300;
for (var row = 0; row < invaderRows; row++) {
for (var col = 0; col < invaderCols; col++) {
var invader = new Invader();
invader.x = invaderStartX + col * invaderSpacingX;
invader.y = invaderStartY + row * invaderSpacingY;
invader.lastX = invader.x;
invader.lastY = invader.y;
game.addChild(invader);
invaders.push(invader);
}
}
// Add invader update logic to game.update
function invaderUpdateHandler() {
// Keep score displays visible and updated
if (typeof showScoreDisplays === "function" && (!p1ScoreDisplay || !p2ScoreDisplay || !highScoreDisplay)) {
showScoreDisplays();
}
updateScoreDisplays();
// Keep score displays visible and updated
if (typeof showScoreDisplays === "function" && (!p1ScoreDisplay || !p2ScoreDisplay || !highScoreDisplay)) {
showScoreDisplays();
}
updateScoreDisplays();
// Move invaders and check for collisions
for (var i = invaders.length - 1; i >= 0; i--) {
var invader = invaders[i];
invader.update();
// Check collision with target1 ONLY if P1 lines are visible (shooting)
if (target && invader.intersects(target) && cannonLines.length >= 2 && (cannonLines[0].alpha > 0 || cannonLines[1].alpha > 0)) {
invader.destroy();
invaders.splice(i, 1);
// Add 50 points to P1 score only
p1Score += 50;
if (p1Score + p2Score > highScore) highScore = p1Score + p2Score;
if (p1Score + p2Score > highScore) highScore = p1Score + p2Score;
LK.setScore(p1Score + p2Score); // Update total score for compatibility
updateScoreDisplays();
updateScoreDisplays();
continue;
}
// Check collision with target2 ONLY if P2 lines are visible (shooting)
if (typeof target2 !== "undefined" && target2 && invader.intersects(target2) && cannonLines.length >= 4 && (cannonLines[2].alpha > 0 || cannonLines[3].alpha > 0)) {
invader.destroy();
invaders.splice(i, 1);
// Add 50 points to P2 score only
p2Score += 50;
if (p1Score + p2Score > highScore) highScore = p1Score + p2Score;
if (p1Score + p2Score > highScore) highScore = p1Score + p2Score;
LK.setScore(p1Score + p2Score); // Update total score for compatibility
updateScoreDisplays();
updateScoreDisplays();
continue;
}
// Game over if invader reaches bottom
if (invader.lastY <= 2732 - 60 && invader.y > 2732 - 60) {
LK.showGameOver();
return;
}
}
// Check if all invaders from the current level are killed
if (invaders.length > 0) {
var allKilled = true;
for (var i = 0; i < invaders.length; i++) {
// Only count invaders that are visible and not destroyed
if (!invaders[i].destroyed && invaders[i].alpha !== 0) {
allKilled = false;
break;
}
}
if (allKilled) {
// Remove all old invaders before starting new level
for (var i = invaders.length - 1; i >= 0; i--) {
if (invaders[i] && typeof invaders[i].destroy === "function") {
invaders[i].destroy();
}
invaders.splice(i, 1);
}
// Add new invaders for the new level
var invaderRows = 3;
var invaderCols = 8;
var invaderSpacingX = 180;
var invaderSpacingY = 120;
var invaderStartX = 2048 / 2 - (invaderCols - 1) * invaderSpacingX / 2;
var invaderStartY = 300;
for (var row = 0; row < invaderRows; row++) {
for (var col = 0; col < invaderCols; col++) {
var invader = new Invader();
invader.x = invaderStartX + col * invaderSpacingX;
invader.y = invaderStartY + row * invaderSpacingY;
invader.lastX = invader.x;
invader.lastY = invader.y;
// Ensure invader is killable and active for this level
invader.alpha = 1;
invader.visible = true;
invader.destroyed = false;
game.addChild(invader);
invaders.push(invader);
}
}
// No stage complete popup, just continue to next level
return;
}
}
}
// Always call invaderUpdateHandler every frame
game.update = invaderUpdateHandler;
}, 1000);
}, 1000);
}, 2000);
}