/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ // --- Enemy Class --- var Enemy = Container.expand(function () { var self = Container.call(this); // Attach enemy asset (image) var enemyAsset = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); self.radius = enemyAsset.width / 2; self.speed = 3; self.angle = 0; self.hasSpawnedText = false; // Flag to track if this enemy already spawned text self.update = function () { // Store lastX, lastY for good practice (if needed for triggers) if (typeof self.lastX === "undefined") self.lastX = self.x; if (typeof self.lastY === "undefined") self.lastY = self.y; // Move in direction of angle self.x += Math.cos(self.angle) * self.speed * globalSpeedMultiplier; self.y += Math.sin(self.angle) * self.speed * globalSpeedMultiplier; // Update lastX, lastY self.lastX = self.x; self.lastY = self.y; }; return self; }); // --- Enemy2 Class (Red, smaller, faster) --- var Enemy2 = Container.expand(function () { var self = Container.call(this); // Attach enemy2 asset (image) var enemyAsset = self.attachAsset('enemy2', { anchorX: 0.5, anchorY: 0.5 }); self.radius = enemyAsset.width / 2; self.speed = 5; // Faster than regular enemy self.angle = 0; self.hasSpawnedText = false; // Flag to track if this enemy already spawned text self.update = function () { // Store lastX, lastY for good practice if (typeof self.lastX === "undefined") self.lastX = self.x; if (typeof self.lastY === "undefined") self.lastY = self.y; // Move in direction of angle self.x += Math.cos(self.angle) * self.speed * globalSpeedMultiplier; self.y += Math.sin(self.angle) * self.speed * globalSpeedMultiplier; // Update lastX, lastY self.lastX = self.x; self.lastY = self.y; }; return self; }); // --- Enemy3 Class (Purple, wide, special movement) --- var Enemy3 = Container.expand(function () { var self = Container.call(this); // Attach enemy3 asset (image) var enemyAsset = self.attachAsset('enemy3', { anchorX: 0.5, anchorY: 0.5 }); self.radius = enemyAsset.width / 2; self.speed = 2; // Slower but wider self.angle = 0; self.wobbleOffset = 0; // For special movement pattern self.hasSpawnedText = false; // Flag to track if this enemy already spawned text self.update = function () { // Store lastX, lastY for good practice if (typeof self.lastX === "undefined") self.lastX = self.x; if (typeof self.lastY === "undefined") self.lastY = self.y; // Special wobble movement self.wobbleOffset += 0.1; var wobbleX = Math.cos(self.wobbleOffset) * 50; // Side-to-side wobble var wobbleY = Math.sin(self.wobbleOffset * 0.5) * 30; // Up-down wobble // Move in direction of angle with wobble self.x += Math.cos(self.angle) * self.speed * globalSpeedMultiplier + wobbleX * 0.02; self.y += Math.sin(self.angle) * self.speed * globalSpeedMultiplier + wobbleY * 0.02; // Update lastX, lastY self.lastX = self.x; self.lastY = self.y; }; return self; }); // --- Player Class --- var Player = Container.expand(function () { var self = Container.call(this); // Attach player asset var playerAsset = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); // Set radius for collision self.radius = playerAsset.width / 2; // Flash effect on hit self.flash = function () { LK.effects.flashObject(self, 0xff0000, 600); }; return self; }); // --- Powerup Class --- var Powerup = Container.expand(function () { var self = Container.call(this); // Attach powerup asset var powerupAsset = self.attachAsset('powerup', { anchorX: 0.5, anchorY: 0.5 }); // Set radius for collision self.radius = powerupAsset.width / 2; return self; }); // --- Trail Class --- var Trail = Container.expand(function (assetId) { var self = Container.call(this); // Attach the same asset as the enemy var trailAsset = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); // Start with some transparency self.alpha = 0.6; // Fade out over time using tween tween(self, { alpha: 0 }, { duration: 700, onFinish: function onFinish() { if (self.parent) { self.destroy(); } } }); return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x181c24 }); /**** * Game Code ****/ // --- Game Variables --- // --- Asset Initialization --- // Red, smaller, faster // Purple, wide, special var player; var enemies = []; var powerups = []; var trails = []; var dragNode = null; var lastPlayerPos = { x: 0, y: 0 }; var lastHit = false; var score = 0; var scoreTxt; var survivalTime = 0; var timeTxt; var spawnTimer = 0; var spawnInterval = 120; // frames (reduced from 150 to 120 for better balance) var powerupTimer = 0; var powerupInterval = 300; // frames (was 600, now twice as frequent) var difficultyTimer = 0; var minSpawnInterval = 60; // Increased from 30 to 60 to keep a reasonable minimum spawn rate var gameStarted = false; // Control when gameplay begins globalSpeedMultiplier = 1.0; // Reset speed multiplier var globalSpeedMultiplier = 1.0; // Multiplier for all enemy speeds var enemy2SpawnMultiplier = 1.0; // Multiplier for Enemy2 spawn chance var enemy3SpawnMultiplier = 1.0; // Multiplier for Enemy3 spawn chance var bestScore = storage.bestScore || 0; // Load best score from storage var bestScoreTxt; var dodgeSoundIndex = 0; // Track which dodge sound to play next // --- UI --- scoreTxt = new Text2('Score: 0', { size: 100, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); timeTxt = new Text2('Time: 0.0', { size: 70, fill: 0xB0EAFF }); timeTxt.anchor.set(0.5, 0); LK.gui.top.addChild(timeTxt); timeTxt.y = 110; bestScoreTxt = new Text2('Best: ' + bestScore, { size: 60, fill: 0x00FF00 }); bestScoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(bestScoreTxt); bestScoreTxt.y = 180; // Warning text about shuriken increase var warningTxt = new Text2('Shurikens Will Increase Every 10 Seconds Be Careful!', { size: 60, fill: 0xFFFF00 }); warningTxt.anchor.set(0.5, 0); LK.gui.top.addChild(warningTxt); warningTxt.y = 300; // Fade out warning text after 3 seconds tween(warningTxt, { alpha: 0 }, { duration: 3000, onFinish: function onFinish() { warningTxt.visible = false; gameStarted = true; // Start the game when text fades out } }); // --- Player --- player = new Player(); player.x = 2048 / 2; player.y = 2732 / 2; game.addChild(player); // --- Helper Functions --- function resetGameVars() { // Check if current score is a new record before resetting if (score > bestScore) { bestScore = score; storage.bestScore = bestScore; // Save to storage } enemies = []; powerups = []; trails = []; score = 0; survivalTime = 0; spawnTimer = 0; spawnInterval = 90; powerupTimer = 0; difficultyTimer = 0; lastHit = false; gameStarted = false; // Reset game start state globalSpeedMultiplier = 1.0; // Reset speed multiplier enemy2SpawnMultiplier = 1.0; // Reset Enemy2 spawn multiplier enemy3SpawnMultiplier = 1.0; // Reset Enemy3 spawn multiplier scoreTxt.setText('Score: 0'); timeTxt.setText('Time: 0.0'); bestScoreTxt.setText('Best: ' + bestScore); player.x = 2048 / 2; player.y = 2732 / 2; // Show warning text again on reset warningTxt.visible = true; warningTxt.alpha = 1; // Fade out warning text after 3 seconds tween(warningTxt, { alpha: 0 }, { duration: 3000, onFinish: function onFinish() { warningTxt.visible = false; gameStarted = true; // Start the game when text fades out } }); } // --- Spawning --- function spawnEnemy() { // Spawn at random edge, move toward player var enemy; // After 10 seconds, start mixing in Enemy2, after 20 seconds add Enemy3 if (survivalTime >= 20) { var r = Math.random(); var enemy2Chance = Math.min(0.5, 0.3 * enemy2SpawnMultiplier); var enemy3Chance = Math.min(0.4, 0.2 * enemy3SpawnMultiplier); var enemyChance = Math.max(0.1, 0.4 - (enemy2Chance - 0.3) - (enemy3Chance - 0.2)); if (r < enemyChance) { enemy = new Enemy(); } else if (r < enemyChance + enemy2Chance) { enemy = new Enemy2(); } else { enemy = new Enemy3(); } } else if (survivalTime >= 10) { var r = Math.random(); var enemy2Chance = Math.min(0.7, 0.4 * enemy2SpawnMultiplier); var enemyChance = Math.max(0.3, 0.6 - (enemy2Chance - 0.4)); if (r < enemyChance) { enemy = new Enemy(); } else { enemy = new Enemy2(); } } else { enemy = new Enemy(); } var edge = Math.floor(Math.random() * 4); var x, y; if (edge === 0) { // Top x = 200 + Math.random() * (2048 - 400); y = -80; } else if (edge === 1) { // Bottom x = 200 + Math.random() * (2048 - 400); y = 2732 + 80; } else if (edge === 2) { // Left x = -80; y = 200 + Math.random() * (2732 - 400); } else { // Right x = 2048 + 80; y = 200 + Math.random() * (2732 - 400); } enemy.x = x; enemy.y = y; // Track spawn time for trail duration control enemy.spawnTime = LK.ticks; // Aim at player var dx = player.x - x; var dy = player.y - y; enemy.angle = Math.atan2(dy, dx); if (typeof baseEnemySpeed === "undefined") { baseEnemySpeed = 3; } // Only set speed for Enemy (default), others use their own if (enemy instanceof Enemy) { enemy.speed = baseEnemySpeed + Math.random() * 1 + survivalTime / 40; // Reduced random from 2 to 1, survival bonus from /20 to /40 } // Add more variety: after 30 seconds, occasionally spawn a random fast Enemy2 or a wobbly Enemy3 if (survivalTime >= 30 && Math.random() < 0.15) { if (Math.random() < 0.5) { enemy = new Enemy2(); enemy.speed += 2 + Math.random() * 2; // Super fast } else { enemy = new Enemy3(); enemy.speed += 1 + Math.random(); // Extra wobbly } enemy.x = x; enemy.y = y; enemy.spawnTime = LK.ticks; enemy.angle = Math.atan2(dy, dx); } enemies.push(enemy); game.addChild(enemy); } function spawnPowerup() { var powerup = new Powerup(); powerup.x = 200 + Math.random() * (2048 - 400); powerup.y = 200 + Math.random() * (2732 - 400); powerups.push(powerup); game.addChild(powerup); } // --- Collision --- function circleIntersect(a, b) { var dx = a.x - b.x; var dy = a.y - b.y; var dist = Math.sqrt(dx * dx + dy * dy); return dist < (a.radius + b.radius) * 0.85; } // --- Drag Controls --- function handleMove(x, y, obj) { if (dragNode) { // Only move player if dragging (i.e., finger/mouse is moving after down) // Calculate movement delta from lastPlayerPos if (lastPlayerPos.active) { var dx = x - lastPlayerPos.x; var dy = y - lastPlayerPos.y; // Only move if there is a delta (prevents teleport on first touch) if (dx !== 0 || dy !== 0) { var newX = dragNode.x + dx; var newY = dragNode.y + dy; // Clamp to game area (avoid top left 100x100) var px = Math.max(100 + player.radius, Math.min(2048 - player.radius, newX)); var py = Math.max(100 + player.radius, Math.min(2732 - player.radius, newY)); dragNode.x = px; dragNode.y = py; } } // Update lastPlayerPos for next move lastPlayerPos.x = x; lastPlayerPos.y = y; lastPlayerPos.active = true; } } game.move = handleMove; game.down = function (x, y, obj) { dragNode = player; // Record initial drag position, but do not move player yet lastPlayerPos.x = x; lastPlayerPos.y = y; lastPlayerPos.active = false; // Prevents teleport on first move }; game.up = function (x, y, obj) { dragNode = null; lastPlayerPos.active = false; }; // --- Main Update Loop --- game.update = function () { // Don't start gameplay until warning text has faded out if (!gameStarted) { return; } // Survival time survivalTime += 1 / 60; timeTxt.setText('Time: ' + survivalTime.toFixed(1)); // Difficulty scaling difficultyTimer++; if (difficultyTimer % 600 === 0) { // Every 10 seconds, increase difficulty // Decrease spawn interval (more frequent spawns) if (spawnInterval > minSpawnInterval) { spawnInterval -= 3; // Reduced from 8 to 3 for more gradual spawn rate increase if (spawnInterval < minSpawnInterval) spawnInterval = minSpawnInterval; } // Increase number of enemies spawned per interval (only every other interval) if (typeof enemiesPerSpawn === "undefined") { enemiesPerSpawn = 1; } if (difficultyTimer % 1200 === 0) { // Only increase every 20 seconds instead of 10 enemiesPerSpawn++; } // Increase base enemy speed if (typeof baseEnemySpeed === "undefined") { baseEnemySpeed = 3; } baseEnemySpeed += 0.3; // Reduced from 1.0 to 0.3 for more gradual speed increase // Increase global speed multiplier for all enemies globalSpeedMultiplier += 0.1; // Reduced from 0.3 to 0.1 for more gradual speed increase // Increase Enemy2 and Enemy3 spawn rates enemy2SpawnMultiplier += 0.2; // Reduced from 0.5 to 0.2 enemy3SpawnMultiplier += 0.15; // Reduced from 0.4 to 0.15 // Introduce new enemy types as time passes // (handled in spawnEnemy, but you can add more logic here if needed) } // Enemy spawn spawnTimer++; if (spawnTimer >= spawnInterval) { // Spawn multiple enemies per interval as difficulty increases if (typeof enemiesPerSpawn === "undefined") { enemiesPerSpawn = 1; } for (var s = 0; s < enemiesPerSpawn; s++) { spawnEnemy(); } spawnTimer = 0; } // Powerup spawn powerupTimer++; if (powerupTimer >= powerupInterval) { // Spawn 2 powerups per interval for more frequent yellow balls for (var i = 0; i < 2; i++) { spawnPowerup(); } powerupTimer = 0; } // Update enemies for (var i = enemies.length - 1; i >= 0; i--) { var e = enemies[i]; e.update(); // Create trail only when enemy is moving faster than base speed AND within spawn time limits var shouldCreateTrail = false; var timeSinceSpawn = (LK.ticks - (e.spawnTime || 0)) / 60; // Convert to seconds // Enemy has 2.25 seconds, Enemy2 and Enemy3 have 1 second var trailTimeLimit = e instanceof Enemy ? 2.25 : 1.0; if (timeSinceSpawn <= trailTimeLimit) { if (e instanceof Enemy && e.speed > baseEnemySpeed + 1) { shouldCreateTrail = true; } else if (e instanceof Enemy2 && e.speed > 5) { shouldCreateTrail = true; } else if (e instanceof Enemy3 && e.speed > 2) { shouldCreateTrail = true; } } if (shouldCreateTrail && LK.ticks % 8 === 0) { var assetId = 'enemy'; // Default if (e instanceof Enemy2) assetId = 'enemy2'; if (e instanceof Enemy3) assetId = 'enemy3'; var trail = new Trail(assetId); trail.x = e.x; trail.y = e.y; trail.rotation = e.rotation || 0; trails.push(trail); game.addChild(trail); } // Remove if off screen if (e.x < -200 || e.x > 2248 || e.y < -200 || e.y > 2932) { e.destroy(); enemies.splice(i, 1); continue; } // Check for close calls (near misses) var dx = e.x - player.x; var dy = e.y - player.y; var distance = Math.sqrt(dx * dx + dy * dy); var closeCallDistance = (e.radius + player.radius) * 1.5; // 1.5x collision distance // Initialize lastWasClose if not set if (typeof e.lastWasClose === "undefined") e.lastWasClose = false; var isCloseNow = distance <= closeCallDistance; // Trigger close call effect when transitioning from not close to close, but only once per enemy if (!e.lastWasClose && isCloseNow && !circleIntersect(e, player) && !e.hasSpawnedText) { // Create encouraging text var messages = ['Good!', 'Wow!', 'Excellent!', 'Nice!', 'Amazing!']; var message = messages[Math.floor(Math.random() * messages.length)]; var encourageText = new Text2(message, { size: 120, fill: 0x00FF00 }); encourageText.anchor.set(0.5, 0.5); encourageText.x = player.x + (Math.random() - 0.5) * 200; // Random offset around player encourageText.y = player.y + (Math.random() - 0.5) * 200; // Ensure text stays within screen bounds encourageText.x = Math.max(100, Math.min(2048 - 100, encourageText.x)); encourageText.y = Math.max(100, Math.min(2732 - 100, encourageText.y)); game.addChild(encourageText); // Animate text: pop up, then fade out encourageText.scaleX = 0.1; encourageText.scaleY = 0.1; encourageText.alpha = 1; // --- Bonus Points and Bonus Text --- score += 10; scoreTxt.setText('Score: ' + score); // Show "Bonus Point +10" text near the score table (top center) var bonusText = new Text2("Bonus Point +10", { size: 80, fill: 0xFFD700 }); bonusText.anchor.set(0.5, 0); bonusText.x = scoreTxt.x; bonusText.y = scoreTxt.y + scoreTxt.height + 10; LK.gui.top.addChild(bonusText); bonusText.alpha = 1; bonusText.scaleX = 0.7; bonusText.scaleY = 0.7; tween(bonusText, { scaleX: 1.1, scaleY: 1.1, y: bonusText.y - 30 }, { duration: 180, easing: tween.easeOut, onFinish: function onFinish() { tween(bonusText, { alpha: 0, y: bonusText.y - 30 }, { duration: 600, onFinish: function onFinish() { if (bonusText.parent) bonusText.destroy(); } }); } }); // Mark this enemy as having spawned text e.hasSpawnedText = true; // Play dodge sound effect - alternate between two sounds var soundName = dodgeSoundIndex === 0 ? 'dodge' : 'dodge2'; LK.getSound(soundName).play(); dodgeSoundIndex = (dodgeSoundIndex + 1) % 2; // Alternate between 0 and 1 // Player pop and flash effect for juicy feedback tween(player, { scaleX: 1.25, scaleY: 1.25 }, { duration: 80, onFinish: function onFinish() { tween(player, { scaleX: 1, scaleY: 1 }, { duration: 120 }); } }); LK.effects.flashObject(player, 0x00ffcc, 200); // First tween: pop up tween(encourageText, { scaleX: 1.2, scaleY: 1.2 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { // Second tween: fade out tween(encourageText, { alpha: 0, scaleX: 0.8, scaleY: 0.8, y: encourageText.y - 50 }, { duration: 800, easing: tween.easeIn, onFinish: function onFinish() { if (encourageText.parent) { encourageText.destroy(); } } }); } }); } // Update last close state e.lastWasClose = isCloseNow; // Collision with player if (circleIntersect(e, player)) { if (!lastHit) { // Player hit: pop and flash effect tween(player, { scaleX: 1.4, scaleY: 1.4 }, { duration: 100, onFinish: function onFinish() { tween(player, { scaleX: 1, scaleY: 1 }, { duration: 180 }); } }); player.flash(); LK.effects.flashScreen(0xff0000, 600); // Update best score before showing game over if (score > bestScore) { bestScore = score; storage.bestScore = bestScore; // Save to storage bestScoreTxt.setText('Best: ' + bestScore); LK.showGameOver('New Record: ' + score); } else { LK.showGameOver(); } lastHit = true; return; } } } // Clean up destroyed trails for (var t = trails.length - 1; t >= 0; t--) { var trail = trails[t]; if (!trail.parent || trail.alpha <= 0) { if (trail.parent) trail.destroy(); trails.splice(t, 1); } } // Remove dead enemies if game over if (lastHit) { for (var j = 0; j < enemies.length; j++) { enemies[j].destroy(); } enemies = []; for (var k = 0; k < powerups.length; k++) { powerups[k].destroy(); } powerups = []; for (var l = 0; l < trails.length; l++) { trails[l].destroy(); } trails = []; return; } // Update powerups for (var p = powerups.length - 1; p >= 0; p--) { var pu = powerups[p]; // Collision with player if (circleIntersect(pu, player)) { score += 10; scoreTxt.setText('Score: ' + score); // Create pop effect before destroying powerup tween(pu, { scaleX: 2.5, scaleY: 2.5, alpha: 0 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { if (pu.parent) { pu.destroy(); } } }); // Player pop and flash effect for collecting powerup tween(player, { scaleX: 1.18, scaleY: 1.18 }, { duration: 80, onFinish: function onFinish() { tween(player, { scaleX: 1, scaleY: 1 }, { duration: 120 }); } }); LK.effects.flashObject(player, 0xffff00, 180); powerups.splice(p, 1); continue; } } // Score increases with time if (Math.floor(survivalTime * 10) % 10 === 0) { var newScore = Math.floor(survivalTime); if (newScore > score) { score = newScore; scoreTxt.setText('Score: ' + score); } } }; // --- Game Over Reset --- LK.on('gameover', function () { resetGameVars(); }); // --- You Win (not used in endless, but for completeness) --- LK.on('youwin', function () { resetGameVars(); }); // --- Start Game Music --- LK.playMusic('Game-music');
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// --- Enemy Class ---
var Enemy = Container.expand(function () {
var self = Container.call(this);
// Attach enemy asset (image)
var enemyAsset = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = enemyAsset.width / 2;
self.speed = 3;
self.angle = 0;
self.hasSpawnedText = false; // Flag to track if this enemy already spawned text
self.update = function () {
// Store lastX, lastY for good practice (if needed for triggers)
if (typeof self.lastX === "undefined") self.lastX = self.x;
if (typeof self.lastY === "undefined") self.lastY = self.y;
// Move in direction of angle
self.x += Math.cos(self.angle) * self.speed * globalSpeedMultiplier;
self.y += Math.sin(self.angle) * self.speed * globalSpeedMultiplier;
// Update lastX, lastY
self.lastX = self.x;
self.lastY = self.y;
};
return self;
});
// --- Enemy2 Class (Red, smaller, faster) ---
var Enemy2 = Container.expand(function () {
var self = Container.call(this);
// Attach enemy2 asset (image)
var enemyAsset = self.attachAsset('enemy2', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = enemyAsset.width / 2;
self.speed = 5; // Faster than regular enemy
self.angle = 0;
self.hasSpawnedText = false; // Flag to track if this enemy already spawned text
self.update = function () {
// Store lastX, lastY for good practice
if (typeof self.lastX === "undefined") self.lastX = self.x;
if (typeof self.lastY === "undefined") self.lastY = self.y;
// Move in direction of angle
self.x += Math.cos(self.angle) * self.speed * globalSpeedMultiplier;
self.y += Math.sin(self.angle) * self.speed * globalSpeedMultiplier;
// Update lastX, lastY
self.lastX = self.x;
self.lastY = self.y;
};
return self;
});
// --- Enemy3 Class (Purple, wide, special movement) ---
var Enemy3 = Container.expand(function () {
var self = Container.call(this);
// Attach enemy3 asset (image)
var enemyAsset = self.attachAsset('enemy3', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = enemyAsset.width / 2;
self.speed = 2; // Slower but wider
self.angle = 0;
self.wobbleOffset = 0; // For special movement pattern
self.hasSpawnedText = false; // Flag to track if this enemy already spawned text
self.update = function () {
// Store lastX, lastY for good practice
if (typeof self.lastX === "undefined") self.lastX = self.x;
if (typeof self.lastY === "undefined") self.lastY = self.y;
// Special wobble movement
self.wobbleOffset += 0.1;
var wobbleX = Math.cos(self.wobbleOffset) * 50; // Side-to-side wobble
var wobbleY = Math.sin(self.wobbleOffset * 0.5) * 30; // Up-down wobble
// Move in direction of angle with wobble
self.x += Math.cos(self.angle) * self.speed * globalSpeedMultiplier + wobbleX * 0.02;
self.y += Math.sin(self.angle) * self.speed * globalSpeedMultiplier + wobbleY * 0.02;
// Update lastX, lastY
self.lastX = self.x;
self.lastY = self.y;
};
return self;
});
// --- Player Class ---
var Player = Container.expand(function () {
var self = Container.call(this);
// Attach player asset
var playerAsset = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
// Set radius for collision
self.radius = playerAsset.width / 2;
// Flash effect on hit
self.flash = function () {
LK.effects.flashObject(self, 0xff0000, 600);
};
return self;
});
// --- Powerup Class ---
var Powerup = Container.expand(function () {
var self = Container.call(this);
// Attach powerup asset
var powerupAsset = self.attachAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5
});
// Set radius for collision
self.radius = powerupAsset.width / 2;
return self;
});
// --- Trail Class ---
var Trail = Container.expand(function (assetId) {
var self = Container.call(this);
// Attach the same asset as the enemy
var trailAsset = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Start with some transparency
self.alpha = 0.6;
// Fade out over time using tween
tween(self, {
alpha: 0
}, {
duration: 700,
onFinish: function onFinish() {
if (self.parent) {
self.destroy();
}
}
});
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181c24
});
/****
* Game Code
****/
// --- Game Variables ---
// --- Asset Initialization ---
// Red, smaller, faster
// Purple, wide, special
var player;
var enemies = [];
var powerups = [];
var trails = [];
var dragNode = null;
var lastPlayerPos = {
x: 0,
y: 0
};
var lastHit = false;
var score = 0;
var scoreTxt;
var survivalTime = 0;
var timeTxt;
var spawnTimer = 0;
var spawnInterval = 120; // frames (reduced from 150 to 120 for better balance)
var powerupTimer = 0;
var powerupInterval = 300; // frames (was 600, now twice as frequent)
var difficultyTimer = 0;
var minSpawnInterval = 60; // Increased from 30 to 60 to keep a reasonable minimum spawn rate
var gameStarted = false; // Control when gameplay begins
globalSpeedMultiplier = 1.0; // Reset speed multiplier
var globalSpeedMultiplier = 1.0; // Multiplier for all enemy speeds
var enemy2SpawnMultiplier = 1.0; // Multiplier for Enemy2 spawn chance
var enemy3SpawnMultiplier = 1.0; // Multiplier for Enemy3 spawn chance
var bestScore = storage.bestScore || 0; // Load best score from storage
var bestScoreTxt;
var dodgeSoundIndex = 0; // Track which dodge sound to play next
// --- UI ---
scoreTxt = new Text2('Score: 0', {
size: 100,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
timeTxt = new Text2('Time: 0.0', {
size: 70,
fill: 0xB0EAFF
});
timeTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(timeTxt);
timeTxt.y = 110;
bestScoreTxt = new Text2('Best: ' + bestScore, {
size: 60,
fill: 0x00FF00
});
bestScoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(bestScoreTxt);
bestScoreTxt.y = 180;
// Warning text about shuriken increase
var warningTxt = new Text2('Shurikens Will Increase Every 10 Seconds Be Careful!', {
size: 60,
fill: 0xFFFF00
});
warningTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(warningTxt);
warningTxt.y = 300;
// Fade out warning text after 3 seconds
tween(warningTxt, {
alpha: 0
}, {
duration: 3000,
onFinish: function onFinish() {
warningTxt.visible = false;
gameStarted = true; // Start the game when text fades out
}
});
// --- Player ---
player = new Player();
player.x = 2048 / 2;
player.y = 2732 / 2;
game.addChild(player);
// --- Helper Functions ---
function resetGameVars() {
// Check if current score is a new record before resetting
if (score > bestScore) {
bestScore = score;
storage.bestScore = bestScore; // Save to storage
}
enemies = [];
powerups = [];
trails = [];
score = 0;
survivalTime = 0;
spawnTimer = 0;
spawnInterval = 90;
powerupTimer = 0;
difficultyTimer = 0;
lastHit = false;
gameStarted = false; // Reset game start state
globalSpeedMultiplier = 1.0; // Reset speed multiplier
enemy2SpawnMultiplier = 1.0; // Reset Enemy2 spawn multiplier
enemy3SpawnMultiplier = 1.0; // Reset Enemy3 spawn multiplier
scoreTxt.setText('Score: 0');
timeTxt.setText('Time: 0.0');
bestScoreTxt.setText('Best: ' + bestScore);
player.x = 2048 / 2;
player.y = 2732 / 2;
// Show warning text again on reset
warningTxt.visible = true;
warningTxt.alpha = 1;
// Fade out warning text after 3 seconds
tween(warningTxt, {
alpha: 0
}, {
duration: 3000,
onFinish: function onFinish() {
warningTxt.visible = false;
gameStarted = true; // Start the game when text fades out
}
});
}
// --- Spawning ---
function spawnEnemy() {
// Spawn at random edge, move toward player
var enemy;
// After 10 seconds, start mixing in Enemy2, after 20 seconds add Enemy3
if (survivalTime >= 20) {
var r = Math.random();
var enemy2Chance = Math.min(0.5, 0.3 * enemy2SpawnMultiplier);
var enemy3Chance = Math.min(0.4, 0.2 * enemy3SpawnMultiplier);
var enemyChance = Math.max(0.1, 0.4 - (enemy2Chance - 0.3) - (enemy3Chance - 0.2));
if (r < enemyChance) {
enemy = new Enemy();
} else if (r < enemyChance + enemy2Chance) {
enemy = new Enemy2();
} else {
enemy = new Enemy3();
}
} else if (survivalTime >= 10) {
var r = Math.random();
var enemy2Chance = Math.min(0.7, 0.4 * enemy2SpawnMultiplier);
var enemyChance = Math.max(0.3, 0.6 - (enemy2Chance - 0.4));
if (r < enemyChance) {
enemy = new Enemy();
} else {
enemy = new Enemy2();
}
} else {
enemy = new Enemy();
}
var edge = Math.floor(Math.random() * 4);
var x, y;
if (edge === 0) {
// Top
x = 200 + Math.random() * (2048 - 400);
y = -80;
} else if (edge === 1) {
// Bottom
x = 200 + Math.random() * (2048 - 400);
y = 2732 + 80;
} else if (edge === 2) {
// Left
x = -80;
y = 200 + Math.random() * (2732 - 400);
} else {
// Right
x = 2048 + 80;
y = 200 + Math.random() * (2732 - 400);
}
enemy.x = x;
enemy.y = y;
// Track spawn time for trail duration control
enemy.spawnTime = LK.ticks;
// Aim at player
var dx = player.x - x;
var dy = player.y - y;
enemy.angle = Math.atan2(dy, dx);
if (typeof baseEnemySpeed === "undefined") {
baseEnemySpeed = 3;
}
// Only set speed for Enemy (default), others use their own
if (enemy instanceof Enemy) {
enemy.speed = baseEnemySpeed + Math.random() * 1 + survivalTime / 40; // Reduced random from 2 to 1, survival bonus from /20 to /40
}
// Add more variety: after 30 seconds, occasionally spawn a random fast Enemy2 or a wobbly Enemy3
if (survivalTime >= 30 && Math.random() < 0.15) {
if (Math.random() < 0.5) {
enemy = new Enemy2();
enemy.speed += 2 + Math.random() * 2; // Super fast
} else {
enemy = new Enemy3();
enemy.speed += 1 + Math.random(); // Extra wobbly
}
enemy.x = x;
enemy.y = y;
enemy.spawnTime = LK.ticks;
enemy.angle = Math.atan2(dy, dx);
}
enemies.push(enemy);
game.addChild(enemy);
}
function spawnPowerup() {
var powerup = new Powerup();
powerup.x = 200 + Math.random() * (2048 - 400);
powerup.y = 200 + Math.random() * (2732 - 400);
powerups.push(powerup);
game.addChild(powerup);
}
// --- Collision ---
function circleIntersect(a, b) {
var dx = a.x - b.x;
var dy = a.y - b.y;
var dist = Math.sqrt(dx * dx + dy * dy);
return dist < (a.radius + b.radius) * 0.85;
}
// --- Drag Controls ---
function handleMove(x, y, obj) {
if (dragNode) {
// Only move player if dragging (i.e., finger/mouse is moving after down)
// Calculate movement delta from lastPlayerPos
if (lastPlayerPos.active) {
var dx = x - lastPlayerPos.x;
var dy = y - lastPlayerPos.y;
// Only move if there is a delta (prevents teleport on first touch)
if (dx !== 0 || dy !== 0) {
var newX = dragNode.x + dx;
var newY = dragNode.y + dy;
// Clamp to game area (avoid top left 100x100)
var px = Math.max(100 + player.radius, Math.min(2048 - player.radius, newX));
var py = Math.max(100 + player.radius, Math.min(2732 - player.radius, newY));
dragNode.x = px;
dragNode.y = py;
}
}
// Update lastPlayerPos for next move
lastPlayerPos.x = x;
lastPlayerPos.y = y;
lastPlayerPos.active = true;
}
}
game.move = handleMove;
game.down = function (x, y, obj) {
dragNode = player;
// Record initial drag position, but do not move player yet
lastPlayerPos.x = x;
lastPlayerPos.y = y;
lastPlayerPos.active = false; // Prevents teleport on first move
};
game.up = function (x, y, obj) {
dragNode = null;
lastPlayerPos.active = false;
};
// --- Main Update Loop ---
game.update = function () {
// Don't start gameplay until warning text has faded out
if (!gameStarted) {
return;
}
// Survival time
survivalTime += 1 / 60;
timeTxt.setText('Time: ' + survivalTime.toFixed(1));
// Difficulty scaling
difficultyTimer++;
if (difficultyTimer % 600 === 0) {
// Every 10 seconds, increase difficulty
// Decrease spawn interval (more frequent spawns)
if (spawnInterval > minSpawnInterval) {
spawnInterval -= 3; // Reduced from 8 to 3 for more gradual spawn rate increase
if (spawnInterval < minSpawnInterval) spawnInterval = minSpawnInterval;
}
// Increase number of enemies spawned per interval (only every other interval)
if (typeof enemiesPerSpawn === "undefined") {
enemiesPerSpawn = 1;
}
if (difficultyTimer % 1200 === 0) {
// Only increase every 20 seconds instead of 10
enemiesPerSpawn++;
}
// Increase base enemy speed
if (typeof baseEnemySpeed === "undefined") {
baseEnemySpeed = 3;
}
baseEnemySpeed += 0.3; // Reduced from 1.0 to 0.3 for more gradual speed increase
// Increase global speed multiplier for all enemies
globalSpeedMultiplier += 0.1; // Reduced from 0.3 to 0.1 for more gradual speed increase
// Increase Enemy2 and Enemy3 spawn rates
enemy2SpawnMultiplier += 0.2; // Reduced from 0.5 to 0.2
enemy3SpawnMultiplier += 0.15; // Reduced from 0.4 to 0.15
// Introduce new enemy types as time passes
// (handled in spawnEnemy, but you can add more logic here if needed)
}
// Enemy spawn
spawnTimer++;
if (spawnTimer >= spawnInterval) {
// Spawn multiple enemies per interval as difficulty increases
if (typeof enemiesPerSpawn === "undefined") {
enemiesPerSpawn = 1;
}
for (var s = 0; s < enemiesPerSpawn; s++) {
spawnEnemy();
}
spawnTimer = 0;
}
// Powerup spawn
powerupTimer++;
if (powerupTimer >= powerupInterval) {
// Spawn 2 powerups per interval for more frequent yellow balls
for (var i = 0; i < 2; i++) {
spawnPowerup();
}
powerupTimer = 0;
}
// Update enemies
for (var i = enemies.length - 1; i >= 0; i--) {
var e = enemies[i];
e.update();
// Create trail only when enemy is moving faster than base speed AND within spawn time limits
var shouldCreateTrail = false;
var timeSinceSpawn = (LK.ticks - (e.spawnTime || 0)) / 60; // Convert to seconds
// Enemy has 2.25 seconds, Enemy2 and Enemy3 have 1 second
var trailTimeLimit = e instanceof Enemy ? 2.25 : 1.0;
if (timeSinceSpawn <= trailTimeLimit) {
if (e instanceof Enemy && e.speed > baseEnemySpeed + 1) {
shouldCreateTrail = true;
} else if (e instanceof Enemy2 && e.speed > 5) {
shouldCreateTrail = true;
} else if (e instanceof Enemy3 && e.speed > 2) {
shouldCreateTrail = true;
}
}
if (shouldCreateTrail && LK.ticks % 8 === 0) {
var assetId = 'enemy'; // Default
if (e instanceof Enemy2) assetId = 'enemy2';
if (e instanceof Enemy3) assetId = 'enemy3';
var trail = new Trail(assetId);
trail.x = e.x;
trail.y = e.y;
trail.rotation = e.rotation || 0;
trails.push(trail);
game.addChild(trail);
}
// Remove if off screen
if (e.x < -200 || e.x > 2248 || e.y < -200 || e.y > 2932) {
e.destroy();
enemies.splice(i, 1);
continue;
}
// Check for close calls (near misses)
var dx = e.x - player.x;
var dy = e.y - player.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var closeCallDistance = (e.radius + player.radius) * 1.5; // 1.5x collision distance
// Initialize lastWasClose if not set
if (typeof e.lastWasClose === "undefined") e.lastWasClose = false;
var isCloseNow = distance <= closeCallDistance;
// Trigger close call effect when transitioning from not close to close, but only once per enemy
if (!e.lastWasClose && isCloseNow && !circleIntersect(e, player) && !e.hasSpawnedText) {
// Create encouraging text
var messages = ['Good!', 'Wow!', 'Excellent!', 'Nice!', 'Amazing!'];
var message = messages[Math.floor(Math.random() * messages.length)];
var encourageText = new Text2(message, {
size: 120,
fill: 0x00FF00
});
encourageText.anchor.set(0.5, 0.5);
encourageText.x = player.x + (Math.random() - 0.5) * 200; // Random offset around player
encourageText.y = player.y + (Math.random() - 0.5) * 200;
// Ensure text stays within screen bounds
encourageText.x = Math.max(100, Math.min(2048 - 100, encourageText.x));
encourageText.y = Math.max(100, Math.min(2732 - 100, encourageText.y));
game.addChild(encourageText);
// Animate text: pop up, then fade out
encourageText.scaleX = 0.1;
encourageText.scaleY = 0.1;
encourageText.alpha = 1;
// --- Bonus Points and Bonus Text ---
score += 10;
scoreTxt.setText('Score: ' + score);
// Show "Bonus Point +10" text near the score table (top center)
var bonusText = new Text2("Bonus Point +10", {
size: 80,
fill: 0xFFD700
});
bonusText.anchor.set(0.5, 0);
bonusText.x = scoreTxt.x;
bonusText.y = scoreTxt.y + scoreTxt.height + 10;
LK.gui.top.addChild(bonusText);
bonusText.alpha = 1;
bonusText.scaleX = 0.7;
bonusText.scaleY = 0.7;
tween(bonusText, {
scaleX: 1.1,
scaleY: 1.1,
y: bonusText.y - 30
}, {
duration: 180,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(bonusText, {
alpha: 0,
y: bonusText.y - 30
}, {
duration: 600,
onFinish: function onFinish() {
if (bonusText.parent) bonusText.destroy();
}
});
}
});
// Mark this enemy as having spawned text
e.hasSpawnedText = true;
// Play dodge sound effect - alternate between two sounds
var soundName = dodgeSoundIndex === 0 ? 'dodge' : 'dodge2';
LK.getSound(soundName).play();
dodgeSoundIndex = (dodgeSoundIndex + 1) % 2; // Alternate between 0 and 1
// Player pop and flash effect for juicy feedback
tween(player, {
scaleX: 1.25,
scaleY: 1.25
}, {
duration: 80,
onFinish: function onFinish() {
tween(player, {
scaleX: 1,
scaleY: 1
}, {
duration: 120
});
}
});
LK.effects.flashObject(player, 0x00ffcc, 200);
// First tween: pop up
tween(encourageText, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
// Second tween: fade out
tween(encourageText, {
alpha: 0,
scaleX: 0.8,
scaleY: 0.8,
y: encourageText.y - 50
}, {
duration: 800,
easing: tween.easeIn,
onFinish: function onFinish() {
if (encourageText.parent) {
encourageText.destroy();
}
}
});
}
});
}
// Update last close state
e.lastWasClose = isCloseNow;
// Collision with player
if (circleIntersect(e, player)) {
if (!lastHit) {
// Player hit: pop and flash effect
tween(player, {
scaleX: 1.4,
scaleY: 1.4
}, {
duration: 100,
onFinish: function onFinish() {
tween(player, {
scaleX: 1,
scaleY: 1
}, {
duration: 180
});
}
});
player.flash();
LK.effects.flashScreen(0xff0000, 600);
// Update best score before showing game over
if (score > bestScore) {
bestScore = score;
storage.bestScore = bestScore; // Save to storage
bestScoreTxt.setText('Best: ' + bestScore);
LK.showGameOver('New Record: ' + score);
} else {
LK.showGameOver();
}
lastHit = true;
return;
}
}
}
// Clean up destroyed trails
for (var t = trails.length - 1; t >= 0; t--) {
var trail = trails[t];
if (!trail.parent || trail.alpha <= 0) {
if (trail.parent) trail.destroy();
trails.splice(t, 1);
}
}
// Remove dead enemies if game over
if (lastHit) {
for (var j = 0; j < enemies.length; j++) {
enemies[j].destroy();
}
enemies = [];
for (var k = 0; k < powerups.length; k++) {
powerups[k].destroy();
}
powerups = [];
for (var l = 0; l < trails.length; l++) {
trails[l].destroy();
}
trails = [];
return;
}
// Update powerups
for (var p = powerups.length - 1; p >= 0; p--) {
var pu = powerups[p];
// Collision with player
if (circleIntersect(pu, player)) {
score += 10;
scoreTxt.setText('Score: ' + score);
// Create pop effect before destroying powerup
tween(pu, {
scaleX: 2.5,
scaleY: 2.5,
alpha: 0
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
if (pu.parent) {
pu.destroy();
}
}
});
// Player pop and flash effect for collecting powerup
tween(player, {
scaleX: 1.18,
scaleY: 1.18
}, {
duration: 80,
onFinish: function onFinish() {
tween(player, {
scaleX: 1,
scaleY: 1
}, {
duration: 120
});
}
});
LK.effects.flashObject(player, 0xffff00, 180);
powerups.splice(p, 1);
continue;
}
}
// Score increases with time
if (Math.floor(survivalTime * 10) % 10 === 0) {
var newScore = Math.floor(survivalTime);
if (newScore > score) {
score = newScore;
scoreTxt.setText('Score: ' + score);
}
}
};
// --- Game Over Reset ---
LK.on('gameover', function () {
resetGameVars();
});
// --- You Win (not used in endless, but for completeness) ---
LK.on('youwin', function () {
resetGameVars();
});
// --- Start Game Music ---
LK.playMusic('Game-music');