User prompt
make the boss faster and add various attack mechanics with visual effects ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
enemies can drop a health potion with a chance of %10 that restores 1 heart
User prompt
make the boss projectile face the direction it was thornw
User prompt
spawn boss in 6th dungein
User prompt
do not spawn any other mobs in boss dungeon and give alert to plaher before the dungeon starts
User prompt
can you make the buttons bigger and apart of each kther
User prompt
the last request didnt change anyting can you try something different for it
User prompt
If a movement key is held down and the finger is moved to another movement key, that movement key is being pressed. instead of still pressing initial button
User prompt
add 2 more dungeon and add a bossfight to last dungeon
User prompt
add more enemy types
User prompt
make the games name and theme "Dungeon Crawler"
User prompt
increasethe alpha of bg asset to 1 for non transparent
User prompt
prevent enemies spawn too close to player at start
User prompt
add a background asset
User prompt
make he speed enemies faster
User prompt
increase the speed of the regular enemies and make them die with 2 hits
User prompt
remove the visual effect and just particles stay
User prompt
increase thhe number of particles
User prompt
add: knockback, blood particles, and visual effects when an enemy is hit ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
add a slash anim asset to the character
User prompt
add a 0.4 sec slash cooldown
User prompt
increase the slash range
User prompt
make the attack a sword slash instead of a punch
User prompt
make an indivdual asset for each enemy type
User prompt
prevent collision between plaer and enemies
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Coin = Container.expand(function () { var self = Container.call(this); var coinGraphics = self.attachAsset('coin', { anchorX: 0.5, anchorY: 0.5 }); self.collectTimer = 0; self.update = function () { // Auto-collect after short delay self.collectTimer++; if (self.collectTimer > 30) { // 0.5 seconds self.collect(); } // Spin animation coinGraphics.rotation += 0.1; }; self.collect = function () { LK.getSound('coin').play(); // Remove from coins array for (var i = coins.length - 1; i >= 0; i--) { if (coins[i] === self) { coins.splice(i, 1); break; } } self.destroy(); }; return self; }); var Enemy = Container.expand(function () { var self = Container.call(this); var enemyGraphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 1.0 }); self.health = 2; self.speed = 1; self.attackCooldown = 0; self.fromLeft = true; self.lastX = 0; self.lastY = 0; self.alerted = false; self.alertedByKnife = false; self.update = function () { var distanceToHero = Math.sqrt(Math.pow(self.x - hero.x, 2) + Math.pow(self.y - hero.y, 2)); var canSeeHero = distanceToHero < 500; // Line of sight range if (canSeeHero || self.alerted) { // Chase hero var dx = hero.x - self.x; var dy = hero.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { var newX = self.x + dx / distance * self.speed; var newY = self.y + dy / distance * self.speed; // Check collision with other enemies before moving var wouldCollide = false; for (var i = 0; i < enemies.length; i++) { var otherEnemy = enemies[i]; if (otherEnemy === self) continue; // Skip self var edx = newX - otherEnemy.x; var edy = newY - otherEnemy.y; var edistance = Math.sqrt(edx * edx + edy * edy); if (edistance < 70) { // Collision threshold between enemies wouldCollide = true; break; } } // Only move if no collision would occur if (!wouldCollide) { self.lastX = self.x; self.lastY = self.y; self.x = newX; self.y = newY; } // Face direction of movement enemyGraphics.scaleX = dx > 0 ? 1 : -1; } // Attack hero if close enough if (distanceToHero < 100 && self.attackCooldown <= 0) { hero.takeDamage(); self.attackCooldown = 120; // 2 seconds at 60fps } } else if (!self.alerted) { // Only roam if not alerted - alerted enemies keep chasing even when they can't see hero if (!self.roamDirection || Math.random() < 0.01) { self.roamDirection = { x: (Math.random() - 0.5) * 2, y: (Math.random() - 0.5) * 2 }; } var newX = self.x + self.roamDirection.x * self.speed * 0.5; var newY = self.y + self.roamDirection.y * self.speed * 0.5; // Check collision with other enemies before roaming var wouldCollide = false; for (var i = 0; i < enemies.length; i++) { var otherEnemy = enemies[i]; if (otherEnemy === self) continue; // Skip self var edx = newX - otherEnemy.x; var edy = newY - otherEnemy.y; var edistance = Math.sqrt(edx * edx + edy * edy); if (edistance < 70) { // Collision threshold between enemies wouldCollide = true; break; } } // Keep within level bounds and check collisions if (!wouldCollide && newX > 100 && newX < currentLevelData.width - 100) { self.lastX = self.x; self.x = newX; enemyGraphics.scaleX = self.roamDirection.x > 0 ? 1 : -1; } if (!wouldCollide && newY > 1600 && newY < 2400) { self.lastY = self.y; self.y = newY; } } else { // Alerted enemies that can't see hero still try to chase in last known direction var dx = hero.x - self.x; var dy = hero.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { var newX = self.x + dx / distance * self.speed; var newY = self.y + dy / distance * self.speed; // Check collision with other enemies before moving var wouldCollide = false; for (var i = 0; i < enemies.length; i++) { var otherEnemy = enemies[i]; if (otherEnemy === self) continue; // Skip self var edx = newX - otherEnemy.x; var edy = newY - otherEnemy.y; var edistance = Math.sqrt(edx * edx + edy * edy); if (edistance < 70) { // Collision threshold between enemies wouldCollide = true; break; } } // Only move if no collision would occur if (!wouldCollide) { self.lastX = self.x; self.lastY = self.y; self.x = newX; self.y = newY; } // Face direction of movement enemyGraphics.scaleX = dx > 0 ? 1 : -1; } } if (self.attackCooldown > 0) { self.attackCooldown--; } }; self.takeDamage = function (fromKnife) { self.health--; LK.effects.flashObject(self, 0xffffff, 200); if (fromKnife) { self.alerted = true; self.alertedByKnife = true; } if (self.health <= 0) { self.die(); } }; self.die = function () { // Drop coin var coin = game.addChild(new Coin()); coin.x = self.x; coin.y = self.y; // Add score and combo var baseScore = 10; var comboMultiplier = Math.floor(hero.comboCount / 5) + 1; var finalScore = baseScore * comboMultiplier; LK.setScore(LK.getScore() + finalScore); hero.addCombo(); // Remove from enemies array for (var i = enemies.length - 1; i >= 0; i--) { if (enemies[i] === self) { enemies.splice(i, 1); break; } } self.destroy(); updateScoreDisplay(); updateEnemiesLeftDisplay(); }; return self; }); var EnemyWarning = Container.expand(function () { var self = Container.call(this); self.targetEnemy = null; self.direction = 'left'; // 'left', 'right', 'up', 'down' self.warningGraphics = null; self.lastAlpha = 0; self.isVisible = false; self.setDirection = function (direction) { if (self.warningGraphics) { self.warningGraphics.destroy(); } var assetName = 'warningArrow' + direction.charAt(0).toUpperCase() + direction.slice(1); self.warningGraphics = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5, alpha: 0 }); self.direction = direction; }; self.update = function () { if (!self.targetEnemy || !self.warningGraphics) return; // Check if enemy is still alive var enemyExists = false; for (var i = 0; i < enemies.length; i++) { if (enemies[i] === self.targetEnemy) { enemyExists = true; break; } } if (!enemyExists) { self.hide(); return; } // Calculate distance from hero to enemy var distanceToHero = Math.sqrt(Math.pow(self.targetEnemy.x - hero.x, 2) + Math.pow(self.targetEnemy.y - hero.y, 2)); // Check if enemy is visible on screen var enemyScreenX = self.targetEnemy.x - camera.x; var enemyScreenY = self.targetEnemy.y - camera.y; var isOnScreen = enemyScreenX >= -100 && enemyScreenX <= 2148 && enemyScreenY >= -100 && enemyScreenY <= 2832; if (isOnScreen) { self.hide(); return; } // Calculate warning opacity based on distance (closer = more visible) var maxDistance = 800; var minDistance = 300; var targetAlpha = 0; if (distanceToHero <= maxDistance) { var normalizedDistance = Math.max(0, Math.min(1, (maxDistance - distanceToHero) / (maxDistance - minDistance))); targetAlpha = normalizedDistance * 0.8; } // Smooth alpha transition if (Math.abs(targetAlpha - self.lastAlpha) > 0.01) { tween.stop(self.warningGraphics, { alpha: true }); tween(self.warningGraphics, { alpha: targetAlpha }, { duration: 200 }); self.lastAlpha = targetAlpha; } // Position warning at screen edge var screenCenterX = 1024; var screenCenterY = 1366; var dx = self.targetEnemy.x - hero.x; var dy = self.targetEnemy.y - hero.y; if (Math.abs(dx) > Math.abs(dy)) { // Horizontal warning if (dx > 0) { self.setDirection('right'); self.x = screenCenterX + 900; self.y = screenCenterY + Math.max(-600, Math.min(600, dy * 0.5)); } else { self.setDirection('left'); self.x = screenCenterX - 900; self.y = screenCenterY + Math.max(-600, Math.min(600, dy * 0.5)); } } else { // Vertical warning if (dy > 0) { self.setDirection('down'); self.x = screenCenterX + Math.max(-800, Math.min(800, dx * 0.5)); self.y = screenCenterY + 1200; } else { self.setDirection('up'); self.x = screenCenterX + Math.max(-800, Math.min(800, dx * 0.5)); self.y = screenCenterY - 1200; } } }; self.hide = function () { if (self.warningGraphics && self.warningGraphics.alpha > 0) { tween.stop(self.warningGraphics, { alpha: true }); tween(self.warningGraphics, { alpha: 0 }, { duration: 300 }); self.lastAlpha = 0; } }; return self; }); var Hero = Container.expand(function () { var self = Container.call(this); var heroGraphics = self.attachAsset('hero', { anchorX: 0.5, anchorY: 1.0 }); self.maxHealth = 5; self.health = self.maxHealth; self.isAttacking = false; self.invulnerable = false; self.damageBoost = false; self.comboCount = 0; self.lastX = 0; self.lastY = 0; self.isWalking = false; self.walkAnimationActive = false; self.walkAnimationFrame = 0; self.walkAnimationTimer = 0; self.walkAnimationSpeed = 10; // frames between texture changes self.attack = function (targetX) { if (self.isAttacking) return; self.isAttacking = true; // Face direction of attack if (targetX < self.x) { heroGraphics.scaleX = -1; } else { heroGraphics.scaleX = 1; } // Attack animation tween(heroGraphics, { scaleY: 1.2 }, { duration: 100 }); tween(heroGraphics, { scaleY: 1.0 }, { duration: 100, onFinish: function onFinish() { self.isAttacking = false; } }); // Create punch effect var effect = game.addChild(LK.getAsset('punchEffect', { anchorX: 0.5, anchorY: 0.5, alpha: 0.8 })); effect.x = self.x + heroGraphics.scaleX * 80; effect.y = self.y - 80; tween(effect, { scaleX: 2, scaleY: 2, alpha: 0 }, { duration: 200, onFinish: function onFinish() { effect.destroy(); } }); LK.getSound('punch').play(); }; self.takeDamage = function () { if (self.invulnerable) return; self.health--; self.comboCount = 0; // Flash red when hit LK.effects.flashObject(self, 0xff0000, 500); // Temporary invulnerability self.invulnerable = true; LK.setTimeout(function () { self.invulnerable = false; }, 1000); LK.getSound('hit').play(); updateHealthDisplay(); if (self.health <= 0) { LK.showGameOver(); } }; self.heal = function () { if (self.health < self.maxHealth) { self.health++; updateHealthDisplay(); } }; self.addCombo = function () { self.comboCount++; }; self.startWalkAnimation = function () { if (self.walkAnimationActive) return; self.walkAnimationActive = true; self.walkAnimationFrame = 0; self.walkAnimationTimer = 0; }; self.stopWalkAnimation = function () { self.isWalking = false; self.walkAnimationActive = false; // Store current scale direction before removing graphics var currentScaleX = heroGraphics.scaleX; // Reset to default hero texture self.removeChild(heroGraphics); heroGraphics = self.attachAsset('hero', { anchorX: 0.5, anchorY: 1.0 }); // Maintain the current scale direction if (currentScaleX < 0) { heroGraphics.scaleX = -1; } }; self.move = function (direction) { var speed = 8; var newX = self.x; var newY = self.y; var didMove = false; if (direction === 'left' && self.x > 100) { newX = self.x - speed; heroGraphics.scaleX = -1; didMove = true; } else if (direction === 'right' && self.x < currentLevelData.width - 100) { newX = self.x + speed; heroGraphics.scaleX = 1; didMove = true; } else if (direction === 'up' && self.y > 1600) { newY = self.y - speed; didMove = true; } else if (direction === 'down' && self.y < 2400) { newY = self.y + speed; didMove = true; } // Check collision with enemies before moving var wouldCollide = false; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx = newX - enemy.x; var dy = newY - enemy.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 120) { // Increased collision threshold to prevent overlap wouldCollide = true; break; } } // Only move if no collision would occur if (!wouldCollide && didMove) { self.lastX = self.x; self.lastY = self.y; self.x = newX; self.y = newY; // Start walking animation if (!self.isWalking) { self.isWalking = true; self.startWalkAnimation(); } // Update walking animation texture cycling if (self.walkAnimationActive) { self.walkAnimationTimer++; if (self.walkAnimationTimer >= self.walkAnimationSpeed) { self.walkAnimationTimer = 0; self.walkAnimationFrame = (self.walkAnimationFrame + 1) % 4; // Cycle through textures: hero, heroWalk1, heroWalk2, heroWalk3 var textureNames = ['hero', 'heroWalk1', 'heroWalk2', 'heroWalk3']; // Store current scale direction before removing graphics var currentScaleX = heroGraphics.scaleX; // Remove current graphics and add new one with correct texture self.removeChild(heroGraphics); heroGraphics = self.attachAsset(textureNames[self.walkAnimationFrame], { anchorX: 0.5, anchorY: 1.0 }); // Maintain the current scale direction if (currentScaleX < 0) { heroGraphics.scaleX = -1; } } } } // Update camera target camera.targetX = self.x - 1024; camera.targetY = self.y - 1366; // Clamp camera to level bounds camera.targetX = Math.max(0, Math.min(camera.targetX, currentLevelData.width - 2048)); camera.targetY = Math.max(0, Math.min(camera.targetY, currentLevelData.height - 2732)); }; return self; }); var Knife = Container.expand(function () { var self = Container.call(this); var knifeGraphics = self.attachAsset('knife', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 15; self.direction = 1; // 1 for right, -1 for left self.routePoints = []; // Points to follow along the route self.currentRouteIndex = 0; // Current target point index self.velocityX = 0; // Velocity for precise targeting self.velocityY = 0; // Velocity for precise targeting self.lastX = 0; self.lastY = 0; self.setRoute = function (routePoints) { self.routePoints = routePoints; self.currentRouteIndex = 0; }; self.update = function () { self.lastX = self.x; self.lastY = self.y; // Follow route if available if (self.routePoints.length > 0 && self.currentRouteIndex < self.routePoints.length) { var targetPoint = self.routePoints[self.currentRouteIndex]; var dx = targetPoint.x - self.x; var dy = targetPoint.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 20) { // Close enough to current target, move to next point self.currentRouteIndex++; } else { // Move toward current target point if (distance > 0) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; // Calculate rotation angle to face target direction var angle = Math.atan2(dy, dx); knifeGraphics.rotation = angle; } } } else if (self.velocityX !== 0 || self.velocityY !== 0) { // Use velocity if set, otherwise use direction-based movement self.x += self.velocityX; self.y += self.velocityY; // Calculate rotation for velocity-based movement var angle = Math.atan2(self.velocityY, self.velocityX); knifeGraphics.rotation = angle; } else { self.x += self.speed * self.direction; self.y -= 2; // Slight upward arc // Calculate rotation for direction-based movement var angle = Math.atan2(-2, self.speed * self.direction); knifeGraphics.rotation = angle; } // Check if knife went off screen if (self.x < -100 || self.x > currentLevelData.width + 100 || self.y < -100 || self.y > 2900) { self.destroy(); // Remove from knives array for (var i = knives.length - 1; i >= 0; i--) { if (knives[i] === self) { knives.splice(i, 1); break; } } } // Check collision with enemies (hit enemy center) for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx = self.x - enemy.x; var dy = self.y - (enemy.y - 70); // Target enemy center var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 50) { // Hit enemy center - deal damage only to first enemy encountered enemy.takeDamage(true); self.destroy(); // Remove from knives array for (var j = knives.length - 1; j >= 0; j--) { if (knives[j] === self) { knives.splice(j, 1); break; } } return; // Exit update immediately to prevent further movement or collision checks } } }; return self; }); var PowerUp = Container.expand(function () { var self = Container.call(this); var powerupGraphics = self.attachAsset('powerup', { anchorX: 0.5, anchorY: 0.5 }); self.type = 'health'; // 'health', 'invulnerable', 'damage' self.lifetime = 600; // 10 seconds self.update = function () { self.lifetime--; if (self.lifetime <= 0) { self.expire(); } // Check collision with hero if (self.intersects(hero)) { self.collect(); } // Pulse animation var scale = 1 + Math.sin(LK.ticks * 0.2) * 0.2; powerupGraphics.scaleX = scale; powerupGraphics.scaleY = scale; }; self.collect = function () { LK.getSound('powerup').play(); if (self.type === 'health') { hero.heal(); } else if (self.type === 'invulnerable') { hero.invulnerable = true; LK.setTimeout(function () { hero.invulnerable = false; }, 5000); } else if (self.type === 'damage') { hero.damageBoost = true; LK.setTimeout(function () { hero.damageBoost = false; }, 5000); } // Remove from powerups array for (var i = powerups.length - 1; i >= 0; i--) { if (powerups[i] === self) { powerups.splice(i, 1); break; } } self.destroy(); }; self.expire = function () { // Remove from powerups array for (var i = powerups.length - 1; i >= 0; i--) { if (powerups[i] === self) { powerups.splice(i, 1); break; } } self.destroy(); }; return self; }); var RouteEffect = Container.expand(function () { var self = Container.call(this); self.routePoints = []; self.routePositions = []; // Store just the x,y coordinates for knife to follow self.targetEnemy = null; self.createRoute = function (enemy) { self.targetEnemy = enemy; self.clearRoute(); // Calculate direct path from hero center to enemy center var heroStartX = hero.x; var heroStartY = hero.y - 80; // Middle of hero asset var enemyMiddleX = enemy.x; var enemyMiddleY = enemy.y - 70; // Middle of enemy asset (enemy height is 140, so middle is -70 from bottom) var dx = enemyMiddleX - heroStartX; var dy = enemyMiddleY - heroStartY; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { // Create route points along the path var numPoints = Math.floor(distance / 50); // Point every 50 pixels for (var i = 0; i <= numPoints; i++) { var t = i / numPoints; var x = heroStartX + dx * t; var y = heroStartY + dy * t; var routePoint = self.addChild(LK.getAsset('routeLine', { anchorX: 0.5, anchorY: 0.5, alpha: 0 })); routePoint.x = x; routePoint.y = y; self.routePoints.push(routePoint); // Store position for knife to follow self.routePositions.push({ x: x, y: y }); // Animate route points appearing with delay tween(routePoint, { alpha: 0.8, scaleX: 2, scaleY: 2 }, { duration: 100 + i * 20 }); } // Add impact effect at enemy position var impactEffect = self.addChild(LK.getAsset('routeEffect', { anchorX: 0.5, anchorY: 0.5, alpha: 0, scaleX: 0.5, scaleY: 0.5 })); impactEffect.x = enemyMiddleX; impactEffect.y = enemyMiddleY; self.routePoints.push(impactEffect); // Animate impact effect tween(impactEffect, { alpha: 1, scaleX: 3, scaleY: 3 }, { duration: 300, easing: tween.easeOut }); // Remove route after 0.5 seconds LK.setTimeout(function () { self.destroy(); // Remove from routeEffects array for (var i = routeEffects.length - 1; i >= 0; i--) { if (routeEffects[i] === self) { routeEffects.splice(i, 1); break; } } }, 500); } }; self.clearRoute = function () { for (var i = self.routePoints.length - 1; i >= 0; i--) { self.routePoints[i].destroy(); } self.routePoints = []; }; self.fadeOut = function () { for (var i = 0; i < self.routePoints.length; i++) { tween(self.routePoints[i], { alpha: 0, scaleX: 0.1, scaleY: 0.1 }, { duration: 200 }); } LK.setTimeout(function () { self.destroy(); }, 300); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x2c3e50 }); /**** * Game Code ****/ // Game variables var hero; var enemies = []; var coins = []; var powerups = []; var enemyWarnings = []; var knives = []; var knivesRemaining = 5; var routeEffects = []; var currentLevel = 1; var levelComplete = false; var hearts = []; // Movement state tracking var movementState = { left: false, right: false, up: false, down: false }; // Camera system var camera = { x: 0, y: 0, targetX: 0, targetY: 0, smoothing: 0.1 }; // Level configuration var levels = [{ enemies: [{ type: 'basic', count: 3 }], width: 4096, height: 2732 }, { enemies: [{ type: 'basic', count: 5 }, { type: 'strong', count: 2 }], width: 5120, height: 2732 }, { enemies: [{ type: 'basic', count: 7 }, { type: 'strong', count: 3 }, { type: 'fast', count: 2 }], width: 6144, height: 2732 }]; var currentLevelData = levels[0]; // UI Elements var scoreText = new Text2('Score: 0', { size: 80, fill: 0xFFFFFF }); scoreText.anchor.set(0, 0); scoreText.x = 150; scoreText.y = 50; LK.gui.topLeft.addChild(scoreText); var levelText = new Text2('Level: 1', { size: 80, fill: 0xFFFFFF }); levelText.anchor.set(0.5, 0); LK.gui.top.addChild(levelText); levelText.y = 50; var comboText = new Text2('Combo: 0x', { size: 60, fill: 0xFFFF00 }); comboText.anchor.set(1, 0); LK.gui.topRight.addChild(comboText); comboText.x = -50; comboText.y = 120; var knivesText = new Text2('Knives: 5', { size: 60, fill: 0x8e44ad }); knivesText.anchor.set(1, 0); LK.gui.topRight.addChild(knivesText); knivesText.x = -50; knivesText.y = 190; var enemiesLeftText = new Text2('Enemies: 0', { size: 60, fill: 0xff4444 }); enemiesLeftText.anchor.set(1, 0); LK.gui.topRight.addChild(enemiesLeftText); enemiesLeftText.x = -50; enemiesLeftText.y = 260; // Create movement and attack buttons var leftButton = LK.getAsset('leftButton', { anchorX: 0.5, anchorY: 0.5, alpha: 0.8 }); leftButton.x = 120; leftButton.y = -250; LK.gui.bottomLeft.addChild(leftButton); var rightButton = LK.getAsset('rightButton', { anchorX: 0.5, anchorY: 0.5, alpha: 0.8 }); rightButton.x = 400; rightButton.y = -250; LK.gui.bottomLeft.addChild(rightButton); var upButton = LK.getAsset('upButton', { anchorX: 0.5, anchorY: 0.5, alpha: 0.8 }); upButton.x = 260; upButton.y = -380; LK.gui.bottomLeft.addChild(upButton); var downButton = LK.getAsset('downButton', { anchorX: 0.5, anchorY: 0.5, alpha: 0.8 }); downButton.x = 260; downButton.y = -120; LK.gui.bottomLeft.addChild(downButton); var attackButton = LK.getAsset('attackButton', { anchorX: 0.5, anchorY: 0.5, alpha: 0.9 }); attackButton.x = -150; attackButton.y = -200; LK.gui.bottomRight.addChild(attackButton); var knifeButton = LK.getAsset('knifeButton', { anchorX: 0.5, anchorY: 0.5, alpha: 0.9 }); knifeButton.x = -350; knifeButton.y = -200; LK.gui.bottomRight.addChild(knifeButton); // Create background grid var backgroundTiles = []; function createBackgroundGrid() { // Clear existing background tiles for (var i = backgroundTiles.length - 1; i >= 0; i--) { backgroundTiles[i].destroy(); } backgroundTiles = []; var tileSize = 200; var tilesX = Math.ceil(currentLevelData.width / tileSize) + 2; var tilesY = Math.ceil(currentLevelData.height / tileSize) + 2; for (var x = 0; x < tilesX; x++) { for (var y = 0; y < tilesY; y++) { var tile = game.addChild(LK.getAsset('backgroundTile', { anchorX: 0, anchorY: 0, alpha: 0.3 })); tile.x = x * tileSize; tile.y = y * tileSize; // Add subtle pattern variation if ((x + y) % 2 === 0) { tile.alpha = 0.2; } backgroundTiles.push(tile); } } } // Create hero hero = game.addChild(new Hero()); hero.x = 1024; // Center of screen hero.y = 2300; // Near bottom // Create health display function updateHealthDisplay() { // Remove existing hearts for (var i = hearts.length - 1; i >= 0; i--) { hearts[i].destroy(); } hearts = []; // Create new hearts for (var i = 0; i < hero.health; i++) { var heart = LK.getAsset('heart', { anchorX: 0.5, anchorY: 0.5 }); heart.x = 200 + i * 80; heart.y = 200; LK.gui.topLeft.addChild(heart); hearts.push(heart); } } function updateScoreDisplay() { scoreText.setText('Score: ' + LK.getScore()); var comboMultiplier = Math.floor(hero.comboCount / 5) + 1; comboText.setText('Combo: ' + comboMultiplier + 'x'); } function updateKnivesDisplay() { knivesText.setText('Knives: ' + knivesRemaining); // Update button alpha based on availability knifeButton.alpha = knivesRemaining > 0 ? 0.9 : 0.3; } function updateEnemiesLeftDisplay() { enemiesLeftText.setText('Enemies: ' + enemies.length); } function initializeLevel() { // Clear existing enemies for (var i = enemies.length - 1; i >= 0; i--) { enemies[i].destroy(); } enemies = []; // Clear existing warnings for (var i = enemyWarnings.length - 1; i >= 0; i--) { enemyWarnings[i].destroy(); } enemyWarnings = []; // Clear existing knives for (var i = knives.length - 1; i >= 0; i--) { knives[i].destroy(); } knives = []; // Clear existing route effects for (var i = routeEffects.length - 1; i >= 0; i--) { routeEffects[i].destroy(); } routeEffects = []; // Reset knife count knivesRemaining = 5; currentLevelData = levels[currentLevel - 1] || levels[levels.length - 1]; levelComplete = false; // Spawn enemies for this level for (var j = 0; j < currentLevelData.enemies.length; j++) { var enemyGroup = currentLevelData.enemies[j]; for (var k = 0; k < enemyGroup.count; k++) { spawnEnemy(enemyGroup.type); } } levelText.setText('Level: ' + currentLevel); updateKnivesDisplay(); updateEnemiesLeftDisplay(); // Create background grid for this level createBackgroundGrid(); } function spawnEnemy(type) { var enemy = game.addChild(new Enemy()); // Random spawn position within level bounds enemy.x = 200 + Math.random() * (currentLevelData.width - 400); enemy.y = 1700 + Math.random() * 600; // Set enemy properties based on type if (type === 'basic') { enemy.health = 2; enemy.speed = 1; } else if (type === 'strong') { enemy.health = 4; enemy.speed = 0.8; enemy.getChildAt(0).tint = 0x8b4513; // Brown tint } else if (type === 'fast') { enemy.health = 1; enemy.speed = 2; enemy.getChildAt(0).tint = 0x00ff00; // Green tint } enemies.push(enemy); } function spawnPowerUp() { var powerup = game.addChild(new PowerUp()); powerup.x = 500 + Math.random() * 1048; // Random x position powerup.y = 1800 + Math.random() * 400; // Above ground level // Random powerup type var types = ['health', 'invulnerable', 'damage']; powerup.type = types[Math.floor(Math.random() * types.length)]; // Color by type var powerupGraphics = powerup.getChildAt(0); if (powerup.type === 'health') { powerupGraphics.tint = 0x00ff00; // Green } else if (powerup.type === 'invulnerable') { powerupGraphics.tint = 0x0088ff; // Blue } else if (powerup.type === 'damage') { powerupGraphics.tint = 0xff8800; // Orange } powerups.push(powerup); } function findNearestEnemy(x, y) { var nearest = null; var shortestDistance = Infinity; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var distance = Math.sqrt(Math.pow(enemy.x - x, 2) + Math.pow(enemy.y - y, 2)); if (distance < shortestDistance) { shortestDistance = distance; nearest = enemy; } } return nearest; } function updateEnemyWarnings() { // Remove warnings for dead enemies for (var i = enemyWarnings.length - 1; i >= 0; i--) { var warning = enemyWarnings[i]; var enemyExists = false; for (var j = 0; j < enemies.length; j++) { if (enemies[j] === warning.targetEnemy) { enemyExists = true; break; } } if (!enemyExists) { warning.destroy(); enemyWarnings.splice(i, 1); } } // Create warnings for new enemies for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var hasWarning = false; for (var j = 0; j < enemyWarnings.length; j++) { if (enemyWarnings[j].targetEnemy === enemy) { hasWarning = true; break; } } if (!hasWarning) { var warning = LK.gui.center.addChild(new EnemyWarning()); warning.targetEnemy = enemy; warning.setDirection('left'); enemyWarnings.push(warning); } } } // Initialize UI updateHealthDisplay(); updateScoreDisplay(); updateKnivesDisplay(); updateEnemiesLeftDisplay(); // Initialize first level initializeLevel(); // Create initial background grid createBackgroundGrid(); // Set initial camera position camera.targetX = hero.x - 1024; camera.targetY = hero.y - 1366; camera.x = camera.targetX; camera.y = camera.targetY; // Button event handlers leftButton.down = function (x, y, obj) { movementState.left = true; }; leftButton.up = function (x, y, obj) { movementState.left = false; }; rightButton.down = function (x, y, obj) { movementState.right = true; }; rightButton.up = function (x, y, obj) { movementState.right = false; }; upButton.down = function (x, y, obj) { movementState.up = true; }; upButton.up = function (x, y, obj) { movementState.up = false; }; downButton.down = function (x, y, obj) { movementState.down = true; }; downButton.up = function (x, y, obj) { movementState.down = false; }; attackButton.down = function (x, y, obj) { if (!hero.isAttacking) { var nearestEnemy = findNearestEnemy(hero.x, hero.y); if (nearestEnemy) { hero.attack(nearestEnemy.x); // Check if attack hits with increased range var distanceToEnemy = Math.sqrt(Math.pow(hero.x - nearestEnemy.x, 2) + Math.pow(hero.y - nearestEnemy.y, 2)); if (distanceToEnemy < 250) { // Increased from 150 to 250 var damage = hero.damageBoost ? 2 : 1; for (var i = 0; i < damage; i++) { nearestEnemy.takeDamage(); } } } else { // Attack in hero's facing direction hero.attack(hero.x + (hero.getChildAt(0).scaleX > 0 ? 100 : -100)); } } }; knifeButton.down = function (x, y, obj) { if (knivesRemaining > 0) { // Find nearest enemy for targeting var nearestEnemy = findNearestEnemy(hero.x, hero.y); if (nearestEnemy) { // Clear existing route effects before creating new one for (var i = routeEffects.length - 1; i >= 0; i--) { routeEffects[i].destroy(); routeEffects.splice(i, 1); } // Create route visualization var routeEffect = game.addChild(new RouteEffect()); routeEffect.createRoute(nearestEnemy); routeEffects.push(routeEffect); // Throw knife to follow the route var knife = game.addChild(new Knife()); knife.x = hero.x; knife.y = hero.y - 80; // Set the route for the knife to follow knife.setRoute(routeEffect.routePositions); // Rotation will be handled automatically in knife update based on target direction knives.push(knife); knivesRemaining--; updateKnivesDisplay(); LK.getSound('knifeThrow').play(); } else { // No enemy found, throw in hero facing direction var knife = game.addChild(new Knife()); knife.x = hero.x; knife.y = hero.y - 80; var heroGraphics = hero.getChildAt(0); knife.direction = heroGraphics.scaleX > 0 ? 1 : -1; // Rotation will be handled automatically in knife update based on movement direction knives.push(knife); knivesRemaining--; updateKnivesDisplay(); LK.getSound('knifeThrow').play(); } } }; // Game input (fallback for screen taps outside buttons) game.down = function (x, y, obj) { // Convert screen coordinates to world coordinates var worldX = x + camera.x; var worldY = y + camera.y; // Check if tap is for movement or attack var distanceToHero = Math.sqrt(Math.pow(worldX - hero.x, 2) + Math.pow(worldY - hero.y, 2)); if (distanceToHero > 200) { // Movement - move toward tap position var dx = worldX - hero.x; var dy = worldY - hero.y; if (Math.abs(dx) > Math.abs(dy)) { hero.move(dx > 0 ? 'right' : 'left'); } else { hero.move(dy > 0 ? 'down' : 'up'); } } else { // Attack if (!hero.isAttacking) { var nearestEnemy = findNearestEnemy(worldX, worldY); if (nearestEnemy) { hero.attack(nearestEnemy.x); // Check if attack hits with increased range var distanceToEnemy = Math.sqrt(Math.pow(hero.x - nearestEnemy.x, 2) + Math.pow(hero.y - nearestEnemy.y, 2)); if (distanceToEnemy < 250) { // Increased from 150 to 250 var damage = hero.damageBoost ? 2 : 1; for (var i = 0; i < damage; i++) { nearestEnemy.takeDamage(); } } } else { // Attack in direction of tap hero.attack(worldX); } } } }; // Main game loop game.update = function () { // Track if hero is moving this frame var wasMoving = hero.isWalking; var isMovingThisFrame = false; // Handle continuous movement based on button states if (movementState.left) { hero.move('left'); isMovingThisFrame = true; } if (movementState.right) { hero.move('right'); isMovingThisFrame = true; } if (movementState.up) { hero.move('up'); isMovingThisFrame = true; } if (movementState.down) { hero.move('down'); isMovingThisFrame = true; } // Stop walking animation if no movement this frame if (wasMoving && !isMovingThisFrame) { hero.stopWalkAnimation(); } // Update camera position smoothly camera.x += (camera.targetX - camera.x) * camera.smoothing; camera.y += (camera.targetY - camera.y) * camera.smoothing; // Apply camera position to game game.x = -camera.x; game.y = -camera.y; // Check for hero-enemy collisions and apply push-back for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var dx = hero.x - enemy.x; var dy = hero.y - enemy.y; var distance = Math.sqrt(dx * dx + dy * dy); // If too close, push hero away from enemy if (distance < 100 && distance > 0) { var pushForce = (100 - distance) * 0.3; var pushX = dx / distance * pushForce; var pushY = dy / distance * pushForce; // Apply push with bounds checking var newHeroX = hero.x + pushX; var newHeroY = hero.y + pushY; // Keep hero within level bounds if (newHeroX > 100 && newHeroX < currentLevelData.width - 100) { hero.x = newHeroX; } if (newHeroY > 1600 && newHeroY < 2400) { hero.y = newHeroY; } // Update camera target when hero is pushed camera.targetX = hero.x - 1024; camera.targetY = hero.y - 1366; camera.targetX = Math.max(0, Math.min(camera.targetX, currentLevelData.width - 2048)); camera.targetY = Math.max(0, Math.min(camera.targetY, currentLevelData.height - 2732)); } } // Update all game objects for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; enemy.update(); } for (var i = coins.length - 1; i >= 0; i--) { coins[i].update(); } for (var i = powerups.length - 1; i >= 0; i--) { powerups[i].update(); } for (var i = knives.length - 1; i >= 0; i--) { knives[i].update(); } // Clean up destroyed route effects for (var i = routeEffects.length - 1; i >= 0; i--) { var routeEffect = routeEffects[i]; if (!routeEffect.parent) { routeEffects.splice(i, 1); } } // Update enemy warnings updateEnemyWarnings(); for (var i = 0; i < enemyWarnings.length; i++) { enemyWarnings[i].update(); } // Check for level completion if (!levelComplete && enemies.length === 0) { levelComplete = true; currentLevel++; if (currentLevel <= levels.length) { // Start next level after delay LK.setTimeout(function () { initializeLevel(); }, 2000); } else { // Game completed LK.showYouWin(); } } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Coin = Container.expand(function () {
var self = Container.call(this);
var coinGraphics = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5
});
self.collectTimer = 0;
self.update = function () {
// Auto-collect after short delay
self.collectTimer++;
if (self.collectTimer > 30) {
// 0.5 seconds
self.collect();
}
// Spin animation
coinGraphics.rotation += 0.1;
};
self.collect = function () {
LK.getSound('coin').play();
// Remove from coins array
for (var i = coins.length - 1; i >= 0; i--) {
if (coins[i] === self) {
coins.splice(i, 1);
break;
}
}
self.destroy();
};
return self;
});
var Enemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 1.0
});
self.health = 2;
self.speed = 1;
self.attackCooldown = 0;
self.fromLeft = true;
self.lastX = 0;
self.lastY = 0;
self.alerted = false;
self.alertedByKnife = false;
self.update = function () {
var distanceToHero = Math.sqrt(Math.pow(self.x - hero.x, 2) + Math.pow(self.y - hero.y, 2));
var canSeeHero = distanceToHero < 500; // Line of sight range
if (canSeeHero || self.alerted) {
// Chase hero
var dx = hero.x - self.x;
var dy = hero.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
var newX = self.x + dx / distance * self.speed;
var newY = self.y + dy / distance * self.speed;
// Check collision with other enemies before moving
var wouldCollide = false;
for (var i = 0; i < enemies.length; i++) {
var otherEnemy = enemies[i];
if (otherEnemy === self) continue; // Skip self
var edx = newX - otherEnemy.x;
var edy = newY - otherEnemy.y;
var edistance = Math.sqrt(edx * edx + edy * edy);
if (edistance < 70) {
// Collision threshold between enemies
wouldCollide = true;
break;
}
}
// Only move if no collision would occur
if (!wouldCollide) {
self.lastX = self.x;
self.lastY = self.y;
self.x = newX;
self.y = newY;
}
// Face direction of movement
enemyGraphics.scaleX = dx > 0 ? 1 : -1;
}
// Attack hero if close enough
if (distanceToHero < 100 && self.attackCooldown <= 0) {
hero.takeDamage();
self.attackCooldown = 120; // 2 seconds at 60fps
}
} else if (!self.alerted) {
// Only roam if not alerted - alerted enemies keep chasing even when they can't see hero
if (!self.roamDirection || Math.random() < 0.01) {
self.roamDirection = {
x: (Math.random() - 0.5) * 2,
y: (Math.random() - 0.5) * 2
};
}
var newX = self.x + self.roamDirection.x * self.speed * 0.5;
var newY = self.y + self.roamDirection.y * self.speed * 0.5;
// Check collision with other enemies before roaming
var wouldCollide = false;
for (var i = 0; i < enemies.length; i++) {
var otherEnemy = enemies[i];
if (otherEnemy === self) continue; // Skip self
var edx = newX - otherEnemy.x;
var edy = newY - otherEnemy.y;
var edistance = Math.sqrt(edx * edx + edy * edy);
if (edistance < 70) {
// Collision threshold between enemies
wouldCollide = true;
break;
}
}
// Keep within level bounds and check collisions
if (!wouldCollide && newX > 100 && newX < currentLevelData.width - 100) {
self.lastX = self.x;
self.x = newX;
enemyGraphics.scaleX = self.roamDirection.x > 0 ? 1 : -1;
}
if (!wouldCollide && newY > 1600 && newY < 2400) {
self.lastY = self.y;
self.y = newY;
}
} else {
// Alerted enemies that can't see hero still try to chase in last known direction
var dx = hero.x - self.x;
var dy = hero.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
var newX = self.x + dx / distance * self.speed;
var newY = self.y + dy / distance * self.speed;
// Check collision with other enemies before moving
var wouldCollide = false;
for (var i = 0; i < enemies.length; i++) {
var otherEnemy = enemies[i];
if (otherEnemy === self) continue; // Skip self
var edx = newX - otherEnemy.x;
var edy = newY - otherEnemy.y;
var edistance = Math.sqrt(edx * edx + edy * edy);
if (edistance < 70) {
// Collision threshold between enemies
wouldCollide = true;
break;
}
}
// Only move if no collision would occur
if (!wouldCollide) {
self.lastX = self.x;
self.lastY = self.y;
self.x = newX;
self.y = newY;
}
// Face direction of movement
enemyGraphics.scaleX = dx > 0 ? 1 : -1;
}
}
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
};
self.takeDamage = function (fromKnife) {
self.health--;
LK.effects.flashObject(self, 0xffffff, 200);
if (fromKnife) {
self.alerted = true;
self.alertedByKnife = true;
}
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
// Drop coin
var coin = game.addChild(new Coin());
coin.x = self.x;
coin.y = self.y;
// Add score and combo
var baseScore = 10;
var comboMultiplier = Math.floor(hero.comboCount / 5) + 1;
var finalScore = baseScore * comboMultiplier;
LK.setScore(LK.getScore() + finalScore);
hero.addCombo();
// Remove from enemies array
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
self.destroy();
updateScoreDisplay();
updateEnemiesLeftDisplay();
};
return self;
});
var EnemyWarning = Container.expand(function () {
var self = Container.call(this);
self.targetEnemy = null;
self.direction = 'left'; // 'left', 'right', 'up', 'down'
self.warningGraphics = null;
self.lastAlpha = 0;
self.isVisible = false;
self.setDirection = function (direction) {
if (self.warningGraphics) {
self.warningGraphics.destroy();
}
var assetName = 'warningArrow' + direction.charAt(0).toUpperCase() + direction.slice(1);
self.warningGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
self.direction = direction;
};
self.update = function () {
if (!self.targetEnemy || !self.warningGraphics) return;
// Check if enemy is still alive
var enemyExists = false;
for (var i = 0; i < enemies.length; i++) {
if (enemies[i] === self.targetEnemy) {
enemyExists = true;
break;
}
}
if (!enemyExists) {
self.hide();
return;
}
// Calculate distance from hero to enemy
var distanceToHero = Math.sqrt(Math.pow(self.targetEnemy.x - hero.x, 2) + Math.pow(self.targetEnemy.y - hero.y, 2));
// Check if enemy is visible on screen
var enemyScreenX = self.targetEnemy.x - camera.x;
var enemyScreenY = self.targetEnemy.y - camera.y;
var isOnScreen = enemyScreenX >= -100 && enemyScreenX <= 2148 && enemyScreenY >= -100 && enemyScreenY <= 2832;
if (isOnScreen) {
self.hide();
return;
}
// Calculate warning opacity based on distance (closer = more visible)
var maxDistance = 800;
var minDistance = 300;
var targetAlpha = 0;
if (distanceToHero <= maxDistance) {
var normalizedDistance = Math.max(0, Math.min(1, (maxDistance - distanceToHero) / (maxDistance - minDistance)));
targetAlpha = normalizedDistance * 0.8;
}
// Smooth alpha transition
if (Math.abs(targetAlpha - self.lastAlpha) > 0.01) {
tween.stop(self.warningGraphics, {
alpha: true
});
tween(self.warningGraphics, {
alpha: targetAlpha
}, {
duration: 200
});
self.lastAlpha = targetAlpha;
}
// Position warning at screen edge
var screenCenterX = 1024;
var screenCenterY = 1366;
var dx = self.targetEnemy.x - hero.x;
var dy = self.targetEnemy.y - hero.y;
if (Math.abs(dx) > Math.abs(dy)) {
// Horizontal warning
if (dx > 0) {
self.setDirection('right');
self.x = screenCenterX + 900;
self.y = screenCenterY + Math.max(-600, Math.min(600, dy * 0.5));
} else {
self.setDirection('left');
self.x = screenCenterX - 900;
self.y = screenCenterY + Math.max(-600, Math.min(600, dy * 0.5));
}
} else {
// Vertical warning
if (dy > 0) {
self.setDirection('down');
self.x = screenCenterX + Math.max(-800, Math.min(800, dx * 0.5));
self.y = screenCenterY + 1200;
} else {
self.setDirection('up');
self.x = screenCenterX + Math.max(-800, Math.min(800, dx * 0.5));
self.y = screenCenterY - 1200;
}
}
};
self.hide = function () {
if (self.warningGraphics && self.warningGraphics.alpha > 0) {
tween.stop(self.warningGraphics, {
alpha: true
});
tween(self.warningGraphics, {
alpha: 0
}, {
duration: 300
});
self.lastAlpha = 0;
}
};
return self;
});
var Hero = Container.expand(function () {
var self = Container.call(this);
var heroGraphics = self.attachAsset('hero', {
anchorX: 0.5,
anchorY: 1.0
});
self.maxHealth = 5;
self.health = self.maxHealth;
self.isAttacking = false;
self.invulnerable = false;
self.damageBoost = false;
self.comboCount = 0;
self.lastX = 0;
self.lastY = 0;
self.isWalking = false;
self.walkAnimationActive = false;
self.walkAnimationFrame = 0;
self.walkAnimationTimer = 0;
self.walkAnimationSpeed = 10; // frames between texture changes
self.attack = function (targetX) {
if (self.isAttacking) return;
self.isAttacking = true;
// Face direction of attack
if (targetX < self.x) {
heroGraphics.scaleX = -1;
} else {
heroGraphics.scaleX = 1;
}
// Attack animation
tween(heroGraphics, {
scaleY: 1.2
}, {
duration: 100
});
tween(heroGraphics, {
scaleY: 1.0
}, {
duration: 100,
onFinish: function onFinish() {
self.isAttacking = false;
}
});
// Create punch effect
var effect = game.addChild(LK.getAsset('punchEffect', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8
}));
effect.x = self.x + heroGraphics.scaleX * 80;
effect.y = self.y - 80;
tween(effect, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
effect.destroy();
}
});
LK.getSound('punch').play();
};
self.takeDamage = function () {
if (self.invulnerable) return;
self.health--;
self.comboCount = 0;
// Flash red when hit
LK.effects.flashObject(self, 0xff0000, 500);
// Temporary invulnerability
self.invulnerable = true;
LK.setTimeout(function () {
self.invulnerable = false;
}, 1000);
LK.getSound('hit').play();
updateHealthDisplay();
if (self.health <= 0) {
LK.showGameOver();
}
};
self.heal = function () {
if (self.health < self.maxHealth) {
self.health++;
updateHealthDisplay();
}
};
self.addCombo = function () {
self.comboCount++;
};
self.startWalkAnimation = function () {
if (self.walkAnimationActive) return;
self.walkAnimationActive = true;
self.walkAnimationFrame = 0;
self.walkAnimationTimer = 0;
};
self.stopWalkAnimation = function () {
self.isWalking = false;
self.walkAnimationActive = false;
// Store current scale direction before removing graphics
var currentScaleX = heroGraphics.scaleX;
// Reset to default hero texture
self.removeChild(heroGraphics);
heroGraphics = self.attachAsset('hero', {
anchorX: 0.5,
anchorY: 1.0
});
// Maintain the current scale direction
if (currentScaleX < 0) {
heroGraphics.scaleX = -1;
}
};
self.move = function (direction) {
var speed = 8;
var newX = self.x;
var newY = self.y;
var didMove = false;
if (direction === 'left' && self.x > 100) {
newX = self.x - speed;
heroGraphics.scaleX = -1;
didMove = true;
} else if (direction === 'right' && self.x < currentLevelData.width - 100) {
newX = self.x + speed;
heroGraphics.scaleX = 1;
didMove = true;
} else if (direction === 'up' && self.y > 1600) {
newY = self.y - speed;
didMove = true;
} else if (direction === 'down' && self.y < 2400) {
newY = self.y + speed;
didMove = true;
}
// Check collision with enemies before moving
var wouldCollide = false;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = newX - enemy.x;
var dy = newY - enemy.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 120) {
// Increased collision threshold to prevent overlap
wouldCollide = true;
break;
}
}
// Only move if no collision would occur
if (!wouldCollide && didMove) {
self.lastX = self.x;
self.lastY = self.y;
self.x = newX;
self.y = newY;
// Start walking animation
if (!self.isWalking) {
self.isWalking = true;
self.startWalkAnimation();
}
// Update walking animation texture cycling
if (self.walkAnimationActive) {
self.walkAnimationTimer++;
if (self.walkAnimationTimer >= self.walkAnimationSpeed) {
self.walkAnimationTimer = 0;
self.walkAnimationFrame = (self.walkAnimationFrame + 1) % 4;
// Cycle through textures: hero, heroWalk1, heroWalk2, heroWalk3
var textureNames = ['hero', 'heroWalk1', 'heroWalk2', 'heroWalk3'];
// Store current scale direction before removing graphics
var currentScaleX = heroGraphics.scaleX;
// Remove current graphics and add new one with correct texture
self.removeChild(heroGraphics);
heroGraphics = self.attachAsset(textureNames[self.walkAnimationFrame], {
anchorX: 0.5,
anchorY: 1.0
});
// Maintain the current scale direction
if (currentScaleX < 0) {
heroGraphics.scaleX = -1;
}
}
}
}
// Update camera target
camera.targetX = self.x - 1024;
camera.targetY = self.y - 1366;
// Clamp camera to level bounds
camera.targetX = Math.max(0, Math.min(camera.targetX, currentLevelData.width - 2048));
camera.targetY = Math.max(0, Math.min(camera.targetY, currentLevelData.height - 2732));
};
return self;
});
var Knife = Container.expand(function () {
var self = Container.call(this);
var knifeGraphics = self.attachAsset('knife', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 15;
self.direction = 1; // 1 for right, -1 for left
self.routePoints = []; // Points to follow along the route
self.currentRouteIndex = 0; // Current target point index
self.velocityX = 0; // Velocity for precise targeting
self.velocityY = 0; // Velocity for precise targeting
self.lastX = 0;
self.lastY = 0;
self.setRoute = function (routePoints) {
self.routePoints = routePoints;
self.currentRouteIndex = 0;
};
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
// Follow route if available
if (self.routePoints.length > 0 && self.currentRouteIndex < self.routePoints.length) {
var targetPoint = self.routePoints[self.currentRouteIndex];
var dx = targetPoint.x - self.x;
var dy = targetPoint.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 20) {
// Close enough to current target, move to next point
self.currentRouteIndex++;
} else {
// Move toward current target point
if (distance > 0) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
// Calculate rotation angle to face target direction
var angle = Math.atan2(dy, dx);
knifeGraphics.rotation = angle;
}
}
} else if (self.velocityX !== 0 || self.velocityY !== 0) {
// Use velocity if set, otherwise use direction-based movement
self.x += self.velocityX;
self.y += self.velocityY;
// Calculate rotation for velocity-based movement
var angle = Math.atan2(self.velocityY, self.velocityX);
knifeGraphics.rotation = angle;
} else {
self.x += self.speed * self.direction;
self.y -= 2; // Slight upward arc
// Calculate rotation for direction-based movement
var angle = Math.atan2(-2, self.speed * self.direction);
knifeGraphics.rotation = angle;
}
// Check if knife went off screen
if (self.x < -100 || self.x > currentLevelData.width + 100 || self.y < -100 || self.y > 2900) {
self.destroy();
// Remove from knives array
for (var i = knives.length - 1; i >= 0; i--) {
if (knives[i] === self) {
knives.splice(i, 1);
break;
}
}
}
// Check collision with enemies (hit enemy center)
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = self.x - enemy.x;
var dy = self.y - (enemy.y - 70); // Target enemy center
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 50) {
// Hit enemy center - deal damage only to first enemy encountered
enemy.takeDamage(true);
self.destroy();
// Remove from knives array
for (var j = knives.length - 1; j >= 0; j--) {
if (knives[j] === self) {
knives.splice(j, 1);
break;
}
}
return; // Exit update immediately to prevent further movement or collision checks
}
}
};
return self;
});
var PowerUp = Container.expand(function () {
var self = Container.call(this);
var powerupGraphics = self.attachAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5
});
self.type = 'health'; // 'health', 'invulnerable', 'damage'
self.lifetime = 600; // 10 seconds
self.update = function () {
self.lifetime--;
if (self.lifetime <= 0) {
self.expire();
}
// Check collision with hero
if (self.intersects(hero)) {
self.collect();
}
// Pulse animation
var scale = 1 + Math.sin(LK.ticks * 0.2) * 0.2;
powerupGraphics.scaleX = scale;
powerupGraphics.scaleY = scale;
};
self.collect = function () {
LK.getSound('powerup').play();
if (self.type === 'health') {
hero.heal();
} else if (self.type === 'invulnerable') {
hero.invulnerable = true;
LK.setTimeout(function () {
hero.invulnerable = false;
}, 5000);
} else if (self.type === 'damage') {
hero.damageBoost = true;
LK.setTimeout(function () {
hero.damageBoost = false;
}, 5000);
}
// Remove from powerups array
for (var i = powerups.length - 1; i >= 0; i--) {
if (powerups[i] === self) {
powerups.splice(i, 1);
break;
}
}
self.destroy();
};
self.expire = function () {
// Remove from powerups array
for (var i = powerups.length - 1; i >= 0; i--) {
if (powerups[i] === self) {
powerups.splice(i, 1);
break;
}
}
self.destroy();
};
return self;
});
var RouteEffect = Container.expand(function () {
var self = Container.call(this);
self.routePoints = [];
self.routePositions = []; // Store just the x,y coordinates for knife to follow
self.targetEnemy = null;
self.createRoute = function (enemy) {
self.targetEnemy = enemy;
self.clearRoute();
// Calculate direct path from hero center to enemy center
var heroStartX = hero.x;
var heroStartY = hero.y - 80; // Middle of hero asset
var enemyMiddleX = enemy.x;
var enemyMiddleY = enemy.y - 70; // Middle of enemy asset (enemy height is 140, so middle is -70 from bottom)
var dx = enemyMiddleX - heroStartX;
var dy = enemyMiddleY - heroStartY;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
// Create route points along the path
var numPoints = Math.floor(distance / 50); // Point every 50 pixels
for (var i = 0; i <= numPoints; i++) {
var t = i / numPoints;
var x = heroStartX + dx * t;
var y = heroStartY + dy * t;
var routePoint = self.addChild(LK.getAsset('routeLine', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
}));
routePoint.x = x;
routePoint.y = y;
self.routePoints.push(routePoint);
// Store position for knife to follow
self.routePositions.push({
x: x,
y: y
});
// Animate route points appearing with delay
tween(routePoint, {
alpha: 0.8,
scaleX: 2,
scaleY: 2
}, {
duration: 100 + i * 20
});
}
// Add impact effect at enemy position
var impactEffect = self.addChild(LK.getAsset('routeEffect', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}));
impactEffect.x = enemyMiddleX;
impactEffect.y = enemyMiddleY;
self.routePoints.push(impactEffect);
// Animate impact effect
tween(impactEffect, {
alpha: 1,
scaleX: 3,
scaleY: 3
}, {
duration: 300,
easing: tween.easeOut
});
// Remove route after 0.5 seconds
LK.setTimeout(function () {
self.destroy();
// Remove from routeEffects array
for (var i = routeEffects.length - 1; i >= 0; i--) {
if (routeEffects[i] === self) {
routeEffects.splice(i, 1);
break;
}
}
}, 500);
}
};
self.clearRoute = function () {
for (var i = self.routePoints.length - 1; i >= 0; i--) {
self.routePoints[i].destroy();
}
self.routePoints = [];
};
self.fadeOut = function () {
for (var i = 0; i < self.routePoints.length; i++) {
tween(self.routePoints[i], {
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 200
});
}
LK.setTimeout(function () {
self.destroy();
}, 300);
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2c3e50
});
/****
* Game Code
****/
// Game variables
var hero;
var enemies = [];
var coins = [];
var powerups = [];
var enemyWarnings = [];
var knives = [];
var knivesRemaining = 5;
var routeEffects = [];
var currentLevel = 1;
var levelComplete = false;
var hearts = [];
// Movement state tracking
var movementState = {
left: false,
right: false,
up: false,
down: false
};
// Camera system
var camera = {
x: 0,
y: 0,
targetX: 0,
targetY: 0,
smoothing: 0.1
};
// Level configuration
var levels = [{
enemies: [{
type: 'basic',
count: 3
}],
width: 4096,
height: 2732
}, {
enemies: [{
type: 'basic',
count: 5
}, {
type: 'strong',
count: 2
}],
width: 5120,
height: 2732
}, {
enemies: [{
type: 'basic',
count: 7
}, {
type: 'strong',
count: 3
}, {
type: 'fast',
count: 2
}],
width: 6144,
height: 2732
}];
var currentLevelData = levels[0];
// UI Elements
var scoreText = new Text2('Score: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreText.anchor.set(0, 0);
scoreText.x = 150;
scoreText.y = 50;
LK.gui.topLeft.addChild(scoreText);
var levelText = new Text2('Level: 1', {
size: 80,
fill: 0xFFFFFF
});
levelText.anchor.set(0.5, 0);
LK.gui.top.addChild(levelText);
levelText.y = 50;
var comboText = new Text2('Combo: 0x', {
size: 60,
fill: 0xFFFF00
});
comboText.anchor.set(1, 0);
LK.gui.topRight.addChild(comboText);
comboText.x = -50;
comboText.y = 120;
var knivesText = new Text2('Knives: 5', {
size: 60,
fill: 0x8e44ad
});
knivesText.anchor.set(1, 0);
LK.gui.topRight.addChild(knivesText);
knivesText.x = -50;
knivesText.y = 190;
var enemiesLeftText = new Text2('Enemies: 0', {
size: 60,
fill: 0xff4444
});
enemiesLeftText.anchor.set(1, 0);
LK.gui.topRight.addChild(enemiesLeftText);
enemiesLeftText.x = -50;
enemiesLeftText.y = 260;
// Create movement and attack buttons
var leftButton = LK.getAsset('leftButton', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8
});
leftButton.x = 120;
leftButton.y = -250;
LK.gui.bottomLeft.addChild(leftButton);
var rightButton = LK.getAsset('rightButton', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8
});
rightButton.x = 400;
rightButton.y = -250;
LK.gui.bottomLeft.addChild(rightButton);
var upButton = LK.getAsset('upButton', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8
});
upButton.x = 260;
upButton.y = -380;
LK.gui.bottomLeft.addChild(upButton);
var downButton = LK.getAsset('downButton', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8
});
downButton.x = 260;
downButton.y = -120;
LK.gui.bottomLeft.addChild(downButton);
var attackButton = LK.getAsset('attackButton', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.9
});
attackButton.x = -150;
attackButton.y = -200;
LK.gui.bottomRight.addChild(attackButton);
var knifeButton = LK.getAsset('knifeButton', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.9
});
knifeButton.x = -350;
knifeButton.y = -200;
LK.gui.bottomRight.addChild(knifeButton);
// Create background grid
var backgroundTiles = [];
function createBackgroundGrid() {
// Clear existing background tiles
for (var i = backgroundTiles.length - 1; i >= 0; i--) {
backgroundTiles[i].destroy();
}
backgroundTiles = [];
var tileSize = 200;
var tilesX = Math.ceil(currentLevelData.width / tileSize) + 2;
var tilesY = Math.ceil(currentLevelData.height / tileSize) + 2;
for (var x = 0; x < tilesX; x++) {
for (var y = 0; y < tilesY; y++) {
var tile = game.addChild(LK.getAsset('backgroundTile', {
anchorX: 0,
anchorY: 0,
alpha: 0.3
}));
tile.x = x * tileSize;
tile.y = y * tileSize;
// Add subtle pattern variation
if ((x + y) % 2 === 0) {
tile.alpha = 0.2;
}
backgroundTiles.push(tile);
}
}
}
// Create hero
hero = game.addChild(new Hero());
hero.x = 1024; // Center of screen
hero.y = 2300; // Near bottom
// Create health display
function updateHealthDisplay() {
// Remove existing hearts
for (var i = hearts.length - 1; i >= 0; i--) {
hearts[i].destroy();
}
hearts = [];
// Create new hearts
for (var i = 0; i < hero.health; i++) {
var heart = LK.getAsset('heart', {
anchorX: 0.5,
anchorY: 0.5
});
heart.x = 200 + i * 80;
heart.y = 200;
LK.gui.topLeft.addChild(heart);
hearts.push(heart);
}
}
function updateScoreDisplay() {
scoreText.setText('Score: ' + LK.getScore());
var comboMultiplier = Math.floor(hero.comboCount / 5) + 1;
comboText.setText('Combo: ' + comboMultiplier + 'x');
}
function updateKnivesDisplay() {
knivesText.setText('Knives: ' + knivesRemaining);
// Update button alpha based on availability
knifeButton.alpha = knivesRemaining > 0 ? 0.9 : 0.3;
}
function updateEnemiesLeftDisplay() {
enemiesLeftText.setText('Enemies: ' + enemies.length);
}
function initializeLevel() {
// Clear existing enemies
for (var i = enemies.length - 1; i >= 0; i--) {
enemies[i].destroy();
}
enemies = [];
// Clear existing warnings
for (var i = enemyWarnings.length - 1; i >= 0; i--) {
enemyWarnings[i].destroy();
}
enemyWarnings = [];
// Clear existing knives
for (var i = knives.length - 1; i >= 0; i--) {
knives[i].destroy();
}
knives = [];
// Clear existing route effects
for (var i = routeEffects.length - 1; i >= 0; i--) {
routeEffects[i].destroy();
}
routeEffects = [];
// Reset knife count
knivesRemaining = 5;
currentLevelData = levels[currentLevel - 1] || levels[levels.length - 1];
levelComplete = false;
// Spawn enemies for this level
for (var j = 0; j < currentLevelData.enemies.length; j++) {
var enemyGroup = currentLevelData.enemies[j];
for (var k = 0; k < enemyGroup.count; k++) {
spawnEnemy(enemyGroup.type);
}
}
levelText.setText('Level: ' + currentLevel);
updateKnivesDisplay();
updateEnemiesLeftDisplay();
// Create background grid for this level
createBackgroundGrid();
}
function spawnEnemy(type) {
var enemy = game.addChild(new Enemy());
// Random spawn position within level bounds
enemy.x = 200 + Math.random() * (currentLevelData.width - 400);
enemy.y = 1700 + Math.random() * 600;
// Set enemy properties based on type
if (type === 'basic') {
enemy.health = 2;
enemy.speed = 1;
} else if (type === 'strong') {
enemy.health = 4;
enemy.speed = 0.8;
enemy.getChildAt(0).tint = 0x8b4513; // Brown tint
} else if (type === 'fast') {
enemy.health = 1;
enemy.speed = 2;
enemy.getChildAt(0).tint = 0x00ff00; // Green tint
}
enemies.push(enemy);
}
function spawnPowerUp() {
var powerup = game.addChild(new PowerUp());
powerup.x = 500 + Math.random() * 1048; // Random x position
powerup.y = 1800 + Math.random() * 400; // Above ground level
// Random powerup type
var types = ['health', 'invulnerable', 'damage'];
powerup.type = types[Math.floor(Math.random() * types.length)];
// Color by type
var powerupGraphics = powerup.getChildAt(0);
if (powerup.type === 'health') {
powerupGraphics.tint = 0x00ff00; // Green
} else if (powerup.type === 'invulnerable') {
powerupGraphics.tint = 0x0088ff; // Blue
} else if (powerup.type === 'damage') {
powerupGraphics.tint = 0xff8800; // Orange
}
powerups.push(powerup);
}
function findNearestEnemy(x, y) {
var nearest = null;
var shortestDistance = Infinity;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var distance = Math.sqrt(Math.pow(enemy.x - x, 2) + Math.pow(enemy.y - y, 2));
if (distance < shortestDistance) {
shortestDistance = distance;
nearest = enemy;
}
}
return nearest;
}
function updateEnemyWarnings() {
// Remove warnings for dead enemies
for (var i = enemyWarnings.length - 1; i >= 0; i--) {
var warning = enemyWarnings[i];
var enemyExists = false;
for (var j = 0; j < enemies.length; j++) {
if (enemies[j] === warning.targetEnemy) {
enemyExists = true;
break;
}
}
if (!enemyExists) {
warning.destroy();
enemyWarnings.splice(i, 1);
}
}
// Create warnings for new enemies
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var hasWarning = false;
for (var j = 0; j < enemyWarnings.length; j++) {
if (enemyWarnings[j].targetEnemy === enemy) {
hasWarning = true;
break;
}
}
if (!hasWarning) {
var warning = LK.gui.center.addChild(new EnemyWarning());
warning.targetEnemy = enemy;
warning.setDirection('left');
enemyWarnings.push(warning);
}
}
}
// Initialize UI
updateHealthDisplay();
updateScoreDisplay();
updateKnivesDisplay();
updateEnemiesLeftDisplay();
// Initialize first level
initializeLevel();
// Create initial background grid
createBackgroundGrid();
// Set initial camera position
camera.targetX = hero.x - 1024;
camera.targetY = hero.y - 1366;
camera.x = camera.targetX;
camera.y = camera.targetY;
// Button event handlers
leftButton.down = function (x, y, obj) {
movementState.left = true;
};
leftButton.up = function (x, y, obj) {
movementState.left = false;
};
rightButton.down = function (x, y, obj) {
movementState.right = true;
};
rightButton.up = function (x, y, obj) {
movementState.right = false;
};
upButton.down = function (x, y, obj) {
movementState.up = true;
};
upButton.up = function (x, y, obj) {
movementState.up = false;
};
downButton.down = function (x, y, obj) {
movementState.down = true;
};
downButton.up = function (x, y, obj) {
movementState.down = false;
};
attackButton.down = function (x, y, obj) {
if (!hero.isAttacking) {
var nearestEnemy = findNearestEnemy(hero.x, hero.y);
if (nearestEnemy) {
hero.attack(nearestEnemy.x);
// Check if attack hits with increased range
var distanceToEnemy = Math.sqrt(Math.pow(hero.x - nearestEnemy.x, 2) + Math.pow(hero.y - nearestEnemy.y, 2));
if (distanceToEnemy < 250) {
// Increased from 150 to 250
var damage = hero.damageBoost ? 2 : 1;
for (var i = 0; i < damage; i++) {
nearestEnemy.takeDamage();
}
}
} else {
// Attack in hero's facing direction
hero.attack(hero.x + (hero.getChildAt(0).scaleX > 0 ? 100 : -100));
}
}
};
knifeButton.down = function (x, y, obj) {
if (knivesRemaining > 0) {
// Find nearest enemy for targeting
var nearestEnemy = findNearestEnemy(hero.x, hero.y);
if (nearestEnemy) {
// Clear existing route effects before creating new one
for (var i = routeEffects.length - 1; i >= 0; i--) {
routeEffects[i].destroy();
routeEffects.splice(i, 1);
}
// Create route visualization
var routeEffect = game.addChild(new RouteEffect());
routeEffect.createRoute(nearestEnemy);
routeEffects.push(routeEffect);
// Throw knife to follow the route
var knife = game.addChild(new Knife());
knife.x = hero.x;
knife.y = hero.y - 80;
// Set the route for the knife to follow
knife.setRoute(routeEffect.routePositions);
// Rotation will be handled automatically in knife update based on target direction
knives.push(knife);
knivesRemaining--;
updateKnivesDisplay();
LK.getSound('knifeThrow').play();
} else {
// No enemy found, throw in hero facing direction
var knife = game.addChild(new Knife());
knife.x = hero.x;
knife.y = hero.y - 80;
var heroGraphics = hero.getChildAt(0);
knife.direction = heroGraphics.scaleX > 0 ? 1 : -1;
// Rotation will be handled automatically in knife update based on movement direction
knives.push(knife);
knivesRemaining--;
updateKnivesDisplay();
LK.getSound('knifeThrow').play();
}
}
};
// Game input (fallback for screen taps outside buttons)
game.down = function (x, y, obj) {
// Convert screen coordinates to world coordinates
var worldX = x + camera.x;
var worldY = y + camera.y;
// Check if tap is for movement or attack
var distanceToHero = Math.sqrt(Math.pow(worldX - hero.x, 2) + Math.pow(worldY - hero.y, 2));
if (distanceToHero > 200) {
// Movement - move toward tap position
var dx = worldX - hero.x;
var dy = worldY - hero.y;
if (Math.abs(dx) > Math.abs(dy)) {
hero.move(dx > 0 ? 'right' : 'left');
} else {
hero.move(dy > 0 ? 'down' : 'up');
}
} else {
// Attack
if (!hero.isAttacking) {
var nearestEnemy = findNearestEnemy(worldX, worldY);
if (nearestEnemy) {
hero.attack(nearestEnemy.x);
// Check if attack hits with increased range
var distanceToEnemy = Math.sqrt(Math.pow(hero.x - nearestEnemy.x, 2) + Math.pow(hero.y - nearestEnemy.y, 2));
if (distanceToEnemy < 250) {
// Increased from 150 to 250
var damage = hero.damageBoost ? 2 : 1;
for (var i = 0; i < damage; i++) {
nearestEnemy.takeDamage();
}
}
} else {
// Attack in direction of tap
hero.attack(worldX);
}
}
}
};
// Main game loop
game.update = function () {
// Track if hero is moving this frame
var wasMoving = hero.isWalking;
var isMovingThisFrame = false;
// Handle continuous movement based on button states
if (movementState.left) {
hero.move('left');
isMovingThisFrame = true;
}
if (movementState.right) {
hero.move('right');
isMovingThisFrame = true;
}
if (movementState.up) {
hero.move('up');
isMovingThisFrame = true;
}
if (movementState.down) {
hero.move('down');
isMovingThisFrame = true;
}
// Stop walking animation if no movement this frame
if (wasMoving && !isMovingThisFrame) {
hero.stopWalkAnimation();
}
// Update camera position smoothly
camera.x += (camera.targetX - camera.x) * camera.smoothing;
camera.y += (camera.targetY - camera.y) * camera.smoothing;
// Apply camera position to game
game.x = -camera.x;
game.y = -camera.y;
// Check for hero-enemy collisions and apply push-back
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = hero.x - enemy.x;
var dy = hero.y - enemy.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// If too close, push hero away from enemy
if (distance < 100 && distance > 0) {
var pushForce = (100 - distance) * 0.3;
var pushX = dx / distance * pushForce;
var pushY = dy / distance * pushForce;
// Apply push with bounds checking
var newHeroX = hero.x + pushX;
var newHeroY = hero.y + pushY;
// Keep hero within level bounds
if (newHeroX > 100 && newHeroX < currentLevelData.width - 100) {
hero.x = newHeroX;
}
if (newHeroY > 1600 && newHeroY < 2400) {
hero.y = newHeroY;
}
// Update camera target when hero is pushed
camera.targetX = hero.x - 1024;
camera.targetY = hero.y - 1366;
camera.targetX = Math.max(0, Math.min(camera.targetX, currentLevelData.width - 2048));
camera.targetY = Math.max(0, Math.min(camera.targetY, currentLevelData.height - 2732));
}
}
// Update all game objects
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
enemy.update();
}
for (var i = coins.length - 1; i >= 0; i--) {
coins[i].update();
}
for (var i = powerups.length - 1; i >= 0; i--) {
powerups[i].update();
}
for (var i = knives.length - 1; i >= 0; i--) {
knives[i].update();
}
// Clean up destroyed route effects
for (var i = routeEffects.length - 1; i >= 0; i--) {
var routeEffect = routeEffects[i];
if (!routeEffect.parent) {
routeEffects.splice(i, 1);
}
}
// Update enemy warnings
updateEnemyWarnings();
for (var i = 0; i < enemyWarnings.length; i++) {
enemyWarnings[i].update();
}
// Check for level completion
if (!levelComplete && enemies.length === 0) {
levelComplete = true;
currentLevel++;
if (currentLevel <= levels.length) {
// Start next level after delay
LK.setTimeout(function () {
initializeLevel();
}, 2000);
} else {
// Game completed
LK.showYouWin();
}
}
};