/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// --- Bullet Setup ---
var Bullet = Container.expand(function () {
var self = Container.call(this);
var bulletSprite = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speedY = -18;
self.update = function () {
self.y += self.speedY;
// Remove if off screen
if (self.y < -100) {
self.destroy();
if (bullets.indexOf(self) !== -1) bullets.splice(bullets.indexOf(self), 1);
}
};
return self;
});
// --- Arrays to track objects ---
// --- Invader Setup ---
// Create a simple invader class
var Invader = Container.expand(function () {
var self = Container.call(this);
var invaderSprite = self.attachAsset('invader', {
anchorX: 0.5,
anchorY: 0.5
});
// Start at random X, near top
self.x = 200 + Math.random() * (2048 - 400);
self.y = 300 + Math.random() * 200;
// Move down slowly
self.speedY = 2 + Math.random() * 2;
self.update = function () {
self.y += self.speedY;
// Remove if off screen
if (self.y > 2732 + 100) {
self.destroy();
if (invaders.indexOf(self) !== -1) invaders.splice(invaders.indexOf(self), 1);
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// --- Arrays to track objects ---
var bullets = [];
var invaders = [];
// --- Development Notice (bottom middle) ---
var devNoticeText = new Text2('still in development. I will update some fridays', {
size: 38,
fill: 0xFFFF00,
align: 'center',
stroke: 0x000000,
strokeThickness: 6
});
devNoticeText.anchor.set(0.5, 1); // center bottom
// Add to bottom center of the GUI
LK.gui.bottom.addChild(devNoticeText);
var titleText = new Text2('Pixelossed Rush', {
size: 180,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 2048 / 2;
titleText.y = 2732 / 2;
game.addChild(titleText);
;
// --- Tap to Start: 5s Countdown then How to Play screen ---
var countdownActive = false;
var countdownValue = 5;
var countdownText = null;
var howToPlayText = null;
var countdownTimer = null;
var attractModeStarted = false;
// Helper to clear countdown UI
function clearCountdownUI() {
if (countdownText && countdownText.parent) countdownText.parent.removeChild(countdownText);
countdownText = null;
}
// Helper to clear How to Play UI
function clearHowToPlayUI() {
if (howToPlayText && howToPlayText.parent) howToPlayText.parent.removeChild(howToPlayText);
howToPlayText = null;
}
// Tap handler for game
game.down = function (x, y, obj) {
if (countdownActive || attractModeStarted) return;
countdownActive = true;
countdownValue = 5;
// Remove attract mode UI if present
clearHowToPlayUI();
clearCountdownUI();
// Remove title, attract, Press Start, and FREE PLAY messages if present
if (titleText && titleText.parent) titleText.parent.removeChild(titleText);
titleText = null;
if (typeof frenzyText !== "undefined" && frenzyText && frenzyText.parent) frenzyText.parent.removeChild(frenzyText);
if (typeof pressStartText !== "undefined" && pressStartText && pressStartText.parent) pressStartText.parent.removeChild(pressStartText);
if (typeof freePlayText !== "undefined" && freePlayText && freePlayText.parent) freePlayText.parent.removeChild(freePlayText);
// Prevent any of these texts from being created after tap if they weren't already present
if (typeof frenzyText === "undefined" || !frenzyText) window.frenzyTextBlocked = true;
if (typeof pressStartText === "undefined" || !pressStartText) window.pressStartTextBlocked = true;
if (typeof freePlayText === "undefined" || !freePlayText) window.freePlayTextBlocked = true;
// REMOVE ATTRACT MODE (AI) ON TAP
// Block attract mode from being created if tap occurred before attract mode creation
window.attractModeBlocked = true;
// Remove attract mode timer and warning sign immediately
if (typeof attractCountdownInterval !== "undefined" && attractCountdownInterval) LK.clearInterval(attractCountdownInterval);
if (typeof attractCountdownText !== "undefined" && attractCountdownText && attractCountdownText.parent) attractCountdownText.parent.removeChild(attractCountdownText);
attractCountdownText = null;
if (typeof hideWarningUI === "function") hideWarningUI();
// Prevent warning sign from spawning after tap
window.attractWarningBlocked = true;
if (typeof attractMode !== "undefined") {
attractMode = false;
// Remove AI cannon if present
if (typeof cannon !== "undefined" && cannon && cannon.parent) {
cannon.parent.removeChild(cannon);
}
// Optionally clear invaders and bullets for a clean start
for (var i = invaders.length - 1; i >= 0; i--) {
if (invaders[i] && invaders[i].parent) invaders[i].parent.removeChild(invaders[i]);
invaders[i].destroy && invaders[i].destroy();
invaders.splice(i, 1);
}
for (var i = bullets.length - 1; i >= 0; i--) {
if (bullets[i] && bullets[i].parent) bullets[i].parent.removeChild(bullets[i]);
bullets[i].destroy && bullets[i].destroy();
bullets.splice(i, 1);
}
}
// Show countdown text in center
countdownText = new Text2(countdownValue + '', {
size: 220,
fill: 0xFFFF00
});
countdownText.anchor.set(0.5, 0.5);
countdownText.x = 2048 / 2;
countdownText.y = 2732 / 2;
game.addChild(countdownText);
// Start countdown timer
countdownTimer = LK.setInterval(function () {
countdownValue--;
if (countdownText) countdownText.setText(countdownValue + '');
if (countdownValue <= 0) {
LK.clearInterval(countdownTimer);
clearCountdownUI();
// Show How to Play screen
howToPlayText = new Text2('How to Play\n\nMove the cannon to shoot invaders!\nDon\'t let any escape!', {
size: 120,
fill: 0xFFFFFF,
align: 'center'
});
howToPlayText.anchor.set(0.5, 0.5);
howToPlayText.x = 2048 / 2;
howToPlayText.y = 2732 / 2;
game.addChild(howToPlayText);
// After 2s, remove How to Play and start attract mode
LK.setTimeout(function () {
clearHowToPlayUI();
attractModeStarted = true;
// Remove attract mode (AI cannon and invaders) after 5s countdown, then start real gameplay
LK.setTimeout(function () {
// Remove AI cannon if present
if (typeof cannon !== "undefined" && cannon && cannon.parent) {
cannon.parent.removeChild(cannon);
}
// Remove all invaders
for (var i = invaders.length - 1; i >= 0; i--) {
if (invaders[i] && invaders[i].parent) invaders[i].parent.removeChild(invaders[i]);
invaders[i].destroy && invaders[i].destroy();
invaders.splice(i, 1);
}
// Remove all bullets
for (var i = bullets.length - 1; i >= 0; i--) {
if (bullets[i] && bullets[i].parent) bullets[i].parent.removeChild(bullets[i]);
bullets[i].destroy && bullets[i].destroy();
bullets.splice(i, 1);
}
// End attract mode
if (typeof attractMode !== "undefined") {
attractMode = false;
}
// --- Start Real Gameplay ---
// Start/reset level timer for 60s level cycles
startLevelTimer();
// Re-add the timer for gameplay (level timer) and make it visible again
if (typeof attractCountdownText === "undefined" || !attractCountdownText) {
attractCountdownText = new Text2("60s", {
size: 80,
fill: 0xFFFF00,
align: 'center',
stroke: 0x000000,
strokeThickness: 6
});
attractCountdownText.anchor.set(0.5, 0);
// Place under FREE PLAY if it exists, else fallback to top center
if (typeof freePlayText !== "undefined" && freePlayText && freePlayText.parent) {
attractCountdownText.x = freePlayText.x;
attractCountdownText.y = freePlayText.y + freePlayText.height + 10;
game.addChild(attractCountdownText);
} else {
attractCountdownText.x = 2048 / 2;
attractCountdownText.y = 40 + 120 + 20 + 90 + 10;
game.addChild(attractCountdownText);
}
}
var gameplayCountdownValue = 60;
if (attractCountdownText) attractCountdownText.setText(gameplayCountdownValue + "s");
if (typeof gameplayCountdownInterval !== "undefined" && gameplayCountdownInterval) LK.clearInterval(gameplayCountdownInterval);
gameplayCountdownInterval = LK.setInterval(function () {
gameplayCountdownValue--;
if (attractCountdownText) attractCountdownText.setText(gameplayCountdownValue + "s");
if (gameplayCountdownValue <= 0) {
if (gameplayCountdownInterval) LK.clearInterval(gameplayCountdownInterval);
if (attractCountdownText && attractCountdownText.parent) attractCountdownText.parent.removeChild(attractCountdownText);
attractCountdownText = null;
}
}, 1000);
// Create player cannon
var playerCannon = LK.getAsset('cannon', {
anchorX: 0.5,
anchorY: 0.5
});
playerCannon.x = 2048 / 2;
playerCannon.y = 2732 - 200;
game.addChild(playerCannon);
// Player state
var playerShootCooldown = 0;
var playerLeftBound = 120;
var playerRightBound = 2048 - 120;
// Touch drag for cannon
var dragging = false;
game.down = function (x, y, obj) {
// Only allow drag if tap is near cannon
if (Math.abs(x - playerCannon.x) < 200 && Math.abs(y - playerCannon.y) < 200) {
dragging = true;
}
};
game.move = function (x, y, obj) {
if (dragging) {
playerCannon.x = Math.max(playerLeftBound, Math.min(playerRightBound, x));
}
};
game.up = function (x, y, obj) {
dragging = false;
};
// Spawn invaders at interval
var gameplayInvaderTimer = LK.setInterval(function () {
var inv = new Invader();
invaders.push(inv);
game.addChild(inv);
}, 700);
// --- Level Timer: Reset gameplay every 60 seconds ---
var levelTimer = null;
var levelNoticeText = null;
var levelNoticeTimeout = null;
function showLevelNotice() {
// Remove previous if present
if (levelNoticeText && levelNoticeText.parent) levelNoticeText.parent.removeChild(levelNoticeText);
levelNoticeText = new Text2("this is still in development! So it’s a beta. please do not get mad. I made this 40 minutes before bedtime. not my fault!", {
size: 38,
fill: 0xFFFF00,
align: 'center',
stroke: 0x000000,
strokeThickness: 6
});
levelNoticeText.anchor.set(0.5, 0.5);
// Center of screen
levelNoticeText.x = 2048 / 2;
levelNoticeText.y = 2732 / 2;
LK.gui.center.addChild(levelNoticeText);
// Remove after 2.5 seconds
if (levelNoticeTimeout) LK.clearTimeout(levelNoticeTimeout);
levelNoticeTimeout = LK.setTimeout(function () {
if (levelNoticeText && levelNoticeText.parent) levelNoticeText.parent.removeChild(levelNoticeText);
levelNoticeText = null;
}, 2500);
}
function startLevelTimer() {
if (levelTimer) LK.clearInterval(levelTimer);
// Show the notice after 60 seconds
levelTimer = LK.setInterval(function () {
showLevelNotice();
// Reset gameplay: clear all invaders and bullets, reset player cannon position, reset score if needed, etc.
// Remove all invaders
for (var i = invaders.length - 1; i >= 0; i--) {
if (invaders[i] && invaders[i].parent) invaders[i].parent.removeChild(invaders[i]);
invaders[i].destroy && invaders[i].destroy();
invaders.splice(i, 1);
}
// Remove all bullets
for (var i = bullets.length - 1; i >= 0; i--) {
if (bullets[i] && bullets[i].parent) bullets[i].parent.removeChild(bullets[i]);
bullets[i].destroy && bullets[i].destroy();
bullets.splice(i, 1);
}
// Reset player cannon position if it exists
if (typeof playerCannon !== "undefined" && playerCannon) {
playerCannon.x = 2048 / 2;
playerCannon.y = 2732 - 200;
}
// Optionally: reset playerShootCooldown
if (typeof playerShootCooldown !== "undefined") playerShootCooldown = 0;
// Optionally: you can increase difficulty here (e.g. decrease invader spawn interval, increase speed, etc.)
// Remove attract mode timer and warning UI if present before starting gameplay timer
if (typeof attractCountdownInterval !== "undefined" && attractCountdownInterval) LK.clearInterval(attractCountdownInterval);
if (typeof attractCountdownText !== "undefined" && attractCountdownText && attractCountdownText.parent) attractCountdownText.parent.removeChild(attractCountdownText);
attractCountdownText = null;
if (typeof hideWarningUI === "function") hideWarningUI();
// Allow warning sign to appear in gameplay last 10s
window.attractWarningBlocked = false;
// Restart the gameplay timer and make it visible again
gameplayCountdownValue = 60;
if (typeof attractCountdownText === "undefined" || !attractCountdownText) {
attractCountdownText = new Text2(gameplayCountdownValue + "s", {
size: 80,
fill: 0xFFFF00,
align: 'center',
stroke: 0x000000,
strokeThickness: 6
});
attractCountdownText.anchor.set(0.5, 0);
// Place under FREE PLAY if it exists, else fallback to top center
if (typeof freePlayText !== "undefined" && freePlayText && freePlayText.parent) {
attractCountdownText.x = freePlayText.x;
attractCountdownText.y = freePlayText.y + freePlayText.height + 10;
game.addChild(attractCountdownText);
} else {
attractCountdownText.x = 2048 / 2;
attractCountdownText.y = 40 + 120 + 20 + 90 + 10;
game.addChild(attractCountdownText);
}
}
if (attractCountdownText) {
attractCountdownText.setText(gameplayCountdownValue + "s");
}
if (typeof gameplayCountdownInterval !== "undefined" && gameplayCountdownInterval) LK.clearInterval(gameplayCountdownInterval);
gameplayCountdownInterval = LK.setInterval(function () {
gameplayCountdownValue--;
if (attractCountdownText) attractCountdownText.setText(gameplayCountdownValue + "s");
// Show/hide warning UI for last 10 seconds in gameplay
if (gameplayCountdownValue <= 10 && gameplayCountdownValue > 0) {
if (typeof showWarningUI === "function") showWarningUI();
} else {
if (typeof hideWarningUI === "function") hideWarningUI();
}
if (gameplayCountdownValue <= 0) {
if (gameplayCountdownInterval) LK.clearInterval(gameplayCountdownInterval);
if (attractCountdownText && attractCountdownText.parent) attractCountdownText.parent.removeChild(attractCountdownText);
attractCountdownText = null;
if (typeof hideWarningUI === "function") hideWarningUI();
}
}, 1000);
}, 60000); // 60 seconds
}
// Main update loop for gameplay
game.update = function () {
// Player shooting (auto-fire)
playerShootCooldown--;
if (playerShootCooldown <= 0) {
// Shoot bullet
var bullet = new Bullet();
bullet.x = playerCannon.x;
bullet.y = playerCannon.y - 60;
bullets.push(bullet);
game.addChild(bullet);
playerShootCooldown = 18;
}
// Update bullets
for (var b = bullets.length - 1; b >= 0; b--) {
var bullet = bullets[b];
if (bullet.update) bullet.update();
// Check collision with invaders
for (var j = invaders.length - 1; j >= 0; j--) {
var inv = invaders[j];
if (bullet.intersects(inv)) {
bullet.destroy();
inv.destroy();
bullets.splice(b, 1);
invaders.splice(j, 1);
break;
}
}
}
// Update invaders
for (var i = invaders.length - 1; i >= 0; i--) {
if (invaders[i].update) invaders[i].update();
// If any invader escapes, end game and show game over
if (invaders[i].y > 2732 + 100) {
LK.showGameOver();
break;
}
}
};
}, 5000);
}, 2000);
}
}, 1000);
};
// Add FRENZY title after 2 seconds with upward tween animation, positioned under the main title
// Expose to global scope for removal
var frenzyText = null;
var pressStartText = null;
var freePlayText = null;
LK.setTimeout(function () {
// Block FRENZY, Press Start, and FREE PLAY if tap occurred before their creation
if (window.frenzyTextBlocked) {
frenzyText = null;
} else {
frenzyText = new Text2('FRENZY', {
size: 200,
fill: 0xFFFFFF
});
frenzyText.anchor.set(0.5, 0.5);
frenzyText.x = 2048 / 2;
// Start below the main title, animate up to just under the main title
var spacing = 120;
var targetY;
if (titleText) {
targetY = titleText.y + titleText.height / 2 + frenzyText.height / 2 + spacing;
} else {
// Fallback: center of screen + some offset if titleText is gone
targetY = 2732 / 2 + 200 + frenzyText.height / 2 + spacing;
}
frenzyText.y = targetY + 200;
game.addChild(frenzyText);
// Animate the title going up to its target position
tween(frenzyText, {
y: targetY
}, {
duration: 900,
easing: tween.easeOut
});
}
if (window.pressStartTextBlocked) {
pressStartText = null;
} else {
// Add "Press Start!" message at the top of the screen
pressStartText = new Text2('Press Start!', {
size: 120,
fill: 0xFFFF00
});
pressStartText.anchor.set(0.5, 0);
// Centered horizontally, top of screen (leave 40px margin for safety)
pressStartText.x = 2048 / 2;
pressStartText.y = 40;
game.addChild(pressStartText);
}
if (window.freePlayTextBlocked) {
freePlayText = null;
} else {
// Add "FREE PLAY" message below "Press Start!" at the top
freePlayText = new Text2('FREE PLAY', {
size: 90,
fill: 0x00FF00
});
freePlayText.anchor.set(0.5, 0);
// Centered horizontally, just below "Press Start!"
freePlayText.x = 2048 / 2;
freePlayText.y = pressStartText && pressStartText.y ? pressStartText.y + pressStartText.height + 20 : 40 + 120 + 20;
game.addChild(freePlayText);
}
}, 2000);
;
// Attract mode: start after FRENZY appears (2s + animation ~1s)
LK.setTimeout(function () {
// Block attract mode if tap occurred before attract mode creation
if (window.attractModeBlocked) return;
// --- Attract Mode State ---
var attractMode = true;
// --- Cannon Setup ---
// Create a cannon asset (box shape, centered at bottom)
var cannon = LK.getAsset('cannon', {
anchorX: 0.5,
anchorY: 0.5
});
// Place cannon at bottom center
cannon.x = 2048 / 2;
cannon.y = 2732 - 200;
game.addChild(cannon);
// --- Arrays to track objects ---
// --- Spawn Invaders ---
var invaderTimer = LK.setInterval(function () {
if (!attractMode) return;
var inv = new Invader();
invaders.push(inv);
game.addChild(inv);
}, 700);
// --- Cannon AI movement ---
var cannonDir = 1; // 1 = right, -1 = left
var cannonSpeed = 16;
var leftBound = 120;
var rightBound = 2048 - 120;
// For shooting
var shootCooldown = 0;
// --- Attract Mode Timer: Only reset attract mode every 60s (no developer notice) ---
// Add a visible countdown timer under the FREE PLAY text
var attractCountdownValue = 60;
var attractCountdownText = new Text2(attractCountdownValue + "s", {
size: 80,
fill: 0xFFFF00,
align: 'center',
stroke: 0x000000,
strokeThickness: 6
});
attractCountdownText.anchor.set(0.5, 0);
// --- WARNING UI for final 10s ---
// Make the warning sign BIG, centered, and layered under the title and FRENZY
var warningGroup = new Container();
var warningText = null;
var warningLineTop = null;
var warningLineBot = null;
var warningLineTextTop = null;
var warningLineTextBot = null;
var warningScrollOffset = 0;
// Place under FREE PLAY if it exists, else fallback to top center
if (typeof freePlayText !== "undefined" && freePlayText && freePlayText.parent) {
attractCountdownText.x = freePlayText.x;
attractCountdownText.y = freePlayText.y + freePlayText.height + 10;
game.addChild(attractCountdownText);
} else {
// fallback: top center
attractCountdownText.x = 2048 / 2;
attractCountdownText.y = 40 + 120 + 20 + 90 + 10; // same as FREE PLAY default + 10px
game.addChild(attractCountdownText);
}
// Helper to show/hide warning UI
function showWarningUI() {
if (typeof window.attractWarningBlocked !== "undefined" && window.attractWarningBlocked) return;
if (warningText) return; // Already shown
// Make the warning sign BIG and centered, and layer it under the title and FRENZY
// Place warningGroup at z-index below titleText and frenzyText
// (Do not use setChildIndex on warningGroup, instead use addChildAt below)
// Red lines (as thick rectangles)
var lineWidth = 1800;
var lineHeight = 36;
var lineColor = 0xFF0000;
var centerX = 2048 / 2;
// Place warning in the vertical center of the screen
var warningAreaHeight = 900;
var baseY = 2732 / 2 - warningAreaHeight / 2;
var spacing = 320;
// Top line
warningLineTop = LK.getAsset('bullet', {
width: lineWidth,
height: lineHeight,
color: lineColor,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
warningLineTop.x = centerX;
warningLineTop.y = baseY + lineHeight / 2;
// Bottom line
warningLineBot = LK.getAsset('bullet', {
width: lineWidth,
height: lineHeight,
color: lineColor,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
warningLineBot.x = centerX;
warningLineBot.y = baseY + warningAreaHeight - lineHeight / 2;
// Center WARNING! text (very big)
warningText = new Text2("WARNING!", {
size: 340,
fill: 0xFF0000,
align: 'center',
stroke: 0x000000,
strokeThickness: 18
});
warningText.anchor.set(0.5, 0.5);
warningText.x = centerX;
warningText.y = baseY + warningAreaHeight / 2;
// Scrolling WARNING! text for lines (big)
var scrollText = "WARNING! WARNING! WARNING! WARNING! WARNING! ";
warningLineTextTop = new Text2(scrollText, {
size: 120,
fill: 0xFFFFFF,
align: 'left',
stroke: 0xFF0000,
strokeThickness: 10
});
warningLineTextTop.anchor.set(0, 0.5);
warningLineTextTop.x = centerX - lineWidth / 2 + 20;
warningLineTextTop.y = warningLineTop.y;
warningLineTextBot = new Text2(scrollText, {
size: 120,
fill: 0xFFFFFF,
align: 'left',
stroke: 0xFF0000,
strokeThickness: 10
});
warningLineTextBot.anchor.set(0, 0.5);
warningLineTextBot.x = centerX - lineWidth / 2 + 20;
warningLineTextBot.y = warningLineBot.y;
warningGroup.addChild(warningLineTop);
warningGroup.addChild(warningLineBot);
warningGroup.addChild(warningText);
warningGroup.addChild(warningLineTextTop);
warningGroup.addChild(warningLineTextBot);
// Insert warningGroup at the correct z-order: under title and FRENZY
// Remove and re-add to ensure correct order
if (game.children && game.children.indexOf(warningGroup) !== -1) {
game.removeChild(warningGroup);
}
// Find index of titleText and frenzyText, insert before the lowest one
var idxTitle = titleText && titleText.parent === game ? game.children.indexOf(titleText) : -1;
var idxFrenzy = frenzyText && frenzyText.parent === game ? game.children.indexOf(frenzyText) : -1;
var insertIdx = 0;
if (idxTitle >= 0 && idxFrenzy >= 0) {
insertIdx = Math.min(idxTitle, idxFrenzy);
} else if (idxTitle >= 0) {
insertIdx = idxTitle;
} else if (idxFrenzy >= 0) {
insertIdx = idxFrenzy;
}
game.addChildAt(warningGroup, insertIdx >= 0 ? insertIdx : 0);
}
function hideWarningUI() {
if (warningGroup && warningGroup.parent) warningGroup.parent.removeChild(warningGroup);
warningText = null;
warningLineTop = null;
warningLineBot = null;
warningLineTextTop = null;
warningLineTextBot = null;
warningGroup = new Container();
warningScrollOffset = 0;
}
function resetAttractCountdown() {
attractCountdownValue = 60;
if (attractCountdownText) attractCountdownText.setText(attractCountdownValue + "s");
hideWarningUI();
}
var attractCountdownInterval = LK.setInterval(function () {
attractCountdownValue--;
if (attractCountdownText) attractCountdownText.setText(attractCountdownValue + "s");
// Show/hide warning UI for last 10 seconds
if (attractCountdownValue <= 10 && attractCountdownValue > 0) {
showWarningUI();
} else {
hideWarningUI();
}
if (attractCountdownValue <= 0) {
// Hide the attract mode timer and warning UI, but do NOT reset gameplay or restart timer
if (attractCountdownText && attractCountdownText.parent) attractCountdownText.parent.removeChild(attractCountdownText);
attractCountdownText = null;
hideWarningUI();
// Optionally, you could set a flag to prevent further attract mode actions if needed
// No gameplay reset or timer restart here!
}
}, 1000); // every second
// Animate scrolling WARNING! text on lines
var lastWarningUpdateTick = 0;
game.update = function (origUpdate) {
return function () {
if (typeof origUpdate === "function") origUpdate.apply(this, arguments);
// Animate warning scroll if visible
if (warningText && warningLineTextTop && warningLineTextBot) {
warningScrollOffset += 8;
// Loop the text horizontally
var textWidth = warningLineTextTop.width;
var lineWidth = 1200;
var baseX = 2048 / 2 - lineWidth / 2 + 10;
warningLineTextTop.x = baseX - warningScrollOffset % textWidth;
warningLineTextBot.x = baseX + warningScrollOffset % textWidth;
}
};
}(game.update);
// When attract mode ends, remove the countdown text and interval
function clearAttractCountdown() {
if (attractCountdownInterval) LK.clearInterval(attractCountdownInterval);
if (attractCountdownText && attractCountdownText.parent) attractCountdownText.parent.removeChild(attractCountdownText);
attractCountdownText = null;
hideWarningUI();
}
// --- Main update loop for attract mode ---
game.update = function () {
if (!attractMode) return;
// --- AI Cannon Movement: Prioritize intercepting invaders about to escape ---
// Find invader closest to bottom (about to escape)
var escapeInvader = null;
var maxY = -99999;
for (var i = 0; i < invaders.length; i++) {
if (invaders[i].y > maxY) {
maxY = invaders[i].y;
escapeInvader = invaders[i];
}
}
// If any invader is close to escaping (e.g. y > 2732-350), target it
var targetInvader = null;
if (escapeInvader && escapeInvader.y > 2732 - 350) {
targetInvader = escapeInvader;
} else {
// Otherwise, target nearest in X as before
var nearest = null;
var minDist = 99999;
for (var i = 0; i < invaders.length; i++) {
var dx = Math.abs(invaders[i].x - cannon.x);
if (dx < minDist) {
minDist = dx;
nearest = invaders[i];
}
}
targetInvader = nearest;
}
// Move cannon toward target invader, but not instantly
if (targetInvader) {
var dx = targetInvader.x - cannon.x;
// Move at most cannonSpeed per frame, but slow down as it gets close
if (Math.abs(dx) > 10) {
cannon.x += Math.sign(dx) * Math.min(cannonSpeed, Math.abs(dx));
}
}
// Clamp cannon within bounds
if (cannon.x > rightBound) cannon.x = rightBound;
if (cannon.x < leftBound) cannon.x = leftBound;
// --- AI Cannon Shooting: Shoot at escaping invaders with higher priority ---
shootCooldown--;
var shouldShoot = false;
if (shootCooldown <= 0 && invaders.length > 0 && targetInvader) {
// If targeting an escaping invader, shoot if aligned in X
if (escapeInvader && escapeInvader === targetInvader && Math.abs(cannon.x - targetInvader.x) < 120) {
// 98% chance to shoot at escaping invader
if (Math.random() < 0.98) shouldShoot = true;
} else if (Math.abs(cannon.x - targetInvader.x) < 120) {
// 80% chance to shoot at other invaders
if (Math.random() < 0.8) shouldShoot = true;
}
if (shouldShoot) {
var bullet = new Bullet();
bullet.x = cannon.x;
bullet.y = cannon.y - 60;
bullets.push(bullet);
game.addChild(bullet);
}
// Randomize next cooldown (faster or slower)
shootCooldown = 18 + Math.floor(Math.random() * 18);
}
// Update bullets
for (var b = bullets.length - 1; b >= 0; b--) {
var bullet = bullets[b];
if (bullet.update) bullet.update();
// Check collision with invaders
for (var j = invaders.length - 1; j >= 0; j--) {
var inv = invaders[j];
if (bullet.intersects(inv)) {
// Destroy both
bullet.destroy();
inv.destroy();
bullets.splice(b, 1);
invaders.splice(j, 1);
break;
}
}
}
// Update invaders
for (var i = invaders.length - 1; i >= 0; i--) {
if (invaders[i].update) invaders[i].update();
// If any invader escapes, end attract mode and show game over
if (invaders[i].y > 2732 + 100) {
attractMode = false;
LK.showGameOver();
break;
}
}
};
}, 3000); // 2s for FRENZY + 1s for animation
; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// --- Bullet Setup ---
var Bullet = Container.expand(function () {
var self = Container.call(this);
var bulletSprite = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speedY = -18;
self.update = function () {
self.y += self.speedY;
// Remove if off screen
if (self.y < -100) {
self.destroy();
if (bullets.indexOf(self) !== -1) bullets.splice(bullets.indexOf(self), 1);
}
};
return self;
});
// --- Arrays to track objects ---
// --- Invader Setup ---
// Create a simple invader class
var Invader = Container.expand(function () {
var self = Container.call(this);
var invaderSprite = self.attachAsset('invader', {
anchorX: 0.5,
anchorY: 0.5
});
// Start at random X, near top
self.x = 200 + Math.random() * (2048 - 400);
self.y = 300 + Math.random() * 200;
// Move down slowly
self.speedY = 2 + Math.random() * 2;
self.update = function () {
self.y += self.speedY;
// Remove if off screen
if (self.y > 2732 + 100) {
self.destroy();
if (invaders.indexOf(self) !== -1) invaders.splice(invaders.indexOf(self), 1);
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// --- Arrays to track objects ---
var bullets = [];
var invaders = [];
// --- Development Notice (bottom middle) ---
var devNoticeText = new Text2('still in development. I will update some fridays', {
size: 38,
fill: 0xFFFF00,
align: 'center',
stroke: 0x000000,
strokeThickness: 6
});
devNoticeText.anchor.set(0.5, 1); // center bottom
// Add to bottom center of the GUI
LK.gui.bottom.addChild(devNoticeText);
var titleText = new Text2('Pixelossed Rush', {
size: 180,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 2048 / 2;
titleText.y = 2732 / 2;
game.addChild(titleText);
;
// --- Tap to Start: 5s Countdown then How to Play screen ---
var countdownActive = false;
var countdownValue = 5;
var countdownText = null;
var howToPlayText = null;
var countdownTimer = null;
var attractModeStarted = false;
// Helper to clear countdown UI
function clearCountdownUI() {
if (countdownText && countdownText.parent) countdownText.parent.removeChild(countdownText);
countdownText = null;
}
// Helper to clear How to Play UI
function clearHowToPlayUI() {
if (howToPlayText && howToPlayText.parent) howToPlayText.parent.removeChild(howToPlayText);
howToPlayText = null;
}
// Tap handler for game
game.down = function (x, y, obj) {
if (countdownActive || attractModeStarted) return;
countdownActive = true;
countdownValue = 5;
// Remove attract mode UI if present
clearHowToPlayUI();
clearCountdownUI();
// Remove title, attract, Press Start, and FREE PLAY messages if present
if (titleText && titleText.parent) titleText.parent.removeChild(titleText);
titleText = null;
if (typeof frenzyText !== "undefined" && frenzyText && frenzyText.parent) frenzyText.parent.removeChild(frenzyText);
if (typeof pressStartText !== "undefined" && pressStartText && pressStartText.parent) pressStartText.parent.removeChild(pressStartText);
if (typeof freePlayText !== "undefined" && freePlayText && freePlayText.parent) freePlayText.parent.removeChild(freePlayText);
// Prevent any of these texts from being created after tap if they weren't already present
if (typeof frenzyText === "undefined" || !frenzyText) window.frenzyTextBlocked = true;
if (typeof pressStartText === "undefined" || !pressStartText) window.pressStartTextBlocked = true;
if (typeof freePlayText === "undefined" || !freePlayText) window.freePlayTextBlocked = true;
// REMOVE ATTRACT MODE (AI) ON TAP
// Block attract mode from being created if tap occurred before attract mode creation
window.attractModeBlocked = true;
// Remove attract mode timer and warning sign immediately
if (typeof attractCountdownInterval !== "undefined" && attractCountdownInterval) LK.clearInterval(attractCountdownInterval);
if (typeof attractCountdownText !== "undefined" && attractCountdownText && attractCountdownText.parent) attractCountdownText.parent.removeChild(attractCountdownText);
attractCountdownText = null;
if (typeof hideWarningUI === "function") hideWarningUI();
// Prevent warning sign from spawning after tap
window.attractWarningBlocked = true;
if (typeof attractMode !== "undefined") {
attractMode = false;
// Remove AI cannon if present
if (typeof cannon !== "undefined" && cannon && cannon.parent) {
cannon.parent.removeChild(cannon);
}
// Optionally clear invaders and bullets for a clean start
for (var i = invaders.length - 1; i >= 0; i--) {
if (invaders[i] && invaders[i].parent) invaders[i].parent.removeChild(invaders[i]);
invaders[i].destroy && invaders[i].destroy();
invaders.splice(i, 1);
}
for (var i = bullets.length - 1; i >= 0; i--) {
if (bullets[i] && bullets[i].parent) bullets[i].parent.removeChild(bullets[i]);
bullets[i].destroy && bullets[i].destroy();
bullets.splice(i, 1);
}
}
// Show countdown text in center
countdownText = new Text2(countdownValue + '', {
size: 220,
fill: 0xFFFF00
});
countdownText.anchor.set(0.5, 0.5);
countdownText.x = 2048 / 2;
countdownText.y = 2732 / 2;
game.addChild(countdownText);
// Start countdown timer
countdownTimer = LK.setInterval(function () {
countdownValue--;
if (countdownText) countdownText.setText(countdownValue + '');
if (countdownValue <= 0) {
LK.clearInterval(countdownTimer);
clearCountdownUI();
// Show How to Play screen
howToPlayText = new Text2('How to Play\n\nMove the cannon to shoot invaders!\nDon\'t let any escape!', {
size: 120,
fill: 0xFFFFFF,
align: 'center'
});
howToPlayText.anchor.set(0.5, 0.5);
howToPlayText.x = 2048 / 2;
howToPlayText.y = 2732 / 2;
game.addChild(howToPlayText);
// After 2s, remove How to Play and start attract mode
LK.setTimeout(function () {
clearHowToPlayUI();
attractModeStarted = true;
// Remove attract mode (AI cannon and invaders) after 5s countdown, then start real gameplay
LK.setTimeout(function () {
// Remove AI cannon if present
if (typeof cannon !== "undefined" && cannon && cannon.parent) {
cannon.parent.removeChild(cannon);
}
// Remove all invaders
for (var i = invaders.length - 1; i >= 0; i--) {
if (invaders[i] && invaders[i].parent) invaders[i].parent.removeChild(invaders[i]);
invaders[i].destroy && invaders[i].destroy();
invaders.splice(i, 1);
}
// Remove all bullets
for (var i = bullets.length - 1; i >= 0; i--) {
if (bullets[i] && bullets[i].parent) bullets[i].parent.removeChild(bullets[i]);
bullets[i].destroy && bullets[i].destroy();
bullets.splice(i, 1);
}
// End attract mode
if (typeof attractMode !== "undefined") {
attractMode = false;
}
// --- Start Real Gameplay ---
// Start/reset level timer for 60s level cycles
startLevelTimer();
// Re-add the timer for gameplay (level timer) and make it visible again
if (typeof attractCountdownText === "undefined" || !attractCountdownText) {
attractCountdownText = new Text2("60s", {
size: 80,
fill: 0xFFFF00,
align: 'center',
stroke: 0x000000,
strokeThickness: 6
});
attractCountdownText.anchor.set(0.5, 0);
// Place under FREE PLAY if it exists, else fallback to top center
if (typeof freePlayText !== "undefined" && freePlayText && freePlayText.parent) {
attractCountdownText.x = freePlayText.x;
attractCountdownText.y = freePlayText.y + freePlayText.height + 10;
game.addChild(attractCountdownText);
} else {
attractCountdownText.x = 2048 / 2;
attractCountdownText.y = 40 + 120 + 20 + 90 + 10;
game.addChild(attractCountdownText);
}
}
var gameplayCountdownValue = 60;
if (attractCountdownText) attractCountdownText.setText(gameplayCountdownValue + "s");
if (typeof gameplayCountdownInterval !== "undefined" && gameplayCountdownInterval) LK.clearInterval(gameplayCountdownInterval);
gameplayCountdownInterval = LK.setInterval(function () {
gameplayCountdownValue--;
if (attractCountdownText) attractCountdownText.setText(gameplayCountdownValue + "s");
if (gameplayCountdownValue <= 0) {
if (gameplayCountdownInterval) LK.clearInterval(gameplayCountdownInterval);
if (attractCountdownText && attractCountdownText.parent) attractCountdownText.parent.removeChild(attractCountdownText);
attractCountdownText = null;
}
}, 1000);
// Create player cannon
var playerCannon = LK.getAsset('cannon', {
anchorX: 0.5,
anchorY: 0.5
});
playerCannon.x = 2048 / 2;
playerCannon.y = 2732 - 200;
game.addChild(playerCannon);
// Player state
var playerShootCooldown = 0;
var playerLeftBound = 120;
var playerRightBound = 2048 - 120;
// Touch drag for cannon
var dragging = false;
game.down = function (x, y, obj) {
// Only allow drag if tap is near cannon
if (Math.abs(x - playerCannon.x) < 200 && Math.abs(y - playerCannon.y) < 200) {
dragging = true;
}
};
game.move = function (x, y, obj) {
if (dragging) {
playerCannon.x = Math.max(playerLeftBound, Math.min(playerRightBound, x));
}
};
game.up = function (x, y, obj) {
dragging = false;
};
// Spawn invaders at interval
var gameplayInvaderTimer = LK.setInterval(function () {
var inv = new Invader();
invaders.push(inv);
game.addChild(inv);
}, 700);
// --- Level Timer: Reset gameplay every 60 seconds ---
var levelTimer = null;
var levelNoticeText = null;
var levelNoticeTimeout = null;
function showLevelNotice() {
// Remove previous if present
if (levelNoticeText && levelNoticeText.parent) levelNoticeText.parent.removeChild(levelNoticeText);
levelNoticeText = new Text2("this is still in development! So it’s a beta. please do not get mad. I made this 40 minutes before bedtime. not my fault!", {
size: 38,
fill: 0xFFFF00,
align: 'center',
stroke: 0x000000,
strokeThickness: 6
});
levelNoticeText.anchor.set(0.5, 0.5);
// Center of screen
levelNoticeText.x = 2048 / 2;
levelNoticeText.y = 2732 / 2;
LK.gui.center.addChild(levelNoticeText);
// Remove after 2.5 seconds
if (levelNoticeTimeout) LK.clearTimeout(levelNoticeTimeout);
levelNoticeTimeout = LK.setTimeout(function () {
if (levelNoticeText && levelNoticeText.parent) levelNoticeText.parent.removeChild(levelNoticeText);
levelNoticeText = null;
}, 2500);
}
function startLevelTimer() {
if (levelTimer) LK.clearInterval(levelTimer);
// Show the notice after 60 seconds
levelTimer = LK.setInterval(function () {
showLevelNotice();
// Reset gameplay: clear all invaders and bullets, reset player cannon position, reset score if needed, etc.
// Remove all invaders
for (var i = invaders.length - 1; i >= 0; i--) {
if (invaders[i] && invaders[i].parent) invaders[i].parent.removeChild(invaders[i]);
invaders[i].destroy && invaders[i].destroy();
invaders.splice(i, 1);
}
// Remove all bullets
for (var i = bullets.length - 1; i >= 0; i--) {
if (bullets[i] && bullets[i].parent) bullets[i].parent.removeChild(bullets[i]);
bullets[i].destroy && bullets[i].destroy();
bullets.splice(i, 1);
}
// Reset player cannon position if it exists
if (typeof playerCannon !== "undefined" && playerCannon) {
playerCannon.x = 2048 / 2;
playerCannon.y = 2732 - 200;
}
// Optionally: reset playerShootCooldown
if (typeof playerShootCooldown !== "undefined") playerShootCooldown = 0;
// Optionally: you can increase difficulty here (e.g. decrease invader spawn interval, increase speed, etc.)
// Remove attract mode timer and warning UI if present before starting gameplay timer
if (typeof attractCountdownInterval !== "undefined" && attractCountdownInterval) LK.clearInterval(attractCountdownInterval);
if (typeof attractCountdownText !== "undefined" && attractCountdownText && attractCountdownText.parent) attractCountdownText.parent.removeChild(attractCountdownText);
attractCountdownText = null;
if (typeof hideWarningUI === "function") hideWarningUI();
// Allow warning sign to appear in gameplay last 10s
window.attractWarningBlocked = false;
// Restart the gameplay timer and make it visible again
gameplayCountdownValue = 60;
if (typeof attractCountdownText === "undefined" || !attractCountdownText) {
attractCountdownText = new Text2(gameplayCountdownValue + "s", {
size: 80,
fill: 0xFFFF00,
align: 'center',
stroke: 0x000000,
strokeThickness: 6
});
attractCountdownText.anchor.set(0.5, 0);
// Place under FREE PLAY if it exists, else fallback to top center
if (typeof freePlayText !== "undefined" && freePlayText && freePlayText.parent) {
attractCountdownText.x = freePlayText.x;
attractCountdownText.y = freePlayText.y + freePlayText.height + 10;
game.addChild(attractCountdownText);
} else {
attractCountdownText.x = 2048 / 2;
attractCountdownText.y = 40 + 120 + 20 + 90 + 10;
game.addChild(attractCountdownText);
}
}
if (attractCountdownText) {
attractCountdownText.setText(gameplayCountdownValue + "s");
}
if (typeof gameplayCountdownInterval !== "undefined" && gameplayCountdownInterval) LK.clearInterval(gameplayCountdownInterval);
gameplayCountdownInterval = LK.setInterval(function () {
gameplayCountdownValue--;
if (attractCountdownText) attractCountdownText.setText(gameplayCountdownValue + "s");
// Show/hide warning UI for last 10 seconds in gameplay
if (gameplayCountdownValue <= 10 && gameplayCountdownValue > 0) {
if (typeof showWarningUI === "function") showWarningUI();
} else {
if (typeof hideWarningUI === "function") hideWarningUI();
}
if (gameplayCountdownValue <= 0) {
if (gameplayCountdownInterval) LK.clearInterval(gameplayCountdownInterval);
if (attractCountdownText && attractCountdownText.parent) attractCountdownText.parent.removeChild(attractCountdownText);
attractCountdownText = null;
if (typeof hideWarningUI === "function") hideWarningUI();
}
}, 1000);
}, 60000); // 60 seconds
}
// Main update loop for gameplay
game.update = function () {
// Player shooting (auto-fire)
playerShootCooldown--;
if (playerShootCooldown <= 0) {
// Shoot bullet
var bullet = new Bullet();
bullet.x = playerCannon.x;
bullet.y = playerCannon.y - 60;
bullets.push(bullet);
game.addChild(bullet);
playerShootCooldown = 18;
}
// Update bullets
for (var b = bullets.length - 1; b >= 0; b--) {
var bullet = bullets[b];
if (bullet.update) bullet.update();
// Check collision with invaders
for (var j = invaders.length - 1; j >= 0; j--) {
var inv = invaders[j];
if (bullet.intersects(inv)) {
bullet.destroy();
inv.destroy();
bullets.splice(b, 1);
invaders.splice(j, 1);
break;
}
}
}
// Update invaders
for (var i = invaders.length - 1; i >= 0; i--) {
if (invaders[i].update) invaders[i].update();
// If any invader escapes, end game and show game over
if (invaders[i].y > 2732 + 100) {
LK.showGameOver();
break;
}
}
};
}, 5000);
}, 2000);
}
}, 1000);
};
// Add FRENZY title after 2 seconds with upward tween animation, positioned under the main title
// Expose to global scope for removal
var frenzyText = null;
var pressStartText = null;
var freePlayText = null;
LK.setTimeout(function () {
// Block FRENZY, Press Start, and FREE PLAY if tap occurred before their creation
if (window.frenzyTextBlocked) {
frenzyText = null;
} else {
frenzyText = new Text2('FRENZY', {
size: 200,
fill: 0xFFFFFF
});
frenzyText.anchor.set(0.5, 0.5);
frenzyText.x = 2048 / 2;
// Start below the main title, animate up to just under the main title
var spacing = 120;
var targetY;
if (titleText) {
targetY = titleText.y + titleText.height / 2 + frenzyText.height / 2 + spacing;
} else {
// Fallback: center of screen + some offset if titleText is gone
targetY = 2732 / 2 + 200 + frenzyText.height / 2 + spacing;
}
frenzyText.y = targetY + 200;
game.addChild(frenzyText);
// Animate the title going up to its target position
tween(frenzyText, {
y: targetY
}, {
duration: 900,
easing: tween.easeOut
});
}
if (window.pressStartTextBlocked) {
pressStartText = null;
} else {
// Add "Press Start!" message at the top of the screen
pressStartText = new Text2('Press Start!', {
size: 120,
fill: 0xFFFF00
});
pressStartText.anchor.set(0.5, 0);
// Centered horizontally, top of screen (leave 40px margin for safety)
pressStartText.x = 2048 / 2;
pressStartText.y = 40;
game.addChild(pressStartText);
}
if (window.freePlayTextBlocked) {
freePlayText = null;
} else {
// Add "FREE PLAY" message below "Press Start!" at the top
freePlayText = new Text2('FREE PLAY', {
size: 90,
fill: 0x00FF00
});
freePlayText.anchor.set(0.5, 0);
// Centered horizontally, just below "Press Start!"
freePlayText.x = 2048 / 2;
freePlayText.y = pressStartText && pressStartText.y ? pressStartText.y + pressStartText.height + 20 : 40 + 120 + 20;
game.addChild(freePlayText);
}
}, 2000);
;
// Attract mode: start after FRENZY appears (2s + animation ~1s)
LK.setTimeout(function () {
// Block attract mode if tap occurred before attract mode creation
if (window.attractModeBlocked) return;
// --- Attract Mode State ---
var attractMode = true;
// --- Cannon Setup ---
// Create a cannon asset (box shape, centered at bottom)
var cannon = LK.getAsset('cannon', {
anchorX: 0.5,
anchorY: 0.5
});
// Place cannon at bottom center
cannon.x = 2048 / 2;
cannon.y = 2732 - 200;
game.addChild(cannon);
// --- Arrays to track objects ---
// --- Spawn Invaders ---
var invaderTimer = LK.setInterval(function () {
if (!attractMode) return;
var inv = new Invader();
invaders.push(inv);
game.addChild(inv);
}, 700);
// --- Cannon AI movement ---
var cannonDir = 1; // 1 = right, -1 = left
var cannonSpeed = 16;
var leftBound = 120;
var rightBound = 2048 - 120;
// For shooting
var shootCooldown = 0;
// --- Attract Mode Timer: Only reset attract mode every 60s (no developer notice) ---
// Add a visible countdown timer under the FREE PLAY text
var attractCountdownValue = 60;
var attractCountdownText = new Text2(attractCountdownValue + "s", {
size: 80,
fill: 0xFFFF00,
align: 'center',
stroke: 0x000000,
strokeThickness: 6
});
attractCountdownText.anchor.set(0.5, 0);
// --- WARNING UI for final 10s ---
// Make the warning sign BIG, centered, and layered under the title and FRENZY
var warningGroup = new Container();
var warningText = null;
var warningLineTop = null;
var warningLineBot = null;
var warningLineTextTop = null;
var warningLineTextBot = null;
var warningScrollOffset = 0;
// Place under FREE PLAY if it exists, else fallback to top center
if (typeof freePlayText !== "undefined" && freePlayText && freePlayText.parent) {
attractCountdownText.x = freePlayText.x;
attractCountdownText.y = freePlayText.y + freePlayText.height + 10;
game.addChild(attractCountdownText);
} else {
// fallback: top center
attractCountdownText.x = 2048 / 2;
attractCountdownText.y = 40 + 120 + 20 + 90 + 10; // same as FREE PLAY default + 10px
game.addChild(attractCountdownText);
}
// Helper to show/hide warning UI
function showWarningUI() {
if (typeof window.attractWarningBlocked !== "undefined" && window.attractWarningBlocked) return;
if (warningText) return; // Already shown
// Make the warning sign BIG and centered, and layer it under the title and FRENZY
// Place warningGroup at z-index below titleText and frenzyText
// (Do not use setChildIndex on warningGroup, instead use addChildAt below)
// Red lines (as thick rectangles)
var lineWidth = 1800;
var lineHeight = 36;
var lineColor = 0xFF0000;
var centerX = 2048 / 2;
// Place warning in the vertical center of the screen
var warningAreaHeight = 900;
var baseY = 2732 / 2 - warningAreaHeight / 2;
var spacing = 320;
// Top line
warningLineTop = LK.getAsset('bullet', {
width: lineWidth,
height: lineHeight,
color: lineColor,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
warningLineTop.x = centerX;
warningLineTop.y = baseY + lineHeight / 2;
// Bottom line
warningLineBot = LK.getAsset('bullet', {
width: lineWidth,
height: lineHeight,
color: lineColor,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
warningLineBot.x = centerX;
warningLineBot.y = baseY + warningAreaHeight - lineHeight / 2;
// Center WARNING! text (very big)
warningText = new Text2("WARNING!", {
size: 340,
fill: 0xFF0000,
align: 'center',
stroke: 0x000000,
strokeThickness: 18
});
warningText.anchor.set(0.5, 0.5);
warningText.x = centerX;
warningText.y = baseY + warningAreaHeight / 2;
// Scrolling WARNING! text for lines (big)
var scrollText = "WARNING! WARNING! WARNING! WARNING! WARNING! ";
warningLineTextTop = new Text2(scrollText, {
size: 120,
fill: 0xFFFFFF,
align: 'left',
stroke: 0xFF0000,
strokeThickness: 10
});
warningLineTextTop.anchor.set(0, 0.5);
warningLineTextTop.x = centerX - lineWidth / 2 + 20;
warningLineTextTop.y = warningLineTop.y;
warningLineTextBot = new Text2(scrollText, {
size: 120,
fill: 0xFFFFFF,
align: 'left',
stroke: 0xFF0000,
strokeThickness: 10
});
warningLineTextBot.anchor.set(0, 0.5);
warningLineTextBot.x = centerX - lineWidth / 2 + 20;
warningLineTextBot.y = warningLineBot.y;
warningGroup.addChild(warningLineTop);
warningGroup.addChild(warningLineBot);
warningGroup.addChild(warningText);
warningGroup.addChild(warningLineTextTop);
warningGroup.addChild(warningLineTextBot);
// Insert warningGroup at the correct z-order: under title and FRENZY
// Remove and re-add to ensure correct order
if (game.children && game.children.indexOf(warningGroup) !== -1) {
game.removeChild(warningGroup);
}
// Find index of titleText and frenzyText, insert before the lowest one
var idxTitle = titleText && titleText.parent === game ? game.children.indexOf(titleText) : -1;
var idxFrenzy = frenzyText && frenzyText.parent === game ? game.children.indexOf(frenzyText) : -1;
var insertIdx = 0;
if (idxTitle >= 0 && idxFrenzy >= 0) {
insertIdx = Math.min(idxTitle, idxFrenzy);
} else if (idxTitle >= 0) {
insertIdx = idxTitle;
} else if (idxFrenzy >= 0) {
insertIdx = idxFrenzy;
}
game.addChildAt(warningGroup, insertIdx >= 0 ? insertIdx : 0);
}
function hideWarningUI() {
if (warningGroup && warningGroup.parent) warningGroup.parent.removeChild(warningGroup);
warningText = null;
warningLineTop = null;
warningLineBot = null;
warningLineTextTop = null;
warningLineTextBot = null;
warningGroup = new Container();
warningScrollOffset = 0;
}
function resetAttractCountdown() {
attractCountdownValue = 60;
if (attractCountdownText) attractCountdownText.setText(attractCountdownValue + "s");
hideWarningUI();
}
var attractCountdownInterval = LK.setInterval(function () {
attractCountdownValue--;
if (attractCountdownText) attractCountdownText.setText(attractCountdownValue + "s");
// Show/hide warning UI for last 10 seconds
if (attractCountdownValue <= 10 && attractCountdownValue > 0) {
showWarningUI();
} else {
hideWarningUI();
}
if (attractCountdownValue <= 0) {
// Hide the attract mode timer and warning UI, but do NOT reset gameplay or restart timer
if (attractCountdownText && attractCountdownText.parent) attractCountdownText.parent.removeChild(attractCountdownText);
attractCountdownText = null;
hideWarningUI();
// Optionally, you could set a flag to prevent further attract mode actions if needed
// No gameplay reset or timer restart here!
}
}, 1000); // every second
// Animate scrolling WARNING! text on lines
var lastWarningUpdateTick = 0;
game.update = function (origUpdate) {
return function () {
if (typeof origUpdate === "function") origUpdate.apply(this, arguments);
// Animate warning scroll if visible
if (warningText && warningLineTextTop && warningLineTextBot) {
warningScrollOffset += 8;
// Loop the text horizontally
var textWidth = warningLineTextTop.width;
var lineWidth = 1200;
var baseX = 2048 / 2 - lineWidth / 2 + 10;
warningLineTextTop.x = baseX - warningScrollOffset % textWidth;
warningLineTextBot.x = baseX + warningScrollOffset % textWidth;
}
};
}(game.update);
// When attract mode ends, remove the countdown text and interval
function clearAttractCountdown() {
if (attractCountdownInterval) LK.clearInterval(attractCountdownInterval);
if (attractCountdownText && attractCountdownText.parent) attractCountdownText.parent.removeChild(attractCountdownText);
attractCountdownText = null;
hideWarningUI();
}
// --- Main update loop for attract mode ---
game.update = function () {
if (!attractMode) return;
// --- AI Cannon Movement: Prioritize intercepting invaders about to escape ---
// Find invader closest to bottom (about to escape)
var escapeInvader = null;
var maxY = -99999;
for (var i = 0; i < invaders.length; i++) {
if (invaders[i].y > maxY) {
maxY = invaders[i].y;
escapeInvader = invaders[i];
}
}
// If any invader is close to escaping (e.g. y > 2732-350), target it
var targetInvader = null;
if (escapeInvader && escapeInvader.y > 2732 - 350) {
targetInvader = escapeInvader;
} else {
// Otherwise, target nearest in X as before
var nearest = null;
var minDist = 99999;
for (var i = 0; i < invaders.length; i++) {
var dx = Math.abs(invaders[i].x - cannon.x);
if (dx < minDist) {
minDist = dx;
nearest = invaders[i];
}
}
targetInvader = nearest;
}
// Move cannon toward target invader, but not instantly
if (targetInvader) {
var dx = targetInvader.x - cannon.x;
// Move at most cannonSpeed per frame, but slow down as it gets close
if (Math.abs(dx) > 10) {
cannon.x += Math.sign(dx) * Math.min(cannonSpeed, Math.abs(dx));
}
}
// Clamp cannon within bounds
if (cannon.x > rightBound) cannon.x = rightBound;
if (cannon.x < leftBound) cannon.x = leftBound;
// --- AI Cannon Shooting: Shoot at escaping invaders with higher priority ---
shootCooldown--;
var shouldShoot = false;
if (shootCooldown <= 0 && invaders.length > 0 && targetInvader) {
// If targeting an escaping invader, shoot if aligned in X
if (escapeInvader && escapeInvader === targetInvader && Math.abs(cannon.x - targetInvader.x) < 120) {
// 98% chance to shoot at escaping invader
if (Math.random() < 0.98) shouldShoot = true;
} else if (Math.abs(cannon.x - targetInvader.x) < 120) {
// 80% chance to shoot at other invaders
if (Math.random() < 0.8) shouldShoot = true;
}
if (shouldShoot) {
var bullet = new Bullet();
bullet.x = cannon.x;
bullet.y = cannon.y - 60;
bullets.push(bullet);
game.addChild(bullet);
}
// Randomize next cooldown (faster or slower)
shootCooldown = 18 + Math.floor(Math.random() * 18);
}
// Update bullets
for (var b = bullets.length - 1; b >= 0; b--) {
var bullet = bullets[b];
if (bullet.update) bullet.update();
// Check collision with invaders
for (var j = invaders.length - 1; j >= 0; j--) {
var inv = invaders[j];
if (bullet.intersects(inv)) {
// Destroy both
bullet.destroy();
inv.destroy();
bullets.splice(b, 1);
invaders.splice(j, 1);
break;
}
}
}
// Update invaders
for (var i = invaders.length - 1; i >= 0; i--) {
if (invaders[i].update) invaders[i].update();
// If any invader escapes, end attract mode and show game over
if (invaders[i].y > 2732 + 100) {
attractMode = false;
LK.showGameOver();
break;
}
}
};
}, 3000); // 2s for FRENZY + 1s for animation
;