User prompt
bossun can barını skorun hemen altına rengi sarı olsun ve eğer bossu yenenersek kötü adam bu nasıl olur der ve sinirlenip kendisi bana saldırır onun canı ise 1000 dir ve onu yenmek için yine hasar barlarını toplamalıyız yine boss daki olduğu gibi ama bu barlar 50 vuruyor çünkü bizde sinilendik bu arada konuşmalar ingilizce olsun
User prompt
boss un canını biraz daha yana al okunmuyor
User prompt
kötü adamın konuşma yerini biraz alta al okunması zor oluyor ve 250 den sonra you win çıkıyor onun yerine dur sen desin ingilizce bir şekilde sonra bi anda herşey dursun ama ben harekt edebileyim sonra 10 saniye sonra ekrana gelip bunun tadına bak desin ve bizi yartaık kovalasın ve bizim onu yenmemiz için ekranın belli bölgelerinde bazı hasar topları çıkacak hasar topunu aldığımızd canavara 20 hasar vursun ama canvarın kalbi 500
User prompt
tap tostartı kaldır
User prompt
kötü karakter çıksın ve konuşsun ve ekrana tap to start gelsin her bölümde böyle olsun
User prompt
ingilizce olsun ve yazısı beyaz olsun söylediklerinin ve ekrana basınca oyun başlasın
User prompt
yap
User prompt
güzel oyunun hikayesi olsun ve bölünleri olsun ilk bölüm kötü adam ekranda belirir ve yanındaki konuşma baloncuğunda dünyayı yok edeceğim der ama bunu yapması için bizi vurması gerekir ve 50 ye kadar vuramazsa ekranda bir daha belirir ve sen şimdi bittin der toplar hızlanır sonra 100 e kadar vuramzsa ekranda bir daha belirir ve yeterder toplar baya hızlanır ve 250 ye kadar dayanırsak lazanırız bu arda metinler şngşlşzce olsun
User prompt
Please fix the bug: 'TypeError: Cannot read properties of undefined (reading 'width')' in or related to this line: 'var r = enemy.ball.width / 2;' Line Number: 119
Code edit (1 edits merged)
Please save this source code
User prompt
Top Savunması
Initial prompt
bir top olsun ve bir üsten toplar gelerek onu yok etmeye çalışsınlar belli bir canımız olsun
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Boss Class var Boss = Container.expand(function () { var self = Container.call(this); // Attach enemy ball asset (boss face) var bossFace = self.attachAsset('enemyBall', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.2, scaleY: 2.2 }); self.bossFace = bossFace; self.speed = 8; self.targetX = 2048 / 2; self.targetY = 800; // Boss starts at top, then chases player self.x = 2048 / 2; self.y = -bossFace.height; self.update = function () { // Move boss into screen, then chase player if (self.y < self.targetY) { self.y += 18; if (self.y > self.targetY) self.y = self.targetY; } else { // Chase player var dx = playerBall.x - self.x; var dy = playerBall.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 10) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed * 0.7; } } }; return self; }); // Damage Ball Class var DamageBall = Container.expand(function () { var self = Container.call(this); // Attach a yellow ball asset var ball = self.attachAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.1, scaleY: 1.1 }); ball.tint = 0xffe600; self.ball = ball; self.speed = 18; // Random X within game area var r = ball.width / 2; self.x = Math.random() * (2048 - 2 * r) + r; self.y = -r - 10; self.update = function () { self.y += self.speed; }; return self; }); // Start the boss chase phase // Enemy Ball Class var EnemyBall = Container.expand(function () { var self = Container.call(this); // Attach enemy ball asset (red circle) var ball = self.attachAsset('enemyBall', { anchorX: 0.5, anchorY: 0.5 }); // Assign ball asset to self.ball for external access self.ball = ball; // Speed will be set on creation self.speed = 6; // Update method: move down self.update = function () { self.y += self.speed; }; return self; }); // Player Ball Class var PlayerBall = Container.expand(function () { var self = Container.call(this); // Attach player ball asset (blue circle) var ball = self.attachAsset('playerBall', { anchorX: 0.5, anchorY: 0.5 }); // For possible future use (e.g., effects) self.ball = ball; // No update needed; position is controlled by drag return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x181818 // Dark background }); /**** * Game Code ****/ // Start the boss chase phase // --- Game Variables --- // Tween plugin for future use (animations, etc.) // --- Asset Initialization (shapes) --- // --- Second Boss Phase Variables (moved to global scope for access in update and reset) --- var secondBoss = null; var secondBossHealth = 1000; var secondBossHealthTxt = null; var inSecondBossPhase = false; var secondBossDamageBalls = []; var secondBossDamageBallSpawnTicks = 0; function startChasePhase() { inChasePhase = true; // Remove all enemies for (var i = 0; i < enemyBalls.length; i++) { enemyBalls[i].destroy(); } enemyBalls = []; // Spawn boss boss = new Boss(); game.addChild(boss); // Show boss health if (bossHealthTxt && bossHealthTxt.parent) bossHealthTxt.parent.removeChild(bossHealthTxt); bossHealth = 500; bossHealthTxt = new Text2("Boss: " + bossHealth, { size: 90, fill: 0xffe600, font: "Arial, Helvetica, sans-serif" }); // Place boss health bar below score, yellow color bossHealthTxt.anchor.set(0.5, 0); bossHealthTxt.x = 2048 / 2; bossHealthTxt.y = 120; // just below score LK.gui.top.addChild(bossHealthTxt); // Reset damage balls for (var j = 0; j < damageBalls.length; j++) { damageBalls[j].destroy(); } damageBalls = []; damageBallSpawnTicks = 0; } // Spawn a damage ball function spawnDamageBall() { var dball = new DamageBall(); damageBalls.push(dball); game.addChild(dball); } var playerBall; var enemyBalls = []; var lives = 3; var score = 0; var spawnInterval = 80; // Initial enemy spawn interval (ticks) var enemySpeed = 6; // Initial enemy speed var ticksSinceLastSpawn = 0; var dragNode = null; var lastGameOver = false; // --- Boss & Chase Phase --- var freezeEnemies = false; var freezeTicks = 0; var chasePhaseStarted = false; var boss = null; var bossHealth = 500; var bossHealthTxt = null; var damageBalls = []; var damageBallSpawnTicks = 0; var inChasePhase = false; // --- Tap to Start Overlay & Villain Dialog Control --- // (Removed tap-to-start overlay and tap logic. Game/dialog starts immediately.) var gameStarted = true; // Control for villain dialog per level (no tap-to-start) var waitingForTap = false; var pendingVillainText = null; var pendingVillainState = null; // Helper to show villain dialog (no tap-to-start overlay) function showVillainAndWait(text, nextState) { // Show villain dialog showVillainBubble(text); waitingForTap = false; pendingVillainText = text; pendingVillainState = nextState; gameStarted = true; } // --- UI Elements --- // Score Text var scoreTxt = new Text2('0', { size: 120, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Lives Text var livesTxt = new Text2('❤ 3', { size: 90, fill: 0xFF5252 }); livesTxt.anchor.set(1, 0); LK.gui.topRight.addChild(livesTxt); // --- Player Ball Setup --- playerBall = new PlayerBall(); game.addChild(playerBall); // Start at bottom center, above the bottom edge playerBall.x = 2048 / 2; playerBall.y = 2732 - 220; // --- Drag Handling --- function handleMove(x, y, obj) { if (dragNode) { // Clamp to game area (keep ball fully visible) var r = playerBall.ball.width / 2; var minX = r, maxX = 2048 - r; var minY = r, maxY = 2732 - r; dragNode.x = Math.max(minX, Math.min(maxX, x)); dragNode.y = Math.max(minY, Math.min(maxY, y)); } } game.move = handleMove; game.down = function (x, y, obj) { // Only start drag if touch/click is on the player ball var dx = x - playerBall.x; var dy = y - playerBall.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist <= playerBall.ball.width / 2) { dragNode = playerBall; handleMove(x, y, obj); } }; game.up = function (x, y, obj) { dragNode = null; }; // --- Enemy Spawning --- function spawnEnemy() { var enemy = new EnemyBall(); // Random X within game area, keeping ball fully visible var r = enemy.ball.width / 2; enemy.x = Math.random() * (2048 - 2 * r) + r; enemy.y = -r - 10; // Start just above the screen enemy.speed = enemySpeed + Math.random() * 2; // Add a bit of speed variation enemyBalls.push(enemy); game.addChild(enemy); } // --- Story & Level State --- var villainState = 0; // 0: intro, 1: after 50, 2: after 100, 3: after 250 (win) var villainBubble = null; var villainTimeout = null; var villainShowTicks = 0; var villainShowDuration = 180; // 3 seconds at 60fps function showVillainBubble(text) { // Remove previous if exists if (villainBubble && villainBubble.parent) villainBubble.parent.removeChild(villainBubble); villainBubble = new Container(); // Villain face (red ball) var face = LK.getAsset('enemyBall', { anchorX: 0.5, anchorY: 1 }); face.x = 0; face.y = 0; villainBubble.addChild(face); // Speech bubble (white rounded box) var bubble = new Container(); var bubbleShape = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.5, scaleY: 1.2 }); bubbleShape.tint = 0xffffff; bubbleShape.alpha = 0.92; bubble.addChild(bubbleShape); // Text (English, white) var txt = new Text2(text, { size: 80, fill: 0xffffff, font: "Arial, Helvetica, sans-serif" }); txt.anchor.set(0.5, 0.5); bubble.addChild(txt); bubble.x = 0; bubble.y = -180; villainBubble.addChild(bubble); // Position villainBubble at top center, but lower for readability villainBubble.x = 2048 / 2; villainBubble.y = 520; // moved down from 320 to 520 game.addChild(villainBubble); villainShowTicks = 0; } function removeVillainBubble() { if (villainBubble && villainBubble.parent) villainBubble.parent.removeChild(villainBubble); villainBubble = null; villainShowTicks = 0; } // --- Game Update Loop --- game.update = function () { // --- Villain Dialog Logic --- if (villainBubble) { villainShowTicks++; if (villainShowTicks > villainShowDuration) { removeVillainBubble(); } } // --- Level/Story Progression --- // At each story point, show villain and advance automatically if (villainState === 0 && LK.ticks < 10) { showVillainAndWait("I will destroy the world!", 1); villainState = 0.25; } if (villainState === 0.25) { // Remove villain bubble after duration, then advance if (!villainBubble) { villainState = 1; } } if (villainState === 1 && score >= 50) { showVillainAndWait("You're finished now!", 2); // Speed up balls spawnInterval = Math.max(30, spawnInterval - 18); enemySpeed += 3.5; villainState = 1.25; } if (villainState === 1.25) { if (!villainBubble) { villainState = 2; } } if (villainState === 2 && score >= 100) { showVillainAndWait("Enough!", 3); // Balls get much faster spawnInterval = Math.max(18, spawnInterval - 10); enemySpeed += 4.5; villainState = 2.25; } if (villainState === 2.25) { if (!villainBubble) { villainState = 3; } } if (villainState === 3 && score >= 250) { showVillainAndWait("Stop right there!", 4); villainState = 3.25; // Freeze all enemies, but allow player to move for (var i = 0; i < enemyBalls.length; i++) { enemyBalls[i].frozen = true; } // Prevent new enemies from spawning freezeEnemies = true; freezeTicks = LK.ticks; chasePhaseStarted = false; } if (villainState === 3.25) { if (!villainBubble && !chasePhaseStarted) { // After villain dialog, wait 10 seconds, then start chase LK.setTimeout(function () { showVillainAndWait("Taste this!", 5); // After dialog, spawn boss and damage balls LK.setTimeout(function () { startChasePhase(); }, villainShowDuration * 16.7); // villainShowDuration is in ticks, convert to ms }, 10000); chasePhaseStarted = true; } // Do not advance villainState until chase phase is started } // --- Enemy Spawning --- if (!freezeEnemies && !inChasePhase) { ticksSinceLastSpawn++; if (ticksSinceLastSpawn >= spawnInterval) { spawnEnemy(); ticksSinceLastSpawn = 0; } } // --- Difficulty Scaling --- if (!freezeEnemies && !inChasePhase) { // Every 600 ticks (~10 seconds), increase difficulty if (LK.ticks % 600 === 0 && LK.ticks > 0) { if (spawnInterval > 30) spawnInterval -= 8; // Faster spawns if (enemySpeed < 22) enemySpeed += 1.2; // Faster enemies } } // --- Update Enemies --- for (var i = enemyBalls.length - 1; i >= 0; i--) { var enemy = enemyBalls[i]; if (!enemy.frozen) { enemy.update(); } // Check collision with player var collides = enemy.intersects(playerBall); if (collides && !enemy.frozen) { // Remove enemy enemy.destroy(); enemyBalls.splice(i, 1); // Lose a life lives--; livesTxt.setText('❤ ' + lives); // Flash player ball LK.effects.flashObject(playerBall, 0xff0000, 400); // Game over if no lives left if (lives <= 0 && !lastGameOver) { lastGameOver = true; LK.effects.flashScreen(0xff0000, 800); LK.showGameOver(); return; } continue; } // Remove if off screen if (enemy.y - enemy.ball.height / 2 > 2732 + 40) { enemy.destroy(); enemyBalls.splice(i, 1); // Score for dodging score++; scoreTxt.setText(score + ''); } } // --- Boss Chase Phase --- if (inChasePhase && boss) { boss.update(); // Check collision with player (lose instantly) if (boss.intersects(playerBall) && !lastGameOver) { lastGameOver = true; LK.effects.flashScreen(0xff0000, 800); LK.showGameOver(); return; } // Update damage balls for (var j = damageBalls.length - 1; j >= 0; j--) { var dball = damageBalls[j]; dball.update(); // If player collects damage ball if (dball.intersects(playerBall)) { // Remove damage ball dball.destroy(); damageBalls.splice(j, 1); // Deal 20 damage to boss bossHealth -= 20; if (bossHealth < 0) bossHealth = 0; if (bossHealthTxt) bossHealthTxt.setText("Boss: " + bossHealth); // Flash boss LK.effects.flashObject(boss, 0xffe600, 400); // If boss defeated, start second boss phase if (bossHealth <= 0 && !inSecondBossPhase) { // Remove first boss and health bar if (boss && boss.parent) boss.parent.removeChild(boss); boss = null; if (bossHealthTxt && bossHealthTxt.parent) bossHealthTxt.parent.removeChild(bossHealthTxt); bossHealthTxt = null; // Remove all damage balls for (var k = 0; k < damageBalls.length; k++) { damageBalls[k].destroy(); } damageBalls = []; inChasePhase = false; // Villain dialog: "How is this possible?!" showVillainAndWait("How is this possible?!", 100); villainState = 99.5; inSecondBossPhase = true; // After dialog, start second boss attack after 2 seconds LK.setTimeout(function () { showVillainAndWait("Now face me!", 101); LK.setTimeout(function () { // Spawn second boss secondBoss = new Boss(); secondBossHealth = 1000; secondBoss.speed = 13; // faster and angrier secondBoss.targetY = 700; secondBoss.bossFace.tint = 0xff2222; // visually more angry game.addChild(secondBoss); // Show second boss health bar if (secondBossHealthTxt && secondBossHealthTxt.parent) secondBossHealthTxt.parent.removeChild(secondBossHealthTxt); secondBossHealthTxt = new Text2("Villain: " + secondBossHealth, { size: 90, fill: 0xffe600, font: "Arial, Helvetica, sans-serif" }); secondBossHealthTxt.anchor.set(0.5, 0); secondBossHealthTxt.x = 2048 / 2; secondBossHealthTxt.y = 220; // below score and first boss bar LK.gui.top.addChild(secondBossHealthTxt); // Reset second boss damage balls for (var m = 0; m < secondBossDamageBalls.length; m++) { secondBossDamageBalls[m].destroy(); } secondBossDamageBalls = []; secondBossDamageBallSpawnTicks = 0; }, villainShowDuration * 16.7); }, 2000); return; } } // Remove if off screen if (dball.y > 2732 + 80) { dball.destroy(); damageBalls.splice(j, 1); } } // Spawn damage balls every 90 ticks damageBallSpawnTicks++; if (damageBallSpawnTicks >= 90) { spawnDamageBall(); damageBallSpawnTicks = 0; } } // --- Second Boss Phase --- if (inSecondBossPhase && secondBoss) { secondBoss.update(); // If villain collides with player, game over if (secondBoss.intersects(playerBall) && !lastGameOver) { lastGameOver = true; LK.effects.flashScreen(0xff0000, 800); LK.showGameOver(); return; } // Update second boss damage balls for (var n = secondBossDamageBalls.length - 1; n >= 0; n--) { var sdball = secondBossDamageBalls[n]; sdball.update(); // If player collects damage ball if (sdball.intersects(playerBall)) { sdball.destroy(); secondBossDamageBalls.splice(n, 1); // Deal 50 damage to second boss secondBossHealth -= 50; if (secondBossHealth < 0) secondBossHealth = 0; if (secondBossHealthTxt) secondBossHealthTxt.setText("Villain: " + secondBossHealth); LK.effects.flashObject(secondBoss, 0xffe600, 400); // If second boss defeated if (secondBossHealth <= 0 && !lastGameOver) { lastGameOver = true; // Boss explosion effect if (secondBoss && secondBoss.parent) { // Create a quick yellow flash and scale up for explosion // Use tween.to for boss explosion tween.to(secondBoss, { scaleX: 3.5, scaleY: 3.5, alpha: 0 }, 700, { easing: "easeOutCubic" }); LK.effects.flashObject(secondBoss, 0xffe600, 400); LK.setTimeout(function () { if (secondBoss && secondBoss.parent) secondBoss.parent.removeChild(secondBoss); }, 700); } else if (secondBoss && !secondBoss.parent) { // fallback secondBoss = null; } if (secondBossHealthTxt && secondBossHealthTxt.parent) secondBossHealthTxt.parent.removeChild(secondBossHealthTxt); // Remove all second boss damage balls for (var p = 0; p < secondBossDamageBalls.length; p++) { secondBossDamageBalls[p].destroy(); } secondBossDamageBalls = []; // Final villain dialog: "No! How could you?!" showVillainAndWait("No! How could you?!", 200); // After dialog, show crowd rescue message LK.setTimeout(function () { // Remove villain bubble if still present removeVillainBubble(); // Show crowd dialog at bottom of screen var crowdBubble = new Container(); // Crowd "face" (use playerBall asset as a group icon) var crowdFace = LK.getAsset('playerBall', { anchorX: 0.5, anchorY: 1, scaleX: 1.5, scaleY: 1.5 }); crowdFace.x = 0; crowdFace.y = 0; crowdFace.tint = 0x4caf50; // green for hope crowdBubble.addChild(crowdFace); // Speech bubble (white, wide) var bubble = new Container(); var bubbleShape = LK.getAsset('centerCircle', { anchorX: 0.5, anchorY: 0.5, scaleX: 3.2, scaleY: 1.3 }); bubbleShape.tint = 0xffffff; bubbleShape.alpha = 0.95; bubble.addChild(bubbleShape); // Text (English, green) var txt = new Text2("You saved us! Thank you, hero!", { size: 90, fill: 0x4caf50, font: "Arial, Helvetica, sans-serif" }); txt.anchor.set(0.5, 0.5); bubble.addChild(txt); bubble.x = 0; bubble.y = -180; crowdBubble.addChild(bubble); // Position at bottom center crowdBubble.x = 2048 / 2; crowdBubble.y = 2732 - 220; game.addChild(crowdBubble); // Flash green for celebration LK.effects.flashScreen(0x00ff00, 800); // Remove crowd bubble and show win after 2.5s LK.setTimeout(function () { if (crowdBubble && crowdBubble.parent) crowdBubble.parent.removeChild(crowdBubble); LK.showYouWin(); }, 2500); }, 1800 + 1200); // after villain dialog and a short pause return; } } // Remove if off screen if (sdball.y > 2732 + 80) { sdball.destroy(); secondBossDamageBalls.splice(n, 1); } } // Spawn second boss damage balls every 60 ticks (faster) secondBossDamageBallSpawnTicks++; if (secondBossDamageBallSpawnTicks >= 60) { var sdb = new DamageBall(); sdb.ball.tint = 0xffe600; secondBossDamageBalls.push(sdb); game.addChild(sdb); secondBossDamageBallSpawnTicks = 0; } } }; // --- Reset Handler (for when game restarts) --- game.on('destroy', function () { // Clean up for (var i = 0; i < enemyBalls.length; i++) { enemyBalls[i].destroy(); } enemyBalls = []; // Remove boss if exists if (boss && boss.parent) boss.parent.removeChild(boss); boss = null; // Remove boss health text if (bossHealthTxt && bossHealthTxt.parent) bossHealthTxt.parent.removeChild(bossHealthTxt); bossHealthTxt = null; // Remove damage balls for (var j = 0; j < damageBalls.length; j++) { damageBalls[j].destroy(); } damageBalls = []; inChasePhase = false; freezeEnemies = false; chasePhaseStarted = false; bossHealth = 500; damageBallSpawnTicks = 0; // Remove second boss and its UI if (secondBoss && secondBoss.parent) secondBoss.parent.removeChild(secondBoss); secondBoss = null; if (secondBossHealthTxt && secondBossHealthTxt.parent) secondBossHealthTxt.parent.removeChild(secondBossHealthTxt); secondBossHealthTxt = null; for (var q = 0; q < secondBossDamageBalls.length; q++) { secondBossDamageBalls[q].destroy(); } secondBossDamageBalls = []; inSecondBossPhase = false; secondBossHealth = 1000; secondBossDamageBallSpawnTicks = 0; lives = 3; score = 0; spawnInterval = 80; enemySpeed = 6; ticksSinceLastSpawn = 0; lastGameOver = false; scoreTxt.setText('0'); livesTxt.setText('❤ 3'); // Reset player position playerBall.x = 2048 / 2; playerBall.y = 2732 - 220; // Reset gameStarted and villain state gameStarted = true; waitingForTap = false; pendingVillainText = null; pendingVillainState = null; villainState = 0; removeVillainBubble(); });
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Boss Class
var Boss = Container.expand(function () {
var self = Container.call(this);
// Attach enemy ball asset (boss face)
var bossFace = self.attachAsset('enemyBall', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.2,
scaleY: 2.2
});
self.bossFace = bossFace;
self.speed = 8;
self.targetX = 2048 / 2;
self.targetY = 800;
// Boss starts at top, then chases player
self.x = 2048 / 2;
self.y = -bossFace.height;
self.update = function () {
// Move boss into screen, then chase player
if (self.y < self.targetY) {
self.y += 18;
if (self.y > self.targetY) self.y = self.targetY;
} else {
// Chase player
var dx = playerBall.x - self.x;
var dy = playerBall.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 10) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed * 0.7;
}
}
};
return self;
});
// Damage Ball Class
var DamageBall = Container.expand(function () {
var self = Container.call(this);
// Attach a yellow ball asset
var ball = self.attachAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.1,
scaleY: 1.1
});
ball.tint = 0xffe600;
self.ball = ball;
self.speed = 18;
// Random X within game area
var r = ball.width / 2;
self.x = Math.random() * (2048 - 2 * r) + r;
self.y = -r - 10;
self.update = function () {
self.y += self.speed;
};
return self;
});
// Start the boss chase phase
// Enemy Ball Class
var EnemyBall = Container.expand(function () {
var self = Container.call(this);
// Attach enemy ball asset (red circle)
var ball = self.attachAsset('enemyBall', {
anchorX: 0.5,
anchorY: 0.5
});
// Assign ball asset to self.ball for external access
self.ball = ball;
// Speed will be set on creation
self.speed = 6;
// Update method: move down
self.update = function () {
self.y += self.speed;
};
return self;
});
// Player Ball Class
var PlayerBall = Container.expand(function () {
var self = Container.call(this);
// Attach player ball asset (blue circle)
var ball = self.attachAsset('playerBall', {
anchorX: 0.5,
anchorY: 0.5
});
// For possible future use (e.g., effects)
self.ball = ball;
// No update needed; position is controlled by drag
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181818 // Dark background
});
/****
* Game Code
****/
// Start the boss chase phase
// --- Game Variables ---
// Tween plugin for future use (animations, etc.)
// --- Asset Initialization (shapes) ---
// --- Second Boss Phase Variables (moved to global scope for access in update and reset) ---
var secondBoss = null;
var secondBossHealth = 1000;
var secondBossHealthTxt = null;
var inSecondBossPhase = false;
var secondBossDamageBalls = [];
var secondBossDamageBallSpawnTicks = 0;
function startChasePhase() {
inChasePhase = true;
// Remove all enemies
for (var i = 0; i < enemyBalls.length; i++) {
enemyBalls[i].destroy();
}
enemyBalls = [];
// Spawn boss
boss = new Boss();
game.addChild(boss);
// Show boss health
if (bossHealthTxt && bossHealthTxt.parent) bossHealthTxt.parent.removeChild(bossHealthTxt);
bossHealth = 500;
bossHealthTxt = new Text2("Boss: " + bossHealth, {
size: 90,
fill: 0xffe600,
font: "Arial, Helvetica, sans-serif"
});
// Place boss health bar below score, yellow color
bossHealthTxt.anchor.set(0.5, 0);
bossHealthTxt.x = 2048 / 2;
bossHealthTxt.y = 120; // just below score
LK.gui.top.addChild(bossHealthTxt);
// Reset damage balls
for (var j = 0; j < damageBalls.length; j++) {
damageBalls[j].destroy();
}
damageBalls = [];
damageBallSpawnTicks = 0;
}
// Spawn a damage ball
function spawnDamageBall() {
var dball = new DamageBall();
damageBalls.push(dball);
game.addChild(dball);
}
var playerBall;
var enemyBalls = [];
var lives = 3;
var score = 0;
var spawnInterval = 80; // Initial enemy spawn interval (ticks)
var enemySpeed = 6; // Initial enemy speed
var ticksSinceLastSpawn = 0;
var dragNode = null;
var lastGameOver = false;
// --- Boss & Chase Phase ---
var freezeEnemies = false;
var freezeTicks = 0;
var chasePhaseStarted = false;
var boss = null;
var bossHealth = 500;
var bossHealthTxt = null;
var damageBalls = [];
var damageBallSpawnTicks = 0;
var inChasePhase = false;
// --- Tap to Start Overlay & Villain Dialog Control ---
// (Removed tap-to-start overlay and tap logic. Game/dialog starts immediately.)
var gameStarted = true;
// Control for villain dialog per level (no tap-to-start)
var waitingForTap = false;
var pendingVillainText = null;
var pendingVillainState = null;
// Helper to show villain dialog (no tap-to-start overlay)
function showVillainAndWait(text, nextState) {
// Show villain dialog
showVillainBubble(text);
waitingForTap = false;
pendingVillainText = text;
pendingVillainState = nextState;
gameStarted = true;
}
// --- UI Elements ---
// Score Text
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Lives Text
var livesTxt = new Text2('❤ 3', {
size: 90,
fill: 0xFF5252
});
livesTxt.anchor.set(1, 0);
LK.gui.topRight.addChild(livesTxt);
// --- Player Ball Setup ---
playerBall = new PlayerBall();
game.addChild(playerBall);
// Start at bottom center, above the bottom edge
playerBall.x = 2048 / 2;
playerBall.y = 2732 - 220;
// --- Drag Handling ---
function handleMove(x, y, obj) {
if (dragNode) {
// Clamp to game area (keep ball fully visible)
var r = playerBall.ball.width / 2;
var minX = r,
maxX = 2048 - r;
var minY = r,
maxY = 2732 - r;
dragNode.x = Math.max(minX, Math.min(maxX, x));
dragNode.y = Math.max(minY, Math.min(maxY, y));
}
}
game.move = handleMove;
game.down = function (x, y, obj) {
// Only start drag if touch/click is on the player ball
var dx = x - playerBall.x;
var dy = y - playerBall.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist <= playerBall.ball.width / 2) {
dragNode = playerBall;
handleMove(x, y, obj);
}
};
game.up = function (x, y, obj) {
dragNode = null;
};
// --- Enemy Spawning ---
function spawnEnemy() {
var enemy = new EnemyBall();
// Random X within game area, keeping ball fully visible
var r = enemy.ball.width / 2;
enemy.x = Math.random() * (2048 - 2 * r) + r;
enemy.y = -r - 10; // Start just above the screen
enemy.speed = enemySpeed + Math.random() * 2; // Add a bit of speed variation
enemyBalls.push(enemy);
game.addChild(enemy);
}
// --- Story & Level State ---
var villainState = 0; // 0: intro, 1: after 50, 2: after 100, 3: after 250 (win)
var villainBubble = null;
var villainTimeout = null;
var villainShowTicks = 0;
var villainShowDuration = 180; // 3 seconds at 60fps
function showVillainBubble(text) {
// Remove previous if exists
if (villainBubble && villainBubble.parent) villainBubble.parent.removeChild(villainBubble);
villainBubble = new Container();
// Villain face (red ball)
var face = LK.getAsset('enemyBall', {
anchorX: 0.5,
anchorY: 1
});
face.x = 0;
face.y = 0;
villainBubble.addChild(face);
// Speech bubble (white rounded box)
var bubble = new Container();
var bubbleShape = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 1.2
});
bubbleShape.tint = 0xffffff;
bubbleShape.alpha = 0.92;
bubble.addChild(bubbleShape);
// Text (English, white)
var txt = new Text2(text, {
size: 80,
fill: 0xffffff,
font: "Arial, Helvetica, sans-serif"
});
txt.anchor.set(0.5, 0.5);
bubble.addChild(txt);
bubble.x = 0;
bubble.y = -180;
villainBubble.addChild(bubble);
// Position villainBubble at top center, but lower for readability
villainBubble.x = 2048 / 2;
villainBubble.y = 520; // moved down from 320 to 520
game.addChild(villainBubble);
villainShowTicks = 0;
}
function removeVillainBubble() {
if (villainBubble && villainBubble.parent) villainBubble.parent.removeChild(villainBubble);
villainBubble = null;
villainShowTicks = 0;
}
// --- Game Update Loop ---
game.update = function () {
// --- Villain Dialog Logic ---
if (villainBubble) {
villainShowTicks++;
if (villainShowTicks > villainShowDuration) {
removeVillainBubble();
}
}
// --- Level/Story Progression ---
// At each story point, show villain and advance automatically
if (villainState === 0 && LK.ticks < 10) {
showVillainAndWait("I will destroy the world!", 1);
villainState = 0.25;
}
if (villainState === 0.25) {
// Remove villain bubble after duration, then advance
if (!villainBubble) {
villainState = 1;
}
}
if (villainState === 1 && score >= 50) {
showVillainAndWait("You're finished now!", 2);
// Speed up balls
spawnInterval = Math.max(30, spawnInterval - 18);
enemySpeed += 3.5;
villainState = 1.25;
}
if (villainState === 1.25) {
if (!villainBubble) {
villainState = 2;
}
}
if (villainState === 2 && score >= 100) {
showVillainAndWait("Enough!", 3);
// Balls get much faster
spawnInterval = Math.max(18, spawnInterval - 10);
enemySpeed += 4.5;
villainState = 2.25;
}
if (villainState === 2.25) {
if (!villainBubble) {
villainState = 3;
}
}
if (villainState === 3 && score >= 250) {
showVillainAndWait("Stop right there!", 4);
villainState = 3.25;
// Freeze all enemies, but allow player to move
for (var i = 0; i < enemyBalls.length; i++) {
enemyBalls[i].frozen = true;
}
// Prevent new enemies from spawning
freezeEnemies = true;
freezeTicks = LK.ticks;
chasePhaseStarted = false;
}
if (villainState === 3.25) {
if (!villainBubble && !chasePhaseStarted) {
// After villain dialog, wait 10 seconds, then start chase
LK.setTimeout(function () {
showVillainAndWait("Taste this!", 5);
// After dialog, spawn boss and damage balls
LK.setTimeout(function () {
startChasePhase();
}, villainShowDuration * 16.7); // villainShowDuration is in ticks, convert to ms
}, 10000);
chasePhaseStarted = true;
}
// Do not advance villainState until chase phase is started
}
// --- Enemy Spawning ---
if (!freezeEnemies && !inChasePhase) {
ticksSinceLastSpawn++;
if (ticksSinceLastSpawn >= spawnInterval) {
spawnEnemy();
ticksSinceLastSpawn = 0;
}
}
// --- Difficulty Scaling ---
if (!freezeEnemies && !inChasePhase) {
// Every 600 ticks (~10 seconds), increase difficulty
if (LK.ticks % 600 === 0 && LK.ticks > 0) {
if (spawnInterval > 30) spawnInterval -= 8; // Faster spawns
if (enemySpeed < 22) enemySpeed += 1.2; // Faster enemies
}
}
// --- Update Enemies ---
for (var i = enemyBalls.length - 1; i >= 0; i--) {
var enemy = enemyBalls[i];
if (!enemy.frozen) {
enemy.update();
}
// Check collision with player
var collides = enemy.intersects(playerBall);
if (collides && !enemy.frozen) {
// Remove enemy
enemy.destroy();
enemyBalls.splice(i, 1);
// Lose a life
lives--;
livesTxt.setText('❤ ' + lives);
// Flash player ball
LK.effects.flashObject(playerBall, 0xff0000, 400);
// Game over if no lives left
if (lives <= 0 && !lastGameOver) {
lastGameOver = true;
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
return;
}
continue;
}
// Remove if off screen
if (enemy.y - enemy.ball.height / 2 > 2732 + 40) {
enemy.destroy();
enemyBalls.splice(i, 1);
// Score for dodging
score++;
scoreTxt.setText(score + '');
}
}
// --- Boss Chase Phase ---
if (inChasePhase && boss) {
boss.update();
// Check collision with player (lose instantly)
if (boss.intersects(playerBall) && !lastGameOver) {
lastGameOver = true;
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
return;
}
// Update damage balls
for (var j = damageBalls.length - 1; j >= 0; j--) {
var dball = damageBalls[j];
dball.update();
// If player collects damage ball
if (dball.intersects(playerBall)) {
// Remove damage ball
dball.destroy();
damageBalls.splice(j, 1);
// Deal 20 damage to boss
bossHealth -= 20;
if (bossHealth < 0) bossHealth = 0;
if (bossHealthTxt) bossHealthTxt.setText("Boss: " + bossHealth);
// Flash boss
LK.effects.flashObject(boss, 0xffe600, 400);
// If boss defeated, start second boss phase
if (bossHealth <= 0 && !inSecondBossPhase) {
// Remove first boss and health bar
if (boss && boss.parent) boss.parent.removeChild(boss);
boss = null;
if (bossHealthTxt && bossHealthTxt.parent) bossHealthTxt.parent.removeChild(bossHealthTxt);
bossHealthTxt = null;
// Remove all damage balls
for (var k = 0; k < damageBalls.length; k++) {
damageBalls[k].destroy();
}
damageBalls = [];
inChasePhase = false;
// Villain dialog: "How is this possible?!"
showVillainAndWait("How is this possible?!", 100);
villainState = 99.5;
inSecondBossPhase = true;
// After dialog, start second boss attack after 2 seconds
LK.setTimeout(function () {
showVillainAndWait("Now face me!", 101);
LK.setTimeout(function () {
// Spawn second boss
secondBoss = new Boss();
secondBossHealth = 1000;
secondBoss.speed = 13; // faster and angrier
secondBoss.targetY = 700;
secondBoss.bossFace.tint = 0xff2222; // visually more angry
game.addChild(secondBoss);
// Show second boss health bar
if (secondBossHealthTxt && secondBossHealthTxt.parent) secondBossHealthTxt.parent.removeChild(secondBossHealthTxt);
secondBossHealthTxt = new Text2("Villain: " + secondBossHealth, {
size: 90,
fill: 0xffe600,
font: "Arial, Helvetica, sans-serif"
});
secondBossHealthTxt.anchor.set(0.5, 0);
secondBossHealthTxt.x = 2048 / 2;
secondBossHealthTxt.y = 220; // below score and first boss bar
LK.gui.top.addChild(secondBossHealthTxt);
// Reset second boss damage balls
for (var m = 0; m < secondBossDamageBalls.length; m++) {
secondBossDamageBalls[m].destroy();
}
secondBossDamageBalls = [];
secondBossDamageBallSpawnTicks = 0;
}, villainShowDuration * 16.7);
}, 2000);
return;
}
}
// Remove if off screen
if (dball.y > 2732 + 80) {
dball.destroy();
damageBalls.splice(j, 1);
}
}
// Spawn damage balls every 90 ticks
damageBallSpawnTicks++;
if (damageBallSpawnTicks >= 90) {
spawnDamageBall();
damageBallSpawnTicks = 0;
}
}
// --- Second Boss Phase ---
if (inSecondBossPhase && secondBoss) {
secondBoss.update();
// If villain collides with player, game over
if (secondBoss.intersects(playerBall) && !lastGameOver) {
lastGameOver = true;
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
return;
}
// Update second boss damage balls
for (var n = secondBossDamageBalls.length - 1; n >= 0; n--) {
var sdball = secondBossDamageBalls[n];
sdball.update();
// If player collects damage ball
if (sdball.intersects(playerBall)) {
sdball.destroy();
secondBossDamageBalls.splice(n, 1);
// Deal 50 damage to second boss
secondBossHealth -= 50;
if (secondBossHealth < 0) secondBossHealth = 0;
if (secondBossHealthTxt) secondBossHealthTxt.setText("Villain: " + secondBossHealth);
LK.effects.flashObject(secondBoss, 0xffe600, 400);
// If second boss defeated
if (secondBossHealth <= 0 && !lastGameOver) {
lastGameOver = true;
// Boss explosion effect
if (secondBoss && secondBoss.parent) {
// Create a quick yellow flash and scale up for explosion
// Use tween.to for boss explosion
tween.to(secondBoss, {
scaleX: 3.5,
scaleY: 3.5,
alpha: 0
}, 700, {
easing: "easeOutCubic"
});
LK.effects.flashObject(secondBoss, 0xffe600, 400);
LK.setTimeout(function () {
if (secondBoss && secondBoss.parent) secondBoss.parent.removeChild(secondBoss);
}, 700);
} else if (secondBoss && !secondBoss.parent) {
// fallback
secondBoss = null;
}
if (secondBossHealthTxt && secondBossHealthTxt.parent) secondBossHealthTxt.parent.removeChild(secondBossHealthTxt);
// Remove all second boss damage balls
for (var p = 0; p < secondBossDamageBalls.length; p++) {
secondBossDamageBalls[p].destroy();
}
secondBossDamageBalls = [];
// Final villain dialog: "No! How could you?!"
showVillainAndWait("No! How could you?!", 200);
// After dialog, show crowd rescue message
LK.setTimeout(function () {
// Remove villain bubble if still present
removeVillainBubble();
// Show crowd dialog at bottom of screen
var crowdBubble = new Container();
// Crowd "face" (use playerBall asset as a group icon)
var crowdFace = LK.getAsset('playerBall', {
anchorX: 0.5,
anchorY: 1,
scaleX: 1.5,
scaleY: 1.5
});
crowdFace.x = 0;
crowdFace.y = 0;
crowdFace.tint = 0x4caf50; // green for hope
crowdBubble.addChild(crowdFace);
// Speech bubble (white, wide)
var bubble = new Container();
var bubbleShape = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3.2,
scaleY: 1.3
});
bubbleShape.tint = 0xffffff;
bubbleShape.alpha = 0.95;
bubble.addChild(bubbleShape);
// Text (English, green)
var txt = new Text2("You saved us! Thank you, hero!", {
size: 90,
fill: 0x4caf50,
font: "Arial, Helvetica, sans-serif"
});
txt.anchor.set(0.5, 0.5);
bubble.addChild(txt);
bubble.x = 0;
bubble.y = -180;
crowdBubble.addChild(bubble);
// Position at bottom center
crowdBubble.x = 2048 / 2;
crowdBubble.y = 2732 - 220;
game.addChild(crowdBubble);
// Flash green for celebration
LK.effects.flashScreen(0x00ff00, 800);
// Remove crowd bubble and show win after 2.5s
LK.setTimeout(function () {
if (crowdBubble && crowdBubble.parent) crowdBubble.parent.removeChild(crowdBubble);
LK.showYouWin();
}, 2500);
}, 1800 + 1200); // after villain dialog and a short pause
return;
}
}
// Remove if off screen
if (sdball.y > 2732 + 80) {
sdball.destroy();
secondBossDamageBalls.splice(n, 1);
}
}
// Spawn second boss damage balls every 60 ticks (faster)
secondBossDamageBallSpawnTicks++;
if (secondBossDamageBallSpawnTicks >= 60) {
var sdb = new DamageBall();
sdb.ball.tint = 0xffe600;
secondBossDamageBalls.push(sdb);
game.addChild(sdb);
secondBossDamageBallSpawnTicks = 0;
}
}
};
// --- Reset Handler (for when game restarts) ---
game.on('destroy', function () {
// Clean up
for (var i = 0; i < enemyBalls.length; i++) {
enemyBalls[i].destroy();
}
enemyBalls = [];
// Remove boss if exists
if (boss && boss.parent) boss.parent.removeChild(boss);
boss = null;
// Remove boss health text
if (bossHealthTxt && bossHealthTxt.parent) bossHealthTxt.parent.removeChild(bossHealthTxt);
bossHealthTxt = null;
// Remove damage balls
for (var j = 0; j < damageBalls.length; j++) {
damageBalls[j].destroy();
}
damageBalls = [];
inChasePhase = false;
freezeEnemies = false;
chasePhaseStarted = false;
bossHealth = 500;
damageBallSpawnTicks = 0;
// Remove second boss and its UI
if (secondBoss && secondBoss.parent) secondBoss.parent.removeChild(secondBoss);
secondBoss = null;
if (secondBossHealthTxt && secondBossHealthTxt.parent) secondBossHealthTxt.parent.removeChild(secondBossHealthTxt);
secondBossHealthTxt = null;
for (var q = 0; q < secondBossDamageBalls.length; q++) {
secondBossDamageBalls[q].destroy();
}
secondBossDamageBalls = [];
inSecondBossPhase = false;
secondBossHealth = 1000;
secondBossDamageBallSpawnTicks = 0;
lives = 3;
score = 0;
spawnInterval = 80;
enemySpeed = 6;
ticksSinceLastSpawn = 0;
lastGameOver = false;
scoreTxt.setText('0');
livesTxt.setText('❤ 3');
// Reset player position
playerBall.x = 2048 / 2;
playerBall.y = 2732 - 220;
// Reset gameStarted and villain state
gameStarted = true;
waitingForTap = false;
pendingVillainText = null;
pendingVillainState = null;
villainState = 0;
removeVillainBubble();
});