/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0
});
/****
* Classes
****/
var ArenaWall = Container.expand(function () {
var self = Container.call(this);
self.wallGraphics = [];
self.createWalls = function (centerX, centerY, size) {
// Clear existing walls
for (var i = 0; i < self.wallGraphics.length; i++) {
self.removeChild(self.wallGraphics[i]);
}
self.wallGraphics = [];
// Calculate positions
var halfSize = size / 2;
var wallSize = 30;
var numWalls = Math.floor(size / wallSize);
// Top and bottom walls
for (var x = 0; x < numWalls; x++) {
var topWall = self.attachAsset('arenaWall', {
anchorX: 0.5,
anchorY: 0.5,
x: centerX - halfSize + (x + 0.5) * wallSize,
y: centerY - halfSize
});
self.wallGraphics.push(topWall);
var bottomWall = self.attachAsset('arenaWall', {
anchorX: 0.5,
anchorY: 0.5,
x: centerX - halfSize + (x + 0.5) * wallSize,
y: centerY + halfSize
});
self.wallGraphics.push(bottomWall);
}
// Left and right walls (skip corners)
for (var y = 1; y < numWalls - 1; y++) {
var leftWall = self.attachAsset('arenaWall', {
anchorX: 0.5,
anchorY: 0.5,
x: centerX - halfSize,
y: centerY - halfSize + (y + 0.5) * wallSize
});
self.wallGraphics.push(leftWall);
var rightWall = self.attachAsset('arenaWall', {
anchorX: 0.5,
anchorY: 0.5,
x: centerX + halfSize,
y: centerY - halfSize + (y + 0.5) * wallSize
});
self.wallGraphics.push(rightWall);
}
};
return self;
});
var Brawler = Container.expand(function () {
var self = Container.call(this);
// Character graphic
var graphic = self.attachAsset('brawler', {
anchorX: 0.5,
anchorY: 0.5
});
// Health bar background
var healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5,
y: -60
});
// Health bar
var healthBar = self.attachAsset('healthBar', {
anchorX: 0,
anchorY: 0.5,
y: -60,
x: -40
});
// Properties
self.maxHealth = 100;
self.health = self.maxHealth;
self.attackCooldown = 0;
self.attackCooldownMax = 20;
self.speed = 5;
self.isDragging = false;
self.direction = {
x: 0,
y: 0
};
self.isPlayer = false;
self.isEnemy = false;
self.attackDamage = 20;
self.lastPosition = {
x: 0,
y: 0
};
self.isDead = false;
// Movement AI for enemies
self.moveTowards = function (target, speed) {
if (!target || self.isDead) {
return;
}
var dx = target.x - self.x;
var dy = target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.direction.x = dx / distance;
self.direction.y = dy / distance;
self.x += self.direction.x * speed;
self.y += self.direction.y * speed;
}
};
// Attack method
self.attack = function () {
if (self.attackCooldown > 0 || self.isDead) {
return;
}
var projectile = new Projectile();
projectile.x = self.x;
projectile.y = self.y;
projectile.direction = {
x: self.direction.x || 1,
y: self.direction.y || 0
};
projectile.owner = self;
game.addChild(projectile);
gameState.projectiles.push(projectile);
self.attackCooldown = self.attackCooldownMax;
LK.getSound('shoot').play();
};
// Take damage method
self.takeDamage = function (amount) {
if (self.isDead) {
return;
}
self.health -= amount;
// Update health bar
healthBar.width = self.health / self.maxHealth * 80;
// Flash the character red
LK.effects.flashObject(graphic, 0xff0000, 200);
LK.getSound('hit').play();
if (self.health <= 0) {
self.die();
}
};
// Die method
self.die = function () {
if (self.isDead) {
return;
}
self.isDead = true;
tween(self, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
self.markForRemoval = true;
}
});
if (self.isPlayer) {
LK.showGameOver();
} else if (self.isEnemy) {
gameState.score += 100;
gameState.defeatedEnemies++;
scoreTxt.setText("Score: " + gameState.score);
if (gameState.defeatedEnemies >= gameState.totalEnemies) {
LK.showYouWin();
}
}
};
// Event handlers
self.down = function (x, y, obj) {
if (self.isPlayer && !self.isDead) {
self.isDragging = true;
}
};
self.up = function (x, y, obj) {
if (self.isPlayer) {
self.isDragging = false;
}
};
self.update = function () {
// Store last position
self.lastPosition.x = self.x;
self.lastPosition.y = self.y;
// Update cooldowns
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
// Keep within arena bounds
var arenaSize = gameState.arenaSize;
var halfSize = arenaSize / 2;
if (self.x < gameState.arenaCenter.x - halfSize + 50) {
self.x = gameState.arenaCenter.x - halfSize + 50;
}
if (self.x > gameState.arenaCenter.x + halfSize - 50) {
self.x = gameState.arenaCenter.x + halfSize - 50;
}
if (self.y < gameState.arenaCenter.y - halfSize + 50) {
self.y = gameState.arenaCenter.y - halfSize + 50;
}
if (self.y > gameState.arenaCenter.y + halfSize - 50) {
self.y = gameState.arenaCenter.y + halfSize - 50;
}
};
return self;
});
var Enemy = Brawler.expand(function () {
var self = Brawler.call(this);
// Override the default brawler graphic with enemy graphic
var graphic = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.isEnemy = true;
self.targetUpdateCooldown = 0;
self.currentTarget = null;
// Override the update method
var parentUpdate = self.update;
self.update = function () {
if (self.isDead) {
return;
}
// Base update from parent
parentUpdate();
// Update target every so often
if (self.targetUpdateCooldown <= 0) {
self.findNewTarget();
self.targetUpdateCooldown = 60; // Update target every 60 frames
} else {
self.targetUpdateCooldown--;
}
// Move towards target
if (self.currentTarget && !self.currentTarget.isDead) {
self.moveTowards(self.currentTarget, self.speed);
// Attack if close enough
var dx = self.currentTarget.x - self.x;
var dy = self.currentTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 300) {
// Set direction for projectile
self.direction.x = dx / distance;
self.direction.y = dy / distance;
// Attack with some randomness
if (Math.random() < 0.03) {
self.attack();
}
}
} else {
// If no target or target is dead, wander randomly
if (Math.random() < 0.02) {
self.direction.x = Math.random() * 2 - 1;
self.direction.y = Math.random() * 2 - 1;
var mag = Math.sqrt(self.direction.x * self.direction.x + self.direction.y * self.direction.y);
self.direction.x /= mag;
self.direction.y /= mag;
}
self.x += self.direction.x * self.speed / 2;
self.y += self.direction.y * self.speed / 2;
}
};
self.findNewTarget = function () {
// Target the player
if (gameState.player && !gameState.player.isDead) {
self.currentTarget = gameState.player;
return;
}
// No valid targets
self.currentTarget = null;
};
return self;
});
var PowerUp = Container.expand(function () {
var self = Container.call(this);
var graphic = self.attachAsset('powerUp', {
anchorX: 0.5,
anchorY: 0.5
});
self.type = 'health'; // health, damage, speed
self.value = 20;
// Visual effect - floating animation
self.floatAnim = function () {
tween(graphic, {
y: -5
}, {
duration: 1000,
easing: tween.sinceOut,
onFinish: function onFinish() {
tween(graphic, {
y: 5
}, {
duration: 1000,
easing: tween.sinceOut,
onFinish: self.floatAnim
});
}
});
};
self.floatAnim();
return self;
});
var Projectile = Container.expand(function () {
var self = Container.call(this);
var graphic = self.attachAsset('projectile', {
anchorX: 0.5,
anchorY: 0.5
});
self.direction = {
x: 0,
y: 0
};
self.speed = 12;
self.damage = 10;
self.lifetime = 60; // frames the projectile lives for
self.owner = null;
self.update = function () {
self.x += self.direction.x * self.speed;
self.y += self.direction.y * self.speed;
self.lifetime--;
if (self.lifetime <= 0) {
self.markForRemoval = true;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
// Game state tracking
var gameState = {
arenaCenter: {
x: 2048 / 2,
y: 2732 / 2
},
arenaSize: 1800,
minArenaSize: 1000,
arenaShrinkRate: 0.2,
arenaShrinkStart: 1000,
// ticks before arena starts shrinking
arenaShrinkTimer: 0,
projectiles: [],
powerUps: [],
enemies: [],
player: null,
score: 0,
level: 1,
defeatedEnemies: 0,
totalEnemies: 5,
gameStarted: false
};
// Create arena background
var arenaBackground = LK.getAsset('arena', {
anchorX: 0.5,
anchorY: 0.5,
x: gameState.arenaCenter.x,
y: gameState.arenaCenter.y,
width: gameState.arenaSize,
height: gameState.arenaSize,
alpha: 0.3
});
game.addChild(arenaBackground);
// Create arena walls
var arenaWalls = new ArenaWall();
game.addChild(arenaWalls);
arenaWalls.createWalls(gameState.arenaCenter.x, gameState.arenaCenter.y, gameState.arenaSize);
// Create player
var player = new Brawler();
player.x = gameState.arenaCenter.x;
player.y = gameState.arenaCenter.y + 300;
player.isPlayer = true;
game.addChild(player);
gameState.player = player;
// Setup GUI elements
var scoreTxt = new Text2("Score: 0", {
size: 50,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0, 0);
LK.gui.topRight.addChild(scoreTxt);
scoreTxt.x = -250;
scoreTxt.y = 50;
var levelTxt = new Text2("Level: 1", {
size: 50,
fill: 0xFFFFFF
});
levelTxt.anchor.set(0, 0);
LK.gui.topRight.addChild(levelTxt);
levelTxt.x = -250;
levelTxt.y = 120;
var arenaSizeTxt = new Text2("Arena Shrinking", {
size: 40,
fill: 0xFF9900
});
arenaSizeTxt.anchor.set(0.5, 0);
arenaSizeTxt.alpha = 0;
LK.gui.top.addChild(arenaSizeTxt);
arenaSizeTxt.y = 50;
// Initialize enemies
function spawnEnemies() {
// Clear existing enemies
for (var i = gameState.enemies.length - 1; i >= 0; i--) {
game.removeChild(gameState.enemies[i]);
gameState.enemies.splice(i, 1);
}
// Set number of enemies based on level
gameState.totalEnemies = 3 + gameState.level * 2;
gameState.defeatedEnemies = 0;
// Spawn new enemies
for (var i = 0; i < gameState.totalEnemies; i++) {
var enemy = new Enemy();
// Position around the arena
var angle = i / gameState.totalEnemies * Math.PI * 2;
var radius = gameState.arenaSize / 2 - 200;
enemy.x = gameState.arenaCenter.x + Math.cos(angle) * radius;
enemy.y = gameState.arenaCenter.y + Math.sin(angle) * radius;
// Scale difficulty with level
enemy.maxHealth = 80 + gameState.level * 20;
enemy.health = enemy.maxHealth;
enemy.speed = 3 + gameState.level * 0.5;
game.addChild(enemy);
gameState.enemies.push(enemy);
}
}
// Spawn power-ups periodically
function spawnPowerUp() {
if (gameState.powerUps.length >= 5) {
return;
} // Limit number of power-ups
var powerUp = new PowerUp();
// Random position within arena
var halfSize = gameState.arenaSize / 2 - 100;
powerUp.x = gameState.arenaCenter.x + Math.random() * halfSize * 2 - halfSize;
powerUp.y = gameState.arenaCenter.y + Math.random() * halfSize * 2 - halfSize;
// Random type
var typeRoll = Math.random();
if (typeRoll < 0.4) {
powerUp.type = 'health';
powerUp.tint = 0x00ff00;
} else if (typeRoll < 0.7) {
powerUp.type = 'damage';
powerUp.tint = 0xff0000;
powerUp.value = 10;
} else {
powerUp.type = 'speed';
powerUp.tint = 0x0088ff;
powerUp.value = 1;
}
game.addChild(powerUp);
gameState.powerUps.push(powerUp);
}
// Game start
function startGame() {
// Reset game state
gameState.score = 0;
gameState.level = 1;
gameState.arenaSize = 1800;
gameState.arenaShrinkTimer = gameState.arenaShrinkStart;
// Reset arena
arenaBackground.width = gameState.arenaSize;
arenaBackground.height = gameState.arenaSize;
arenaWalls.createWalls(gameState.arenaCenter.x, gameState.arenaCenter.y, gameState.arenaSize);
// Reset player
player.health = player.maxHealth;
player.isDead = false;
player.alpha = 1;
player.speed = 5;
player.attackDamage = 20;
// Spawn enemies
spawnEnemies();
// Update UI
scoreTxt.setText("Score: " + gameState.score);
levelTxt.setText("Level: " + gameState.level);
// Play music
LK.playMusic('battleMusic');
gameState.gameStarted = true;
}
// Handle touch/mouse events
var dragTarget = null;
function handleMove(x, y, obj) {
if (dragTarget && dragTarget.isDragging) {
// Update player position
dragTarget.x = x;
dragTarget.y = y;
// Calculate direction based on movement
var dx = x - dragTarget.lastPosition.x;
var dy = y - dragTarget.lastPosition.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0.1) {
dragTarget.direction.x = dx / dist;
dragTarget.direction.y = dy / dist;
}
}
}
game.down = function (x, y, obj) {
if (!gameState.gameStarted) {
startGame();
return;
}
dragTarget = player;
if (!player.isDead) {
player.isDragging = true;
handleMove(x, y, obj);
}
};
game.up = function (x, y, obj) {
if (dragTarget) {
dragTarget.isDragging = false;
dragTarget = null;
}
};
game.move = handleMove;
// Main game update loop
game.update = function () {
if (!gameState.gameStarted) {
return;
}
// Shrink arena over time
if (gameState.arenaShrinkTimer > 0) {
gameState.arenaShrinkTimer--;
if (gameState.arenaShrinkTimer === 300) {
// Show warning message
tween(arenaSizeTxt, {
alpha: 1
}, {
duration: 300,
onFinish: function onFinish() {
tween(arenaSizeTxt, {
alpha: 0
}, {
duration: 300
});
}
});
}
} else {
if (gameState.arenaSize > gameState.minArenaSize) {
gameState.arenaSize -= gameState.arenaShrinkRate;
// Update arena visuals
arenaBackground.width = gameState.arenaSize;
arenaBackground.height = gameState.arenaSize;
arenaWalls.createWalls(gameState.arenaCenter.x, gameState.arenaCenter.y, gameState.arenaSize);
}
}
// Spawn power-ups randomly
if (Math.random() < 0.005) {
spawnPowerUp();
}
// Player attack on tap/click/drag release
if (player.isDragging === false && dragTarget === null && !player.isDead) {
player.attack();
}
// Update projectiles and check collisions
for (var i = gameState.projectiles.length - 1; i >= 0; i--) {
var projectile = gameState.projectiles[i];
// Check if projectile is marked for removal
if (projectile.markForRemoval) {
game.removeChild(projectile);
gameState.projectiles.splice(i, 1);
continue;
}
// Check projectile collisions with enemies
if (projectile.owner === player) {
for (var j = 0; j < gameState.enemies.length; j++) {
var enemy = gameState.enemies[j];
if (!enemy.isDead && projectile.intersects(enemy)) {
enemy.takeDamage(player.attackDamage);
projectile.markForRemoval = true;
break;
}
}
}
// Check projectile collisions with player
else if (projectile.owner.isEnemy && !player.isDead && projectile.intersects(player)) {
player.takeDamage(projectile.owner.attackDamage);
projectile.markForRemoval = true;
}
// Check if projectile is out of arena bounds
var halfSize = gameState.arenaSize / 2;
if (projectile.x < gameState.arenaCenter.x - halfSize || projectile.x > gameState.arenaCenter.x + halfSize || projectile.y < gameState.arenaCenter.y - halfSize || projectile.y > gameState.arenaCenter.y + halfSize) {
projectile.markForRemoval = true;
}
}
// Check power-up collisions with player
for (var i = gameState.powerUps.length - 1; i >= 0; i--) {
var powerUp = gameState.powerUps[i];
if (!player.isDead && powerUp.intersects(player)) {
// Apply power-up effect
if (powerUp.type === 'health') {
player.health = Math.min(player.maxHealth, player.health + powerUp.value);
// Update health bar
var healthBar = player.getChildAt(2); // Health bar is at index 2
healthBar.width = player.health / player.maxHealth * 80;
} else if (powerUp.type === 'damage') {
player.attackDamage += powerUp.value;
} else if (powerUp.type === 'speed') {
player.speed += powerUp.value;
}
// Remove power-up
game.removeChild(powerUp);
gameState.powerUps.splice(i, 1);
// Play sound and add score
LK.getSound('powerup').play();
gameState.score += 50;
scoreTxt.setText("Score: " + gameState.score);
}
// Remove power-ups outside arena bounds
var halfSize = gameState.arenaSize / 2;
if (powerUp.x < gameState.arenaCenter.x - halfSize || powerUp.x > gameState.arenaCenter.x + halfSize || powerUp.y < gameState.arenaCenter.y - halfSize || powerUp.y > gameState.arenaCenter.y + halfSize) {
game.removeChild(powerUp);
gameState.powerUps.splice(i, 1);
}
}
// Check for level completion
if (gameState.defeatedEnemies >= gameState.totalEnemies) {
// Level up
gameState.level++;
levelTxt.setText("Level: " + gameState.level);
// Reset arena
gameState.arenaSize = 1800;
gameState.arenaShrinkTimer = gameState.arenaShrinkStart;
arenaBackground.width = gameState.arenaSize;
arenaBackground.height = gameState.arenaSize;
arenaWalls.createWalls(gameState.arenaCenter.x, gameState.arenaCenter.y, gameState.arenaSize);
// Spawn new enemies
spawnEnemies();
// Bonus score for completing level
gameState.score += 500;
scoreTxt.setText("Score: " + gameState.score);
// Clear old power-ups
for (var i = gameState.powerUps.length - 1; i >= 0; i--) {
game.removeChild(gameState.powerUps[i]);
gameState.powerUps.splice(i, 1);
}
}
// Check for game over (player dead)
if (player.isDead) {
// Update high score
if (gameState.score > storage.highScore) {
storage.highScore = gameState.score;
}
}
// Clean up dead enemies
for (var i = gameState.enemies.length - 1; i >= 0; i--) {
if (gameState.enemies[i].markForRemoval) {
game.removeChild(gameState.enemies[i]);
gameState.enemies.splice(i, 1);
}
}
};
// Start music
LK.playMusic('battleMusic', {
fade: {
start: 0,
end: 0.3,
duration: 1000
}
});
// Initial instruction text
var instructionText = new Text2("Tap to start!\nDrag to move, release to shoot", {
size: 60,
fill: 0xFFFFFF
});
instructionText.anchor.set(0.5, 0.5);
instructionText.x = gameState.arenaCenter.x;
instructionText.y = gameState.arenaCenter.y - 200;
game.addChild(instructionText);
// Hide instructions when game starts
var originalUpdate = game.update;
game.update = function () {
if (!gameState.gameStarted) {
// Make text pulse before game starts
instructionText.alpha = 0.5 + Math.sin(LK.ticks / 20) * 0.5;
return;
}
// Remove instruction text once when game starts
if (instructionText.parent) {
game.removeChild(instructionText);
}
// Call the original update
originalUpdate.call(game);
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0
});
/****
* Classes
****/
var ArenaWall = Container.expand(function () {
var self = Container.call(this);
self.wallGraphics = [];
self.createWalls = function (centerX, centerY, size) {
// Clear existing walls
for (var i = 0; i < self.wallGraphics.length; i++) {
self.removeChild(self.wallGraphics[i]);
}
self.wallGraphics = [];
// Calculate positions
var halfSize = size / 2;
var wallSize = 30;
var numWalls = Math.floor(size / wallSize);
// Top and bottom walls
for (var x = 0; x < numWalls; x++) {
var topWall = self.attachAsset('arenaWall', {
anchorX: 0.5,
anchorY: 0.5,
x: centerX - halfSize + (x + 0.5) * wallSize,
y: centerY - halfSize
});
self.wallGraphics.push(topWall);
var bottomWall = self.attachAsset('arenaWall', {
anchorX: 0.5,
anchorY: 0.5,
x: centerX - halfSize + (x + 0.5) * wallSize,
y: centerY + halfSize
});
self.wallGraphics.push(bottomWall);
}
// Left and right walls (skip corners)
for (var y = 1; y < numWalls - 1; y++) {
var leftWall = self.attachAsset('arenaWall', {
anchorX: 0.5,
anchorY: 0.5,
x: centerX - halfSize,
y: centerY - halfSize + (y + 0.5) * wallSize
});
self.wallGraphics.push(leftWall);
var rightWall = self.attachAsset('arenaWall', {
anchorX: 0.5,
anchorY: 0.5,
x: centerX + halfSize,
y: centerY - halfSize + (y + 0.5) * wallSize
});
self.wallGraphics.push(rightWall);
}
};
return self;
});
var Brawler = Container.expand(function () {
var self = Container.call(this);
// Character graphic
var graphic = self.attachAsset('brawler', {
anchorX: 0.5,
anchorY: 0.5
});
// Health bar background
var healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5,
y: -60
});
// Health bar
var healthBar = self.attachAsset('healthBar', {
anchorX: 0,
anchorY: 0.5,
y: -60,
x: -40
});
// Properties
self.maxHealth = 100;
self.health = self.maxHealth;
self.attackCooldown = 0;
self.attackCooldownMax = 20;
self.speed = 5;
self.isDragging = false;
self.direction = {
x: 0,
y: 0
};
self.isPlayer = false;
self.isEnemy = false;
self.attackDamage = 20;
self.lastPosition = {
x: 0,
y: 0
};
self.isDead = false;
// Movement AI for enemies
self.moveTowards = function (target, speed) {
if (!target || self.isDead) {
return;
}
var dx = target.x - self.x;
var dy = target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.direction.x = dx / distance;
self.direction.y = dy / distance;
self.x += self.direction.x * speed;
self.y += self.direction.y * speed;
}
};
// Attack method
self.attack = function () {
if (self.attackCooldown > 0 || self.isDead) {
return;
}
var projectile = new Projectile();
projectile.x = self.x;
projectile.y = self.y;
projectile.direction = {
x: self.direction.x || 1,
y: self.direction.y || 0
};
projectile.owner = self;
game.addChild(projectile);
gameState.projectiles.push(projectile);
self.attackCooldown = self.attackCooldownMax;
LK.getSound('shoot').play();
};
// Take damage method
self.takeDamage = function (amount) {
if (self.isDead) {
return;
}
self.health -= amount;
// Update health bar
healthBar.width = self.health / self.maxHealth * 80;
// Flash the character red
LK.effects.flashObject(graphic, 0xff0000, 200);
LK.getSound('hit').play();
if (self.health <= 0) {
self.die();
}
};
// Die method
self.die = function () {
if (self.isDead) {
return;
}
self.isDead = true;
tween(self, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
self.markForRemoval = true;
}
});
if (self.isPlayer) {
LK.showGameOver();
} else if (self.isEnemy) {
gameState.score += 100;
gameState.defeatedEnemies++;
scoreTxt.setText("Score: " + gameState.score);
if (gameState.defeatedEnemies >= gameState.totalEnemies) {
LK.showYouWin();
}
}
};
// Event handlers
self.down = function (x, y, obj) {
if (self.isPlayer && !self.isDead) {
self.isDragging = true;
}
};
self.up = function (x, y, obj) {
if (self.isPlayer) {
self.isDragging = false;
}
};
self.update = function () {
// Store last position
self.lastPosition.x = self.x;
self.lastPosition.y = self.y;
// Update cooldowns
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
// Keep within arena bounds
var arenaSize = gameState.arenaSize;
var halfSize = arenaSize / 2;
if (self.x < gameState.arenaCenter.x - halfSize + 50) {
self.x = gameState.arenaCenter.x - halfSize + 50;
}
if (self.x > gameState.arenaCenter.x + halfSize - 50) {
self.x = gameState.arenaCenter.x + halfSize - 50;
}
if (self.y < gameState.arenaCenter.y - halfSize + 50) {
self.y = gameState.arenaCenter.y - halfSize + 50;
}
if (self.y > gameState.arenaCenter.y + halfSize - 50) {
self.y = gameState.arenaCenter.y + halfSize - 50;
}
};
return self;
});
var Enemy = Brawler.expand(function () {
var self = Brawler.call(this);
// Override the default brawler graphic with enemy graphic
var graphic = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.isEnemy = true;
self.targetUpdateCooldown = 0;
self.currentTarget = null;
// Override the update method
var parentUpdate = self.update;
self.update = function () {
if (self.isDead) {
return;
}
// Base update from parent
parentUpdate();
// Update target every so often
if (self.targetUpdateCooldown <= 0) {
self.findNewTarget();
self.targetUpdateCooldown = 60; // Update target every 60 frames
} else {
self.targetUpdateCooldown--;
}
// Move towards target
if (self.currentTarget && !self.currentTarget.isDead) {
self.moveTowards(self.currentTarget, self.speed);
// Attack if close enough
var dx = self.currentTarget.x - self.x;
var dy = self.currentTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 300) {
// Set direction for projectile
self.direction.x = dx / distance;
self.direction.y = dy / distance;
// Attack with some randomness
if (Math.random() < 0.03) {
self.attack();
}
}
} else {
// If no target or target is dead, wander randomly
if (Math.random() < 0.02) {
self.direction.x = Math.random() * 2 - 1;
self.direction.y = Math.random() * 2 - 1;
var mag = Math.sqrt(self.direction.x * self.direction.x + self.direction.y * self.direction.y);
self.direction.x /= mag;
self.direction.y /= mag;
}
self.x += self.direction.x * self.speed / 2;
self.y += self.direction.y * self.speed / 2;
}
};
self.findNewTarget = function () {
// Target the player
if (gameState.player && !gameState.player.isDead) {
self.currentTarget = gameState.player;
return;
}
// No valid targets
self.currentTarget = null;
};
return self;
});
var PowerUp = Container.expand(function () {
var self = Container.call(this);
var graphic = self.attachAsset('powerUp', {
anchorX: 0.5,
anchorY: 0.5
});
self.type = 'health'; // health, damage, speed
self.value = 20;
// Visual effect - floating animation
self.floatAnim = function () {
tween(graphic, {
y: -5
}, {
duration: 1000,
easing: tween.sinceOut,
onFinish: function onFinish() {
tween(graphic, {
y: 5
}, {
duration: 1000,
easing: tween.sinceOut,
onFinish: self.floatAnim
});
}
});
};
self.floatAnim();
return self;
});
var Projectile = Container.expand(function () {
var self = Container.call(this);
var graphic = self.attachAsset('projectile', {
anchorX: 0.5,
anchorY: 0.5
});
self.direction = {
x: 0,
y: 0
};
self.speed = 12;
self.damage = 10;
self.lifetime = 60; // frames the projectile lives for
self.owner = null;
self.update = function () {
self.x += self.direction.x * self.speed;
self.y += self.direction.y * self.speed;
self.lifetime--;
if (self.lifetime <= 0) {
self.markForRemoval = true;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
// Game state tracking
var gameState = {
arenaCenter: {
x: 2048 / 2,
y: 2732 / 2
},
arenaSize: 1800,
minArenaSize: 1000,
arenaShrinkRate: 0.2,
arenaShrinkStart: 1000,
// ticks before arena starts shrinking
arenaShrinkTimer: 0,
projectiles: [],
powerUps: [],
enemies: [],
player: null,
score: 0,
level: 1,
defeatedEnemies: 0,
totalEnemies: 5,
gameStarted: false
};
// Create arena background
var arenaBackground = LK.getAsset('arena', {
anchorX: 0.5,
anchorY: 0.5,
x: gameState.arenaCenter.x,
y: gameState.arenaCenter.y,
width: gameState.arenaSize,
height: gameState.arenaSize,
alpha: 0.3
});
game.addChild(arenaBackground);
// Create arena walls
var arenaWalls = new ArenaWall();
game.addChild(arenaWalls);
arenaWalls.createWalls(gameState.arenaCenter.x, gameState.arenaCenter.y, gameState.arenaSize);
// Create player
var player = new Brawler();
player.x = gameState.arenaCenter.x;
player.y = gameState.arenaCenter.y + 300;
player.isPlayer = true;
game.addChild(player);
gameState.player = player;
// Setup GUI elements
var scoreTxt = new Text2("Score: 0", {
size: 50,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0, 0);
LK.gui.topRight.addChild(scoreTxt);
scoreTxt.x = -250;
scoreTxt.y = 50;
var levelTxt = new Text2("Level: 1", {
size: 50,
fill: 0xFFFFFF
});
levelTxt.anchor.set(0, 0);
LK.gui.topRight.addChild(levelTxt);
levelTxt.x = -250;
levelTxt.y = 120;
var arenaSizeTxt = new Text2("Arena Shrinking", {
size: 40,
fill: 0xFF9900
});
arenaSizeTxt.anchor.set(0.5, 0);
arenaSizeTxt.alpha = 0;
LK.gui.top.addChild(arenaSizeTxt);
arenaSizeTxt.y = 50;
// Initialize enemies
function spawnEnemies() {
// Clear existing enemies
for (var i = gameState.enemies.length - 1; i >= 0; i--) {
game.removeChild(gameState.enemies[i]);
gameState.enemies.splice(i, 1);
}
// Set number of enemies based on level
gameState.totalEnemies = 3 + gameState.level * 2;
gameState.defeatedEnemies = 0;
// Spawn new enemies
for (var i = 0; i < gameState.totalEnemies; i++) {
var enemy = new Enemy();
// Position around the arena
var angle = i / gameState.totalEnemies * Math.PI * 2;
var radius = gameState.arenaSize / 2 - 200;
enemy.x = gameState.arenaCenter.x + Math.cos(angle) * radius;
enemy.y = gameState.arenaCenter.y + Math.sin(angle) * radius;
// Scale difficulty with level
enemy.maxHealth = 80 + gameState.level * 20;
enemy.health = enemy.maxHealth;
enemy.speed = 3 + gameState.level * 0.5;
game.addChild(enemy);
gameState.enemies.push(enemy);
}
}
// Spawn power-ups periodically
function spawnPowerUp() {
if (gameState.powerUps.length >= 5) {
return;
} // Limit number of power-ups
var powerUp = new PowerUp();
// Random position within arena
var halfSize = gameState.arenaSize / 2 - 100;
powerUp.x = gameState.arenaCenter.x + Math.random() * halfSize * 2 - halfSize;
powerUp.y = gameState.arenaCenter.y + Math.random() * halfSize * 2 - halfSize;
// Random type
var typeRoll = Math.random();
if (typeRoll < 0.4) {
powerUp.type = 'health';
powerUp.tint = 0x00ff00;
} else if (typeRoll < 0.7) {
powerUp.type = 'damage';
powerUp.tint = 0xff0000;
powerUp.value = 10;
} else {
powerUp.type = 'speed';
powerUp.tint = 0x0088ff;
powerUp.value = 1;
}
game.addChild(powerUp);
gameState.powerUps.push(powerUp);
}
// Game start
function startGame() {
// Reset game state
gameState.score = 0;
gameState.level = 1;
gameState.arenaSize = 1800;
gameState.arenaShrinkTimer = gameState.arenaShrinkStart;
// Reset arena
arenaBackground.width = gameState.arenaSize;
arenaBackground.height = gameState.arenaSize;
arenaWalls.createWalls(gameState.arenaCenter.x, gameState.arenaCenter.y, gameState.arenaSize);
// Reset player
player.health = player.maxHealth;
player.isDead = false;
player.alpha = 1;
player.speed = 5;
player.attackDamage = 20;
// Spawn enemies
spawnEnemies();
// Update UI
scoreTxt.setText("Score: " + gameState.score);
levelTxt.setText("Level: " + gameState.level);
// Play music
LK.playMusic('battleMusic');
gameState.gameStarted = true;
}
// Handle touch/mouse events
var dragTarget = null;
function handleMove(x, y, obj) {
if (dragTarget && dragTarget.isDragging) {
// Update player position
dragTarget.x = x;
dragTarget.y = y;
// Calculate direction based on movement
var dx = x - dragTarget.lastPosition.x;
var dy = y - dragTarget.lastPosition.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0.1) {
dragTarget.direction.x = dx / dist;
dragTarget.direction.y = dy / dist;
}
}
}
game.down = function (x, y, obj) {
if (!gameState.gameStarted) {
startGame();
return;
}
dragTarget = player;
if (!player.isDead) {
player.isDragging = true;
handleMove(x, y, obj);
}
};
game.up = function (x, y, obj) {
if (dragTarget) {
dragTarget.isDragging = false;
dragTarget = null;
}
};
game.move = handleMove;
// Main game update loop
game.update = function () {
if (!gameState.gameStarted) {
return;
}
// Shrink arena over time
if (gameState.arenaShrinkTimer > 0) {
gameState.arenaShrinkTimer--;
if (gameState.arenaShrinkTimer === 300) {
// Show warning message
tween(arenaSizeTxt, {
alpha: 1
}, {
duration: 300,
onFinish: function onFinish() {
tween(arenaSizeTxt, {
alpha: 0
}, {
duration: 300
});
}
});
}
} else {
if (gameState.arenaSize > gameState.minArenaSize) {
gameState.arenaSize -= gameState.arenaShrinkRate;
// Update arena visuals
arenaBackground.width = gameState.arenaSize;
arenaBackground.height = gameState.arenaSize;
arenaWalls.createWalls(gameState.arenaCenter.x, gameState.arenaCenter.y, gameState.arenaSize);
}
}
// Spawn power-ups randomly
if (Math.random() < 0.005) {
spawnPowerUp();
}
// Player attack on tap/click/drag release
if (player.isDragging === false && dragTarget === null && !player.isDead) {
player.attack();
}
// Update projectiles and check collisions
for (var i = gameState.projectiles.length - 1; i >= 0; i--) {
var projectile = gameState.projectiles[i];
// Check if projectile is marked for removal
if (projectile.markForRemoval) {
game.removeChild(projectile);
gameState.projectiles.splice(i, 1);
continue;
}
// Check projectile collisions with enemies
if (projectile.owner === player) {
for (var j = 0; j < gameState.enemies.length; j++) {
var enemy = gameState.enemies[j];
if (!enemy.isDead && projectile.intersects(enemy)) {
enemy.takeDamage(player.attackDamage);
projectile.markForRemoval = true;
break;
}
}
}
// Check projectile collisions with player
else if (projectile.owner.isEnemy && !player.isDead && projectile.intersects(player)) {
player.takeDamage(projectile.owner.attackDamage);
projectile.markForRemoval = true;
}
// Check if projectile is out of arena bounds
var halfSize = gameState.arenaSize / 2;
if (projectile.x < gameState.arenaCenter.x - halfSize || projectile.x > gameState.arenaCenter.x + halfSize || projectile.y < gameState.arenaCenter.y - halfSize || projectile.y > gameState.arenaCenter.y + halfSize) {
projectile.markForRemoval = true;
}
}
// Check power-up collisions with player
for (var i = gameState.powerUps.length - 1; i >= 0; i--) {
var powerUp = gameState.powerUps[i];
if (!player.isDead && powerUp.intersects(player)) {
// Apply power-up effect
if (powerUp.type === 'health') {
player.health = Math.min(player.maxHealth, player.health + powerUp.value);
// Update health bar
var healthBar = player.getChildAt(2); // Health bar is at index 2
healthBar.width = player.health / player.maxHealth * 80;
} else if (powerUp.type === 'damage') {
player.attackDamage += powerUp.value;
} else if (powerUp.type === 'speed') {
player.speed += powerUp.value;
}
// Remove power-up
game.removeChild(powerUp);
gameState.powerUps.splice(i, 1);
// Play sound and add score
LK.getSound('powerup').play();
gameState.score += 50;
scoreTxt.setText("Score: " + gameState.score);
}
// Remove power-ups outside arena bounds
var halfSize = gameState.arenaSize / 2;
if (powerUp.x < gameState.arenaCenter.x - halfSize || powerUp.x > gameState.arenaCenter.x + halfSize || powerUp.y < gameState.arenaCenter.y - halfSize || powerUp.y > gameState.arenaCenter.y + halfSize) {
game.removeChild(powerUp);
gameState.powerUps.splice(i, 1);
}
}
// Check for level completion
if (gameState.defeatedEnemies >= gameState.totalEnemies) {
// Level up
gameState.level++;
levelTxt.setText("Level: " + gameState.level);
// Reset arena
gameState.arenaSize = 1800;
gameState.arenaShrinkTimer = gameState.arenaShrinkStart;
arenaBackground.width = gameState.arenaSize;
arenaBackground.height = gameState.arenaSize;
arenaWalls.createWalls(gameState.arenaCenter.x, gameState.arenaCenter.y, gameState.arenaSize);
// Spawn new enemies
spawnEnemies();
// Bonus score for completing level
gameState.score += 500;
scoreTxt.setText("Score: " + gameState.score);
// Clear old power-ups
for (var i = gameState.powerUps.length - 1; i >= 0; i--) {
game.removeChild(gameState.powerUps[i]);
gameState.powerUps.splice(i, 1);
}
}
// Check for game over (player dead)
if (player.isDead) {
// Update high score
if (gameState.score > storage.highScore) {
storage.highScore = gameState.score;
}
}
// Clean up dead enemies
for (var i = gameState.enemies.length - 1; i >= 0; i--) {
if (gameState.enemies[i].markForRemoval) {
game.removeChild(gameState.enemies[i]);
gameState.enemies.splice(i, 1);
}
}
};
// Start music
LK.playMusic('battleMusic', {
fade: {
start: 0,
end: 0.3,
duration: 1000
}
});
// Initial instruction text
var instructionText = new Text2("Tap to start!\nDrag to move, release to shoot", {
size: 60,
fill: 0xFFFFFF
});
instructionText.anchor.set(0.5, 0.5);
instructionText.x = gameState.arenaCenter.x;
instructionText.y = gameState.arenaCenter.y - 200;
game.addChild(instructionText);
// Hide instructions when game starts
var originalUpdate = game.update;
game.update = function () {
if (!gameState.gameStarted) {
// Make text pulse before game starts
instructionText.alpha = 0.5 + Math.sin(LK.ticks / 20) * 0.5;
return;
}
// Remove instruction text once when game starts
if (instructionText.parent) {
game.removeChild(instructionText);
}
// Call the original update
originalUpdate.call(game);
};