/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ // Boss enemy class var Boss = Container.expand(function () { var self = Container.call(this); self.isBoss = true; self.state = 0; // 0: alive, 1: fading self.bossAsset = self.attachAsset('enemy_shielded', { anchorX: 0.5, anchorY: 0.5 }); self.x = CENTER_X; self.y = CENTER_Y - 700; self.speed = 12; self.targetAngle = 0; self.moveTimer = 0; self.fadeTimer = 0; self.lastLaserHit = false; self.escapeDir = 1; // 1: right, -1: left // Set boss to a new random direction (left or right of laser) self.setNewDirection = function () { // Pick a side to escape: left or right of laser self.escapeDir = Math.random() < 0.5 ? 1 : -1; // Offset angle from laser self.targetAngle = laserAngle + Math.PI / 2 * self.escapeDir; // Clamp to [-PI, PI] if (self.targetAngle > Math.PI) self.targetAngle -= 2 * Math.PI; if (self.targetAngle < -Math.PI) self.targetAngle += 2 * Math.PI; // Set moveTimer for how long to move in this direction self.moveTimer = 30 + Math.floor(Math.random() * 30); }; self.setNewDirection(); self.update = function () { if (self.state === 1) { self.fadeTimer += 1; self.alpha = 1 - self.fadeTimer / 60; if (self.fadeTimer > 60) { self.destroy(); } return; } // Move away from laser direction var moveAngle = self.targetAngle; var moveDist = self.speed; self.x += Math.cos(moveAngle) * moveDist; self.y += Math.sin(moveAngle) * moveDist; // Clamp boss inside game area if (self.x < 200) self.x = 200; if (self.x > 1848) self.x = 1848; if (self.y < 200) self.y = 200; if (self.y > 2532) self.y = 2532; self.moveTimer -= 1; if (self.moveTimer <= 0) { self.setNewDirection(); } }; // Boss takes hit self.hitByLaser = function () { if (self.state === 0) { self.state = 1; self.fadeTimer = 0; LK.getSound('enemy_down').play(); } }; return self; }); // ENEMY STATES // 0: normal // 1: shielded (red) // 2: fading (about to die) var Enemy = Container.expand(function () { var self = Container.call(this); // Default: normal enemy self.state = 0; // 0: normal, 1: shielded, 2: fading self.isWizard = false; self.isProtected = false; // If protected by wizard self.protector = null; // Wizard protecting this enemy // Asset self.enemyAsset = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); self.shieldAsset = null; // For shield effect // For movement self.speed = 2 + Math.random() * 1.5; // Slightly random speed self.angle = 0; // Will be set on spawn self.radius = 0; // Distance from center // For fade-out self.fadeTimer = 0; // For collision self.lastLaserHit = false; // For wizard self.isWizard = false; self.shieldedEnemy = null; // For wizard: the enemy it protects // Set state (change color/asset) self.setState = function (state) { self.state = state; if (self.isWizard) { // Always wizard asset if (self.enemyAsset) self.enemyAsset.destroy(); self.enemyAsset = self.attachAsset('wizard', { anchorX: 0.5, anchorY: 0.5 }); } else if (state === 0) { if (self.enemyAsset) self.enemyAsset.destroy(); self.enemyAsset = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); } else if (state === 1) { if (self.enemyAsset) self.enemyAsset.destroy(); self.enemyAsset = self.attachAsset('enemy_shielded', { anchorX: 0.5, anchorY: 0.5 }); } else if (state === 2) { if (self.enemyAsset) self.enemyAsset.destroy(); self.enemyAsset = self.attachAsset('enemy_fading', { anchorX: 0.5, anchorY: 0.5 }); } }; // Show/hide shield self.setShield = function (on) { if (on && !self.shieldAsset) { self.shieldAsset = self.attachAsset('shield', { anchorX: 0.5, anchorY: 0.5 }); self.shieldAsset.alpha = 0.35; } else if (!on && self.shieldAsset) { self.shieldAsset.destroy(); self.shieldAsset = null; } }; // Called every tick self.update = function () { // Move towards center var dx = 1024 - self.x; var dy = 1366 - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 0) { var moveDist = Math.min(self.speed, dist); self.x += dx / dist * moveDist; self.y += dy / dist * moveDist; } // Fading out if (self.state === 2) { self.fadeTimer += 1; if (self.fadeTimer > 30) { // Remove after fade self.destroy(); } else { self.alpha = 1 - self.fadeTimer / 30; } } }; // For wizard: assign protected enemy self.setShieldedEnemy = function (enemy) { self.shieldedEnemy = enemy; if (enemy) { enemy.isProtected = true; enemy.protector = self; enemy.setShield(true); } }; // For wizard: remove shield from protected enemy self.removeShieldedEnemy = function () { if (self.shieldedEnemy) { self.shieldedEnemy.isProtected = false; self.shieldedEnemy.protector = null; self.shieldedEnemy.setShield(false); self.shieldedEnemy = null; } }; // For protected enemy: called if wizard dies self.removeProtection = function () { self.isProtected = false; self.protector = null; self.setShield(false); }; return self; }); // --- Dangerous Enemy Types --- // Fast enemy: moves faster, dies in 1 hit, red color // Tank enemy: moves slow, takes 3 hits, purple color // Exploder enemy: explodes on death, orange color // Tank Enemy var TankEnemy = Enemy.expand(function () { var self = Enemy.call(this); self.tankHits = 0; // 0,1,2,3 (3rd is death) self.speed = 1.2 + Math.random() * 0.5; self.setState = function (state) { self.state = state; if (self.enemyAsset) self.enemyAsset.destroy(); if (self.tankHits === 0) { self.enemyAsset = self.attachAsset('enemy_tank', { anchorX: 0.5, anchorY: 0.5 }); } else if (self.tankHits === 1) { self.enemyAsset = self.attachAsset('enemy_tank_dmg1', { anchorX: 0.5, anchorY: 0.5 }); } else if (self.tankHits === 2) { self.enemyAsset = self.attachAsset('enemy_tank_dmg2', { anchorX: 0.5, anchorY: 0.5 }); } else { self.enemyAsset = self.attachAsset('enemy_fading', { anchorX: 0.5, anchorY: 0.5 }); } }; self.update = function () { var dx = 1024 - self.x; var dy = 1366 - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 0) { var moveDist = Math.min(self.speed, dist); self.x += dx / dist * moveDist; self.y += dy / dist * moveDist; } if (self.state === 2) { self.fadeTimer += 1; if (self.fadeTimer > 40) { self.destroy(); } else { self.alpha = 1 - self.fadeTimer / 40; } } }; return self; }); // --- Dangerous Enemy Classes --- // Fast Enemy var FastEnemy = Enemy.expand(function () { var self = Enemy.call(this); self.speed = 5 + Math.random() * 2; self.setState = function (state) { self.state = state; if (self.enemyAsset) self.enemyAsset.destroy(); self.enemyAsset = self.attachAsset('enemy_fast', { anchorX: 0.5, anchorY: 0.5 }); }; // Only 1 hit to die self.update = function () { var dx = 1024 - self.x; var dy = 1366 - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 0) { var moveDist = Math.min(self.speed, dist); self.x += dx / dist * moveDist; self.y += dy / dist * moveDist; } if (self.state === 2) { self.fadeTimer += 1; if (self.fadeTimer > 18) { self.destroy(); } else { self.alpha = 1 - self.fadeTimer / 18; } } }; return self; }); // Exploder Enemy var ExploderEnemy = Enemy.expand(function () { var self = Enemy.call(this); self.speed = 2.5 + Math.random() * 1.2; self.setState = function (state) { self.state = state; if (self.enemyAsset) self.enemyAsset.destroy(); self.enemyAsset = self.attachAsset('enemy_exploder', { anchorX: 0.5, anchorY: 0.5 }); }; self.update = function () { var dx = 1024 - self.x; var dy = 1366 - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 0) { var moveDist = Math.min(self.speed, dist); self.x += dx / dist * moveDist; self.y += dy / dist * moveDist; } if (self.state === 2) { self.fadeTimer += 1; if (self.fadeTimer > 22) { self.destroy(); } else { self.alpha = 1 - self.fadeTimer / 22; } } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x181a1b }); /**** * Game Code ****/ // Exploder enemy: explodes on death, orange color // Tank enemy: moves slow, takes 3 hits, purple color // Fast enemy: moves faster, dies in 1 hit, red color // --- Dangerous Enemy Types --- // Center of screen // Main character (center) // Lazer beam (thin, long rectangle) // Enemy: normal // Enemy: shielded (first hit) // Enemy: fading (second hit) // Enemy: wizard // Shield effect (for protected enemies) // Sound for enemy destroyed var CENTER_X = 1024; var CENTER_Y = 1366; // Main character var hero = new Container(); var heroAsset = hero.attachAsset('hero', { anchorX: 0.5, anchorY: 0.5 }); hero.x = CENTER_X; hero.y = CENTER_Y; game.addChild(hero); // Laser var laser = new Container(); var laserAsset = laser.attachAsset('laser', { anchorX: 0.5, anchorY: 0 }); laser.x = CENTER_X; laser.y = CENTER_Y; laser.rotation = 0; game.addChild(laser); // Laser direction (in radians) var laserAngle = 0; // 0 = up var laserTargetAngle = 0; // For smooth tweening // Touch drag control // Remove dragging logic, always follow last touch/mouse position var lastPointer = { x: CENTER_X, y: CENTER_Y }; var enemies = []; var spawnWaveTimer = 0; var waveNumber = 1; // Boss var boss = null; var bossActive = false; var bossDefeated = false; // Score var score = 0; // Import storage plugin for persistent high score // Retrieve high score from storage, or 0 if not set var highScore = storage.highScore || 0; // Score text var scoreTxt = new Text2('0', { size: 120, fill: "#fff" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // High score text var highScoreTxt = new Text2('En Yüksek: ' + highScore, { size: 60, fill: 0xFFD700 }); highScoreTxt.anchor.set(0.5, 0); highScoreTxt.y = 110; // Place below the main score LK.gui.top.addChild(highScoreTxt); // Helper: spawn a single enemy at random angle/distance function spawnEnemy(type) { var enemy; if (type === 'wizard') { enemy = new Enemy(); enemy.isWizard = true; enemy.setState(0); } else if (type === 'fast') { enemy = new FastEnemy(); enemy.setState(0); } else if (type === 'tank') { enemy = new TankEnemy(); enemy.setState(0); } else if (type === 'exploder') { enemy = new ExploderEnemy(); enemy.setState(0); } else { enemy = new Enemy(); enemy.setState(0); } // Spawn at random angle, at edge of screen var angle = Math.random() * Math.PI * 2; var dist = 1200 + Math.random() * 400; enemy.x = CENTER_X + Math.cos(angle) * dist; enemy.y = CENTER_Y + Math.sin(angle) * dist; enemy.angle = angle; enemy.radius = dist; // Set speed for normal/wizard if (type === 'wizard') { enemy.speed = 2.5 + Math.random() * 1.2; } else if (type === 'normal') { enemy.speed = 2 + Math.random() * 1.5; } game.addChild(enemy); enemies.push(enemy); return enemy; } // Helper: assign wizards to protect nearest enemy function assignWizards() { // Get all wizards var wizards = []; var normalEnemies = []; for (var i = 0; i < enemies.length; ++i) { var e = enemies[i]; if (e.isWizard) wizards.push(e);else if (!e.isProtected) normalEnemies.push(e); } // For each wizard, find nearest unprotected normal enemy (not wizard, not protected, not fading) for (var j = 0; j < wizards.length; ++j) { var wiz = wizards[j]; // Remove old shield wiz.removeShieldedEnemy(); // Find nearest normal enemy (not wizard, not protected, not fading) var minDist = 99999; var nearest = null; for (var k = 0; k < enemies.length; ++k) { var e2 = enemies[k]; if (e2 === wiz) continue; if (e2.isWizard) continue; // Only shield normal enemies if (e2.isProtected) continue; if (e2.state === 2) continue; // Fading out var dx = wiz.x - e2.x; var dy = wiz.y - e2.y; var d = dx * dx + dy * dy; if (d < minDist) { minDist = d; nearest = e2; } } if (nearest) { wiz.setShieldedEnemy(nearest); } } } // Helper: spawn a wave of enemies function spawnWave() { // After boss is defeated, scale up difficulty and spawn more dangerous enemies var postBoss = bossDefeated && score >= 500; var baseEnemies = 3 + Math.floor(waveNumber * 0.7); var numEnemies = postBoss ? Math.floor(baseEnemies * (1.2 + (score - 500) / 1000)) : baseEnemies; var numWizards = postBoss ? Math.floor(numEnemies * 0.08) : Math.min(1 + Math.floor(waveNumber / 3), Math.floor(numEnemies / 3)); // Dangerous enemy ratios var fastRatio = postBoss ? Math.min(0.25 + (score - 500) / 2000, 0.45) : 0; var tankRatio = postBoss ? Math.min(0.18 + (score - 500) / 3000, 0.32) : 0; var exploderRatio = postBoss ? Math.min(0.12 + (score - 500) / 4000, 0.22) : 0; // Normal enemies become rare var normalRatio = 1 - (fastRatio + tankRatio + exploderRatio + numWizards / numEnemies); // Clamp if (normalRatio < 0.08) normalRatio = 0.08; // Build enemy type pool var pool = []; for (var i = 0; i < Math.floor(numEnemies * fastRatio); ++i) pool.push('fast'); for (var i = 0; i < Math.floor(numEnemies * tankRatio); ++i) pool.push('tank'); for (var i = 0; i < Math.floor(numEnemies * exploderRatio); ++i) pool.push('exploder'); for (var i = 0; i < Math.floor(numEnemies * normalRatio); ++i) pool.push('normal'); // Fill up to numEnemies while (pool.length < numEnemies) pool.push('normal'); // Shuffle pool for (var i = pool.length - 1; i > 0; --i) { var ri = Math.floor(Math.random() * (i + 1)); var tmp = pool[i]; pool[i] = pool[ri]; pool[ri] = tmp; } // Spawn wizards for (var i = 0; i < numWizards; ++i) { spawnEnemy('wizard'); } // Spawn from pool for (var j = 0; j < pool.length; ++j) { spawnEnemy(pool[j]); } assignWizards(); waveNumber += 1; } // Helper: check if a point is inside the laser beam function pointInLaser(px, py) { // Laser is a rectangle from (CENTER_X, CENTER_Y) in direction laserAngle, length = laserAsset.height var dx = px - CENTER_X; var dy = py - CENTER_Y; var len = Math.sqrt(dx * dx + dy * dy); if (len < 60) return false; // Don't hit at center var angleToPoint = Math.atan2(dy, dx); var diff = Math.abs(angleToPoint - laserAngle); // Normalize diff to [0, PI] while (diff > Math.PI) diff = Math.abs(diff - 2 * Math.PI); // If within 0.13 rad (~7.5 deg) of laser direction, and within laser length if (diff < 0.13 && len < laserAsset.height) { return true; } return false; } // Helper: remove enemy from array and game function removeEnemy(enemy) { // Remove shield if wizard if (enemy.isWizard) { enemy.removeShieldedEnemy(); } // Remove protection if protected if (enemy.isProtected && enemy.protector) { enemy.protector.removeShieldedEnemy(); } for (var i = enemies.length - 1; i >= 0; --i) { if (enemies[i] === enemy) { enemies.splice(i, 1); break; } } enemy.destroy(); } // Touch/drag controls game.down = function (x, y, obj) { // Ignore top left 100x100 for menu if (x < 100 && y < 100) return; // Always update last pointer position and angle lastPointer.x = x; lastPointer.y = y; var dx = x - CENTER_X; var dy = y - CENTER_Y; var angle = Math.atan2(dy, dx); if (angle > Math.PI) angle -= 2 * Math.PI; if (angle < -Math.PI) angle += 2 * Math.PI; laserTargetAngle = angle; }; game.move = function (x, y, obj) { // Always update last pointer position lastPointer.x = x; lastPointer.y = y; // Calculate angle from center to pointer var dx = x - CENTER_X; var dy = y - CENTER_Y; var angle = Math.atan2(dy, dx); // Clamp to [-PI, PI] if (angle > Math.PI) angle -= 2 * Math.PI; if (angle < -Math.PI) angle += 2 * Math.PI; laserTargetAngle = angle; }; game.up = function (x, y, obj) { // No drag state to reset, but update pointer and angle for consistency lastPointer.x = x; lastPointer.y = y; var dx = x - CENTER_X; var dy = y - CENTER_Y; var angle = Math.atan2(dy, dx); if (angle > Math.PI) angle -= 2 * Math.PI; if (angle < -Math.PI) angle += 2 * Math.PI; laserTargetAngle = angle; }; // Main update loop game.update = function () { // Smoothly rotate laser towards target angle var diff = laserTargetAngle - laserAngle; while (diff > Math.PI) diff -= 2 * Math.PI; while (diff < -Math.PI) diff += 2 * Math.PI; laserAngle += diff * 0.18; // Smooth // Clamp if (laserAngle > Math.PI) laserAngle -= 2 * Math.PI; if (laserAngle < -Math.PI) laserAngle += 2 * Math.PI; laser.rotation = laserAngle - Math.PI / 2; // Because asset is vertical // Boss phase logic if (!bossActive && score >= 500 && !bossDefeated) { // Remove all enemies for (var i = enemies.length - 1; i >= 0; --i) { enemies[i].destroy(); } enemies = []; // Spawn boss boss = new Boss(); boss.x = CENTER_X; boss.y = CENTER_Y - 700; game.addChild(boss); bossActive = true; } if (bossActive && boss && !bossDefeated) { boss.update(); // Boss collision with laser if (boss.state === 0 && pointInLaser(boss.x, boss.y)) { boss.hitByLaser(); score += 20; scoreTxt.setText(score); // Update high score if needed if (score > highScore) { highScore = score; storage.highScore = highScore; highScoreTxt.setText('En Yüksek: ' + highScore); } } // Remove boss if faded out if (boss.state === 1 && boss.fadeTimer > 60) { boss.destroy(); boss = null; bossActive = false; bossDefeated = true; // No win popup, continue game // Resume spawning waves waveNumber += 1; spawnWaveTimer = 0; // Optionally, you can spawn a new wave immediately or let the normal wave logic handle it // spawnWave(); // No return here, let the game continue } // Boss game over if reaches center if (boss) { var bdx = boss.x - CENTER_X; var bdy = boss.y - CENTER_Y; var bdist = Math.sqrt(bdx * bdx + bdy * bdy); if (bdist < 120 && boss.state === 0) { LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); return; } } return; // Only boss is active, skip rest } // Update all enemies for (var i = enemies.length - 1; i >= 0; --i) { var e = enemies[i]; e.update(); // Check if reached center (game over) var dx = e.x - CENTER_X; var dy = e.y - CENTER_Y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 100 && e.state !== 2) { // Game over LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); return; } } // Laser collision var anyLaserHit = false; for (var j = enemies.length - 1; j >= 0; --j) { var e2 = enemies[j]; if (e2.state === 2) continue; // Already dying if (pointInLaser(e2.x, e2.y)) { // If protected, can't be hit if (e2.isProtected) { // Show shield effect e2.setShield(true); continue; } // Wizards: die in one hit if (e2.isWizard) { e2.setState(2); e2.fadeTimer = 0; e2.removeShieldedEnemy(); LK.getSound('wizard_down').play(); score += 5; scoreTxt.setText(score); if (score > highScore) { highScore = score; storage.highScore = highScore; highScoreTxt.setText('En Yüksek: ' + highScore); } anyLaserHit = true; continue; } // Fast enemy: 1 hit to die if (e2 instanceof FastEnemy) { e2.setState(2); e2.fadeTimer = 0; LK.getSound('enemy_down').play(); score += 2; scoreTxt.setText(score); if (score > highScore) { highScore = score; storage.highScore = highScore; highScoreTxt.setText('En Yüksek: ' + highScore); } anyLaserHit = true; continue; } // Tank enemy: 3 hits to die if (e2 instanceof TankEnemy) { if (e2.state !== 2) { e2.tankHits += 1; if (e2.tankHits < 3) { e2.setState(0); LK.getSound('laser_hit').play(); } else { e2.setState(2); e2.fadeTimer = 0; LK.getSound('enemy_down').play(); score += 4; scoreTxt.setText(score); if (score > highScore) { highScore = score; storage.highScore = highScore; highScoreTxt.setText('En Yüksek: ' + highScore); } } anyLaserHit = true; continue; } } // Exploder enemy: 1 hit to die, but will explode (handled in removeEnemy) if (e2 instanceof ExploderEnemy) { e2.setState(2); e2.fadeTimer = 0; LK.getSound('enemy_down').play(); score += 3; scoreTxt.setText(score); if (score > highScore) { highScore = score; storage.highScore = highScore; highScoreTxt.setText('En Yüksek: ' + highScore); } anyLaserHit = true; continue; } // Normal enemy: 2 hits to die if (e2.state === 0) { e2.setState(1); LK.getSound('laser_hit').play(); anyLaserHit = true; } else if (e2.state === 1) { e2.setState(2); e2.fadeTimer = 0; LK.getSound('enemy_down').play(); score += 1; scoreTxt.setText(score); if (score > highScore) { highScore = score; storage.highScore = highScore; highScoreTxt.setText('En Yüksek: ' + highScore); } anyLaserHit = true; } } else { // Not hit, remove shield effect if any if (!e2.isWizard && !e2.isProtected) { e2.setShield(false); } } } // Remove dead enemies for (var k = enemies.length - 1; k >= 0; --k) { var e3 = enemies[k]; // Exploder enemy: explode on death if (e3 instanceof ExploderEnemy && e3.state === 2 && e3.fadeTimer === 22) { // Check if player is close to explosion var dx = e3.x - CENTER_X; var dy = e3.y - CENTER_Y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 220) { LK.effects.flashScreen(0xff6600, 1000); LK.showGameOver(); return; } // Optionally, damage other enemies in radius (not implemented for simplicity) } if (e3.state === 2 && (e3 instanceof FastEnemy && e3.fadeTimer > 18 || e3 instanceof TankEnemy && e3.fadeTimer > 40 || e3 instanceof ExploderEnemy && e3.fadeTimer > 22 || !(e3 instanceof FastEnemy) && !(e3 instanceof TankEnemy) && !(e3 instanceof ExploderEnemy) && e3.fadeTimer > 30)) { removeEnemy(e3); } } // If all enemies gone, spawn next wave var living = 0; for (var m = 0; m < enemies.length; ++m) { if (enemies[m].state !== 2) living += 1; } if (living === 0) { spawnWaveTimer += 1; if (spawnWaveTimer > 40) { spawnWave(); spawnWaveTimer = 0; } } else { spawnWaveTimer = 0; } // (Removed wave-based win condition so game continues indefinitely) }; // Start first wave spawnWave(); scoreTxt.setText(score); // Play action-packed background music LK.playMusic('action_bgmusic');
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Boss enemy class
var Boss = Container.expand(function () {
var self = Container.call(this);
self.isBoss = true;
self.state = 0; // 0: alive, 1: fading
self.bossAsset = self.attachAsset('enemy_shielded', {
anchorX: 0.5,
anchorY: 0.5
});
self.x = CENTER_X;
self.y = CENTER_Y - 700;
self.speed = 12;
self.targetAngle = 0;
self.moveTimer = 0;
self.fadeTimer = 0;
self.lastLaserHit = false;
self.escapeDir = 1; // 1: right, -1: left
// Set boss to a new random direction (left or right of laser)
self.setNewDirection = function () {
// Pick a side to escape: left or right of laser
self.escapeDir = Math.random() < 0.5 ? 1 : -1;
// Offset angle from laser
self.targetAngle = laserAngle + Math.PI / 2 * self.escapeDir;
// Clamp to [-PI, PI]
if (self.targetAngle > Math.PI) self.targetAngle -= 2 * Math.PI;
if (self.targetAngle < -Math.PI) self.targetAngle += 2 * Math.PI;
// Set moveTimer for how long to move in this direction
self.moveTimer = 30 + Math.floor(Math.random() * 30);
};
self.setNewDirection();
self.update = function () {
if (self.state === 1) {
self.fadeTimer += 1;
self.alpha = 1 - self.fadeTimer / 60;
if (self.fadeTimer > 60) {
self.destroy();
}
return;
}
// Move away from laser direction
var moveAngle = self.targetAngle;
var moveDist = self.speed;
self.x += Math.cos(moveAngle) * moveDist;
self.y += Math.sin(moveAngle) * moveDist;
// Clamp boss inside game area
if (self.x < 200) self.x = 200;
if (self.x > 1848) self.x = 1848;
if (self.y < 200) self.y = 200;
if (self.y > 2532) self.y = 2532;
self.moveTimer -= 1;
if (self.moveTimer <= 0) {
self.setNewDirection();
}
};
// Boss takes hit
self.hitByLaser = function () {
if (self.state === 0) {
self.state = 1;
self.fadeTimer = 0;
LK.getSound('enemy_down').play();
}
};
return self;
});
// ENEMY STATES
// 0: normal
// 1: shielded (red)
// 2: fading (about to die)
var Enemy = Container.expand(function () {
var self = Container.call(this);
// Default: normal enemy
self.state = 0; // 0: normal, 1: shielded, 2: fading
self.isWizard = false;
self.isProtected = false; // If protected by wizard
self.protector = null; // Wizard protecting this enemy
// Asset
self.enemyAsset = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.shieldAsset = null; // For shield effect
// For movement
self.speed = 2 + Math.random() * 1.5; // Slightly random speed
self.angle = 0; // Will be set on spawn
self.radius = 0; // Distance from center
// For fade-out
self.fadeTimer = 0;
// For collision
self.lastLaserHit = false;
// For wizard
self.isWizard = false;
self.shieldedEnemy = null; // For wizard: the enemy it protects
// Set state (change color/asset)
self.setState = function (state) {
self.state = state;
if (self.isWizard) {
// Always wizard asset
if (self.enemyAsset) self.enemyAsset.destroy();
self.enemyAsset = self.attachAsset('wizard', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (state === 0) {
if (self.enemyAsset) self.enemyAsset.destroy();
self.enemyAsset = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (state === 1) {
if (self.enemyAsset) self.enemyAsset.destroy();
self.enemyAsset = self.attachAsset('enemy_shielded', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (state === 2) {
if (self.enemyAsset) self.enemyAsset.destroy();
self.enemyAsset = self.attachAsset('enemy_fading', {
anchorX: 0.5,
anchorY: 0.5
});
}
};
// Show/hide shield
self.setShield = function (on) {
if (on && !self.shieldAsset) {
self.shieldAsset = self.attachAsset('shield', {
anchorX: 0.5,
anchorY: 0.5
});
self.shieldAsset.alpha = 0.35;
} else if (!on && self.shieldAsset) {
self.shieldAsset.destroy();
self.shieldAsset = null;
}
};
// Called every tick
self.update = function () {
// Move towards center
var dx = 1024 - self.x;
var dy = 1366 - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
var moveDist = Math.min(self.speed, dist);
self.x += dx / dist * moveDist;
self.y += dy / dist * moveDist;
}
// Fading out
if (self.state === 2) {
self.fadeTimer += 1;
if (self.fadeTimer > 30) {
// Remove after fade
self.destroy();
} else {
self.alpha = 1 - self.fadeTimer / 30;
}
}
};
// For wizard: assign protected enemy
self.setShieldedEnemy = function (enemy) {
self.shieldedEnemy = enemy;
if (enemy) {
enemy.isProtected = true;
enemy.protector = self;
enemy.setShield(true);
}
};
// For wizard: remove shield from protected enemy
self.removeShieldedEnemy = function () {
if (self.shieldedEnemy) {
self.shieldedEnemy.isProtected = false;
self.shieldedEnemy.protector = null;
self.shieldedEnemy.setShield(false);
self.shieldedEnemy = null;
}
};
// For protected enemy: called if wizard dies
self.removeProtection = function () {
self.isProtected = false;
self.protector = null;
self.setShield(false);
};
return self;
});
// --- Dangerous Enemy Types ---
// Fast enemy: moves faster, dies in 1 hit, red color
// Tank enemy: moves slow, takes 3 hits, purple color
// Exploder enemy: explodes on death, orange color
// Tank Enemy
var TankEnemy = Enemy.expand(function () {
var self = Enemy.call(this);
self.tankHits = 0; // 0,1,2,3 (3rd is death)
self.speed = 1.2 + Math.random() * 0.5;
self.setState = function (state) {
self.state = state;
if (self.enemyAsset) self.enemyAsset.destroy();
if (self.tankHits === 0) {
self.enemyAsset = self.attachAsset('enemy_tank', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.tankHits === 1) {
self.enemyAsset = self.attachAsset('enemy_tank_dmg1', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.tankHits === 2) {
self.enemyAsset = self.attachAsset('enemy_tank_dmg2', {
anchorX: 0.5,
anchorY: 0.5
});
} else {
self.enemyAsset = self.attachAsset('enemy_fading', {
anchorX: 0.5,
anchorY: 0.5
});
}
};
self.update = function () {
var dx = 1024 - self.x;
var dy = 1366 - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
var moveDist = Math.min(self.speed, dist);
self.x += dx / dist * moveDist;
self.y += dy / dist * moveDist;
}
if (self.state === 2) {
self.fadeTimer += 1;
if (self.fadeTimer > 40) {
self.destroy();
} else {
self.alpha = 1 - self.fadeTimer / 40;
}
}
};
return self;
});
// --- Dangerous Enemy Classes ---
// Fast Enemy
var FastEnemy = Enemy.expand(function () {
var self = Enemy.call(this);
self.speed = 5 + Math.random() * 2;
self.setState = function (state) {
self.state = state;
if (self.enemyAsset) self.enemyAsset.destroy();
self.enemyAsset = self.attachAsset('enemy_fast', {
anchorX: 0.5,
anchorY: 0.5
});
};
// Only 1 hit to die
self.update = function () {
var dx = 1024 - self.x;
var dy = 1366 - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
var moveDist = Math.min(self.speed, dist);
self.x += dx / dist * moveDist;
self.y += dy / dist * moveDist;
}
if (self.state === 2) {
self.fadeTimer += 1;
if (self.fadeTimer > 18) {
self.destroy();
} else {
self.alpha = 1 - self.fadeTimer / 18;
}
}
};
return self;
});
// Exploder Enemy
var ExploderEnemy = Enemy.expand(function () {
var self = Enemy.call(this);
self.speed = 2.5 + Math.random() * 1.2;
self.setState = function (state) {
self.state = state;
if (self.enemyAsset) self.enemyAsset.destroy();
self.enemyAsset = self.attachAsset('enemy_exploder', {
anchorX: 0.5,
anchorY: 0.5
});
};
self.update = function () {
var dx = 1024 - self.x;
var dy = 1366 - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
var moveDist = Math.min(self.speed, dist);
self.x += dx / dist * moveDist;
self.y += dy / dist * moveDist;
}
if (self.state === 2) {
self.fadeTimer += 1;
if (self.fadeTimer > 22) {
self.destroy();
} else {
self.alpha = 1 - self.fadeTimer / 22;
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181a1b
});
/****
* Game Code
****/
// Exploder enemy: explodes on death, orange color
// Tank enemy: moves slow, takes 3 hits, purple color
// Fast enemy: moves faster, dies in 1 hit, red color
// --- Dangerous Enemy Types ---
// Center of screen
// Main character (center)
// Lazer beam (thin, long rectangle)
// Enemy: normal
// Enemy: shielded (first hit)
// Enemy: fading (second hit)
// Enemy: wizard
// Shield effect (for protected enemies)
// Sound for enemy destroyed
var CENTER_X = 1024;
var CENTER_Y = 1366;
// Main character
var hero = new Container();
var heroAsset = hero.attachAsset('hero', {
anchorX: 0.5,
anchorY: 0.5
});
hero.x = CENTER_X;
hero.y = CENTER_Y;
game.addChild(hero);
// Laser
var laser = new Container();
var laserAsset = laser.attachAsset('laser', {
anchorX: 0.5,
anchorY: 0
});
laser.x = CENTER_X;
laser.y = CENTER_Y;
laser.rotation = 0;
game.addChild(laser);
// Laser direction (in radians)
var laserAngle = 0; // 0 = up
var laserTargetAngle = 0; // For smooth tweening
// Touch drag control
// Remove dragging logic, always follow last touch/mouse position
var lastPointer = {
x: CENTER_X,
y: CENTER_Y
};
var enemies = [];
var spawnWaveTimer = 0;
var waveNumber = 1;
// Boss
var boss = null;
var bossActive = false;
var bossDefeated = false;
// Score
var score = 0;
// Import storage plugin for persistent high score
// Retrieve high score from storage, or 0 if not set
var highScore = storage.highScore || 0;
// Score text
var scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// High score text
var highScoreTxt = new Text2('En Yüksek: ' + highScore, {
size: 60,
fill: 0xFFD700
});
highScoreTxt.anchor.set(0.5, 0);
highScoreTxt.y = 110; // Place below the main score
LK.gui.top.addChild(highScoreTxt);
// Helper: spawn a single enemy at random angle/distance
function spawnEnemy(type) {
var enemy;
if (type === 'wizard') {
enemy = new Enemy();
enemy.isWizard = true;
enemy.setState(0);
} else if (type === 'fast') {
enemy = new FastEnemy();
enemy.setState(0);
} else if (type === 'tank') {
enemy = new TankEnemy();
enemy.setState(0);
} else if (type === 'exploder') {
enemy = new ExploderEnemy();
enemy.setState(0);
} else {
enemy = new Enemy();
enemy.setState(0);
}
// Spawn at random angle, at edge of screen
var angle = Math.random() * Math.PI * 2;
var dist = 1200 + Math.random() * 400;
enemy.x = CENTER_X + Math.cos(angle) * dist;
enemy.y = CENTER_Y + Math.sin(angle) * dist;
enemy.angle = angle;
enemy.radius = dist;
// Set speed for normal/wizard
if (type === 'wizard') {
enemy.speed = 2.5 + Math.random() * 1.2;
} else if (type === 'normal') {
enemy.speed = 2 + Math.random() * 1.5;
}
game.addChild(enemy);
enemies.push(enemy);
return enemy;
}
// Helper: assign wizards to protect nearest enemy
function assignWizards() {
// Get all wizards
var wizards = [];
var normalEnemies = [];
for (var i = 0; i < enemies.length; ++i) {
var e = enemies[i];
if (e.isWizard) wizards.push(e);else if (!e.isProtected) normalEnemies.push(e);
}
// For each wizard, find nearest unprotected normal enemy (not wizard, not protected, not fading)
for (var j = 0; j < wizards.length; ++j) {
var wiz = wizards[j];
// Remove old shield
wiz.removeShieldedEnemy();
// Find nearest normal enemy (not wizard, not protected, not fading)
var minDist = 99999;
var nearest = null;
for (var k = 0; k < enemies.length; ++k) {
var e2 = enemies[k];
if (e2 === wiz) continue;
if (e2.isWizard) continue; // Only shield normal enemies
if (e2.isProtected) continue;
if (e2.state === 2) continue; // Fading out
var dx = wiz.x - e2.x;
var dy = wiz.y - e2.y;
var d = dx * dx + dy * dy;
if (d < minDist) {
minDist = d;
nearest = e2;
}
}
if (nearest) {
wiz.setShieldedEnemy(nearest);
}
}
}
// Helper: spawn a wave of enemies
function spawnWave() {
// After boss is defeated, scale up difficulty and spawn more dangerous enemies
var postBoss = bossDefeated && score >= 500;
var baseEnemies = 3 + Math.floor(waveNumber * 0.7);
var numEnemies = postBoss ? Math.floor(baseEnemies * (1.2 + (score - 500) / 1000)) : baseEnemies;
var numWizards = postBoss ? Math.floor(numEnemies * 0.08) : Math.min(1 + Math.floor(waveNumber / 3), Math.floor(numEnemies / 3));
// Dangerous enemy ratios
var fastRatio = postBoss ? Math.min(0.25 + (score - 500) / 2000, 0.45) : 0;
var tankRatio = postBoss ? Math.min(0.18 + (score - 500) / 3000, 0.32) : 0;
var exploderRatio = postBoss ? Math.min(0.12 + (score - 500) / 4000, 0.22) : 0;
// Normal enemies become rare
var normalRatio = 1 - (fastRatio + tankRatio + exploderRatio + numWizards / numEnemies);
// Clamp
if (normalRatio < 0.08) normalRatio = 0.08;
// Build enemy type pool
var pool = [];
for (var i = 0; i < Math.floor(numEnemies * fastRatio); ++i) pool.push('fast');
for (var i = 0; i < Math.floor(numEnemies * tankRatio); ++i) pool.push('tank');
for (var i = 0; i < Math.floor(numEnemies * exploderRatio); ++i) pool.push('exploder');
for (var i = 0; i < Math.floor(numEnemies * normalRatio); ++i) pool.push('normal');
// Fill up to numEnemies
while (pool.length < numEnemies) pool.push('normal');
// Shuffle pool
for (var i = pool.length - 1; i > 0; --i) {
var ri = Math.floor(Math.random() * (i + 1));
var tmp = pool[i];
pool[i] = pool[ri];
pool[ri] = tmp;
}
// Spawn wizards
for (var i = 0; i < numWizards; ++i) {
spawnEnemy('wizard');
}
// Spawn from pool
for (var j = 0; j < pool.length; ++j) {
spawnEnemy(pool[j]);
}
assignWizards();
waveNumber += 1;
}
// Helper: check if a point is inside the laser beam
function pointInLaser(px, py) {
// Laser is a rectangle from (CENTER_X, CENTER_Y) in direction laserAngle, length = laserAsset.height
var dx = px - CENTER_X;
var dy = py - CENTER_Y;
var len = Math.sqrt(dx * dx + dy * dy);
if (len < 60) return false; // Don't hit at center
var angleToPoint = Math.atan2(dy, dx);
var diff = Math.abs(angleToPoint - laserAngle);
// Normalize diff to [0, PI]
while (diff > Math.PI) diff = Math.abs(diff - 2 * Math.PI);
// If within 0.13 rad (~7.5 deg) of laser direction, and within laser length
if (diff < 0.13 && len < laserAsset.height) {
return true;
}
return false;
}
// Helper: remove enemy from array and game
function removeEnemy(enemy) {
// Remove shield if wizard
if (enemy.isWizard) {
enemy.removeShieldedEnemy();
}
// Remove protection if protected
if (enemy.isProtected && enemy.protector) {
enemy.protector.removeShieldedEnemy();
}
for (var i = enemies.length - 1; i >= 0; --i) {
if (enemies[i] === enemy) {
enemies.splice(i, 1);
break;
}
}
enemy.destroy();
}
// Touch/drag controls
game.down = function (x, y, obj) {
// Ignore top left 100x100 for menu
if (x < 100 && y < 100) return;
// Always update last pointer position and angle
lastPointer.x = x;
lastPointer.y = y;
var dx = x - CENTER_X;
var dy = y - CENTER_Y;
var angle = Math.atan2(dy, dx);
if (angle > Math.PI) angle -= 2 * Math.PI;
if (angle < -Math.PI) angle += 2 * Math.PI;
laserTargetAngle = angle;
};
game.move = function (x, y, obj) {
// Always update last pointer position
lastPointer.x = x;
lastPointer.y = y;
// Calculate angle from center to pointer
var dx = x - CENTER_X;
var dy = y - CENTER_Y;
var angle = Math.atan2(dy, dx);
// Clamp to [-PI, PI]
if (angle > Math.PI) angle -= 2 * Math.PI;
if (angle < -Math.PI) angle += 2 * Math.PI;
laserTargetAngle = angle;
};
game.up = function (x, y, obj) {
// No drag state to reset, but update pointer and angle for consistency
lastPointer.x = x;
lastPointer.y = y;
var dx = x - CENTER_X;
var dy = y - CENTER_Y;
var angle = Math.atan2(dy, dx);
if (angle > Math.PI) angle -= 2 * Math.PI;
if (angle < -Math.PI) angle += 2 * Math.PI;
laserTargetAngle = angle;
};
// Main update loop
game.update = function () {
// Smoothly rotate laser towards target angle
var diff = laserTargetAngle - laserAngle;
while (diff > Math.PI) diff -= 2 * Math.PI;
while (diff < -Math.PI) diff += 2 * Math.PI;
laserAngle += diff * 0.18; // Smooth
// Clamp
if (laserAngle > Math.PI) laserAngle -= 2 * Math.PI;
if (laserAngle < -Math.PI) laserAngle += 2 * Math.PI;
laser.rotation = laserAngle - Math.PI / 2; // Because asset is vertical
// Boss phase logic
if (!bossActive && score >= 500 && !bossDefeated) {
// Remove all enemies
for (var i = enemies.length - 1; i >= 0; --i) {
enemies[i].destroy();
}
enemies = [];
// Spawn boss
boss = new Boss();
boss.x = CENTER_X;
boss.y = CENTER_Y - 700;
game.addChild(boss);
bossActive = true;
}
if (bossActive && boss && !bossDefeated) {
boss.update();
// Boss collision with laser
if (boss.state === 0 && pointInLaser(boss.x, boss.y)) {
boss.hitByLaser();
score += 20;
scoreTxt.setText(score);
// Update high score if needed
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
highScoreTxt.setText('En Yüksek: ' + highScore);
}
}
// Remove boss if faded out
if (boss.state === 1 && boss.fadeTimer > 60) {
boss.destroy();
boss = null;
bossActive = false;
bossDefeated = true;
// No win popup, continue game
// Resume spawning waves
waveNumber += 1;
spawnWaveTimer = 0;
// Optionally, you can spawn a new wave immediately or let the normal wave logic handle it
// spawnWave();
// No return here, let the game continue
}
// Boss game over if reaches center
if (boss) {
var bdx = boss.x - CENTER_X;
var bdy = boss.y - CENTER_Y;
var bdist = Math.sqrt(bdx * bdx + bdy * bdy);
if (bdist < 120 && boss.state === 0) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
return;
}
}
return; // Only boss is active, skip rest
}
// Update all enemies
for (var i = enemies.length - 1; i >= 0; --i) {
var e = enemies[i];
e.update();
// Check if reached center (game over)
var dx = e.x - CENTER_X;
var dy = e.y - CENTER_Y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 100 && e.state !== 2) {
// Game over
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
return;
}
}
// Laser collision
var anyLaserHit = false;
for (var j = enemies.length - 1; j >= 0; --j) {
var e2 = enemies[j];
if (e2.state === 2) continue; // Already dying
if (pointInLaser(e2.x, e2.y)) {
// If protected, can't be hit
if (e2.isProtected) {
// Show shield effect
e2.setShield(true);
continue;
}
// Wizards: die in one hit
if (e2.isWizard) {
e2.setState(2);
e2.fadeTimer = 0;
e2.removeShieldedEnemy();
LK.getSound('wizard_down').play();
score += 5;
scoreTxt.setText(score);
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
highScoreTxt.setText('En Yüksek: ' + highScore);
}
anyLaserHit = true;
continue;
}
// Fast enemy: 1 hit to die
if (e2 instanceof FastEnemy) {
e2.setState(2);
e2.fadeTimer = 0;
LK.getSound('enemy_down').play();
score += 2;
scoreTxt.setText(score);
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
highScoreTxt.setText('En Yüksek: ' + highScore);
}
anyLaserHit = true;
continue;
}
// Tank enemy: 3 hits to die
if (e2 instanceof TankEnemy) {
if (e2.state !== 2) {
e2.tankHits += 1;
if (e2.tankHits < 3) {
e2.setState(0);
LK.getSound('laser_hit').play();
} else {
e2.setState(2);
e2.fadeTimer = 0;
LK.getSound('enemy_down').play();
score += 4;
scoreTxt.setText(score);
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
highScoreTxt.setText('En Yüksek: ' + highScore);
}
}
anyLaserHit = true;
continue;
}
}
// Exploder enemy: 1 hit to die, but will explode (handled in removeEnemy)
if (e2 instanceof ExploderEnemy) {
e2.setState(2);
e2.fadeTimer = 0;
LK.getSound('enemy_down').play();
score += 3;
scoreTxt.setText(score);
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
highScoreTxt.setText('En Yüksek: ' + highScore);
}
anyLaserHit = true;
continue;
}
// Normal enemy: 2 hits to die
if (e2.state === 0) {
e2.setState(1);
LK.getSound('laser_hit').play();
anyLaserHit = true;
} else if (e2.state === 1) {
e2.setState(2);
e2.fadeTimer = 0;
LK.getSound('enemy_down').play();
score += 1;
scoreTxt.setText(score);
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
highScoreTxt.setText('En Yüksek: ' + highScore);
}
anyLaserHit = true;
}
} else {
// Not hit, remove shield effect if any
if (!e2.isWizard && !e2.isProtected) {
e2.setShield(false);
}
}
}
// Remove dead enemies
for (var k = enemies.length - 1; k >= 0; --k) {
var e3 = enemies[k];
// Exploder enemy: explode on death
if (e3 instanceof ExploderEnemy && e3.state === 2 && e3.fadeTimer === 22) {
// Check if player is close to explosion
var dx = e3.x - CENTER_X;
var dy = e3.y - CENTER_Y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 220) {
LK.effects.flashScreen(0xff6600, 1000);
LK.showGameOver();
return;
}
// Optionally, damage other enemies in radius (not implemented for simplicity)
}
if (e3.state === 2 && (e3 instanceof FastEnemy && e3.fadeTimer > 18 || e3 instanceof TankEnemy && e3.fadeTimer > 40 || e3 instanceof ExploderEnemy && e3.fadeTimer > 22 || !(e3 instanceof FastEnemy) && !(e3 instanceof TankEnemy) && !(e3 instanceof ExploderEnemy) && e3.fadeTimer > 30)) {
removeEnemy(e3);
}
}
// If all enemies gone, spawn next wave
var living = 0;
for (var m = 0; m < enemies.length; ++m) {
if (enemies[m].state !== 2) living += 1;
}
if (living === 0) {
spawnWaveTimer += 1;
if (spawnWaveTimer > 40) {
spawnWave();
spawnWaveTimer = 0;
}
} else {
spawnWaveTimer = 0;
}
// (Removed wave-based win condition so game continues indefinitely)
};
// Start first wave
spawnWave();
scoreTxt.setText(score);
// Play action-packed background music
LK.playMusic('action_bgmusic');