User prompt
limit the invaders to prevent lag. plus above level 8 why’d the assets say bye bye? Plus there are no sounds!
User prompt
Add ∞ levels. plus add more sounds! And the YOU label is not on top of the player cannon. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
make the YOU be on top of the player cannon and follow the cannon. plus add TTS for the “READY?” “GO!” by making sounds. And when it goes it displays a blinking text saying: “shoot every single invader!” ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Pause all gameplay when it says “READY?” “GO!” and after the go fades out the gameplay starts. And while gameplay is happening display a “YOU” with an arrow pointing to your character. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Add a “READY?” “GO!” at the start of the first level. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
end game when you hit level 8
User prompt
hide cannons bullets when hit level 8
User prompt
when you hit level 8 hide cannons. gameplay. bullets. super bars. mega bar. and the percentage for the super bar
User prompt
Please fix the bug (Invaders Arnt Spawining at a sertan Level)
User prompt
move the super bars percentage to the top of your name and apply that to everyone and make the percentage bigger
User prompt
replace the text “AI-Controlled Cannons” to “4 Player Game”
User prompt
hide hitboxes to remove lag
User prompt
add more invaders each level
User prompt
remove boss
User prompt
please fix the bug (Boss can’t lose health when shot at)
User prompt
add hitboxes to the boss so it can lose health when the HITBOX is hit
User prompt
show hitboxez
User prompt
when the super bar is filled and on 100% play the sound called “super”
User prompt
make collision between the bullet and the invaders and the boss
User prompt
why isn’t the boss taking damage? fix that!
User prompt
make the boss lose health when hit and make the boss shoot huge projectiles
User prompt
make the AI movement target the invader that’s the closest to them or that’s the only one.
User prompt
after 10 levels. a warning sign pops up. and the sign lasts for 3s. after those 3 secs the sign that says “BOSS INCOMING” and the message also lasts for 3 seconds and the boss comes. during that boss a bar is under the boss and the bar is the health.
User prompt
if the countdown ends. the super bar enables itself and resets the percentage and if you enable it manually. it still resets your percentage and the super bar enabled only lasts for 10 seconds
User prompt
each shot you make the more fire rate you get. and make the total score of the level on top and make a level tracker that tracks what level your on
/****
* 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? TTS sound
if (window.speechSynthesis) {
var readyUtterance = new SpeechSynthesisUtterance("Ready");
readyUtterance.rate = 1.2;
window.speechSynthesis.speak(readyUtterance);
}
// 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! TTS sound
if (window.speechSynthesis) {
var goUtterance = new SpeechSynthesisUtterance("Go");
goUtterance.rate = 1.2;
window.speechSynthesis.speak(goUtterance);
}
// 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);
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;
} ===================================================================
--- original.js
+++ change.js
@@ -529,9 +529,9 @@
borderRadius: 24
});
youText.anchor.set(0.5, 1);
youText.x = cannonSprites[0].x;
- youText.y = cannonSprites[0].y - 40;
+ youText.y = cannonSprites[0].y - 110;
game.addChild(youText);
youLabelRef = youText;
// Create arrow pointing down to player cannon
var arrowText = new Text2("↓", {
@@ -866,13 +866,10 @@
// --- Pause gameplay during READY? GO! sequence ---
if (gameplayPaused) {
return;
}
- // --- End game at level 8 ---
- if (currentLevel >= 8) {
- LK.showGameOver();
- 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
@@ -1266,8 +1263,11 @@
}
}
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);
@@ -1291,8 +1291,11 @@
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)
@@ -1473,9 +1476,9 @@
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 - 40;
+ youLabelRef.y = cannonSprites[0].y - 110;
}
if (arrowLabelRef && cannonSprites[0]) {
arrowLabelRef.x = cannonSprites[0].x;
}