/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181818
});
/****
* Game Code
****/
// --- Level Score and Level Tracker UI ---
var levelScore = 0;
var levelScoreText = new Text2("Score: 0", {
size: 110,
fill: 0xffffff,
background: 0x222222,
padding: 30,
borderRadius: 30
});
levelScoreText.anchor.set(0.5, 0);
levelScoreText.x = 2048 / 2;
levelScoreText.y = 40;
levelScoreText.visible = false;
game.addChild(levelScoreText);
// Level tracker
var currentLevel = 1;
var levelTrackerText = new Text2("Level 1", {
size: 90,
fill: 0xffffff,
background: 0x222222,
padding: 24,
borderRadius: 24
});
levelTrackerText.anchor.set(0.5, 0);
levelTrackerText.x = 2048 / 2;
levelTrackerText.y = 40 + levelScoreText.height + 10;
levelTrackerText.visible = false;
game.addChild(levelTrackerText);
// --- Mega Bar UI (vertical, right side) ---
// Only create and add the mega bar when gameplay starts (not on menu/loading)
var megaBarBg = null;
var megaBarFill = null;
function createMegaBar() {
// Remove if already present
if (megaBarBg && megaBarBg.parent) megaBarBg.parent.removeChild(megaBarBg);
if (megaBarFill && megaBarFill.parent) megaBarFill.parent.removeChild(megaBarFill);
// Make the bar much bigger
megaBarBg = LK.getAsset('box', {
width: 180,
height: 1800,
color: 0x333366,
anchorX: 1,
anchorY: 0
});
megaBarBg.x = 2048 - 40;
megaBarBg.y = 150;
game.addChild(megaBarBg);
megaBarFill = LK.getAsset('box', {
width: 144,
height: 0,
color: 0x66ccff,
anchorX: 1,
anchorY: 1
});
megaBarFill.x = 2048 - 40 + 18;
megaBarFill.y = 150 + 1800 - 18;
game.addChild(megaBarFill);
setMegaBarFill(0);
}
// Helper to set mega bar fill (0 to 1)
function setMegaBarFill(percent) {
if (!megaBarFill) return;
percent = Math.max(0, Math.min(1, percent));
megaBarFill.height = Math.floor(1764 * percent);
megaBarFill.y = 150 + 1800 - 18 - megaBarFill.height;
}
function removeMegaBar() {
if (megaBarBg && megaBarBg.parent) megaBarBg.parent.removeChild(megaBarBg);
if (megaBarFill && megaBarFill.parent) megaBarFill.parent.removeChild(megaBarFill);
megaBarBg = null;
megaBarFill = null;
}
// --- AI and Player Cannon Movement Logic ---
// Import tween plugin for animations
game.setBackgroundColor(0x000000); // Ensure black background
// Create the text, initially invisible
var madeByText = new Text2("Made By", {
size: 90,
fill: 0xFFFFFF
});
madeByText.anchor.set(0.5, 1);
madeByText.alpha = 0;
madeByText.x = 2048 / 2;
madeByText.y = 2732 / 2 - 120;
game.addChild(madeByText);
var studioText = new Text2("Pixelated Studios,", {
size: 180,
fill: 0xFFFFFF
});
studioText.anchor.set(0.5, 0.5);
studioText.alpha = 0;
studioText.x = 2048 / 2;
studioText.y = 2732 / 2;
game.addChild(studioText);
// --- SKIP BUTTON LOGIC ---
var skipBtn = new Text2("Skip", {
size: 80,
fill: 0xffffff,
background: 0x222222,
padding: 40,
borderRadius: 30
});
skipBtn.anchor.set(1, 0);
skipBtn.x = 2048 - 60;
skipBtn.y = 60;
skipBtn.alpha = 0.85;
game.addChild(skipBtn);
var skipIntro = false;
var skipTimeouts = [];
function showTitleScreen() {
// Remove skip button if present
if (skipBtn && skipBtn.parent) skipBtn.parent.removeChild(skipBtn);
// Remove all intro texts if present
if (madeByText && madeByText.parent) madeByText.parent.removeChild(madeByText);
if (studioText && studioText.parent) studioText.parent.removeChild(studioText);
if (geminiMadeByText && geminiMadeByText.parent) geminiMadeByText.parent.removeChild(geminiMadeByText);
if (geminiText && geminiText.parent) geminiText.parent.removeChild(geminiText);
if (andText && andText.parent) andText.parent.removeChild(andText);
if (upitText && upitText.parent) upitText.parent.removeChild(upitText);
// Remove mega bar if present (hide on menu)
removeMegaBar();
// Create the title text
var titleText = new Text2("Pixelossed Rush Frenzy 2", {
size: 180,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 2048 / 2;
titleText.y = 2732 / 2;
game.addChild(titleText);
// Create Multi-Player and Single-Player buttons just below the title, larger and more visible
var buttonWidth = 900;
var buttonHeight = 180;
var buttonSpacing = 60;
// Position buttons just below the title
var titleBottomY = titleText.y + titleText.height / 2 + 80;
var multiPlayerBtn = new Text2("Multi-Player", {
size: 110,
fill: 0x222222,
background: 0xFFFFFF,
padding: 60,
borderRadius: 40
});
multiPlayerBtn.anchor.set(0.5, 0.5);
multiPlayerBtn.x = 2048 / 2;
multiPlayerBtn.y = titleBottomY + buttonHeight / 2;
var singlePlayerBtn = new Text2("Single-Player", {
size: 110,
fill: 0x222222,
background: 0xFFFFFF,
padding: 60,
borderRadius: 40
});
singlePlayerBtn.anchor.set(0.5, 0.5);
singlePlayerBtn.x = 2048 / 2;
singlePlayerBtn.y = multiPlayerBtn.y + buttonHeight + buttonSpacing;
game.addChild(multiPlayerBtn);
game.addChild(singlePlayerBtn);
// Add .down event handlers to make buttons press-able
multiPlayerBtn.down = function (x, y, obj) {
LK.effects.flashObject(multiPlayerBtn, 0x00ff00, 400);
// Hide the main menu buttons and title
titleText.visible = false;
multiPlayerBtn.visible = false;
singlePlayerBtn.visible = false;
// Show the Multi-Player mode selection screen
// Create 4 Player Game button
var aiCannonsBtn = new Text2("4 Player Game", {
size: 90,
fill: 0x222222,
background: 0xFFFFFF,
padding: 50,
borderRadius: 40
});
aiCannonsBtn.anchor.set(0.5, 0.5);
aiCannonsBtn.x = 2048 / 2;
aiCannonsBtn.y = 2732 / 2 - 120;
// Create Online AI controlled Cannons button
var onlineAICannonsBtn = new Text2("Online AI controlled Cannons", {
size: 90,
fill: 0x222222,
background: 0xFFFFFF,
padding: 50,
borderRadius: 40
});
onlineAICannonsBtn.anchor.set(0.5, 0.5);
onlineAICannonsBtn.x = 2048 / 2;
onlineAICannonsBtn.y = 2732 / 2 + 120;
game.addChild(aiCannonsBtn);
game.addChild(onlineAICannonsBtn);
// Add simple flash feedback for these buttons (actual logic can be added later)
aiCannonsBtn.down = function (x, y, obj) {
LK.effects.flashObject(aiCannonsBtn, 0x00ff00, 400);
// Add logic for AI-Controlled Cannons mode here
};
onlineAICannonsBtn.down = function (x, y, obj) {
LK.effects.flashObject(onlineAICannonsBtn, 0x00ff00, 400);
// Hide the mode selection buttons
aiCannonsBtn.visible = false;
onlineAICannonsBtn.visible = false;
// --- Setup for 3 lines, 4 spaces per line ---
var lines = [];
var spacesPerLine = 4;
var totalLines = 3;
var spaceWidth = 260;
var spaceHeight = 220;
var spaceSpacingX = 80;
var spaceSpacingY = 80;
var startY = 2732 / 2 - (totalLines - 1) * (spaceHeight + spaceSpacingY) / 2 - 100;
var startX = 2048 / 2 - (spacesPerLine - 1) * (spaceWidth + spaceSpacingX) / 2;
// Store references for later if needed
var onlineCannonSpaces = [];
for (var line = 0; line < totalLines; ++line) {
var y = startY + line * (spaceHeight + spaceSpacingY);
for (var col = 0; col < spacesPerLine; ++col) {
var x = startX + col * (spaceWidth + spaceSpacingX);
// Create a container for each space
var spaceContainer = new Container();
spaceContainer.x = x;
spaceContainer.y = y;
// Draw background for the first space of the first line (P1)
if (line === 0 && col === 0) {
var bg = LK.getAsset('box', {
width: spaceWidth,
height: spaceHeight,
color: 0xff2222,
anchorX: 0.5,
anchorY: 0.5
});
spaceContainer.addChild(bg);
} else {
// Draw a neutral background for other spaces
var bg = LK.getAsset('box', {
width: spaceWidth,
height: spaceHeight,
color: 0xeeeeee,
anchorX: 0.5,
anchorY: 0.5
});
spaceContainer.addChild(bg);
}
// Add text below the space (except for first line)
var labelText = "";
if (line === 0) {
// First line: only show P1 under first space, others show "looking for players..."
if (col === 0) {
labelText = "P1";
} else {
labelText = "looking for players...";
}
} else {
// All other lines: show "looking for players..." under all spaces
labelText = "looking for players...";
}
// Only add text if not the first line's spaces (per instructions)
if (!(line === 0)) {
var label = new Text2(labelText, {
size: 48,
fill: 0x333333
});
label.anchor.set(0.5, 0);
label.x = 0;
label.y = spaceHeight / 2 + 10;
spaceContainer.addChild(label);
spaceContainer._label = label; // Store reference for later
} else if (col !== 0) {
// For first line, only add text to spaces 2,3,4
var label = new Text2(labelText, {
size: 48,
fill: 0x333333
});
label.anchor.set(0.5, 0);
label.x = 0;
label.y = spaceHeight / 2 + 10;
spaceContainer.addChild(label);
spaceContainer._label = label; // Store reference for later
} else if (col === 0) {
// For first space, add "P1" below
var p1Label = new Text2("P1", {
size: 48,
fill: 0xffffff
});
p1Label.anchor.set(0.5, 0);
p1Label.x = 0;
p1Label.y = spaceHeight / 2 + 10;
spaceContainer.addChild(p1Label);
spaceContainer._label = null; // No random name for P1
}
// Center anchor for the container
spaceContainer.pivot.x = 0;
spaceContainer.pivot.y = 0;
game.addChild(spaceContainer);
onlineCannonSpaces.push(spaceContainer);
}
}
// After 5 seconds, show random names under the spaces (except P1)
LK.setTimeout(function () {
// List of random names to pick from
var randomNames = ["PixelHero", "GeminiBot", "UpitAI", "CannonKing", "BlasterX", "NovaRush", "Firestorm", "Zapster", "RedRocket", "BlueBolt", "GreenGunner", "ShadowAI", "LaserLynx", "TurboTiger", "CyberCannon", "Vortex"];
// Shuffle the names array
for (var i = randomNames.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = randomNames[i];
randomNames[i] = randomNames[j];
randomNames[j] = temp;
}
var nameIdx = 0;
for (var i = 0; i < onlineCannonSpaces.length; ++i) {
var space = onlineCannonSpaces[i];
// Only update if this space has a label (not P1)
if (space._label) {
// Pick a random name, cycle if we run out
var name = randomNames[nameIdx % randomNames.length];
space._label.setText(name);
// Make the name more visible and white
if (space._label.style) {
space._label.style.fill = 0xffffff;
space._label.style.size = 64;
}
nameIdx++;
} else if (i === 0) {
// Make P1 label more visible and white
for (var c = 0; c < space.children.length; ++c) {
var child = space.children[c];
if (child && child.setText && child.text === "P1" && child.style) {
child.style.fill = 0xffffff;
child.style.size = 64;
}
}
}
}
// --- 10 second countdown timer in bottom right ---
var countdownTime = 10;
var countdownText = new Text2(countdownTime + "", {
size: 120,
fill: 0xffffff,
background: 0x222222,
padding: 30,
borderRadius: 40
});
countdownText.anchor.set(1, 1);
// Place in bottom right, with margin
countdownText.x = 2048 - 60;
countdownText.y = 2732 - 60;
game.addChild(countdownText);
var countdownInterval = LK.setInterval(function () {
countdownTime--;
if (countdownTime >= 0) {
countdownText.setText(countdownTime + "");
}
if (countdownTime <= 0) {
LK.clearInterval(countdownInterval);
// Optionally, you can hide or remove the timer here
// countdownText.visible = false;
// After countdown, show "Loading…" screen
// Hide all spaces and countdown
for (var i = 0; i < onlineCannonSpaces.length; ++i) {
if (onlineCannonSpaces[i] && onlineCannonSpaces[i].parent) {
onlineCannonSpaces[i].parent.removeChild(onlineCannonSpaces[i]);
}
}
if (countdownText && countdownText.parent) {
countdownText.parent.removeChild(countdownText);
}
// Show "Loading…" centered
var loadingText = new Text2("Loading…", {
size: 180,
fill: 0xffffff,
background: 0x222222,
padding: 80,
borderRadius: 60
});
loadingText.anchor.set(0.5, 0.5);
loadingText.x = 2048 / 2;
loadingText.y = 2732 / 2 - 120;
game.addChild(loadingText);
// --- Loading Bar Logic ---
var barWidth = 900;
var barHeight = 80;
var barBg = LK.getAsset('box', {
width: barWidth,
height: barHeight,
color: 0x444444,
anchorX: 0.5,
anchorY: 0.5
});
barBg.x = 2048 / 2;
barBg.y = 2732 / 2 + 80;
game.addChild(barBg);
var barFill = LK.getAsset('box', {
width: 1,
height: barHeight - 16,
color: 0xffffff,
anchorX: 0,
anchorY: 0.5
});
barFill.x = 2048 / 2 - barWidth / 2 + 8;
barFill.y = 2732 / 2 + 80;
game.addChild(barFill);
var loadingPercent = 0;
var loadingBarInterval = LK.setInterval(function () {
// Randomly increase percent by 3-18% per tick, but never above 100
var increment = Math.floor(Math.random() * 16) + 3;
loadingPercent += increment;
if (loadingPercent > 100) loadingPercent = 100;
// Animate bar width
barFill.width = Math.floor((barWidth - 16) * (loadingPercent / 100));
// Optionally, show percent text above bar
if (!barFill._percentText) {
var percentText = new Text2("0%", {
size: 60,
fill: 0xffffff,
background: 0x222222,
padding: 18,
borderRadius: 30
});
percentText.anchor.set(0.5, 1);
percentText.x = 2048 / 2;
percentText.y = barBg.y - barHeight / 2 - 10;
game.addChild(percentText);
barFill._percentText = percentText;
}
barFill._percentText.setText(loadingPercent + "%");
// If full, stop interval (simulate loading complete)
if (loadingPercent >= 100) {
LK.clearInterval(loadingBarInterval);
// Optionally, fade out loading bar and text after a short delay
LK.setTimeout(function () {
if (loadingText && loadingText.parent) loadingText.parent.removeChild(loadingText);
if (barBg && barBg.parent) barBg.parent.removeChild(barBg);
if (barFill && barFill.parent) barFill.parent.removeChild(barFill);
if (barFill._percentText && barFill._percentText.parent) barFill._percentText.parent.removeChild(barFill._percentText);
// --- After loading, show AI-controlled cannons and player cannon with names ---
// Show mega bar now that gameplay is starting
createMegaBar();
// Show level score and tracker
if (levelScoreText) {
levelScoreText.visible = true;
levelScore = 0;
levelScoreText.setText("Score: 0");
}
if (levelTrackerText) {
levelTrackerText.visible = true;
currentLevel = 1;
levelTrackerText.setText("Level 1");
}
// --- READY? GO! Sequence with TTS and Instructions ---
var gameplayPaused = true; // Pause gameplay during sequence
var readyText = new Text2("READY?", {
size: 280,
fill: 0xFFFF00,
background: 0x222222,
padding: 60,
borderRadius: 40
});
readyText.anchor.set(0.5, 0.5);
readyText.x = 2048 / 2;
readyText.y = 2732 / 2;
readyText.alpha = 0;
game.addChild(readyText);
// Fade in READY? over 600ms
tween(readyText, {
alpha: 1
}, {
duration: 600,
easing: tween.easeOut
});
// Play READY sound effect
var readySound = LK.getSound('ready');
if (readySound) readySound.play();
// Keep READY? visible for 1.5s, then fade out and show GO!
LK.setTimeout(function () {
tween(readyText, {
alpha: 0
}, {
duration: 400,
easing: tween.easeIn,
onFinish: function onFinish() {
readyText.setText("GO!");
readyText.alpha = 0;
// Fade in GO! over 400ms
tween(readyText, {
alpha: 1
}, {
duration: 400,
easing: tween.easeOut
});
// Play GO sound effect
var goSound = LK.getSound('go');
if (goSound) goSound.play();
// Keep GO! visible for 1s, then fade out and remove
LK.setTimeout(function () {
tween(readyText, {
alpha: 0
}, {
duration: 400,
easing: tween.easeIn,
onFinish: function onFinish() {
if (readyText.parent) readyText.parent.removeChild(readyText);
// Resume gameplay and show YOU label with arrow
gameplayPaused = false;
// Create YOU label positioned on top of player cannon
var youText = new Text2("YOU", {
size: 120,
fill: 0xFFFF00,
background: 0x222222,
padding: 30,
borderRadius: 24
});
youText.anchor.set(0.5, 1);
youText.x = cannonSprites[0].x;
youText.y = cannonSprites[0].y - 110;
game.addChild(youText);
youLabelRef = youText;
// Create arrow pointing down to player cannon
var arrowText = new Text2("↓", {
size: 180,
fill: 0xFFFF00
});
arrowText.anchor.set(0.5, 0.5);
arrowText.x = cannonSprites[0].x;
arrowText.y = cannonSprites[0].y - 150;
game.addChild(arrowText);
arrowLabelRef = arrowText;
// Animate arrow bouncing
function animateArrowBounce() {
tween(arrowText, {
y: arrowText.y + 30
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(arrowText, {
y: cannonSprites[0].y - 150
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: animateArrowBounce
});
}
});
}
animateArrowBounce();
// Create blinking instruction text
var instructionText = new Text2("shoot every single invader!", {
size: 100,
fill: 0xFFFF00,
background: 0x222222,
padding: 30,
borderRadius: 24
});
instructionText.anchor.set(0.5, 0.5);
instructionText.x = 2048 / 2;
instructionText.y = 2732 / 2 + 200;
instructionText.alpha = 1;
game.addChild(instructionText);
// Blink animation
function blinkInstruction() {
tween(instructionText, {
alpha: 0.2
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(instructionText, {
alpha: 1
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: blinkInstruction
});
}
});
}
blinkInstruction();
// Fade out YOU, arrow, and instruction after 4 seconds
LK.setTimeout(function () {
tween(youText, {
alpha: 0
}, {
duration: 600,
easing: tween.easeIn,
onFinish: function onFinish() {
if (youText.parent) youText.parent.removeChild(youText);
}
});
tween(arrowText, {
alpha: 0
}, {
duration: 600,
easing: tween.easeIn,
onFinish: function onFinish() {
if (arrowText.parent) arrowText.parent.removeChild(arrowText);
}
});
tween(instructionText, {
alpha: 0
}, {
duration: 600,
easing: tween.easeIn,
onFinish: function onFinish() {
if (instructionText.parent) instructionText.parent.removeChild(instructionText);
}
});
}, 4000);
}
});
}, 1000);
}
});
}, 1500);
// We'll use the same random names as before, and show them above the AI cannons
// Get the names that were picked for the AI cannons (from previous randomization)
var aiCannonNames = [];
for (var i = 0; i < onlineCannonSpaces.length; ++i) {
var space = onlineCannonSpaces[i];
if (space && space._label && space._label.text) {
aiCannonNames.push(space._label.text);
}
}
// The first is always "P1" (player), the rest are AI names
// We'll show 12 cannons in 2 rows of 6, player on the top left, 11 AI to the right and below
var cannonCount = 12;
var cannonsPerRow = 6;
var cannonSpacingX = 320;
var cannonSpacingY = 340;
// Move all cannons to the bottom and restrict movement to left/right only
var cannonYStart = 2732 - 220; // Place all cannons near the bottom of the screen
var cannonXStart = 2048 / 2 - (cannonsPerRow - 1) * cannonSpacingX / 2;
// Store references for later if needed
var cannonSprites = [];
var cannonNameLabels = [];
var cannonSuperBars = []; // Super bar UI for each cannon
// Prepare a list of random names for AI cannons (excluding P1)
var aiCannonRandomNames = ["PixelHero", "GeminiBot", "UpitAI", "CannonKing", "BlasterX", "NovaRush", "Firestorm", "Zapster", "RedRocket", "BlueBolt", "GreenGunner", "ShadowAI", "LaserLynx", "TurboTiger", "CyberCannon", "Vortex", "IronBlast", "MegaShot", "ThunderCore", "BlastMaster", "Rocketron", "SteelBarrel", "PyroPulse", "BoltBlaze"];
// Shuffle the names array
for (var i = aiCannonRandomNames.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = aiCannonRandomNames[i];
aiCannonRandomNames[i] = aiCannonRandomNames[j];
aiCannonRandomNames[j] = temp;
}
var aiCannonNameIdx = 0;
for (var i = 0; i < cannonCount; ++i) {
var row = Math.floor(i / cannonsPerRow);
var col = i % cannonsPerRow;
var x = cannonXStart + col * cannonSpacingX;
var y = cannonYStart + row * cannonSpacingY;
// Create a cannon (use 'box' asset for now)
var cannonColor = i === 0 ? 0xff2222 : 0xeeeeee;
var cannon = LK.getAsset('box', {
width: 180,
height: 180,
color: cannonColor,
anchorX: 0.5,
anchorY: 1
});
cannon.x = x;
cannon.y = y;
game.addChild(cannon);
cannonSprites.push(cannon);
// Add the name above the cannon
var nameText = "";
if (i === 0) {
nameText = "P1";
} else {
// Assign a random name from the shuffled list, cycling if needed
nameText = aiCannonRandomNames[aiCannonNameIdx % aiCannonRandomNames.length];
aiCannonNameIdx++;
}
var nameLabel = new Text2(nameText, {
size: 80,
fill: 0xffffff,
background: 0x222222,
padding: 24,
borderRadius: 30
});
nameLabel.anchor.set(0.5, 1);
nameLabel.x = x;
nameLabel.y = y - 200;
game.addChild(nameLabel);
cannonNameLabels.push(nameLabel);
// --- Super Bar (above each cannon) ---
var superBarBg = LK.getAsset('box', {
width: 160,
height: 24,
color: 0x222222,
anchorX: 0.5,
anchorY: 1
});
superBarBg.x = x;
superBarBg.y = y - 220;
game.addChild(superBarBg);
var superBarFill = LK.getAsset('box', {
width: 152,
height: 16,
color: 0x66ff66,
anchorX: 0.5,
anchorY: 1
});
superBarFill.x = x;
superBarFill.y = y - 220 + 4;
game.addChild(superBarFill);
// Helper to set super bar fill (0 to 1)
superBarFill._setFill = function (percent) {
percent = Math.max(0, Math.min(1, percent));
superBarFill.width = Math.floor(152 * percent);
};
superBarFill._setFill(1); // Start full
cannonSuperBars.push({
bg: superBarBg,
fill: superBarFill
});
}
// Show the number of cannons below the rows, centered
var cannonCountText = new Text2("Cannons: " + cannonCount, {
size: 80,
fill: 0xffffff,
background: 0x222222,
padding: 24,
borderRadius: 30
});
cannonCountText.anchor.set(0.5, 0);
cannonCountText.x = 2048 / 2;
cannonCountText.y = cannonYStart + 2 * cannonSpacingY + 40;
game.addChild(cannonCountText);
// --- AI and Player Cannon Movement Logic ---
// We'll move all cannons (including player) on the bottom row, and make AI move like a player
// Only bottom row (row 1, i=6..11) is movable, but for this task, move all cannons (including player) on the bottom
// Store cannon state for movement
var cannonStates = [];
for (var i = 0; i < cannonSprites.length; ++i) {
// All cannons start with a random direction and speed
var isPlayer = i === 0;
cannonStates.push({
vx: (Math.random() * 2 - 1) * 6,
// -6 to 6 px/frame
vy: 0,
targetX: cannonSprites[i].x,
targetY: cannonSprites[i].y,
moveCooldown: 0,
isPlayer: isPlayer,
aiMoveTimer: 0,
aiTargetX: cannonSprites[i].x,
aiTargetY: cannonSprites[i].y,
shootCooldown: Math.floor(Math.random() * 30) + 30 // randomize initial shoot cooldown (0.5s-1s)
});
}
// --- Automatic Shooting for All Cannons ---
// We'll create a bullet asset and manage bullets in an array
var bulletAssetColor = 0x44aaff;
var bulletWidth = 32;
var bulletHeight = 80;
var bulletSpeed = -32; // negative Y, shoots upward
var bullets = [];
// --- Super Bar Percentage Tracking ---
var superBarPercents = [];
for (var i = 0; i < 12; ++i) {
superBarPercents.push(0); // Start empty (0%)
}
// Helper to create a bullet at (x, y)
function createBullet(x, y, color) {
var bullet = LK.getAsset('box', {
width: bulletWidth,
height: bulletHeight,
color: color || bulletAssetColor,
anchorX: 0.5,
anchorY: 1
});
bullet.x = x;
bullet.y = y - 90; // start just above the cannon
bullet._vy = bulletSpeed;
// Track which cannon shot this bullet (for super bar)
bullet._shooterIdx = null;
return bullet;
}
// Helper to show a +1% popup above a super bar
function showSuperBarPopup(idx, amount) {
if (!cannonSuperBars[idx]) return;
var bar = cannonSuperBars[idx].fill;
var cannonLabel = cannonNameLabels[idx];
var popup = new Text2("+" + amount + "%", {
size: 54,
fill: 0xffff00,
background: 0x222222,
padding: 12,
borderRadius: 18
});
popup.anchor.set(0.5, 1);
popup.x = bar.x;
popup.y = bar.y - 16;
game.addChild(popup);
// Show the name of the cannon above the popup
var namePopup = null;
if (cannonLabel && cannonLabel.text) {
namePopup = new Text2(cannonLabel.text, {
size: 38,
fill: 0xffffff,
background: 0x222222,
padding: 8,
borderRadius: 12
});
namePopup.anchor.set(0.5, 1);
namePopup.x = bar.x;
namePopup.y = popup.y - 48;
game.addChild(namePopup);
}
// Animate popup up and fade out
tween(popup, {
y: popup.y - 60,
alpha: 0
}, {
duration: 900,
easing: tween.easeOut,
onComplete: function onComplete() {
if (popup.parent) popup.parent.removeChild(popup);
}
});
if (namePopup) {
tween(namePopup, {
y: namePopup.y - 60,
alpha: 0
}, {
duration: 900,
easing: tween.easeOut,
onComplete: function onComplete() {
if (namePopup.parent) namePopup.parent.removeChild(namePopup);
}
});
}
}
// Helper: clamp value
function clamp(val, min, max) {
return Math.max(min, Math.min(max, val));
}
// Movement bounds (keep cannons inside visible area)
var minX = 120;
var maxX = 2048 - 120;
var minY = cannonYStart - 40;
var maxY = cannonYStart + cannonSpacingY + 40;
// Simulate "player" movement (bottom left cannon, i=0) as AI for now
// All other cannons are AI
// Animate movement every frame
game.update = function () {
// --- Pause gameplay during READY? GO! sequence ---
if (gameplayPaused) {
return;
}
// --- Levels continue indefinitely (no level 8 end condition) ---
// Game continues to spawn invaders at increasing difficulty
// --- Hide gameplay elements at level 8 and above ---
if (typeof game._elementsHiddenForLevel8 === "undefined") game._elementsHiddenForLevel8 = false;
if (currentLevel >= 8 && !game._elementsHiddenForLevel8) {
// Hide all cannons
for (var i = 0; i < cannonSprites.length; ++i) {
if (cannonSprites[i] && cannonSprites[i].parent) cannonSprites[i].parent.removeChild(cannonSprites[i]);
if (cannonNameLabels[i] && cannonNameLabels[i].parent) cannonNameLabels[i].parent.removeChild(cannonNameLabels[i]);
if (cannonSuperBars[i]) {
if (cannonSuperBars[i].bg && cannonSuperBars[i].bg.parent) cannonSuperBars[i].bg.parent.removeChild(cannonSuperBars[i].bg);
if (cannonSuperBars[i].fill && cannonSuperBars[i].fill.parent) cannonSuperBars[i].fill.parent.removeChild(cannonSuperBars[i].fill);
if (cannonSuperBars[i]._percentText && cannonSuperBars[i]._percentText.parent) cannonSuperBars[i]._percentText.parent.removeChild(cannonSuperBars[i]._percentText);
if (cannonSuperBars[i]._superCountdown && cannonSuperBars[i]._superCountdown.text && cannonSuperBars[i]._superCountdown.text.parent) cannonSuperBars[i]._superCountdown.text.parent.removeChild(cannonSuperBars[i]._superCountdown.text);
}
}
// Hide all bullets
for (var b = 0; b < bullets.length; ++b) {
if (bullets[b] && bullets[b].parent) bullets[b].parent.removeChild(bullets[b]);
}
bullets = [];
// Hide mega bar
removeMegaBar();
// Hide super shot laser if present
if (superShotLaser && superShotLaser.parent) superShotLaser.parent.removeChild(superShotLaser);
superShotLaser = null;
// Mark as hidden so we don't repeat
game._elementsHiddenForLevel8 = true;
}
// --- Cannon Movement and Name Label Update ---
for (var i = 0; i < cannonSprites.length; ++i) {
var cannon = cannonSprites[i];
var state = cannonStates[i];
// Hitbox visualization for cannons removed to reduce lag
// --- Super Bar UI follows cannon ---
if (cannonSuperBars[i]) {
cannonSuperBars[i].bg.x = cannon.x;
cannonSuperBars[i].bg.y = cannon.y - 220;
cannonSuperBars[i].fill.x = cannon.x;
cannonSuperBars[i].fill.y = cannon.y - 220 + 4;
}
// Animate super bar fill and show percentage
if (cannonSuperBars[i]) {
// Set fill to actual value
var percent = superBarPercents[i];
// --- Play 'super' sound when bar fills to 100% (from <1 to >=1) ---
if (typeof cannonSuperBars[i]._lastPercent === "undefined") cannonSuperBars[i]._lastPercent = 0;
if (cannonSuperBars[i]._lastPercent < 1 && percent >= 1) {
// Play the "super" sound
var superSound = LK.getSound('super');
if (superSound) superSound.play();
}
cannonSuperBars[i]._lastPercent = percent;
cannonSuperBars[i].fill._setFill(percent);
// Show percentage text above the cannon name (and above the bar)
if (!cannonSuperBars[i]._percentText) {
var pctText = new Text2("100%", {
size: 90,
// bigger size
fill: 0xffffff,
background: 0x222222,
padding: 16,
borderRadius: 18
});
pctText.anchor.set(0.5, 1);
// Place above the name label if available, else above the bar
if (cannonNameLabels[i]) {
pctText.x = cannonNameLabels[i].x;
pctText.y = cannonNameLabels[i].y - 18;
} else {
pctText.x = cannonSuperBars[i].fill.x;
pctText.y = cannonSuperBars[i].fill.y - 60;
}
game.addChild(pctText);
cannonSuperBars[i]._percentText = pctText;
}
// Update percent text and position
if (cannonSuperBars[i]._percentText) {
cannonSuperBars[i]._percentText.setText(Math.floor(percent * 100) + "%");
// Place above the name label if available, else above the bar
if (cannonNameLabels[i]) {
cannonSuperBars[i]._percentText.x = cannonNameLabels[i].x;
cannonSuperBars[i]._percentText.y = cannonNameLabels[i].y - 18;
} else {
cannonSuperBars[i]._percentText.x = cannonSuperBars[i].fill.x;
cannonSuperBars[i]._percentText.y = cannonSuperBars[i].fill.y - 60;
}
}
// --- Show 10s countdown over cannon if super bar is 100% and not already counting down ---
if (!cannonSuperBars[i]._superCountdown && percent >= 1) {
// Create countdown text
var cdText = new Text2("10", {
size: 90,
fill: 0xffee00,
background: 0x222222,
padding: 18,
borderRadius: 24
});
cdText.anchor.set(0.5, 0.5);
cdText.x = cannonSprites[i].x;
cdText.y = cannonSprites[i].y - 120;
game.addChild(cdText);
cannonSuperBars[i]._superCountdown = {
text: cdText,
time: 10 * 60 // 10 seconds at 60fps
};
// If this is P1, enable super shot for 10s and reset bar
if (i === 0 && !superShotActive) {
superShotActive = true;
superShotTimer = 10 * 60;
// Remove any previous laser
if (superShotLaser && superShotLaser.parent) superShotLaser.parent.removeChild(superShotLaser);
var p1 = cannonSprites[0];
superShotLaser = LK.getAsset('box', {
width: 40,
height: 2732,
color: 0xff2222,
anchorX: 0.5,
anchorY: 1
});
superShotLaser.x = p1.x;
superShotLaser.y = p1.y - p1.height;
superShotLaser.alpha = 0.7;
game.addChild(superShotLaser);
// Reset super bar percent
superBarPercents[0] = 0;
cannonSuperBars[0].fill._setFill(0);
if (cannonSuperBars[0]._percentText) cannonSuperBars[0]._percentText.setText("0%");
}
}
// If counting down, update timer and text
if (cannonSuperBars[i]._superCountdown) {
var sc = cannonSuperBars[i]._superCountdown;
sc.text.x = cannonSprites[i].x;
sc.text.y = cannonSprites[i].y - 120;
sc.time--;
var secondsLeft = Math.ceil(sc.time / 60);
sc.text.setText(secondsLeft + "");
if (sc.time <= 0) {
// Countdown finished, remove text and reset super bar
if (sc.text.parent) sc.text.parent.removeChild(sc.text);
cannonSuperBars[i]._superCountdown = null;
superBarPercents[i] = 0;
cannonSuperBars[i].fill._setFill(0);
if (cannonSuperBars[i]._percentText) cannonSuperBars[i]._percentText.setText("0%");
// If this is P1, also end super shot if still active
if (i === 0 && superShotActive) {
superShotActive = false;
if (superShotLaser && superShotLaser.parent) superShotLaser.parent.removeChild(superShotLaser);
superShotLaser = null;
}
}
}
}
if (i === 0) {
// P1 cannon: do not move by AI, position is set by hand/touch events
// Just update the name label to follow the cannon
if (cannonNameLabels[i]) {
cannonNameLabels[i].x = cannon.x;
cannonNameLabels[i].y = cannon.y - 200;
}
} else {
// AI logic: every 1.2-2.5s, pick the closest invader (or the only one) as target
if (state.aiMoveTimer <= 0) {
var closestInvader = null;
var closestDist = Infinity;
if (game._invaders && game._invaders.length > 0) {
for (var ai_ci = 0; ai_ci < game._invaders.length; ++ai_ci) {
var ai_inv = game._invaders[ai_ci];
if (!ai_inv._alive) continue;
var dx = ai_inv.x - cannon.x;
var dy = ai_inv.y - cannon.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < closestDist) {
closestDist = dist;
closestInvader = ai_inv;
}
}
}
// If found, target its X position, else random
if (closestInvader) {
state.aiTargetX = clamp(closestInvader.x, minX, maxX);
} else {
state.aiTargetX = clamp(cannon.x + (Math.random() * 2 - 1) * 400, minX, maxX);
}
state.aiTargetY = cannonYStart; // Always at the bottom
state.aiMoveTimer = 72 + Math.floor(Math.random() * 60); // 1.2s to 2.2s at 60fps
} else {
state.aiMoveTimer--;
}
// Move towards target (only X axis, Y is fixed)
var dx = state.aiTargetX - cannon.x;
var dist = Math.abs(dx);
var speed = 10 + Math.random() * 2; // px per frame, a bit jittery
if (dist > 2) {
cannon.x += dx / dist * Math.min(speed, dist);
cannon.y = cannonYStart; // Always at the bottom
// Also move the name label
if (cannonNameLabels[i]) {
cannonNameLabels[i].x = cannon.x;
cannonNameLabels[i].y = cannon.y - 200;
}
} else {
// Snap to target if close
cannon.x = state.aiTargetX;
cannon.y = cannonYStart; // Always at the bottom
if (cannonNameLabels[i]) {
cannonNameLabels[i].x = cannon.x;
cannonNameLabels[i].y = cannon.y - 200;
}
}
}
// --- Automatic Shooting ---
if (state.shootCooldown > 0) {
state.shootCooldown--;
} else {
// Shoot a bullet
var bulletColor = i === 0 ? 0xff2222 : bulletAssetColor;
var bullet = createBullet(cannon.x, cannon.y, bulletColor);
bullet._shooterIdx = i;
game.addChild(bullet);
bullets.push(bullet);
// Reset shoot cooldown (randomize a bit for AI, fixed for player)
if (i === 0) {
// P1: fire rate increases with each shot, min 6 frames (0.1s)
if (typeof state.minShootCooldown === "undefined") state.minShootCooldown = 30;
if (typeof state.shotsFired === "undefined") state.shotsFired = 0;
state.shotsFired++;
// Decrease cooldown by 1 frame per shot, but not below 6
state.minShootCooldown = Math.max(6, 30 - state.shotsFired);
state.shootCooldown = state.minShootCooldown;
} else {
state.shootCooldown = 24 + Math.floor(Math.random() * 24); // AI: 0.4s-0.8s
}
}
}
// --- Animate mega bar fill (demo pulse) ---
if (megaBarFill) {
var megaPercent = 0.5 + 0.5 * Math.sin(LK.ticks / 120 * Math.PI * 2);
setMegaBarFill(megaPercent);
}
// --- Bullet Movement and Cleanup ---
for (var b = bullets.length - 1; b >= 0; --b) {
var bullet = bullets[b];
bullet.y += bullet._vy;
// Hitbox visualization for bullets removed to reduce lag
// --- Add Invaders (move and draw) ---
// We'll create invaders if not already present
if (!game._invadersInitialized) {
game._invaders = [];
// Scale invader rows and columns with currentLevel, but clamp to reasonable max
var invaderRows = Math.min(3 + Math.floor((currentLevel - 1) / 2), 8);
var invaderCols = Math.min(8 + Math.floor((currentLevel - 1) / 1.5), 16);
// Cap total invaders to prevent lag
var maxTotalInvaders = 40;
var targetInvaders = Math.min(invaderRows * invaderCols, maxTotalInvaders);
invaderRows = Math.ceil(targetInvaders / invaderCols);
if (invaderRows * invaderCols > maxTotalInvaders) {
invaderCols = Math.floor(maxTotalInvaders / invaderRows);
}
var invaderSpacingX = 180;
var invaderSpacingY = 160;
var invaderStartX = 2048 / 2 - (invaderCols - 1) * invaderSpacingX / 2;
var invaderStartY = 320;
for (var ir = 0; ir < invaderRows; ++ir) {
for (var ic = 0; ic < invaderCols; ++ic) {
// Place huge invaders in the center columns of the first row (only if enough columns)
if (ir === 0 && invaderCols >= 6 && (ic === Math.floor(invaderCols / 2) - 1 || ic === Math.floor(invaderCols / 2))) {
var hugeInv = createHugeInvader(invaderStartX + ic * invaderSpacingX, invaderStartY + ir * invaderSpacingY, 3, 0xffa500);
game.addChild(hugeInv);
game._invaders.push(hugeInv);
} else {
// Add red invaders: every 3rd invader in each row is red and 2x faster
var isRed = ic % 3 === 2;
var invaderColor = isRed ? 0xff2222 : 0x33ff99;
var invader = LK.getAsset('box', {
width: 120,
height: 80,
color: invaderColor,
anchorX: 0.5,
anchorY: 0.5
});
invader.x = invaderStartX + ic * invaderSpacingX;
invader.y = invaderStartY + ir * invaderSpacingY;
invader._vx = (2 + Math.random() * 2) * (isRed ? 2 : 1);
invader._vy = 0;
invader._dir = Math.random() > 0.5 ? 1 : -1;
invader._row = ir;
invader._col = ic;
invader._alive = true;
invader._lastX = invader.x;
invader._lastY = invader.y;
invader._isHuge = false;
invader._isRed = isRed;
game.addChild(invader);
game._invaders.push(invader);
}
}
}
game._invadersInitialized = true;
}
// Move invaders (simple left-right bounce)
// Each bounce, all invaders get faster
if (game._invaders) {
var bounced = false;
for (var ii = 0; ii < game._invaders.length; ++ii) {
var inv = game._invaders[ii];
if (!inv._alive) {
// Hide hitbox if invader is dead
if (inv._hitboxRect && inv._hitboxRect.parent) inv._hitboxRect.visible = false;
continue;
}
// Hitbox visualization for invaders removed to reduce lag
inv._lastX = inv.x;
inv._lastY = inv.y;
inv.x += inv._vx * inv._dir;
// Bounce at screen edges
if (inv.x < 120) {
inv.x = 120;
inv._dir = 1;
bounced = true;
// Move invader lower on bounce
inv.y += 60;
}
if (inv.x > 2048 - 120) {
inv.x = 2048 - 120;
inv._dir = -1;
bounced = true;
// Move invader lower on bounce
inv.y += 60;
}
// Teleport invader to top if it hits the bottom of the screen
if (inv.y > 2732 - inv.height / 2) {
inv.y = inv.height / 2 + 10;
}
}
// If any invader bounced, increase speed of all invaders
if (bounced) {
for (var ii = 0; ii < game._invaders.length; ++ii) {
var inv = game._invaders[ii];
if (!inv._alive) continue;
// Red invaders always 2x faster
if (inv._isHuge) {
inv._vx *= 1.08; // Huge invaders: increase speed moderately
} else if (inv._isRed) {
inv._vx *= 1.16; // Red invaders: increase speed more
} else {
inv._vx *= 1.08; // Normal invaders: increase speed
}
// Clamp to a reasonable max speed
if (inv._vx > 32) inv._vx = 32;
}
}
}
// --- Check collision with invaders (all invaders) ---
var hitInvader = -1;
if (game._invaders) {
for (var ci = 0; ci < game._invaders.length; ++ci) {
var invader = game._invaders[ci];
if (!invader._alive) continue;
// Use .intersects for collision detection
if (typeof bullet.lastWasIntersectingInvaders === "undefined") bullet.lastWasIntersectingInvaders = [];
if (typeof bullet.lastWasIntersectingInvaders[ci] === "undefined") bullet.lastWasIntersectingInvaders[ci] = false;
var isIntersecting = bullet.intersects(invader);
// Only trigger on the exact frame of collision
if (!bullet.lastWasIntersectingInvaders[ci] && isIntersecting) {
hitInvader = ci;
bullet.lastWasIntersectingInvaders[ci] = isIntersecting;
break;
}
bullet.lastWasIntersectingInvaders[ci] = isIntersecting;
}
}
if (hitInvader !== -1 && bullet._shooterIdx !== null) {
// Only increment super bar if shooter exists and hit an invader
var shooterIdx = bullet._shooterIdx;
superBarPercents[shooterIdx] = clamp(superBarPercents[shooterIdx] + 0.01, 0, 1);
if (cannonSuperBars[shooterIdx] && cannonSuperBars[shooterIdx].fill && typeof cannonSuperBars[shooterIdx].fill._setFill === "function") {
cannonSuperBars[shooterIdx].fill._setFill(superBarPercents[shooterIdx]);
}
showSuperBarPopup(shooterIdx, 1);
// Remove bullet
if (bullet.parent) bullet.parent.removeChild(bullet);
bullets.splice(b, 1);
// Remove invader
var inv = game._invaders[hitInvader];
if (inv && inv.parent) {
// If it's a huge invader, split it!
if (inv._isHuge && inv._size > 1) {
// Split into two smaller invaders of size-1
var splitSize = inv._size - 1;
var offset = 80 * splitSize;
for (var s = -1; s <= 1; s += 2) {
var splitInv = createHugeInvader(inv.x + s * offset, inv.y, splitSize, splitSize === 2 ? 0xffd700 : 0xff69b4 // gold for big, pink for small
);
// Give them a little horizontal velocity away from center
splitInv._vx = 2.5 * s;
splitInv._dir = s;
game.addChild(splitInv);
game._invaders.push(splitInv);
}
}
inv._alive = false;
inv.parent.removeChild(inv);
// Play hit sound
var hitSound = LK.getSound('hit');
if (hitSound) hitSound.play();
// --- Increment and show level score ---
levelScore += 1;
if (levelScoreText) {
levelScoreText.setText("Score: " + levelScore);
}
}
// --- Check if all invaders are destroyed, then start next level ---
var allDead = true;
for (var checki = 0; checki < game._invaders.length; ++checki) {
if (game._invaders[checki]._alive) {
allDead = false;
break;
}
}
if (allDead) {
// Remove any remaining invader objects from stage
for (var remi = 0; remi < game._invaders.length; ++remi) {
if (game._invaders[remi].parent) game._invaders[remi].parent.removeChild(game._invaders[remi]);
}
// Next level: re-initialize invaders, increase difficulty if desired
game._invaders = [];
game._invadersInitialized = false; // Ensure this is reset so new invaders spawn next frame
// Boss logic removed - no boss will appear, only invaders and normal level progression.
// Advance level and update tracker
currentLevel++;
// Play level up sound
var levelupSound = LK.getSound('levelup');
if (levelupSound) levelupSound.play();
if (levelTrackerText) {
levelTrackerText.setText("Level " + currentLevel);
}
// Optionally, reset or keep score as desired (here, keep score)
}
continue;
}
// Remove bullet if off screen
if (bullet.y + bullet.height < 0) {
if (bullet.parent) bullet.parent.removeChild(bullet);
bullets.splice(b, 1);
}
}
// --- Power-Up Logic ---
// Power-up types
var powerUpTypes = [{
name: "Super Filler",
color: 0xffe066
}, {
name: "Healer",
color: 0x66ffb3
}, {
name: "Shield",
color: 0x66aaff
}];
var powerUps = [];
var powerUpWidth = 120;
var powerUpHeight = 120;
var powerUpSpeed = 8; // px per frame, falls down
// Helper to create a power-up at (x, y) with a type index
function createPowerUp(x, y, typeIdx) {
var type = powerUpTypes[typeIdx];
var pu = LK.getAsset('box', {
width: powerUpWidth,
height: powerUpHeight,
color: type.color,
anchorX: 0.5,
anchorY: 0.5
});
pu.x = x;
pu.y = y;
pu._vy = powerUpSpeed;
pu._typeIdx = typeIdx;
// Add label
var label = new Text2(type.name, {
size: 38,
fill: 0x222222,
background: 0xffffff,
padding: 10,
borderRadius: 18
});
label.anchor.set(0.5, 0.5);
label.x = 0;
label.y = 0;
pu.addChild(label);
return pu;
}
// Power-up spawn timer
var powerUpInterval = 1200; // 20 seconds at 60fps
var powerUpTimer = powerUpInterval;
game.updatePowerUps = function () {
// Spawn logic
powerUpTimer--;
if (powerUpTimer <= 0) {
// Pick random type
var idx = Math.floor(Math.random() * powerUpTypes.length);
// Spawn at random X along the top, not too close to edges
var px = Math.floor(Math.random() * (maxX - minX - 200)) + minX + 100;
var py = -powerUpHeight / 2;
var pu = createPowerUp(px, py, idx);
game.addChild(pu);
powerUps.push(pu);
powerUpTimer = powerUpInterval;
}
// Move power-ups
for (var p = powerUps.length - 1; p >= 0; --p) {
var pu = powerUps[p];
pu.y += pu._vy;
// Hitbox visualization for powerups removed to reduce lag
// Remove if off screen
if (pu.y - powerUpHeight / 2 > 2732) {
if (pu.parent) pu.parent.removeChild(pu);
powerUps.splice(p, 1);
continue;
}
// TODO: Add collision logic with cannons if needed
}
};
// Call power-up update each frame
game.updatePowerUps();
};
// --- P1 Cannon Hand/Touch Movement Logic ---
var p1Dragging = false;
// Double tap detection for super shot
var lastTapTime = 0;
var doubleTapThreshold = 400; // ms
var superShotActive = false;
var superShotTimer = 0;
var superShotDuration = 60; // 1 second at 60fps
var superShotLaser = null;
game.down = function (x, y, obj) {
// Only allow drag if touch/click is within P1 cannon bounds
var p1 = cannonSprites[0];
// Use bounding box for hit test
var halfW = p1.width / 2;
var halfH = p1.height;
var now = Date.now();
if (x >= p1.x - halfW && x <= p1.x + halfW && y >= p1.y - halfH && y <= p1.y) {
// Double tap detection for super shot
if (superBarPercents[0] >= 1 && !superShotActive) {
if (now - lastTapTime < doubleTapThreshold) {
// Activate super shot for 10 seconds!
superShotActive = true;
superShotTimer = 10 * 60; // 10 seconds at 60fps
// Remove countdown if present
if (cannonSuperBars[0]._superCountdown && cannonSuperBars[0]._superCountdown.text.parent) {
cannonSuperBars[0]._superCountdown.text.parent.removeChild(cannonSuperBars[0]._superCountdown.text);
}
cannonSuperBars[0]._superCountdown = null;
// Show laser
if (superShotLaser && superShotLaser.parent) superShotLaser.parent.removeChild(superShotLaser);
superShotLaser = LK.getAsset('box', {
width: 40,
height: 2732,
color: 0xff2222,
anchorX: 0.5,
anchorY: 1
});
superShotLaser.x = p1.x;
superShotLaser.y = p1.y - p1.height;
superShotLaser.alpha = 0.7;
game.addChild(superShotLaser);
// Reset super bar percent
superBarPercents[0] = 0;
cannonSuperBars[0].fill._setFill(0);
if (cannonSuperBars[0]._percentText) cannonSuperBars[0]._percentText.setText("0%");
}
lastTapTime = now;
} else {
lastTapTime = now;
}
p1Dragging = true;
}
};
game.move = function (x, y, obj) {
if (p1Dragging) {
// Clamp movement to allowed bounds, only X axis, Y is fixed at bottom
var p1 = cannonSprites[0];
var halfW = p1.width / 2;
var newX = clamp(x, minX, maxX);
p1.x = newX;
p1.y = cannonYStart; // Always at the bottom
// Move the name label as well
if (cannonNameLabels[0]) {
cannonNameLabels[0].x = p1.x;
cannonNameLabels[0].y = p1.y - 200;
}
// Move laser if super shot is active
if (superShotActive && superShotLaser) {
superShotLaser.x = p1.x;
}
}
};
// Update YOU label and arrow to follow player cannon every frame
var youLabelFollowInterval = null;
function startYouLabelFollowing() {
if (youLabelFollowInterval) LK.clearInterval(youLabelFollowInterval);
youLabelFollowInterval = LK.setInterval(function () {
// This will be called from game.update to keep YOU label on top of cannon
}, 16);
}
game.up = function (x, y, obj) {
p1Dragging = false;
};
// --- Super Shot Laser Logic in game.update ---
var _oldGameUpdate = game.update;
var youLabelRef = null;
var arrowLabelRef = null;
game.update = function () {
// Update YOU label and arrow to follow player cannon
if (youLabelRef && cannonSprites[0]) {
youLabelRef.x = cannonSprites[0].x;
youLabelRef.y = cannonSprites[0].y - 110;
}
if (arrowLabelRef && cannonSprites[0]) {
arrowLabelRef.x = cannonSprites[0].x;
}
// Super shot logic
if (superShotActive && superShotLaser) {
// Laser follows cannon
var p1 = cannonSprites[0];
superShotLaser.x = p1.x;
superShotLaser.y = p1.y - p1.height;
// Destroy all invaders in laser path
if (game._invaders) {
for (var ii = 0; ii < game._invaders.length; ++ii) {
var inv = game._invaders[ii];
if (!inv._alive) continue;
// Check if invader is in laser's X range
if (Math.abs(inv.x - superShotLaser.x) < (superShotLaser.width + inv.width) / 2) {
// Remove invader
inv._alive = false;
if (inv.parent) inv.parent.removeChild(inv);
}
}
}
// Destroy all bullets in laser path except player's own
for (var b = bullets.length - 1; b >= 0; --b) {
var bullet = bullets[b];
if (bullet._shooterIdx !== 0) {
if (Math.abs(bullet.x - superShotLaser.x) < (superShotLaser.width + bullet.width) / 2) {
if (bullet.parent) bullet.parent.removeChild(bullet);
bullets.splice(b, 1);
}
}
}
// Power-ups are not destroyed by laser (optional: add if needed)
// Decrement timer
superShotTimer--;
if (superShotTimer <= 0) {
// End super shot
superShotActive = false;
if (superShotLaser && superShotLaser.parent) superShotLaser.parent.removeChild(superShotLaser);
superShotLaser = null;
// Reset super bar (already reset on activation, so do not reset again here)
// If countdown text is still present, remove it
if (cannonSuperBars[0] && cannonSuperBars[0]._superCountdown && cannonSuperBars[0]._superCountdown.text.parent) {
cannonSuperBars[0]._superCountdown.text.parent.removeChild(cannonSuperBars[0]._superCountdown.text);
cannonSuperBars[0]._superCountdown = null;
}
}
}
// Call original update logic
if (_oldGameUpdate) _oldGameUpdate.apply(this, arguments);
};
}, 800);
}
}, 350);
}
}, 1000);
}, 5000);
};
};
singlePlayerBtn.down = function (x, y, obj) {
// You can add your Single-Player logic here
LK.effects.flashObject(singlePlayerBtn, 0x00ff00, 400);
// For now, just flash the button to show it was pressed
};
}
// Show title immediately
studioText.alpha = 1;
madeByText.alpha = 1;
// Helper to clear all intro timeouts if skipping
function clearIntroTimeouts() {
for (var i = 0; i < skipTimeouts.length; ++i) {
LK.clearTimeout(skipTimeouts[i]);
}
skipTimeouts = [];
}
// Add skip button handler
skipBtn.down = function (x, y, obj) {
skipIntro = true;
clearIntroTimeouts();
showTitleScreen();
};
// Stay for 2 seconds, then hide instantly
skipTimeouts.push(LK.setTimeout(function () {
if (skipIntro) return;
studioText.alpha = 0;
madeByText.alpha = 0;
}, 2000));
// After Pixelated Studios, show Gemini Games
var geminiMadeByText = new Text2("Made By", {
size: 90,
fill: 0xFFFFFF
});
geminiMadeByText.anchor.set(0.5, 1);
geminiMadeByText.alpha = 0;
geminiMadeByText.x = 2048 / 2;
geminiMadeByText.y = 2732 / 2 - 120;
game.addChild(geminiMadeByText);
var geminiText = new Text2("Gemini Games,", {
size: 180,
fill: 0xFFFFFF
});
geminiText.anchor.set(0.5, 0.5);
geminiText.alpha = 0;
geminiText.x = 2048 / 2;
geminiText.y = 2732 / 2;
game.addChild(geminiText);
// 3 seconds after Pixelated Studios fades out (4+600+2000+600+3000 = 10200ms)
skipTimeouts.push(LK.setTimeout(function () {
if (skipIntro) return;
tween(geminiText, {
alpha: 1
}, {
duration: 600,
easing: tween.easeOut
});
tween(geminiMadeByText, {
alpha: 1
}, {
duration: 600,
easing: tween.easeOut
});
skipTimeouts.push(LK.setTimeout(function () {
if (skipIntro) return;
tween(geminiText, {
alpha: 0
}, {
duration: 600,
easing: tween.easeIn
});
tween(geminiMadeByText, {
alpha: 0
}, {
duration: 600,
easing: tween.easeIn
});
}, 2000));
}, 10200));
// After Gemini Games, show Upit Studios
var andText = new Text2("And", {
size: 90,
fill: 0xFFFFFF
});
andText.anchor.set(0.5, 1);
andText.alpha = 0;
andText.x = 2048 / 2;
andText.y = 2732 / 2 - 120;
game.addChild(andText);
var upitText = new Text2("Upit Studios", {
size: 180,
fill: 0xFFFFFF
});
upitText.anchor.set(0.5, 0.5);
upitText.alpha = 0;
upitText.x = 2048 / 2;
upitText.y = 2732 / 2;
game.addChild(upitText);
// 3 seconds after Gemini Games fades out (10200+600+2000+600+3000 = 16300ms)
skipTimeouts.push(LK.setTimeout(function () {
if (skipIntro) return;
tween(upitText, {
alpha: 1
}, {
duration: 600,
easing: tween.easeOut
});
tween(andText, {
alpha: 1
}, {
duration: 600,
easing: tween.easeOut
});
skipTimeouts.push(LK.setTimeout(function () {
if (skipIntro) return;
tween(upitText, {
alpha: 0
}, {
duration: 600,
easing: tween.easeIn
});
tween(andText, {
alpha: 0
}, {
duration: 600,
easing: tween.easeIn
});
}, 2000));
}, 16300));
// After Upit Studios, show the title screen
// 3 seconds after Upit Studios fades out (16300+600+2000+600+3000 = 22400ms)
skipTimeouts.push(LK.setTimeout(function () {
if (skipIntro) return;
showTitleScreen();
}, 22400));
;
;
// --- Huge Invader Class ---
// A huge invader that splits into two smaller invaders when shot
function createHugeInvader(x, y, size, color) {
// size: 1 = normal, 2 = big, 3 = huge
var width = 120 * size;
var height = 80 * size;
var invader = LK.getAsset('box', {
width: width,
height: height,
color: color || 0xffa500,
anchorX: 0.5,
anchorY: 0.5
});
invader.x = x;
invader.y = y;
invader._vx = 1.5 + Math.random() * 1.5;
invader._vy = 0;
invader._dir = Math.random() > 0.5 ? 1 : -1;
invader._size = size;
invader._alive = true;
invader._lastX = invader.x;
invader._lastY = invader.y;
invader._isHuge = true;
return invader;
} /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181818
});
/****
* Game Code
****/
// --- Level Score and Level Tracker UI ---
var levelScore = 0;
var levelScoreText = new Text2("Score: 0", {
size: 110,
fill: 0xffffff,
background: 0x222222,
padding: 30,
borderRadius: 30
});
levelScoreText.anchor.set(0.5, 0);
levelScoreText.x = 2048 / 2;
levelScoreText.y = 40;
levelScoreText.visible = false;
game.addChild(levelScoreText);
// Level tracker
var currentLevel = 1;
var levelTrackerText = new Text2("Level 1", {
size: 90,
fill: 0xffffff,
background: 0x222222,
padding: 24,
borderRadius: 24
});
levelTrackerText.anchor.set(0.5, 0);
levelTrackerText.x = 2048 / 2;
levelTrackerText.y = 40 + levelScoreText.height + 10;
levelTrackerText.visible = false;
game.addChild(levelTrackerText);
// --- Mega Bar UI (vertical, right side) ---
// Only create and add the mega bar when gameplay starts (not on menu/loading)
var megaBarBg = null;
var megaBarFill = null;
function createMegaBar() {
// Remove if already present
if (megaBarBg && megaBarBg.parent) megaBarBg.parent.removeChild(megaBarBg);
if (megaBarFill && megaBarFill.parent) megaBarFill.parent.removeChild(megaBarFill);
// Make the bar much bigger
megaBarBg = LK.getAsset('box', {
width: 180,
height: 1800,
color: 0x333366,
anchorX: 1,
anchorY: 0
});
megaBarBg.x = 2048 - 40;
megaBarBg.y = 150;
game.addChild(megaBarBg);
megaBarFill = LK.getAsset('box', {
width: 144,
height: 0,
color: 0x66ccff,
anchorX: 1,
anchorY: 1
});
megaBarFill.x = 2048 - 40 + 18;
megaBarFill.y = 150 + 1800 - 18;
game.addChild(megaBarFill);
setMegaBarFill(0);
}
// Helper to set mega bar fill (0 to 1)
function setMegaBarFill(percent) {
if (!megaBarFill) return;
percent = Math.max(0, Math.min(1, percent));
megaBarFill.height = Math.floor(1764 * percent);
megaBarFill.y = 150 + 1800 - 18 - megaBarFill.height;
}
function removeMegaBar() {
if (megaBarBg && megaBarBg.parent) megaBarBg.parent.removeChild(megaBarBg);
if (megaBarFill && megaBarFill.parent) megaBarFill.parent.removeChild(megaBarFill);
megaBarBg = null;
megaBarFill = null;
}
// --- AI and Player Cannon Movement Logic ---
// Import tween plugin for animations
game.setBackgroundColor(0x000000); // Ensure black background
// Create the text, initially invisible
var madeByText = new Text2("Made By", {
size: 90,
fill: 0xFFFFFF
});
madeByText.anchor.set(0.5, 1);
madeByText.alpha = 0;
madeByText.x = 2048 / 2;
madeByText.y = 2732 / 2 - 120;
game.addChild(madeByText);
var studioText = new Text2("Pixelated Studios,", {
size: 180,
fill: 0xFFFFFF
});
studioText.anchor.set(0.5, 0.5);
studioText.alpha = 0;
studioText.x = 2048 / 2;
studioText.y = 2732 / 2;
game.addChild(studioText);
// --- SKIP BUTTON LOGIC ---
var skipBtn = new Text2("Skip", {
size: 80,
fill: 0xffffff,
background: 0x222222,
padding: 40,
borderRadius: 30
});
skipBtn.anchor.set(1, 0);
skipBtn.x = 2048 - 60;
skipBtn.y = 60;
skipBtn.alpha = 0.85;
game.addChild(skipBtn);
var skipIntro = false;
var skipTimeouts = [];
function showTitleScreen() {
// Remove skip button if present
if (skipBtn && skipBtn.parent) skipBtn.parent.removeChild(skipBtn);
// Remove all intro texts if present
if (madeByText && madeByText.parent) madeByText.parent.removeChild(madeByText);
if (studioText && studioText.parent) studioText.parent.removeChild(studioText);
if (geminiMadeByText && geminiMadeByText.parent) geminiMadeByText.parent.removeChild(geminiMadeByText);
if (geminiText && geminiText.parent) geminiText.parent.removeChild(geminiText);
if (andText && andText.parent) andText.parent.removeChild(andText);
if (upitText && upitText.parent) upitText.parent.removeChild(upitText);
// Remove mega bar if present (hide on menu)
removeMegaBar();
// Create the title text
var titleText = new Text2("Pixelossed Rush Frenzy 2", {
size: 180,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 2048 / 2;
titleText.y = 2732 / 2;
game.addChild(titleText);
// Create Multi-Player and Single-Player buttons just below the title, larger and more visible
var buttonWidth = 900;
var buttonHeight = 180;
var buttonSpacing = 60;
// Position buttons just below the title
var titleBottomY = titleText.y + titleText.height / 2 + 80;
var multiPlayerBtn = new Text2("Multi-Player", {
size: 110,
fill: 0x222222,
background: 0xFFFFFF,
padding: 60,
borderRadius: 40
});
multiPlayerBtn.anchor.set(0.5, 0.5);
multiPlayerBtn.x = 2048 / 2;
multiPlayerBtn.y = titleBottomY + buttonHeight / 2;
var singlePlayerBtn = new Text2("Single-Player", {
size: 110,
fill: 0x222222,
background: 0xFFFFFF,
padding: 60,
borderRadius: 40
});
singlePlayerBtn.anchor.set(0.5, 0.5);
singlePlayerBtn.x = 2048 / 2;
singlePlayerBtn.y = multiPlayerBtn.y + buttonHeight + buttonSpacing;
game.addChild(multiPlayerBtn);
game.addChild(singlePlayerBtn);
// Add .down event handlers to make buttons press-able
multiPlayerBtn.down = function (x, y, obj) {
LK.effects.flashObject(multiPlayerBtn, 0x00ff00, 400);
// Hide the main menu buttons and title
titleText.visible = false;
multiPlayerBtn.visible = false;
singlePlayerBtn.visible = false;
// Show the Multi-Player mode selection screen
// Create 4 Player Game button
var aiCannonsBtn = new Text2("4 Player Game", {
size: 90,
fill: 0x222222,
background: 0xFFFFFF,
padding: 50,
borderRadius: 40
});
aiCannonsBtn.anchor.set(0.5, 0.5);
aiCannonsBtn.x = 2048 / 2;
aiCannonsBtn.y = 2732 / 2 - 120;
// Create Online AI controlled Cannons button
var onlineAICannonsBtn = new Text2("Online AI controlled Cannons", {
size: 90,
fill: 0x222222,
background: 0xFFFFFF,
padding: 50,
borderRadius: 40
});
onlineAICannonsBtn.anchor.set(0.5, 0.5);
onlineAICannonsBtn.x = 2048 / 2;
onlineAICannonsBtn.y = 2732 / 2 + 120;
game.addChild(aiCannonsBtn);
game.addChild(onlineAICannonsBtn);
// Add simple flash feedback for these buttons (actual logic can be added later)
aiCannonsBtn.down = function (x, y, obj) {
LK.effects.flashObject(aiCannonsBtn, 0x00ff00, 400);
// Add logic for AI-Controlled Cannons mode here
};
onlineAICannonsBtn.down = function (x, y, obj) {
LK.effects.flashObject(onlineAICannonsBtn, 0x00ff00, 400);
// Hide the mode selection buttons
aiCannonsBtn.visible = false;
onlineAICannonsBtn.visible = false;
// --- Setup for 3 lines, 4 spaces per line ---
var lines = [];
var spacesPerLine = 4;
var totalLines = 3;
var spaceWidth = 260;
var spaceHeight = 220;
var spaceSpacingX = 80;
var spaceSpacingY = 80;
var startY = 2732 / 2 - (totalLines - 1) * (spaceHeight + spaceSpacingY) / 2 - 100;
var startX = 2048 / 2 - (spacesPerLine - 1) * (spaceWidth + spaceSpacingX) / 2;
// Store references for later if needed
var onlineCannonSpaces = [];
for (var line = 0; line < totalLines; ++line) {
var y = startY + line * (spaceHeight + spaceSpacingY);
for (var col = 0; col < spacesPerLine; ++col) {
var x = startX + col * (spaceWidth + spaceSpacingX);
// Create a container for each space
var spaceContainer = new Container();
spaceContainer.x = x;
spaceContainer.y = y;
// Draw background for the first space of the first line (P1)
if (line === 0 && col === 0) {
var bg = LK.getAsset('box', {
width: spaceWidth,
height: spaceHeight,
color: 0xff2222,
anchorX: 0.5,
anchorY: 0.5
});
spaceContainer.addChild(bg);
} else {
// Draw a neutral background for other spaces
var bg = LK.getAsset('box', {
width: spaceWidth,
height: spaceHeight,
color: 0xeeeeee,
anchorX: 0.5,
anchorY: 0.5
});
spaceContainer.addChild(bg);
}
// Add text below the space (except for first line)
var labelText = "";
if (line === 0) {
// First line: only show P1 under first space, others show "looking for players..."
if (col === 0) {
labelText = "P1";
} else {
labelText = "looking for players...";
}
} else {
// All other lines: show "looking for players..." under all spaces
labelText = "looking for players...";
}
// Only add text if not the first line's spaces (per instructions)
if (!(line === 0)) {
var label = new Text2(labelText, {
size: 48,
fill: 0x333333
});
label.anchor.set(0.5, 0);
label.x = 0;
label.y = spaceHeight / 2 + 10;
spaceContainer.addChild(label);
spaceContainer._label = label; // Store reference for later
} else if (col !== 0) {
// For first line, only add text to spaces 2,3,4
var label = new Text2(labelText, {
size: 48,
fill: 0x333333
});
label.anchor.set(0.5, 0);
label.x = 0;
label.y = spaceHeight / 2 + 10;
spaceContainer.addChild(label);
spaceContainer._label = label; // Store reference for later
} else if (col === 0) {
// For first space, add "P1" below
var p1Label = new Text2("P1", {
size: 48,
fill: 0xffffff
});
p1Label.anchor.set(0.5, 0);
p1Label.x = 0;
p1Label.y = spaceHeight / 2 + 10;
spaceContainer.addChild(p1Label);
spaceContainer._label = null; // No random name for P1
}
// Center anchor for the container
spaceContainer.pivot.x = 0;
spaceContainer.pivot.y = 0;
game.addChild(spaceContainer);
onlineCannonSpaces.push(spaceContainer);
}
}
// After 5 seconds, show random names under the spaces (except P1)
LK.setTimeout(function () {
// List of random names to pick from
var randomNames = ["PixelHero", "GeminiBot", "UpitAI", "CannonKing", "BlasterX", "NovaRush", "Firestorm", "Zapster", "RedRocket", "BlueBolt", "GreenGunner", "ShadowAI", "LaserLynx", "TurboTiger", "CyberCannon", "Vortex"];
// Shuffle the names array
for (var i = randomNames.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = randomNames[i];
randomNames[i] = randomNames[j];
randomNames[j] = temp;
}
var nameIdx = 0;
for (var i = 0; i < onlineCannonSpaces.length; ++i) {
var space = onlineCannonSpaces[i];
// Only update if this space has a label (not P1)
if (space._label) {
// Pick a random name, cycle if we run out
var name = randomNames[nameIdx % randomNames.length];
space._label.setText(name);
// Make the name more visible and white
if (space._label.style) {
space._label.style.fill = 0xffffff;
space._label.style.size = 64;
}
nameIdx++;
} else if (i === 0) {
// Make P1 label more visible and white
for (var c = 0; c < space.children.length; ++c) {
var child = space.children[c];
if (child && child.setText && child.text === "P1" && child.style) {
child.style.fill = 0xffffff;
child.style.size = 64;
}
}
}
}
// --- 10 second countdown timer in bottom right ---
var countdownTime = 10;
var countdownText = new Text2(countdownTime + "", {
size: 120,
fill: 0xffffff,
background: 0x222222,
padding: 30,
borderRadius: 40
});
countdownText.anchor.set(1, 1);
// Place in bottom right, with margin
countdownText.x = 2048 - 60;
countdownText.y = 2732 - 60;
game.addChild(countdownText);
var countdownInterval = LK.setInterval(function () {
countdownTime--;
if (countdownTime >= 0) {
countdownText.setText(countdownTime + "");
}
if (countdownTime <= 0) {
LK.clearInterval(countdownInterval);
// Optionally, you can hide or remove the timer here
// countdownText.visible = false;
// After countdown, show "Loading…" screen
// Hide all spaces and countdown
for (var i = 0; i < onlineCannonSpaces.length; ++i) {
if (onlineCannonSpaces[i] && onlineCannonSpaces[i].parent) {
onlineCannonSpaces[i].parent.removeChild(onlineCannonSpaces[i]);
}
}
if (countdownText && countdownText.parent) {
countdownText.parent.removeChild(countdownText);
}
// Show "Loading…" centered
var loadingText = new Text2("Loading…", {
size: 180,
fill: 0xffffff,
background: 0x222222,
padding: 80,
borderRadius: 60
});
loadingText.anchor.set(0.5, 0.5);
loadingText.x = 2048 / 2;
loadingText.y = 2732 / 2 - 120;
game.addChild(loadingText);
// --- Loading Bar Logic ---
var barWidth = 900;
var barHeight = 80;
var barBg = LK.getAsset('box', {
width: barWidth,
height: barHeight,
color: 0x444444,
anchorX: 0.5,
anchorY: 0.5
});
barBg.x = 2048 / 2;
barBg.y = 2732 / 2 + 80;
game.addChild(barBg);
var barFill = LK.getAsset('box', {
width: 1,
height: barHeight - 16,
color: 0xffffff,
anchorX: 0,
anchorY: 0.5
});
barFill.x = 2048 / 2 - barWidth / 2 + 8;
barFill.y = 2732 / 2 + 80;
game.addChild(barFill);
var loadingPercent = 0;
var loadingBarInterval = LK.setInterval(function () {
// Randomly increase percent by 3-18% per tick, but never above 100
var increment = Math.floor(Math.random() * 16) + 3;
loadingPercent += increment;
if (loadingPercent > 100) loadingPercent = 100;
// Animate bar width
barFill.width = Math.floor((barWidth - 16) * (loadingPercent / 100));
// Optionally, show percent text above bar
if (!barFill._percentText) {
var percentText = new Text2("0%", {
size: 60,
fill: 0xffffff,
background: 0x222222,
padding: 18,
borderRadius: 30
});
percentText.anchor.set(0.5, 1);
percentText.x = 2048 / 2;
percentText.y = barBg.y - barHeight / 2 - 10;
game.addChild(percentText);
barFill._percentText = percentText;
}
barFill._percentText.setText(loadingPercent + "%");
// If full, stop interval (simulate loading complete)
if (loadingPercent >= 100) {
LK.clearInterval(loadingBarInterval);
// Optionally, fade out loading bar and text after a short delay
LK.setTimeout(function () {
if (loadingText && loadingText.parent) loadingText.parent.removeChild(loadingText);
if (barBg && barBg.parent) barBg.parent.removeChild(barBg);
if (barFill && barFill.parent) barFill.parent.removeChild(barFill);
if (barFill._percentText && barFill._percentText.parent) barFill._percentText.parent.removeChild(barFill._percentText);
// --- After loading, show AI-controlled cannons and player cannon with names ---
// Show mega bar now that gameplay is starting
createMegaBar();
// Show level score and tracker
if (levelScoreText) {
levelScoreText.visible = true;
levelScore = 0;
levelScoreText.setText("Score: 0");
}
if (levelTrackerText) {
levelTrackerText.visible = true;
currentLevel = 1;
levelTrackerText.setText("Level 1");
}
// --- READY? GO! Sequence with TTS and Instructions ---
var gameplayPaused = true; // Pause gameplay during sequence
var readyText = new Text2("READY?", {
size: 280,
fill: 0xFFFF00,
background: 0x222222,
padding: 60,
borderRadius: 40
});
readyText.anchor.set(0.5, 0.5);
readyText.x = 2048 / 2;
readyText.y = 2732 / 2;
readyText.alpha = 0;
game.addChild(readyText);
// Fade in READY? over 600ms
tween(readyText, {
alpha: 1
}, {
duration: 600,
easing: tween.easeOut
});
// Play READY sound effect
var readySound = LK.getSound('ready');
if (readySound) readySound.play();
// Keep READY? visible for 1.5s, then fade out and show GO!
LK.setTimeout(function () {
tween(readyText, {
alpha: 0
}, {
duration: 400,
easing: tween.easeIn,
onFinish: function onFinish() {
readyText.setText("GO!");
readyText.alpha = 0;
// Fade in GO! over 400ms
tween(readyText, {
alpha: 1
}, {
duration: 400,
easing: tween.easeOut
});
// Play GO sound effect
var goSound = LK.getSound('go');
if (goSound) goSound.play();
// Keep GO! visible for 1s, then fade out and remove
LK.setTimeout(function () {
tween(readyText, {
alpha: 0
}, {
duration: 400,
easing: tween.easeIn,
onFinish: function onFinish() {
if (readyText.parent) readyText.parent.removeChild(readyText);
// Resume gameplay and show YOU label with arrow
gameplayPaused = false;
// Create YOU label positioned on top of player cannon
var youText = new Text2("YOU", {
size: 120,
fill: 0xFFFF00,
background: 0x222222,
padding: 30,
borderRadius: 24
});
youText.anchor.set(0.5, 1);
youText.x = cannonSprites[0].x;
youText.y = cannonSprites[0].y - 110;
game.addChild(youText);
youLabelRef = youText;
// Create arrow pointing down to player cannon
var arrowText = new Text2("↓", {
size: 180,
fill: 0xFFFF00
});
arrowText.anchor.set(0.5, 0.5);
arrowText.x = cannonSprites[0].x;
arrowText.y = cannonSprites[0].y - 150;
game.addChild(arrowText);
arrowLabelRef = arrowText;
// Animate arrow bouncing
function animateArrowBounce() {
tween(arrowText, {
y: arrowText.y + 30
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(arrowText, {
y: cannonSprites[0].y - 150
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: animateArrowBounce
});
}
});
}
animateArrowBounce();
// Create blinking instruction text
var instructionText = new Text2("shoot every single invader!", {
size: 100,
fill: 0xFFFF00,
background: 0x222222,
padding: 30,
borderRadius: 24
});
instructionText.anchor.set(0.5, 0.5);
instructionText.x = 2048 / 2;
instructionText.y = 2732 / 2 + 200;
instructionText.alpha = 1;
game.addChild(instructionText);
// Blink animation
function blinkInstruction() {
tween(instructionText, {
alpha: 0.2
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(instructionText, {
alpha: 1
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: blinkInstruction
});
}
});
}
blinkInstruction();
// Fade out YOU, arrow, and instruction after 4 seconds
LK.setTimeout(function () {
tween(youText, {
alpha: 0
}, {
duration: 600,
easing: tween.easeIn,
onFinish: function onFinish() {
if (youText.parent) youText.parent.removeChild(youText);
}
});
tween(arrowText, {
alpha: 0
}, {
duration: 600,
easing: tween.easeIn,
onFinish: function onFinish() {
if (arrowText.parent) arrowText.parent.removeChild(arrowText);
}
});
tween(instructionText, {
alpha: 0
}, {
duration: 600,
easing: tween.easeIn,
onFinish: function onFinish() {
if (instructionText.parent) instructionText.parent.removeChild(instructionText);
}
});
}, 4000);
}
});
}, 1000);
}
});
}, 1500);
// We'll use the same random names as before, and show them above the AI cannons
// Get the names that were picked for the AI cannons (from previous randomization)
var aiCannonNames = [];
for (var i = 0; i < onlineCannonSpaces.length; ++i) {
var space = onlineCannonSpaces[i];
if (space && space._label && space._label.text) {
aiCannonNames.push(space._label.text);
}
}
// The first is always "P1" (player), the rest are AI names
// We'll show 12 cannons in 2 rows of 6, player on the top left, 11 AI to the right and below
var cannonCount = 12;
var cannonsPerRow = 6;
var cannonSpacingX = 320;
var cannonSpacingY = 340;
// Move all cannons to the bottom and restrict movement to left/right only
var cannonYStart = 2732 - 220; // Place all cannons near the bottom of the screen
var cannonXStart = 2048 / 2 - (cannonsPerRow - 1) * cannonSpacingX / 2;
// Store references for later if needed
var cannonSprites = [];
var cannonNameLabels = [];
var cannonSuperBars = []; // Super bar UI for each cannon
// Prepare a list of random names for AI cannons (excluding P1)
var aiCannonRandomNames = ["PixelHero", "GeminiBot", "UpitAI", "CannonKing", "BlasterX", "NovaRush", "Firestorm", "Zapster", "RedRocket", "BlueBolt", "GreenGunner", "ShadowAI", "LaserLynx", "TurboTiger", "CyberCannon", "Vortex", "IronBlast", "MegaShot", "ThunderCore", "BlastMaster", "Rocketron", "SteelBarrel", "PyroPulse", "BoltBlaze"];
// Shuffle the names array
for (var i = aiCannonRandomNames.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = aiCannonRandomNames[i];
aiCannonRandomNames[i] = aiCannonRandomNames[j];
aiCannonRandomNames[j] = temp;
}
var aiCannonNameIdx = 0;
for (var i = 0; i < cannonCount; ++i) {
var row = Math.floor(i / cannonsPerRow);
var col = i % cannonsPerRow;
var x = cannonXStart + col * cannonSpacingX;
var y = cannonYStart + row * cannonSpacingY;
// Create a cannon (use 'box' asset for now)
var cannonColor = i === 0 ? 0xff2222 : 0xeeeeee;
var cannon = LK.getAsset('box', {
width: 180,
height: 180,
color: cannonColor,
anchorX: 0.5,
anchorY: 1
});
cannon.x = x;
cannon.y = y;
game.addChild(cannon);
cannonSprites.push(cannon);
// Add the name above the cannon
var nameText = "";
if (i === 0) {
nameText = "P1";
} else {
// Assign a random name from the shuffled list, cycling if needed
nameText = aiCannonRandomNames[aiCannonNameIdx % aiCannonRandomNames.length];
aiCannonNameIdx++;
}
var nameLabel = new Text2(nameText, {
size: 80,
fill: 0xffffff,
background: 0x222222,
padding: 24,
borderRadius: 30
});
nameLabel.anchor.set(0.5, 1);
nameLabel.x = x;
nameLabel.y = y - 200;
game.addChild(nameLabel);
cannonNameLabels.push(nameLabel);
// --- Super Bar (above each cannon) ---
var superBarBg = LK.getAsset('box', {
width: 160,
height: 24,
color: 0x222222,
anchorX: 0.5,
anchorY: 1
});
superBarBg.x = x;
superBarBg.y = y - 220;
game.addChild(superBarBg);
var superBarFill = LK.getAsset('box', {
width: 152,
height: 16,
color: 0x66ff66,
anchorX: 0.5,
anchorY: 1
});
superBarFill.x = x;
superBarFill.y = y - 220 + 4;
game.addChild(superBarFill);
// Helper to set super bar fill (0 to 1)
superBarFill._setFill = function (percent) {
percent = Math.max(0, Math.min(1, percent));
superBarFill.width = Math.floor(152 * percent);
};
superBarFill._setFill(1); // Start full
cannonSuperBars.push({
bg: superBarBg,
fill: superBarFill
});
}
// Show the number of cannons below the rows, centered
var cannonCountText = new Text2("Cannons: " + cannonCount, {
size: 80,
fill: 0xffffff,
background: 0x222222,
padding: 24,
borderRadius: 30
});
cannonCountText.anchor.set(0.5, 0);
cannonCountText.x = 2048 / 2;
cannonCountText.y = cannonYStart + 2 * cannonSpacingY + 40;
game.addChild(cannonCountText);
// --- AI and Player Cannon Movement Logic ---
// We'll move all cannons (including player) on the bottom row, and make AI move like a player
// Only bottom row (row 1, i=6..11) is movable, but for this task, move all cannons (including player) on the bottom
// Store cannon state for movement
var cannonStates = [];
for (var i = 0; i < cannonSprites.length; ++i) {
// All cannons start with a random direction and speed
var isPlayer = i === 0;
cannonStates.push({
vx: (Math.random() * 2 - 1) * 6,
// -6 to 6 px/frame
vy: 0,
targetX: cannonSprites[i].x,
targetY: cannonSprites[i].y,
moveCooldown: 0,
isPlayer: isPlayer,
aiMoveTimer: 0,
aiTargetX: cannonSprites[i].x,
aiTargetY: cannonSprites[i].y,
shootCooldown: Math.floor(Math.random() * 30) + 30 // randomize initial shoot cooldown (0.5s-1s)
});
}
// --- Automatic Shooting for All Cannons ---
// We'll create a bullet asset and manage bullets in an array
var bulletAssetColor = 0x44aaff;
var bulletWidth = 32;
var bulletHeight = 80;
var bulletSpeed = -32; // negative Y, shoots upward
var bullets = [];
// --- Super Bar Percentage Tracking ---
var superBarPercents = [];
for (var i = 0; i < 12; ++i) {
superBarPercents.push(0); // Start empty (0%)
}
// Helper to create a bullet at (x, y)
function createBullet(x, y, color) {
var bullet = LK.getAsset('box', {
width: bulletWidth,
height: bulletHeight,
color: color || bulletAssetColor,
anchorX: 0.5,
anchorY: 1
});
bullet.x = x;
bullet.y = y - 90; // start just above the cannon
bullet._vy = bulletSpeed;
// Track which cannon shot this bullet (for super bar)
bullet._shooterIdx = null;
return bullet;
}
// Helper to show a +1% popup above a super bar
function showSuperBarPopup(idx, amount) {
if (!cannonSuperBars[idx]) return;
var bar = cannonSuperBars[idx].fill;
var cannonLabel = cannonNameLabels[idx];
var popup = new Text2("+" + amount + "%", {
size: 54,
fill: 0xffff00,
background: 0x222222,
padding: 12,
borderRadius: 18
});
popup.anchor.set(0.5, 1);
popup.x = bar.x;
popup.y = bar.y - 16;
game.addChild(popup);
// Show the name of the cannon above the popup
var namePopup = null;
if (cannonLabel && cannonLabel.text) {
namePopup = new Text2(cannonLabel.text, {
size: 38,
fill: 0xffffff,
background: 0x222222,
padding: 8,
borderRadius: 12
});
namePopup.anchor.set(0.5, 1);
namePopup.x = bar.x;
namePopup.y = popup.y - 48;
game.addChild(namePopup);
}
// Animate popup up and fade out
tween(popup, {
y: popup.y - 60,
alpha: 0
}, {
duration: 900,
easing: tween.easeOut,
onComplete: function onComplete() {
if (popup.parent) popup.parent.removeChild(popup);
}
});
if (namePopup) {
tween(namePopup, {
y: namePopup.y - 60,
alpha: 0
}, {
duration: 900,
easing: tween.easeOut,
onComplete: function onComplete() {
if (namePopup.parent) namePopup.parent.removeChild(namePopup);
}
});
}
}
// Helper: clamp value
function clamp(val, min, max) {
return Math.max(min, Math.min(max, val));
}
// Movement bounds (keep cannons inside visible area)
var minX = 120;
var maxX = 2048 - 120;
var minY = cannonYStart - 40;
var maxY = cannonYStart + cannonSpacingY + 40;
// Simulate "player" movement (bottom left cannon, i=0) as AI for now
// All other cannons are AI
// Animate movement every frame
game.update = function () {
// --- Pause gameplay during READY? GO! sequence ---
if (gameplayPaused) {
return;
}
// --- Levels continue indefinitely (no level 8 end condition) ---
// Game continues to spawn invaders at increasing difficulty
// --- Hide gameplay elements at level 8 and above ---
if (typeof game._elementsHiddenForLevel8 === "undefined") game._elementsHiddenForLevel8 = false;
if (currentLevel >= 8 && !game._elementsHiddenForLevel8) {
// Hide all cannons
for (var i = 0; i < cannonSprites.length; ++i) {
if (cannonSprites[i] && cannonSprites[i].parent) cannonSprites[i].parent.removeChild(cannonSprites[i]);
if (cannonNameLabels[i] && cannonNameLabels[i].parent) cannonNameLabels[i].parent.removeChild(cannonNameLabels[i]);
if (cannonSuperBars[i]) {
if (cannonSuperBars[i].bg && cannonSuperBars[i].bg.parent) cannonSuperBars[i].bg.parent.removeChild(cannonSuperBars[i].bg);
if (cannonSuperBars[i].fill && cannonSuperBars[i].fill.parent) cannonSuperBars[i].fill.parent.removeChild(cannonSuperBars[i].fill);
if (cannonSuperBars[i]._percentText && cannonSuperBars[i]._percentText.parent) cannonSuperBars[i]._percentText.parent.removeChild(cannonSuperBars[i]._percentText);
if (cannonSuperBars[i]._superCountdown && cannonSuperBars[i]._superCountdown.text && cannonSuperBars[i]._superCountdown.text.parent) cannonSuperBars[i]._superCountdown.text.parent.removeChild(cannonSuperBars[i]._superCountdown.text);
}
}
// Hide all bullets
for (var b = 0; b < bullets.length; ++b) {
if (bullets[b] && bullets[b].parent) bullets[b].parent.removeChild(bullets[b]);
}
bullets = [];
// Hide mega bar
removeMegaBar();
// Hide super shot laser if present
if (superShotLaser && superShotLaser.parent) superShotLaser.parent.removeChild(superShotLaser);
superShotLaser = null;
// Mark as hidden so we don't repeat
game._elementsHiddenForLevel8 = true;
}
// --- Cannon Movement and Name Label Update ---
for (var i = 0; i < cannonSprites.length; ++i) {
var cannon = cannonSprites[i];
var state = cannonStates[i];
// Hitbox visualization for cannons removed to reduce lag
// --- Super Bar UI follows cannon ---
if (cannonSuperBars[i]) {
cannonSuperBars[i].bg.x = cannon.x;
cannonSuperBars[i].bg.y = cannon.y - 220;
cannonSuperBars[i].fill.x = cannon.x;
cannonSuperBars[i].fill.y = cannon.y - 220 + 4;
}
// Animate super bar fill and show percentage
if (cannonSuperBars[i]) {
// Set fill to actual value
var percent = superBarPercents[i];
// --- Play 'super' sound when bar fills to 100% (from <1 to >=1) ---
if (typeof cannonSuperBars[i]._lastPercent === "undefined") cannonSuperBars[i]._lastPercent = 0;
if (cannonSuperBars[i]._lastPercent < 1 && percent >= 1) {
// Play the "super" sound
var superSound = LK.getSound('super');
if (superSound) superSound.play();
}
cannonSuperBars[i]._lastPercent = percent;
cannonSuperBars[i].fill._setFill(percent);
// Show percentage text above the cannon name (and above the bar)
if (!cannonSuperBars[i]._percentText) {
var pctText = new Text2("100%", {
size: 90,
// bigger size
fill: 0xffffff,
background: 0x222222,
padding: 16,
borderRadius: 18
});
pctText.anchor.set(0.5, 1);
// Place above the name label if available, else above the bar
if (cannonNameLabels[i]) {
pctText.x = cannonNameLabels[i].x;
pctText.y = cannonNameLabels[i].y - 18;
} else {
pctText.x = cannonSuperBars[i].fill.x;
pctText.y = cannonSuperBars[i].fill.y - 60;
}
game.addChild(pctText);
cannonSuperBars[i]._percentText = pctText;
}
// Update percent text and position
if (cannonSuperBars[i]._percentText) {
cannonSuperBars[i]._percentText.setText(Math.floor(percent * 100) + "%");
// Place above the name label if available, else above the bar
if (cannonNameLabels[i]) {
cannonSuperBars[i]._percentText.x = cannonNameLabels[i].x;
cannonSuperBars[i]._percentText.y = cannonNameLabels[i].y - 18;
} else {
cannonSuperBars[i]._percentText.x = cannonSuperBars[i].fill.x;
cannonSuperBars[i]._percentText.y = cannonSuperBars[i].fill.y - 60;
}
}
// --- Show 10s countdown over cannon if super bar is 100% and not already counting down ---
if (!cannonSuperBars[i]._superCountdown && percent >= 1) {
// Create countdown text
var cdText = new Text2("10", {
size: 90,
fill: 0xffee00,
background: 0x222222,
padding: 18,
borderRadius: 24
});
cdText.anchor.set(0.5, 0.5);
cdText.x = cannonSprites[i].x;
cdText.y = cannonSprites[i].y - 120;
game.addChild(cdText);
cannonSuperBars[i]._superCountdown = {
text: cdText,
time: 10 * 60 // 10 seconds at 60fps
};
// If this is P1, enable super shot for 10s and reset bar
if (i === 0 && !superShotActive) {
superShotActive = true;
superShotTimer = 10 * 60;
// Remove any previous laser
if (superShotLaser && superShotLaser.parent) superShotLaser.parent.removeChild(superShotLaser);
var p1 = cannonSprites[0];
superShotLaser = LK.getAsset('box', {
width: 40,
height: 2732,
color: 0xff2222,
anchorX: 0.5,
anchorY: 1
});
superShotLaser.x = p1.x;
superShotLaser.y = p1.y - p1.height;
superShotLaser.alpha = 0.7;
game.addChild(superShotLaser);
// Reset super bar percent
superBarPercents[0] = 0;
cannonSuperBars[0].fill._setFill(0);
if (cannonSuperBars[0]._percentText) cannonSuperBars[0]._percentText.setText("0%");
}
}
// If counting down, update timer and text
if (cannonSuperBars[i]._superCountdown) {
var sc = cannonSuperBars[i]._superCountdown;
sc.text.x = cannonSprites[i].x;
sc.text.y = cannonSprites[i].y - 120;
sc.time--;
var secondsLeft = Math.ceil(sc.time / 60);
sc.text.setText(secondsLeft + "");
if (sc.time <= 0) {
// Countdown finished, remove text and reset super bar
if (sc.text.parent) sc.text.parent.removeChild(sc.text);
cannonSuperBars[i]._superCountdown = null;
superBarPercents[i] = 0;
cannonSuperBars[i].fill._setFill(0);
if (cannonSuperBars[i]._percentText) cannonSuperBars[i]._percentText.setText("0%");
// If this is P1, also end super shot if still active
if (i === 0 && superShotActive) {
superShotActive = false;
if (superShotLaser && superShotLaser.parent) superShotLaser.parent.removeChild(superShotLaser);
superShotLaser = null;
}
}
}
}
if (i === 0) {
// P1 cannon: do not move by AI, position is set by hand/touch events
// Just update the name label to follow the cannon
if (cannonNameLabels[i]) {
cannonNameLabels[i].x = cannon.x;
cannonNameLabels[i].y = cannon.y - 200;
}
} else {
// AI logic: every 1.2-2.5s, pick the closest invader (or the only one) as target
if (state.aiMoveTimer <= 0) {
var closestInvader = null;
var closestDist = Infinity;
if (game._invaders && game._invaders.length > 0) {
for (var ai_ci = 0; ai_ci < game._invaders.length; ++ai_ci) {
var ai_inv = game._invaders[ai_ci];
if (!ai_inv._alive) continue;
var dx = ai_inv.x - cannon.x;
var dy = ai_inv.y - cannon.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < closestDist) {
closestDist = dist;
closestInvader = ai_inv;
}
}
}
// If found, target its X position, else random
if (closestInvader) {
state.aiTargetX = clamp(closestInvader.x, minX, maxX);
} else {
state.aiTargetX = clamp(cannon.x + (Math.random() * 2 - 1) * 400, minX, maxX);
}
state.aiTargetY = cannonYStart; // Always at the bottom
state.aiMoveTimer = 72 + Math.floor(Math.random() * 60); // 1.2s to 2.2s at 60fps
} else {
state.aiMoveTimer--;
}
// Move towards target (only X axis, Y is fixed)
var dx = state.aiTargetX - cannon.x;
var dist = Math.abs(dx);
var speed = 10 + Math.random() * 2; // px per frame, a bit jittery
if (dist > 2) {
cannon.x += dx / dist * Math.min(speed, dist);
cannon.y = cannonYStart; // Always at the bottom
// Also move the name label
if (cannonNameLabels[i]) {
cannonNameLabels[i].x = cannon.x;
cannonNameLabels[i].y = cannon.y - 200;
}
} else {
// Snap to target if close
cannon.x = state.aiTargetX;
cannon.y = cannonYStart; // Always at the bottom
if (cannonNameLabels[i]) {
cannonNameLabels[i].x = cannon.x;
cannonNameLabels[i].y = cannon.y - 200;
}
}
}
// --- Automatic Shooting ---
if (state.shootCooldown > 0) {
state.shootCooldown--;
} else {
// Shoot a bullet
var bulletColor = i === 0 ? 0xff2222 : bulletAssetColor;
var bullet = createBullet(cannon.x, cannon.y, bulletColor);
bullet._shooterIdx = i;
game.addChild(bullet);
bullets.push(bullet);
// Reset shoot cooldown (randomize a bit for AI, fixed for player)
if (i === 0) {
// P1: fire rate increases with each shot, min 6 frames (0.1s)
if (typeof state.minShootCooldown === "undefined") state.minShootCooldown = 30;
if (typeof state.shotsFired === "undefined") state.shotsFired = 0;
state.shotsFired++;
// Decrease cooldown by 1 frame per shot, but not below 6
state.minShootCooldown = Math.max(6, 30 - state.shotsFired);
state.shootCooldown = state.minShootCooldown;
} else {
state.shootCooldown = 24 + Math.floor(Math.random() * 24); // AI: 0.4s-0.8s
}
}
}
// --- Animate mega bar fill (demo pulse) ---
if (megaBarFill) {
var megaPercent = 0.5 + 0.5 * Math.sin(LK.ticks / 120 * Math.PI * 2);
setMegaBarFill(megaPercent);
}
// --- Bullet Movement and Cleanup ---
for (var b = bullets.length - 1; b >= 0; --b) {
var bullet = bullets[b];
bullet.y += bullet._vy;
// Hitbox visualization for bullets removed to reduce lag
// --- Add Invaders (move and draw) ---
// We'll create invaders if not already present
if (!game._invadersInitialized) {
game._invaders = [];
// Scale invader rows and columns with currentLevel, but clamp to reasonable max
var invaderRows = Math.min(3 + Math.floor((currentLevel - 1) / 2), 8);
var invaderCols = Math.min(8 + Math.floor((currentLevel - 1) / 1.5), 16);
// Cap total invaders to prevent lag
var maxTotalInvaders = 40;
var targetInvaders = Math.min(invaderRows * invaderCols, maxTotalInvaders);
invaderRows = Math.ceil(targetInvaders / invaderCols);
if (invaderRows * invaderCols > maxTotalInvaders) {
invaderCols = Math.floor(maxTotalInvaders / invaderRows);
}
var invaderSpacingX = 180;
var invaderSpacingY = 160;
var invaderStartX = 2048 / 2 - (invaderCols - 1) * invaderSpacingX / 2;
var invaderStartY = 320;
for (var ir = 0; ir < invaderRows; ++ir) {
for (var ic = 0; ic < invaderCols; ++ic) {
// Place huge invaders in the center columns of the first row (only if enough columns)
if (ir === 0 && invaderCols >= 6 && (ic === Math.floor(invaderCols / 2) - 1 || ic === Math.floor(invaderCols / 2))) {
var hugeInv = createHugeInvader(invaderStartX + ic * invaderSpacingX, invaderStartY + ir * invaderSpacingY, 3, 0xffa500);
game.addChild(hugeInv);
game._invaders.push(hugeInv);
} else {
// Add red invaders: every 3rd invader in each row is red and 2x faster
var isRed = ic % 3 === 2;
var invaderColor = isRed ? 0xff2222 : 0x33ff99;
var invader = LK.getAsset('box', {
width: 120,
height: 80,
color: invaderColor,
anchorX: 0.5,
anchorY: 0.5
});
invader.x = invaderStartX + ic * invaderSpacingX;
invader.y = invaderStartY + ir * invaderSpacingY;
invader._vx = (2 + Math.random() * 2) * (isRed ? 2 : 1);
invader._vy = 0;
invader._dir = Math.random() > 0.5 ? 1 : -1;
invader._row = ir;
invader._col = ic;
invader._alive = true;
invader._lastX = invader.x;
invader._lastY = invader.y;
invader._isHuge = false;
invader._isRed = isRed;
game.addChild(invader);
game._invaders.push(invader);
}
}
}
game._invadersInitialized = true;
}
// Move invaders (simple left-right bounce)
// Each bounce, all invaders get faster
if (game._invaders) {
var bounced = false;
for (var ii = 0; ii < game._invaders.length; ++ii) {
var inv = game._invaders[ii];
if (!inv._alive) {
// Hide hitbox if invader is dead
if (inv._hitboxRect && inv._hitboxRect.parent) inv._hitboxRect.visible = false;
continue;
}
// Hitbox visualization for invaders removed to reduce lag
inv._lastX = inv.x;
inv._lastY = inv.y;
inv.x += inv._vx * inv._dir;
// Bounce at screen edges
if (inv.x < 120) {
inv.x = 120;
inv._dir = 1;
bounced = true;
// Move invader lower on bounce
inv.y += 60;
}
if (inv.x > 2048 - 120) {
inv.x = 2048 - 120;
inv._dir = -1;
bounced = true;
// Move invader lower on bounce
inv.y += 60;
}
// Teleport invader to top if it hits the bottom of the screen
if (inv.y > 2732 - inv.height / 2) {
inv.y = inv.height / 2 + 10;
}
}
// If any invader bounced, increase speed of all invaders
if (bounced) {
for (var ii = 0; ii < game._invaders.length; ++ii) {
var inv = game._invaders[ii];
if (!inv._alive) continue;
// Red invaders always 2x faster
if (inv._isHuge) {
inv._vx *= 1.08; // Huge invaders: increase speed moderately
} else if (inv._isRed) {
inv._vx *= 1.16; // Red invaders: increase speed more
} else {
inv._vx *= 1.08; // Normal invaders: increase speed
}
// Clamp to a reasonable max speed
if (inv._vx > 32) inv._vx = 32;
}
}
}
// --- Check collision with invaders (all invaders) ---
var hitInvader = -1;
if (game._invaders) {
for (var ci = 0; ci < game._invaders.length; ++ci) {
var invader = game._invaders[ci];
if (!invader._alive) continue;
// Use .intersects for collision detection
if (typeof bullet.lastWasIntersectingInvaders === "undefined") bullet.lastWasIntersectingInvaders = [];
if (typeof bullet.lastWasIntersectingInvaders[ci] === "undefined") bullet.lastWasIntersectingInvaders[ci] = false;
var isIntersecting = bullet.intersects(invader);
// Only trigger on the exact frame of collision
if (!bullet.lastWasIntersectingInvaders[ci] && isIntersecting) {
hitInvader = ci;
bullet.lastWasIntersectingInvaders[ci] = isIntersecting;
break;
}
bullet.lastWasIntersectingInvaders[ci] = isIntersecting;
}
}
if (hitInvader !== -1 && bullet._shooterIdx !== null) {
// Only increment super bar if shooter exists and hit an invader
var shooterIdx = bullet._shooterIdx;
superBarPercents[shooterIdx] = clamp(superBarPercents[shooterIdx] + 0.01, 0, 1);
if (cannonSuperBars[shooterIdx] && cannonSuperBars[shooterIdx].fill && typeof cannonSuperBars[shooterIdx].fill._setFill === "function") {
cannonSuperBars[shooterIdx].fill._setFill(superBarPercents[shooterIdx]);
}
showSuperBarPopup(shooterIdx, 1);
// Remove bullet
if (bullet.parent) bullet.parent.removeChild(bullet);
bullets.splice(b, 1);
// Remove invader
var inv = game._invaders[hitInvader];
if (inv && inv.parent) {
// If it's a huge invader, split it!
if (inv._isHuge && inv._size > 1) {
// Split into two smaller invaders of size-1
var splitSize = inv._size - 1;
var offset = 80 * splitSize;
for (var s = -1; s <= 1; s += 2) {
var splitInv = createHugeInvader(inv.x + s * offset, inv.y, splitSize, splitSize === 2 ? 0xffd700 : 0xff69b4 // gold for big, pink for small
);
// Give them a little horizontal velocity away from center
splitInv._vx = 2.5 * s;
splitInv._dir = s;
game.addChild(splitInv);
game._invaders.push(splitInv);
}
}
inv._alive = false;
inv.parent.removeChild(inv);
// Play hit sound
var hitSound = LK.getSound('hit');
if (hitSound) hitSound.play();
// --- Increment and show level score ---
levelScore += 1;
if (levelScoreText) {
levelScoreText.setText("Score: " + levelScore);
}
}
// --- Check if all invaders are destroyed, then start next level ---
var allDead = true;
for (var checki = 0; checki < game._invaders.length; ++checki) {
if (game._invaders[checki]._alive) {
allDead = false;
break;
}
}
if (allDead) {
// Remove any remaining invader objects from stage
for (var remi = 0; remi < game._invaders.length; ++remi) {
if (game._invaders[remi].parent) game._invaders[remi].parent.removeChild(game._invaders[remi]);
}
// Next level: re-initialize invaders, increase difficulty if desired
game._invaders = [];
game._invadersInitialized = false; // Ensure this is reset so new invaders spawn next frame
// Boss logic removed - no boss will appear, only invaders and normal level progression.
// Advance level and update tracker
currentLevel++;
// Play level up sound
var levelupSound = LK.getSound('levelup');
if (levelupSound) levelupSound.play();
if (levelTrackerText) {
levelTrackerText.setText("Level " + currentLevel);
}
// Optionally, reset or keep score as desired (here, keep score)
}
continue;
}
// Remove bullet if off screen
if (bullet.y + bullet.height < 0) {
if (bullet.parent) bullet.parent.removeChild(bullet);
bullets.splice(b, 1);
}
}
// --- Power-Up Logic ---
// Power-up types
var powerUpTypes = [{
name: "Super Filler",
color: 0xffe066
}, {
name: "Healer",
color: 0x66ffb3
}, {
name: "Shield",
color: 0x66aaff
}];
var powerUps = [];
var powerUpWidth = 120;
var powerUpHeight = 120;
var powerUpSpeed = 8; // px per frame, falls down
// Helper to create a power-up at (x, y) with a type index
function createPowerUp(x, y, typeIdx) {
var type = powerUpTypes[typeIdx];
var pu = LK.getAsset('box', {
width: powerUpWidth,
height: powerUpHeight,
color: type.color,
anchorX: 0.5,
anchorY: 0.5
});
pu.x = x;
pu.y = y;
pu._vy = powerUpSpeed;
pu._typeIdx = typeIdx;
// Add label
var label = new Text2(type.name, {
size: 38,
fill: 0x222222,
background: 0xffffff,
padding: 10,
borderRadius: 18
});
label.anchor.set(0.5, 0.5);
label.x = 0;
label.y = 0;
pu.addChild(label);
return pu;
}
// Power-up spawn timer
var powerUpInterval = 1200; // 20 seconds at 60fps
var powerUpTimer = powerUpInterval;
game.updatePowerUps = function () {
// Spawn logic
powerUpTimer--;
if (powerUpTimer <= 0) {
// Pick random type
var idx = Math.floor(Math.random() * powerUpTypes.length);
// Spawn at random X along the top, not too close to edges
var px = Math.floor(Math.random() * (maxX - minX - 200)) + minX + 100;
var py = -powerUpHeight / 2;
var pu = createPowerUp(px, py, idx);
game.addChild(pu);
powerUps.push(pu);
powerUpTimer = powerUpInterval;
}
// Move power-ups
for (var p = powerUps.length - 1; p >= 0; --p) {
var pu = powerUps[p];
pu.y += pu._vy;
// Hitbox visualization for powerups removed to reduce lag
// Remove if off screen
if (pu.y - powerUpHeight / 2 > 2732) {
if (pu.parent) pu.parent.removeChild(pu);
powerUps.splice(p, 1);
continue;
}
// TODO: Add collision logic with cannons if needed
}
};
// Call power-up update each frame
game.updatePowerUps();
};
// --- P1 Cannon Hand/Touch Movement Logic ---
var p1Dragging = false;
// Double tap detection for super shot
var lastTapTime = 0;
var doubleTapThreshold = 400; // ms
var superShotActive = false;
var superShotTimer = 0;
var superShotDuration = 60; // 1 second at 60fps
var superShotLaser = null;
game.down = function (x, y, obj) {
// Only allow drag if touch/click is within P1 cannon bounds
var p1 = cannonSprites[0];
// Use bounding box for hit test
var halfW = p1.width / 2;
var halfH = p1.height;
var now = Date.now();
if (x >= p1.x - halfW && x <= p1.x + halfW && y >= p1.y - halfH && y <= p1.y) {
// Double tap detection for super shot
if (superBarPercents[0] >= 1 && !superShotActive) {
if (now - lastTapTime < doubleTapThreshold) {
// Activate super shot for 10 seconds!
superShotActive = true;
superShotTimer = 10 * 60; // 10 seconds at 60fps
// Remove countdown if present
if (cannonSuperBars[0]._superCountdown && cannonSuperBars[0]._superCountdown.text.parent) {
cannonSuperBars[0]._superCountdown.text.parent.removeChild(cannonSuperBars[0]._superCountdown.text);
}
cannonSuperBars[0]._superCountdown = null;
// Show laser
if (superShotLaser && superShotLaser.parent) superShotLaser.parent.removeChild(superShotLaser);
superShotLaser = LK.getAsset('box', {
width: 40,
height: 2732,
color: 0xff2222,
anchorX: 0.5,
anchorY: 1
});
superShotLaser.x = p1.x;
superShotLaser.y = p1.y - p1.height;
superShotLaser.alpha = 0.7;
game.addChild(superShotLaser);
// Reset super bar percent
superBarPercents[0] = 0;
cannonSuperBars[0].fill._setFill(0);
if (cannonSuperBars[0]._percentText) cannonSuperBars[0]._percentText.setText("0%");
}
lastTapTime = now;
} else {
lastTapTime = now;
}
p1Dragging = true;
}
};
game.move = function (x, y, obj) {
if (p1Dragging) {
// Clamp movement to allowed bounds, only X axis, Y is fixed at bottom
var p1 = cannonSprites[0];
var halfW = p1.width / 2;
var newX = clamp(x, minX, maxX);
p1.x = newX;
p1.y = cannonYStart; // Always at the bottom
// Move the name label as well
if (cannonNameLabels[0]) {
cannonNameLabels[0].x = p1.x;
cannonNameLabels[0].y = p1.y - 200;
}
// Move laser if super shot is active
if (superShotActive && superShotLaser) {
superShotLaser.x = p1.x;
}
}
};
// Update YOU label and arrow to follow player cannon every frame
var youLabelFollowInterval = null;
function startYouLabelFollowing() {
if (youLabelFollowInterval) LK.clearInterval(youLabelFollowInterval);
youLabelFollowInterval = LK.setInterval(function () {
// This will be called from game.update to keep YOU label on top of cannon
}, 16);
}
game.up = function (x, y, obj) {
p1Dragging = false;
};
// --- Super Shot Laser Logic in game.update ---
var _oldGameUpdate = game.update;
var youLabelRef = null;
var arrowLabelRef = null;
game.update = function () {
// Update YOU label and arrow to follow player cannon
if (youLabelRef && cannonSprites[0]) {
youLabelRef.x = cannonSprites[0].x;
youLabelRef.y = cannonSprites[0].y - 110;
}
if (arrowLabelRef && cannonSprites[0]) {
arrowLabelRef.x = cannonSprites[0].x;
}
// Super shot logic
if (superShotActive && superShotLaser) {
// Laser follows cannon
var p1 = cannonSprites[0];
superShotLaser.x = p1.x;
superShotLaser.y = p1.y - p1.height;
// Destroy all invaders in laser path
if (game._invaders) {
for (var ii = 0; ii < game._invaders.length; ++ii) {
var inv = game._invaders[ii];
if (!inv._alive) continue;
// Check if invader is in laser's X range
if (Math.abs(inv.x - superShotLaser.x) < (superShotLaser.width + inv.width) / 2) {
// Remove invader
inv._alive = false;
if (inv.parent) inv.parent.removeChild(inv);
}
}
}
// Destroy all bullets in laser path except player's own
for (var b = bullets.length - 1; b >= 0; --b) {
var bullet = bullets[b];
if (bullet._shooterIdx !== 0) {
if (Math.abs(bullet.x - superShotLaser.x) < (superShotLaser.width + bullet.width) / 2) {
if (bullet.parent) bullet.parent.removeChild(bullet);
bullets.splice(b, 1);
}
}
}
// Power-ups are not destroyed by laser (optional: add if needed)
// Decrement timer
superShotTimer--;
if (superShotTimer <= 0) {
// End super shot
superShotActive = false;
if (superShotLaser && superShotLaser.parent) superShotLaser.parent.removeChild(superShotLaser);
superShotLaser = null;
// Reset super bar (already reset on activation, so do not reset again here)
// If countdown text is still present, remove it
if (cannonSuperBars[0] && cannonSuperBars[0]._superCountdown && cannonSuperBars[0]._superCountdown.text.parent) {
cannonSuperBars[0]._superCountdown.text.parent.removeChild(cannonSuperBars[0]._superCountdown.text);
cannonSuperBars[0]._superCountdown = null;
}
}
}
// Call original update logic
if (_oldGameUpdate) _oldGameUpdate.apply(this, arguments);
};
}, 800);
}
}, 350);
}
}, 1000);
}, 5000);
};
};
singlePlayerBtn.down = function (x, y, obj) {
// You can add your Single-Player logic here
LK.effects.flashObject(singlePlayerBtn, 0x00ff00, 400);
// For now, just flash the button to show it was pressed
};
}
// Show title immediately
studioText.alpha = 1;
madeByText.alpha = 1;
// Helper to clear all intro timeouts if skipping
function clearIntroTimeouts() {
for (var i = 0; i < skipTimeouts.length; ++i) {
LK.clearTimeout(skipTimeouts[i]);
}
skipTimeouts = [];
}
// Add skip button handler
skipBtn.down = function (x, y, obj) {
skipIntro = true;
clearIntroTimeouts();
showTitleScreen();
};
// Stay for 2 seconds, then hide instantly
skipTimeouts.push(LK.setTimeout(function () {
if (skipIntro) return;
studioText.alpha = 0;
madeByText.alpha = 0;
}, 2000));
// After Pixelated Studios, show Gemini Games
var geminiMadeByText = new Text2("Made By", {
size: 90,
fill: 0xFFFFFF
});
geminiMadeByText.anchor.set(0.5, 1);
geminiMadeByText.alpha = 0;
geminiMadeByText.x = 2048 / 2;
geminiMadeByText.y = 2732 / 2 - 120;
game.addChild(geminiMadeByText);
var geminiText = new Text2("Gemini Games,", {
size: 180,
fill: 0xFFFFFF
});
geminiText.anchor.set(0.5, 0.5);
geminiText.alpha = 0;
geminiText.x = 2048 / 2;
geminiText.y = 2732 / 2;
game.addChild(geminiText);
// 3 seconds after Pixelated Studios fades out (4+600+2000+600+3000 = 10200ms)
skipTimeouts.push(LK.setTimeout(function () {
if (skipIntro) return;
tween(geminiText, {
alpha: 1
}, {
duration: 600,
easing: tween.easeOut
});
tween(geminiMadeByText, {
alpha: 1
}, {
duration: 600,
easing: tween.easeOut
});
skipTimeouts.push(LK.setTimeout(function () {
if (skipIntro) return;
tween(geminiText, {
alpha: 0
}, {
duration: 600,
easing: tween.easeIn
});
tween(geminiMadeByText, {
alpha: 0
}, {
duration: 600,
easing: tween.easeIn
});
}, 2000));
}, 10200));
// After Gemini Games, show Upit Studios
var andText = new Text2("And", {
size: 90,
fill: 0xFFFFFF
});
andText.anchor.set(0.5, 1);
andText.alpha = 0;
andText.x = 2048 / 2;
andText.y = 2732 / 2 - 120;
game.addChild(andText);
var upitText = new Text2("Upit Studios", {
size: 180,
fill: 0xFFFFFF
});
upitText.anchor.set(0.5, 0.5);
upitText.alpha = 0;
upitText.x = 2048 / 2;
upitText.y = 2732 / 2;
game.addChild(upitText);
// 3 seconds after Gemini Games fades out (10200+600+2000+600+3000 = 16300ms)
skipTimeouts.push(LK.setTimeout(function () {
if (skipIntro) return;
tween(upitText, {
alpha: 1
}, {
duration: 600,
easing: tween.easeOut
});
tween(andText, {
alpha: 1
}, {
duration: 600,
easing: tween.easeOut
});
skipTimeouts.push(LK.setTimeout(function () {
if (skipIntro) return;
tween(upitText, {
alpha: 0
}, {
duration: 600,
easing: tween.easeIn
});
tween(andText, {
alpha: 0
}, {
duration: 600,
easing: tween.easeIn
});
}, 2000));
}, 16300));
// After Upit Studios, show the title screen
// 3 seconds after Upit Studios fades out (16300+600+2000+600+3000 = 22400ms)
skipTimeouts.push(LK.setTimeout(function () {
if (skipIntro) return;
showTitleScreen();
}, 22400));
;
;
// --- Huge Invader Class ---
// A huge invader that splits into two smaller invaders when shot
function createHugeInvader(x, y, size, color) {
// size: 1 = normal, 2 = big, 3 = huge
var width = 120 * size;
var height = 80 * size;
var invader = LK.getAsset('box', {
width: width,
height: height,
color: color || 0xffa500,
anchorX: 0.5,
anchorY: 0.5
});
invader.x = x;
invader.y = y;
invader._vx = 1.5 + Math.random() * 1.5;
invader._vy = 0;
invader._dir = Math.random() > 0.5 ? 1 : -1;
invader._size = size;
invader._alive = true;
invader._lastX = invader.x;
invader._lastY = invader.y;
invader._isHuge = true;
return invader;
}