/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { highScore: 0, spellsUnlocked: 1 }); /**** * Classes ****/ var AreaSpell = Container.expand(function () { var self = Container.call(this); var spellGraphic = self.attachAsset('areaSpell', { anchorX: 0.5, anchorY: 0.5, alpha: 0.7 }); self.damage = 30; self.lifespan = 60; // 1 second at 60fps self.age = 0; self.update = function () { self.age++; // Fade out as it ages spellGraphic.alpha = 0.7 * (1 - self.age / self.lifespan); // Scale up slightly var scale = 1 + self.age / self.lifespan * 0.5; spellGraphic.scaleX = scale; spellGraphic.scaleY = scale; if (self.age >= self.lifespan) { return true; // Mark for removal } return false; }; return self; }); var Barrier = Container.expand(function () { var self = Container.call(this); var barrierGraphic = self.attachAsset('barrier', { anchorX: 0.5, anchorY: 0.5 }); self.health = 30; self.maxHealth = 30; self.lifespan = 300; // 5 seconds at 60fps self.age = 0; self.takeDamage = function (amount) { self.health -= amount; // Visual feedback LK.effects.flashObject(self, 0xffffff, 200); // Update transparency based on remaining health barrierGraphic.alpha = 0.3 + self.health / self.maxHealth * 0.7; return self.health <= 0; }; self.update = function () { self.age++; // Fade out as it approaches end of life if (self.age > self.lifespan * 0.7) { barrierGraphic.alpha = 0.3 + self.health / self.maxHealth * 0.7 * (1 - (self.age - self.lifespan * 0.7) / (self.lifespan * 0.3)); } if (self.age >= self.lifespan) { return true; // Mark for removal } return false; }; return self; }); var Crystal = Container.expand(function () { var self = Container.call(this); var crystalGraphic = self.attachAsset('crystal', { anchorX: 0.5, anchorY: 0.5 }); self.health = 100; self.maxHealth = 100; self.pulseStrength = 0; self.canUseAreaSpell = false; self.takeDamage = function (amount) { self.health -= amount; if (self.health <= 0) { self.health = 0; LK.showGameOver(); } // Visual feedback LK.effects.flashObject(self, 0xff0000, 500); LK.getSound('crystalHit').play(); // Update pulse strength (for area spell availability) self.pulseStrength += amount; if (self.pulseStrength >= 30) { self.pulseStrength = 30; self.canUseAreaSpell = true; // Pulse animation to show area spell is ready if (!self.isPulsing) { self.isPulsing = true; self.pulseAnimation(); } } }; self.useAreaSpell = function () { if (!self.canUseAreaSpell) { return false; } self.pulseStrength = 0; self.canUseAreaSpell = false; self.isPulsing = false; // Reset scale from pulsing animation tween(crystalGraphic, { scaleX: 1, scaleY: 1 }, { duration: 100 }); return true; }; self.pulseAnimation = function () { if (!self.canUseAreaSpell) { self.isPulsing = false; return; } tween(crystalGraphic, { scaleX: 1.2, scaleY: 1.2 }, { duration: 700, easing: tween.easeInOut, onFinish: function onFinish() { tween(crystalGraphic, { scaleX: 1, scaleY: 1 }, { duration: 700, easing: tween.easeInOut, onFinish: self.pulseAnimation }); } }); }; self.heal = function (amount) { self.health += amount; if (self.health > self.maxHealth) { self.health = self.maxHealth; } }; return self; }); var Enemy = Container.expand(function (type) { var self = Container.call(this); self.type = type || 'basic'; var assetId = 'basicEnemy'; var enemyScale = 1; self.health = 20; self.maxHealth = 20; self.speed = 1; self.damage = 10; self.scoreValue = 10; if (self.type === 'fast') { assetId = 'fastEnemy'; self.health = 10; self.maxHealth = 10; self.speed = 1.5; self.damage = 5; self.scoreValue = 15; } else if (self.type === 'tough') { assetId = 'toughEnemy'; self.health = 40; self.maxHealth = 40; self.speed = 0.6; self.damage = 15; self.scoreValue = 25; } else if (self.type === 'miniBoss') { assetId = 'miniBoss'; self.health = 100; self.maxHealth = 100; self.speed = 0.4; self.damage = 25; self.scoreValue = 100; enemyScale = 1.5; } var enemyGraphic = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5, scaleX: enemyScale, scaleY: enemyScale }); self.takeDamage = function (amount) { self.health -= amount; // Visual feedback LK.effects.flashObject(self, 0xffffff, 200); LK.getSound('enemyHit').play(); if (self.health <= 0) { LK.getSound('enemyDeath').play(); return true; // Enemy is defeated } return false; }; self.update = function () { // Move toward crystal var dx = crystal.x - self.x; var dy = crystal.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > self.speed) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } else { // Enemy reached crystal, attack it crystal.takeDamage(self.damage); return true; // Mark for removal } return false; }; return self; }); var Guardian = Container.expand(function () { var self = Container.call(this); var guardianGraphic = self.attachAsset('guardian', { anchorX: 0.5, anchorY: 0.5 }); self.targetX = 0; self.targetY = 0; self.movementSpeed = 6; self.castSpell = function (targetX, targetY, type) { var spell = new Spell(type); spell.x = self.x; spell.y = self.y; // Calculate direction var dx = targetX - self.x; var dy = targetY - self.y; var magnitude = Math.sqrt(dx * dx + dy * dy); // Normalize and set velocity spell.velocityX = dx / magnitude * spell.speed; spell.velocityY = dy / magnitude * spell.speed; return spell; }; self.createBarrier = function (x, y, rotation) { var barrier = new Barrier(); barrier.x = x; barrier.y = y; barrier.rotation = rotation; return barrier; }; self.update = function () { // Move toward target position if (self.targetX !== 0 || self.targetY !== 0) { var dx = self.targetX - self.x; var dy = self.targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > self.movementSpeed) { self.x += dx / distance * self.movementSpeed; self.y += dy / distance * self.movementSpeed; } else { self.x = self.targetX; self.y = self.targetY; self.targetX = 0; self.targetY = 0; } } }; return self; }); var Spell = Container.expand(function (type) { var self = Container.call(this); self.type = type || 'basic'; var assetId = 'basicSpell'; var spellColor = 0xffff00; self.damage = 10; self.speed = 12; self.lifespan = 120; // in frames (2 seconds at 60fps) self.age = 0; if (self.type === 'fire') { self.damage = 15; spellColor = 0xff4400; } else if (self.type === 'ice') { self.damage = 8; self.speed = 10; spellColor = 0x44ddff; } else if (self.type === 'lightning') { self.damage = 20; self.speed = 16; spellColor = 0xddff00; } var spellGraphic = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5, tint: spellColor }); self.velocityX = 0; self.velocityY = 0; self.update = function () { self.x += self.velocityX; self.y += self.velocityY; self.age++; if (self.age >= self.lifespan) { return true; // Mark for removal } return false; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x112233 }); /**** * Game Code ****/ // Add background image asset // Game dimensions var gameWidth = 2048; var gameHeight = 2732; // Game state variables var crystal; var guardian; var spells = []; var barriers = []; var enemies = []; var areaSpells = []; var wave = 1; var score = 0; var spawnTimer = 0; var spawnDelay = 120; // 2 seconds at 60fps var waveInProgress = false; var enemiesInWave = 0; var enemiesSpawned = 0; var dragBarrierStart = null; // UI elements var scoreTxt; var waveTxt; var healthTxt; var areaSpellReadyTxt; // Initialize game function initGame() { // Attach background image var background = LK.getAsset('background', { anchorX: 0.5, anchorY: 0.5, x: gameWidth / 2, y: gameHeight / 2 }); game.addChild(background); // Create and position crystal LK.playMusic('battleMusic'); // Create and position crystal crystal = new Crystal(); crystal.x = gameWidth / 2; crystal.y = gameHeight / 2; game.addChild(crystal); // Create guardian guardian = new Guardian(); guardian.x = gameWidth / 2; guardian.y = gameHeight / 2 + 300; game.addChild(guardian); // Create UI elements createUI(); // Reset game state wave = 1; score = 0; LK.setScore(score); spawnTimer = 60; // Start first wave after 1 second waveInProgress = false; updateUI(); } function createUI() { // Score text scoreTxt = new Text2('Score: 0', { size: 60, fill: 0xFFFFFF }); scoreTxt.anchor.set(1, 0); // Right aligned LK.gui.topRight.addChild(scoreTxt); // Wave text waveTxt = new Text2('Wave: 1', { size: 60, fill: 0xFFFFFF }); waveTxt.anchor.set(0, 0); // Left aligned // Make sure this doesn't overlap with the top left corner menu icon waveTxt.x = 120; // Add some padding from the left edge LK.gui.top.addChild(waveTxt); // Health text healthTxt = new Text2('Crystal: 100/100', { size: 60, fill: 0xFFFFFF }); healthTxt.anchor.set(0.5, 0); LK.gui.top.addChild(healthTxt); // Area spell ready indicator areaSpellReadyTxt = new Text2('Area Spell: Ready!', { size: 50, fill: 0xFFFF00 }); areaSpellReadyTxt.anchor.set(0.5, 0); areaSpellReadyTxt.y = 70; areaSpellReadyTxt.visible = false; LK.gui.top.addChild(areaSpellReadyTxt); } function updateUI() { scoreTxt.setText('Score: ' + score); waveTxt.setText('Wave: ' + wave); healthTxt.setText('Crystal: ' + crystal.health + '/' + crystal.maxHealth); // Show/hide area spell indicator areaSpellReadyTxt.visible = crystal.canUseAreaSpell; } function startWave() { waveInProgress = true; enemiesInWave = 5 + wave * 2; enemiesSpawned = 0; spawnTimer = 0; // Determine if this wave has a mini-boss hasMiniBase = wave % 3 === 0; if (hasMiniBase) { enemiesInWave += 1; // Add mini-boss to count } } function spawnEnemy() { // Determine enemy type based on wave and some randomness var enemyType = 'basic'; var randomValue = Math.random(); if (wave >= 3 && enemiesSpawned === enemiesInWave - 1 && wave % 3 === 0) { // Spawn mini-boss as last enemy in every third wave enemyType = 'miniBoss'; } else if (wave >= 2 && randomValue < 0.3) { enemyType = 'fast'; } else if (wave >= 4 && randomValue < 0.5) { enemyType = 'tough'; } var enemy = new Enemy(enemyType); // Spawn at random position around the edges var side = Math.floor(Math.random() * 4); switch (side) { case 0: // Top enemy.x = Math.random() * gameWidth; enemy.y = -50; break; case 1: // Right enemy.x = gameWidth + 50; enemy.y = Math.random() * gameHeight; break; case 2: // Bottom enemy.x = Math.random() * gameWidth; enemy.y = gameHeight + 50; break; case 3: // Left enemy.x = -50; enemy.y = Math.random() * gameHeight; break; } enemies.push(enemy); game.addChild(enemy); enemiesSpawned++; // Check if we've spawned all enemies for this wave if (enemiesSpawned >= enemiesInWave) { waveInProgress = false; } } function checkWaveCompletion() { if (!waveInProgress && enemies.length === 0 && enemiesSpawned >= enemiesInWave && enemiesInWave > 0) { // Wave completed wave++; LK.getSound('waveComplete').play(); // Add bonus points for completing wave score += wave * 50; LK.setScore(score); // Update high score if needed if (score > storage.highScore) { storage.highScore = score; } updateUI(); // Short delay before next wave spawnTimer = 180; // 3 seconds at 60fps enemiesInWave = 0; } } function castAreaSpell() { if (!crystal.canUseAreaSpell) { return; } var areaSpell = new AreaSpell(); areaSpell.x = crystal.x; areaSpell.y = crystal.y; areaSpells.push(areaSpell); game.addChild(areaSpell); LK.getSound('areaSpell').play(); // Use the crystal's area spell ability crystal.useAreaSpell(); updateUI(); } // Event handlers game.down = function (x, y, obj) { // Check if clicking on crystal and area spell is ready if (crystal.canUseAreaSpell) { var crystalGlobalPos = game.toLocal(crystal.position); var dx = x - crystalGlobalPos.x; var dy = y - crystalGlobalPos.y; var distanceSquared = dx * dx + dy * dy; if (distanceSquared <= 120 * 120) { castAreaSpell(); return; } } // Start barrier creation dragBarrierStart = { x: x, y: y }; // Set guardian target guardian.targetX = x; guardian.targetY = y; }; game.move = function (x, y, obj) { if (dragBarrierStart) { // Visual feedback for barrier drawing // In a real implementation, we'd show a preview of the barrier } }; game.up = function (x, y, obj) { if (dragBarrierStart) { // Calculate barrier properties var dx = x - dragBarrierStart.x; var dy = y - dragBarrierStart.y; var length = Math.sqrt(dx * dx + dy * dy); // Only create barrier if drag distance is sufficient if (length > 100) { // Calculate barrier position (middle of the line) var barrierX = dragBarrierStart.x + dx / 2; var barrierY = dragBarrierStart.y + dy / 2; // Calculate rotation angle var rotation = Math.atan2(dy, dx); var barrier = guardian.createBarrier(barrierX, barrierY, rotation); barriers.push(barrier); game.addChild(barrier); LK.getSound('barrierCreate').play(); } else { // If not creating a barrier, cast a spell instead var spell = guardian.castSpell(x, y, 'basic'); spells.push(spell); game.addChild(spell); LK.getSound('spellCast').play(); } dragBarrierStart = null; } }; // Game update loop game.update = function () { // Initialize game on first update if (!crystal) { initGame(); return; } // Update guardian guardian.update(); // Spawn enemies if (spawnTimer <= 0) { if (!waveInProgress && enemiesInWave === 0) { startWave(); } else if (waveInProgress && enemiesSpawned < enemiesInWave) { spawnEnemy(); spawnTimer = spawnDelay - wave * 5; // Decrease spawn delay as waves progress if (spawnTimer < 30) { spawnTimer = 30; } // Minimum spawn delay } } else { spawnTimer--; } // Update spells and check for collisions for (var i = spells.length - 1; i >= 0; i--) { var removeSpell = spells[i].update(); // Check for collisions with enemies for (var j = enemies.length - 1; j >= 0; j--) { if (spells[i].intersects(enemies[j])) { removeSpell = true; // Apply damage to enemy if (enemies[j].takeDamage(spells[i].damage)) { // Enemy defeated score += enemies[j].scoreValue; LK.setScore(score); enemies[j].destroy(); enemies.splice(j, 1); } break; } } // Remove spell if needed if (removeSpell) { spells[i].destroy(); spells.splice(i, 1); } } // Update barriers for (var i = barriers.length - 1; i >= 0; i--) { if (barriers[i].update()) { barriers[i].destroy(); barriers.splice(i, 1); continue; } } // Update enemies for (var i = enemies.length - 1; i >= 0; i--) { var removeEnemy = enemies[i].update(); // Check for collisions with barriers for (var j = barriers.length - 1; j >= 0; j--) { if (enemies[i].intersects(barriers[j])) { // Enemy hits barrier if (barriers[j].takeDamage(1)) { barriers[j].destroy(); barriers.splice(j, 1); } // Slow down or push back enemy slightly (simplified physics) if (barriers[j] !== undefined) { var dx = enemies[i].x - barriers[j].x; var dy = enemies[i].y - barriers[j].y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 0) { enemies[i].x += dx / dist * 2; enemies[i].y += dy / dist * 2; } } break; } } // Remove enemy if needed if (removeEnemy) { enemies[i].destroy(); enemies.splice(i, 1); } } // Update area spells for (var i = areaSpells.length - 1; i >= 0; i--) { if (areaSpells[i].update()) { areaSpells[i].destroy(); areaSpells.splice(i, 1); continue; } // Check for enemy collisions for (var j = enemies.length - 1; j >= 0; j--) { if (areaSpells[i].intersects(enemies[j])) { // Apply damage to enemy if (enemies[j].takeDamage(areaSpells[i].damage)) { // Enemy defeated score += enemies[j].scoreValue; LK.setScore(score); enemies[j].destroy(); enemies.splice(j, 1); } } } } // Check if wave is complete checkWaveCompletion(); // Update UI updateUI(); };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0,
spellsUnlocked: 1
});
/****
* Classes
****/
var AreaSpell = Container.expand(function () {
var self = Container.call(this);
var spellGraphic = self.attachAsset('areaSpell', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.7
});
self.damage = 30;
self.lifespan = 60; // 1 second at 60fps
self.age = 0;
self.update = function () {
self.age++;
// Fade out as it ages
spellGraphic.alpha = 0.7 * (1 - self.age / self.lifespan);
// Scale up slightly
var scale = 1 + self.age / self.lifespan * 0.5;
spellGraphic.scaleX = scale;
spellGraphic.scaleY = scale;
if (self.age >= self.lifespan) {
return true; // Mark for removal
}
return false;
};
return self;
});
var Barrier = Container.expand(function () {
var self = Container.call(this);
var barrierGraphic = self.attachAsset('barrier', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 30;
self.maxHealth = 30;
self.lifespan = 300; // 5 seconds at 60fps
self.age = 0;
self.takeDamage = function (amount) {
self.health -= amount;
// Visual feedback
LK.effects.flashObject(self, 0xffffff, 200);
// Update transparency based on remaining health
barrierGraphic.alpha = 0.3 + self.health / self.maxHealth * 0.7;
return self.health <= 0;
};
self.update = function () {
self.age++;
// Fade out as it approaches end of life
if (self.age > self.lifespan * 0.7) {
barrierGraphic.alpha = 0.3 + self.health / self.maxHealth * 0.7 * (1 - (self.age - self.lifespan * 0.7) / (self.lifespan * 0.3));
}
if (self.age >= self.lifespan) {
return true; // Mark for removal
}
return false;
};
return self;
});
var Crystal = Container.expand(function () {
var self = Container.call(this);
var crystalGraphic = self.attachAsset('crystal', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 100;
self.maxHealth = 100;
self.pulseStrength = 0;
self.canUseAreaSpell = false;
self.takeDamage = function (amount) {
self.health -= amount;
if (self.health <= 0) {
self.health = 0;
LK.showGameOver();
}
// Visual feedback
LK.effects.flashObject(self, 0xff0000, 500);
LK.getSound('crystalHit').play();
// Update pulse strength (for area spell availability)
self.pulseStrength += amount;
if (self.pulseStrength >= 30) {
self.pulseStrength = 30;
self.canUseAreaSpell = true;
// Pulse animation to show area spell is ready
if (!self.isPulsing) {
self.isPulsing = true;
self.pulseAnimation();
}
}
};
self.useAreaSpell = function () {
if (!self.canUseAreaSpell) {
return false;
}
self.pulseStrength = 0;
self.canUseAreaSpell = false;
self.isPulsing = false;
// Reset scale from pulsing animation
tween(crystalGraphic, {
scaleX: 1,
scaleY: 1
}, {
duration: 100
});
return true;
};
self.pulseAnimation = function () {
if (!self.canUseAreaSpell) {
self.isPulsing = false;
return;
}
tween(crystalGraphic, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 700,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(crystalGraphic, {
scaleX: 1,
scaleY: 1
}, {
duration: 700,
easing: tween.easeInOut,
onFinish: self.pulseAnimation
});
}
});
};
self.heal = function (amount) {
self.health += amount;
if (self.health > self.maxHealth) {
self.health = self.maxHealth;
}
};
return self;
});
var Enemy = Container.expand(function (type) {
var self = Container.call(this);
self.type = type || 'basic';
var assetId = 'basicEnemy';
var enemyScale = 1;
self.health = 20;
self.maxHealth = 20;
self.speed = 1;
self.damage = 10;
self.scoreValue = 10;
if (self.type === 'fast') {
assetId = 'fastEnemy';
self.health = 10;
self.maxHealth = 10;
self.speed = 1.5;
self.damage = 5;
self.scoreValue = 15;
} else if (self.type === 'tough') {
assetId = 'toughEnemy';
self.health = 40;
self.maxHealth = 40;
self.speed = 0.6;
self.damage = 15;
self.scoreValue = 25;
} else if (self.type === 'miniBoss') {
assetId = 'miniBoss';
self.health = 100;
self.maxHealth = 100;
self.speed = 0.4;
self.damage = 25;
self.scoreValue = 100;
enemyScale = 1.5;
}
var enemyGraphic = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: enemyScale,
scaleY: enemyScale
});
self.takeDamage = function (amount) {
self.health -= amount;
// Visual feedback
LK.effects.flashObject(self, 0xffffff, 200);
LK.getSound('enemyHit').play();
if (self.health <= 0) {
LK.getSound('enemyDeath').play();
return true; // Enemy is defeated
}
return false;
};
self.update = function () {
// Move toward crystal
var dx = crystal.x - self.x;
var dy = crystal.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > self.speed) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
} else {
// Enemy reached crystal, attack it
crystal.takeDamage(self.damage);
return true; // Mark for removal
}
return false;
};
return self;
});
var Guardian = Container.expand(function () {
var self = Container.call(this);
var guardianGraphic = self.attachAsset('guardian', {
anchorX: 0.5,
anchorY: 0.5
});
self.targetX = 0;
self.targetY = 0;
self.movementSpeed = 6;
self.castSpell = function (targetX, targetY, type) {
var spell = new Spell(type);
spell.x = self.x;
spell.y = self.y;
// Calculate direction
var dx = targetX - self.x;
var dy = targetY - self.y;
var magnitude = Math.sqrt(dx * dx + dy * dy);
// Normalize and set velocity
spell.velocityX = dx / magnitude * spell.speed;
spell.velocityY = dy / magnitude * spell.speed;
return spell;
};
self.createBarrier = function (x, y, rotation) {
var barrier = new Barrier();
barrier.x = x;
barrier.y = y;
barrier.rotation = rotation;
return barrier;
};
self.update = function () {
// Move toward target position
if (self.targetX !== 0 || self.targetY !== 0) {
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > self.movementSpeed) {
self.x += dx / distance * self.movementSpeed;
self.y += dy / distance * self.movementSpeed;
} else {
self.x = self.targetX;
self.y = self.targetY;
self.targetX = 0;
self.targetY = 0;
}
}
};
return self;
});
var Spell = Container.expand(function (type) {
var self = Container.call(this);
self.type = type || 'basic';
var assetId = 'basicSpell';
var spellColor = 0xffff00;
self.damage = 10;
self.speed = 12;
self.lifespan = 120; // in frames (2 seconds at 60fps)
self.age = 0;
if (self.type === 'fire') {
self.damage = 15;
spellColor = 0xff4400;
} else if (self.type === 'ice') {
self.damage = 8;
self.speed = 10;
spellColor = 0x44ddff;
} else if (self.type === 'lightning') {
self.damage = 20;
self.speed = 16;
spellColor = 0xddff00;
}
var spellGraphic = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5,
tint: spellColor
});
self.velocityX = 0;
self.velocityY = 0;
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
self.age++;
if (self.age >= self.lifespan) {
return true; // Mark for removal
}
return false;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x112233
});
/****
* Game Code
****/
// Add background image asset
// Game dimensions
var gameWidth = 2048;
var gameHeight = 2732;
// Game state variables
var crystal;
var guardian;
var spells = [];
var barriers = [];
var enemies = [];
var areaSpells = [];
var wave = 1;
var score = 0;
var spawnTimer = 0;
var spawnDelay = 120; // 2 seconds at 60fps
var waveInProgress = false;
var enemiesInWave = 0;
var enemiesSpawned = 0;
var dragBarrierStart = null;
// UI elements
var scoreTxt;
var waveTxt;
var healthTxt;
var areaSpellReadyTxt;
// Initialize game
function initGame() {
// Attach background image
var background = LK.getAsset('background', {
anchorX: 0.5,
anchorY: 0.5,
x: gameWidth / 2,
y: gameHeight / 2
});
game.addChild(background);
// Create and position crystal
LK.playMusic('battleMusic');
// Create and position crystal
crystal = new Crystal();
crystal.x = gameWidth / 2;
crystal.y = gameHeight / 2;
game.addChild(crystal);
// Create guardian
guardian = new Guardian();
guardian.x = gameWidth / 2;
guardian.y = gameHeight / 2 + 300;
game.addChild(guardian);
// Create UI elements
createUI();
// Reset game state
wave = 1;
score = 0;
LK.setScore(score);
spawnTimer = 60; // Start first wave after 1 second
waveInProgress = false;
updateUI();
}
function createUI() {
// Score text
scoreTxt = new Text2('Score: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(1, 0); // Right aligned
LK.gui.topRight.addChild(scoreTxt);
// Wave text
waveTxt = new Text2('Wave: 1', {
size: 60,
fill: 0xFFFFFF
});
waveTxt.anchor.set(0, 0); // Left aligned
// Make sure this doesn't overlap with the top left corner menu icon
waveTxt.x = 120; // Add some padding from the left edge
LK.gui.top.addChild(waveTxt);
// Health text
healthTxt = new Text2('Crystal: 100/100', {
size: 60,
fill: 0xFFFFFF
});
healthTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(healthTxt);
// Area spell ready indicator
areaSpellReadyTxt = new Text2('Area Spell: Ready!', {
size: 50,
fill: 0xFFFF00
});
areaSpellReadyTxt.anchor.set(0.5, 0);
areaSpellReadyTxt.y = 70;
areaSpellReadyTxt.visible = false;
LK.gui.top.addChild(areaSpellReadyTxt);
}
function updateUI() {
scoreTxt.setText('Score: ' + score);
waveTxt.setText('Wave: ' + wave);
healthTxt.setText('Crystal: ' + crystal.health + '/' + crystal.maxHealth);
// Show/hide area spell indicator
areaSpellReadyTxt.visible = crystal.canUseAreaSpell;
}
function startWave() {
waveInProgress = true;
enemiesInWave = 5 + wave * 2;
enemiesSpawned = 0;
spawnTimer = 0;
// Determine if this wave has a mini-boss
hasMiniBase = wave % 3 === 0;
if (hasMiniBase) {
enemiesInWave += 1; // Add mini-boss to count
}
}
function spawnEnemy() {
// Determine enemy type based on wave and some randomness
var enemyType = 'basic';
var randomValue = Math.random();
if (wave >= 3 && enemiesSpawned === enemiesInWave - 1 && wave % 3 === 0) {
// Spawn mini-boss as last enemy in every third wave
enemyType = 'miniBoss';
} else if (wave >= 2 && randomValue < 0.3) {
enemyType = 'fast';
} else if (wave >= 4 && randomValue < 0.5) {
enemyType = 'tough';
}
var enemy = new Enemy(enemyType);
// Spawn at random position around the edges
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
// Top
enemy.x = Math.random() * gameWidth;
enemy.y = -50;
break;
case 1:
// Right
enemy.x = gameWidth + 50;
enemy.y = Math.random() * gameHeight;
break;
case 2:
// Bottom
enemy.x = Math.random() * gameWidth;
enemy.y = gameHeight + 50;
break;
case 3:
// Left
enemy.x = -50;
enemy.y = Math.random() * gameHeight;
break;
}
enemies.push(enemy);
game.addChild(enemy);
enemiesSpawned++;
// Check if we've spawned all enemies for this wave
if (enemiesSpawned >= enemiesInWave) {
waveInProgress = false;
}
}
function checkWaveCompletion() {
if (!waveInProgress && enemies.length === 0 && enemiesSpawned >= enemiesInWave && enemiesInWave > 0) {
// Wave completed
wave++;
LK.getSound('waveComplete').play();
// Add bonus points for completing wave
score += wave * 50;
LK.setScore(score);
// Update high score if needed
if (score > storage.highScore) {
storage.highScore = score;
}
updateUI();
// Short delay before next wave
spawnTimer = 180; // 3 seconds at 60fps
enemiesInWave = 0;
}
}
function castAreaSpell() {
if (!crystal.canUseAreaSpell) {
return;
}
var areaSpell = new AreaSpell();
areaSpell.x = crystal.x;
areaSpell.y = crystal.y;
areaSpells.push(areaSpell);
game.addChild(areaSpell);
LK.getSound('areaSpell').play();
// Use the crystal's area spell ability
crystal.useAreaSpell();
updateUI();
}
// Event handlers
game.down = function (x, y, obj) {
// Check if clicking on crystal and area spell is ready
if (crystal.canUseAreaSpell) {
var crystalGlobalPos = game.toLocal(crystal.position);
var dx = x - crystalGlobalPos.x;
var dy = y - crystalGlobalPos.y;
var distanceSquared = dx * dx + dy * dy;
if (distanceSquared <= 120 * 120) {
castAreaSpell();
return;
}
}
// Start barrier creation
dragBarrierStart = {
x: x,
y: y
};
// Set guardian target
guardian.targetX = x;
guardian.targetY = y;
};
game.move = function (x, y, obj) {
if (dragBarrierStart) {
// Visual feedback for barrier drawing
// In a real implementation, we'd show a preview of the barrier
}
};
game.up = function (x, y, obj) {
if (dragBarrierStart) {
// Calculate barrier properties
var dx = x - dragBarrierStart.x;
var dy = y - dragBarrierStart.y;
var length = Math.sqrt(dx * dx + dy * dy);
// Only create barrier if drag distance is sufficient
if (length > 100) {
// Calculate barrier position (middle of the line)
var barrierX = dragBarrierStart.x + dx / 2;
var barrierY = dragBarrierStart.y + dy / 2;
// Calculate rotation angle
var rotation = Math.atan2(dy, dx);
var barrier = guardian.createBarrier(barrierX, barrierY, rotation);
barriers.push(barrier);
game.addChild(barrier);
LK.getSound('barrierCreate').play();
} else {
// If not creating a barrier, cast a spell instead
var spell = guardian.castSpell(x, y, 'basic');
spells.push(spell);
game.addChild(spell);
LK.getSound('spellCast').play();
}
dragBarrierStart = null;
}
};
// Game update loop
game.update = function () {
// Initialize game on first update
if (!crystal) {
initGame();
return;
}
// Update guardian
guardian.update();
// Spawn enemies
if (spawnTimer <= 0) {
if (!waveInProgress && enemiesInWave === 0) {
startWave();
} else if (waveInProgress && enemiesSpawned < enemiesInWave) {
spawnEnemy();
spawnTimer = spawnDelay - wave * 5; // Decrease spawn delay as waves progress
if (spawnTimer < 30) {
spawnTimer = 30;
} // Minimum spawn delay
}
} else {
spawnTimer--;
}
// Update spells and check for collisions
for (var i = spells.length - 1; i >= 0; i--) {
var removeSpell = spells[i].update();
// Check for collisions with enemies
for (var j = enemies.length - 1; j >= 0; j--) {
if (spells[i].intersects(enemies[j])) {
removeSpell = true;
// Apply damage to enemy
if (enemies[j].takeDamage(spells[i].damage)) {
// Enemy defeated
score += enemies[j].scoreValue;
LK.setScore(score);
enemies[j].destroy();
enemies.splice(j, 1);
}
break;
}
}
// Remove spell if needed
if (removeSpell) {
spells[i].destroy();
spells.splice(i, 1);
}
}
// Update barriers
for (var i = barriers.length - 1; i >= 0; i--) {
if (barriers[i].update()) {
barriers[i].destroy();
barriers.splice(i, 1);
continue;
}
}
// Update enemies
for (var i = enemies.length - 1; i >= 0; i--) {
var removeEnemy = enemies[i].update();
// Check for collisions with barriers
for (var j = barriers.length - 1; j >= 0; j--) {
if (enemies[i].intersects(barriers[j])) {
// Enemy hits barrier
if (barriers[j].takeDamage(1)) {
barriers[j].destroy();
barriers.splice(j, 1);
}
// Slow down or push back enemy slightly (simplified physics)
if (barriers[j] !== undefined) {
var dx = enemies[i].x - barriers[j].x;
var dy = enemies[i].y - barriers[j].y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
enemies[i].x += dx / dist * 2;
enemies[i].y += dy / dist * 2;
}
}
break;
}
}
// Remove enemy if needed
if (removeEnemy) {
enemies[i].destroy();
enemies.splice(i, 1);
}
}
// Update area spells
for (var i = areaSpells.length - 1; i >= 0; i--) {
if (areaSpells[i].update()) {
areaSpells[i].destroy();
areaSpells.splice(i, 1);
continue;
}
// Check for enemy collisions
for (var j = enemies.length - 1; j >= 0; j--) {
if (areaSpells[i].intersects(enemies[j])) {
// Apply damage to enemy
if (enemies[j].takeDamage(areaSpells[i].damage)) {
// Enemy defeated
score += enemies[j].scoreValue;
LK.setScore(score);
enemies[j].destroy();
enemies.splice(j, 1);
}
}
}
}
// Check if wave is complete
checkWaveCompletion();
// Update UI
updateUI();
};
blue light crystal. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
top down anime mage character. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
red orc scarry taunt put hands up Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
top down temple floor. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
angry running green orc reptile taunting scarry. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
blue light of snow crystall. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
orang fire ball light. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
2d png black fanta bull orc anger taunt. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
full body angry taunt orc grim reaper style. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows