/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Bullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8;
self.damage = 20;
self.targetX = 0;
self.targetY = 0;
self.velocityX = 0;
self.velocityY = 0;
// Velocity will be calculated when bullet is created
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
// Check if off screen
if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
self.markForDestroy = true;
}
// Check collision with enemies
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (getDistance(self, enemy) < 60) {
enemy.takeDamage(self.damage);
self.markForDestroy = true;
break;
}
}
};
return self;
});
var Enemy = Container.expand(function (type) {
var self = Container.call(this);
self.type = type || 'basic';
var assetName = self.type + 'Enemy';
var enemyGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
// Set stats based on type
if (self.type === 'basic') {
self.health = 30;
self.speed = 1.5;
self.damage = 10;
self.experienceValue = 2;
} else if (self.type === 'fast') {
self.health = 15;
self.speed = 3.5;
self.damage = 8;
self.experienceValue = 3;
} else if (self.type === 'tank') {
self.health = 80;
self.speed = 0.8;
self.damage = 20;
self.experienceValue = 5;
}
self.maxHealth = self.health;
self.takeDamage = function (amount) {
self.health -= amount;
LK.effects.flashObject(self, 0xFFFFFF, 100);
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
// Drop experience orb
var expOrb = new ExperienceOrb();
expOrb.x = self.x;
expOrb.y = self.y;
expOrb.value = self.experienceValue;
experienceOrbs.push(expOrb);
game.addChild(expOrb);
// Chance to drop gem
if (Math.random() < 0.3) {
var gem = new Gem();
gem.x = self.x;
gem.y = self.y;
gems.push(gem);
game.addChild(gem);
}
LK.getSound('enemyHit').play();
LK.setScore(LK.getScore() + self.experienceValue * 10);
};
self.update = function () {
// Move towards 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;
}
// Check collision with player
if (getDistance(self, player) < 100) {
player.takeDamage(self.damage);
}
};
return self;
});
var ExperienceOrb = Container.expand(function () {
var self = Container.call(this);
var orbGraphics = self.attachAsset('experienceOrb', {
anchorX: 0.5,
anchorY: 0.5
});
self.value = 1;
self.collectRadius = 100;
self.update = function () {
// Move towards player if close enough
var distance = getDistance(self, player);
if (distance < self.collectRadius) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var moveSpeed = 6;
if (distance > 0) {
self.x += dx / distance * moveSpeed;
self.y += dy / distance * moveSpeed;
}
// Collect if very close
if (distance < 50) {
player.gainExperience(self.value);
self.markForDestroy = true;
}
}
};
return self;
});
var Gem = Container.expand(function () {
var self = Container.call(this);
var gemGraphics = self.attachAsset('gem', {
anchorX: 0.5,
anchorY: 0.5
});
self.value = 1;
self.update = function () {
// Simple floating animation
self.y += Math.sin(LK.ticks * 0.1) * 0.5;
// Collect if player touches
if (getDistance(self, player) < 80) {
gemsCollected++;
LK.getSound('gemCollect').play();
// Add collection animation
tween(self, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
self.markForDestroy = true;
}
});
}
};
return self;
});
var MenuState = Container.expand(function () {
var self = Container.call(this);
// Create menu background
var menuBg = self.attachAsset('menuBackground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
// Create title background
var titleBg = self.attachAsset('titleBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 600
});
// Create title text
var titleText = new Text2('VILLAIN DEFENSE', {
size: 120,
fill: 0x000000
});
titleText.anchor.set(0.5, 0.5);
self.addChild(titleText);
titleText.x = 1024;
titleText.y = 600;
// Animate title
tween(titleText, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(titleText, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 1000,
easing: tween.easeInOut
});
}
});
// Create play button
var playBtn = self.attachAsset('playButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1000
});
var playText = new Text2('JUGAR', {
size: 60,
fill: 0xFFFFFF
});
playText.anchor.set(0.5, 0.5);
playBtn.addChild(playText);
// Play button interactions
playBtn.down = function (x, y, obj) {
tween(this, {
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 100,
onFinish: function onFinish() {
tween(this, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100
});
}
});
startNewGame();
};
playBtn.move = function (x, y, obj) {
tween(this, {
scaleX: 1.1,
scaleY: 1.1,
tint: 0xff6666
}, {
duration: 200
});
};
// Create settings button
var settingsBtn = self.attachAsset('settingsButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1200
});
var settingsText = new Text2('OPCIONES', {
size: 40,
fill: 0xFFFFFF
});
settingsText.anchor.set(0.5, 0.5);
settingsBtn.addChild(settingsText);
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 100;
self.maxHealth = 100;
self.speed = 4;
self.damage = 20;
self.attackSpeed = 1.0;
self.lastAttack = 0;
self.level = 1;
self.experience = 0;
self.experienceToNext = 10;
self.takeDamage = function (amount) {
self.health -= amount;
LK.effects.flashObject(self, 0xFF0000, 200);
if (self.health <= 0) {
returnToMenu();
}
};
self.gainExperience = function (amount) {
self.experience += amount;
if (self.experience >= self.experienceToNext) {
self.levelUp();
}
};
self.levelUp = function () {
self.level++;
self.experience = 0;
self.experienceToNext = Math.floor(self.experienceToNext * 1.2);
LK.getSound('levelUp').play();
showUpgradeChoice();
};
self.update = function () {
// Auto-attack logic with configurable cooldown
var cooldown = self.attackCooldown || 108; // Default 1.8 seconds
var range = self.attackRange || 200; // Default range
if (LK.ticks - self.lastAttack > cooldown) {
var nearestEnemy = findNearestEnemy();
if (nearestEnemy && getDistance(self, nearestEnemy) < range) {
var bullet = new Bullet();
bullet.x = self.x;
bullet.y = self.y;
bullet.targetX = nearestEnemy.x;
bullet.targetY = nearestEnemy.y;
bullet.damage = self.damage;
// Calculate velocity towards target
var dx = bullet.targetX - bullet.x;
var dy = bullet.targetY - bullet.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
bullet.velocityX = dx / distance * bullet.speed;
bullet.velocityY = dy / distance * bullet.speed;
}
bullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
self.lastAttack = LK.ticks;
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2E2E2E
});
/****
* Game Code
****/
// Game state management
var currentState = 'menu'; // 'menu' or 'playing'
var menuState = null;
var player;
var enemies = [];
var bullets = [];
var experienceOrbs = [];
var gems = [];
var waveNumber = 1;
var enemiesPerWave = 5;
var lastWaveTime = 0;
var gemsCollected = storage.gemsCollected || 0;
var upgradePanelVisible = false;
var upgradePanel = null;
var upgradeOptions = [];
// Initialize menu state
menuState = game.addChild(new MenuState());
// Game reset function
function resetGameState() {
// Reset player stats
if (player) {
player.health = 100;
player.maxHealth = 100;
player.level = 1;
player.experience = 0;
player.experienceToNext = 10;
player.damage = 20;
player.speed = 4;
player.attackCooldown = 108;
player.attackRange = 200;
player.lastAttack = 0;
player.x = 1024;
player.y = 1366;
}
// Clear arrays
for (var i = enemies.length - 1; i >= 0; i--) {
enemies[i].destroy();
}
enemies = [];
for (var i = bullets.length - 1; i >= 0; i--) {
bullets[i].destroy();
}
bullets = [];
for (var i = experienceOrbs.length - 1; i >= 0; i--) {
experienceOrbs[i].destroy();
}
experienceOrbs = [];
for (var i = gems.length - 1; i >= 0; i--) {
gems[i].destroy();
}
gems = [];
// Reset game variables
waveNumber = 1;
enemiesPerWave = 5;
lastWaveTime = 0;
gemsCollected = 0;
upgradePanelVisible = false;
// Clear storage
storage.gemsCollected = 0;
// Reset UI
LK.setScore(0);
}
// Start new game function
function startNewGame() {
currentState = 'playing';
// Hide menu
if (menuState) {
tween(menuState, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 500,
onFinish: function onFinish() {
menuState.destroy();
menuState = null;
}
});
}
// Reset game state
resetGameState();
// Initialize player if not exists
if (!player) {
player = game.addChild(new Player());
}
// Show UI elements
healthBar.alpha = 1;
levelText.alpha = 1;
waveText.alpha = 1;
gemsText.alpha = 1;
expBarBg.alpha = 1;
expText.alpha = 1;
}
// Return to menu function
function returnToMenu() {
currentState = 'menu';
// Hide UI elements
healthBar.alpha = 0;
levelText.alpha = 0;
waveText.alpha = 0;
gemsText.alpha = 0;
expBarBg.alpha = 0;
expText.alpha = 0;
// Show menu
menuState = game.addChild(new MenuState());
menuState.alpha = 0;
tween(menuState, {
alpha: 1
}, {
duration: 500
});
}
// Upgrade definitions
var upgradeTypes = {
fireRate: {
name: "Fire Rate +",
description: "Reduce attack cooldown",
apply: function apply() {
player.attackCooldown = Math.max(30, (player.attackCooldown || 108) - 18);
}
},
damage: {
name: "Damage +",
description: "Increase bullet damage",
apply: function apply() {
player.damage += 8;
}
},
range: {
name: "Range +",
description: "Increase attack range",
apply: function apply() {
player.attackRange = (player.attackRange || 200) + 50;
}
},
speed: {
name: "Speed +",
description: "Move faster",
apply: function apply() {
player.speed += 0.8;
}
},
health: {
name: "Max Health +",
description: "Increase maximum health",
apply: function apply() {
player.maxHealth += 25;
player.health += 25;
}
}
};
// UI Elements (initially hidden)
var healthBar = new Text2('Health: 100/100', {
size: 60,
fill: 0xFF4444
});
healthBar.anchor.set(0, 0);
healthBar.alpha = 0;
LK.gui.topLeft.addChild(healthBar);
healthBar.x = 120; // Offset from menu icon
var levelText = new Text2('Level: 1', {
size: 60,
fill: 0xFFFF44
});
levelText.anchor.set(0.5, 0);
levelText.alpha = 0;
LK.gui.top.addChild(levelText);
var waveText = new Text2('Wave: 1', {
size: 60,
fill: 0x44FFFF
});
waveText.anchor.set(1, 0);
waveText.alpha = 0;
LK.gui.topRight.addChild(waveText);
var gemsText = new Text2('Gems: ' + gemsCollected, {
size: 50,
fill: 0x00BCD4
});
gemsText.anchor.set(0, 1);
gemsText.alpha = 0;
LK.gui.bottomLeft.addChild(gemsText);
// Experience bar
var expBarBg = LK.getAsset('experienceOrb', {
width: 400,
height: 20,
anchorX: 0.5,
anchorY: 1
});
expBarBg.tint = 0x333333;
expBarBg.alpha = 0;
LK.gui.bottom.addChild(expBarBg);
expBarBg.y = -20;
var expBarFill = LK.getAsset('experienceOrb', {
width: 1,
height: 18,
anchorX: 0,
anchorY: 1
});
expBarFill.tint = 0x8bc34a;
expBarBg.addChild(expBarFill);
expBarFill.x = -200;
expBarFill.y = -1;
var expText = new Text2('XP: 0/10', {
size: 40,
fill: 0xFFFFFF
});
expText.anchor.set(0.5, 1);
expText.alpha = 0;
LK.gui.bottom.addChild(expText);
expText.y = -50;
// Utility functions
function getDistance(obj1, obj2) {
var dx = obj1.x - obj2.x;
var dy = obj1.y - obj2.y;
return Math.sqrt(dx * dx + dy * dy);
}
function findNearestEnemy() {
var nearest = null;
var minDistance = Infinity;
for (var i = 0; i < enemies.length; i++) {
var distance = getDistance(player, enemies[i]);
if (distance < minDistance) {
minDistance = distance;
nearest = enemies[i];
}
}
return nearest;
}
function spawnEnemy() {
var type = 'basic';
var rand = Math.random();
if (rand < 0.6) {
type = 'basic';
} else if (rand < 0.8) {
type = 'fast';
} else {
type = 'tank';
}
var enemy = new Enemy(type);
// Spawn from random edge
var side = Math.floor(Math.random() * 4);
if (side === 0) {
// Top
enemy.x = Math.random() * 2048;
enemy.y = -50;
} else if (side === 1) {
// Right
enemy.x = 2098;
enemy.y = Math.random() * 2732;
} else if (side === 2) {
// Bottom
enemy.x = Math.random() * 2048;
enemy.y = 2782;
} else {
// Left
enemy.x = -50;
enemy.y = Math.random() * 2732;
}
enemies.push(enemy);
game.addChild(enemy);
}
function showUpgradeChoice() {
if (upgradePanelVisible) return;
upgradePanelVisible = true;
// Create main upgrade panel container
upgradePanel = new Container();
upgradePanel.alpha = 0;
upgradePanel.scaleX = 0.8;
upgradePanel.scaleY = 0.8;
LK.gui.center.addChild(upgradePanel);
// Create dark background overlay
var backgroundOverlay = LK.getAsset('experienceOrb', {
width: 1000,
height: 700,
anchorX: 0.5,
anchorY: 0.5
});
backgroundOverlay.tint = 0x000000;
backgroundOverlay.alpha = 0.3;
upgradePanel.addChild(backgroundOverlay);
// Create main upgrade box
var upgradeBox = LK.getAsset('experienceOrb', {
width: 1200,
height: 900,
anchorX: 0.5,
anchorY: 0.5
});
upgradeBox.tint = 0x2E2E2E;
upgradeBox.alpha = 0.95;
upgradePanel.addChild(upgradeBox);
// Create border for upgrade box
var boxBorder = LK.getAsset('experienceOrb', {
width: 1220,
height: 920,
anchorX: 0.5,
anchorY: 0.5
});
boxBorder.tint = 0xFFD700;
upgradePanel.addChild(boxBorder);
upgradePanel.addChild(upgradeBox); // Add box on top of border
// Animate panel entrance
tween(upgradePanel, {
alpha: 1.0,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 400,
easing: tween.easeOut
});
// Create title
var title = new Text2('LEVEL UP!', {
size: 80,
fill: 0xFFD700
});
title.anchor.set(0.5, 0.5);
upgradePanel.addChild(title);
title.y = -220;
// Create subtitle
var subtitle = new Text2('Choose an upgrade:', {
size: 50,
fill: 0xFFFFFF
});
subtitle.anchor.set(0.5, 0.5);
upgradePanel.addChild(subtitle);
subtitle.y = -150;
// Select 3 random upgrades
var availableUpgrades = Object.keys(upgradeTypes);
var selectedUpgrades = [];
for (var i = 0; i < 3; i++) {
var randomIndex = Math.floor(Math.random() * availableUpgrades.length);
selectedUpgrades.push(availableUpgrades[randomIndex]);
availableUpgrades.splice(randomIndex, 1);
}
// Create upgrade buttons container
var buttonsContainer = new Container();
upgradePanel.addChild(buttonsContainer);
buttonsContainer.y = -20;
// Create upgrade buttons
upgradeOptions = [];
for (var i = 0; i < selectedUpgrades.length; i++) {
var upgradeKey = selectedUpgrades[i];
var upgrade = upgradeTypes[upgradeKey];
// Create button background
var buttonBg = LK.getAsset('experienceOrb', {
width: 240,
height: 160,
anchorX: 0.5,
anchorY: 0.5
});
buttonBg.tint = 0x2E2E2E;
buttonsContainer.addChild(buttonBg);
buttonBg.x = (i - 1) * 280;
// Create button
var button = LK.getAsset('experienceOrb', {
width: 220,
height: 140,
anchorX: 0.5,
anchorY: 0.5
});
button.tint = 0x4CAF50;
button.upgradeKey = upgradeKey;
button.originalTint = 0x4CAF50;
buttonsContainer.addChild(button);
button.x = (i - 1) * 280;
// Add hover effect
button.move = function (x, y, obj) {
if (this.intersects && typeof this.intersects === 'function') {
tween(this, {
tint: 0x66BB6A,
scaleX: 1.05,
scaleY: 1.05
}, {
duration: 200
});
}
};
var buttonText = new Text2(upgrade.name, {
size: 42,
fill: 0xFFFFFF
});
buttonText.anchor.set(0.5, 0.5);
button.addChild(buttonText);
// Create description text below the button
var descText = new Text2(upgrade.description, {
size: 28,
fill: 0xE0E0E0
});
descText.anchor.set(0.5, 0.5);
buttonsContainer.addChild(descText);
descText.x = (i - 1) * 280;
descText.y = 120; // Position below buttons
// Add button functionality
button.down = function (x, y, obj) {
var upgradeKey = this.upgradeKey;
if (upgradeKey && upgradeTypes[upgradeKey]) {
// Add visual feedback
tween(this, {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 100,
onFinish: function onFinish() {
tween(this, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100
});
}
});
selectUpgrade(upgradeKey);
}
};
upgradeOptions.push(button);
}
}
function selectUpgrade(upgradeKey) {
if (!upgradePanelVisible || !upgradeKey) return;
// Apply the upgrade
if (upgradeTypes[upgradeKey] && typeof upgradeTypes[upgradeKey].apply === 'function') {
upgradeTypes[upgradeKey].apply();
}
// Show upgrade confirmation
var confirmText = new Text2('Upgrade Applied!', {
size: 60,
fill: 0x00FF00
});
confirmText.anchor.set(0.5, 0.5);
upgradePanel.addChild(confirmText);
confirmText.y = 180;
confirmText.alpha = 0;
// Animate confirmation text
tween(confirmText, {
alpha: 1,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
onFinish: function onFinish() {
tween(confirmText, {
alpha: 0
}, {
duration: 300
});
}
});
// Hide upgrade panel with tween animation
if (upgradePanel) {
tween(upgradePanel, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 400,
easing: tween.easeIn,
onFinish: function onFinish() {
upgradePanel.destroy();
upgradePanel = null;
}
});
}
upgradeOptions = [];
upgradePanelVisible = false;
}
// Touch controls
var dragStarted = false;
game.down = function (x, y, obj) {
if (currentState === 'playing' && player) {
dragStarted = true;
player.x = x;
player.y = y;
}
};
game.move = function (x, y, obj) {
if (currentState === 'playing' && dragStarted && player) {
player.x = x;
player.y = y;
}
};
game.up = function (x, y, obj) {
if (currentState === 'playing') {
dragStarted = false;
}
};
// Add gem spawn timer
var lastGemSpawn = 0;
// Main game loop
game.update = function () {
// Only run game logic when in playing state
if (currentState !== 'playing' || !player) return;
// Spawn gems automatically every 5 seconds
if (LK.ticks - lastGemSpawn > 300) {
// 5 seconds at 60fps
var gem = new Gem();
// Random position on screen
gem.x = Math.random() * 1800 + 124; // Keep away from edges
gem.y = Math.random() * 2400 + 166;
gems.push(gem);
game.addChild(gem);
lastGemSpawn = LK.ticks;
}
// Spawn waves - faster at start
var waveInterval = Math.max(600, 1800 - waveNumber * 60); // Start at 10s, max 30s
if (LK.ticks - lastWaveTime > waveInterval) {
waveNumber++;
enemiesPerWave += 2;
lastWaveTime = LK.ticks;
// Spawn enemies for this wave
for (var i = 0; i < enemiesPerWave; i++) {
LK.setTimeout(function () {
spawnEnemy();
}, i * 200); // Stagger spawns
}
}
// Update enemies
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
if (enemy.health <= 0) {
enemy.die();
enemy.destroy();
enemies.splice(i, 1);
}
}
// Update bullets
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
if (bullet.markForDestroy) {
bullet.destroy();
bullets.splice(i, 1);
}
}
// Update experience orbs
for (var i = experienceOrbs.length - 1; i >= 0; i--) {
var orb = experienceOrbs[i];
if (orb.markForDestroy) {
orb.destroy();
experienceOrbs.splice(i, 1);
}
}
// Update gems
for (var i = gems.length - 1; i >= 0; i--) {
var gem = gems[i];
if (gem.markForDestroy) {
gem.destroy();
gems.splice(i, 1);
}
}
// Update UI
healthBar.setText('Health: ' + player.health + '/' + player.maxHealth);
levelText.setText('Level: ' + player.level);
waveText.setText('Wave: ' + waveNumber);
gemsText.setText('Gems: ' + gemsCollected);
// Update experience bar
var expPercentage = player.experience / player.experienceToNext;
expBarFill.width = 398 * expPercentage;
expText.setText('XP: ' + player.experience + '/' + player.experienceToNext);
// Save gems to storage
storage.gemsCollected = gemsCollected;
// Keep player within bounds
player.x = Math.max(40, Math.min(2008, player.x));
player.y = Math.max(40, Math.min(2692, player.y));
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Bullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8;
self.damage = 20;
self.targetX = 0;
self.targetY = 0;
self.velocityX = 0;
self.velocityY = 0;
// Velocity will be calculated when bullet is created
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
// Check if off screen
if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
self.markForDestroy = true;
}
// Check collision with enemies
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (getDistance(self, enemy) < 60) {
enemy.takeDamage(self.damage);
self.markForDestroy = true;
break;
}
}
};
return self;
});
var Enemy = Container.expand(function (type) {
var self = Container.call(this);
self.type = type || 'basic';
var assetName = self.type + 'Enemy';
var enemyGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
// Set stats based on type
if (self.type === 'basic') {
self.health = 30;
self.speed = 1.5;
self.damage = 10;
self.experienceValue = 2;
} else if (self.type === 'fast') {
self.health = 15;
self.speed = 3.5;
self.damage = 8;
self.experienceValue = 3;
} else if (self.type === 'tank') {
self.health = 80;
self.speed = 0.8;
self.damage = 20;
self.experienceValue = 5;
}
self.maxHealth = self.health;
self.takeDamage = function (amount) {
self.health -= amount;
LK.effects.flashObject(self, 0xFFFFFF, 100);
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
// Drop experience orb
var expOrb = new ExperienceOrb();
expOrb.x = self.x;
expOrb.y = self.y;
expOrb.value = self.experienceValue;
experienceOrbs.push(expOrb);
game.addChild(expOrb);
// Chance to drop gem
if (Math.random() < 0.3) {
var gem = new Gem();
gem.x = self.x;
gem.y = self.y;
gems.push(gem);
game.addChild(gem);
}
LK.getSound('enemyHit').play();
LK.setScore(LK.getScore() + self.experienceValue * 10);
};
self.update = function () {
// Move towards 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;
}
// Check collision with player
if (getDistance(self, player) < 100) {
player.takeDamage(self.damage);
}
};
return self;
});
var ExperienceOrb = Container.expand(function () {
var self = Container.call(this);
var orbGraphics = self.attachAsset('experienceOrb', {
anchorX: 0.5,
anchorY: 0.5
});
self.value = 1;
self.collectRadius = 100;
self.update = function () {
// Move towards player if close enough
var distance = getDistance(self, player);
if (distance < self.collectRadius) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var moveSpeed = 6;
if (distance > 0) {
self.x += dx / distance * moveSpeed;
self.y += dy / distance * moveSpeed;
}
// Collect if very close
if (distance < 50) {
player.gainExperience(self.value);
self.markForDestroy = true;
}
}
};
return self;
});
var Gem = Container.expand(function () {
var self = Container.call(this);
var gemGraphics = self.attachAsset('gem', {
anchorX: 0.5,
anchorY: 0.5
});
self.value = 1;
self.update = function () {
// Simple floating animation
self.y += Math.sin(LK.ticks * 0.1) * 0.5;
// Collect if player touches
if (getDistance(self, player) < 80) {
gemsCollected++;
LK.getSound('gemCollect').play();
// Add collection animation
tween(self, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
self.markForDestroy = true;
}
});
}
};
return self;
});
var MenuState = Container.expand(function () {
var self = Container.call(this);
// Create menu background
var menuBg = self.attachAsset('menuBackground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
// Create title background
var titleBg = self.attachAsset('titleBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 600
});
// Create title text
var titleText = new Text2('VILLAIN DEFENSE', {
size: 120,
fill: 0x000000
});
titleText.anchor.set(0.5, 0.5);
self.addChild(titleText);
titleText.x = 1024;
titleText.y = 600;
// Animate title
tween(titleText, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(titleText, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 1000,
easing: tween.easeInOut
});
}
});
// Create play button
var playBtn = self.attachAsset('playButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1000
});
var playText = new Text2('JUGAR', {
size: 60,
fill: 0xFFFFFF
});
playText.anchor.set(0.5, 0.5);
playBtn.addChild(playText);
// Play button interactions
playBtn.down = function (x, y, obj) {
tween(this, {
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 100,
onFinish: function onFinish() {
tween(this, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100
});
}
});
startNewGame();
};
playBtn.move = function (x, y, obj) {
tween(this, {
scaleX: 1.1,
scaleY: 1.1,
tint: 0xff6666
}, {
duration: 200
});
};
// Create settings button
var settingsBtn = self.attachAsset('settingsButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1200
});
var settingsText = new Text2('OPCIONES', {
size: 40,
fill: 0xFFFFFF
});
settingsText.anchor.set(0.5, 0.5);
settingsBtn.addChild(settingsText);
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 100;
self.maxHealth = 100;
self.speed = 4;
self.damage = 20;
self.attackSpeed = 1.0;
self.lastAttack = 0;
self.level = 1;
self.experience = 0;
self.experienceToNext = 10;
self.takeDamage = function (amount) {
self.health -= amount;
LK.effects.flashObject(self, 0xFF0000, 200);
if (self.health <= 0) {
returnToMenu();
}
};
self.gainExperience = function (amount) {
self.experience += amount;
if (self.experience >= self.experienceToNext) {
self.levelUp();
}
};
self.levelUp = function () {
self.level++;
self.experience = 0;
self.experienceToNext = Math.floor(self.experienceToNext * 1.2);
LK.getSound('levelUp').play();
showUpgradeChoice();
};
self.update = function () {
// Auto-attack logic with configurable cooldown
var cooldown = self.attackCooldown || 108; // Default 1.8 seconds
var range = self.attackRange || 200; // Default range
if (LK.ticks - self.lastAttack > cooldown) {
var nearestEnemy = findNearestEnemy();
if (nearestEnemy && getDistance(self, nearestEnemy) < range) {
var bullet = new Bullet();
bullet.x = self.x;
bullet.y = self.y;
bullet.targetX = nearestEnemy.x;
bullet.targetY = nearestEnemy.y;
bullet.damage = self.damage;
// Calculate velocity towards target
var dx = bullet.targetX - bullet.x;
var dy = bullet.targetY - bullet.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
bullet.velocityX = dx / distance * bullet.speed;
bullet.velocityY = dy / distance * bullet.speed;
}
bullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
self.lastAttack = LK.ticks;
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2E2E2E
});
/****
* Game Code
****/
// Game state management
var currentState = 'menu'; // 'menu' or 'playing'
var menuState = null;
var player;
var enemies = [];
var bullets = [];
var experienceOrbs = [];
var gems = [];
var waveNumber = 1;
var enemiesPerWave = 5;
var lastWaveTime = 0;
var gemsCollected = storage.gemsCollected || 0;
var upgradePanelVisible = false;
var upgradePanel = null;
var upgradeOptions = [];
// Initialize menu state
menuState = game.addChild(new MenuState());
// Game reset function
function resetGameState() {
// Reset player stats
if (player) {
player.health = 100;
player.maxHealth = 100;
player.level = 1;
player.experience = 0;
player.experienceToNext = 10;
player.damage = 20;
player.speed = 4;
player.attackCooldown = 108;
player.attackRange = 200;
player.lastAttack = 0;
player.x = 1024;
player.y = 1366;
}
// Clear arrays
for (var i = enemies.length - 1; i >= 0; i--) {
enemies[i].destroy();
}
enemies = [];
for (var i = bullets.length - 1; i >= 0; i--) {
bullets[i].destroy();
}
bullets = [];
for (var i = experienceOrbs.length - 1; i >= 0; i--) {
experienceOrbs[i].destroy();
}
experienceOrbs = [];
for (var i = gems.length - 1; i >= 0; i--) {
gems[i].destroy();
}
gems = [];
// Reset game variables
waveNumber = 1;
enemiesPerWave = 5;
lastWaveTime = 0;
gemsCollected = 0;
upgradePanelVisible = false;
// Clear storage
storage.gemsCollected = 0;
// Reset UI
LK.setScore(0);
}
// Start new game function
function startNewGame() {
currentState = 'playing';
// Hide menu
if (menuState) {
tween(menuState, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 500,
onFinish: function onFinish() {
menuState.destroy();
menuState = null;
}
});
}
// Reset game state
resetGameState();
// Initialize player if not exists
if (!player) {
player = game.addChild(new Player());
}
// Show UI elements
healthBar.alpha = 1;
levelText.alpha = 1;
waveText.alpha = 1;
gemsText.alpha = 1;
expBarBg.alpha = 1;
expText.alpha = 1;
}
// Return to menu function
function returnToMenu() {
currentState = 'menu';
// Hide UI elements
healthBar.alpha = 0;
levelText.alpha = 0;
waveText.alpha = 0;
gemsText.alpha = 0;
expBarBg.alpha = 0;
expText.alpha = 0;
// Show menu
menuState = game.addChild(new MenuState());
menuState.alpha = 0;
tween(menuState, {
alpha: 1
}, {
duration: 500
});
}
// Upgrade definitions
var upgradeTypes = {
fireRate: {
name: "Fire Rate +",
description: "Reduce attack cooldown",
apply: function apply() {
player.attackCooldown = Math.max(30, (player.attackCooldown || 108) - 18);
}
},
damage: {
name: "Damage +",
description: "Increase bullet damage",
apply: function apply() {
player.damage += 8;
}
},
range: {
name: "Range +",
description: "Increase attack range",
apply: function apply() {
player.attackRange = (player.attackRange || 200) + 50;
}
},
speed: {
name: "Speed +",
description: "Move faster",
apply: function apply() {
player.speed += 0.8;
}
},
health: {
name: "Max Health +",
description: "Increase maximum health",
apply: function apply() {
player.maxHealth += 25;
player.health += 25;
}
}
};
// UI Elements (initially hidden)
var healthBar = new Text2('Health: 100/100', {
size: 60,
fill: 0xFF4444
});
healthBar.anchor.set(0, 0);
healthBar.alpha = 0;
LK.gui.topLeft.addChild(healthBar);
healthBar.x = 120; // Offset from menu icon
var levelText = new Text2('Level: 1', {
size: 60,
fill: 0xFFFF44
});
levelText.anchor.set(0.5, 0);
levelText.alpha = 0;
LK.gui.top.addChild(levelText);
var waveText = new Text2('Wave: 1', {
size: 60,
fill: 0x44FFFF
});
waveText.anchor.set(1, 0);
waveText.alpha = 0;
LK.gui.topRight.addChild(waveText);
var gemsText = new Text2('Gems: ' + gemsCollected, {
size: 50,
fill: 0x00BCD4
});
gemsText.anchor.set(0, 1);
gemsText.alpha = 0;
LK.gui.bottomLeft.addChild(gemsText);
// Experience bar
var expBarBg = LK.getAsset('experienceOrb', {
width: 400,
height: 20,
anchorX: 0.5,
anchorY: 1
});
expBarBg.tint = 0x333333;
expBarBg.alpha = 0;
LK.gui.bottom.addChild(expBarBg);
expBarBg.y = -20;
var expBarFill = LK.getAsset('experienceOrb', {
width: 1,
height: 18,
anchorX: 0,
anchorY: 1
});
expBarFill.tint = 0x8bc34a;
expBarBg.addChild(expBarFill);
expBarFill.x = -200;
expBarFill.y = -1;
var expText = new Text2('XP: 0/10', {
size: 40,
fill: 0xFFFFFF
});
expText.anchor.set(0.5, 1);
expText.alpha = 0;
LK.gui.bottom.addChild(expText);
expText.y = -50;
// Utility functions
function getDistance(obj1, obj2) {
var dx = obj1.x - obj2.x;
var dy = obj1.y - obj2.y;
return Math.sqrt(dx * dx + dy * dy);
}
function findNearestEnemy() {
var nearest = null;
var minDistance = Infinity;
for (var i = 0; i < enemies.length; i++) {
var distance = getDistance(player, enemies[i]);
if (distance < minDistance) {
minDistance = distance;
nearest = enemies[i];
}
}
return nearest;
}
function spawnEnemy() {
var type = 'basic';
var rand = Math.random();
if (rand < 0.6) {
type = 'basic';
} else if (rand < 0.8) {
type = 'fast';
} else {
type = 'tank';
}
var enemy = new Enemy(type);
// Spawn from random edge
var side = Math.floor(Math.random() * 4);
if (side === 0) {
// Top
enemy.x = Math.random() * 2048;
enemy.y = -50;
} else if (side === 1) {
// Right
enemy.x = 2098;
enemy.y = Math.random() * 2732;
} else if (side === 2) {
// Bottom
enemy.x = Math.random() * 2048;
enemy.y = 2782;
} else {
// Left
enemy.x = -50;
enemy.y = Math.random() * 2732;
}
enemies.push(enemy);
game.addChild(enemy);
}
function showUpgradeChoice() {
if (upgradePanelVisible) return;
upgradePanelVisible = true;
// Create main upgrade panel container
upgradePanel = new Container();
upgradePanel.alpha = 0;
upgradePanel.scaleX = 0.8;
upgradePanel.scaleY = 0.8;
LK.gui.center.addChild(upgradePanel);
// Create dark background overlay
var backgroundOverlay = LK.getAsset('experienceOrb', {
width: 1000,
height: 700,
anchorX: 0.5,
anchorY: 0.5
});
backgroundOverlay.tint = 0x000000;
backgroundOverlay.alpha = 0.3;
upgradePanel.addChild(backgroundOverlay);
// Create main upgrade box
var upgradeBox = LK.getAsset('experienceOrb', {
width: 1200,
height: 900,
anchorX: 0.5,
anchorY: 0.5
});
upgradeBox.tint = 0x2E2E2E;
upgradeBox.alpha = 0.95;
upgradePanel.addChild(upgradeBox);
// Create border for upgrade box
var boxBorder = LK.getAsset('experienceOrb', {
width: 1220,
height: 920,
anchorX: 0.5,
anchorY: 0.5
});
boxBorder.tint = 0xFFD700;
upgradePanel.addChild(boxBorder);
upgradePanel.addChild(upgradeBox); // Add box on top of border
// Animate panel entrance
tween(upgradePanel, {
alpha: 1.0,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 400,
easing: tween.easeOut
});
// Create title
var title = new Text2('LEVEL UP!', {
size: 80,
fill: 0xFFD700
});
title.anchor.set(0.5, 0.5);
upgradePanel.addChild(title);
title.y = -220;
// Create subtitle
var subtitle = new Text2('Choose an upgrade:', {
size: 50,
fill: 0xFFFFFF
});
subtitle.anchor.set(0.5, 0.5);
upgradePanel.addChild(subtitle);
subtitle.y = -150;
// Select 3 random upgrades
var availableUpgrades = Object.keys(upgradeTypes);
var selectedUpgrades = [];
for (var i = 0; i < 3; i++) {
var randomIndex = Math.floor(Math.random() * availableUpgrades.length);
selectedUpgrades.push(availableUpgrades[randomIndex]);
availableUpgrades.splice(randomIndex, 1);
}
// Create upgrade buttons container
var buttonsContainer = new Container();
upgradePanel.addChild(buttonsContainer);
buttonsContainer.y = -20;
// Create upgrade buttons
upgradeOptions = [];
for (var i = 0; i < selectedUpgrades.length; i++) {
var upgradeKey = selectedUpgrades[i];
var upgrade = upgradeTypes[upgradeKey];
// Create button background
var buttonBg = LK.getAsset('experienceOrb', {
width: 240,
height: 160,
anchorX: 0.5,
anchorY: 0.5
});
buttonBg.tint = 0x2E2E2E;
buttonsContainer.addChild(buttonBg);
buttonBg.x = (i - 1) * 280;
// Create button
var button = LK.getAsset('experienceOrb', {
width: 220,
height: 140,
anchorX: 0.5,
anchorY: 0.5
});
button.tint = 0x4CAF50;
button.upgradeKey = upgradeKey;
button.originalTint = 0x4CAF50;
buttonsContainer.addChild(button);
button.x = (i - 1) * 280;
// Add hover effect
button.move = function (x, y, obj) {
if (this.intersects && typeof this.intersects === 'function') {
tween(this, {
tint: 0x66BB6A,
scaleX: 1.05,
scaleY: 1.05
}, {
duration: 200
});
}
};
var buttonText = new Text2(upgrade.name, {
size: 42,
fill: 0xFFFFFF
});
buttonText.anchor.set(0.5, 0.5);
button.addChild(buttonText);
// Create description text below the button
var descText = new Text2(upgrade.description, {
size: 28,
fill: 0xE0E0E0
});
descText.anchor.set(0.5, 0.5);
buttonsContainer.addChild(descText);
descText.x = (i - 1) * 280;
descText.y = 120; // Position below buttons
// Add button functionality
button.down = function (x, y, obj) {
var upgradeKey = this.upgradeKey;
if (upgradeKey && upgradeTypes[upgradeKey]) {
// Add visual feedback
tween(this, {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 100,
onFinish: function onFinish() {
tween(this, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100
});
}
});
selectUpgrade(upgradeKey);
}
};
upgradeOptions.push(button);
}
}
function selectUpgrade(upgradeKey) {
if (!upgradePanelVisible || !upgradeKey) return;
// Apply the upgrade
if (upgradeTypes[upgradeKey] && typeof upgradeTypes[upgradeKey].apply === 'function') {
upgradeTypes[upgradeKey].apply();
}
// Show upgrade confirmation
var confirmText = new Text2('Upgrade Applied!', {
size: 60,
fill: 0x00FF00
});
confirmText.anchor.set(0.5, 0.5);
upgradePanel.addChild(confirmText);
confirmText.y = 180;
confirmText.alpha = 0;
// Animate confirmation text
tween(confirmText, {
alpha: 1,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
onFinish: function onFinish() {
tween(confirmText, {
alpha: 0
}, {
duration: 300
});
}
});
// Hide upgrade panel with tween animation
if (upgradePanel) {
tween(upgradePanel, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 400,
easing: tween.easeIn,
onFinish: function onFinish() {
upgradePanel.destroy();
upgradePanel = null;
}
});
}
upgradeOptions = [];
upgradePanelVisible = false;
}
// Touch controls
var dragStarted = false;
game.down = function (x, y, obj) {
if (currentState === 'playing' && player) {
dragStarted = true;
player.x = x;
player.y = y;
}
};
game.move = function (x, y, obj) {
if (currentState === 'playing' && dragStarted && player) {
player.x = x;
player.y = y;
}
};
game.up = function (x, y, obj) {
if (currentState === 'playing') {
dragStarted = false;
}
};
// Add gem spawn timer
var lastGemSpawn = 0;
// Main game loop
game.update = function () {
// Only run game logic when in playing state
if (currentState !== 'playing' || !player) return;
// Spawn gems automatically every 5 seconds
if (LK.ticks - lastGemSpawn > 300) {
// 5 seconds at 60fps
var gem = new Gem();
// Random position on screen
gem.x = Math.random() * 1800 + 124; // Keep away from edges
gem.y = Math.random() * 2400 + 166;
gems.push(gem);
game.addChild(gem);
lastGemSpawn = LK.ticks;
}
// Spawn waves - faster at start
var waveInterval = Math.max(600, 1800 - waveNumber * 60); // Start at 10s, max 30s
if (LK.ticks - lastWaveTime > waveInterval) {
waveNumber++;
enemiesPerWave += 2;
lastWaveTime = LK.ticks;
// Spawn enemies for this wave
for (var i = 0; i < enemiesPerWave; i++) {
LK.setTimeout(function () {
spawnEnemy();
}, i * 200); // Stagger spawns
}
}
// Update enemies
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
if (enemy.health <= 0) {
enemy.die();
enemy.destroy();
enemies.splice(i, 1);
}
}
// Update bullets
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
if (bullet.markForDestroy) {
bullet.destroy();
bullets.splice(i, 1);
}
}
// Update experience orbs
for (var i = experienceOrbs.length - 1; i >= 0; i--) {
var orb = experienceOrbs[i];
if (orb.markForDestroy) {
orb.destroy();
experienceOrbs.splice(i, 1);
}
}
// Update gems
for (var i = gems.length - 1; i >= 0; i--) {
var gem = gems[i];
if (gem.markForDestroy) {
gem.destroy();
gems.splice(i, 1);
}
}
// Update UI
healthBar.setText('Health: ' + player.health + '/' + player.maxHealth);
levelText.setText('Level: ' + player.level);
waveText.setText('Wave: ' + waveNumber);
gemsText.setText('Gems: ' + gemsCollected);
// Update experience bar
var expPercentage = player.experience / player.experienceToNext;
expBarFill.width = 398 * expPercentage;
expText.setText('XP: ' + player.experience + '/' + player.experienceToNext);
// Save gems to storage
storage.gemsCollected = gemsCollected;
// Keep player within bounds
player.x = Math.max(40, Math.min(2008, player.x));
player.y = Math.max(40, Math.min(2692, player.y));
};