User prompt
El fondo súperior principal bájalo más
User prompt
Centra todas las imágenes de fondo con la ubicación de la base principal
User prompt
Cambia el color del fondo clásico a negro
User prompt
Ubica más abajo los dos fondos superiores
User prompt
Invierte las partes duplicadas para que estén de cabeza
User prompt
Duplica las imágenes de fondo hacia abajo creando un modo espejo pero invertido
User prompt
Aumenta aún más el límite
User prompt
Aumenta el límite de la pantalla en la parte derecha
User prompt
Mueve todas las bases al medio de la pantalla
User prompt
Centra todas las bases en la mitad de izquierda y derecha
User prompt
Agrega dinamismo a las imágenes de fondo, las principales que se muevan más con el movimiento de camara y las de fondo que se muevan menos y lento ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
As más zoom en los jugadores
User prompt
La camara se expande demaciado , arreglalo por favor ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Que la camara no sobrepase el límite de la parte superior de la pantalla
User prompt
Que la camara no sobrepase el límite del tamaño de la pantalla
User prompt
Actualiza a los bots con las nuevas ubicaciones de las bases
User prompt
Ubica el punto de aparición de cada jugador en su base respectiva
User prompt
Agregue distancia a todas las bases entre si
User prompt
Mueve todas las bases a la esquina inferior derecha
User prompt
Mueve las bases más hacia el borde derecho inferior
User prompt
Mueve las bases más hacia abajo
User prompt
Ajusta el último fondo al borde izquierdo superior
User prompt
Actualiza el tamaño de los fondos al tamaño maximo que se expande la pantalla
User prompt
Que la camara siga a los jugadores, y que se espada si los jugadores se alejan y que se enfoque si los jugadores se agrupan ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Que la camara se acerque si los jugadores están muy juntos y se aleje si los jugadores se esparcen ↪💡 Consider importing and using the following plugins: @upit/tween.v1
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Fighter = Container.expand(function (playerNumber, color) { var self = Container.call(this); var assetName = 'player1'; if (playerNumber === 2) assetName = 'player2';else if (playerNumber === 3) assetName = 'player3';else if (playerNumber === 4) assetName = 'player4'; var fighterGraphics = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 1.0 }); self.playerNumber = playerNumber; self.velocityX = 0; self.velocityY = 0; self.onGround = false; self.jumpsRemaining = 2; // Allow double jump self.health = 0; self.maxHealth = 100; self.damage = 0; // New damage accumulation property self.moveSpeed = 8; self.jumpPower = 25; self.knockbackResistance = 1.0; self.lastHitTime = 0; self.invulnerable = false; self.powerupCount = 0; // Track number of powerups collected self.baseMoveSpeed = 8; // Store original move speed self.baseJumpPower = 25; // Store original jump power self.baseDamage = 15; // Store original damage self.baseKnockback = 20; // Store original knockback self.lastVelocityX = 0; // Track last velocity for rotation // Create damage bar background var damageBarBg = self.addChild(LK.getAsset('platform', { width: 100, height: 12, anchorX: 0.5, anchorY: 1.0, x: 0, y: -130 })); damageBarBg.tint = 0x333333; damageBarBg.scaleX = 0.8; damageBarBg.scaleY = 0.3; // Create damage bar fill self.damageBar = self.addChild(LK.getAsset('platform', { width: 100, height: 12, anchorX: 0, anchorY: 1.0, x: -40, y: -130 })); self.damageBar.tint = 0xff4444; self.damageBar.scaleX = 0; self.damageBar.scaleY = 0.3; // Create damage text self.damageText = self.addChild(new Text2('0%', { size: 30, fill: 0xFFFFFF, stroke: 0x000000, strokeThickness: 4 })); self.damageText.anchor.set(0.5, 1); self.damageText.x = 0; self.damageText.y = -140; self.takeDamage = function (damage, knockbackX, knockbackY) { if (self.invulnerable) return; self.health += damage; self.damage += damage; // Accumulate damage for knockback scaling // Cap damage at 999% if (self.damage > 999) self.damage = 999; // Update damage display self.damageText.setText(Math.floor(self.damage) + '%'); // Reapply stroke properties to maintain black background texture if (self.damageText.style) { self.damageText.style.stroke = 0x000000; self.damageText.style.strokeThickness = 4; } // Update damage bar scale (0 to 1 based on damage up to 200%) var barScale = Math.min(self.damage / 200, 1); self.damageBar.scaleX = barScale * 0.8; // Change bar color based on damage level if (self.damage < 50) { self.damageBar.tint = 0x44ff44; // Green for low damage } else if (self.damage < 100) { self.damageBar.tint = 0xffff44; // Yellow for medium damage } else if (self.damage < 150) { self.damageBar.tint = 0xff8844; // Orange for high damage } else { self.damageBar.tint = 0xff4444; // Red for very high damage } // Check if player has reached 200 damage - explosion and life loss if (self.damage >= 200) { // Create explosion effect with tween tween(self, { scaleX: 2.0, scaleY: 2.0, alpha: 0 }, { duration: 500, easing: tween.bounceOut, onFinish: function onFinish() { // Reset scale and alpha after explosion self.scaleX = 1.0; self.scaleY = 1.0; self.alpha = 1.0; // Trigger respawn which handles life loss self.respawn(); } }); // Flash screen effect for dramatic explosion LK.effects.flashScreen(0xff8800, 600); // Play explosion sound effect LK.getSound('explosion').play(); // Reset damage to prevent multiple explosions self.damage = 0; return; // Exit early to prevent further processing this frame } // Knockback multiplier based on accumulated damage - exponential scaling for stronger hits at higher damage var knockbackMultiplier = 1.5 + self.damage / 20 + Math.pow(self.damage / 100, 1.5); // Exponential scaling that increases dramatically with damage self.velocityX += knockbackX * knockbackMultiplier / self.knockbackResistance; self.velocityY += knockbackY * knockbackMultiplier / self.knockbackResistance; // Flash effect when hit LK.effects.flashObject(self, 0xff0000, 300); self.invulnerable = true; LK.setTimeout(function () { self.invulnerable = false; }, 500); LK.getSound('hit').play(); }; self.attack = function (targetX, targetY) { var attackRange = 150; var damage = self.baseDamage + self.powerupCount * 8; // Increase damage by 8 per powerup var baseKnockback = self.baseKnockback + self.powerupCount * 10; // Increase knockback by 10 per powerup for (var i = 0; i < fighters.length; i++) { var target = fighters[i]; if (target === self) continue; var distance = Math.sqrt(Math.pow(target.x - self.x, 2) + Math.pow(target.y - self.y, 2)); if (distance <= attackRange) { var angle = Math.atan2(target.y - self.y, target.x - self.x); var knockbackX = Math.cos(angle) * baseKnockback; var knockbackY = Math.sin(angle) * baseKnockback - 5; // Slight upward angle target.takeDamage(damage, knockbackX, knockbackY); } } }; self.update = function () { // Update movement stats based on powerups self.moveSpeed = self.baseMoveSpeed + self.powerupCount * 3; // Increase move speed by 3 per powerup self.jumpPower = self.baseJumpPower + self.powerupCount * 5; // Increase jump power by 5 per powerup // Apply gravity if (!self.onGround) { self.velocityY += 1.2; } // Apply friction self.velocityX *= 0.85; if (self.onGround) { self.velocityX *= 0.75; } // Limit velocities to prevent teleportation var maxVelocityX = 20; var maxVelocityY = 30; if (self.velocityX > maxVelocityX) { self.velocityX = maxVelocityX; } if (self.velocityX < -maxVelocityX) { self.velocityX = -maxVelocityX; } if (self.velocityY > maxVelocityY) { self.velocityY = maxVelocityY; } if (self.velocityY < -maxVelocityY) { self.velocityY = -maxVelocityY; } // Track last position for smoke trail if (self.lastX === undefined) self.lastX = self.x; if (self.lastY === undefined) self.lastY = self.y; // Update position self.x += self.velocityX; self.y += self.velocityY; // Generate smoke trail when moving var movementSpeed = Math.sqrt(Math.pow(self.x - self.lastX, 2) + Math.pow(self.y - self.lastY, 2)); if (movementSpeed > 3 && LK.ticks % 3 === 0) { // Calculate movement direction var movementDirectionX = self.x - self.lastX; var movementDirectionY = self.y - self.lastY; // Normalize the direction var directionLength = Math.sqrt(movementDirectionX * movementDirectionX + movementDirectionY * movementDirectionY); if (directionLength > 0) { movementDirectionX /= directionLength; movementDirectionY /= directionLength; } // Position smoke trail behind the movement direction var smokeDistance = 30; // Distance behind the player var smokeX = self.x - movementDirectionX * smokeDistance; var smokeY = self.y - movementDirectionY * smokeDistance + 20; var smoke = new SmokeTrail(self.playerNumber, smokeX, smokeY); game.addChild(smoke); smokeTrails.push(smoke); } // Update last position self.lastX = self.x; self.lastY = self.y; // Update visual size based on powerup count var targetScale = 1.0 + self.powerupCount * 0.2; // Increase size by 20% per powerup if (Math.abs(fighterGraphics.scaleX) !== targetScale) { var isFlipped = fighterGraphics.scaleX < 0; fighterGraphics.scaleX = isFlipped ? -targetScale : targetScale; fighterGraphics.scaleY = targetScale; } // Rotate player based on movement direction if (Math.abs(self.velocityX) > 1) { // Only rotate if moving with significant velocity if (self.velocityX > 0) { // Moving right - face right (normal orientation) fighterGraphics.scaleX = Math.abs(fighterGraphics.scaleX); } else { // Moving left - face left (flip horizontally) fighterGraphics.scaleX = -Math.abs(fighterGraphics.scaleX); } } // Platform collision var wasOnGround = self.onGround; self.onGround = false; for (var i = 0; i < platforms.length; i++) { var platform = platforms[i]; // More precise collision detection with tighter bounds var platformLeft = platform.x - platform.width / 2 + 10; // Add small margin var platformRight = platform.x + platform.width / 2 - 10; // Add small margin var platformTop = platform.y - platform.height / 2; var platformBottom = platform.y + platform.height / 2; // Check if player is within platform horizontal bounds and approaching from above if (self.x >= platformLeft && self.x <= platformRight && self.y >= platformTop - 20 && self.y <= platformBottom + 5 && self.velocityY >= 0) { // Snap player to exact platform surface self.y = platformTop; self.velocityY = 0; // Play landing sound if just landed (wasn't on ground before) if (!wasOnGround) { LK.getSound('landing').play(); } self.onGround = true; self.jumpsRemaining = 2; // Reset jumps when landing break; } } // Check if fallen off screen if (self.y > 2900) { // Play fall sound effect LK.getSound('fall').play(); self.respawn(); } // Fighter-to-fighter collision detection and pushing for (var i = 0; i < fighters.length; i++) { var otherFighter = fighters[i]; if (otherFighter === self || otherFighter.destroyed) continue; // Calculate distance between fighters var deltaX = otherFighter.x - self.x; var deltaY = otherFighter.y - self.y; var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); // Check if fighters are colliding (within collision radius) var collisionRadius = 80; // Adjust this value to change collision sensitivity if (distance < collisionRadius && distance > 0) { // Calculate push force based on overlap var overlap = collisionRadius - distance; var pushStrength = overlap * 0.3; // Adjust push strength // Normalize direction vector var normalX = deltaX / distance; var normalY = deltaY / distance; // Apply push forces to both fighters var pushX = normalX * pushStrength; var pushY = normalY * pushStrength * 0.3; // Reduced vertical push // Push both fighters apart self.velocityX -= pushX; self.velocityY -= pushY; otherFighter.velocityX += pushX; otherFighter.velocityY += pushY; // Separate fighters to prevent overlap var separationX = normalX * (overlap * 0.5); var separationY = normalY * (overlap * 0.3); self.x -= separationX; self.y -= separationY; otherFighter.x += separationX; otherFighter.y += separationY; } } // Screen boundaries (horizontal bouncing) if (self.x < 60) { self.x = 60; self.velocityX = Math.abs(self.velocityX) * 0.5; } if (self.x > 1988) { self.x = 1988; self.velocityX = -Math.abs(self.velocityX) * 0.5; } }; self.respawn = function () { // Decrease lives for the player who died if (self.playerNumber === 1) { player1Lives--; } else if (self.playerNumber === 2) { player2Lives--; } else if (self.playerNumber === 3) { player3Lives--; } else if (self.playerNumber === 4) { player4Lives--; } updateScoreDisplay(); // Check if player is eliminated (no lives left) var isEliminated = false; if (self.playerNumber === 1 && player1Lives <= 0) { isEliminated = true; } else if (self.playerNumber === 2 && player2Lives <= 0) { isEliminated = true; } else if (self.playerNumber === 3 && player3Lives <= 0) { isEliminated = true; } else if (self.playerNumber === 4 && player4Lives <= 0) { isEliminated = true; } if (isEliminated) { // Remove player from the game completely self.destroy(); // Remove from fighters array for (var j = fighters.length - 1; j >= 0; j--) { if (fighters[j] === self) { fighters.splice(j, 1); break; } } activePlayers--; // Check if only one player remains if (activePlayers <= 1) { // Find the winner var winner = ""; if (player1Lives > 0) winner = "Player 1";else if (player2Lives > 0) winner = "Player 2";else if (player3Lives > 0) winner = "Player 3";else if (player4Lives > 0) winner = "Player 4"; LK.setScore(100); LK.showGameOver(); } } else { // Player still has lives, respawn normally if (self.playerNumber === 1) { self.x = 400; } else if (self.playerNumber === 2) { self.x = 1648; } else if (self.playerNumber === 3) { self.x = 700; } else if (self.playerNumber === 4) { self.x = 1348; } self.y = 1000; self.velocityX = 0; self.velocityY = 0; self.health = 0; self.damage = 0; // Reset damage on respawn self.powerupCount = 0; // Reset powerup count on respawn self.damageText.setText('0%'); // Reapply stroke properties to maintain black background texture if (self.damageText.style) { self.damageText.style.stroke = 0x000000; self.damageText.style.strokeThickness = 4; } self.damageBar.scaleX = 0; self.damageBar.tint = 0x44ff44; self.jumpsRemaining = 2; // Reset jumps on respawn } }; return self; }); var PowerUp = Container.expand(function () { var self = Container.call(this); var powerupGraphics = self.attachAsset('powerup', { anchorX: 0.5, anchorY: 0.5 }); self.lifeTime = 0; self.maxLifeTime = 600; // 10 seconds at 60fps self.collected = false; self.velocityY = 0; // Falling velocity self.onGround = false; // Track if powerup has landed self.update = function () { self.lifeTime++; self.rotation += 0.05; // Apply gravity if not on ground if (!self.onGround) { self.velocityY += 0.8; // Gravity effect self.y += self.velocityY; // Check platform collision for landing for (var i = 0; i < platforms.length; i++) { var platform = platforms[i]; var platformLeft = platform.x - platform.width / 2; var platformRight = platform.x + platform.width / 2; var platformTop = platform.y - platform.height / 2; var platformBottom = platform.y + platform.height / 2; // Check if powerup is within platform bounds and landing on top if (self.x >= platformLeft && self.x <= platformRight && self.y >= platformTop - 30 && self.y <= platformBottom && self.velocityY >= 0) { self.y = platformTop - 30; // Position on top of platform self.velocityY = 0; self.onGround = true; break; } } // Remove if fallen off screen if (self.y > 2800) { self.destroy(); for (var j = powerups.length - 1; j >= 0; j--) { if (powerups[j] === self) { powerups.splice(j, 1); break; } } return; } } else { // Bounce effect when on ground self.y += Math.sin(self.lifeTime * 0.1) * 0.5; } if (self.lifeTime > self.maxLifeTime) { self.destroy(); for (var i = powerups.length - 1; i >= 0; i--) { if (powerups[i] === self) { powerups.splice(i, 1); break; } } } // Check collection by players if (!self.collected) { for (var i = 0; i < fighters.length; i++) { var fighter = fighters[i]; var distance = Math.sqrt(Math.pow(fighter.x - self.x, 2) + Math.pow(fighter.y - self.y, 2)); if (distance < 80) { self.collected = true; // Increase powerup count for stacking effects fighter.powerupCount++; // Visual effects for powerup collection LK.getSound('powerup').play(); LK.effects.flashObject(fighter, 0xffd700, 800); // Create scaling animation effect tween(fighter, { scaleX: 1.3, scaleY: 1.3 }, { duration: 200, easing: tween.bounceOut, onFinish: function onFinish() { // Scale back down slightly tween(fighter, { scaleX: 1.0 + fighter.powerupCount * 0.2, scaleY: 1.0 + fighter.powerupCount * 0.2 }, { duration: 300, easing: tween.easeOut }); } }); // Temporary knockback resistance boost (stacks with permanent effects) var originalResistance = fighter.knockbackResistance; fighter.knockbackResistance *= 0.7; // 30% less knockback temporarily LK.setTimeout(function () { fighter.knockbackResistance = originalResistance; }, 3000); self.destroy(); for (var j = powerups.length - 1; j >= 0; j--) { if (powerups[j] === self) { powerups.splice(j, 1); break; } } break; } } } }; return self; }); var SmokeTrail = Container.expand(function (playerNumber, x, y) { var self = Container.call(this); var assetName = 'smokeTrail1'; if (playerNumber === 2) assetName = 'smokeTrail2';else if (playerNumber === 3) assetName = 'smokeTrail3';else if (playerNumber === 4) assetName = 'smokeTrail4'; var smokeGraphics = self.attachAsset(assetName, { anchorX: 0.5, anchorY: 0.5 }); self.x = x; self.y = y; self.lifeTime = 0; self.maxLifeTime = 30; // 0.5 seconds at 60fps self.startScale = 0.6 + Math.random() * 0.4; self.endScale = 1.2 + Math.random() * 0.8; smokeGraphics.scaleX = self.startScale; smokeGraphics.scaleY = self.startScale; smokeGraphics.alpha = 0.9; self.velocityX = (Math.random() - 0.5) * 2; self.velocityY = -2 - Math.random() * 3; self.update = function () { self.lifeTime++; var progress = self.lifeTime / self.maxLifeTime; // Move the smoke particle self.x += self.velocityX; self.y += self.velocityY; // Fade out and scale up over time smokeGraphics.alpha = 0.9 * (1 - progress); var currentScale = self.startScale + (self.endScale - self.startScale) * progress; smokeGraphics.scaleX = currentScale; smokeGraphics.scaleY = currentScale; // Remove when life time is over if (self.lifeTime >= self.maxLifeTime) { self.destroy(); for (var i = smokeTrails.length - 1; i >= 0; i--) { if (smokeTrails[i] === self) { smokeTrails.splice(i, 1); break; } } } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x87ceeb }); /**** * Game Code ****/ // Game variables var fighters = []; var platforms = []; var powerups = []; var smokeTrails = []; var player1Lives = 3; var player2Lives = 3; var player3Lives = 3; var player4Lives = 3; var activePlayers = 4; var draggedFighter = null; var lastTapTime = 0; var powerupSpawnTimer = 0; // Camera zoom variables var cameraZoom = 1.0; var targetCameraZoom = 1.0; var cameraUpdateTimer = 0; var lastPlayerSpread = 0; // Function to calculate camera zoom based on player positions function calculateCameraZoom() { var aliveFighters = []; // Collect all alive fighters for (var i = 0; i < fighters.length; i++) { var fighter = fighters[i]; if (!fighter.destroyed) { if (fighter.playerNumber === 1 && player1Lives > 0) aliveFighters.push(fighter);else if (fighter.playerNumber === 2 && player2Lives > 0) aliveFighters.push(fighter);else if (fighter.playerNumber === 3 && player3Lives > 0) aliveFighters.push(fighter);else if (fighter.playerNumber === 4 && player4Lives > 0) aliveFighters.push(fighter); } } if (aliveFighters.length < 2) { return 1.0; // Default zoom if less than 2 players } // Calculate bounding box of all alive players var minX = aliveFighters[0].x; var maxX = aliveFighters[0].x; var minY = aliveFighters[0].y; var maxY = aliveFighters[0].y; for (var i = 1; i < aliveFighters.length; i++) { var fighter = aliveFighters[i]; if (fighter.x < minX) minX = fighter.x; if (fighter.x > maxX) maxX = fighter.x; if (fighter.y < minY) minY = fighter.y; if (fighter.y > maxY) maxY = fighter.y; } // Calculate spread (distance between furthest players) var spreadX = maxX - minX; var spreadY = maxY - minY; var totalSpread = Math.sqrt(spreadX * spreadX + spreadY * spreadY); // Calculate zoom based on spread var minSpread = 200; // Minimum distance for max zoom var maxSpread = 1200; // Maximum distance for min zoom var minZoom = 0.6; // Minimum zoom level (zoomed out) var maxZoom = 1.4; // Maximum zoom level (zoomed in) // Clamp spread to our range if (totalSpread < minSpread) totalSpread = minSpread; if (totalSpread > maxSpread) totalSpread = maxSpread; // Calculate zoom inversely proportional to spread var zoomProgress = (totalSpread - minSpread) / (maxSpread - minSpread); var zoom = maxZoom - zoomProgress * (maxZoom - minZoom); return zoom; } // Add background image var backgroundImage = game.addChild(LK.getAsset('background', { anchorX: 0, anchorY: 0, x: 0, y: 0 })); // Add second background image on top of the first var backgroundOverlay = game.addChild(LK.getAsset('backgroundOverlay', { anchorX: 0, anchorY: 1, x: 0, y: 2732 })); // Add third background image above the previous two var backgroundTop = game.addChild(LK.getAsset('backgroundTop', { anchorX: 0, anchorY: 1, x: 0, y: 2732 })); // Button press states var leftButtonPressed = false; var rightButtonPressed = false; var jumpButtonPressed = false; var attackButtonPressed = false; // Create platforms var mainPlatform = game.addChild(LK.getAsset('mainPlatform', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1400 })); platforms.push(mainPlatform); var leftPlatform = game.addChild(LK.getAsset('platform', { anchorX: 0.5, anchorY: 0.5, x: 400, y: 1100 })); platforms.push(leftPlatform); var rightPlatform = game.addChild(LK.getAsset('platform', { anchorX: 0.5, anchorY: 0.5, x: 1648, y: 1100 })); platforms.push(rightPlatform); var topPlatform = game.addChild(LK.getAsset('platform', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 800 })); platforms.push(topPlatform); // Add decorative textured images directly on top of platforms var mainPlatformTexture = game.addChild(LK.getAsset('platformTexture', { anchorX: 0.5, anchorY: 1.0, x: 1024, y: 1250 })); mainPlatformTexture.scaleX = 2.5; // Scale to match main platform width mainPlatformTexture.scaleY = 0.8; var leftPlatformTexture = game.addChild(LK.getAsset('platformTexture', { anchorX: 0.5, anchorY: 1.0, x: 400, y: 1000 })); leftPlatformTexture.scaleX = 1.25; // Scale to match platform width leftPlatformTexture.scaleY = 0.6; var rightPlatformTexture = game.addChild(LK.getAsset('platformTexture', { anchorX: 0.5, anchorY: 1.0, x: 1648, y: 1000 })); rightPlatformTexture.scaleX = 1.25; // Scale to match platform width rightPlatformTexture.scaleY = 0.6; var topPlatformTexture = game.addChild(LK.getAsset('platformTexture', { anchorX: 0.5, anchorY: 1.0, x: 1024, y: 700 })); topPlatformTexture.scaleX = 1.25; // Scale to match platform width topPlatformTexture.scaleY = 0.6; // Create fighters var player1 = game.addChild(new Fighter(1)); player1.x = 400; player1.y = 1000; fighters.push(player1); var player2 = game.addChild(new Fighter(2)); player2.x = 1648; player2.y = 1000; fighters.push(player2); var player3 = game.addChild(new Fighter(3)); player3.x = 700; player3.y = 700; fighters.push(player3); var player4 = game.addChild(new Fighter(4)); player4.x = 1348; player4.y = 700; fighters.push(player4); // UI Elements var scoreText = new Text2('Player 1: 0 | Player 2: 0', { size: 80, fill: 0xFFFFFF }); scoreText.anchor.set(0.5, 0); LK.gui.top.addChild(scoreText); // Create left movement button for player 1 var leftButton = LK.getAsset('leftButtonIcon', { anchorX: 0, anchorY: 1, x: 50, y: -160, scaleX: 3, scaleY: 3 }); LK.gui.bottomLeft.addChild(leftButton); // Create right movement button for player 1 var rightButton = LK.getAsset('rightButtonIcon', { anchorX: 0, anchorY: 1, x: 280, y: -160, scaleX: 3, scaleY: 3 }); LK.gui.bottomLeft.addChild(rightButton); // Create jump button for player 1 var jumpButton = LK.getAsset('jumpButtonIcon', { anchorX: 0, anchorY: 1, x: 165, y: -300, scaleX: 3, scaleY: 3 }); LK.gui.bottomLeft.addChild(jumpButton); // Create attack button for player 1 var attackButton = LK.getAsset('attackButtonIcon', { anchorX: 1, anchorY: 1, x: -100, y: -160, scaleX: 3, scaleY: 3 }); LK.gui.bottomRight.addChild(attackButton); function updateScoreDisplay() { scoreText.setText('P1 Lives: ' + player1Lives + ' | P2 Lives: ' + player2Lives + ' | P3 Lives: ' + player3Lives + ' | P4 Lives: ' + player4Lives); } // Left button event handler leftButton.down = function (x, y, obj) { leftButtonPressed = true; leftButton.alpha = 0.6; tween(leftButton, { scaleX: 2.7, scaleY: 2.7 }, { duration: 100 }); }; leftButton.up = function (x, y, obj) { leftButtonPressed = false; leftButton.alpha = 1.0; tween(leftButton, { scaleX: 3.0, scaleY: 3.0 }, { duration: 100 }); }; // Right button event handler rightButton.down = function (x, y, obj) { rightButtonPressed = true; rightButton.alpha = 0.6; tween(rightButton, { scaleX: 2.7, scaleY: 2.7 }, { duration: 100 }); }; rightButton.up = function (x, y, obj) { rightButtonPressed = false; rightButton.alpha = 1.0; tween(rightButton, { scaleX: 3.0, scaleY: 3.0 }, { duration: 100 }); }; // Jump button event handler jumpButton.down = function (x, y, obj) { jumpButtonPressed = true; jumpButton.alpha = 0.6; tween(jumpButton, { scaleX: 2.7, scaleY: 2.7 }, { duration: 100 }); }; jumpButton.up = function (x, y, obj) { jumpButtonPressed = false; jumpButton.alpha = 1.0; tween(jumpButton, { scaleX: 3.0, scaleY: 3.0 }, { duration: 100 }); }; // Attack button event handler attackButton.down = function (x, y, obj) { attackButtonPressed = true; attackButton.alpha = 0.6; tween(attackButton, { scaleX: 2.7, scaleY: 2.7 }, { duration: 100 }); }; attackButton.up = function (x, y, obj) { attackButtonPressed = false; attackButton.alpha = 1.0; tween(attackButton, { scaleX: 3.0, scaleY: 3.0 }, { duration: 100 }); }; function spawnPowerUp() { if (powerups.length < 2) { var powerup = game.addChild(new PowerUp()); // Spawn from random position above the screen powerup.x = 300 + Math.random() * 1448; // Random X within game bounds powerup.y = -100; // Start above screen powerup.velocityY = 2 + Math.random() * 3; // Initial falling speed powerups.push(powerup); } } // Game controls game.down = function (x, y, obj) { var currentTime = LK.ticks; var tapSpeed = 20; // frames between taps for double tap // Find closest fighter to touch point (only player 1) var closestFighter = null; var closestDistance = Infinity; for (var i = 0; i < fighters.length; i++) { var fighter = fighters[i]; // Only allow touch control for player 1 if they're still alive if (fighter.playerNumber !== 1 || player1Lives <= 0) continue; var distance = Math.sqrt(Math.pow(fighter.x - x, 2) + Math.pow(fighter.y - y, 2)); if (distance < closestDistance && distance < 200) { closestDistance = distance; closestFighter = fighter; } } if (closestFighter) { // Check for double tap (attack) if (currentTime - lastTapTime < tapSpeed) { closestFighter.attack(x, y); draggedFighter = null; } else { draggedFighter = closestFighter; } lastTapTime = currentTime; } }; game.move = function (x, y, obj) { if (draggedFighter && draggedFighter.playerNumber === 1) { var deltaX = x - draggedFighter.x; var deltaY = y - draggedFighter.y; // Move fighter towards touch point draggedFighter.velocityX += deltaX * 0.3; // Jump if dragging upward and has jumps remaining if (deltaY < -50 && draggedFighter.jumpsRemaining > 0) { draggedFighter.velocityY = -draggedFighter.jumpPower; draggedFighter.jumpsRemaining--; draggedFighter.onGround = false; LK.getSound('jump').play(); } } }; game.up = function (x, y, obj) { draggedFighter = null; }; // Function to find closest target for AI function findClosestTarget(aiPlayer) { var closestPlayer = null; var closestDistance = Infinity; for (var i = 0; i < fighters.length; i++) { var fighter = fighters[i]; // Skip self, dead players, and destroyed fighters if (fighter === aiPlayer) continue; if (fighter.destroyed) continue; if (fighter.playerNumber === 1 && player1Lives <= 0) continue; if (fighter.playerNumber === 2 && player2Lives <= 0) continue; if (fighter.playerNumber === 3 && player3Lives <= 0) continue; if (fighter.playerNumber === 4 && player4Lives <= 0) continue; var distance = Math.sqrt(Math.pow(fighter.x - aiPlayer.x, 2) + Math.pow(fighter.y - aiPlayer.y, 2)); if (distance < closestDistance) { closestDistance = distance; closestPlayer = fighter; } } return { player: closestPlayer, distance: closestDistance }; } // AI variables for player 2 var aiDecisionTimer = 0; var aiAction = 'idle'; var aiTargetX = player2.x; var aiCooldown = 0; // AI variables for player 3 var ai3DecisionTimer = 0; var ai3Action = 'idle'; var ai3TargetX = 700; var ai3Cooldown = 0; // AI variables for player 4 var ai4DecisionTimer = 0; var ai4Action = 'idle'; var ai4TargetX = 1348; var ai4Cooldown = 0; game.update = function () { // Start background music on first update if (LK.ticks === 1) { LK.playMusic('backgroundMusic'); } // Handle continuous button presses for player 1 (only if alive) if (player1Lives > 0) { if (leftButtonPressed) { player1.velocityX -= player1.moveSpeed * 0.5; } if (rightButtonPressed) { player1.velocityX += player1.moveSpeed * 0.5; } if (jumpButtonPressed && player1.jumpsRemaining > 0) { player1.velocityY = -player1.jumpPower; player1.jumpsRemaining--; player1.onGround = false; LK.getSound('jump').play(); jumpButtonPressed = false; // Prevent continuous jumping } if (attackButtonPressed) { player1.attack(player1.x, player1.y); attackButtonPressed = false; // Prevent continuous attacking } } // Spawn power-ups occasionally powerupSpawnTimer++; if (powerupSpawnTimer > 300) { // Every 5 seconds if (Math.random() < 0.3) { spawnPowerUp(); } powerupSpawnTimer = 0; } // AI for player 2 (only if alive) if (player2Lives > 0) { aiDecisionTimer++; if (aiCooldown > 0) { aiCooldown--; } // Make AI decisions every 30 frames (half second) if (aiDecisionTimer > 30) { aiDecisionTimer = 0; // Find closest target var closestTarget = findClosestTarget(player2); var distanceToClosest = closestTarget.distance; // Check for nearby powerups var nearestPowerup = null; var nearestPowerupDistance = Infinity; for (var i = 0; i < powerups.length; i++) { var powerup = powerups[i]; var distance = Math.sqrt(Math.pow(powerup.x - player2.x, 2) + Math.pow(powerup.y - player2.y, 2)); if (distance < nearestPowerupDistance) { nearestPowerupDistance = distance; nearestPowerup = powerup; } } // AI decision making if (aiCooldown === 0 && closestTarget.player) { if (distanceToClosest < 200 && Math.random() < 0.8) { // Attack if close to closest target aiAction = 'attack'; aiCooldown = 60; // Attack cooldown } else if (nearestPowerup && nearestPowerupDistance < 300 && Math.random() < 0.3) { // Go for powerup if nearby (lower priority) aiAction = 'moveToPowerup'; aiTargetX = nearestPowerup.x; } else if (Math.random() < 0.9) { // Chase closest target most of the time aiAction = 'moveToPlayer'; aiTargetX = closestTarget.player.x; } else { // Random movement (rare) aiAction = 'randomMove'; aiTargetX = 400 + Math.random() * 1248; // Random X within screen bounds } } else if (!closestTarget.player) { aiAction = 'idle'; } } // Execute AI actions if (aiAction === 'attack' && closestTarget.player) { player2.attack(closestTarget.player.x, closestTarget.player.y); aiAction = 'idle'; } else if (aiAction === 'moveToPowerup' || aiAction === 'moveToPlayer' || aiAction === 'randomMove') { // Move towards target var deltaX = aiTargetX - player2.x; if (Math.abs(deltaX) > 50) { player2.velocityX += deltaX * 0.2; } // Jump if target is above or if stuck if (aiAction === 'moveToPlayer' && closestTarget && closestTarget.player && closestTarget.player.y < player2.y - 100 || Math.abs(player2.velocityX) < 1 && player2.onGround && Math.random() < 0.1) { if (player2.jumpsRemaining > 0) { player2.velocityY = -player2.jumpPower; player2.jumpsRemaining--; player2.onGround = false; LK.getSound('jump').play(); } } // Reset action if close enough to target if (Math.abs(deltaX) < 100) { aiAction = 'idle'; } } // AI logic: double jump if can't reach player who is much higher var heightDifference = player2.y - player1.y; var horizontalDistance = Math.abs(player2.x - player1.x); if (heightDifference > 200 && horizontalDistance < 400 && player2.jumpsRemaining > 0) { // Check if AI is below player and perform double jump to reach them player2.velocityY = -player2.jumpPower; player2.jumpsRemaining--; player2.onGround = false; LK.getSound('jump').play(); } // AI emergency jump if falling off platform if (player2.y > 1500 && player2.jumpsRemaining > 0) { player2.velocityY = -player2.jumpPower; player2.jumpsRemaining--; player2.onGround = false; LK.getSound('jump').play(); } } // AI for player 3 (only if alive) if (player3Lives > 0) { ai3DecisionTimer++; if (ai3Cooldown > 0) { ai3Cooldown--; } if (ai3DecisionTimer > 45) { ai3DecisionTimer = 0; // Find closest target var closestTarget_3 = findClosestTarget(player3); var closestDistance_3 = closestTarget_3.distance; if (ai3Cooldown === 0 && closestTarget_3.player) { if (closestDistance_3 < 200 && Math.random() < 0.7) { ai3Action = 'attack'; ai3Cooldown = 70; } else if (Math.random() < 0.8) { // Chase closest target most of the time ai3Action = 'moveToPlayer'; ai3TargetX = closestTarget_3.player.x; } else if (Math.random() < 0.3) { ai3Action = 'randomMove'; ai3TargetX = 400 + Math.random() * 1248; } else { ai3Action = 'idle'; } } else if (!closestTarget_3.player) { ai3Action = 'idle'; } } if (ai3Action === 'attack' && closestTarget_3.player) { player3.attack(closestTarget_3.player.x, closestTarget_3.player.y); ai3Action = 'idle'; } else if (ai3Action === 'moveToPlayer' || ai3Action === 'randomMove') { var deltaX_3 = ai3TargetX - player3.x; if (Math.abs(deltaX_3) > 50) { player3.velocityX += deltaX_3 * 0.18; } if (Math.abs(deltaX_3) > 200 && player3.onGround && Math.random() < 0.15) { if (player3.jumpsRemaining > 0) { player3.velocityY = -player3.jumpPower; player3.jumpsRemaining--; player3.onGround = false; LK.getSound('jump').play(); } } if (Math.abs(deltaX_3) < 100) { ai3Action = 'idle'; } } if (player3.y > 1500 && player3.jumpsRemaining > 0) { player3.velocityY = -player3.jumpPower; player3.jumpsRemaining--; player3.onGround = false; LK.getSound('jump').play(); } } // AI for player 4 (only if alive) if (player4Lives > 0) { ai4DecisionTimer++; if (ai4Cooldown > 0) { ai4Cooldown--; } if (ai4DecisionTimer > 35) { ai4DecisionTimer = 0; // Find closest target var closestTarget_4 = findClosestTarget(player4); var closestDistance_4 = closestTarget_4.distance; if (ai4Cooldown === 0 && closestTarget_4.player) { if (closestDistance_4 < 180 && Math.random() < 0.85) { ai4Action = 'attack'; ai4Cooldown = 50; } else if (Math.random() < 0.9) { // Chase closest target aggressively ai4Action = 'moveToPlayer'; ai4TargetX = closestTarget_4.player.x; } else if (Math.random() < 0.4) { ai4Action = 'randomMove'; ai4TargetX = 400 + Math.random() * 1248; } else { ai4Action = 'idle'; } } else if (!closestTarget_4.player) { ai4Action = 'idle'; } } if (ai4Action === 'attack' && closestTarget_4.player) { player4.attack(closestTarget_4.player.x, closestTarget_4.player.y); ai4Action = 'idle'; } else if (ai4Action === 'moveToPlayer' || ai4Action === 'randomMove') { var deltaX_4 = ai4TargetX - player4.x; if (Math.abs(deltaX_4) > 50) { player4.velocityX += deltaX_4 * 0.22; } if (Math.abs(deltaX_4) > 150 && player4.onGround && Math.random() < 0.2) { if (player4.jumpsRemaining > 0) { player4.velocityY = -player4.jumpPower; player4.jumpsRemaining--; player4.onGround = false; LK.getSound('jump').play(); } } if (Math.abs(deltaX_4) < 100) { ai4Action = 'idle'; } } if (player4.y > 1500 && player4.jumpsRemaining > 0) { player4.velocityY = -player4.jumpPower; player4.jumpsRemaining--; player4.onGround = false; LK.getSound('jump').play(); } } // Update all fighters for (var i = fighters.length - 1; i >= 0; i--) { if (fighters[i] && !fighters[i].destroyed) { fighters[i].update(); } } // Update all powerups for (var i = powerups.length - 1; i >= 0; i--) { if (powerups[i] && !powerups[i].destroyed) { powerups[i].update(); } } // Update all smoke trails for (var i = smokeTrails.length - 1; i >= 0; i--) { if (smokeTrails[i] && !smokeTrails[i].destroyed) { smokeTrails[i].update(); } } // Update camera zoom based on player positions cameraUpdateTimer++; if (cameraUpdateTimer > 10) { // Update camera every 10 frames for smooth performance cameraUpdateTimer = 0; var newTargetZoom = calculateCameraZoom(); // Only update if zoom change is significant to avoid constant tweening if (Math.abs(newTargetZoom - targetCameraZoom) > 0.05) { targetCameraZoom = newTargetZoom; // Stop any existing camera tween tween.stop(game, { scaleX: true, scaleY: true }); // Tween to new zoom level tween(game, { scaleX: targetCameraZoom, scaleY: targetCameraZoom }, { duration: 1000, easing: tween.easeInOut }); } } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Fighter = Container.expand(function (playerNumber, color) {
var self = Container.call(this);
var assetName = 'player1';
if (playerNumber === 2) assetName = 'player2';else if (playerNumber === 3) assetName = 'player3';else if (playerNumber === 4) assetName = 'player4';
var fighterGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 1.0
});
self.playerNumber = playerNumber;
self.velocityX = 0;
self.velocityY = 0;
self.onGround = false;
self.jumpsRemaining = 2; // Allow double jump
self.health = 0;
self.maxHealth = 100;
self.damage = 0; // New damage accumulation property
self.moveSpeed = 8;
self.jumpPower = 25;
self.knockbackResistance = 1.0;
self.lastHitTime = 0;
self.invulnerable = false;
self.powerupCount = 0; // Track number of powerups collected
self.baseMoveSpeed = 8; // Store original move speed
self.baseJumpPower = 25; // Store original jump power
self.baseDamage = 15; // Store original damage
self.baseKnockback = 20; // Store original knockback
self.lastVelocityX = 0; // Track last velocity for rotation
// Create damage bar background
var damageBarBg = self.addChild(LK.getAsset('platform', {
width: 100,
height: 12,
anchorX: 0.5,
anchorY: 1.0,
x: 0,
y: -130
}));
damageBarBg.tint = 0x333333;
damageBarBg.scaleX = 0.8;
damageBarBg.scaleY = 0.3;
// Create damage bar fill
self.damageBar = self.addChild(LK.getAsset('platform', {
width: 100,
height: 12,
anchorX: 0,
anchorY: 1.0,
x: -40,
y: -130
}));
self.damageBar.tint = 0xff4444;
self.damageBar.scaleX = 0;
self.damageBar.scaleY = 0.3;
// Create damage text
self.damageText = self.addChild(new Text2('0%', {
size: 30,
fill: 0xFFFFFF,
stroke: 0x000000,
strokeThickness: 4
}));
self.damageText.anchor.set(0.5, 1);
self.damageText.x = 0;
self.damageText.y = -140;
self.takeDamage = function (damage, knockbackX, knockbackY) {
if (self.invulnerable) return;
self.health += damage;
self.damage += damage; // Accumulate damage for knockback scaling
// Cap damage at 999%
if (self.damage > 999) self.damage = 999;
// Update damage display
self.damageText.setText(Math.floor(self.damage) + '%');
// Reapply stroke properties to maintain black background texture
if (self.damageText.style) {
self.damageText.style.stroke = 0x000000;
self.damageText.style.strokeThickness = 4;
}
// Update damage bar scale (0 to 1 based on damage up to 200%)
var barScale = Math.min(self.damage / 200, 1);
self.damageBar.scaleX = barScale * 0.8;
// Change bar color based on damage level
if (self.damage < 50) {
self.damageBar.tint = 0x44ff44; // Green for low damage
} else if (self.damage < 100) {
self.damageBar.tint = 0xffff44; // Yellow for medium damage
} else if (self.damage < 150) {
self.damageBar.tint = 0xff8844; // Orange for high damage
} else {
self.damageBar.tint = 0xff4444; // Red for very high damage
}
// Check if player has reached 200 damage - explosion and life loss
if (self.damage >= 200) {
// Create explosion effect with tween
tween(self, {
scaleX: 2.0,
scaleY: 2.0,
alpha: 0
}, {
duration: 500,
easing: tween.bounceOut,
onFinish: function onFinish() {
// Reset scale and alpha after explosion
self.scaleX = 1.0;
self.scaleY = 1.0;
self.alpha = 1.0;
// Trigger respawn which handles life loss
self.respawn();
}
});
// Flash screen effect for dramatic explosion
LK.effects.flashScreen(0xff8800, 600);
// Play explosion sound effect
LK.getSound('explosion').play();
// Reset damage to prevent multiple explosions
self.damage = 0;
return; // Exit early to prevent further processing this frame
}
// Knockback multiplier based on accumulated damage - exponential scaling for stronger hits at higher damage
var knockbackMultiplier = 1.5 + self.damage / 20 + Math.pow(self.damage / 100, 1.5); // Exponential scaling that increases dramatically with damage
self.velocityX += knockbackX * knockbackMultiplier / self.knockbackResistance;
self.velocityY += knockbackY * knockbackMultiplier / self.knockbackResistance;
// Flash effect when hit
LK.effects.flashObject(self, 0xff0000, 300);
self.invulnerable = true;
LK.setTimeout(function () {
self.invulnerable = false;
}, 500);
LK.getSound('hit').play();
};
self.attack = function (targetX, targetY) {
var attackRange = 150;
var damage = self.baseDamage + self.powerupCount * 8; // Increase damage by 8 per powerup
var baseKnockback = self.baseKnockback + self.powerupCount * 10; // Increase knockback by 10 per powerup
for (var i = 0; i < fighters.length; i++) {
var target = fighters[i];
if (target === self) continue;
var distance = Math.sqrt(Math.pow(target.x - self.x, 2) + Math.pow(target.y - self.y, 2));
if (distance <= attackRange) {
var angle = Math.atan2(target.y - self.y, target.x - self.x);
var knockbackX = Math.cos(angle) * baseKnockback;
var knockbackY = Math.sin(angle) * baseKnockback - 5; // Slight upward angle
target.takeDamage(damage, knockbackX, knockbackY);
}
}
};
self.update = function () {
// Update movement stats based on powerups
self.moveSpeed = self.baseMoveSpeed + self.powerupCount * 3; // Increase move speed by 3 per powerup
self.jumpPower = self.baseJumpPower + self.powerupCount * 5; // Increase jump power by 5 per powerup
// Apply gravity
if (!self.onGround) {
self.velocityY += 1.2;
}
// Apply friction
self.velocityX *= 0.85;
if (self.onGround) {
self.velocityX *= 0.75;
}
// Limit velocities to prevent teleportation
var maxVelocityX = 20;
var maxVelocityY = 30;
if (self.velocityX > maxVelocityX) {
self.velocityX = maxVelocityX;
}
if (self.velocityX < -maxVelocityX) {
self.velocityX = -maxVelocityX;
}
if (self.velocityY > maxVelocityY) {
self.velocityY = maxVelocityY;
}
if (self.velocityY < -maxVelocityY) {
self.velocityY = -maxVelocityY;
}
// Track last position for smoke trail
if (self.lastX === undefined) self.lastX = self.x;
if (self.lastY === undefined) self.lastY = self.y;
// Update position
self.x += self.velocityX;
self.y += self.velocityY;
// Generate smoke trail when moving
var movementSpeed = Math.sqrt(Math.pow(self.x - self.lastX, 2) + Math.pow(self.y - self.lastY, 2));
if (movementSpeed > 3 && LK.ticks % 3 === 0) {
// Calculate movement direction
var movementDirectionX = self.x - self.lastX;
var movementDirectionY = self.y - self.lastY;
// Normalize the direction
var directionLength = Math.sqrt(movementDirectionX * movementDirectionX + movementDirectionY * movementDirectionY);
if (directionLength > 0) {
movementDirectionX /= directionLength;
movementDirectionY /= directionLength;
}
// Position smoke trail behind the movement direction
var smokeDistance = 30; // Distance behind the player
var smokeX = self.x - movementDirectionX * smokeDistance;
var smokeY = self.y - movementDirectionY * smokeDistance + 20;
var smoke = new SmokeTrail(self.playerNumber, smokeX, smokeY);
game.addChild(smoke);
smokeTrails.push(smoke);
}
// Update last position
self.lastX = self.x;
self.lastY = self.y;
// Update visual size based on powerup count
var targetScale = 1.0 + self.powerupCount * 0.2; // Increase size by 20% per powerup
if (Math.abs(fighterGraphics.scaleX) !== targetScale) {
var isFlipped = fighterGraphics.scaleX < 0;
fighterGraphics.scaleX = isFlipped ? -targetScale : targetScale;
fighterGraphics.scaleY = targetScale;
}
// Rotate player based on movement direction
if (Math.abs(self.velocityX) > 1) {
// Only rotate if moving with significant velocity
if (self.velocityX > 0) {
// Moving right - face right (normal orientation)
fighterGraphics.scaleX = Math.abs(fighterGraphics.scaleX);
} else {
// Moving left - face left (flip horizontally)
fighterGraphics.scaleX = -Math.abs(fighterGraphics.scaleX);
}
}
// Platform collision
var wasOnGround = self.onGround;
self.onGround = false;
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
// More precise collision detection with tighter bounds
var platformLeft = platform.x - platform.width / 2 + 10; // Add small margin
var platformRight = platform.x + platform.width / 2 - 10; // Add small margin
var platformTop = platform.y - platform.height / 2;
var platformBottom = platform.y + platform.height / 2;
// Check if player is within platform horizontal bounds and approaching from above
if (self.x >= platformLeft && self.x <= platformRight && self.y >= platformTop - 20 && self.y <= platformBottom + 5 && self.velocityY >= 0) {
// Snap player to exact platform surface
self.y = platformTop;
self.velocityY = 0;
// Play landing sound if just landed (wasn't on ground before)
if (!wasOnGround) {
LK.getSound('landing').play();
}
self.onGround = true;
self.jumpsRemaining = 2; // Reset jumps when landing
break;
}
}
// Check if fallen off screen
if (self.y > 2900) {
// Play fall sound effect
LK.getSound('fall').play();
self.respawn();
}
// Fighter-to-fighter collision detection and pushing
for (var i = 0; i < fighters.length; i++) {
var otherFighter = fighters[i];
if (otherFighter === self || otherFighter.destroyed) continue;
// Calculate distance between fighters
var deltaX = otherFighter.x - self.x;
var deltaY = otherFighter.y - self.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
// Check if fighters are colliding (within collision radius)
var collisionRadius = 80; // Adjust this value to change collision sensitivity
if (distance < collisionRadius && distance > 0) {
// Calculate push force based on overlap
var overlap = collisionRadius - distance;
var pushStrength = overlap * 0.3; // Adjust push strength
// Normalize direction vector
var normalX = deltaX / distance;
var normalY = deltaY / distance;
// Apply push forces to both fighters
var pushX = normalX * pushStrength;
var pushY = normalY * pushStrength * 0.3; // Reduced vertical push
// Push both fighters apart
self.velocityX -= pushX;
self.velocityY -= pushY;
otherFighter.velocityX += pushX;
otherFighter.velocityY += pushY;
// Separate fighters to prevent overlap
var separationX = normalX * (overlap * 0.5);
var separationY = normalY * (overlap * 0.3);
self.x -= separationX;
self.y -= separationY;
otherFighter.x += separationX;
otherFighter.y += separationY;
}
}
// Screen boundaries (horizontal bouncing)
if (self.x < 60) {
self.x = 60;
self.velocityX = Math.abs(self.velocityX) * 0.5;
}
if (self.x > 1988) {
self.x = 1988;
self.velocityX = -Math.abs(self.velocityX) * 0.5;
}
};
self.respawn = function () {
// Decrease lives for the player who died
if (self.playerNumber === 1) {
player1Lives--;
} else if (self.playerNumber === 2) {
player2Lives--;
} else if (self.playerNumber === 3) {
player3Lives--;
} else if (self.playerNumber === 4) {
player4Lives--;
}
updateScoreDisplay();
// Check if player is eliminated (no lives left)
var isEliminated = false;
if (self.playerNumber === 1 && player1Lives <= 0) {
isEliminated = true;
} else if (self.playerNumber === 2 && player2Lives <= 0) {
isEliminated = true;
} else if (self.playerNumber === 3 && player3Lives <= 0) {
isEliminated = true;
} else if (self.playerNumber === 4 && player4Lives <= 0) {
isEliminated = true;
}
if (isEliminated) {
// Remove player from the game completely
self.destroy();
// Remove from fighters array
for (var j = fighters.length - 1; j >= 0; j--) {
if (fighters[j] === self) {
fighters.splice(j, 1);
break;
}
}
activePlayers--;
// Check if only one player remains
if (activePlayers <= 1) {
// Find the winner
var winner = "";
if (player1Lives > 0) winner = "Player 1";else if (player2Lives > 0) winner = "Player 2";else if (player3Lives > 0) winner = "Player 3";else if (player4Lives > 0) winner = "Player 4";
LK.setScore(100);
LK.showGameOver();
}
} else {
// Player still has lives, respawn normally
if (self.playerNumber === 1) {
self.x = 400;
} else if (self.playerNumber === 2) {
self.x = 1648;
} else if (self.playerNumber === 3) {
self.x = 700;
} else if (self.playerNumber === 4) {
self.x = 1348;
}
self.y = 1000;
self.velocityX = 0;
self.velocityY = 0;
self.health = 0;
self.damage = 0; // Reset damage on respawn
self.powerupCount = 0; // Reset powerup count on respawn
self.damageText.setText('0%');
// Reapply stroke properties to maintain black background texture
if (self.damageText.style) {
self.damageText.style.stroke = 0x000000;
self.damageText.style.strokeThickness = 4;
}
self.damageBar.scaleX = 0;
self.damageBar.tint = 0x44ff44;
self.jumpsRemaining = 2; // Reset jumps on respawn
}
};
return self;
});
var PowerUp = Container.expand(function () {
var self = Container.call(this);
var powerupGraphics = self.attachAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5
});
self.lifeTime = 0;
self.maxLifeTime = 600; // 10 seconds at 60fps
self.collected = false;
self.velocityY = 0; // Falling velocity
self.onGround = false; // Track if powerup has landed
self.update = function () {
self.lifeTime++;
self.rotation += 0.05;
// Apply gravity if not on ground
if (!self.onGround) {
self.velocityY += 0.8; // Gravity effect
self.y += self.velocityY;
// Check platform collision for landing
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
var platformLeft = platform.x - platform.width / 2;
var platformRight = platform.x + platform.width / 2;
var platformTop = platform.y - platform.height / 2;
var platformBottom = platform.y + platform.height / 2;
// Check if powerup is within platform bounds and landing on top
if (self.x >= platformLeft && self.x <= platformRight && self.y >= platformTop - 30 && self.y <= platformBottom && self.velocityY >= 0) {
self.y = platformTop - 30; // Position on top of platform
self.velocityY = 0;
self.onGround = true;
break;
}
}
// Remove if fallen off screen
if (self.y > 2800) {
self.destroy();
for (var j = powerups.length - 1; j >= 0; j--) {
if (powerups[j] === self) {
powerups.splice(j, 1);
break;
}
}
return;
}
} else {
// Bounce effect when on ground
self.y += Math.sin(self.lifeTime * 0.1) * 0.5;
}
if (self.lifeTime > self.maxLifeTime) {
self.destroy();
for (var i = powerups.length - 1; i >= 0; i--) {
if (powerups[i] === self) {
powerups.splice(i, 1);
break;
}
}
}
// Check collection by players
if (!self.collected) {
for (var i = 0; i < fighters.length; i++) {
var fighter = fighters[i];
var distance = Math.sqrt(Math.pow(fighter.x - self.x, 2) + Math.pow(fighter.y - self.y, 2));
if (distance < 80) {
self.collected = true;
// Increase powerup count for stacking effects
fighter.powerupCount++;
// Visual effects for powerup collection
LK.getSound('powerup').play();
LK.effects.flashObject(fighter, 0xffd700, 800);
// Create scaling animation effect
tween(fighter, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 200,
easing: tween.bounceOut,
onFinish: function onFinish() {
// Scale back down slightly
tween(fighter, {
scaleX: 1.0 + fighter.powerupCount * 0.2,
scaleY: 1.0 + fighter.powerupCount * 0.2
}, {
duration: 300,
easing: tween.easeOut
});
}
});
// Temporary knockback resistance boost (stacks with permanent effects)
var originalResistance = fighter.knockbackResistance;
fighter.knockbackResistance *= 0.7; // 30% less knockback temporarily
LK.setTimeout(function () {
fighter.knockbackResistance = originalResistance;
}, 3000);
self.destroy();
for (var j = powerups.length - 1; j >= 0; j--) {
if (powerups[j] === self) {
powerups.splice(j, 1);
break;
}
}
break;
}
}
}
};
return self;
});
var SmokeTrail = Container.expand(function (playerNumber, x, y) {
var self = Container.call(this);
var assetName = 'smokeTrail1';
if (playerNumber === 2) assetName = 'smokeTrail2';else if (playerNumber === 3) assetName = 'smokeTrail3';else if (playerNumber === 4) assetName = 'smokeTrail4';
var smokeGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
self.x = x;
self.y = y;
self.lifeTime = 0;
self.maxLifeTime = 30; // 0.5 seconds at 60fps
self.startScale = 0.6 + Math.random() * 0.4;
self.endScale = 1.2 + Math.random() * 0.8;
smokeGraphics.scaleX = self.startScale;
smokeGraphics.scaleY = self.startScale;
smokeGraphics.alpha = 0.9;
self.velocityX = (Math.random() - 0.5) * 2;
self.velocityY = -2 - Math.random() * 3;
self.update = function () {
self.lifeTime++;
var progress = self.lifeTime / self.maxLifeTime;
// Move the smoke particle
self.x += self.velocityX;
self.y += self.velocityY;
// Fade out and scale up over time
smokeGraphics.alpha = 0.9 * (1 - progress);
var currentScale = self.startScale + (self.endScale - self.startScale) * progress;
smokeGraphics.scaleX = currentScale;
smokeGraphics.scaleY = currentScale;
// Remove when life time is over
if (self.lifeTime >= self.maxLifeTime) {
self.destroy();
for (var i = smokeTrails.length - 1; i >= 0; i--) {
if (smokeTrails[i] === self) {
smokeTrails.splice(i, 1);
break;
}
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87ceeb
});
/****
* Game Code
****/
// Game variables
var fighters = [];
var platforms = [];
var powerups = [];
var smokeTrails = [];
var player1Lives = 3;
var player2Lives = 3;
var player3Lives = 3;
var player4Lives = 3;
var activePlayers = 4;
var draggedFighter = null;
var lastTapTime = 0;
var powerupSpawnTimer = 0;
// Camera zoom variables
var cameraZoom = 1.0;
var targetCameraZoom = 1.0;
var cameraUpdateTimer = 0;
var lastPlayerSpread = 0;
// Function to calculate camera zoom based on player positions
function calculateCameraZoom() {
var aliveFighters = [];
// Collect all alive fighters
for (var i = 0; i < fighters.length; i++) {
var fighter = fighters[i];
if (!fighter.destroyed) {
if (fighter.playerNumber === 1 && player1Lives > 0) aliveFighters.push(fighter);else if (fighter.playerNumber === 2 && player2Lives > 0) aliveFighters.push(fighter);else if (fighter.playerNumber === 3 && player3Lives > 0) aliveFighters.push(fighter);else if (fighter.playerNumber === 4 && player4Lives > 0) aliveFighters.push(fighter);
}
}
if (aliveFighters.length < 2) {
return 1.0; // Default zoom if less than 2 players
}
// Calculate bounding box of all alive players
var minX = aliveFighters[0].x;
var maxX = aliveFighters[0].x;
var minY = aliveFighters[0].y;
var maxY = aliveFighters[0].y;
for (var i = 1; i < aliveFighters.length; i++) {
var fighter = aliveFighters[i];
if (fighter.x < minX) minX = fighter.x;
if (fighter.x > maxX) maxX = fighter.x;
if (fighter.y < minY) minY = fighter.y;
if (fighter.y > maxY) maxY = fighter.y;
}
// Calculate spread (distance between furthest players)
var spreadX = maxX - minX;
var spreadY = maxY - minY;
var totalSpread = Math.sqrt(spreadX * spreadX + spreadY * spreadY);
// Calculate zoom based on spread
var minSpread = 200; // Minimum distance for max zoom
var maxSpread = 1200; // Maximum distance for min zoom
var minZoom = 0.6; // Minimum zoom level (zoomed out)
var maxZoom = 1.4; // Maximum zoom level (zoomed in)
// Clamp spread to our range
if (totalSpread < minSpread) totalSpread = minSpread;
if (totalSpread > maxSpread) totalSpread = maxSpread;
// Calculate zoom inversely proportional to spread
var zoomProgress = (totalSpread - minSpread) / (maxSpread - minSpread);
var zoom = maxZoom - zoomProgress * (maxZoom - minZoom);
return zoom;
}
// Add background image
var backgroundImage = game.addChild(LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
}));
// Add second background image on top of the first
var backgroundOverlay = game.addChild(LK.getAsset('backgroundOverlay', {
anchorX: 0,
anchorY: 1,
x: 0,
y: 2732
}));
// Add third background image above the previous two
var backgroundTop = game.addChild(LK.getAsset('backgroundTop', {
anchorX: 0,
anchorY: 1,
x: 0,
y: 2732
}));
// Button press states
var leftButtonPressed = false;
var rightButtonPressed = false;
var jumpButtonPressed = false;
var attackButtonPressed = false;
// Create platforms
var mainPlatform = game.addChild(LK.getAsset('mainPlatform', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1400
}));
platforms.push(mainPlatform);
var leftPlatform = game.addChild(LK.getAsset('platform', {
anchorX: 0.5,
anchorY: 0.5,
x: 400,
y: 1100
}));
platforms.push(leftPlatform);
var rightPlatform = game.addChild(LK.getAsset('platform', {
anchorX: 0.5,
anchorY: 0.5,
x: 1648,
y: 1100
}));
platforms.push(rightPlatform);
var topPlatform = game.addChild(LK.getAsset('platform', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 800
}));
platforms.push(topPlatform);
// Add decorative textured images directly on top of platforms
var mainPlatformTexture = game.addChild(LK.getAsset('platformTexture', {
anchorX: 0.5,
anchorY: 1.0,
x: 1024,
y: 1250
}));
mainPlatformTexture.scaleX = 2.5; // Scale to match main platform width
mainPlatformTexture.scaleY = 0.8;
var leftPlatformTexture = game.addChild(LK.getAsset('platformTexture', {
anchorX: 0.5,
anchorY: 1.0,
x: 400,
y: 1000
}));
leftPlatformTexture.scaleX = 1.25; // Scale to match platform width
leftPlatformTexture.scaleY = 0.6;
var rightPlatformTexture = game.addChild(LK.getAsset('platformTexture', {
anchorX: 0.5,
anchorY: 1.0,
x: 1648,
y: 1000
}));
rightPlatformTexture.scaleX = 1.25; // Scale to match platform width
rightPlatformTexture.scaleY = 0.6;
var topPlatformTexture = game.addChild(LK.getAsset('platformTexture', {
anchorX: 0.5,
anchorY: 1.0,
x: 1024,
y: 700
}));
topPlatformTexture.scaleX = 1.25; // Scale to match platform width
topPlatformTexture.scaleY = 0.6;
// Create fighters
var player1 = game.addChild(new Fighter(1));
player1.x = 400;
player1.y = 1000;
fighters.push(player1);
var player2 = game.addChild(new Fighter(2));
player2.x = 1648;
player2.y = 1000;
fighters.push(player2);
var player3 = game.addChild(new Fighter(3));
player3.x = 700;
player3.y = 700;
fighters.push(player3);
var player4 = game.addChild(new Fighter(4));
player4.x = 1348;
player4.y = 700;
fighters.push(player4);
// UI Elements
var scoreText = new Text2('Player 1: 0 | Player 2: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreText);
// Create left movement button for player 1
var leftButton = LK.getAsset('leftButtonIcon', {
anchorX: 0,
anchorY: 1,
x: 50,
y: -160,
scaleX: 3,
scaleY: 3
});
LK.gui.bottomLeft.addChild(leftButton);
// Create right movement button for player 1
var rightButton = LK.getAsset('rightButtonIcon', {
anchorX: 0,
anchorY: 1,
x: 280,
y: -160,
scaleX: 3,
scaleY: 3
});
LK.gui.bottomLeft.addChild(rightButton);
// Create jump button for player 1
var jumpButton = LK.getAsset('jumpButtonIcon', {
anchorX: 0,
anchorY: 1,
x: 165,
y: -300,
scaleX: 3,
scaleY: 3
});
LK.gui.bottomLeft.addChild(jumpButton);
// Create attack button for player 1
var attackButton = LK.getAsset('attackButtonIcon', {
anchorX: 1,
anchorY: 1,
x: -100,
y: -160,
scaleX: 3,
scaleY: 3
});
LK.gui.bottomRight.addChild(attackButton);
function updateScoreDisplay() {
scoreText.setText('P1 Lives: ' + player1Lives + ' | P2 Lives: ' + player2Lives + ' | P3 Lives: ' + player3Lives + ' | P4 Lives: ' + player4Lives);
}
// Left button event handler
leftButton.down = function (x, y, obj) {
leftButtonPressed = true;
leftButton.alpha = 0.6;
tween(leftButton, {
scaleX: 2.7,
scaleY: 2.7
}, {
duration: 100
});
};
leftButton.up = function (x, y, obj) {
leftButtonPressed = false;
leftButton.alpha = 1.0;
tween(leftButton, {
scaleX: 3.0,
scaleY: 3.0
}, {
duration: 100
});
};
// Right button event handler
rightButton.down = function (x, y, obj) {
rightButtonPressed = true;
rightButton.alpha = 0.6;
tween(rightButton, {
scaleX: 2.7,
scaleY: 2.7
}, {
duration: 100
});
};
rightButton.up = function (x, y, obj) {
rightButtonPressed = false;
rightButton.alpha = 1.0;
tween(rightButton, {
scaleX: 3.0,
scaleY: 3.0
}, {
duration: 100
});
};
// Jump button event handler
jumpButton.down = function (x, y, obj) {
jumpButtonPressed = true;
jumpButton.alpha = 0.6;
tween(jumpButton, {
scaleX: 2.7,
scaleY: 2.7
}, {
duration: 100
});
};
jumpButton.up = function (x, y, obj) {
jumpButtonPressed = false;
jumpButton.alpha = 1.0;
tween(jumpButton, {
scaleX: 3.0,
scaleY: 3.0
}, {
duration: 100
});
};
// Attack button event handler
attackButton.down = function (x, y, obj) {
attackButtonPressed = true;
attackButton.alpha = 0.6;
tween(attackButton, {
scaleX: 2.7,
scaleY: 2.7
}, {
duration: 100
});
};
attackButton.up = function (x, y, obj) {
attackButtonPressed = false;
attackButton.alpha = 1.0;
tween(attackButton, {
scaleX: 3.0,
scaleY: 3.0
}, {
duration: 100
});
};
function spawnPowerUp() {
if (powerups.length < 2) {
var powerup = game.addChild(new PowerUp());
// Spawn from random position above the screen
powerup.x = 300 + Math.random() * 1448; // Random X within game bounds
powerup.y = -100; // Start above screen
powerup.velocityY = 2 + Math.random() * 3; // Initial falling speed
powerups.push(powerup);
}
}
// Game controls
game.down = function (x, y, obj) {
var currentTime = LK.ticks;
var tapSpeed = 20; // frames between taps for double tap
// Find closest fighter to touch point (only player 1)
var closestFighter = null;
var closestDistance = Infinity;
for (var i = 0; i < fighters.length; i++) {
var fighter = fighters[i];
// Only allow touch control for player 1 if they're still alive
if (fighter.playerNumber !== 1 || player1Lives <= 0) continue;
var distance = Math.sqrt(Math.pow(fighter.x - x, 2) + Math.pow(fighter.y - y, 2));
if (distance < closestDistance && distance < 200) {
closestDistance = distance;
closestFighter = fighter;
}
}
if (closestFighter) {
// Check for double tap (attack)
if (currentTime - lastTapTime < tapSpeed) {
closestFighter.attack(x, y);
draggedFighter = null;
} else {
draggedFighter = closestFighter;
}
lastTapTime = currentTime;
}
};
game.move = function (x, y, obj) {
if (draggedFighter && draggedFighter.playerNumber === 1) {
var deltaX = x - draggedFighter.x;
var deltaY = y - draggedFighter.y;
// Move fighter towards touch point
draggedFighter.velocityX += deltaX * 0.3;
// Jump if dragging upward and has jumps remaining
if (deltaY < -50 && draggedFighter.jumpsRemaining > 0) {
draggedFighter.velocityY = -draggedFighter.jumpPower;
draggedFighter.jumpsRemaining--;
draggedFighter.onGround = false;
LK.getSound('jump').play();
}
}
};
game.up = function (x, y, obj) {
draggedFighter = null;
};
// Function to find closest target for AI
function findClosestTarget(aiPlayer) {
var closestPlayer = null;
var closestDistance = Infinity;
for (var i = 0; i < fighters.length; i++) {
var fighter = fighters[i];
// Skip self, dead players, and destroyed fighters
if (fighter === aiPlayer) continue;
if (fighter.destroyed) continue;
if (fighter.playerNumber === 1 && player1Lives <= 0) continue;
if (fighter.playerNumber === 2 && player2Lives <= 0) continue;
if (fighter.playerNumber === 3 && player3Lives <= 0) continue;
if (fighter.playerNumber === 4 && player4Lives <= 0) continue;
var distance = Math.sqrt(Math.pow(fighter.x - aiPlayer.x, 2) + Math.pow(fighter.y - aiPlayer.y, 2));
if (distance < closestDistance) {
closestDistance = distance;
closestPlayer = fighter;
}
}
return {
player: closestPlayer,
distance: closestDistance
};
}
// AI variables for player 2
var aiDecisionTimer = 0;
var aiAction = 'idle';
var aiTargetX = player2.x;
var aiCooldown = 0;
// AI variables for player 3
var ai3DecisionTimer = 0;
var ai3Action = 'idle';
var ai3TargetX = 700;
var ai3Cooldown = 0;
// AI variables for player 4
var ai4DecisionTimer = 0;
var ai4Action = 'idle';
var ai4TargetX = 1348;
var ai4Cooldown = 0;
game.update = function () {
// Start background music on first update
if (LK.ticks === 1) {
LK.playMusic('backgroundMusic');
}
// Handle continuous button presses for player 1 (only if alive)
if (player1Lives > 0) {
if (leftButtonPressed) {
player1.velocityX -= player1.moveSpeed * 0.5;
}
if (rightButtonPressed) {
player1.velocityX += player1.moveSpeed * 0.5;
}
if (jumpButtonPressed && player1.jumpsRemaining > 0) {
player1.velocityY = -player1.jumpPower;
player1.jumpsRemaining--;
player1.onGround = false;
LK.getSound('jump').play();
jumpButtonPressed = false; // Prevent continuous jumping
}
if (attackButtonPressed) {
player1.attack(player1.x, player1.y);
attackButtonPressed = false; // Prevent continuous attacking
}
}
// Spawn power-ups occasionally
powerupSpawnTimer++;
if (powerupSpawnTimer > 300) {
// Every 5 seconds
if (Math.random() < 0.3) {
spawnPowerUp();
}
powerupSpawnTimer = 0;
}
// AI for player 2 (only if alive)
if (player2Lives > 0) {
aiDecisionTimer++;
if (aiCooldown > 0) {
aiCooldown--;
}
// Make AI decisions every 30 frames (half second)
if (aiDecisionTimer > 30) {
aiDecisionTimer = 0;
// Find closest target
var closestTarget = findClosestTarget(player2);
var distanceToClosest = closestTarget.distance;
// Check for nearby powerups
var nearestPowerup = null;
var nearestPowerupDistance = Infinity;
for (var i = 0; i < powerups.length; i++) {
var powerup = powerups[i];
var distance = Math.sqrt(Math.pow(powerup.x - player2.x, 2) + Math.pow(powerup.y - player2.y, 2));
if (distance < nearestPowerupDistance) {
nearestPowerupDistance = distance;
nearestPowerup = powerup;
}
}
// AI decision making
if (aiCooldown === 0 && closestTarget.player) {
if (distanceToClosest < 200 && Math.random() < 0.8) {
// Attack if close to closest target
aiAction = 'attack';
aiCooldown = 60; // Attack cooldown
} else if (nearestPowerup && nearestPowerupDistance < 300 && Math.random() < 0.3) {
// Go for powerup if nearby (lower priority)
aiAction = 'moveToPowerup';
aiTargetX = nearestPowerup.x;
} else if (Math.random() < 0.9) {
// Chase closest target most of the time
aiAction = 'moveToPlayer';
aiTargetX = closestTarget.player.x;
} else {
// Random movement (rare)
aiAction = 'randomMove';
aiTargetX = 400 + Math.random() * 1248; // Random X within screen bounds
}
} else if (!closestTarget.player) {
aiAction = 'idle';
}
}
// Execute AI actions
if (aiAction === 'attack' && closestTarget.player) {
player2.attack(closestTarget.player.x, closestTarget.player.y);
aiAction = 'idle';
} else if (aiAction === 'moveToPowerup' || aiAction === 'moveToPlayer' || aiAction === 'randomMove') {
// Move towards target
var deltaX = aiTargetX - player2.x;
if (Math.abs(deltaX) > 50) {
player2.velocityX += deltaX * 0.2;
}
// Jump if target is above or if stuck
if (aiAction === 'moveToPlayer' && closestTarget && closestTarget.player && closestTarget.player.y < player2.y - 100 || Math.abs(player2.velocityX) < 1 && player2.onGround && Math.random() < 0.1) {
if (player2.jumpsRemaining > 0) {
player2.velocityY = -player2.jumpPower;
player2.jumpsRemaining--;
player2.onGround = false;
LK.getSound('jump').play();
}
}
// Reset action if close enough to target
if (Math.abs(deltaX) < 100) {
aiAction = 'idle';
}
}
// AI logic: double jump if can't reach player who is much higher
var heightDifference = player2.y - player1.y;
var horizontalDistance = Math.abs(player2.x - player1.x);
if (heightDifference > 200 && horizontalDistance < 400 && player2.jumpsRemaining > 0) {
// Check if AI is below player and perform double jump to reach them
player2.velocityY = -player2.jumpPower;
player2.jumpsRemaining--;
player2.onGround = false;
LK.getSound('jump').play();
}
// AI emergency jump if falling off platform
if (player2.y > 1500 && player2.jumpsRemaining > 0) {
player2.velocityY = -player2.jumpPower;
player2.jumpsRemaining--;
player2.onGround = false;
LK.getSound('jump').play();
}
}
// AI for player 3 (only if alive)
if (player3Lives > 0) {
ai3DecisionTimer++;
if (ai3Cooldown > 0) {
ai3Cooldown--;
}
if (ai3DecisionTimer > 45) {
ai3DecisionTimer = 0;
// Find closest target
var closestTarget_3 = findClosestTarget(player3);
var closestDistance_3 = closestTarget_3.distance;
if (ai3Cooldown === 0 && closestTarget_3.player) {
if (closestDistance_3 < 200 && Math.random() < 0.7) {
ai3Action = 'attack';
ai3Cooldown = 70;
} else if (Math.random() < 0.8) {
// Chase closest target most of the time
ai3Action = 'moveToPlayer';
ai3TargetX = closestTarget_3.player.x;
} else if (Math.random() < 0.3) {
ai3Action = 'randomMove';
ai3TargetX = 400 + Math.random() * 1248;
} else {
ai3Action = 'idle';
}
} else if (!closestTarget_3.player) {
ai3Action = 'idle';
}
}
if (ai3Action === 'attack' && closestTarget_3.player) {
player3.attack(closestTarget_3.player.x, closestTarget_3.player.y);
ai3Action = 'idle';
} else if (ai3Action === 'moveToPlayer' || ai3Action === 'randomMove') {
var deltaX_3 = ai3TargetX - player3.x;
if (Math.abs(deltaX_3) > 50) {
player3.velocityX += deltaX_3 * 0.18;
}
if (Math.abs(deltaX_3) > 200 && player3.onGround && Math.random() < 0.15) {
if (player3.jumpsRemaining > 0) {
player3.velocityY = -player3.jumpPower;
player3.jumpsRemaining--;
player3.onGround = false;
LK.getSound('jump').play();
}
}
if (Math.abs(deltaX_3) < 100) {
ai3Action = 'idle';
}
}
if (player3.y > 1500 && player3.jumpsRemaining > 0) {
player3.velocityY = -player3.jumpPower;
player3.jumpsRemaining--;
player3.onGround = false;
LK.getSound('jump').play();
}
}
// AI for player 4 (only if alive)
if (player4Lives > 0) {
ai4DecisionTimer++;
if (ai4Cooldown > 0) {
ai4Cooldown--;
}
if (ai4DecisionTimer > 35) {
ai4DecisionTimer = 0;
// Find closest target
var closestTarget_4 = findClosestTarget(player4);
var closestDistance_4 = closestTarget_4.distance;
if (ai4Cooldown === 0 && closestTarget_4.player) {
if (closestDistance_4 < 180 && Math.random() < 0.85) {
ai4Action = 'attack';
ai4Cooldown = 50;
} else if (Math.random() < 0.9) {
// Chase closest target aggressively
ai4Action = 'moveToPlayer';
ai4TargetX = closestTarget_4.player.x;
} else if (Math.random() < 0.4) {
ai4Action = 'randomMove';
ai4TargetX = 400 + Math.random() * 1248;
} else {
ai4Action = 'idle';
}
} else if (!closestTarget_4.player) {
ai4Action = 'idle';
}
}
if (ai4Action === 'attack' && closestTarget_4.player) {
player4.attack(closestTarget_4.player.x, closestTarget_4.player.y);
ai4Action = 'idle';
} else if (ai4Action === 'moveToPlayer' || ai4Action === 'randomMove') {
var deltaX_4 = ai4TargetX - player4.x;
if (Math.abs(deltaX_4) > 50) {
player4.velocityX += deltaX_4 * 0.22;
}
if (Math.abs(deltaX_4) > 150 && player4.onGround && Math.random() < 0.2) {
if (player4.jumpsRemaining > 0) {
player4.velocityY = -player4.jumpPower;
player4.jumpsRemaining--;
player4.onGround = false;
LK.getSound('jump').play();
}
}
if (Math.abs(deltaX_4) < 100) {
ai4Action = 'idle';
}
}
if (player4.y > 1500 && player4.jumpsRemaining > 0) {
player4.velocityY = -player4.jumpPower;
player4.jumpsRemaining--;
player4.onGround = false;
LK.getSound('jump').play();
}
}
// Update all fighters
for (var i = fighters.length - 1; i >= 0; i--) {
if (fighters[i] && !fighters[i].destroyed) {
fighters[i].update();
}
}
// Update all powerups
for (var i = powerups.length - 1; i >= 0; i--) {
if (powerups[i] && !powerups[i].destroyed) {
powerups[i].update();
}
}
// Update all smoke trails
for (var i = smokeTrails.length - 1; i >= 0; i--) {
if (smokeTrails[i] && !smokeTrails[i].destroyed) {
smokeTrails[i].update();
}
}
// Update camera zoom based on player positions
cameraUpdateTimer++;
if (cameraUpdateTimer > 10) {
// Update camera every 10 frames for smooth performance
cameraUpdateTimer = 0;
var newTargetZoom = calculateCameraZoom();
// Only update if zoom change is significant to avoid constant tweening
if (Math.abs(newTargetZoom - targetCameraZoom) > 0.05) {
targetCameraZoom = newTargetZoom;
// Stop any existing camera tween
tween.stop(game, {
scaleX: true,
scaleY: true
});
// Tween to new zoom level
tween(game, {
scaleX: targetCameraZoom,
scaleY: targetCameraZoom
}, {
duration: 1000,
easing: tween.easeInOut
});
}
}
};
Slime azul, pixelart. In-Game asset. 2d. High contrast. No shadows
Slime rojo, pixelart. In-Game asset. 2d. High contrast. No shadows
Slime amarillo, pixelart. In-Game asset. 2d. High contrast. No shadows
Slime verde, pixelart. In-Game asset. 2d. High contrast. No shadows
Montañas en atardecer pixelart. In-Game asset. 2d. High contrast. No shadows
Elimina la montaña
Burbuja azul, una , pixelart. In-Game asset. 2d. High contrast. No shadows
Esfera amarilla con la letra P , pixelart. In-Game asset. 2d. High contrast. No shadows
Flecha azul de Slime , pixelart. In-Game asset. 2d. High contrast. No shadows
Cámbiale el color a rojo , pixelart
Cámbiale el color a amarillo, pixelart
Cámbiale el color a verde, pixelart
Apaga las luces de la ventanas
Luciérnaga, pixelart. In-Game asset. 2d. High contrast. No shadows
Cabeza de girasol, pixelart. In-Game asset. 2d. High contrast. No shadows
Número 1 azul , pixelart. In-Game asset. 2d. High contrast. No shadows
Número 0 azul, pixelart. In-Game asset. 2d. High contrast. No shadows
Número 3 azul, pixelart. In-Game asset. 2d. High contrast. No shadows
Retro, pixelart