/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var BossBullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bossBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8;
self.damage = 5;
self.update = function () {
self.y += self.speed;
};
return self;
});
var BossFish = Container.expand(function () {
var self = Container.call(this);
var fishGraphics = self.attachAsset('bossFish', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 500;
self.speed = 2;
self.shootCooldown = 30;
self.moveDirection = 1;
self.attackPattern = 0; // 0: triple shot, 1: spread shot, 2: rapid fire
self.attackCooldown = 0;
self.pauseCooldown = 0;
self.attacksInPattern = 0;
self.maxAttacksPerPattern = 5;
self.update = function () {
self.x += self.speed * self.moveDirection;
if (self.x <= 100 || self.x >= 2048 - 100) {
self.moveDirection *= -1;
}
// Handle pauses between attack patterns
if (self.pauseCooldown > 0) {
self.pauseCooldown--;
return;
}
if (self.shootCooldown > 0) {
self.shootCooldown--;
} else {
self.executeAttackPattern();
self.attacksInPattern++;
// Check if we need to switch patterns or pause
if (self.attacksInPattern >= self.maxAttacksPerPattern) {
self.attacksInPattern = 0;
self.attackPattern = (self.attackPattern + 1) % 3;
self.pauseCooldown = 90; // 1.5 second pause between patterns
} else {
// Set cooldown based on attack pattern
if (self.attackPattern === 2) {
// Rapid fire
self.shootCooldown = 10;
} else {
self.shootCooldown = 25;
}
}
}
};
self.executeAttackPattern = function () {
if (self.attackPattern === 0) {
// Pattern 1: Triple shot (original)
for (var i = 0; i < 3; i++) {
var bullet = new BossBullet();
bullet.x = self.x + (i - 1) * 50;
bullet.y = self.y + 70;
enemyBullets.push(bullet);
game.addChild(bullet);
}
} else if (self.attackPattern === 1) {
// Pattern 2: Spread shot (5 bullets in wide arc)
for (var i = 0; i < 5; i++) {
var bullet = new BossBullet();
bullet.x = self.x + (i - 2) * 80;
bullet.y = self.y + 70;
// Add slight angle variation
var angle = (i - 2) * 0.2;
bullet.speedX = Math.sin(angle) * 2;
bullet.speedY = bullet.speed + Math.abs(Math.cos(angle)) * 2;
bullet.update = function () {
this.x += this.speedX || 0;
this.y += this.speedY || this.speed;
};
enemyBullets.push(bullet);
game.addChild(bullet);
}
} else if (self.attackPattern === 2) {
// Pattern 3: Rapid fire (single bullet aimed at player)
var bullet = new BossBullet();
bullet.x = self.x;
bullet.y = self.y + 70;
// Aim towards player
var deltaX = player.x - bullet.x;
var deltaY = player.y - bullet.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance > 0) {
bullet.speedX = deltaX / distance * 3;
bullet.speedY = deltaY / distance * 3;
bullet.update = function () {
this.x += this.speedX;
this.y += this.speedY;
};
}
enemyBullets.push(bullet);
game.addChild(bullet);
}
};
self.shootEnemy = function () {
for (var i = 0; i < 3; i++) {
var bullet = new BossBullet();
bullet.x = self.x + (i - 1) * 50;
bullet.y = self.y + 70;
enemyBullets.push(bullet);
game.addChild(bullet);
}
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xffffff, 200);
// Visual feedback when boss takes significant damage
if (self.health <= 75 && self.health > 50) {
tween(self, {
tint: 0xff9999
}, {
duration: 300,
onFinish: function onFinish() {
tween(self, {
tint: 0xffffff
}, {
duration: 300
});
}
});
} else if (self.health <= 50 && self.health > 25) {
tween(self, {
tint: 0xff6666
}, {
duration: 300,
onFinish: function onFinish() {
tween(self, {
tint: 0xffffff
}, {
duration: 300
});
}
});
} else if (self.health <= 25) {
tween(self, {
tint: 0xff3333
}, {
duration: 300,
onFinish: function onFinish() {
tween(self, {
tint: 0xffffff
}, {
duration: 300
});
}
});
}
if (self.health <= 0) {
LK.getSound('enemyHit').play();
return true;
}
return false;
};
return self;
});
var Bubble = Container.expand(function () {
var self = Container.call(this);
var bubbleGraphics = self.attachAsset('bubble', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = -16;
self.damage = 1;
self.update = function () {
self.y += self.speed;
};
return self;
});
var EnemyBullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('enemyBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 4;
self.damage = 1;
self.update = function () {
self.y += self.speed;
};
return self;
});
var EnemyBullet2 = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('enemyBullet2', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 5;
self.damage = 2;
self.update = function () {
self.y += self.speed;
};
return self;
});
var EnemyBullet3 = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('enemyBullet3', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 6;
self.damage = 3;
self.update = function () {
self.y += self.speed;
};
return self;
});
var EnemyBullet4 = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('enemyBullet4', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 7;
self.damage = 4;
self.update = function () {
self.y += self.speed;
};
return self;
});
var EnemyFish = Container.expand(function () {
var self = Container.call(this);
var fishGraphics = self.attachAsset('enemyFish', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 2;
self.speed = 2;
self.shootCooldown = Math.random() * 120 + 60;
self.update = function () {
// Keep enemy fish in upper zone (y between 100-400)
if (self.y < 100) {
self.y = 100;
} else if (self.y > 400) {
self.y = 400;
}
// Move horizontally instead of vertically
if (self.moveDirection === undefined) {
self.moveDirection = Math.random() > 0.5 ? 1 : -1;
self.horizontalSpeed = 1;
}
self.x += self.horizontalSpeed * self.moveDirection;
// Bounce off screen edges
if (self.x <= 50 || self.x >= 2048 - 50) {
self.moveDirection *= -1;
}
if (self.shootCooldown > 0) {
self.shootCooldown--;
} else {
self.shootEnemy();
self.shootCooldown = Math.random() * 120 + 60;
}
};
self.shootEnemy = function () {
if (self.y > 0 && self.y < 2732 - 200) {
var bullet = new EnemyBullet();
bullet.x = self.x;
bullet.y = self.y + 40;
enemyBullets.push(bullet);
game.addChild(bullet);
}
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xffffff, 200);
// Add tween effect for damage feedback
tween(self, {
tint: 0xff4444
}, {
duration: 150,
onFinish: function onFinish() {
tween(self, {
tint: 0xffffff
}, {
duration: 150
});
}
});
if (self.health <= 0) {
LK.getSound('enemyHit').play();
return true;
}
return false;
};
return self;
});
var EnemyFish2 = Container.expand(function () {
var self = Container.call(this);
var fishGraphics = self.attachAsset('enemyFish2', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 4;
self.speed = 2;
self.shootCooldown = Math.random() * 100 + 50;
self.update = function () {
// Keep enemy fish in upper zone (y between 100-400)
if (self.y < 100) {
self.y = 100;
} else if (self.y > 400) {
self.y = 400;
}
// Move horizontally instead of vertically
if (self.moveDirection === undefined) {
self.moveDirection = Math.random() > 0.5 ? 1 : -1;
self.horizontalSpeed = 1;
}
self.x += self.horizontalSpeed * self.moveDirection;
// Bounce off screen edges
if (self.x <= 50 || self.x >= 2048 - 50) {
self.moveDirection *= -1;
}
if (self.shootCooldown > 0) {
self.shootCooldown--;
} else {
self.shootEnemy();
self.shootCooldown = Math.random() * 100 + 50;
}
};
self.shootEnemy = function () {
if (self.y > 0 && self.y < 2732 - 200) {
var bullet = new EnemyBullet2();
bullet.x = self.x;
bullet.y = self.y + 40;
enemyBullets.push(bullet);
game.addChild(bullet);
}
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xffffff, 200);
// Add tween effect for damage feedback
tween(self, {
tint: 0xff4444
}, {
duration: 150,
onFinish: function onFinish() {
tween(self, {
tint: 0xffffff
}, {
duration: 150
});
}
});
if (self.health <= 0) {
LK.getSound('enemyHit').play();
return true;
}
return false;
};
return self;
});
var EnemyFish3 = Container.expand(function () {
var self = Container.call(this);
var fishGraphics = self.attachAsset('enemyFish3', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 10;
self.speed = 2;
self.shootCooldown = Math.random() * 80 + 40;
self.update = function () {
// Keep enemy fish in upper zone (y between 100-400)
if (self.y < 100) {
self.y = 100;
} else if (self.y > 400) {
self.y = 400;
}
// Move horizontally instead of vertically
if (self.moveDirection === undefined) {
self.moveDirection = Math.random() > 0.5 ? 1 : -1;
self.horizontalSpeed = 1;
}
self.x += self.horizontalSpeed * self.moveDirection;
// Bounce off screen edges
if (self.x <= 50 || self.x >= 2048 - 50) {
self.moveDirection *= -1;
}
if (self.shootCooldown > 0) {
self.shootCooldown--;
} else {
self.shootEnemy();
self.shootCooldown = Math.random() * 80 + 40;
}
};
self.shootEnemy = function () {
if (self.y > 0 && self.y < 2732 - 200) {
var bullet = new EnemyBullet3();
bullet.x = self.x;
bullet.y = self.y + 40;
enemyBullets.push(bullet);
game.addChild(bullet);
}
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xffffff, 200);
// Add tween effect for damage feedback
tween(self, {
tint: 0xff4444
}, {
duration: 150,
onFinish: function onFinish() {
tween(self, {
tint: 0xffffff
}, {
duration: 150
});
}
});
if (self.health <= 0) {
LK.getSound('enemyHit').play();
return true;
}
return false;
};
return self;
});
var EnemyFish4 = Container.expand(function () {
var self = Container.call(this);
var fishGraphics = self.attachAsset('enemyFish4', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 15;
self.speed = 2;
self.shootCooldown = Math.random() * 60 + 30;
self.update = function () {
// Keep enemy fish in upper zone (y between 100-400)
if (self.y < 100) {
self.y = 100;
} else if (self.y > 400) {
self.y = 400;
}
// Move horizontally instead of vertically
if (self.moveDirection === undefined) {
self.moveDirection = Math.random() > 0.5 ? 1 : -1;
self.horizontalSpeed = 1;
}
self.x += self.horizontalSpeed * self.moveDirection;
// Bounce off screen edges
if (self.x <= 50 || self.x >= 2048 - 50) {
self.moveDirection *= -1;
}
if (self.shootCooldown > 0) {
self.shootCooldown--;
} else {
self.shootEnemy();
self.shootCooldown = Math.random() * 60 + 30;
}
};
self.shootEnemy = function () {
if (self.y > 0 && self.y < 2732 - 200) {
var bullet = new EnemyBullet4();
bullet.x = self.x;
bullet.y = self.y + 40;
enemyBullets.push(bullet);
game.addChild(bullet);
}
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xffffff, 200);
// Add tween effect for damage feedback
tween(self, {
tint: 0xff4444
}, {
duration: 150,
onFinish: function onFinish() {
tween(self, {
tint: 0xffffff
}, {
duration: 150
});
}
});
if (self.health <= 0) {
LK.getSound('enemyHit').play();
return true;
}
return false;
};
return self;
});
var PlayerFish = Container.expand(function () {
var self = Container.call(this);
var fishGraphics = self.attachAsset('playerFish', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 3;
self.damage = 1;
self.maxHealth = 3;
self.shootCooldown = 0;
self.update = function () {
// Only shoot when game is in playing state
if (gameState !== 'playing') {
return;
}
if (self.shootCooldown > 0) {
self.shootCooldown--;
} else {
// Auto shoot every 25 frames to match cooldown
self.shoot();
}
};
self.takeDamage = function (damage) {
if (playerInvulnerable) return; // Cannot take damage while invulnerable
if (damage === undefined) damage = 1;
self.health -= damage;
LK.effects.flashObject(self, 0xff0000, 500);
LK.getSound('playerHit').play();
// Start invulnerability period
playerInvulnerable = true;
invulnerabilityTimer = invulnerabilityDuration;
// Create blinking effect during invulnerability
tween(self, {
alpha: 0.3
}, {
duration: 200,
onFinish: function onFinish() {
tween(self, {
alpha: 1
}, {
duration: 200
});
}
});
if (self.health <= 0) {
LK.showGameOver();
}
};
self.shoot = function () {
if (self.shootCooldown <= 0) {
var bubble = new Bubble();
bubble.x = self.x;
bubble.y = self.y - 50;
bubble.damage = self.damage;
bubbles.push(bubble);
game.addChild(bubble);
self.shootCooldown = 25;
LK.getSound('bubbleShoot').play();
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x001F3F
});
/****
* Game Code
****/
// Add background
var background = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.addChild(background);
var player;
var enemies = [];
var bubbles = [];
var enemyBullets = [];
var currentLevel = 1;
var maxLevel = 10;
var enemiesSpawned = 0;
var enemiesKilled = 0;
var spawnTimer = 0;
var gameState = 'menu';
var boss = null;
var playerInvulnerable = false;
var invulnerabilityTimer = 0;
var invulnerabilityDuration = 120; // 2 seconds at 60fps
// UI Elements
var healthText = new Text2('Health: 3', {
size: 60,
fill: 0xFFFFFF
});
healthText.anchor.set(0, 0);
healthText.x = 150;
healthText.y = 50;
healthText.visible = false;
LK.gui.topLeft.addChild(healthText);
var levelText = new Text2('Level: 1', {
size: 60,
fill: 0xFFFFFF
});
levelText.anchor.set(0.5, 0);
levelText.visible = false;
LK.gui.top.addChild(levelText);
var damageText = new Text2('Damage: 1', {
size: 60,
fill: 0xFFFFFF
});
damageText.anchor.set(1, 0);
damageText.x = -50;
damageText.y = 50;
damageText.visible = false;
LK.gui.topRight.addChild(damageText);
var bossHealthText = new Text2('', {
size: 60,
fill: 0xFF0000
});
bossHealthText.anchor.set(0.5, 0);
bossHealthText.x = 0;
bossHealthText.y = 150;
bossHealthText.visible = false;
LK.gui.top.addChild(bossHealthText);
// Start Menu UI
var startMenuContainer = new Container();
startMenuContainer.visible = true;
LK.gui.center.addChild(startMenuContainer);
var gameTitle = new Text2('Ocean Battle', {
size: 120,
fill: 0x00FFFF
});
gameTitle.anchor.set(0.5, 0.5);
gameTitle.y = -200;
startMenuContainer.addChild(gameTitle);
var startButton = LK.getAsset('playerFish', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 50,
scaleX: 2,
scaleY: 2
});
startMenuContainer.addChild(startButton);
var startButtonText = new Text2('Iniciar', {
size: 80,
fill: 0xFFFFFF
});
startButtonText.anchor.set(0.5, 0.5);
startButtonText.x = 0;
startButtonText.y = 150;
startMenuContainer.addChild(startButtonText);
// Upgrade UI (hidden initially)
var upgradeContainer = new Container();
upgradeContainer.visible = false;
LK.gui.center.addChild(upgradeContainer);
var upgradeTitle = new Text2('Choose Upgrade:', {
size: 80,
fill: 0xFFFFFF
});
upgradeTitle.anchor.set(0.5, 0.5);
upgradeTitle.y = -150;
upgradeContainer.addChild(upgradeTitle);
var healthUpgradeButton = LK.getAsset('playerFish', {
anchorX: 0.5,
anchorY: 0.5,
x: -200,
y: 0,
scaleX: 1.5,
scaleY: 1.5
});
upgradeContainer.addChild(healthUpgradeButton);
var healthUpgradeText = new Text2('+2 Health', {
size: 50,
fill: 0xFFFFFF
});
healthUpgradeText.anchor.set(0.5, 0);
healthUpgradeText.x = -200;
healthUpgradeText.y = 150;
upgradeContainer.addChild(healthUpgradeText);
var damageUpgradeButton = LK.getAsset('bubble', {
anchorX: 0.5,
anchorY: 0.5,
x: 200,
y: 0,
scaleX: 2,
scaleY: 2
});
upgradeContainer.addChild(damageUpgradeButton);
var damageUpgradeText = new Text2('+1 Damage', {
size: 50,
fill: 0xFFFFFF
});
damageUpgradeText.anchor.set(0.5, 0);
damageUpgradeText.x = 200;
damageUpgradeText.y = 150;
upgradeContainer.addChild(damageUpgradeText);
// Initialize player
player = game.addChild(new PlayerFish());
player.x = 1024;
player.y = 2500;
function updateUI() {
healthText.setText('Health: ' + player.health);
levelText.setText('Level: ' + currentLevel);
damageText.setText('Damage: ' + player.damage);
// Update boss health display
if (boss && boss.health > 0) {
bossHealthText.setText('Boss Health: ' + boss.health);
bossHealthText.visible = true;
// Position the text below the boss
var bossPosition = game.toLocal(boss.parent.toGlobal(boss.position));
var guiPosition = LK.gui.top.toLocal(game.toGlobal(bossPosition));
bossHealthText.x = guiPosition.x;
bossHealthText.y = guiPosition.y + 450; // Position below boss (boss height is ~400px + margin)
} else {
bossHealthText.visible = false;
}
}
function spawnEnemy() {
var enemy;
if (currentLevel === 10 && !boss) {
boss = new BossFish();
boss.x = 1024;
boss.y = 200;
enemies.push(boss);
game.addChild(boss);
} else if (currentLevel < 10) {
// Count already spawned fish types
var alreadySpawnedFish1 = 0;
var alreadySpawnedFish2 = 0;
var alreadySpawnedFish3 = 0;
var alreadySpawnedFish4 = 0;
for (var e = 0; e < enemies.length; e++) {
if (enemies[e] instanceof EnemyFish4) {
alreadySpawnedFish4++;
} else if (enemies[e] instanceof EnemyFish3) {
alreadySpawnedFish3++;
} else if (enemies[e] instanceof EnemyFish2) {
alreadySpawnedFish2++;
} else if (enemies[e] instanceof EnemyFish) {
alreadySpawnedFish1++;
}
}
// Calculate required numbers for this level
var requiredFish1 = 10; // Always 10 level 1 fish
var requiredFish2 = Math.max(0, currentLevel - 1); // Level 2 = 1, Level 3 = 2, etc.
var requiredFish3 = Math.max(0, currentLevel - 4); // Level 5 = 1, Level 6 = 2, etc.
var requiredFish4 = Math.max(0, currentLevel - 6); // Level 7 = 1, Level 8 = 2, etc.
// Decide what type to spawn based on requirements
if (currentLevel >= 7 && alreadySpawnedFish4 < requiredFish4) {
enemy = new EnemyFish4();
} else if (currentLevel >= 5 && alreadySpawnedFish3 < requiredFish3) {
enemy = new EnemyFish3();
} else if (currentLevel >= 2 && alreadySpawnedFish2 < requiredFish2) {
enemy = new EnemyFish2();
} else if (alreadySpawnedFish1 < requiredFish1) {
enemy = new EnemyFish();
} else {
// All required fish spawned, don't spawn more
return;
}
enemy.x = Math.random() * (2048 - 200) + 100;
enemy.y = Math.random() * 200 + 100; // Spawn in upper zone (100-300)
enemies.push(enemy);
game.addChild(enemy);
enemiesSpawned++;
}
}
function getEnemiesForLevel(level) {
if (level === 1) {
return 10; // 10 level 1 fish only
} else if (level >= 2 && level <= 4) {
return 10 + (level - 1); // 10 level 1 fish + increasing level 2 fish
} else if (level >= 5 && level <= 6) {
return 10 + (level - 1) + (level - 4); // 10 level 1 fish + level 2 fish + level 3 fish
} else if (level >= 7 && level <= 9) {
return 10 + (level - 1) + (level - 4) + (level - 6); // 10 level 1 fish + level 2 fish + level 3 fish + level 4 fish
} else {
return 0; // Level 10 is boss only
}
}
function startLevel() {
enemiesSpawned = 0;
enemiesKilled = 0;
spawnTimer = 0;
gameState = 'playing';
boss = null;
// Change background for level 6 and beyond
if (currentLevel >= 6) {
background.destroy();
background = LK.getAsset('background2', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.addChildAt(background, 0); // Add at bottom layer
// Change music for level 6
LK.playMusic('oceanAmbient2');
}
// Restore player health to maximum at start of each level
player.health = player.maxHealth;
if (currentLevel === 10) {
spawnEnemy(); // Spawn boss immediately
}
}
function showUpgradeScreen() {
gameState = 'upgrading';
upgradeContainer.visible = true;
// Update upgrade text based on current level
if (currentLevel >= 7) {
healthUpgradeText.setText('+4 Health');
damageUpgradeText.setText('+2 Damage');
} else {
healthUpgradeText.setText('+2 Health');
damageUpgradeText.setText('+1 Damage');
}
}
function hideUpgradeScreen() {
upgradeContainer.visible = false;
}
function selectHealthUpgrade() {
if (gameState === 'upgrading') {
var healthIncrease = currentLevel >= 7 ? 4 : 2;
player.health += healthIncrease;
player.maxHealth += healthIncrease;
hideUpgradeScreen();
nextLevel();
}
}
function selectDamageUpgrade() {
if (gameState === 'upgrading') {
var damageIncrease = currentLevel >= 7 ? 2 : 1;
player.damage += damageIncrease;
hideUpgradeScreen();
nextLevel();
}
}
function startGame() {
gameState = 'playing';
startMenuContainer.visible = false;
healthText.visible = true;
levelText.visible = true;
damageText.visible = true;
startLevel();
updateUI();
}
function nextLevel() {
currentLevel++;
if (currentLevel > maxLevel) {
LK.showYouWin();
return;
}
startLevel();
updateUI();
}
// Event handlers
startButton.down = function (x, y, obj) {
startGame();
};
healthUpgradeButton.down = function (x, y, obj) {
selectHealthUpgrade();
};
damageUpgradeButton.down = function (x, y, obj) {
selectDamageUpgrade();
};
game.down = function (x, y, obj) {
// Player now shoots automatically, no manual shooting needed
};
game.move = function (x, y, obj) {
if (gameState === 'playing') {
player.x = Math.max(60, Math.min(2048 - 60, x));
player.y = Math.max(2000, Math.min(2732 - 60, y));
}
};
// Play background music
LK.playMusic('oceanAmbient');
game.update = function () {
if (gameState === 'menu') {
return;
}
if (gameState !== 'playing') {
// Stop player from shooting when not in playing state
player.shootCooldown = 25;
return;
}
// Handle player invulnerability
if (playerInvulnerable) {
invulnerabilityTimer--;
// Create blinking effect every 10 frames
if (invulnerabilityTimer % 20 < 10) {
player.alpha = 0.3;
} else {
player.alpha = 1;
}
// End invulnerability
if (invulnerabilityTimer <= 0) {
playerInvulnerable = false;
player.alpha = 1; // Ensure player is fully visible
tween.stop(player, {
alpha: true
}); // Stop any ongoing alpha tweens
}
}
// Spawn enemies
if (currentLevel < 10) {
spawnTimer++;
var maxEnemies = getEnemiesForLevel(currentLevel);
if (spawnTimer >= 90 && enemiesSpawned < maxEnemies) {
spawnEnemy();
spawnTimer = 0;
}
}
// Update bubbles
for (var i = bubbles.length - 1; i >= 0; i--) {
var bubble = bubbles[i];
if (bubble.lastY === undefined) bubble.lastY = bubble.y;
// Remove off-screen bubbles
if (bubble.lastY >= -30 && bubble.y < -30) {
bubble.destroy();
bubbles.splice(i, 1);
continue;
}
// Check bubble-enemy collisions
var hit = false;
for (var j = enemies.length - 1; j >= 0; j--) {
var enemy = enemies[j];
if (bubble.intersects(enemy)) {
if (enemy.takeDamage(bubble.damage)) {
enemy.destroy();
enemies.splice(j, 1);
enemiesKilled++;
}
bubble.destroy();
bubbles.splice(i, 1);
hit = true;
break;
}
}
if (!hit) {
bubble.lastY = bubble.y;
}
}
// Update enemy bullets
for (var i = enemyBullets.length - 1; i >= 0; i--) {
var bullet = enemyBullets[i];
if (bullet.lastY === undefined) bullet.lastY = bullet.y;
// Remove off-screen bullets
if (bullet.lastY <= 2732 + 30 && bullet.y > 2732 + 30) {
bullet.destroy();
enemyBullets.splice(i, 1);
continue;
}
// Check bullet-player collision
if (bullet.intersects(player) && !playerInvulnerable) {
player.takeDamage(bullet.damage);
bullet.destroy();
enemyBullets.splice(i, 1);
continue;
}
bullet.lastY = bullet.y;
}
// Update enemies
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
if (enemy.lastY === undefined) enemy.lastY = enemy.y;
// Remove off-screen enemies (not boss)
if (enemy !== boss && enemy.lastY <= 2732 + 50 && enemy.y > 2732 + 50) {
enemy.destroy();
enemies.splice(i, 1);
continue;
}
// Check enemy-player collision
if (enemy.intersects(player) && !playerInvulnerable) {
player.takeDamage(1);
}
enemy.lastY = enemy.y;
}
// Check level completion
if (currentLevel < 10) {
var maxEnemies = getEnemiesForLevel(currentLevel);
if (enemiesKilled >= maxEnemies && enemies.length === 0) {
// Clear all enemy bullets when level is completed
for (var k = enemyBullets.length - 1; k >= 0; k--) {
enemyBullets[k].destroy();
enemyBullets.splice(k, 1);
}
showUpgradeScreen();
}
} else {
// Boss level
if (enemies.length === 0) {
// Clear all enemy bullets when boss is defeated
for (var k = enemyBullets.length - 1; k >= 0; k--) {
enemyBullets[k].destroy();
enemyBullets.splice(k, 1);
}
LK.showYouWin();
}
}
updateUI();
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var BossBullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bossBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8;
self.damage = 5;
self.update = function () {
self.y += self.speed;
};
return self;
});
var BossFish = Container.expand(function () {
var self = Container.call(this);
var fishGraphics = self.attachAsset('bossFish', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 500;
self.speed = 2;
self.shootCooldown = 30;
self.moveDirection = 1;
self.attackPattern = 0; // 0: triple shot, 1: spread shot, 2: rapid fire
self.attackCooldown = 0;
self.pauseCooldown = 0;
self.attacksInPattern = 0;
self.maxAttacksPerPattern = 5;
self.update = function () {
self.x += self.speed * self.moveDirection;
if (self.x <= 100 || self.x >= 2048 - 100) {
self.moveDirection *= -1;
}
// Handle pauses between attack patterns
if (self.pauseCooldown > 0) {
self.pauseCooldown--;
return;
}
if (self.shootCooldown > 0) {
self.shootCooldown--;
} else {
self.executeAttackPattern();
self.attacksInPattern++;
// Check if we need to switch patterns or pause
if (self.attacksInPattern >= self.maxAttacksPerPattern) {
self.attacksInPattern = 0;
self.attackPattern = (self.attackPattern + 1) % 3;
self.pauseCooldown = 90; // 1.5 second pause between patterns
} else {
// Set cooldown based on attack pattern
if (self.attackPattern === 2) {
// Rapid fire
self.shootCooldown = 10;
} else {
self.shootCooldown = 25;
}
}
}
};
self.executeAttackPattern = function () {
if (self.attackPattern === 0) {
// Pattern 1: Triple shot (original)
for (var i = 0; i < 3; i++) {
var bullet = new BossBullet();
bullet.x = self.x + (i - 1) * 50;
bullet.y = self.y + 70;
enemyBullets.push(bullet);
game.addChild(bullet);
}
} else if (self.attackPattern === 1) {
// Pattern 2: Spread shot (5 bullets in wide arc)
for (var i = 0; i < 5; i++) {
var bullet = new BossBullet();
bullet.x = self.x + (i - 2) * 80;
bullet.y = self.y + 70;
// Add slight angle variation
var angle = (i - 2) * 0.2;
bullet.speedX = Math.sin(angle) * 2;
bullet.speedY = bullet.speed + Math.abs(Math.cos(angle)) * 2;
bullet.update = function () {
this.x += this.speedX || 0;
this.y += this.speedY || this.speed;
};
enemyBullets.push(bullet);
game.addChild(bullet);
}
} else if (self.attackPattern === 2) {
// Pattern 3: Rapid fire (single bullet aimed at player)
var bullet = new BossBullet();
bullet.x = self.x;
bullet.y = self.y + 70;
// Aim towards player
var deltaX = player.x - bullet.x;
var deltaY = player.y - bullet.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance > 0) {
bullet.speedX = deltaX / distance * 3;
bullet.speedY = deltaY / distance * 3;
bullet.update = function () {
this.x += this.speedX;
this.y += this.speedY;
};
}
enemyBullets.push(bullet);
game.addChild(bullet);
}
};
self.shootEnemy = function () {
for (var i = 0; i < 3; i++) {
var bullet = new BossBullet();
bullet.x = self.x + (i - 1) * 50;
bullet.y = self.y + 70;
enemyBullets.push(bullet);
game.addChild(bullet);
}
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xffffff, 200);
// Visual feedback when boss takes significant damage
if (self.health <= 75 && self.health > 50) {
tween(self, {
tint: 0xff9999
}, {
duration: 300,
onFinish: function onFinish() {
tween(self, {
tint: 0xffffff
}, {
duration: 300
});
}
});
} else if (self.health <= 50 && self.health > 25) {
tween(self, {
tint: 0xff6666
}, {
duration: 300,
onFinish: function onFinish() {
tween(self, {
tint: 0xffffff
}, {
duration: 300
});
}
});
} else if (self.health <= 25) {
tween(self, {
tint: 0xff3333
}, {
duration: 300,
onFinish: function onFinish() {
tween(self, {
tint: 0xffffff
}, {
duration: 300
});
}
});
}
if (self.health <= 0) {
LK.getSound('enemyHit').play();
return true;
}
return false;
};
return self;
});
var Bubble = Container.expand(function () {
var self = Container.call(this);
var bubbleGraphics = self.attachAsset('bubble', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = -16;
self.damage = 1;
self.update = function () {
self.y += self.speed;
};
return self;
});
var EnemyBullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('enemyBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 4;
self.damage = 1;
self.update = function () {
self.y += self.speed;
};
return self;
});
var EnemyBullet2 = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('enemyBullet2', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 5;
self.damage = 2;
self.update = function () {
self.y += self.speed;
};
return self;
});
var EnemyBullet3 = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('enemyBullet3', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 6;
self.damage = 3;
self.update = function () {
self.y += self.speed;
};
return self;
});
var EnemyBullet4 = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('enemyBullet4', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 7;
self.damage = 4;
self.update = function () {
self.y += self.speed;
};
return self;
});
var EnemyFish = Container.expand(function () {
var self = Container.call(this);
var fishGraphics = self.attachAsset('enemyFish', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 2;
self.speed = 2;
self.shootCooldown = Math.random() * 120 + 60;
self.update = function () {
// Keep enemy fish in upper zone (y between 100-400)
if (self.y < 100) {
self.y = 100;
} else if (self.y > 400) {
self.y = 400;
}
// Move horizontally instead of vertically
if (self.moveDirection === undefined) {
self.moveDirection = Math.random() > 0.5 ? 1 : -1;
self.horizontalSpeed = 1;
}
self.x += self.horizontalSpeed * self.moveDirection;
// Bounce off screen edges
if (self.x <= 50 || self.x >= 2048 - 50) {
self.moveDirection *= -1;
}
if (self.shootCooldown > 0) {
self.shootCooldown--;
} else {
self.shootEnemy();
self.shootCooldown = Math.random() * 120 + 60;
}
};
self.shootEnemy = function () {
if (self.y > 0 && self.y < 2732 - 200) {
var bullet = new EnemyBullet();
bullet.x = self.x;
bullet.y = self.y + 40;
enemyBullets.push(bullet);
game.addChild(bullet);
}
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xffffff, 200);
// Add tween effect for damage feedback
tween(self, {
tint: 0xff4444
}, {
duration: 150,
onFinish: function onFinish() {
tween(self, {
tint: 0xffffff
}, {
duration: 150
});
}
});
if (self.health <= 0) {
LK.getSound('enemyHit').play();
return true;
}
return false;
};
return self;
});
var EnemyFish2 = Container.expand(function () {
var self = Container.call(this);
var fishGraphics = self.attachAsset('enemyFish2', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 4;
self.speed = 2;
self.shootCooldown = Math.random() * 100 + 50;
self.update = function () {
// Keep enemy fish in upper zone (y between 100-400)
if (self.y < 100) {
self.y = 100;
} else if (self.y > 400) {
self.y = 400;
}
// Move horizontally instead of vertically
if (self.moveDirection === undefined) {
self.moveDirection = Math.random() > 0.5 ? 1 : -1;
self.horizontalSpeed = 1;
}
self.x += self.horizontalSpeed * self.moveDirection;
// Bounce off screen edges
if (self.x <= 50 || self.x >= 2048 - 50) {
self.moveDirection *= -1;
}
if (self.shootCooldown > 0) {
self.shootCooldown--;
} else {
self.shootEnemy();
self.shootCooldown = Math.random() * 100 + 50;
}
};
self.shootEnemy = function () {
if (self.y > 0 && self.y < 2732 - 200) {
var bullet = new EnemyBullet2();
bullet.x = self.x;
bullet.y = self.y + 40;
enemyBullets.push(bullet);
game.addChild(bullet);
}
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xffffff, 200);
// Add tween effect for damage feedback
tween(self, {
tint: 0xff4444
}, {
duration: 150,
onFinish: function onFinish() {
tween(self, {
tint: 0xffffff
}, {
duration: 150
});
}
});
if (self.health <= 0) {
LK.getSound('enemyHit').play();
return true;
}
return false;
};
return self;
});
var EnemyFish3 = Container.expand(function () {
var self = Container.call(this);
var fishGraphics = self.attachAsset('enemyFish3', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 10;
self.speed = 2;
self.shootCooldown = Math.random() * 80 + 40;
self.update = function () {
// Keep enemy fish in upper zone (y between 100-400)
if (self.y < 100) {
self.y = 100;
} else if (self.y > 400) {
self.y = 400;
}
// Move horizontally instead of vertically
if (self.moveDirection === undefined) {
self.moveDirection = Math.random() > 0.5 ? 1 : -1;
self.horizontalSpeed = 1;
}
self.x += self.horizontalSpeed * self.moveDirection;
// Bounce off screen edges
if (self.x <= 50 || self.x >= 2048 - 50) {
self.moveDirection *= -1;
}
if (self.shootCooldown > 0) {
self.shootCooldown--;
} else {
self.shootEnemy();
self.shootCooldown = Math.random() * 80 + 40;
}
};
self.shootEnemy = function () {
if (self.y > 0 && self.y < 2732 - 200) {
var bullet = new EnemyBullet3();
bullet.x = self.x;
bullet.y = self.y + 40;
enemyBullets.push(bullet);
game.addChild(bullet);
}
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xffffff, 200);
// Add tween effect for damage feedback
tween(self, {
tint: 0xff4444
}, {
duration: 150,
onFinish: function onFinish() {
tween(self, {
tint: 0xffffff
}, {
duration: 150
});
}
});
if (self.health <= 0) {
LK.getSound('enemyHit').play();
return true;
}
return false;
};
return self;
});
var EnemyFish4 = Container.expand(function () {
var self = Container.call(this);
var fishGraphics = self.attachAsset('enemyFish4', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 15;
self.speed = 2;
self.shootCooldown = Math.random() * 60 + 30;
self.update = function () {
// Keep enemy fish in upper zone (y between 100-400)
if (self.y < 100) {
self.y = 100;
} else if (self.y > 400) {
self.y = 400;
}
// Move horizontally instead of vertically
if (self.moveDirection === undefined) {
self.moveDirection = Math.random() > 0.5 ? 1 : -1;
self.horizontalSpeed = 1;
}
self.x += self.horizontalSpeed * self.moveDirection;
// Bounce off screen edges
if (self.x <= 50 || self.x >= 2048 - 50) {
self.moveDirection *= -1;
}
if (self.shootCooldown > 0) {
self.shootCooldown--;
} else {
self.shootEnemy();
self.shootCooldown = Math.random() * 60 + 30;
}
};
self.shootEnemy = function () {
if (self.y > 0 && self.y < 2732 - 200) {
var bullet = new EnemyBullet4();
bullet.x = self.x;
bullet.y = self.y + 40;
enemyBullets.push(bullet);
game.addChild(bullet);
}
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xffffff, 200);
// Add tween effect for damage feedback
tween(self, {
tint: 0xff4444
}, {
duration: 150,
onFinish: function onFinish() {
tween(self, {
tint: 0xffffff
}, {
duration: 150
});
}
});
if (self.health <= 0) {
LK.getSound('enemyHit').play();
return true;
}
return false;
};
return self;
});
var PlayerFish = Container.expand(function () {
var self = Container.call(this);
var fishGraphics = self.attachAsset('playerFish', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 3;
self.damage = 1;
self.maxHealth = 3;
self.shootCooldown = 0;
self.update = function () {
// Only shoot when game is in playing state
if (gameState !== 'playing') {
return;
}
if (self.shootCooldown > 0) {
self.shootCooldown--;
} else {
// Auto shoot every 25 frames to match cooldown
self.shoot();
}
};
self.takeDamage = function (damage) {
if (playerInvulnerable) return; // Cannot take damage while invulnerable
if (damage === undefined) damage = 1;
self.health -= damage;
LK.effects.flashObject(self, 0xff0000, 500);
LK.getSound('playerHit').play();
// Start invulnerability period
playerInvulnerable = true;
invulnerabilityTimer = invulnerabilityDuration;
// Create blinking effect during invulnerability
tween(self, {
alpha: 0.3
}, {
duration: 200,
onFinish: function onFinish() {
tween(self, {
alpha: 1
}, {
duration: 200
});
}
});
if (self.health <= 0) {
LK.showGameOver();
}
};
self.shoot = function () {
if (self.shootCooldown <= 0) {
var bubble = new Bubble();
bubble.x = self.x;
bubble.y = self.y - 50;
bubble.damage = self.damage;
bubbles.push(bubble);
game.addChild(bubble);
self.shootCooldown = 25;
LK.getSound('bubbleShoot').play();
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x001F3F
});
/****
* Game Code
****/
// Add background
var background = LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.addChild(background);
var player;
var enemies = [];
var bubbles = [];
var enemyBullets = [];
var currentLevel = 1;
var maxLevel = 10;
var enemiesSpawned = 0;
var enemiesKilled = 0;
var spawnTimer = 0;
var gameState = 'menu';
var boss = null;
var playerInvulnerable = false;
var invulnerabilityTimer = 0;
var invulnerabilityDuration = 120; // 2 seconds at 60fps
// UI Elements
var healthText = new Text2('Health: 3', {
size: 60,
fill: 0xFFFFFF
});
healthText.anchor.set(0, 0);
healthText.x = 150;
healthText.y = 50;
healthText.visible = false;
LK.gui.topLeft.addChild(healthText);
var levelText = new Text2('Level: 1', {
size: 60,
fill: 0xFFFFFF
});
levelText.anchor.set(0.5, 0);
levelText.visible = false;
LK.gui.top.addChild(levelText);
var damageText = new Text2('Damage: 1', {
size: 60,
fill: 0xFFFFFF
});
damageText.anchor.set(1, 0);
damageText.x = -50;
damageText.y = 50;
damageText.visible = false;
LK.gui.topRight.addChild(damageText);
var bossHealthText = new Text2('', {
size: 60,
fill: 0xFF0000
});
bossHealthText.anchor.set(0.5, 0);
bossHealthText.x = 0;
bossHealthText.y = 150;
bossHealthText.visible = false;
LK.gui.top.addChild(bossHealthText);
// Start Menu UI
var startMenuContainer = new Container();
startMenuContainer.visible = true;
LK.gui.center.addChild(startMenuContainer);
var gameTitle = new Text2('Ocean Battle', {
size: 120,
fill: 0x00FFFF
});
gameTitle.anchor.set(0.5, 0.5);
gameTitle.y = -200;
startMenuContainer.addChild(gameTitle);
var startButton = LK.getAsset('playerFish', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 50,
scaleX: 2,
scaleY: 2
});
startMenuContainer.addChild(startButton);
var startButtonText = new Text2('Iniciar', {
size: 80,
fill: 0xFFFFFF
});
startButtonText.anchor.set(0.5, 0.5);
startButtonText.x = 0;
startButtonText.y = 150;
startMenuContainer.addChild(startButtonText);
// Upgrade UI (hidden initially)
var upgradeContainer = new Container();
upgradeContainer.visible = false;
LK.gui.center.addChild(upgradeContainer);
var upgradeTitle = new Text2('Choose Upgrade:', {
size: 80,
fill: 0xFFFFFF
});
upgradeTitle.anchor.set(0.5, 0.5);
upgradeTitle.y = -150;
upgradeContainer.addChild(upgradeTitle);
var healthUpgradeButton = LK.getAsset('playerFish', {
anchorX: 0.5,
anchorY: 0.5,
x: -200,
y: 0,
scaleX: 1.5,
scaleY: 1.5
});
upgradeContainer.addChild(healthUpgradeButton);
var healthUpgradeText = new Text2('+2 Health', {
size: 50,
fill: 0xFFFFFF
});
healthUpgradeText.anchor.set(0.5, 0);
healthUpgradeText.x = -200;
healthUpgradeText.y = 150;
upgradeContainer.addChild(healthUpgradeText);
var damageUpgradeButton = LK.getAsset('bubble', {
anchorX: 0.5,
anchorY: 0.5,
x: 200,
y: 0,
scaleX: 2,
scaleY: 2
});
upgradeContainer.addChild(damageUpgradeButton);
var damageUpgradeText = new Text2('+1 Damage', {
size: 50,
fill: 0xFFFFFF
});
damageUpgradeText.anchor.set(0.5, 0);
damageUpgradeText.x = 200;
damageUpgradeText.y = 150;
upgradeContainer.addChild(damageUpgradeText);
// Initialize player
player = game.addChild(new PlayerFish());
player.x = 1024;
player.y = 2500;
function updateUI() {
healthText.setText('Health: ' + player.health);
levelText.setText('Level: ' + currentLevel);
damageText.setText('Damage: ' + player.damage);
// Update boss health display
if (boss && boss.health > 0) {
bossHealthText.setText('Boss Health: ' + boss.health);
bossHealthText.visible = true;
// Position the text below the boss
var bossPosition = game.toLocal(boss.parent.toGlobal(boss.position));
var guiPosition = LK.gui.top.toLocal(game.toGlobal(bossPosition));
bossHealthText.x = guiPosition.x;
bossHealthText.y = guiPosition.y + 450; // Position below boss (boss height is ~400px + margin)
} else {
bossHealthText.visible = false;
}
}
function spawnEnemy() {
var enemy;
if (currentLevel === 10 && !boss) {
boss = new BossFish();
boss.x = 1024;
boss.y = 200;
enemies.push(boss);
game.addChild(boss);
} else if (currentLevel < 10) {
// Count already spawned fish types
var alreadySpawnedFish1 = 0;
var alreadySpawnedFish2 = 0;
var alreadySpawnedFish3 = 0;
var alreadySpawnedFish4 = 0;
for (var e = 0; e < enemies.length; e++) {
if (enemies[e] instanceof EnemyFish4) {
alreadySpawnedFish4++;
} else if (enemies[e] instanceof EnemyFish3) {
alreadySpawnedFish3++;
} else if (enemies[e] instanceof EnemyFish2) {
alreadySpawnedFish2++;
} else if (enemies[e] instanceof EnemyFish) {
alreadySpawnedFish1++;
}
}
// Calculate required numbers for this level
var requiredFish1 = 10; // Always 10 level 1 fish
var requiredFish2 = Math.max(0, currentLevel - 1); // Level 2 = 1, Level 3 = 2, etc.
var requiredFish3 = Math.max(0, currentLevel - 4); // Level 5 = 1, Level 6 = 2, etc.
var requiredFish4 = Math.max(0, currentLevel - 6); // Level 7 = 1, Level 8 = 2, etc.
// Decide what type to spawn based on requirements
if (currentLevel >= 7 && alreadySpawnedFish4 < requiredFish4) {
enemy = new EnemyFish4();
} else if (currentLevel >= 5 && alreadySpawnedFish3 < requiredFish3) {
enemy = new EnemyFish3();
} else if (currentLevel >= 2 && alreadySpawnedFish2 < requiredFish2) {
enemy = new EnemyFish2();
} else if (alreadySpawnedFish1 < requiredFish1) {
enemy = new EnemyFish();
} else {
// All required fish spawned, don't spawn more
return;
}
enemy.x = Math.random() * (2048 - 200) + 100;
enemy.y = Math.random() * 200 + 100; // Spawn in upper zone (100-300)
enemies.push(enemy);
game.addChild(enemy);
enemiesSpawned++;
}
}
function getEnemiesForLevel(level) {
if (level === 1) {
return 10; // 10 level 1 fish only
} else if (level >= 2 && level <= 4) {
return 10 + (level - 1); // 10 level 1 fish + increasing level 2 fish
} else if (level >= 5 && level <= 6) {
return 10 + (level - 1) + (level - 4); // 10 level 1 fish + level 2 fish + level 3 fish
} else if (level >= 7 && level <= 9) {
return 10 + (level - 1) + (level - 4) + (level - 6); // 10 level 1 fish + level 2 fish + level 3 fish + level 4 fish
} else {
return 0; // Level 10 is boss only
}
}
function startLevel() {
enemiesSpawned = 0;
enemiesKilled = 0;
spawnTimer = 0;
gameState = 'playing';
boss = null;
// Change background for level 6 and beyond
if (currentLevel >= 6) {
background.destroy();
background = LK.getAsset('background2', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.addChildAt(background, 0); // Add at bottom layer
// Change music for level 6
LK.playMusic('oceanAmbient2');
}
// Restore player health to maximum at start of each level
player.health = player.maxHealth;
if (currentLevel === 10) {
spawnEnemy(); // Spawn boss immediately
}
}
function showUpgradeScreen() {
gameState = 'upgrading';
upgradeContainer.visible = true;
// Update upgrade text based on current level
if (currentLevel >= 7) {
healthUpgradeText.setText('+4 Health');
damageUpgradeText.setText('+2 Damage');
} else {
healthUpgradeText.setText('+2 Health');
damageUpgradeText.setText('+1 Damage');
}
}
function hideUpgradeScreen() {
upgradeContainer.visible = false;
}
function selectHealthUpgrade() {
if (gameState === 'upgrading') {
var healthIncrease = currentLevel >= 7 ? 4 : 2;
player.health += healthIncrease;
player.maxHealth += healthIncrease;
hideUpgradeScreen();
nextLevel();
}
}
function selectDamageUpgrade() {
if (gameState === 'upgrading') {
var damageIncrease = currentLevel >= 7 ? 2 : 1;
player.damage += damageIncrease;
hideUpgradeScreen();
nextLevel();
}
}
function startGame() {
gameState = 'playing';
startMenuContainer.visible = false;
healthText.visible = true;
levelText.visible = true;
damageText.visible = true;
startLevel();
updateUI();
}
function nextLevel() {
currentLevel++;
if (currentLevel > maxLevel) {
LK.showYouWin();
return;
}
startLevel();
updateUI();
}
// Event handlers
startButton.down = function (x, y, obj) {
startGame();
};
healthUpgradeButton.down = function (x, y, obj) {
selectHealthUpgrade();
};
damageUpgradeButton.down = function (x, y, obj) {
selectDamageUpgrade();
};
game.down = function (x, y, obj) {
// Player now shoots automatically, no manual shooting needed
};
game.move = function (x, y, obj) {
if (gameState === 'playing') {
player.x = Math.max(60, Math.min(2048 - 60, x));
player.y = Math.max(2000, Math.min(2732 - 60, y));
}
};
// Play background music
LK.playMusic('oceanAmbient');
game.update = function () {
if (gameState === 'menu') {
return;
}
if (gameState !== 'playing') {
// Stop player from shooting when not in playing state
player.shootCooldown = 25;
return;
}
// Handle player invulnerability
if (playerInvulnerable) {
invulnerabilityTimer--;
// Create blinking effect every 10 frames
if (invulnerabilityTimer % 20 < 10) {
player.alpha = 0.3;
} else {
player.alpha = 1;
}
// End invulnerability
if (invulnerabilityTimer <= 0) {
playerInvulnerable = false;
player.alpha = 1; // Ensure player is fully visible
tween.stop(player, {
alpha: true
}); // Stop any ongoing alpha tweens
}
}
// Spawn enemies
if (currentLevel < 10) {
spawnTimer++;
var maxEnemies = getEnemiesForLevel(currentLevel);
if (spawnTimer >= 90 && enemiesSpawned < maxEnemies) {
spawnEnemy();
spawnTimer = 0;
}
}
// Update bubbles
for (var i = bubbles.length - 1; i >= 0; i--) {
var bubble = bubbles[i];
if (bubble.lastY === undefined) bubble.lastY = bubble.y;
// Remove off-screen bubbles
if (bubble.lastY >= -30 && bubble.y < -30) {
bubble.destroy();
bubbles.splice(i, 1);
continue;
}
// Check bubble-enemy collisions
var hit = false;
for (var j = enemies.length - 1; j >= 0; j--) {
var enemy = enemies[j];
if (bubble.intersects(enemy)) {
if (enemy.takeDamage(bubble.damage)) {
enemy.destroy();
enemies.splice(j, 1);
enemiesKilled++;
}
bubble.destroy();
bubbles.splice(i, 1);
hit = true;
break;
}
}
if (!hit) {
bubble.lastY = bubble.y;
}
}
// Update enemy bullets
for (var i = enemyBullets.length - 1; i >= 0; i--) {
var bullet = enemyBullets[i];
if (bullet.lastY === undefined) bullet.lastY = bullet.y;
// Remove off-screen bullets
if (bullet.lastY <= 2732 + 30 && bullet.y > 2732 + 30) {
bullet.destroy();
enemyBullets.splice(i, 1);
continue;
}
// Check bullet-player collision
if (bullet.intersects(player) && !playerInvulnerable) {
player.takeDamage(bullet.damage);
bullet.destroy();
enemyBullets.splice(i, 1);
continue;
}
bullet.lastY = bullet.y;
}
// Update enemies
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
if (enemy.lastY === undefined) enemy.lastY = enemy.y;
// Remove off-screen enemies (not boss)
if (enemy !== boss && enemy.lastY <= 2732 + 50 && enemy.y > 2732 + 50) {
enemy.destroy();
enemies.splice(i, 1);
continue;
}
// Check enemy-player collision
if (enemy.intersects(player) && !playerInvulnerable) {
player.takeDamage(1);
}
enemy.lastY = enemy.y;
}
// Check level completion
if (currentLevel < 10) {
var maxEnemies = getEnemiesForLevel(currentLevel);
if (enemiesKilled >= maxEnemies && enemies.length === 0) {
// Clear all enemy bullets when level is completed
for (var k = enemyBullets.length - 1; k >= 0; k--) {
enemyBullets[k].destroy();
enemyBullets.splice(k, 1);
}
showUpgradeScreen();
}
} else {
// Boss level
if (enemies.length === 0) {
// Clear all enemy bullets when boss is defeated
for (var k = enemyBullets.length - 1; k >= 0; k--) {
enemyBullets[k].destroy();
enemyBullets.splice(k, 1);
}
LK.showYouWin();
}
}
updateUI();
};
Water Bubble. In-Game asset. High contrast. No shadows. 2D
a orange water bubble. In-Game asset. 2d. High contrast. No shadows
a orange fish seen from top to bottom. In-Game asset. 2d. High contrast. No shadows
a blue fish seen from above, looking up. In-Game asset. 2d. High contrast. No shadows
a purple evil fish seen from top to bottom. In-Game asset. 2d. High contrast. No shadows
a red fish seen from top to bottom. In-Game asset. 2d. High contrast. No shadows
a gold fish seen from top to bottom. In-Game asset. 2d. High contrast. No shadows
a gold dark fish seen from top to bottom. In-Game asset. 2d. High contrast. No shadows
image of the ocean bottom. In-Game asset. 2d. High contrast. No shadows
image of the bottom of the ocean, dark background. In-Game asset. 2d. High contrast. No shadows