User prompt
Pon la 3 música del álbum musica bh
User prompt
Sigue sin funcionar
User prompt
Cuando le disparas al corazón no te da una vida extra, arregla eso
User prompt
As que cada 5 segundos aparezca un corazon que si el jugador le dispara al corazón le da una vida extra y después de 4 segundos el corazón desaparece ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
As que cada 5 segundos aparezca un corazon que si el jugador le dispara al corazón le da una vida extra
User prompt
Y as que para que el jugador gane tenga que llegar a los 10.000 puntos
User prompt
Please fix the bug: 'TypeError: Cannot set properties of undefined (setting 'fill')' in or related to this line: 'playerHealthTxt.style.fill = 0xffa500; // Orange when low' Line Number: 362
User prompt
As que el jugador tenga tres vidas y que aguante 3 balas
User prompt
As que Allan un contador que diga cuánto falta para que llegue el jefe
User prompt
As que cada un minuto aparece un enemigo gigante que aguante 5 balas y as que el enemigo gigante se valla acercando al jugador y si toca al jugador el jugador pierde
User prompt
As que cuando se diga la dirección adónde se moverá el enemigo aparezca un un círculo en el lugar donde se moverá el enemigo y después de 2 segundos desaparece ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
As que cuando una bala rebote 3 veces desaparesca
User prompt
As que una bala antes de desaparecer deba matar a 2 enemigos
User prompt
As que el jugador al principio solo puede disparar una bala pero mientras más enemigos mate más balas dispare y as que mientras más enemigos mate más enemigos aparece dan
User prompt
As que cuando el jugador dispara la bala aparece adelante de el y que cuando aparece no toque al jugador Genera una bala frente al jugador
User prompt
As que cuando el jugador dispara la bala aparece adelante de el y que cuando aparezca no toque al jugador
User prompt
As que si la bala del jugador falla as que rebote contra la pared y si la bala le da al jugador el jugador pierde y as que cada 3 segundos digan dónde se moverá el enemigo
Code edit (1 edits merged)
Please save this source code
User prompt
Rhythm Shooter: Beat the Enemy
Initial prompt
As un juego donde el jugador tenga que dispararle a enemigos al ritmo y que le tenga que disparar cada 5 segundos de la música y Que los enemigos se muevan cada 5 segundos y As que las balas del jugador sean muy rápidas y as que el enemigo tenga un dilei de movimiento
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var BeatIndicator = Container.expand(function () {
var self = Container.call(this);
var indicatorGraphics = self.attachAsset('beatIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
indicatorGraphics.alpha = 0.3;
self.pulse = function () {
indicatorGraphics.alpha = 0.8;
tween(indicatorGraphics, {
alpha: 0.3,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
indicatorGraphics.scaleX = 1;
indicatorGraphics.scaleY = 1;
}
});
};
return self;
});
var Bullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 25;
self.directionX = 0;
self.directionY = -1;
self.enemiesKilled = 0;
self.maxKills = 2;
self.bounceCount = 0;
self.maxBounces = 3;
self.update = function () {
// Only move if player is moving
if (!isPlayerMoving) return;
// Store previous position for collision detection
if (self.lastX === undefined) self.lastX = self.x;
if (self.lastY === undefined) self.lastY = self.y;
self.x += self.directionX * self.speed;
self.y += self.directionY * self.speed;
// Check wall collisions and bounce
if (self.x <= 10 && self.directionX < 0) {
self.directionX = -self.directionX; // Bounce off left wall
self.x = 10;
self.bounceCount++;
}
if (self.x >= 2038 && self.directionX > 0) {
self.directionX = -self.directionX; // Bounce off right wall
self.x = 2038;
self.bounceCount++;
}
if (self.y <= 10 && self.directionY < 0) {
self.directionY = -self.directionY; // Bounce off top wall
self.y = 10;
self.bounceCount++;
}
if (self.y >= 2722 && self.directionY > 0) {
self.directionY = -self.directionY; // Bounce off bottom wall
self.y = 2722;
self.bounceCount++;
}
// Check wall object collisions
for (var w = 0; w < walls.length; w++) {
var wall = walls[w];
if (self.intersects(wall)) {
// Damage the wall
wall.health--;
LK.effects.flashObject(wall, 0xff0000, 200);
// Calculate bounce direction based on wall orientation
var wallWidth = wall.width || 150;
var wallHeight = wall.height || 20;
// Determine which side of the wall was hit
var dx = self.x - wall.x;
var dy = self.y - wall.y;
// If wall is more horizontal (wider than tall)
if (wallWidth > wallHeight) {
// Bounce vertically
if (Math.abs(dy) > Math.abs(dx)) {
self.directionY = -self.directionY;
self.y = wall.y + (dy > 0 ? wallHeight / 2 + 10 : -wallHeight / 2 - 10);
}
} else {
// Bounce horizontally
if (Math.abs(dx) > Math.abs(dy)) {
self.directionX = -self.directionX;
self.x = wall.x + (dx > 0 ? wallWidth / 2 + 10 : -wallWidth / 2 - 10);
}
}
self.bounceCount++;
break;
}
}
// Update last positions
self.lastX = self.x;
self.lastY = self.y;
};
return self;
});
var Enemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.targetX = self.x;
self.targetY = self.y;
self.moveSpeed = 0.1;
self.isMoving = false;
self.shootTimer = 0;
self.shootInterval = 120 + Math.random() * 180; // Random interval between 2-5 seconds
self.setTarget = function (x, y) {
self.targetX = x;
self.targetY = y;
self.isMoving = true;
tween(self, {
x: x,
y: y
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
self.isMoving = false;
}
});
};
self.update = function () {
// Only update if player is moving
if (!isPlayerMoving) return;
self.shootTimer++;
if (self.shootTimer >= self.shootInterval) {
// Shoot at random location
var randomX = Math.random() * 2048;
var randomY = Math.random() * 2732;
// Create enemy bullet
var enemyBullet = game.addChild(new EnemyBullet());
var dx = randomX - self.x;
var dy = randomY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
enemyBullet.directionX = dx / distance;
enemyBullet.directionY = dy / distance;
}
enemyBullet.x = self.x;
enemyBullet.y = self.y;
enemyBullets.push(enemyBullet);
// Reset timer with new random interval
self.shootTimer = 0;
self.shootInterval = 120 + Math.random() * 180;
}
};
return self;
});
var EnemyBullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
bulletGraphics.tint = 0xff5722; // Orange/red tint to differentiate from player bullets
self.speed = 15;
self.directionX = 0;
self.directionY = 1;
self.bounceCount = 0;
self.maxBounces = 3;
self.update = function () {
// Only move if player is moving
if (!isPlayerMoving) return;
// Store previous position for collision detection
if (self.lastX === undefined) self.lastX = self.x;
if (self.lastY === undefined) self.lastY = self.y;
self.x += self.directionX * self.speed;
self.y += self.directionY * self.speed;
// Check wall collisions and bounce
if (self.x <= 10 && self.directionX < 0) {
self.directionX = -self.directionX; // Bounce off left wall
self.x = 10;
self.bounceCount++;
}
if (self.x >= 2038 && self.directionX > 0) {
self.directionX = -self.directionX; // Bounce off right wall
self.x = 2038;
self.bounceCount++;
}
if (self.y <= 10 && self.directionY < 0) {
self.directionY = -self.directionY; // Bounce off top wall
self.y = 10;
self.bounceCount++;
}
if (self.y >= 2722 && self.directionY > 0) {
self.directionY = -self.directionY; // Bounce off bottom wall
self.y = 2722;
self.bounceCount++;
}
// Check wall object collisions
for (var w = 0; w < walls.length; w++) {
var wall = walls[w];
if (self.intersects(wall)) {
// Damage the wall
wall.health--;
LK.effects.flashObject(wall, 0xff0000, 200);
// Calculate bounce direction based on wall orientation
var wallWidth = wall.width || 150;
var wallHeight = wall.height || 20;
// Determine which side of the wall was hit
var dx = self.x - wall.x;
var dy = self.y - wall.y;
// If wall is more horizontal (wider than tall)
if (wallWidth > wallHeight) {
// Bounce vertically
if (Math.abs(dy) > Math.abs(dx)) {
self.directionY = -self.directionY;
self.y = wall.y + (dy > 0 ? wallHeight / 2 + 10 : -wallHeight / 2 - 10);
}
} else {
// Bounce horizontally
if (Math.abs(dx) > Math.abs(dy)) {
self.directionX = -self.directionX;
self.x = wall.x + (dx > 0 ? wallWidth / 2 + 10 : -wallWidth / 2 - 10);
}
}
self.bounceCount++;
break;
}
}
// Remove if bounced too many times
if (self.bounceCount >= self.maxBounces) {
self.shouldDestroy = true;
}
// Remove if off-screen (fallback)
if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
// Mark for removal
self.shouldDestroy = true;
}
// Update last positions
self.lastX = self.x;
self.lastY = self.y;
};
return self;
});
var GiantEnemy = Container.expand(function () {
var self = Container.call(this);
var giantGraphics = self.attachAsset('giantEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 5;
self.speed = 2;
self.update = function () {
// Only move if player is moving
if (!isPlayerMoving) return;
// Move toward player
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
return self;
});
var Lightning = Container.expand(function () {
var self = Container.call(this);
var lightningGraphics = self.attachAsset('lightning', {
anchorX: 0.5,
anchorY: 0.5
});
self.duration = 30; // Lightning lasts 0.5 seconds
self.timer = 0;
self.speedY = 15; // Speed of downward movement
self.update = function () {
// Only update if player is moving
if (!isPlayerMoving) return;
self.timer++;
self.y += self.speedY; // Move lightning downwards
// Flash effect
if (self.timer % 5 === 0) {
lightningGraphics.alpha = lightningGraphics.alpha === 1 ? 0.7 : 1;
}
// Mark for destruction if it moves off-screen (bottom)
if (self.y > 2732 + lightningGraphics.height / 2) {
self.shouldDestroy = true;
}
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.canShoot = true;
self.shootCooldown = 0;
return self;
});
var PredictionCircle = Container.expand(function () {
var self = Container.call(this);
var circleGraphics = self.attachAsset('predictionCircle', {
anchorX: 0.5,
anchorY: 0.5
});
circleGraphics.alpha = 0.7;
self.show = function () {
// Make circle visible and animate it
circleGraphics.alpha = 0.7;
circleGraphics.scaleX = 0.5;
circleGraphics.scaleY = 0.5;
// Animate appearance
tween(circleGraphics, {
scaleX: 1,
scaleY: 1,
alpha: 0.9
}, {
duration: 200,
easing: tween.easeOut
});
// Auto-hide after 2 seconds
tween(circleGraphics, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
self.destroy();
}
});
};
return self;
});
var Wall = Container.expand(function () {
var self = Container.call(this);
var wallGraphics = self.attachAsset('wall', {
anchorX: 0.5,
anchorY: 0.5
});
wallGraphics.alpha = 0.8;
self.duration = 1800; // Wall lasts 30 seconds at 60fps
self.timer = 0;
self.health = 15; // Wall can take 15 hits before breaking
self.maxHealth = 15;
self.update = function () {
// Only update if player is moving
if (!isPlayerMoving) return;
self.timer++;
// Flash warning when about to expire (last 3 seconds)
if (self.timer >= self.duration - 180 && self.timer % 20 === 0) {
wallGraphics.alpha = wallGraphics.alpha === 0.8 ? 0.3 : 0.8;
}
// Flash red when damaged (health-based visual feedback)
if (self.health < self.maxHealth) {
var healthRatio = self.health / self.maxHealth;
wallGraphics.alpha = 0.5 + healthRatio * 0.3; // Fade as health decreases
if (self.health <= 5) {
// Flash red when critically damaged
if (self.timer % 30 === 0) {
wallGraphics.tint = wallGraphics.tint === 0xffffff ? 0xff4444 : 0xffffff;
}
}
}
// Mark for destruction when health reaches zero
if (self.health <= 0) {
self.shouldDestroy = true;
}
// Mark for destruction when timer expires
if (self.timer >= self.duration) {
self.shouldDestroy = true;
}
};
return self;
});
var Warning = Container.expand(function () {
var self = Container.call(this);
var warningGraphics = self.attachAsset('warning', {
anchorX: 0.5,
anchorY: 0.5
});
self.timer = 120; // 2 seconds warning time
self.flashTimer = 0;
self.update = function () {
// Only update if player is moving
if (!isPlayerMoving) return;
self.timer--;
self.flashTimer++;
// Flash warning every 10 frames
if (self.flashTimer % 10 === 0) {
warningGraphics.alpha = warningGraphics.alpha === 1 ? 0.3 : 1;
}
// Mark for destruction when timer expires
if (self.timer <= 0) {
self.shouldSpawnLightning = true;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a2e
});
/****
* Game Code
****/
// Game variables
var player;
var enemies = [];
var bullets = [];
var enemyBullets = [];
var predictionCircles = [];
var beatIndicator;
var beatTimer = 0;
var beatInterval = 300; // 5 seconds at 60fps
var nextBeatTime = 0;
var canShootWindow = false;
var shootWindow = 60; // 1 second window after beat
var shootWindowTimer = 0;
var totalKills = 0;
var bulletsPerShot = 1;
var enemyPositions = [{
x: 400,
y: 300
}, {
x: 1648,
y: 300
}, {
x: 1024,
y: 200
}, {
x: 600,
y: 500
}, {
x: 1448,
y: 500
}];
var currentWave = 0;
var enemyPredictionTimer = 0;
var enemyPredictionInterval = 180; // 3 seconds at 60fps
var giantEnemies = [];
var giantEnemyTimer = 0;
var giantEnemyInterval = 3600; // 60 seconds at 60fps (1 minute)
var warnings = [];
var lightnings = [];
var warningSpawnTimer = 0;
var warningSpawnInterval = 180; // 3 seconds at 60fps
// UI elements
var scoreTxt = new Text2('Score: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var shootStatusTxt = new Text2('', {
size: 45,
fill: 0xFFFF00
});
shootStatusTxt.anchor.set(0.5, 0);
shootStatusTxt.y = 140;
LK.gui.top.addChild(shootStatusTxt);
var enemyPredictionTxt = new Text2('', {
size: 40,
fill: 0xff9800
});
enemyPredictionTxt.anchor.set(0.5, 0);
enemyPredictionTxt.y = 190;
LK.gui.top.addChild(enemyPredictionTxt);
var bossCountdownTxt = new Text2('Next Boss: 60.0s', {
size: 45,
fill: 0xff0000
});
bossCountdownTxt.anchor.set(0.5, 0);
bossCountdownTxt.y = 240;
LK.gui.top.addChild(bossCountdownTxt);
// Player health system
var playerHealth = 3;
var maxPlayerHealth = 3;
// Shooting cooldown system (2 seconds = 120 frames at 60fps)
var shootCooldown = 0;
var shootCooldownMax = 120;
var canShoot = true;
var playerHealthTxt = new Text2('Lives: 3/3', {
size: 45,
fill: 0x00ff00
});
playerHealthTxt.anchor.set(0.5, 0);
playerHealthTxt.y = 290;
LK.gui.top.addChild(playerHealthTxt);
var shootCooldownTxt = new Text2('Shoot: Ready', {
size: 45,
fill: 0x00ff00
});
shootCooldownTxt.anchor.set(0.5, 0);
shootCooldownTxt.y = 390;
LK.gui.top.addChild(shootCooldownTxt);
var movementRequiredTxt = new Text2('', {
size: 50,
fill: 0xffff00
});
movementRequiredTxt.anchor.set(0.5, 0);
movementRequiredTxt.y = 340;
LK.gui.top.addChild(movementRequiredTxt);
// Movement buttons
var leftButton = new Text2('◀', {
size: 80,
fill: 0xffffff
});
leftButton.anchor.set(0.5, 0.5);
leftButton.x = 200;
leftButton.y = 200;
LK.gui.bottomLeft.addChild(leftButton);
var rightButton = new Text2('▶', {
size: 80,
fill: 0xffffff
});
rightButton.anchor.set(0.5, 0.5);
rightButton.x = -200;
rightButton.y = 200;
LK.gui.bottomRight.addChild(rightButton);
var upButton = new Text2('▲', {
size: 80,
fill: 0xffffff
});
upButton.anchor.set(0.5, 0.5);
upButton.x = 0;
upButton.y = 300;
LK.gui.bottom.addChild(upButton);
var downButton = new Text2('▼', {
size: 80,
fill: 0xffffff
});
downButton.anchor.set(0.5, 0.5);
upButton.x = 0;
upButton.y = 100;
LK.gui.bottom.addChild(downButton);
// Controls instructions in corner
var controlsTxt = new Text2('Controls:\n◀▶ Move\n▲▼ Up/Down\nTouch: Shoot', {
size: 30,
fill: 0xffffff
});
controlsTxt.anchor.set(1, 1);
controlsTxt.x = -20;
controlsTxt.y = -20;
LK.gui.bottomRight.addChild(controlsTxt);
// Shooting instruction text in corner
var shootInstructionTxt = new Text2('Solo puedes disparar cuando\nescuches un chasquido de dedos', {
size: 25,
fill: 0xffff00
});
shootInstructionTxt.anchor.set(0, 1);
shootInstructionTxt.x = 20;
shootInstructionTxt.y = -20;
LK.gui.bottomLeft.addChild(shootInstructionTxt);
// Wall system
var walls = [];
var wallPlacementMode = false;
var wallCooldown = 300; // 5 seconds at 60fps
var wallCooldownTimer = 0;
var canPlaceWall = true;
var maxWalls = 3;
// Wall placement button
var wallBtn = new Text2('PLACE WALL', {
size: 70,
fill: 0x795548
});
wallBtn.anchor.set(1, 0);
wallBtn.x = -20;
wallBtn.y = 120;
LK.gui.topRight.addChild(wallBtn);
// Wall status text
var wallStatusTxt = new Text2('Walls: 0/3 - Ready', {
size: 40,
fill: 0x795548
});
wallStatusTxt.anchor.set(0.5, 0);
wallStatusTxt.y = 440;
LK.gui.top.addChild(wallStatusTxt);
// Initialize player
player = game.addChild(new Player());
player.x = 1024;
player.y = 2400;
player.speed = 8;
// Movement state
var moveLeft = false;
var moveRight = false;
var moveUp = false;
var moveDown = false;
// Player movement tracking for time system
var playerLastX = 0;
var playerLastY = 0;
var isPlayerMoving = false;
// Forced movement system - player must move every 10 seconds
var timeSinceLastMovement = 0;
var maxTimeWithoutMovement = 600; // 10 seconds at 60fps
var movementRequired = false;
// Initialize beat indicator
beatIndicator = game.addChild(new BeatIndicator());
beatIndicator.x = 1024;
beatIndicator.y = 1366;
// Initialize enemies
function spawnEnemies() {
// Clear existing enemies
for (var i = enemies.length - 1; i >= 0; i--) {
enemies[i].destroy();
enemies.splice(i, 1);
}
// Clear any remaining prediction circles
for (var c = predictionCircles.length - 1; c >= 0; c--) {
predictionCircles[c].destroy();
predictionCircles.splice(c, 1);
}
// Spawn new enemies - base amount plus bonus based on total kills
var baseEnemies = 3 + Math.floor(currentWave / 2);
var bonusEnemies = Math.floor(totalKills / 5); // 1 extra enemy per 5 kills
var numEnemies = Math.min(baseEnemies + bonusEnemies, 5);
for (var i = 0; i < numEnemies; i++) {
var enemy = game.addChild(new Enemy());
var pos = enemyPositions[i % enemyPositions.length];
enemy.x = pos.x;
enemy.y = pos.y;
enemies.push(enemy);
}
}
function moveEnemies() {
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var newPos = enemyPositions[(enemies.indexOf(enemy) + currentWave + 1) % enemyPositions.length];
enemy.setTarget(newPos.x, newPos.y);
}
}
function createBullet(targetX, targetY) {
// Update bullets per shot based on total kills
bulletsPerShot = 1 + Math.floor(totalKills / 3); // 1 extra bullet per 3 kills
bulletsPerShot = Math.min(bulletsPerShot, 5); // Cap at 5 bullets
// Create multiple bullets
for (var b = 0; b < bulletsPerShot; b++) {
var bullet = game.addChild(new Bullet());
// Calculate direction to target first
var dx = targetX - player.x;
var dy = targetY - player.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Add spread for multiple bullets
var spreadAngle = 0;
if (bulletsPerShot > 1) {
var maxSpread = Math.PI / 6; // 30 degrees total spread
spreadAngle = (b - (bulletsPerShot - 1) / 2) * (maxSpread / (bulletsPerShot - 1));
}
if (distance > 0) {
var baseDirectionX = dx / distance;
var baseDirectionY = dy / distance;
// Apply spread rotation
bullet.directionX = baseDirectionX * Math.cos(spreadAngle) - baseDirectionY * Math.sin(spreadAngle);
bullet.directionY = baseDirectionX * Math.sin(spreadAngle) + baseDirectionY * Math.cos(spreadAngle);
}
// Position bullet in front of player (offset by player size + bullet size to avoid collision)
var offsetDistance = 80; // Player size (70) + bullet height (40) + small buffer
bullet.x = player.x + bullet.directionX * offsetDistance;
bullet.y = player.y + bullet.directionY * offsetDistance;
bullets.push(bullet);
}
LK.getSound('shoot').play();
}
function damagePlayer() {
playerHealth--;
playerHealthTxt.setText('Lives: ' + playerHealth + '/' + maxPlayerHealth);
// Flash player red when damaged
LK.effects.flashObject(player, 0xff0000, 500);
// Update health text color based on remaining health
if (playerHealth <= 1) {
playerHealthTxt.fill = 0xff0000; // Red when critical
} else if (playerHealth <= 2) {
playerHealthTxt.fill = 0xffa500; // Orange when low
}
// Check if player is dead
if (playerHealth <= 0) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
return true; // Player is dead
}
return false; // Player is still alive
}
// Touch/click handling
game.down = function (x, y, obj) {
// Convert to GUI coordinates to check button presses
var guiPos = LK.gui.toLocal({
x: x,
y: y
});
// Check wall placement button (top right area)
if (guiPos.x > LK.gui.width - 250 && guiPos.y < 180 && guiPos.y > 100) {
// Wall placement button area
if (canPlaceWall && walls.length < maxWalls) {
wallPlacementMode = true;
}
} else if (wallPlacementMode) {
// Place wall at touch position
var wall = game.addChild(new Wall());
wall.x = x;
wall.y = y;
walls.push(wall);
wallPlacementMode = false;
canPlaceWall = false;
wallCooldownTimer = wallCooldown;
} else if (guiPos.x < 400 && guiPos.y > LK.gui.height - 400) {
// Left button area
moveLeft = true;
} else if (guiPos.x > LK.gui.width - 400 && guiPos.y > LK.gui.height - 400) {
// Right button area
moveRight = true;
} else if (guiPos.x > LK.gui.width / 2 - 100 && guiPos.x < LK.gui.width / 2 + 100 && guiPos.y > LK.gui.height - 500 && guiPos.y < LK.gui.height - 300) {
// Up button area
moveUp = true;
} else if (guiPos.x > LK.gui.width / 2 - 100 && guiPos.x < LK.gui.width / 2 + 100 && guiPos.y > LK.gui.height - 200) {
// Down button area
moveDown = true;
} else {
// Check if player can shoot (2-second cooldown)
if (canShoot) {
createBullet(x, y);
// Start cooldown
canShoot = false;
shootCooldown = shootCooldownMax;
}
}
};
// Touch release handling
game.up = function (x, y, obj) {
// Stop all movement when touch is released
moveLeft = false;
moveRight = false;
moveUp = false;
moveDown = false;
};
// Initialize game
spawnEnemies();
nextBeatTime = beatInterval;
// Start background music
LK.playMusic('musicabg3');
// Main game update loop
game.update = function () {
// Check if player is actually moving (position changed)
isPlayerMoving = player.x !== playerLastX || player.y !== playerLastY;
playerLastX = player.x;
playerLastY = player.y;
// Update forced movement timer
if (isPlayerMoving) {
// Player moved, reset timer
timeSinceLastMovement = 0;
movementRequired = false;
} else {
// Player hasn't moved, increment timer
timeSinceLastMovement++;
}
// Check if movement is required
if (timeSinceLastMovement >= maxTimeWithoutMovement && !movementRequired) {
movementRequired = true;
// Flash screen to indicate movement is required
LK.effects.flashScreen(0xffff00, 500);
}
// If movement has been required for too long, damage player
if (movementRequired && timeSinceLastMovement >= maxTimeWithoutMovement + 60) {
// 1 second grace period
if (damagePlayer()) {
return; // Player died, exit update loop
}
// Reset timer to give player another chance
timeSinceLastMovement = maxTimeWithoutMovement;
}
// Only advance timers if player is moving
if (isPlayerMoving) {
beatTimer++;
giantEnemyTimer++;
warningSpawnTimer++;
}
// Warning spawn system
if (warningSpawnTimer >= warningSpawnInterval) {
// Spawn warning at random position
var warning = game.addChild(new Warning());
warning.x = 100 + Math.random() * 1848; // Keep within screen bounds
warning.y = 100 + Math.random() * 2532;
warnings.push(warning);
warningSpawnTimer = 0;
// Decrease interval slightly over time to increase difficulty
warningSpawnInterval = Math.max(120, warningSpawnInterval - 2);
}
// Giant enemy spawn system
if (giantEnemyTimer >= giantEnemyInterval) {
// Spawn giant enemy at random edge of screen
var giantEnemy = game.addChild(new GiantEnemy());
var side = Math.floor(Math.random() * 4); // 0=top, 1=right, 2=bottom, 3=left
if (side === 0) {
// top
giantEnemy.x = Math.random() * 2048;
giantEnemy.y = 0;
} else if (side === 1) {
// right
giantEnemy.x = 2048;
giantEnemy.y = Math.random() * 2732;
} else if (side === 2) {
// bottom
giantEnemy.x = Math.random() * 2048;
giantEnemy.y = 2732;
} else {
// left
giantEnemy.x = 0;
giantEnemy.y = Math.random() * 2732;
}
giantEnemies.push(giantEnemy);
giantEnemyTimer = 0;
}
// Beat system
if (beatTimer >= nextBeatTime) {
// Beat occurred
beatIndicator.pulse();
LK.getSound('beat').play();
// Move enemies
moveEnemies();
currentWave++;
// Enable shooting window
canShootWindow = true;
shootWindowTimer = 0;
// Reset beat timer
beatTimer = 0;
nextBeatTime = beatInterval;
}
// Shooting window management
if (canShootWindow) {
shootWindowTimer++;
if (shootWindowTimer >= shootWindow) {
canShootWindow = false;
}
}
// Update player movement
if (moveLeft && player.x > 35) {
player.x -= player.speed;
}
if (moveRight && player.x < 2048 - 35) {
player.x += player.speed;
}
if (moveUp && player.y > 35) {
player.y -= player.speed;
}
if (moveDown && player.y < 2732 - 35) {
player.y += player.speed;
}
// Update wall system
if (wallCooldownTimer > 0) {
wallCooldownTimer--;
if (wallCooldownTimer <= 0) {
canPlaceWall = true;
}
}
// Update wall button appearance
if (wallPlacementMode) {
wallBtn.setText('TAP TO PLACE');
wallBtn.fill = 0xffff00;
} else if (walls.length >= maxWalls) {
wallBtn.setText('MAX WALLS');
wallBtn.fill = 0xff0000;
} else if (wallCooldownTimer > 0) {
wallBtn.setText('WALL: ' + (wallCooldownTimer / 60.0).toFixed(1) + 's');
wallBtn.fill = 0xff0000;
} else {
wallBtn.setText('PLACE WALL');
wallBtn.fill = 0x795548;
}
// Update wall status
if (canPlaceWall && walls.length < maxWalls) {
wallStatusTxt.setText('Walls: ' + walls.length + '/' + maxWalls + ' - Ready');
wallStatusTxt.fill = 0x00ff00;
} else if (wallCooldownTimer > 0) {
wallStatusTxt.setText('Walls: ' + walls.length + '/' + maxWalls + ' - ' + (wallCooldownTimer / 60.0).toFixed(1) + 's');
wallStatusTxt.fill = 0xff0000;
} else {
wallStatusTxt.setText('Walls: ' + walls.length + '/' + maxWalls + ' - Full');
wallStatusTxt.fill = 0xff0000;
}
// Update walls
for (var w = walls.length - 1; w >= 0; w--) {
var wall = walls[w];
if (wall.shouldDestroy) {
wall.destroy();
walls.splice(w, 1);
}
}
// Update shooting cooldown
if (shootCooldown > 0) {
shootCooldown--;
if (shootCooldown <= 0) {
canShoot = true;
}
}
// Update UI
var timeToNextBeat = (nextBeatTime - beatTimer) / 60.0;
if (canShootWindow) {
var windowTimeLeft = (shootWindow - shootWindowTimer) / 60.0;
shootStatusTxt.setText('SHOOT NOW! (' + windowTimeLeft.toFixed(1) + 's)');
} else {
shootStatusTxt.setText('');
}
// Update shooting cooldown status text
if (canShoot) {
shootCooldownTxt.setText('Shoot: Ready');
shootCooldownTxt.fill = 0x00ff00;
} else {
var shootTimeLeft = shootCooldown / 60.0;
shootCooldownTxt.setText('Shoot: ' + shootTimeLeft.toFixed(1) + 's');
shootCooldownTxt.fill = 0xff0000;
}
// Update movement requirement status
if (movementRequired) {
var graceTimeLeft = (maxTimeWithoutMovement + 60 - timeSinceLastMovement) / 60.0;
movementRequiredTxt.setText('¡MUÉVETE AHORA! ' + graceTimeLeft.toFixed(1) + 's');
movementRequiredTxt.fill = 0xff0000;
} else if (timeSinceLastMovement > maxTimeWithoutMovement * 0.7) {
// Warning at 70% of time
var timeLeft = (maxTimeWithoutMovement - timeSinceLastMovement) / 60.0;
movementRequiredTxt.setText('Muévete en: ' + timeLeft.toFixed(1) + 's');
movementRequiredTxt.fill = 0xffa500;
} else {
movementRequiredTxt.setText('');
}
// Update boss countdown
var timeToNextBoss = (giantEnemyInterval - giantEnemyTimer) / 60.0;
bossCountdownTxt.setText('Next Boss: ' + timeToNextBoss.toFixed(1) + 's');
// Enemy movement prediction system
enemyPredictionTimer++;
if (enemyPredictionTimer >= enemyPredictionInterval) {
// Clear existing prediction circles
for (var c = predictionCircles.length - 1; c >= 0; c--) {
predictionCircles[c].destroy();
predictionCircles.splice(c, 1);
}
// Announce where enemies will move
var nextPositions = [];
for (var k = 0; k < enemies.length; k++) {
var enemy = enemies[k];
var nextPosIndex = (enemies.indexOf(enemy) + currentWave + 1) % enemyPositions.length;
var nextPos = enemyPositions[nextPosIndex];
nextPositions.push('(' + Math.floor(nextPos.x) + ',' + Math.floor(nextPos.y) + ')');
// Create prediction circle at the target position
var predictionCircle = game.addChild(new PredictionCircle());
predictionCircle.x = nextPos.x;
predictionCircle.y = nextPos.y;
predictionCircles.push(predictionCircle);
predictionCircle.show();
}
if (nextPositions.length > 0) {
enemyPredictionTxt.setText('Enemies moving to: ' + nextPositions.join(', '));
}
enemyPredictionTimer = 0;
}
// Update bullets (only if player is moving)
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
// Check if bullet has bounced too many times
if (bullet.bounceCount >= bullet.maxBounces) {
bullet.destroy();
bullets.splice(i, 1);
continue;
}
// Check bullet-player collision (player loses if hit by own bullet)
if (bullet.intersects(player)) {
// Damage player and remove bullet
if (damagePlayer()) {
return; // Player died, exit update loop
}
bullet.destroy();
bullets.splice(i, 1);
continue;
}
// Remove off-screen check since bullets now bounce
// Check bullet-enemy collisions
for (var j = enemies.length - 1; j >= 0; j--) {
var enemy = enemies[j];
if (bullet.intersects(enemy)) {
// Hit!
totalKills++;
bullet.enemiesKilled++;
LK.setScore(LK.getScore() + 100);
scoreTxt.setText('Score: ' + LK.getScore() + ' | Kills: ' + totalKills + ' | Bullets: ' + bulletsPerShot);
LK.getSound('hit').play();
LK.effects.flashObject(enemy, 0xffffff, 200);
// Remove enemy
enemy.destroy();
enemies.splice(j, 1);
// Only remove bullet if it has killed maximum enemies
if (bullet.enemiesKilled >= bullet.maxKills) {
bullet.destroy();
bullets.splice(i, 1);
}
break;
}
}
// Check bullet-giant enemy collisions
for (var g = giantEnemies.length - 1; g >= 0; g--) {
var giantEnemy = giantEnemies[g];
if (bullet.intersects(giantEnemy)) {
// Hit giant enemy
giantEnemy.health--;
bullet.enemiesKilled++;
LK.setScore(LK.getScore() + 50);
scoreTxt.setText('Score: ' + LK.getScore() + ' | Kills: ' + totalKills + ' | Bullets: ' + bulletsPerShot);
LK.getSound('hit').play();
LK.effects.flashObject(giantEnemy, 0xffffff, 200);
// Check if giant enemy is defeated
if (giantEnemy.health <= 0) {
totalKills++;
giantEnemy.destroy();
giantEnemies.splice(g, 1);
}
// Only remove bullet if it has killed maximum enemies
if (bullet.enemiesKilled >= bullet.maxKills) {
bullet.destroy();
bullets.splice(i, 1);
}
break;
}
}
}
// Check giant enemy-player collisions
for (var g = 0; g < giantEnemies.length; g++) {
var giantEnemy = giantEnemies[g];
if (giantEnemy.intersects(player)) {
// Add collision tracking to prevent multiple hits per frame
if (giantEnemy.lastPlayerHit === undefined) giantEnemy.lastPlayerHit = false;
if (!giantEnemy.lastPlayerHit) {
giantEnemy.lastPlayerHit = true;
if (damagePlayer()) {
return; // Player died, exit update loop
}
}
} else {
giantEnemy.lastPlayerHit = false;
}
}
// Update enemy bullets (only if player is moving)
if (isPlayerMoving) {
for (var eb = enemyBullets.length - 1; eb >= 0; eb--) {
var enemyBullet = enemyBullets[eb];
// Remove off-screen enemy bullets
if (enemyBullet.shouldDestroy) {
enemyBullet.destroy();
enemyBullets.splice(eb, 1);
continue;
}
// Check enemy bullet-player collision
if (enemyBullet.intersects(player)) {
if (damagePlayer()) {
return; // Player died, exit update loop
}
enemyBullet.destroy();
enemyBullets.splice(eb, 1);
continue;
}
}
}
// Update warnings
for (var w = warnings.length - 1; w >= 0; w--) {
var warning = warnings[w];
if (warning.shouldSpawnLightning) {
// Create lightning at warning position
var lightning = game.addChild(new Lightning());
lightning.x = warning.x;
lightning.y = warning.y;
lightnings.push(lightning);
// Remove warning
warning.destroy();
warnings.splice(w, 1);
}
}
// Update lightnings
for (var l = lightnings.length - 1; l >= 0; l--) {
var lightning = lightnings[l];
// Check lightning-player collision
if (lightning.intersects(player)) {
// Lightning kills player instantly
if (damagePlayer()) {
return; // Player died, exit update loop
}
}
// Remove lightning when timer expires
if (lightning.shouldDestroy) {
lightning.destroy();
lightnings.splice(l, 1);
}
}
// Check win condition - player wins at 10,000 points
if (LK.getScore() >= 10000) {
LK.showYouWin();
}
// Spawn new enemies if all are destroyed
if (enemies.length === 0) {
spawnEnemies();
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var BeatIndicator = Container.expand(function () {
var self = Container.call(this);
var indicatorGraphics = self.attachAsset('beatIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
indicatorGraphics.alpha = 0.3;
self.pulse = function () {
indicatorGraphics.alpha = 0.8;
tween(indicatorGraphics, {
alpha: 0.3,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
indicatorGraphics.scaleX = 1;
indicatorGraphics.scaleY = 1;
}
});
};
return self;
});
var Bullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 25;
self.directionX = 0;
self.directionY = -1;
self.enemiesKilled = 0;
self.maxKills = 2;
self.bounceCount = 0;
self.maxBounces = 3;
self.update = function () {
// Only move if player is moving
if (!isPlayerMoving) return;
// Store previous position for collision detection
if (self.lastX === undefined) self.lastX = self.x;
if (self.lastY === undefined) self.lastY = self.y;
self.x += self.directionX * self.speed;
self.y += self.directionY * self.speed;
// Check wall collisions and bounce
if (self.x <= 10 && self.directionX < 0) {
self.directionX = -self.directionX; // Bounce off left wall
self.x = 10;
self.bounceCount++;
}
if (self.x >= 2038 && self.directionX > 0) {
self.directionX = -self.directionX; // Bounce off right wall
self.x = 2038;
self.bounceCount++;
}
if (self.y <= 10 && self.directionY < 0) {
self.directionY = -self.directionY; // Bounce off top wall
self.y = 10;
self.bounceCount++;
}
if (self.y >= 2722 && self.directionY > 0) {
self.directionY = -self.directionY; // Bounce off bottom wall
self.y = 2722;
self.bounceCount++;
}
// Check wall object collisions
for (var w = 0; w < walls.length; w++) {
var wall = walls[w];
if (self.intersects(wall)) {
// Damage the wall
wall.health--;
LK.effects.flashObject(wall, 0xff0000, 200);
// Calculate bounce direction based on wall orientation
var wallWidth = wall.width || 150;
var wallHeight = wall.height || 20;
// Determine which side of the wall was hit
var dx = self.x - wall.x;
var dy = self.y - wall.y;
// If wall is more horizontal (wider than tall)
if (wallWidth > wallHeight) {
// Bounce vertically
if (Math.abs(dy) > Math.abs(dx)) {
self.directionY = -self.directionY;
self.y = wall.y + (dy > 0 ? wallHeight / 2 + 10 : -wallHeight / 2 - 10);
}
} else {
// Bounce horizontally
if (Math.abs(dx) > Math.abs(dy)) {
self.directionX = -self.directionX;
self.x = wall.x + (dx > 0 ? wallWidth / 2 + 10 : -wallWidth / 2 - 10);
}
}
self.bounceCount++;
break;
}
}
// Update last positions
self.lastX = self.x;
self.lastY = self.y;
};
return self;
});
var Enemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.targetX = self.x;
self.targetY = self.y;
self.moveSpeed = 0.1;
self.isMoving = false;
self.shootTimer = 0;
self.shootInterval = 120 + Math.random() * 180; // Random interval between 2-5 seconds
self.setTarget = function (x, y) {
self.targetX = x;
self.targetY = y;
self.isMoving = true;
tween(self, {
x: x,
y: y
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
self.isMoving = false;
}
});
};
self.update = function () {
// Only update if player is moving
if (!isPlayerMoving) return;
self.shootTimer++;
if (self.shootTimer >= self.shootInterval) {
// Shoot at random location
var randomX = Math.random() * 2048;
var randomY = Math.random() * 2732;
// Create enemy bullet
var enemyBullet = game.addChild(new EnemyBullet());
var dx = randomX - self.x;
var dy = randomY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
enemyBullet.directionX = dx / distance;
enemyBullet.directionY = dy / distance;
}
enemyBullet.x = self.x;
enemyBullet.y = self.y;
enemyBullets.push(enemyBullet);
// Reset timer with new random interval
self.shootTimer = 0;
self.shootInterval = 120 + Math.random() * 180;
}
};
return self;
});
var EnemyBullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
bulletGraphics.tint = 0xff5722; // Orange/red tint to differentiate from player bullets
self.speed = 15;
self.directionX = 0;
self.directionY = 1;
self.bounceCount = 0;
self.maxBounces = 3;
self.update = function () {
// Only move if player is moving
if (!isPlayerMoving) return;
// Store previous position for collision detection
if (self.lastX === undefined) self.lastX = self.x;
if (self.lastY === undefined) self.lastY = self.y;
self.x += self.directionX * self.speed;
self.y += self.directionY * self.speed;
// Check wall collisions and bounce
if (self.x <= 10 && self.directionX < 0) {
self.directionX = -self.directionX; // Bounce off left wall
self.x = 10;
self.bounceCount++;
}
if (self.x >= 2038 && self.directionX > 0) {
self.directionX = -self.directionX; // Bounce off right wall
self.x = 2038;
self.bounceCount++;
}
if (self.y <= 10 && self.directionY < 0) {
self.directionY = -self.directionY; // Bounce off top wall
self.y = 10;
self.bounceCount++;
}
if (self.y >= 2722 && self.directionY > 0) {
self.directionY = -self.directionY; // Bounce off bottom wall
self.y = 2722;
self.bounceCount++;
}
// Check wall object collisions
for (var w = 0; w < walls.length; w++) {
var wall = walls[w];
if (self.intersects(wall)) {
// Damage the wall
wall.health--;
LK.effects.flashObject(wall, 0xff0000, 200);
// Calculate bounce direction based on wall orientation
var wallWidth = wall.width || 150;
var wallHeight = wall.height || 20;
// Determine which side of the wall was hit
var dx = self.x - wall.x;
var dy = self.y - wall.y;
// If wall is more horizontal (wider than tall)
if (wallWidth > wallHeight) {
// Bounce vertically
if (Math.abs(dy) > Math.abs(dx)) {
self.directionY = -self.directionY;
self.y = wall.y + (dy > 0 ? wallHeight / 2 + 10 : -wallHeight / 2 - 10);
}
} else {
// Bounce horizontally
if (Math.abs(dx) > Math.abs(dy)) {
self.directionX = -self.directionX;
self.x = wall.x + (dx > 0 ? wallWidth / 2 + 10 : -wallWidth / 2 - 10);
}
}
self.bounceCount++;
break;
}
}
// Remove if bounced too many times
if (self.bounceCount >= self.maxBounces) {
self.shouldDestroy = true;
}
// Remove if off-screen (fallback)
if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
// Mark for removal
self.shouldDestroy = true;
}
// Update last positions
self.lastX = self.x;
self.lastY = self.y;
};
return self;
});
var GiantEnemy = Container.expand(function () {
var self = Container.call(this);
var giantGraphics = self.attachAsset('giantEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 5;
self.speed = 2;
self.update = function () {
// Only move if player is moving
if (!isPlayerMoving) return;
// Move toward player
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
return self;
});
var Lightning = Container.expand(function () {
var self = Container.call(this);
var lightningGraphics = self.attachAsset('lightning', {
anchorX: 0.5,
anchorY: 0.5
});
self.duration = 30; // Lightning lasts 0.5 seconds
self.timer = 0;
self.speedY = 15; // Speed of downward movement
self.update = function () {
// Only update if player is moving
if (!isPlayerMoving) return;
self.timer++;
self.y += self.speedY; // Move lightning downwards
// Flash effect
if (self.timer % 5 === 0) {
lightningGraphics.alpha = lightningGraphics.alpha === 1 ? 0.7 : 1;
}
// Mark for destruction if it moves off-screen (bottom)
if (self.y > 2732 + lightningGraphics.height / 2) {
self.shouldDestroy = true;
}
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.canShoot = true;
self.shootCooldown = 0;
return self;
});
var PredictionCircle = Container.expand(function () {
var self = Container.call(this);
var circleGraphics = self.attachAsset('predictionCircle', {
anchorX: 0.5,
anchorY: 0.5
});
circleGraphics.alpha = 0.7;
self.show = function () {
// Make circle visible and animate it
circleGraphics.alpha = 0.7;
circleGraphics.scaleX = 0.5;
circleGraphics.scaleY = 0.5;
// Animate appearance
tween(circleGraphics, {
scaleX: 1,
scaleY: 1,
alpha: 0.9
}, {
duration: 200,
easing: tween.easeOut
});
// Auto-hide after 2 seconds
tween(circleGraphics, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
self.destroy();
}
});
};
return self;
});
var Wall = Container.expand(function () {
var self = Container.call(this);
var wallGraphics = self.attachAsset('wall', {
anchorX: 0.5,
anchorY: 0.5
});
wallGraphics.alpha = 0.8;
self.duration = 1800; // Wall lasts 30 seconds at 60fps
self.timer = 0;
self.health = 15; // Wall can take 15 hits before breaking
self.maxHealth = 15;
self.update = function () {
// Only update if player is moving
if (!isPlayerMoving) return;
self.timer++;
// Flash warning when about to expire (last 3 seconds)
if (self.timer >= self.duration - 180 && self.timer % 20 === 0) {
wallGraphics.alpha = wallGraphics.alpha === 0.8 ? 0.3 : 0.8;
}
// Flash red when damaged (health-based visual feedback)
if (self.health < self.maxHealth) {
var healthRatio = self.health / self.maxHealth;
wallGraphics.alpha = 0.5 + healthRatio * 0.3; // Fade as health decreases
if (self.health <= 5) {
// Flash red when critically damaged
if (self.timer % 30 === 0) {
wallGraphics.tint = wallGraphics.tint === 0xffffff ? 0xff4444 : 0xffffff;
}
}
}
// Mark for destruction when health reaches zero
if (self.health <= 0) {
self.shouldDestroy = true;
}
// Mark for destruction when timer expires
if (self.timer >= self.duration) {
self.shouldDestroy = true;
}
};
return self;
});
var Warning = Container.expand(function () {
var self = Container.call(this);
var warningGraphics = self.attachAsset('warning', {
anchorX: 0.5,
anchorY: 0.5
});
self.timer = 120; // 2 seconds warning time
self.flashTimer = 0;
self.update = function () {
// Only update if player is moving
if (!isPlayerMoving) return;
self.timer--;
self.flashTimer++;
// Flash warning every 10 frames
if (self.flashTimer % 10 === 0) {
warningGraphics.alpha = warningGraphics.alpha === 1 ? 0.3 : 1;
}
// Mark for destruction when timer expires
if (self.timer <= 0) {
self.shouldSpawnLightning = true;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a2e
});
/****
* Game Code
****/
// Game variables
var player;
var enemies = [];
var bullets = [];
var enemyBullets = [];
var predictionCircles = [];
var beatIndicator;
var beatTimer = 0;
var beatInterval = 300; // 5 seconds at 60fps
var nextBeatTime = 0;
var canShootWindow = false;
var shootWindow = 60; // 1 second window after beat
var shootWindowTimer = 0;
var totalKills = 0;
var bulletsPerShot = 1;
var enemyPositions = [{
x: 400,
y: 300
}, {
x: 1648,
y: 300
}, {
x: 1024,
y: 200
}, {
x: 600,
y: 500
}, {
x: 1448,
y: 500
}];
var currentWave = 0;
var enemyPredictionTimer = 0;
var enemyPredictionInterval = 180; // 3 seconds at 60fps
var giantEnemies = [];
var giantEnemyTimer = 0;
var giantEnemyInterval = 3600; // 60 seconds at 60fps (1 minute)
var warnings = [];
var lightnings = [];
var warningSpawnTimer = 0;
var warningSpawnInterval = 180; // 3 seconds at 60fps
// UI elements
var scoreTxt = new Text2('Score: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var shootStatusTxt = new Text2('', {
size: 45,
fill: 0xFFFF00
});
shootStatusTxt.anchor.set(0.5, 0);
shootStatusTxt.y = 140;
LK.gui.top.addChild(shootStatusTxt);
var enemyPredictionTxt = new Text2('', {
size: 40,
fill: 0xff9800
});
enemyPredictionTxt.anchor.set(0.5, 0);
enemyPredictionTxt.y = 190;
LK.gui.top.addChild(enemyPredictionTxt);
var bossCountdownTxt = new Text2('Next Boss: 60.0s', {
size: 45,
fill: 0xff0000
});
bossCountdownTxt.anchor.set(0.5, 0);
bossCountdownTxt.y = 240;
LK.gui.top.addChild(bossCountdownTxt);
// Player health system
var playerHealth = 3;
var maxPlayerHealth = 3;
// Shooting cooldown system (2 seconds = 120 frames at 60fps)
var shootCooldown = 0;
var shootCooldownMax = 120;
var canShoot = true;
var playerHealthTxt = new Text2('Lives: 3/3', {
size: 45,
fill: 0x00ff00
});
playerHealthTxt.anchor.set(0.5, 0);
playerHealthTxt.y = 290;
LK.gui.top.addChild(playerHealthTxt);
var shootCooldownTxt = new Text2('Shoot: Ready', {
size: 45,
fill: 0x00ff00
});
shootCooldownTxt.anchor.set(0.5, 0);
shootCooldownTxt.y = 390;
LK.gui.top.addChild(shootCooldownTxt);
var movementRequiredTxt = new Text2('', {
size: 50,
fill: 0xffff00
});
movementRequiredTxt.anchor.set(0.5, 0);
movementRequiredTxt.y = 340;
LK.gui.top.addChild(movementRequiredTxt);
// Movement buttons
var leftButton = new Text2('◀', {
size: 80,
fill: 0xffffff
});
leftButton.anchor.set(0.5, 0.5);
leftButton.x = 200;
leftButton.y = 200;
LK.gui.bottomLeft.addChild(leftButton);
var rightButton = new Text2('▶', {
size: 80,
fill: 0xffffff
});
rightButton.anchor.set(0.5, 0.5);
rightButton.x = -200;
rightButton.y = 200;
LK.gui.bottomRight.addChild(rightButton);
var upButton = new Text2('▲', {
size: 80,
fill: 0xffffff
});
upButton.anchor.set(0.5, 0.5);
upButton.x = 0;
upButton.y = 300;
LK.gui.bottom.addChild(upButton);
var downButton = new Text2('▼', {
size: 80,
fill: 0xffffff
});
downButton.anchor.set(0.5, 0.5);
upButton.x = 0;
upButton.y = 100;
LK.gui.bottom.addChild(downButton);
// Controls instructions in corner
var controlsTxt = new Text2('Controls:\n◀▶ Move\n▲▼ Up/Down\nTouch: Shoot', {
size: 30,
fill: 0xffffff
});
controlsTxt.anchor.set(1, 1);
controlsTxt.x = -20;
controlsTxt.y = -20;
LK.gui.bottomRight.addChild(controlsTxt);
// Shooting instruction text in corner
var shootInstructionTxt = new Text2('Solo puedes disparar cuando\nescuches un chasquido de dedos', {
size: 25,
fill: 0xffff00
});
shootInstructionTxt.anchor.set(0, 1);
shootInstructionTxt.x = 20;
shootInstructionTxt.y = -20;
LK.gui.bottomLeft.addChild(shootInstructionTxt);
// Wall system
var walls = [];
var wallPlacementMode = false;
var wallCooldown = 300; // 5 seconds at 60fps
var wallCooldownTimer = 0;
var canPlaceWall = true;
var maxWalls = 3;
// Wall placement button
var wallBtn = new Text2('PLACE WALL', {
size: 70,
fill: 0x795548
});
wallBtn.anchor.set(1, 0);
wallBtn.x = -20;
wallBtn.y = 120;
LK.gui.topRight.addChild(wallBtn);
// Wall status text
var wallStatusTxt = new Text2('Walls: 0/3 - Ready', {
size: 40,
fill: 0x795548
});
wallStatusTxt.anchor.set(0.5, 0);
wallStatusTxt.y = 440;
LK.gui.top.addChild(wallStatusTxt);
// Initialize player
player = game.addChild(new Player());
player.x = 1024;
player.y = 2400;
player.speed = 8;
// Movement state
var moveLeft = false;
var moveRight = false;
var moveUp = false;
var moveDown = false;
// Player movement tracking for time system
var playerLastX = 0;
var playerLastY = 0;
var isPlayerMoving = false;
// Forced movement system - player must move every 10 seconds
var timeSinceLastMovement = 0;
var maxTimeWithoutMovement = 600; // 10 seconds at 60fps
var movementRequired = false;
// Initialize beat indicator
beatIndicator = game.addChild(new BeatIndicator());
beatIndicator.x = 1024;
beatIndicator.y = 1366;
// Initialize enemies
function spawnEnemies() {
// Clear existing enemies
for (var i = enemies.length - 1; i >= 0; i--) {
enemies[i].destroy();
enemies.splice(i, 1);
}
// Clear any remaining prediction circles
for (var c = predictionCircles.length - 1; c >= 0; c--) {
predictionCircles[c].destroy();
predictionCircles.splice(c, 1);
}
// Spawn new enemies - base amount plus bonus based on total kills
var baseEnemies = 3 + Math.floor(currentWave / 2);
var bonusEnemies = Math.floor(totalKills / 5); // 1 extra enemy per 5 kills
var numEnemies = Math.min(baseEnemies + bonusEnemies, 5);
for (var i = 0; i < numEnemies; i++) {
var enemy = game.addChild(new Enemy());
var pos = enemyPositions[i % enemyPositions.length];
enemy.x = pos.x;
enemy.y = pos.y;
enemies.push(enemy);
}
}
function moveEnemies() {
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var newPos = enemyPositions[(enemies.indexOf(enemy) + currentWave + 1) % enemyPositions.length];
enemy.setTarget(newPos.x, newPos.y);
}
}
function createBullet(targetX, targetY) {
// Update bullets per shot based on total kills
bulletsPerShot = 1 + Math.floor(totalKills / 3); // 1 extra bullet per 3 kills
bulletsPerShot = Math.min(bulletsPerShot, 5); // Cap at 5 bullets
// Create multiple bullets
for (var b = 0; b < bulletsPerShot; b++) {
var bullet = game.addChild(new Bullet());
// Calculate direction to target first
var dx = targetX - player.x;
var dy = targetY - player.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Add spread for multiple bullets
var spreadAngle = 0;
if (bulletsPerShot > 1) {
var maxSpread = Math.PI / 6; // 30 degrees total spread
spreadAngle = (b - (bulletsPerShot - 1) / 2) * (maxSpread / (bulletsPerShot - 1));
}
if (distance > 0) {
var baseDirectionX = dx / distance;
var baseDirectionY = dy / distance;
// Apply spread rotation
bullet.directionX = baseDirectionX * Math.cos(spreadAngle) - baseDirectionY * Math.sin(spreadAngle);
bullet.directionY = baseDirectionX * Math.sin(spreadAngle) + baseDirectionY * Math.cos(spreadAngle);
}
// Position bullet in front of player (offset by player size + bullet size to avoid collision)
var offsetDistance = 80; // Player size (70) + bullet height (40) + small buffer
bullet.x = player.x + bullet.directionX * offsetDistance;
bullet.y = player.y + bullet.directionY * offsetDistance;
bullets.push(bullet);
}
LK.getSound('shoot').play();
}
function damagePlayer() {
playerHealth--;
playerHealthTxt.setText('Lives: ' + playerHealth + '/' + maxPlayerHealth);
// Flash player red when damaged
LK.effects.flashObject(player, 0xff0000, 500);
// Update health text color based on remaining health
if (playerHealth <= 1) {
playerHealthTxt.fill = 0xff0000; // Red when critical
} else if (playerHealth <= 2) {
playerHealthTxt.fill = 0xffa500; // Orange when low
}
// Check if player is dead
if (playerHealth <= 0) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
return true; // Player is dead
}
return false; // Player is still alive
}
// Touch/click handling
game.down = function (x, y, obj) {
// Convert to GUI coordinates to check button presses
var guiPos = LK.gui.toLocal({
x: x,
y: y
});
// Check wall placement button (top right area)
if (guiPos.x > LK.gui.width - 250 && guiPos.y < 180 && guiPos.y > 100) {
// Wall placement button area
if (canPlaceWall && walls.length < maxWalls) {
wallPlacementMode = true;
}
} else if (wallPlacementMode) {
// Place wall at touch position
var wall = game.addChild(new Wall());
wall.x = x;
wall.y = y;
walls.push(wall);
wallPlacementMode = false;
canPlaceWall = false;
wallCooldownTimer = wallCooldown;
} else if (guiPos.x < 400 && guiPos.y > LK.gui.height - 400) {
// Left button area
moveLeft = true;
} else if (guiPos.x > LK.gui.width - 400 && guiPos.y > LK.gui.height - 400) {
// Right button area
moveRight = true;
} else if (guiPos.x > LK.gui.width / 2 - 100 && guiPos.x < LK.gui.width / 2 + 100 && guiPos.y > LK.gui.height - 500 && guiPos.y < LK.gui.height - 300) {
// Up button area
moveUp = true;
} else if (guiPos.x > LK.gui.width / 2 - 100 && guiPos.x < LK.gui.width / 2 + 100 && guiPos.y > LK.gui.height - 200) {
// Down button area
moveDown = true;
} else {
// Check if player can shoot (2-second cooldown)
if (canShoot) {
createBullet(x, y);
// Start cooldown
canShoot = false;
shootCooldown = shootCooldownMax;
}
}
};
// Touch release handling
game.up = function (x, y, obj) {
// Stop all movement when touch is released
moveLeft = false;
moveRight = false;
moveUp = false;
moveDown = false;
};
// Initialize game
spawnEnemies();
nextBeatTime = beatInterval;
// Start background music
LK.playMusic('musicabg3');
// Main game update loop
game.update = function () {
// Check if player is actually moving (position changed)
isPlayerMoving = player.x !== playerLastX || player.y !== playerLastY;
playerLastX = player.x;
playerLastY = player.y;
// Update forced movement timer
if (isPlayerMoving) {
// Player moved, reset timer
timeSinceLastMovement = 0;
movementRequired = false;
} else {
// Player hasn't moved, increment timer
timeSinceLastMovement++;
}
// Check if movement is required
if (timeSinceLastMovement >= maxTimeWithoutMovement && !movementRequired) {
movementRequired = true;
// Flash screen to indicate movement is required
LK.effects.flashScreen(0xffff00, 500);
}
// If movement has been required for too long, damage player
if (movementRequired && timeSinceLastMovement >= maxTimeWithoutMovement + 60) {
// 1 second grace period
if (damagePlayer()) {
return; // Player died, exit update loop
}
// Reset timer to give player another chance
timeSinceLastMovement = maxTimeWithoutMovement;
}
// Only advance timers if player is moving
if (isPlayerMoving) {
beatTimer++;
giantEnemyTimer++;
warningSpawnTimer++;
}
// Warning spawn system
if (warningSpawnTimer >= warningSpawnInterval) {
// Spawn warning at random position
var warning = game.addChild(new Warning());
warning.x = 100 + Math.random() * 1848; // Keep within screen bounds
warning.y = 100 + Math.random() * 2532;
warnings.push(warning);
warningSpawnTimer = 0;
// Decrease interval slightly over time to increase difficulty
warningSpawnInterval = Math.max(120, warningSpawnInterval - 2);
}
// Giant enemy spawn system
if (giantEnemyTimer >= giantEnemyInterval) {
// Spawn giant enemy at random edge of screen
var giantEnemy = game.addChild(new GiantEnemy());
var side = Math.floor(Math.random() * 4); // 0=top, 1=right, 2=bottom, 3=left
if (side === 0) {
// top
giantEnemy.x = Math.random() * 2048;
giantEnemy.y = 0;
} else if (side === 1) {
// right
giantEnemy.x = 2048;
giantEnemy.y = Math.random() * 2732;
} else if (side === 2) {
// bottom
giantEnemy.x = Math.random() * 2048;
giantEnemy.y = 2732;
} else {
// left
giantEnemy.x = 0;
giantEnemy.y = Math.random() * 2732;
}
giantEnemies.push(giantEnemy);
giantEnemyTimer = 0;
}
// Beat system
if (beatTimer >= nextBeatTime) {
// Beat occurred
beatIndicator.pulse();
LK.getSound('beat').play();
// Move enemies
moveEnemies();
currentWave++;
// Enable shooting window
canShootWindow = true;
shootWindowTimer = 0;
// Reset beat timer
beatTimer = 0;
nextBeatTime = beatInterval;
}
// Shooting window management
if (canShootWindow) {
shootWindowTimer++;
if (shootWindowTimer >= shootWindow) {
canShootWindow = false;
}
}
// Update player movement
if (moveLeft && player.x > 35) {
player.x -= player.speed;
}
if (moveRight && player.x < 2048 - 35) {
player.x += player.speed;
}
if (moveUp && player.y > 35) {
player.y -= player.speed;
}
if (moveDown && player.y < 2732 - 35) {
player.y += player.speed;
}
// Update wall system
if (wallCooldownTimer > 0) {
wallCooldownTimer--;
if (wallCooldownTimer <= 0) {
canPlaceWall = true;
}
}
// Update wall button appearance
if (wallPlacementMode) {
wallBtn.setText('TAP TO PLACE');
wallBtn.fill = 0xffff00;
} else if (walls.length >= maxWalls) {
wallBtn.setText('MAX WALLS');
wallBtn.fill = 0xff0000;
} else if (wallCooldownTimer > 0) {
wallBtn.setText('WALL: ' + (wallCooldownTimer / 60.0).toFixed(1) + 's');
wallBtn.fill = 0xff0000;
} else {
wallBtn.setText('PLACE WALL');
wallBtn.fill = 0x795548;
}
// Update wall status
if (canPlaceWall && walls.length < maxWalls) {
wallStatusTxt.setText('Walls: ' + walls.length + '/' + maxWalls + ' - Ready');
wallStatusTxt.fill = 0x00ff00;
} else if (wallCooldownTimer > 0) {
wallStatusTxt.setText('Walls: ' + walls.length + '/' + maxWalls + ' - ' + (wallCooldownTimer / 60.0).toFixed(1) + 's');
wallStatusTxt.fill = 0xff0000;
} else {
wallStatusTxt.setText('Walls: ' + walls.length + '/' + maxWalls + ' - Full');
wallStatusTxt.fill = 0xff0000;
}
// Update walls
for (var w = walls.length - 1; w >= 0; w--) {
var wall = walls[w];
if (wall.shouldDestroy) {
wall.destroy();
walls.splice(w, 1);
}
}
// Update shooting cooldown
if (shootCooldown > 0) {
shootCooldown--;
if (shootCooldown <= 0) {
canShoot = true;
}
}
// Update UI
var timeToNextBeat = (nextBeatTime - beatTimer) / 60.0;
if (canShootWindow) {
var windowTimeLeft = (shootWindow - shootWindowTimer) / 60.0;
shootStatusTxt.setText('SHOOT NOW! (' + windowTimeLeft.toFixed(1) + 's)');
} else {
shootStatusTxt.setText('');
}
// Update shooting cooldown status text
if (canShoot) {
shootCooldownTxt.setText('Shoot: Ready');
shootCooldownTxt.fill = 0x00ff00;
} else {
var shootTimeLeft = shootCooldown / 60.0;
shootCooldownTxt.setText('Shoot: ' + shootTimeLeft.toFixed(1) + 's');
shootCooldownTxt.fill = 0xff0000;
}
// Update movement requirement status
if (movementRequired) {
var graceTimeLeft = (maxTimeWithoutMovement + 60 - timeSinceLastMovement) / 60.0;
movementRequiredTxt.setText('¡MUÉVETE AHORA! ' + graceTimeLeft.toFixed(1) + 's');
movementRequiredTxt.fill = 0xff0000;
} else if (timeSinceLastMovement > maxTimeWithoutMovement * 0.7) {
// Warning at 70% of time
var timeLeft = (maxTimeWithoutMovement - timeSinceLastMovement) / 60.0;
movementRequiredTxt.setText('Muévete en: ' + timeLeft.toFixed(1) + 's');
movementRequiredTxt.fill = 0xffa500;
} else {
movementRequiredTxt.setText('');
}
// Update boss countdown
var timeToNextBoss = (giantEnemyInterval - giantEnemyTimer) / 60.0;
bossCountdownTxt.setText('Next Boss: ' + timeToNextBoss.toFixed(1) + 's');
// Enemy movement prediction system
enemyPredictionTimer++;
if (enemyPredictionTimer >= enemyPredictionInterval) {
// Clear existing prediction circles
for (var c = predictionCircles.length - 1; c >= 0; c--) {
predictionCircles[c].destroy();
predictionCircles.splice(c, 1);
}
// Announce where enemies will move
var nextPositions = [];
for (var k = 0; k < enemies.length; k++) {
var enemy = enemies[k];
var nextPosIndex = (enemies.indexOf(enemy) + currentWave + 1) % enemyPositions.length;
var nextPos = enemyPositions[nextPosIndex];
nextPositions.push('(' + Math.floor(nextPos.x) + ',' + Math.floor(nextPos.y) + ')');
// Create prediction circle at the target position
var predictionCircle = game.addChild(new PredictionCircle());
predictionCircle.x = nextPos.x;
predictionCircle.y = nextPos.y;
predictionCircles.push(predictionCircle);
predictionCircle.show();
}
if (nextPositions.length > 0) {
enemyPredictionTxt.setText('Enemies moving to: ' + nextPositions.join(', '));
}
enemyPredictionTimer = 0;
}
// Update bullets (only if player is moving)
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
// Check if bullet has bounced too many times
if (bullet.bounceCount >= bullet.maxBounces) {
bullet.destroy();
bullets.splice(i, 1);
continue;
}
// Check bullet-player collision (player loses if hit by own bullet)
if (bullet.intersects(player)) {
// Damage player and remove bullet
if (damagePlayer()) {
return; // Player died, exit update loop
}
bullet.destroy();
bullets.splice(i, 1);
continue;
}
// Remove off-screen check since bullets now bounce
// Check bullet-enemy collisions
for (var j = enemies.length - 1; j >= 0; j--) {
var enemy = enemies[j];
if (bullet.intersects(enemy)) {
// Hit!
totalKills++;
bullet.enemiesKilled++;
LK.setScore(LK.getScore() + 100);
scoreTxt.setText('Score: ' + LK.getScore() + ' | Kills: ' + totalKills + ' | Bullets: ' + bulletsPerShot);
LK.getSound('hit').play();
LK.effects.flashObject(enemy, 0xffffff, 200);
// Remove enemy
enemy.destroy();
enemies.splice(j, 1);
// Only remove bullet if it has killed maximum enemies
if (bullet.enemiesKilled >= bullet.maxKills) {
bullet.destroy();
bullets.splice(i, 1);
}
break;
}
}
// Check bullet-giant enemy collisions
for (var g = giantEnemies.length - 1; g >= 0; g--) {
var giantEnemy = giantEnemies[g];
if (bullet.intersects(giantEnemy)) {
// Hit giant enemy
giantEnemy.health--;
bullet.enemiesKilled++;
LK.setScore(LK.getScore() + 50);
scoreTxt.setText('Score: ' + LK.getScore() + ' | Kills: ' + totalKills + ' | Bullets: ' + bulletsPerShot);
LK.getSound('hit').play();
LK.effects.flashObject(giantEnemy, 0xffffff, 200);
// Check if giant enemy is defeated
if (giantEnemy.health <= 0) {
totalKills++;
giantEnemy.destroy();
giantEnemies.splice(g, 1);
}
// Only remove bullet if it has killed maximum enemies
if (bullet.enemiesKilled >= bullet.maxKills) {
bullet.destroy();
bullets.splice(i, 1);
}
break;
}
}
}
// Check giant enemy-player collisions
for (var g = 0; g < giantEnemies.length; g++) {
var giantEnemy = giantEnemies[g];
if (giantEnemy.intersects(player)) {
// Add collision tracking to prevent multiple hits per frame
if (giantEnemy.lastPlayerHit === undefined) giantEnemy.lastPlayerHit = false;
if (!giantEnemy.lastPlayerHit) {
giantEnemy.lastPlayerHit = true;
if (damagePlayer()) {
return; // Player died, exit update loop
}
}
} else {
giantEnemy.lastPlayerHit = false;
}
}
// Update enemy bullets (only if player is moving)
if (isPlayerMoving) {
for (var eb = enemyBullets.length - 1; eb >= 0; eb--) {
var enemyBullet = enemyBullets[eb];
// Remove off-screen enemy bullets
if (enemyBullet.shouldDestroy) {
enemyBullet.destroy();
enemyBullets.splice(eb, 1);
continue;
}
// Check enemy bullet-player collision
if (enemyBullet.intersects(player)) {
if (damagePlayer()) {
return; // Player died, exit update loop
}
enemyBullet.destroy();
enemyBullets.splice(eb, 1);
continue;
}
}
}
// Update warnings
for (var w = warnings.length - 1; w >= 0; w--) {
var warning = warnings[w];
if (warning.shouldSpawnLightning) {
// Create lightning at warning position
var lightning = game.addChild(new Lightning());
lightning.x = warning.x;
lightning.y = warning.y;
lightnings.push(lightning);
// Remove warning
warning.destroy();
warnings.splice(w, 1);
}
}
// Update lightnings
for (var l = lightnings.length - 1; l >= 0; l--) {
var lightning = lightnings[l];
// Check lightning-player collision
if (lightning.intersects(player)) {
// Lightning kills player instantly
if (damagePlayer()) {
return; // Player died, exit update loop
}
}
// Remove lightning when timer expires
if (lightning.shouldDestroy) {
lightning.destroy();
lightnings.splice(l, 1);
}
}
// Check win condition - player wins at 10,000 points
if (LK.getScore() >= 10000) {
LK.showYouWin();
}
// Spawn new enemies if all are destroyed
if (enemies.length === 0) {
spawnEnemies();
}
};