/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// AreaAttack class (visual only, handles hit in game code)
var AreaAttack = Container.expand(function () {
var self = Container.call(this);
var areaGfx = self.attachAsset('areaAttack', {
anchorX: 0.5,
anchorY: 0.5
});
areaGfx.alpha = 0.4;
self.update = function () {};
return self;
});
// Boss class (player)
var Boss = Container.expand(function () {
var self = Container.call(this);
var bossGfx = self.attachAsset('boss', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = bossGfx.width * 0.5;
self.cooldowns = {
area: 0,
shield: 0,
trap: 0
};
self.shieldActive = false;
self.shieldTimer = 0;
self.update = function () {
// Cooldown timers
if (self.cooldowns.area > 0) self.cooldowns.area--;
if (self.cooldowns.shield > 0) self.cooldowns.shield--;
if (self.cooldowns.trap > 0) self.cooldowns.trap--;
if (self.shieldActive) {
self.shieldTimer--;
if (self.shieldTimer <= 0) {
self.shieldActive = false;
if (self.shieldObj) {
self.shieldObj.destroy();
self.shieldObj = null;
}
}
}
};
return self;
});
// Hero class (basic, blue, straight movement)
var Hero = Container.expand(function () {
var self = Container.call(this);
var heroGfx = self.attachAsset('hero', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 6 + Math.random() * 2;
self.hp = 1;
self.type = 1;
self.update = function () {
// Move toward core
var dx = core.x - self.x;
var dy = core.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
}
};
return self;
});
// Hero2 class (green, zigzag movement)
var Hero2 = Container.expand(function () {
var self = Container.call(this);
var heroGfx = self.attachAsset('hero2', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 5 + Math.random() * 2;
self.hp = 2;
self.type = 2;
self.zigzagAngle = Math.random() * Math.PI * 2;
self.update = function () {
// Zigzag toward core
var dx = core.x - self.x;
var dy = core.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
var angle = Math.atan2(dy, dx);
// Add zigzag
self.zigzagAngle += 0.15;
var zigzag = Math.sin(self.zigzagAngle) * 0.5;
angle += zigzag;
self.x += Math.cos(angle) * self.speed;
self.y += Math.sin(angle) * self.speed;
}
};
return self;
});
// Hero3 class (red, fast, pauses randomly)
var Hero3 = Container.expand(function () {
var self = Container.call(this);
var heroGfx = self.attachAsset('hero3', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8 + Math.random() * 2;
self.hp = 1;
self.type = 3;
self.pauseTimer = 0;
self.update = function () {
if (self.pauseTimer > 0) {
self.pauseTimer--;
return;
}
// Randomly pause
if (Math.random() < 0.01) {
self.pauseTimer = 30 + Math.floor(Math.random() * 30);
return;
}
// Move toward core
var dx = core.x - self.x;
var dy = core.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
}
};
return self;
});
// Shield class (visual only)
var Shield = Container.expand(function () {
var self = Container.call(this);
var shieldGfx = self.attachAsset('shield', {
anchorX: 0.5,
anchorY: 0.5
});
shieldGfx.alpha = 0.25;
self.update = function () {};
return self;
});
// Trap class (placed by boss, destroys hero on contact)
var Trap = Container.expand(function () {
var self = Container.call(this);
var trapGfx = self.attachAsset('trap', {
anchorX: 0.5,
anchorY: 0.5
});
self.timer = 240; // 4 seconds
self.update = function () {
self.timer--;
if (self.timer <= 0) {
self.destroy();
for (var i = 0; i < traps.length; ++i) {
if (traps[i] === self) {
traps.splice(i, 1);
break;
}
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Add dungeon background image as a changeable background
var dungeonBg = LK.getAsset('dungeon_bg_001', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.addChild(dungeonBg);
// Center positions
// Boss (player) asset: large ellipse, purple
// Core asset: smaller ellipse, gold
// Hero asset: rectangle, blue
// Hero2 asset: rectangle, green (for variety)
// Hero3 asset: rectangle, red (for variety)
// Area attack asset: large semi-transparent ellipse, orange
// Shield asset: large semi-transparent ellipse, cyan
// Trap asset: small ellipse, dark gray
// Sound for area attack
var centerX = 2048 / 2;
var centerY = 2732 / 2;
// Core removed: no core asset or object in the middle
// Instead, define a virtual core position for heroes to target
var core = {
x: centerX,
y: centerY + 500
};
// Boss (player)
var boss = new Boss();
boss.x = centerX;
boss.y = centerY + 900;
game.addChild(boss);
// Boss health
var BOSS_MAX_HP = 10;
var bossHp = BOSS_MAX_HP;
// Boss life bar (simple rectangle)
var bossHpBarBg = LK.getAsset('hero', {
anchorX: 0.5,
anchorY: 0.5
});
bossHpBarBg.width = 400;
bossHpBarBg.height = 40;
bossHpBarBg.tint = 0x222222;
bossHpBarBg.alpha = 0.7;
bossHpBarBg.x = boss.x;
bossHpBarBg.y = boss.y - boss.radius - 60;
game.addChild(bossHpBarBg);
var bossHpBar = LK.getAsset('hero', {
anchorX: 0.5,
anchorY: 0.5
});
bossHpBar.width = 390;
bossHpBar.height = 28;
bossHpBar.tint = 0x43e97b;
bossHpBar.x = boss.x;
bossHpBar.y = boss.y - boss.radius - 60;
game.addChild(bossHpBar);
// Score display
var scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Arrays for game objects
var heroes = [];
var traps = [];
var areaAttacks = [];
// Dragging boss
var dragNode = null;
// Ability state
var abilityMode = null; // null, 'area', 'shield', 'trap'
var areaAttackObj = null;
// Ability cooldowns (frames)
var AREA_COOLDOWN = 360; // 6s
var SHIELD_COOLDOWN = 540; // 9s
var TRAP_COOLDOWN = 180; // 3s
// Ability durations
var SHIELD_DURATION = 180; // 3s
// Area attack radius
var AREA_RADIUS = 300;
// Shield radius
var SHIELD_RADIUS = 200;
// Trap radius
var TRAP_RADIUS = 60;
// --- Ability Icon Buttons (replace text buttons) ---
// Area Attack Icon Button
var areaBtnIcon = LK.getAsset('areaAttack', {
anchorX: 0.5,
anchorY: 0.5
});
areaBtnIcon.width = 160;
areaBtnIcon.height = 160;
LK.gui.bottom.addChild(areaBtnIcon);
areaBtnIcon.x = -400;
areaBtnIcon.y = -100;
// Shield Icon Button
var shieldBtnIcon = LK.getAsset('shield', {
anchorX: 0.5,
anchorY: 0.5
});
shieldBtnIcon.width = 160;
shieldBtnIcon.height = 160;
LK.gui.bottom.addChild(shieldBtnIcon);
shieldBtnIcon.x = 0;
shieldBtnIcon.y = -100;
// Trap Icon Button
var trapBtnIcon = LK.getAsset('trap', {
anchorX: 0.5,
anchorY: 0.5
});
trapBtnIcon.width = 160;
trapBtnIcon.height = 160;
LK.gui.bottom.addChild(trapBtnIcon);
trapBtnIcon.x = 400;
trapBtnIcon.y = -100;
// Add a semi-transparent overlay for cooldown effect for each button
function makeCooldownOverlay(width, height) {
var overlay = LK.getAsset('hero', {
anchorX: 0.5,
anchorY: 0.5
});
overlay.width = width;
overlay.height = height;
overlay.tint = 0x222222;
overlay.alpha = 0.6;
return overlay;
}
var areaBtnOverlay = makeCooldownOverlay(160, 160);
var shieldBtnOverlay = makeCooldownOverlay(160, 160);
var trapBtnOverlay = makeCooldownOverlay(160, 160);
LK.gui.bottom.addChild(areaBtnOverlay);
LK.gui.bottom.addChild(shieldBtnOverlay);
LK.gui.bottom.addChild(trapBtnOverlay);
areaBtnOverlay.x = areaBtnIcon.x;
areaBtnOverlay.y = areaBtnIcon.y;
shieldBtnOverlay.x = shieldBtnIcon.x;
shieldBtnOverlay.y = shieldBtnIcon.y;
trapBtnOverlay.x = trapBtnIcon.x;
trapBtnOverlay.y = trapBtnIcon.y;
// Add a small text label under each icon
var areaBtnLabel = new Text2('Area', {
size: 48,
fill: "#fff"
});
areaBtnLabel.anchor.set(0.5, 0);
LK.gui.bottom.addChild(areaBtnLabel);
areaBtnLabel.x = areaBtnIcon.x;
areaBtnLabel.y = areaBtnIcon.y + 90;
var shieldBtnLabel = new Text2('Shield', {
size: 48,
fill: "#fff"
});
shieldBtnLabel.anchor.set(0.5, 0);
LK.gui.bottom.addChild(shieldBtnLabel);
shieldBtnLabel.x = shieldBtnIcon.x;
shieldBtnLabel.y = shieldBtnIcon.y + 90;
var trapBtnLabel = new Text2('Trap', {
size: 48,
fill: "#fff"
});
trapBtnLabel.anchor.set(0.5, 0);
LK.gui.bottom.addChild(trapBtnLabel);
trapBtnLabel.x = trapBtnIcon.x;
trapBtnLabel.y = trapBtnIcon.y + 90;
// Ability button handlers (icon down events)
areaBtnIcon.down = function (x, y, obj) {
useAreaAttack();
};
shieldBtnIcon.down = function (x, y, obj) {
useShield();
};
trapBtnIcon.down = function (x, y, obj) {
useTrap();
};
// Helper: check if two objects intersect (circle-based)
function circlesIntersect(a, rA, b, rB) {
var dx = a.x - b.x;
var dy = a.y - b.y;
var dist = Math.sqrt(dx * dx + dy * dy);
return dist < rA + rB;
}
// Helper: spawn a hero at random edge, and make them stronger as more spawn
var heroSpawnCount = 0;
function spawnHero() {
var edge = Math.floor(Math.random() * 4);
var x, y;
if (edge === 0) {
// Top
x = 200 + Math.random() * (2048 - 400);
y = -100;
} else if (edge === 1) {
// Bottom
x = 200 + Math.random() * (2048 - 400);
y = 2732 + 100;
} else if (edge === 2) {
// Left
x = -100;
y = 400 + Math.random() * (2732 - 800);
} else {
// Right
x = 2048 + 100;
y = 400 + Math.random() * (2732 - 800);
}
// Only spawn one hero at a time (no batch spawn)
// Make each new hero a little stronger
heroSpawnCount++;
var t = Math.random();
var hero;
if (t < 0.5) {
hero = new Hero();
} else if (t < 0.8) {
hero = new Hero2();
} else {
hero = new Hero3();
}
// Increase hero stats as spawn count increases
var hpBoost = Math.floor(heroSpawnCount / 10);
var speedBoost = Math.min(heroSpawnCount * 0.05, 3);
hero.hp += hpBoost;
hero.speed += speedBoost;
hero.x = x;
hero.y = y;
hero.lastIntersectingCore = false;
hero.lastIntersectingBoss = false;
hero.lastIntersectingTrap = false;
heroes.push(hero);
game.addChild(hero);
}
// Ability: Area attack
function useAreaAttack() {
if (boss.cooldowns.area > 0) return;
boss.cooldowns.area = AREA_COOLDOWN;
// Visual
areaAttackObj = new AreaAttack();
areaAttackObj.x = boss.x;
areaAttackObj.y = boss.y;
areaAttackObj.scaleX = 0.1;
areaAttackObj.scaleY = 0.1;
areaAttackObj.alpha = 0.5;
game.addChild(areaAttackObj);
areaAttacks.push(areaAttackObj);
// Animate
tween(areaAttackObj, {
scaleX: 1,
scaleY: 1,
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
if (areaAttackObj) {
areaAttackObj.destroy();
var idx = areaAttacks.indexOf(areaAttackObj);
if (idx !== -1) areaAttacks.splice(idx, 1);
areaAttackObj = null;
}
}
});
// Hit all heroes in radius
for (var i = heroes.length - 1; i >= 0; --i) {
var h = heroes[i];
if (circlesIntersect(boss, AREA_RADIUS, h, 60)) {
h.hp -= 1;
LK.getSound('area').play();
LK.effects.flashObject(h, 0xff9800, 300);
if (h.hp <= 0) {
LK.setScore(LK.getScore() + 1);
scoreTxt.setText(LK.getScore());
LK.getSound('heroHit').play();
h.destroy();
heroes.splice(i, 1);
}
}
}
}
// Ability: Shield
function useShield() {
if (boss.cooldowns.shield > 0) return;
boss.cooldowns.shield = SHIELD_COOLDOWN;
boss.shieldActive = true;
boss.shieldTimer = SHIELD_DURATION;
if (boss.shieldObj) boss.shieldObj.destroy();
boss.shieldObj = new Shield();
boss.shieldObj.x = boss.x;
boss.shieldObj.y = boss.y;
game.addChild(boss.shieldObj);
LK.getSound('shield').play();
}
// Ability: Trap
function useTrap() {
if (boss.cooldowns.trap > 0) return;
boss.cooldowns.trap = TRAP_COOLDOWN;
// Place trap at boss position
var trap = new Trap();
trap.x = boss.x;
trap.y = boss.y;
traps.push(trap);
game.addChild(trap);
LK.getSound('trap').play();
}
// Ability button handlers (handled by icon event handlers above)
// Dragging boss
function handleMove(x, y, obj) {
if (dragNode) {
// Clamp boss inside game area
var minX = boss.radius + 40;
var maxX = 2048 - boss.radius - 40;
var minY = boss.radius + 200;
var maxY = 2732 - boss.radius - 40;
boss.x = Math.max(minX, Math.min(maxX, x));
boss.y = Math.max(minY, Math.min(maxY, y));
if (boss.shieldObj) {
boss.shieldObj.x = boss.x;
boss.shieldObj.y = boss.y;
}
}
}
game.move = handleMove;
game.down = function (x, y, obj) {
// Only drag if touch or mouse is on boss
var dx = x - boss.x;
var dy = y - boss.y;
if (dx * dx + dy * dy < boss.radius * boss.radius) {
dragNode = boss;
handleMove(x, y, obj);
}
};
game.up = function (x, y, obj) {
dragNode = null;
};
// Main game update
game.update = function () {
// Update boss
boss.update();
// Update boss life bar position
bossHpBarBg.x = boss.x;
bossHpBarBg.y = boss.y - boss.radius - 60;
bossHpBar.x = boss.x;
bossHpBar.y = boss.y - boss.radius - 60;
// Update ability icon overlays to show cooldown visually
function updateAbilityOverlay(overlay, cooldown, maxCooldown) {
if (cooldown <= 0) {
overlay.alpha = 0;
} else {
// Show overlay, alpha increases as cooldown is higher
overlay.alpha = 0.6;
// Visually show cooldown as a darken effect (simulate by scaling Y)
var frac = Math.max(0, Math.min(1, cooldown / maxCooldown));
overlay.scaleY = frac;
overlay.y = overlay._baseY !== undefined ? overlay._baseY : overlay.y;
overlay.y = (overlay._baseY = overlay.y) - (1 - frac) * (overlay.height / 2);
}
}
updateAbilityOverlay(areaBtnOverlay, boss.cooldowns.area, AREA_COOLDOWN);
updateAbilityOverlay(shieldBtnOverlay, boss.cooldowns.shield, SHIELD_COOLDOWN);
updateAbilityOverlay(trapBtnOverlay, boss.cooldowns.trap, TRAP_COOLDOWN);
// Update heroes
for (var i = heroes.length - 1; i >= 0; --i) {
var h = heroes[i];
h.update();
// Check collision with shield
if (boss.shieldActive && boss.shieldObj && circlesIntersect(boss, SHIELD_RADIUS, h, 60)) {
// Repel hero
var dx = h.x - boss.x;
var dy = h.y - boss.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
h.x += dx / dist * 40;
h.y += dy / dist * 40;
}
LK.effects.flashObject(h, 0x00e5ff, 200);
}
// Check collision with traps
for (var t = traps.length - 1; t >= 0; --t) {
var trap = traps[t];
if (circlesIntersect(h, 60, trap, TRAP_RADIUS)) {
LK.setScore(LK.getScore() + 1);
scoreTxt.setText(LK.getScore());
LK.getSound('heroHit').play();
h.destroy();
heroes.splice(i, 1);
trap.destroy();
traps.splice(t, 1);
break;
}
}
// Check collision with boss (boss takes damage)
var nowIntersectingBoss = circlesIntersect(h, 60, boss, boss.radius);
if (!h.lastIntersectingBoss && nowIntersectingBoss) {
bossHp--;
// Flash boss
LK.effects.flashObject(boss, 0xff0000, 400);
// Remove hero
h.destroy();
heroes.splice(i, 1);
// Update boss life bar
var hpFrac = Math.max(0, bossHp) / BOSS_MAX_HP;
bossHpBar.width = 390 * hpFrac;
if (bossHp <= 0) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
return;
}
}
h.lastIntersectingBoss = nowIntersectingBoss;
}
// Update traps
for (var i = traps.length - 1; i >= 0; --i) {
traps[i].update();
}
// Remove area attack visuals if needed
for (var i = areaAttacks.length - 1; i >= 0; --i) {
// No update needed, handled by tween
}
// Spawn heroes
// Only spawn a new hero if there are no heroes currently on screen
if (heroes.length === 0 && LK.ticks % 15 === 0) {
spawnHero();
}
// Win condition: survive 60 heroes
if (LK.getScore() >= 60) {
LK.showYouWin();
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// AreaAttack class (visual only, handles hit in game code)
var AreaAttack = Container.expand(function () {
var self = Container.call(this);
var areaGfx = self.attachAsset('areaAttack', {
anchorX: 0.5,
anchorY: 0.5
});
areaGfx.alpha = 0.4;
self.update = function () {};
return self;
});
// Boss class (player)
var Boss = Container.expand(function () {
var self = Container.call(this);
var bossGfx = self.attachAsset('boss', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = bossGfx.width * 0.5;
self.cooldowns = {
area: 0,
shield: 0,
trap: 0
};
self.shieldActive = false;
self.shieldTimer = 0;
self.update = function () {
// Cooldown timers
if (self.cooldowns.area > 0) self.cooldowns.area--;
if (self.cooldowns.shield > 0) self.cooldowns.shield--;
if (self.cooldowns.trap > 0) self.cooldowns.trap--;
if (self.shieldActive) {
self.shieldTimer--;
if (self.shieldTimer <= 0) {
self.shieldActive = false;
if (self.shieldObj) {
self.shieldObj.destroy();
self.shieldObj = null;
}
}
}
};
return self;
});
// Hero class (basic, blue, straight movement)
var Hero = Container.expand(function () {
var self = Container.call(this);
var heroGfx = self.attachAsset('hero', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 6 + Math.random() * 2;
self.hp = 1;
self.type = 1;
self.update = function () {
// Move toward core
var dx = core.x - self.x;
var dy = core.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
}
};
return self;
});
// Hero2 class (green, zigzag movement)
var Hero2 = Container.expand(function () {
var self = Container.call(this);
var heroGfx = self.attachAsset('hero2', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 5 + Math.random() * 2;
self.hp = 2;
self.type = 2;
self.zigzagAngle = Math.random() * Math.PI * 2;
self.update = function () {
// Zigzag toward core
var dx = core.x - self.x;
var dy = core.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
var angle = Math.atan2(dy, dx);
// Add zigzag
self.zigzagAngle += 0.15;
var zigzag = Math.sin(self.zigzagAngle) * 0.5;
angle += zigzag;
self.x += Math.cos(angle) * self.speed;
self.y += Math.sin(angle) * self.speed;
}
};
return self;
});
// Hero3 class (red, fast, pauses randomly)
var Hero3 = Container.expand(function () {
var self = Container.call(this);
var heroGfx = self.attachAsset('hero3', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8 + Math.random() * 2;
self.hp = 1;
self.type = 3;
self.pauseTimer = 0;
self.update = function () {
if (self.pauseTimer > 0) {
self.pauseTimer--;
return;
}
// Randomly pause
if (Math.random() < 0.01) {
self.pauseTimer = 30 + Math.floor(Math.random() * 30);
return;
}
// Move toward core
var dx = core.x - self.x;
var dy = core.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
}
};
return self;
});
// Shield class (visual only)
var Shield = Container.expand(function () {
var self = Container.call(this);
var shieldGfx = self.attachAsset('shield', {
anchorX: 0.5,
anchorY: 0.5
});
shieldGfx.alpha = 0.25;
self.update = function () {};
return self;
});
// Trap class (placed by boss, destroys hero on contact)
var Trap = Container.expand(function () {
var self = Container.call(this);
var trapGfx = self.attachAsset('trap', {
anchorX: 0.5,
anchorY: 0.5
});
self.timer = 240; // 4 seconds
self.update = function () {
self.timer--;
if (self.timer <= 0) {
self.destroy();
for (var i = 0; i < traps.length; ++i) {
if (traps[i] === self) {
traps.splice(i, 1);
break;
}
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Add dungeon background image as a changeable background
var dungeonBg = LK.getAsset('dungeon_bg_001', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.addChild(dungeonBg);
// Center positions
// Boss (player) asset: large ellipse, purple
// Core asset: smaller ellipse, gold
// Hero asset: rectangle, blue
// Hero2 asset: rectangle, green (for variety)
// Hero3 asset: rectangle, red (for variety)
// Area attack asset: large semi-transparent ellipse, orange
// Shield asset: large semi-transparent ellipse, cyan
// Trap asset: small ellipse, dark gray
// Sound for area attack
var centerX = 2048 / 2;
var centerY = 2732 / 2;
// Core removed: no core asset or object in the middle
// Instead, define a virtual core position for heroes to target
var core = {
x: centerX,
y: centerY + 500
};
// Boss (player)
var boss = new Boss();
boss.x = centerX;
boss.y = centerY + 900;
game.addChild(boss);
// Boss health
var BOSS_MAX_HP = 10;
var bossHp = BOSS_MAX_HP;
// Boss life bar (simple rectangle)
var bossHpBarBg = LK.getAsset('hero', {
anchorX: 0.5,
anchorY: 0.5
});
bossHpBarBg.width = 400;
bossHpBarBg.height = 40;
bossHpBarBg.tint = 0x222222;
bossHpBarBg.alpha = 0.7;
bossHpBarBg.x = boss.x;
bossHpBarBg.y = boss.y - boss.radius - 60;
game.addChild(bossHpBarBg);
var bossHpBar = LK.getAsset('hero', {
anchorX: 0.5,
anchorY: 0.5
});
bossHpBar.width = 390;
bossHpBar.height = 28;
bossHpBar.tint = 0x43e97b;
bossHpBar.x = boss.x;
bossHpBar.y = boss.y - boss.radius - 60;
game.addChild(bossHpBar);
// Score display
var scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Arrays for game objects
var heroes = [];
var traps = [];
var areaAttacks = [];
// Dragging boss
var dragNode = null;
// Ability state
var abilityMode = null; // null, 'area', 'shield', 'trap'
var areaAttackObj = null;
// Ability cooldowns (frames)
var AREA_COOLDOWN = 360; // 6s
var SHIELD_COOLDOWN = 540; // 9s
var TRAP_COOLDOWN = 180; // 3s
// Ability durations
var SHIELD_DURATION = 180; // 3s
// Area attack radius
var AREA_RADIUS = 300;
// Shield radius
var SHIELD_RADIUS = 200;
// Trap radius
var TRAP_RADIUS = 60;
// --- Ability Icon Buttons (replace text buttons) ---
// Area Attack Icon Button
var areaBtnIcon = LK.getAsset('areaAttack', {
anchorX: 0.5,
anchorY: 0.5
});
areaBtnIcon.width = 160;
areaBtnIcon.height = 160;
LK.gui.bottom.addChild(areaBtnIcon);
areaBtnIcon.x = -400;
areaBtnIcon.y = -100;
// Shield Icon Button
var shieldBtnIcon = LK.getAsset('shield', {
anchorX: 0.5,
anchorY: 0.5
});
shieldBtnIcon.width = 160;
shieldBtnIcon.height = 160;
LK.gui.bottom.addChild(shieldBtnIcon);
shieldBtnIcon.x = 0;
shieldBtnIcon.y = -100;
// Trap Icon Button
var trapBtnIcon = LK.getAsset('trap', {
anchorX: 0.5,
anchorY: 0.5
});
trapBtnIcon.width = 160;
trapBtnIcon.height = 160;
LK.gui.bottom.addChild(trapBtnIcon);
trapBtnIcon.x = 400;
trapBtnIcon.y = -100;
// Add a semi-transparent overlay for cooldown effect for each button
function makeCooldownOverlay(width, height) {
var overlay = LK.getAsset('hero', {
anchorX: 0.5,
anchorY: 0.5
});
overlay.width = width;
overlay.height = height;
overlay.tint = 0x222222;
overlay.alpha = 0.6;
return overlay;
}
var areaBtnOverlay = makeCooldownOverlay(160, 160);
var shieldBtnOverlay = makeCooldownOverlay(160, 160);
var trapBtnOverlay = makeCooldownOverlay(160, 160);
LK.gui.bottom.addChild(areaBtnOverlay);
LK.gui.bottom.addChild(shieldBtnOverlay);
LK.gui.bottom.addChild(trapBtnOverlay);
areaBtnOverlay.x = areaBtnIcon.x;
areaBtnOverlay.y = areaBtnIcon.y;
shieldBtnOverlay.x = shieldBtnIcon.x;
shieldBtnOverlay.y = shieldBtnIcon.y;
trapBtnOverlay.x = trapBtnIcon.x;
trapBtnOverlay.y = trapBtnIcon.y;
// Add a small text label under each icon
var areaBtnLabel = new Text2('Area', {
size: 48,
fill: "#fff"
});
areaBtnLabel.anchor.set(0.5, 0);
LK.gui.bottom.addChild(areaBtnLabel);
areaBtnLabel.x = areaBtnIcon.x;
areaBtnLabel.y = areaBtnIcon.y + 90;
var shieldBtnLabel = new Text2('Shield', {
size: 48,
fill: "#fff"
});
shieldBtnLabel.anchor.set(0.5, 0);
LK.gui.bottom.addChild(shieldBtnLabel);
shieldBtnLabel.x = shieldBtnIcon.x;
shieldBtnLabel.y = shieldBtnIcon.y + 90;
var trapBtnLabel = new Text2('Trap', {
size: 48,
fill: "#fff"
});
trapBtnLabel.anchor.set(0.5, 0);
LK.gui.bottom.addChild(trapBtnLabel);
trapBtnLabel.x = trapBtnIcon.x;
trapBtnLabel.y = trapBtnIcon.y + 90;
// Ability button handlers (icon down events)
areaBtnIcon.down = function (x, y, obj) {
useAreaAttack();
};
shieldBtnIcon.down = function (x, y, obj) {
useShield();
};
trapBtnIcon.down = function (x, y, obj) {
useTrap();
};
// Helper: check if two objects intersect (circle-based)
function circlesIntersect(a, rA, b, rB) {
var dx = a.x - b.x;
var dy = a.y - b.y;
var dist = Math.sqrt(dx * dx + dy * dy);
return dist < rA + rB;
}
// Helper: spawn a hero at random edge, and make them stronger as more spawn
var heroSpawnCount = 0;
function spawnHero() {
var edge = Math.floor(Math.random() * 4);
var x, y;
if (edge === 0) {
// Top
x = 200 + Math.random() * (2048 - 400);
y = -100;
} else if (edge === 1) {
// Bottom
x = 200 + Math.random() * (2048 - 400);
y = 2732 + 100;
} else if (edge === 2) {
// Left
x = -100;
y = 400 + Math.random() * (2732 - 800);
} else {
// Right
x = 2048 + 100;
y = 400 + Math.random() * (2732 - 800);
}
// Only spawn one hero at a time (no batch spawn)
// Make each new hero a little stronger
heroSpawnCount++;
var t = Math.random();
var hero;
if (t < 0.5) {
hero = new Hero();
} else if (t < 0.8) {
hero = new Hero2();
} else {
hero = new Hero3();
}
// Increase hero stats as spawn count increases
var hpBoost = Math.floor(heroSpawnCount / 10);
var speedBoost = Math.min(heroSpawnCount * 0.05, 3);
hero.hp += hpBoost;
hero.speed += speedBoost;
hero.x = x;
hero.y = y;
hero.lastIntersectingCore = false;
hero.lastIntersectingBoss = false;
hero.lastIntersectingTrap = false;
heroes.push(hero);
game.addChild(hero);
}
// Ability: Area attack
function useAreaAttack() {
if (boss.cooldowns.area > 0) return;
boss.cooldowns.area = AREA_COOLDOWN;
// Visual
areaAttackObj = new AreaAttack();
areaAttackObj.x = boss.x;
areaAttackObj.y = boss.y;
areaAttackObj.scaleX = 0.1;
areaAttackObj.scaleY = 0.1;
areaAttackObj.alpha = 0.5;
game.addChild(areaAttackObj);
areaAttacks.push(areaAttackObj);
// Animate
tween(areaAttackObj, {
scaleX: 1,
scaleY: 1,
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
if (areaAttackObj) {
areaAttackObj.destroy();
var idx = areaAttacks.indexOf(areaAttackObj);
if (idx !== -1) areaAttacks.splice(idx, 1);
areaAttackObj = null;
}
}
});
// Hit all heroes in radius
for (var i = heroes.length - 1; i >= 0; --i) {
var h = heroes[i];
if (circlesIntersect(boss, AREA_RADIUS, h, 60)) {
h.hp -= 1;
LK.getSound('area').play();
LK.effects.flashObject(h, 0xff9800, 300);
if (h.hp <= 0) {
LK.setScore(LK.getScore() + 1);
scoreTxt.setText(LK.getScore());
LK.getSound('heroHit').play();
h.destroy();
heroes.splice(i, 1);
}
}
}
}
// Ability: Shield
function useShield() {
if (boss.cooldowns.shield > 0) return;
boss.cooldowns.shield = SHIELD_COOLDOWN;
boss.shieldActive = true;
boss.shieldTimer = SHIELD_DURATION;
if (boss.shieldObj) boss.shieldObj.destroy();
boss.shieldObj = new Shield();
boss.shieldObj.x = boss.x;
boss.shieldObj.y = boss.y;
game.addChild(boss.shieldObj);
LK.getSound('shield').play();
}
// Ability: Trap
function useTrap() {
if (boss.cooldowns.trap > 0) return;
boss.cooldowns.trap = TRAP_COOLDOWN;
// Place trap at boss position
var trap = new Trap();
trap.x = boss.x;
trap.y = boss.y;
traps.push(trap);
game.addChild(trap);
LK.getSound('trap').play();
}
// Ability button handlers (handled by icon event handlers above)
// Dragging boss
function handleMove(x, y, obj) {
if (dragNode) {
// Clamp boss inside game area
var minX = boss.radius + 40;
var maxX = 2048 - boss.radius - 40;
var minY = boss.radius + 200;
var maxY = 2732 - boss.radius - 40;
boss.x = Math.max(minX, Math.min(maxX, x));
boss.y = Math.max(minY, Math.min(maxY, y));
if (boss.shieldObj) {
boss.shieldObj.x = boss.x;
boss.shieldObj.y = boss.y;
}
}
}
game.move = handleMove;
game.down = function (x, y, obj) {
// Only drag if touch or mouse is on boss
var dx = x - boss.x;
var dy = y - boss.y;
if (dx * dx + dy * dy < boss.radius * boss.radius) {
dragNode = boss;
handleMove(x, y, obj);
}
};
game.up = function (x, y, obj) {
dragNode = null;
};
// Main game update
game.update = function () {
// Update boss
boss.update();
// Update boss life bar position
bossHpBarBg.x = boss.x;
bossHpBarBg.y = boss.y - boss.radius - 60;
bossHpBar.x = boss.x;
bossHpBar.y = boss.y - boss.radius - 60;
// Update ability icon overlays to show cooldown visually
function updateAbilityOverlay(overlay, cooldown, maxCooldown) {
if (cooldown <= 0) {
overlay.alpha = 0;
} else {
// Show overlay, alpha increases as cooldown is higher
overlay.alpha = 0.6;
// Visually show cooldown as a darken effect (simulate by scaling Y)
var frac = Math.max(0, Math.min(1, cooldown / maxCooldown));
overlay.scaleY = frac;
overlay.y = overlay._baseY !== undefined ? overlay._baseY : overlay.y;
overlay.y = (overlay._baseY = overlay.y) - (1 - frac) * (overlay.height / 2);
}
}
updateAbilityOverlay(areaBtnOverlay, boss.cooldowns.area, AREA_COOLDOWN);
updateAbilityOverlay(shieldBtnOverlay, boss.cooldowns.shield, SHIELD_COOLDOWN);
updateAbilityOverlay(trapBtnOverlay, boss.cooldowns.trap, TRAP_COOLDOWN);
// Update heroes
for (var i = heroes.length - 1; i >= 0; --i) {
var h = heroes[i];
h.update();
// Check collision with shield
if (boss.shieldActive && boss.shieldObj && circlesIntersect(boss, SHIELD_RADIUS, h, 60)) {
// Repel hero
var dx = h.x - boss.x;
var dy = h.y - boss.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
h.x += dx / dist * 40;
h.y += dy / dist * 40;
}
LK.effects.flashObject(h, 0x00e5ff, 200);
}
// Check collision with traps
for (var t = traps.length - 1; t >= 0; --t) {
var trap = traps[t];
if (circlesIntersect(h, 60, trap, TRAP_RADIUS)) {
LK.setScore(LK.getScore() + 1);
scoreTxt.setText(LK.getScore());
LK.getSound('heroHit').play();
h.destroy();
heroes.splice(i, 1);
trap.destroy();
traps.splice(t, 1);
break;
}
}
// Check collision with boss (boss takes damage)
var nowIntersectingBoss = circlesIntersect(h, 60, boss, boss.radius);
if (!h.lastIntersectingBoss && nowIntersectingBoss) {
bossHp--;
// Flash boss
LK.effects.flashObject(boss, 0xff0000, 400);
// Remove hero
h.destroy();
heroes.splice(i, 1);
// Update boss life bar
var hpFrac = Math.max(0, bossHp) / BOSS_MAX_HP;
bossHpBar.width = 390 * hpFrac;
if (bossHp <= 0) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
return;
}
}
h.lastIntersectingBoss = nowIntersectingBoss;
}
// Update traps
for (var i = traps.length - 1; i >= 0; --i) {
traps[i].update();
}
// Remove area attack visuals if needed
for (var i = areaAttacks.length - 1; i >= 0; --i) {
// No update needed, handled by tween
}
// Spawn heroes
// Only spawn a new hero if there are no heroes currently on screen
if (heroes.length === 0 && LK.ticks % 15 === 0) {
spawnHero();
}
// Win condition: survive 60 heroes
if (LK.getScore() >= 60) {
LK.showYouWin();
}
};
earthquake image. In-Game asset. 2d. High contrast. No shadows
It's a scary monster, it has a 50-meter sword in its hand, it looks like the full moon sword in the game Metin2.. In-Game asset. 2d. High contrast. No shadows
game Charakter. In-Game asset. 2d. High contrast. No shadows
shield. In-Game asset. 2d. High contrast. No shadows
trap. In-Game asset. 2d. High contrast. No shadows
2d dungeon. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat