/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var AI = Container.expand(function () {
var self = Container.call(this);
var aiGraphics = self.attachAsset('Caminando1', {
anchorX: 0.5,
anchorY: 1.0,
width: 60,
height: 60
});
self.target = null;
self.lastX = 0;
self.lastY = 0;
self.stuckCounter = 0;
self.jumpCooldown = 0;
self.scanDistance = 800;
self.pathfindingCooldown = 0;
self.velocityY = 0;
self.velocityX = 0;
self.isGrounded = false;
self.jumpPower = -20;
self.gravity = 1.2;
self.speed = 4; // Slower than player
self.maxFallSpeed = 20;
self.jumpCount = 0;
self.maxJumps = 2;
// AI health and regeneration system
self.health = 100;
self.maxHealth = 100;
self.regenerationRate = 2; // Health points per second
self.damageCooldown = 0;
self.isRegenerating = false;
self.regenerationDelay = 60; // Frames to wait before starting regeneration after damage
// AI decision making
self.makeDecision = function () {
if (!aiGoalAsset) return;
// Update cooldowns
if (self.jumpCooldown > 0) self.jumpCooldown--;
if (self.pathfindingCooldown > 0) self.pathfindingCooldown--;
// Check if stuck
var aiMovement = Math.abs(self.x - self.lastX) + Math.abs(self.y - self.lastY);
if (aiMovement < 2) {
self.stuckCounter++;
} else {
self.stuckCounter = 0;
}
self.lastX = self.x;
self.lastY = self.y;
// Decision state system - AI can choose between different actions
var decisionState = self.evaluateSituation();
switch (decisionState) {
case 'JUMP':
if (self.isGrounded && self.jumpCooldown <= 0) {
self.jump();
self.jumpCooldown = 20;
}
break;
case 'MOVE_LEFT':
self.moveLeft();
break;
case 'MOVE_RIGHT':
self.moveRight();
break;
case 'STAY_STILL':
// AI decides to stay still - do nothing
self.velocityX *= 0.5; // Slow down gradually
break;
default:
// Default fallback behavior
self.executeDefaultBehavior();
break;
}
};
// Evaluate current situation and decide what action to take
self.evaluateSituation = function () {
if (!aiGoalAsset) return 'MOVE_RIGHT'; // Default if no goal
var goalDistance = aiGoalAsset.x - self.x;
var goalVerticalDistance = aiGoalAsset.y - self.y;
var obstacleAhead = self.checkForObstaclesAhead();
var distanceToGoal = Math.abs(goalDistance);
var goalReachable = distanceToGoal <= 1000; // Check if goal is within reachable distance
// Priority 1: If stuck for too long, jump
if (self.stuckCounter > 30 && self.isGrounded && self.jumpCooldown <= 0) {
return 'JUMP';
}
// Priority 2: Jump over obstacles when moving
if (obstacleAhead && self.isGrounded && self.jumpCooldown <= 0 && Math.abs(self.velocityX) > 1) {
return 'JUMP';
}
// Priority 3: Jump if goal is significantly above and we're close horizontally
if (Math.abs(goalDistance) < 200 && goalVerticalDistance < -100 && self.isGrounded && self.jumpCooldown <= 0) {
return 'JUMP';
}
// Priority 4: Stay still if very close to goal (within 100 units)
if (Math.abs(goalDistance) < 100 && Math.abs(goalVerticalDistance) < 50) {
return 'STAY_STILL';
}
// Priority 5: Stay still if on unstable ground or dangerous situation
if (!self.isGrounded && self.velocityY > 10) {
return 'STAY_STILL'; // Don't make sudden moves while falling fast
}
// Priority 6: Move towards goal only if it's reachable
if (goalReachable) {
if (goalDistance > 50) {
return 'MOVE_RIGHT';
} else if (goalDistance < -50) {
return 'MOVE_LEFT';
}
} else {
// Goal is not reachable due to distance restriction - move towards it to get closer
if (goalDistance > 0) {
return 'MOVE_RIGHT';
} else {
return 'MOVE_LEFT';
}
}
// Priority 7: Stay still if goal is close horizontally and reachable
if (goalReachable && Math.abs(goalDistance) < 100) {
return 'STAY_STILL';
}
return 'MOVE_RIGHT'; // Default movement
};
// Execute default behavior when no specific decision is made
self.executeDefaultBehavior = function () {
// Find target platform to move towards goal
if (self.pathfindingCooldown <= 0) {
self.target = self.findBestPlatform();
self.pathfindingCooldown = 10;
}
// Navigate towards target
if (self.target) {
self.navigateToTarget();
} else {
// Move towards goal as fallback
if (aiGoalAsset) {
var goalDistance = aiGoalAsset.x - self.x;
var goalVerticalDistance = aiGoalAsset.y - self.y;
// Check for obstacles ahead when moving towards goal
var obstacleAhead = self.checkForObstaclesAhead();
if (obstacleAhead && self.isGrounded && self.jumpCooldown <= 0) {
self.jump();
self.jumpCooldown = 20;
}
if (goalDistance > 50) {
self.moveRight();
} else if (goalDistance < -50) {
self.moveLeft();
}
} else {
// No goal yet, move right by default
// Check for obstacles when moving right
var obstacleAhead = self.checkForObstaclesAhead();
if (obstacleAhead && self.isGrounded && self.jumpCooldown <= 0) {
self.jump();
self.jumpCooldown = 20;
}
self.moveRight();
}
}
};
self.findBestPlatform = function () {
var bestPlatform = null;
var bestScore = -999999;
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
// Skip platforms too far away or too close
if (Math.abs(platform.x - self.x) > 400 || Math.abs(platform.x - self.x) < 20) continue;
// Calculate score based on progress towards goal and reachability
var distanceToGoal = Math.abs(platform.x - aiGoalAsset.x);
var progressScore = (platform.x - self.x) * 2; // Favor forward movement
var goalProximityScore = -distanceToGoal * 0.5; // Favor platforms closer to goal
var heightPenalty = Math.abs(platform.y - self.y) * 0.1;
var score = progressScore + goalProximityScore - heightPenalty;
// Check if platform is reachable
if (self.isPlatformReachableByAI(platform)) {
score += 1000; // Bonus for reachable platforms
}
// Check for obstacles on platform
if (self.hasObstacleOnPlatform(platform)) {
score -= 500; // Penalty for obstacles
}
if (score > bestScore) {
bestScore = score;
bestPlatform = platform;
}
}
return bestPlatform;
};
self.isPlatformReachableByAI = function (platform) {
var horizontalDistance = Math.abs(platform.x - self.x);
var verticalDistance = platform.y - self.y;
// Simple reachability check
if (horizontalDistance > 350) return false; // Too far horizontally
if (verticalDistance > 400) return false; // Too high
if (verticalDistance < -600) return false; // Too far below
return true;
};
self.hasObstacleOnPlatform = function (platform) {
for (var i = 0; i < obstacles.length; i++) {
var obstacle = obstacles[i];
var distToPlatform = Math.abs(obstacle.x - platform.x);
var heightDiff = Math.abs(obstacle.y - platform.y);
if (distToPlatform < platform.width / 2 + 50 && heightDiff < 100) {
return true;
}
}
return false;
};
self.navigateToTarget = function () {
if (!self.target) return;
var horizontalDistance = self.target.x - self.x;
var verticalDistance = self.target.y - self.y;
// Check if we need to jump over obstacles
var needsJump = self.checkForObstaclesAhead();
// Jump if target is above us or if there's an obstacle (be more aggressive)
if ((verticalDistance < -50 || needsJump) && self.isGrounded && self.jumpCooldown <= 0) {
self.jump();
self.jumpCooldown = 20; // Reduced cooldown for more responsive jumping
}
// Also jump if we're close to the target horizontally but need to reach it vertically
if (Math.abs(horizontalDistance) < 100 && verticalDistance < -50 && self.isGrounded && self.jumpCooldown <= 0) {
self.jump();
self.jumpCooldown = 20;
}
// Move horizontally towards target
if (horizontalDistance > 30) {
self.moveRight();
} else if (horizontalDistance < -30) {
self.moveLeft();
}
// Check if we've reached the target platform
if (Math.abs(horizontalDistance) < 50 && Math.abs(verticalDistance) < 100) {
self.target = null; // Find new target
}
};
self.checkForObstaclesAhead = function () {
// Look ahead based on current movement speed and direction
var lookAheadDistance = Math.abs(self.velocityX) * 15 + 100; // Scale with speed
var direction = self.velocityX >= 0 ? 1 : -1; // Moving right or left
for (var i = 0; i < obstacles.length; i++) {
var obstacle = obstacles[i];
var obstacleDistance = (obstacle.x - self.x) * direction;
var heightDiff = Math.abs(obstacle.y - self.y);
// Check if obstacle is ahead in movement direction and at similar height
if (obstacleDistance > 0 && obstacleDistance < lookAheadDistance && heightDiff < 120) {
return true;
}
}
return false;
};
self.moveLeft = function () {
self.velocityX = -self.speed;
};
self.moveRight = function () {
self.velocityX = self.speed;
};
self.jump = function () {
if (self.jumpCount < self.maxJumps) {
self.velocityY = self.jumpPower;
self.jumpCount++;
if (self.isGrounded) {
self.isGrounded = false;
}
}
};
// AI takes damage and handles regeneration
self.takeDamage = function (amount) {
amount = amount || 20; // Default damage amount
self.health -= amount;
if (self.health < 0) self.health = 0;
self.damageCooldown = self.regenerationDelay;
self.isRegenerating = false;
// Flash AI red when taking damage
LK.effects.flashObject(self, 0xff0000, 500);
// If health reaches 0, reset AI position and health
if (self.health <= 0) {
self.health = self.maxHealth;
// Find platform closest to Final1 asset and respawn there
if (goalAsset && platforms.length > 0) {
var closestPlatform = null;
var shortestDistance = 999999;
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
var distance = Math.abs(platform.x - goalAsset.x);
if (distance < shortestDistance) {
shortestDistance = distance;
closestPlatform = platform;
}
}
// Position AI on the closest platform to Final1
if (closestPlatform) {
self.x = closestPlatform.x;
self.y = closestPlatform.y - 20; // Position slightly above platform
} else {
// Fallback to player position if no platform found
self.x = player.x;
self.y = player.y;
}
} else {
// Fallback to player position if no Final1 asset exists
self.x = player.x;
self.y = player.y;
}
self.velocityX = 0;
self.velocityY = 0;
LK.effects.flashObject(self, 0x00ff00, 1000); // Green flash for regeneration
}
};
self.update = function () {
// Reset jump count when grounded
if (self.isGrounded) {
self.jumpCount = 0;
}
// Apply gravity
if (!self.isGrounded) {
self.velocityY += self.gravity;
if (self.velocityY > self.maxFallSpeed) {
self.velocityY = self.maxFallSpeed;
}
}
// Check for obstacles before moving and jump if needed
if (self.isGrounded && self.jumpCooldown <= 0 && Math.abs(self.velocityX) > 2) {
var obstacleAhead = self.checkForObstaclesAhead();
if (obstacleAhead) {
self.jump();
self.jumpCooldown = 25;
}
}
// Apply horizontal movement with friction
self.x += self.velocityX;
self.velocityX *= 0.85;
// Apply vertical movement
self.y += self.velocityY;
// Check if AI needs to jump to avoid falling (gap detection)
if (self.isGrounded && self.jumpCooldown <= 0) {
// Look ahead to see if there's a platform to land on
var foundPlatformAhead = false;
var lookAheadX = self.x + (self.velocityX > 0 ? 150 : -150);
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 heightDiff = Math.abs(platform.y - self.y);
// Check if there's a platform ahead at similar height
if (lookAheadX >= platformLeft && lookAheadX <= platformRight && heightDiff < 200) {
foundPlatformAhead = true;
break;
}
}
// If no platform found ahead and we're moving, jump to try to reach something
if (!foundPlatformAhead && Math.abs(self.velocityX) > 2) {
self.jump();
self.jumpCooldown = 40;
}
}
// Handle damage cooldown and regeneration
if (self.damageCooldown > 0) {
self.damageCooldown--;
} else if (self.health < self.maxHealth) {
// Start regenerating when cooldown is over
if (!self.isRegenerating) {
self.isRegenerating = true;
}
// Regenerate health (60 FPS, so divide by 60 for per-second rate)
self.health += self.regenerationRate / 60;
if (self.health > self.maxHealth) {
self.health = self.maxHealth;
self.isRegenerating = false;
}
}
// Make AI decisions
self.makeDecision();
};
return self;
});
var AI2 = Container.expand(function () {
var self = Container.call(this);
var aiGraphics = self.attachAsset('Caminando2', {
anchorX: 0.5,
anchorY: 1.0,
width: 60,
height: 60
});
self.target = null;
self.lastX = 0;
self.lastY = 0;
self.stuckCounter = 0;
self.jumpCooldown = 0;
self.scanDistance = 800;
self.pathfindingCooldown = 0;
self.velocityY = 0;
self.velocityX = 0;
self.isGrounded = false;
self.jumpPower = -18;
self.gravity = 1.2;
self.speed = 6; // Slower than player
self.maxFallSpeed = 20;
self.jumpCount = 0;
self.maxJumps = 2;
// AI health and regeneration system
self.health = 100;
self.maxHealth = 100;
self.regenerationRate = 2; // Health points per second
self.damageCooldown = 0;
self.isRegenerating = false;
self.regenerationDelay = 60; // Frames to wait before starting regeneration after damage
// AI decision making
self.makeDecision = function () {
if (!aiGoalAsset) return;
// Update cooldowns
if (self.jumpCooldown > 0) self.jumpCooldown--;
if (self.pathfindingCooldown > 0) self.pathfindingCooldown--;
// Check if stuck
var aiMovement = Math.abs(self.x - self.lastX) + Math.abs(self.y - self.lastY);
if (aiMovement < 2) {
self.stuckCounter++;
} else {
self.stuckCounter = 0;
}
self.lastX = self.x;
self.lastY = self.y;
// Decision state system - AI can choose between different actions
var decisionState = self.evaluateSituation();
switch (decisionState) {
case 'JUMP':
if (self.isGrounded && self.jumpCooldown <= 0) {
self.jump();
self.jumpCooldown = 20;
}
break;
case 'MOVE_LEFT':
self.moveLeft();
break;
case 'MOVE_RIGHT':
self.moveRight();
break;
case 'STAY_STILL':
// AI decides to stay still - do nothing
self.velocityX *= 0.5; // Slow down gradually
break;
default:
// Default fallback behavior
self.executeDefaultBehavior();
break;
}
};
// Evaluate current situation and decide what action to take
self.evaluateSituation = function () {
if (!aiGoalAsset) return 'MOVE_RIGHT'; // Default if no goal
var goalDistance = aiGoalAsset.x - self.x;
var goalVerticalDistance = aiGoalAsset.y - self.y;
var obstacleAhead = self.checkForObstaclesAhead();
var distanceToGoal = Math.abs(goalDistance);
var goalReachable = distanceToGoal <= 1000; // Check if goal is within reachable distance
// Priority 1: If stuck for too long, jump
if (self.stuckCounter > 30 && self.isGrounded && self.jumpCooldown <= 0) {
return 'JUMP';
}
// Priority 2: Jump over obstacles when moving
if (obstacleAhead && self.isGrounded && self.jumpCooldown <= 0 && Math.abs(self.velocityX) > 1) {
return 'JUMP';
}
// Priority 3: Jump if goal is significantly above and we're close horizontally
if (Math.abs(goalDistance) < 200 && goalVerticalDistance < -100 && self.isGrounded && self.jumpCooldown <= 0) {
return 'JUMP';
}
// Priority 4: Stay still if very close to goal (within 100 units)
if (Math.abs(goalDistance) < 100 && Math.abs(goalVerticalDistance) < 50) {
return 'STAY_STILL';
}
// Priority 5: Stay still if on unstable ground or dangerous situation
if (!self.isGrounded && self.velocityY > 10) {
return 'STAY_STILL'; // Don't make sudden moves while falling fast
}
// Priority 6: Move towards goal only if it's reachable
if (goalReachable) {
if (goalDistance > 50) {
return 'MOVE_RIGHT';
} else if (goalDistance < -50) {
return 'MOVE_LEFT';
}
} else {
// Goal is not reachable due to distance restriction - move towards it to get closer
if (goalDistance > 0) {
return 'MOVE_RIGHT';
} else {
return 'MOVE_LEFT';
}
}
// Priority 7: Stay still if goal is close horizontally and reachable
if (goalReachable && Math.abs(goalDistance) < 100) {
return 'STAY_STILL';
}
return 'MOVE_RIGHT'; // Default movement
};
// Execute default behavior when no specific decision is made
self.executeDefaultBehavior = function () {
// Find target platform to move towards goal
if (self.pathfindingCooldown <= 0) {
self.target = self.findBestPlatform();
self.pathfindingCooldown = 10;
}
// Navigate towards target
if (self.target) {
self.navigateToTarget();
} else {
// Move towards goal as fallback
if (aiGoalAsset) {
var goalDistance = aiGoalAsset.x - self.x;
var goalVerticalDistance = aiGoalAsset.y - self.y;
// Check for obstacles ahead when moving towards goal
var obstacleAhead = self.checkForObstaclesAhead();
if (obstacleAhead && self.isGrounded && self.jumpCooldown <= 0) {
self.jump();
self.jumpCooldown = 20;
}
if (goalDistance > 50) {
self.moveRight();
} else if (goalDistance < -50) {
self.moveLeft();
}
} else {
// No goal yet, move right by default
// Check for obstacles when moving right
var obstacleAhead = self.checkForObstaclesAhead();
if (obstacleAhead && self.isGrounded && self.jumpCooldown <= 0) {
self.jump();
self.jumpCooldown = 20;
}
self.moveRight();
}
}
};
self.findBestPlatform = function () {
var bestPlatform = null;
var bestScore = -999999;
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
// Skip platforms too far away or too close
if (Math.abs(platform.x - self.x) > 400 || Math.abs(platform.x - self.x) < 20) continue;
// Calculate score based on progress towards goal and reachability
var distanceToGoal = Math.abs(platform.x - aiGoalAsset.x);
var progressScore = (platform.x - self.x) * 2; // Favor forward movement
var goalProximityScore = -distanceToGoal * 0.5; // Favor platforms closer to goal
var heightPenalty = Math.abs(platform.y - self.y) * 0.1;
var score = progressScore + goalProximityScore - heightPenalty;
// Check if platform is reachable
if (self.isPlatformReachableByAI(platform)) {
score += 1000; // Bonus for reachable platforms
}
// Check for obstacles on platform
if (self.hasObstacleOnPlatform(platform)) {
score -= 500; // Penalty for obstacles
}
if (score > bestScore) {
bestScore = score;
bestPlatform = platform;
}
}
return bestPlatform;
};
self.isPlatformReachableByAI = function (platform) {
var horizontalDistance = Math.abs(platform.x - self.x);
var verticalDistance = platform.y - self.y;
// Simple reachability check
if (horizontalDistance > 350) return false; // Too far horizontally
if (verticalDistance > 400) return false; // Too high
if (verticalDistance < -600) return false; // Too far below
return true;
};
self.hasObstacleOnPlatform = function (platform) {
for (var i = 0; i < obstacles.length; i++) {
var obstacle = obstacles[i];
var distToPlatform = Math.abs(obstacle.x - platform.x);
var heightDiff = Math.abs(obstacle.y - platform.y);
if (distToPlatform < platform.width / 2 + 50 && heightDiff < 100) {
return true;
}
}
return false;
};
self.navigateToTarget = function () {
if (!self.target) return;
var horizontalDistance = self.target.x - self.x;
var verticalDistance = self.target.y - self.y;
// Check if we need to jump over obstacles
var needsJump = self.checkForObstaclesAhead();
// Jump if target is above us or if there's an obstacle (be more aggressive)
if ((verticalDistance < -50 || needsJump) && self.isGrounded && self.jumpCooldown <= 0) {
self.jump();
self.jumpCooldown = 20; // Reduced cooldown for more responsive jumping
}
// Also jump if we're close to the target horizontally but need to reach it vertically
if (Math.abs(horizontalDistance) < 100 && verticalDistance < -50 && self.isGrounded && self.jumpCooldown <= 0) {
self.jump();
self.jumpCooldown = 20;
}
// Move horizontally towards target
if (horizontalDistance > 30) {
self.moveRight();
} else if (horizontalDistance < -30) {
self.moveLeft();
}
// Check if we've reached the target platform
if (Math.abs(horizontalDistance) < 50 && Math.abs(verticalDistance) < 100) {
self.target = null; // Find new target
}
};
self.checkForObstaclesAhead = function () {
// Look ahead based on current movement speed and direction
var lookAheadDistance = Math.abs(self.velocityX) * 15 + 100; // Scale with speed
var direction = self.velocityX >= 0 ? 1 : -1; // Moving right or left
for (var i = 0; i < obstacles.length; i++) {
var obstacle = obstacles[i];
var obstacleDistance = (obstacle.x - self.x) * direction;
var heightDiff = Math.abs(obstacle.y - self.y);
// Check if obstacle is ahead in movement direction and at similar height
if (obstacleDistance > 0 && obstacleDistance < lookAheadDistance && heightDiff < 120) {
return true;
}
}
return false;
};
self.moveLeft = function () {
self.velocityX = -self.speed;
};
self.moveRight = function () {
self.velocityX = self.speed;
};
self.jump = function () {
if (self.jumpCount < self.maxJumps) {
self.velocityY = self.jumpPower;
self.jumpCount++;
if (self.isGrounded) {
self.isGrounded = false;
}
}
};
// AI takes damage and handles regeneration
self.takeDamage = function (amount) {
amount = amount || 20; // Default damage amount
self.health -= amount;
if (self.health < 0) self.health = 0;
self.damageCooldown = self.regenerationDelay;
self.isRegenerating = false;
// Flash AI red when taking damage
LK.effects.flashObject(self, 0xff0000, 500);
// If health reaches 0, reset AI position and health
if (self.health <= 0) {
self.health = self.maxHealth;
// Find platform closest to Final1 asset and respawn there
if (goalAsset && platforms.length > 0) {
var closestPlatform = null;
var shortestDistance = 999999;
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
var distance = Math.abs(platform.x - goalAsset.x);
if (distance < shortestDistance) {
shortestDistance = distance;
closestPlatform = platform;
}
}
// Position AI on the closest platform to Final1
if (closestPlatform) {
self.x = closestPlatform.x;
self.y = closestPlatform.y - 20; // Position slightly above platform
} else {
// Fallback to player position if no platform found
self.x = player.x;
self.y = player.y;
}
} else {
// Fallback to player position if no Final1 asset exists
self.x = player.x;
self.y = player.y;
}
self.velocityX = 0;
self.velocityY = 0;
LK.effects.flashObject(self, 0x00ff00, 1000); // Green flash for regeneration
}
};
self.update = function () {
// Reset jump count when grounded
if (self.isGrounded) {
self.jumpCount = 0;
}
// Apply gravity
if (!self.isGrounded) {
self.velocityY += self.gravity;
if (self.velocityY > self.maxFallSpeed) {
self.velocityY = self.maxFallSpeed;
}
}
// Check for obstacles before moving and jump if needed
if (self.isGrounded && self.jumpCooldown <= 0 && Math.abs(self.velocityX) > 2) {
var obstacleAhead = self.checkForObstaclesAhead();
if (obstacleAhead) {
self.jump();
self.jumpCooldown = 25;
}
}
// Apply horizontal movement with friction
self.x += self.velocityX;
self.velocityX *= 0.85;
// Apply vertical movement
self.y += self.velocityY;
// Check if AI needs to jump to avoid falling (gap detection)
if (self.isGrounded && self.jumpCooldown <= 0) {
// Look ahead to see if there's a platform to land on
var foundPlatformAhead = false;
var lookAheadX = self.x + (self.velocityX > 0 ? 150 : -150);
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 heightDiff = Math.abs(platform.y - self.y);
// Check if there's a platform ahead at similar height
if (lookAheadX >= platformLeft && lookAheadX <= platformRight && heightDiff < 200) {
foundPlatformAhead = true;
break;
}
}
// If no platform found ahead and we're moving, jump to try to reach something
if (!foundPlatformAhead && Math.abs(self.velocityX) > 2) {
self.jump();
self.jumpCooldown = 40;
}
}
// Handle damage cooldown and regeneration
if (self.damageCooldown > 0) {
self.damageCooldown--;
} else if (self.health < self.maxHealth) {
// Start regenerating when cooldown is over
if (!self.isRegenerating) {
self.isRegenerating = true;
}
// Regenerate health (60 FPS, so divide by 60 for per-second rate)
self.health += self.regenerationRate / 60;
if (self.health > self.maxHealth) {
self.health = self.maxHealth;
self.isRegenerating = false;
}
}
// Make AI decisions
self.makeDecision();
};
return self;
});
var Obstacle = Container.expand(function () {
var self = Container.call(this);
var obstacleGraphics = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 1.0
});
self.width = 30;
self.height = 60;
return self;
});
var Platform = Container.expand(function (width) {
var self = Container.call(this);
width = width || 300;
var platformGraphics = self.attachAsset('platform', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: width / 300
});
self.width = width;
self.height = 40;
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var assetToUse = selectedPlayerAsset || 'player';
var playerGraphics = self.attachAsset(assetToUse, {
anchorX: 0.5,
anchorY: 1.0
});
self.velocityY = 0;
self.velocityX = 0;
self.isGrounded = false;
self.jumpPower = -25;
self.gravity = 1.2;
self.speed = 8;
self.maxFallSpeed = 20;
self.isMoving = false;
self.idleAnimationTime = 0;
self.movementAnimationTime = 0;
self.jumpCount = 0;
self.maxJumps = 2;
self.moveLeft = function () {
self.velocityX = -self.speed;
};
self.moveRight = function () {
self.velocityX = self.speed;
};
self.jump = function () {
if (self.jumpCount < self.maxJumps) {
self.velocityY = self.jumpPower;
self.jumpCount++;
if (self.isGrounded) {
self.isGrounded = false;
}
LK.getSound('jump').play();
}
};
self.update = function () {
// Reset jump count when grounded (do this first)
if (self.isGrounded) {
self.jumpCount = 0;
}
// Apply gravity
if (!self.isGrounded) {
self.velocityY += self.gravity;
if (self.velocityY > self.maxFallSpeed) {
self.velocityY = self.maxFallSpeed;
}
}
// Apply horizontal movement with friction
self.x += self.velocityX;
self.velocityX *= 0.85;
// Apply vertical movement
self.y += self.velocityY;
// Check if player is moving horizontally
self.isMoving = Math.abs(self.velocityX) > 0.5;
// Update animations
if (self.isMoving) {
// Movement animation - slight bouncing effect
self.movementAnimationTime += 0.3;
var bounceOffset = Math.sin(self.movementAnimationTime) * 3;
playerGraphics.y = bounceOffset;
playerGraphics.scaleX = 1 + Math.sin(self.movementAnimationTime * 2) * 0.05;
playerGraphics.scaleY = 1 + Math.cos(self.movementAnimationTime * 2) * 0.05;
// Reset idle animation
self.idleAnimationTime = 0;
} else {
// Idle animation - gentle floating effect
self.idleAnimationTime += 0.1;
var floatOffset = Math.sin(self.idleAnimationTime) * 2;
playerGraphics.y = floatOffset;
playerGraphics.scaleX = 1 + Math.sin(self.idleAnimationTime * 0.5) * 0.02;
playerGraphics.scaleY = 1 + Math.cos(self.idleAnimationTime * 0.5) * 0.02;
// Reset movement animation
self.movementAnimationTime = 0;
}
// No horizontal boundaries - allow infinite movement for side-scrolling
// Check if fallen off screen
if (self.y > 2800) {
LK.showGameOver();
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87CEEB
});
/****
* Game Code
****/
// Set background to Cielo1 asset
var backgroundAsset = LK.getAsset('Cielo1', {
width: 2048,
height: 2732,
anchorX: 0,
anchorY: 0
});
backgroundAsset.x = 0;
backgroundAsset.y = 0;
game.addChildAt(backgroundAsset, 0); // Add as first child so it's behind everything
var gameState = 'start'; // 'start', 'playing', 'paused'
var startButton = null;
var player;
var platforms = [];
var obstacles = [];
var cameraOffset = 0;
var distanceScore = 0;
var lastPlatformX = 0;
var leftButtonPressed = false;
var rightButtonPressed = false;
var jumpButtonPressed = false;
var gameAttempts = 0;
var levelSeed = 0;
var goalReached = false;
var goalAsset = null;
var aiPlayer = null;
var aiCameraOffset = 0;
var aiDistanceScore = 0;
var aiGoalAsset = null;
var aiGoalReached = false;
var aiLastPlatformX = 0;
var aiPlayer2 = null;
var playerLives = 5;
var maxLives = 5;
var hearts = [];
var playerInvulnerable = false;
var invulnerabilityDuration = 120; // 2 seconds at 60 FPS
var invulnerabilityTimer = 0;
var infiniteMode = false;
var infiniteModeButton = null;
// Create score display
var scoreText = new Text2('Distance: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreText);
// Create control instruction text
var instructionText = new Text2('Use Buttons to Move and Jump', {
size: 40,
fill: 0xFFFFFF
});
instructionText.anchor.set(0.5, 1);
LK.gui.bottom.addChild(instructionText);
// Create start screen elements
var titleText = new Text2('Pixel Race', {
size: 120,
fill: 0xFFD700
});
titleText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(titleText);
// Create player selection buttons on the left side
var playerSelectionButtons = [];
var selectedPlayerAsset = 'player'; // Default player asset
// Button 1 - Player2 asset
var button1 = LK.getAsset('Player2', {
width: 120,
height: 120,
anchorX: 0.5,
anchorY: 0.5
});
var button1Text = new Text2('1', {
size: 50,
fill: 0xFFFFFF
});
button1Text.anchor.set(0.5, 0.5);
button1.addChild(button1Text);
button1.x = -400;
button1.y = -200;
button1.alpha = 0.8;
LK.gui.center.addChild(button1);
playerSelectionButtons.push(button1);
button1.down = function () {
selectedPlayerAsset = 'Player2';
updateButtonSelection(0);
};
// Button 2 - Player3 asset
var button2 = LK.getAsset('Player3', {
width: 120,
height: 120,
anchorX: 0.5,
anchorY: 0.5
});
var button2Text = new Text2('2', {
size: 50,
fill: 0xFFFFFF
});
button2Text.anchor.set(0.5, 0.5);
button2.addChild(button2Text);
button2.x = -400;
button2.y = -100;
button2.alpha = 0.8;
LK.gui.center.addChild(button2);
playerSelectionButtons.push(button2);
button2.down = function () {
selectedPlayerAsset = 'Player3';
updateButtonSelection(1);
};
// Button 3 - Player4 asset
var button3 = LK.getAsset('Player4', {
width: 120,
height: 120,
anchorX: 0.5,
anchorY: 0.5
});
var button3Text = new Text2('3', {
size: 50,
fill: 0xFFFFFF
});
button3Text.anchor.set(0.5, 0.5);
button3.addChild(button3Text);
button3.x = -400;
button3.y = 0;
button3.alpha = 0.8;
LK.gui.center.addChild(button3);
playerSelectionButtons.push(button3);
button3.down = function () {
selectedPlayerAsset = 'Player4';
updateButtonSelection(2);
};
// Button 4 - (no asset specified, using default player)
var button4 = LK.getAsset('player', {
width: 120,
height: 120,
anchorX: 0.5,
anchorY: 0.5
});
var button4Text = new Text2('4', {
size: 50,
fill: 0xFFFFFF
});
button4Text.anchor.set(0.5, 0.5);
button4.addChild(button4Text);
button4.x = -400;
button4.y = 100;
button4.alpha = 0.8;
LK.gui.center.addChild(button4);
playerSelectionButtons.push(button4);
button4.down = function () {
selectedPlayerAsset = 'player';
updateButtonSelection(3);
};
// Button 5 - Player5 asset
var button5 = LK.getAsset('Player5', {
width: 120,
height: 120,
anchorX: 0.5,
anchorY: 0.5
});
var button5Text = new Text2('5', {
size: 50,
fill: 0xFFFFFF
});
button5Text.anchor.set(0.5, 0.5);
button5.addChild(button5Text);
button5.x = -400;
button5.y = 200;
button5.alpha = 0.8;
LK.gui.center.addChild(button5);
playerSelectionButtons.push(button5);
button5.down = function () {
selectedPlayerAsset = 'Player5';
updateButtonSelection(4);
};
// Function to update button selection visual feedback
function updateButtonSelection(selectedIndex) {
for (var i = 0; i < playerSelectionButtons.length; i++) {
if (i === selectedIndex) {
playerSelectionButtons[i].alpha = 1.0;
playerSelectionButtons[i].scaleX = 1.2;
playerSelectionButtons[i].scaleY = 1.2;
} else {
playerSelectionButtons[i].alpha = 0.8;
playerSelectionButtons[i].scaleX = 1.0;
playerSelectionButtons[i].scaleY = 1.0;
}
}
}
// Set default selection
updateButtonSelection(4); // Default to button 5 (player asset)
var startButtonText = new Text2('START GAME', {
size: 80,
fill: 0xFFFFFF
});
startButtonText.anchor.set(0.5, 0.5);
// Create start button using Star1 asset as background
startButton = LK.getAsset('Star1', {
width: 300,
height: 120,
anchorX: 0.5,
anchorY: 0.5
});
startButton.tint = 0x32cd32;
startButton.alpha = 0.8;
LK.gui.center.addChild(startButton);
startButton.addChild(startButtonText);
startButton.y = 100;
// Hide game UI elements initially
// Create infinite mode button
var infiniteModeButtonText = new Text2('INFINITE MODE', {
size: 60,
fill: 0xFFFFFF
});
infiniteModeButtonText.anchor.set(0.5, 0.5);
infiniteModeButton = LK.getAsset('Star1', {
width: 280,
height: 100,
anchorX: 0.5,
anchorY: 0.5
});
infiniteModeButton.tint = 0xFF6600;
infiniteModeButton.alpha = 0.8;
LK.gui.center.addChild(infiniteModeButton);
infiniteModeButton.addChild(infiniteModeButtonText);
infiniteModeButton.y = 250;
scoreText.visible = false;
instructionText.visible = false;
// Create heart life display
function createHeartDisplay() {
// Clear existing hearts
for (var i = 0; i < hearts.length; i++) {
hearts[i].destroy();
}
hearts = [];
// Create new hearts based on current lives
for (var i = 0; i < maxLives; i++) {
var heart = LK.getAsset('corason1', {
width: 60,
height: 60,
anchorX: 0.5,
anchorY: 0.5
});
heart.x = 150 + i * 70; // Position hearts horizontally
heart.y = 80; // Position at top
heart.alpha = i < playerLives ? 1.0 : 0.3; // Full opacity for active lives, faded for lost lives
hearts.push(heart);
LK.gui.topLeft.addChild(heart);
}
}
// Initialize heart display
createHeartDisplay();
// Function to initialize the game
function initializeGame() {
// Initialize player
player = new Player();
player.x = 300;
player.y = 2000;
game.addChild(player);
// Initialize AI
aiPlayer = new AI();
aiPlayer.x = 200;
aiPlayer.y = 2000;
game.addChild(aiPlayer);
// Initialize AI2
aiPlayer2 = new AI2();
aiPlayer2.x = 150;
aiPlayer2.y = 2000;
game.addChild(aiPlayer2);
}
// Start button event handlers
startButton.down = function () {
startButton.alpha = 1.0;
startButton.scaleX = 0.95;
startButton.scaleY = 0.95;
};
startButton.up = function () {
startButton.alpha = 0.8;
startButton.scaleX = 1.0;
startButton.scaleY = 1.0;
// Start the game
gameState = 'playing';
// Hide start screen elements
titleText.visible = false;
startButton.visible = false;
infiniteModeButton.visible = false;
// Hide player selection buttons
for (var i = 0; i < playerSelectionButtons.length; i++) {
playerSelectionButtons[i].visible = false;
}
// Show game UI elements
scoreText.visible = true;
instructionText.visible = true;
// Initialize game objects
initializeGame();
};
// Infinite mode button event handlers
infiniteModeButton.down = function () {
infiniteModeButton.alpha = 1.0;
infiniteModeButton.scaleX = 0.95;
infiniteModeButton.scaleY = 0.95;
};
infiniteModeButton.up = function () {
infiniteModeButton.alpha = 0.8;
infiniteModeButton.scaleX = 1.0;
infiniteModeButton.scaleY = 1.0;
// Start infinite mode
infiniteMode = true;
gameState = 'playing';
// Hide start screen elements
titleText.visible = false;
startButton.visible = false;
infiniteModeButton.visible = false;
// Hide player selection buttons
for (var i = 0; i < playerSelectionButtons.length; i++) {
playerSelectionButtons[i].visible = false;
}
// Show game UI elements
scoreText.visible = true;
instructionText.visible = true;
// Initialize game objects without AI and Final1
initializeInfiniteGame();
};
// Function to initialize infinite mode game
function initializeInfiniteGame() {
// Initialize player only
player = new Player();
player.x = 300;
player.y = 2000;
game.addChild(player);
}
// Initialize AI-specific variables
aiCameraOffset = 0;
aiDistanceScore = 0;
aiLastPlatformX = 2200; // Same as initial lastPlatformX
// Create initial platforms
function createPlatform(x, y, width) {
var platform = new Platform(width);
platform.x = x;
platform.y = y;
platforms.push(platform);
game.addChild(platform);
return platform;
}
function createObstacle(x, y) {
var obstacle = new Obstacle();
obstacle.x = x;
obstacle.y = y;
obstacles.push(obstacle);
game.addChild(obstacle);
return obstacle;
}
// Generate initial level
createPlatform(200, 2100, 400); // Starting platform
createPlatform(600, 2000, 300);
createPlatform(1000, 1900, 250);
createPlatform(1400, 1800, 300);
createObstacle(1400, 1800);
createPlatform(1800, 1700, 200);
createPlatform(2200, 1600, 350);
lastPlatformX = 2200;
function generateLevel() {
// Remove platforms that are too far behind
for (var i = platforms.length - 1; i >= 0; i--) {
var platform = platforms[i];
if (platform.x < cameraOffset - 500) {
platform.destroy();
platforms.splice(i, 1);
}
}
// Remove obstacles that are too far behind
for (var i = obstacles.length - 1; i >= 0; i--) {
var obstacle = obstacles[i];
if (obstacle.x < cameraOffset - 500) {
obstacle.destroy();
obstacles.splice(i, 1);
}
}
// Generate new platforms ahead for player
while (lastPlatformX < cameraOffset + 3000) {
var lastPlatform = findLastPlatform();
var newPlatform = generateVerifiedPlatform(lastPlatform);
lastPlatformX = newPlatform.x;
createPlatform(newPlatform.x, newPlatform.y, newPlatform.width);
// Add obstacles with attempt-based variation
var obstacleChance = 0.2 + gameAttempts * 0.05; // Increase difficulty with attempts
if (obstacleChance > 0.5) obstacleChance = 0.5; // Cap at 50%
if (Math.random() < obstacleChance) {
createObstacle(newPlatform.x, newPlatform.y);
}
}
// Generate new platforms ahead for AI
while (aiLastPlatformX < aiCameraOffset + 3000) {
var aiLastPlatform = findLastPlatform();
var aiNewPlatform = generateVerifiedPlatform(aiLastPlatform);
aiLastPlatformX = aiNewPlatform.x;
createPlatform(aiNewPlatform.x, aiNewPlatform.y, aiNewPlatform.width);
// Add obstacles for AI path
if (Math.random() < 0.3) {
createObstacle(aiNewPlatform.x, aiNewPlatform.y);
}
}
}
function findLastPlatform() {
var lastPlatform = null;
var maxX = -999999;
for (var i = 0; i < platforms.length; i++) {
if (platforms[i].x > maxX) {
maxX = platforms[i].x;
lastPlatform = platforms[i];
}
}
return lastPlatform;
}
function generateVerifiedPlatform(lastPlatform) {
var maxJumpDistance = 320; // Maximum horizontal jump distance
var maxJumpHeight = 350; // Maximum jump height (going up)
var maxFallHeight = 600; // Maximum fall height (going down)
// Attempt-based level variation
var difficultyMultiplier = 1 + gameAttempts * 0.1;
if (difficultyMultiplier > 2) difficultyMultiplier = 2; // Cap difficulty
var attemptSeed = gameAttempts * 12345 % 100000;
var pseudoRandom = (attemptSeed + lastPlatformX * 7) % 1000 / 1000;
var gapDistance = 150 + pseudoRandom * 200 * difficultyMultiplier;
var platformWidth = 150 + pseudoRandom * 150;
var heightVariation = -150 + pseudoRandom * 300;
// Ensure gap is not too large
if (gapDistance > maxJumpDistance) {
gapDistance = maxJumpDistance - 20;
}
var newX = lastPlatform.x + gapDistance;
var newY = lastPlatform.y + heightVariation;
// Verify vertical reachability
var heightDifference = newY - lastPlatform.y;
if (heightDifference > maxFallHeight) {
newY = lastPlatform.y + maxFallHeight;
} else if (heightDifference < -maxJumpHeight) {
newY = lastPlatform.y - maxJumpHeight;
}
// Keep platforms within screen bounds - terrain cannot go above middle of screen
if (newY > 2200) newY = 2200;
if (newY < 1366) newY = 1366; // Middle of screen (2732/2 = 1366) - platforms cannot generate above this
// Verify horizontal reachability with physics simulation
if (!isPlatformReachable(lastPlatform, {
x: newX,
y: newY,
width: platformWidth
})) {
// Adjust platform to make it reachable
gapDistance = maxJumpDistance - 50;
newX = lastPlatform.x + gapDistance;
// Also adjust height to be more forgiving
if (heightDifference > 0) {
newY = lastPlatform.y + Math.min(heightDifference, 200);
} else {
newY = lastPlatform.y + Math.max(heightDifference, -250);
}
}
return {
x: newX,
y: newY,
width: platformWidth
};
}
function isPlatformReachable(fromPlatform, toPlatform) {
// Simulate a jump from the edge of fromPlatform to toPlatform
var startX = fromPlatform.x + fromPlatform.width / 2;
var startY = fromPlatform.y;
var targetX = toPlatform.x;
var targetY = toPlatform.y;
var targetLeft = targetX - toPlatform.width / 2;
var targetRight = targetX + toPlatform.width / 2;
// Simulate jump physics
var jumpPower = -25;
var gravity = 1.2;
var speed = 8;
var maxSimulationTime = 100; // Prevent infinite loops
var simX = startX;
var simY = startY;
var velocityY = jumpPower;
var velocityX = speed;
for (var t = 0; t < maxSimulationTime; t++) {
simX += velocityX;
simY += velocityY;
velocityY += gravity;
// Check if we've reached the target platform level
if (simY >= targetY && simX >= targetLeft && simX <= targetRight) {
return true;
}
// If we've fallen too far below the target, we can't reach it
if (simY > targetY + 100) {
break;
}
}
return false;
}
function checkCollisions() {
player.isGrounded = false;
// Check platform collisions
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
var playerBounds = {
left: player.x - 30,
right: player.x + 30,
top: player.y - 60,
bottom: player.y
};
var platformBounds = {
left: platform.x - platform.width / 2,
right: platform.x + platform.width / 2,
top: platform.y - 20,
bottom: platform.y + 20
};
// Check if player overlaps with platform
if (playerBounds.right > platformBounds.left && playerBounds.left < platformBounds.right && playerBounds.bottom > platformBounds.top && playerBounds.top < platformBounds.bottom) {
// Landing on top of platform
if (player.velocityY >= 0 && playerBounds.bottom - platformBounds.top < 20) {
player.y = platformBounds.top;
player.velocityY = 0;
player.isGrounded = true;
player.jumpCount = 0; // Reset jump count immediately when landing
}
}
}
// Check obstacle collisions - only if player is not invulnerable
for (var i = 0; i < obstacles.length; i++) {
var obstacle = obstacles[i];
if (player.intersects(obstacle) && !playerInvulnerable) {
// Player loses one life
playerLives--;
// Start invulnerability period
playerInvulnerable = true;
invulnerabilityTimer = invulnerabilityDuration;
// Flash player red when taking damage
LK.effects.flashObject(player, 0xff0000, 500);
LK.effects.flashScreen(0xff0000, 500);
// Create blinking effect during invulnerability using tween
tween(player, {
alpha: 0.3
}, {
duration: 200,
onFinish: function onFinish() {
tween(player, {
alpha: 1.0
}, {
duration: 200
});
}
});
// Update heart display
createHeartDisplay();
// Check if player is out of lives
if (playerLives <= 0) {
LK.showGameOver();
return;
} else {
// Push player away from obstacle to prevent multiple hits
if (obstacle.x > player.x) {
player.x -= 50; // Push left
} else {
player.x += 50; // Push right
}
player.velocityX = 0; // Stop horizontal movement
}
}
}
// Final1 asset is unreachable - no collision detection needed
}
function updateCamera() {
// Camera follows player with some offset
var targetCameraX = player.x - 400;
cameraOffset += (targetCameraX - cameraOffset) * 0.1;
// Move all game objects relative to camera
game.x = -cameraOffset;
// Make background follow player movement in opposite direction for proper parallax effect
if (backgroundAsset) {
backgroundAsset.x = cameraOffset; // Match exact player movement speed
}
// Update distance score
distanceScore = Math.floor(cameraOffset / 10);
scoreText.setText('Distance: ' + distanceScore);
LK.setScore(distanceScore);
// Create Final1 asset that moves dynamically (skip in infinite mode)
if (!goalAsset && !infiniteMode) {
goalAsset = LK.getAsset('Final1', {
width: 100,
height: 100,
anchorX: 0.5,
anchorY: 1.0
});
goalAsset.moveSpeed = 8; // Much faster movement speed for Final1
goalAsset.moveDirection = 1; // 1 for right, -1 for left
goalAsset.baseY = 800; // Base Y position
goalAsset.floatTime = 0; // For floating animation
game.addChild(goalAsset);
}
// Handle goal asset logic for both modes
if (goalAsset) {
// Move goal asset (Final1 or Moneda3) with floating motion
goalAsset.moveSpeed = goalAsset.moveSpeed || 8;
goalAsset.moveDirection = goalAsset.moveDirection || 1;
goalAsset.baseY = goalAsset.baseY || 800;
goalAsset.floatTime = goalAsset.floatTime || 0;
// Move horizontally only to the right, but stop when goal is reached
if (!goalReached) {
goalAsset.x += goalAsset.moveSpeed;
// Stop at right boundary but don't reverse direction
var screenRight = cameraOffset + 1800;
if (goalAsset.x >= screenRight) {
goalAsset.x = screenRight; // Keep at right boundary
}
}
// Floating vertical motion - only if animation is not deactivated
if (!goalAsset.animationDeactivated) {
goalAsset.floatTime += 0.02;
goalAsset.y = goalAsset.baseY + Math.sin(goalAsset.floatTime) * 50;
} else {
// Keep goal asset fixed at base Y position when animation is deactivated
goalAsset.y = goalAsset.baseY;
}
// Distance-based reachability system
var playerDistance = Math.abs(goalAsset.x - player.x);
var aiDistance = Math.abs(goalAsset.x - (aiPlayer ? aiPlayer.x : 9999));
// Check if goal asset has reached target distance (15000 for normal mode only)
var finalAssetDistance = goalAsset.x - 300; // Subtract initial player position
var targetDistance = 15000;
if (finalAssetDistance >= targetDistance && !goalReached) {
goalReached = true;
// Find nearest platform and position goal asset on it
var nearestPlatform = null;
var shortestDistance = 999999;
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
var distance = Math.abs(platform.x - goalAsset.x);
if (distance < shortestDistance) {
shortestDistance = distance;
nearestPlatform = platform;
}
}
// Position goal asset on the nearest platform
if (nearestPlatform) {
goalAsset.x = nearestPlatform.x;
goalAsset.y = nearestPlatform.y - 20; // Position slightly above platform
goalAsset.baseY = nearestPlatform.y - 20; // Update base Y for floating
// Deactivate floating animation by setting a flag
goalAsset.animationDeactivated = true;
}
LK.effects.flashScreen(0xFFD700, 2000); // Flash gold when Final1 becomes reachable
}
// Check if player touches goal asset when it's reachable
if (goalReached && playerDistance < 100) {
LK.effects.flashScreen(0x00FF00, 1500); // Green flash for player success
LK.showYouWin();
}
}
}
function updateAICamera() {
// AI camera follows AI player
if (!aiPlayer) return; // Skip if AI player doesn't exist (infinite mode)
var aiTargetCameraX = aiPlayer.x - 400;
aiCameraOffset += (aiTargetCameraX - aiCameraOffset) * 0.1;
// Update AI distance score
aiDistanceScore = Math.floor(aiCameraOffset / 10);
// AI uses the same moving Final1 asset as the player's goal
if (!aiGoalAsset && goalAsset) {
aiGoalAsset = goalAsset; // Share the same moving Final1 asset
}
}
// Create control buttons
var leftButton = LK.getAsset('Izquierda1', {
width: 150,
height: 100,
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
leftButton.alpha = 0.7;
var rightButton = LK.getAsset('Derecha2', {
width: 150,
height: 100,
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
rightButton.alpha = 0.7;
var jumpButton = LK.getAsset('Salto1', {
width: 120,
height: 120,
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.2,
scaleY: 2.2
});
jumpButton.tint = 0x32cd32;
jumpButton.alpha = 0.8;
// Position buttons in GUI - left and right buttons together in bottom left
LK.gui.bottomLeft.addChild(leftButton);
leftButton.x = 80;
leftButton.y = -100;
LK.gui.bottomLeft.addChild(rightButton);
rightButton.x = 300;
rightButton.y = -100;
LK.gui.bottomRight.addChild(jumpButton);
jumpButton.x = -130;
jumpButton.y = -100;
// Button event handlers
leftButton.down = function () {
leftButton.alpha = 1.0;
leftButtonPressed = true;
};
leftButton.up = function () {
leftButton.alpha = 0.7;
leftButtonPressed = false;
};
rightButton.down = function () {
rightButton.alpha = 1.0;
rightButtonPressed = true;
};
rightButton.up = function () {
rightButton.alpha = 0.7;
rightButtonPressed = false;
};
jumpButton.down = function () {
jumpButton.alpha = 1.0;
jumpButton.scaleX = 1.3;
jumpButton.scaleY = 1.3;
jumpButtonPressed = true;
if (player && gameState === 'playing') {
player.jump();
}
};
jumpButton.up = function () {
jumpButton.alpha = 0.8;
jumpButton.scaleX = 1.5;
jumpButton.scaleY = 1.5;
jumpButtonPressed = false;
};
// Track when game starts/resets
var originalShowGameOver = LK.showGameOver;
LK.showGameOver = function () {
gameAttempts++;
levelSeed = gameAttempts * 54321; // Change seed each attempt
// Reset player lives when game restarts
playerLives = maxLives;
// Reset invulnerability state
playerInvulnerable = false;
invulnerabilityTimer = 0;
createHeartDisplay();
// Reset game state to start screen
gameState = 'start';
// Show start screen elements
titleText.visible = true;
startButton.visible = true;
infiniteModeButton.visible = true;
// Show player selection buttons
for (var i = 0; i < playerSelectionButtons.length; i++) {
playerSelectionButtons[i].visible = true;
}
// Hide game UI elements
scoreText.visible = false;
instructionText.visible = false;
// Clear game objects
if (player) {
player.destroy();
player = null;
}
if (aiPlayer) {
aiPlayer.destroy();
aiPlayer = null;
}
if (aiPlayer2) {
aiPlayer2.destroy();
aiPlayer2 = null;
}
if (goalAsset) {
goalAsset.destroy();
goalAsset = null;
}
// Clear platforms and obstacles
for (var i = 0; i < platforms.length; i++) {
platforms[i].destroy();
}
platforms = [];
for (var i = 0; i < obstacles.length; i++) {
obstacles[i].destroy();
}
obstacles = [];
// Reset game variables
cameraOffset = 0;
distanceScore = 0;
lastPlatformX = 0;
goalReached = false;
aiCameraOffset = 0;
aiDistanceScore = 0;
aiGoalAsset = null;
aiGoalReached = false;
aiLastPlatformX = 0;
infiniteMode = false;
originalShowGameOver();
};
game.update = function () {
// Only update game when in playing state
if (gameState !== 'playing') {
return;
}
// Handle invulnerability timer
if (playerInvulnerable) {
invulnerabilityTimer--;
if (invulnerabilityTimer <= 0) {
playerInvulnerable = false;
// Ensure player is fully visible when invulnerability ends
tween.stop(player, {
alpha: true
});
player.alpha = 1.0;
}
}
// Handle continuous movement based on button states
if (leftButtonPressed) {
player.moveLeft();
}
if (rightButtonPressed) {
player.moveRight();
}
// Handle continuous jump (allows jump when held down and landing)
if (jumpButtonPressed) {
player.jump();
}
// Update AI if it exists (skip in infinite mode)
if (aiPlayer && !infiniteMode) {
aiPlayer.update();
// Check AI collisions with platforms
aiPlayer.isGrounded = false;
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
var aiBounds = {
left: aiPlayer.x - 50,
right: aiPlayer.x + 50,
top: aiPlayer.y - 100,
bottom: aiPlayer.y
};
var platformBounds = {
left: platform.x - platform.width / 2,
right: platform.x + platform.width / 2,
top: platform.y - 20,
bottom: platform.y + 20
};
// Check if AI overlaps with platform
if (aiBounds.right > platformBounds.left && aiBounds.left < platformBounds.right && aiBounds.bottom > platformBounds.top && aiBounds.top < platformBounds.bottom) {
// Landing on top of platform
if (aiPlayer.velocityY >= 0 && aiBounds.bottom - platformBounds.top < 40) {
aiPlayer.y = platformBounds.top;
aiPlayer.velocityY = 0;
aiPlayer.isGrounded = true;
aiPlayer.jumpCount = 0; // Reset jump count immediately when landing
}
}
}
// Check AI obstacle collisions
for (var i = 0; i < obstacles.length; i++) {
var obstacle = obstacles[i];
if (aiPlayer.intersects(obstacle)) {
// AI takes damage from obstacle and handles it independently
aiPlayer.takeDamage(25); // AI takes damage instead of affecting player
}
}
}
// Update AI2 if it exists (skip in infinite mode)
if (aiPlayer2 && !infiniteMode) {
aiPlayer2.update();
// Check AI2 collisions with platforms
aiPlayer2.isGrounded = false;
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
var ai2Bounds = {
left: aiPlayer2.x - 50,
right: aiPlayer2.x + 50,
top: aiPlayer2.y - 100,
bottom: aiPlayer2.y
};
var platformBounds = {
left: platform.x - platform.width / 2,
right: platform.x + platform.width / 2,
top: platform.y - 20,
bottom: platform.y + 20
};
// Check if AI2 overlaps with platform
if (ai2Bounds.right > platformBounds.left && ai2Bounds.left < platformBounds.right && ai2Bounds.bottom > platformBounds.top && ai2Bounds.top < platformBounds.bottom) {
// Landing on top of platform
if (aiPlayer2.velocityY >= 0 && ai2Bounds.bottom - platformBounds.top < 40) {
aiPlayer2.y = platformBounds.top;
aiPlayer2.velocityY = 0;
aiPlayer2.isGrounded = true;
aiPlayer2.jumpCount = 0; // Reset jump count immediately when landing
}
}
}
// Check AI2 obstacle collisions
for (var i = 0; i < obstacles.length; i++) {
var obstacle = obstacles[i];
if (aiPlayer2.intersects(obstacle)) {
// AI2 takes damage from obstacle and handles it independently
aiPlayer2.takeDamage(25); // AI2 takes damage instead of affecting player
}
}
// Check if player is falling into void (no platform below for a significant distance)
if (player.y > 2400 && !player.isGrounded) {
// Start checking earlier than complete fall off
var foundPlatformBelow = false;
// Look for any platform below the player within a reasonable distance
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
var horizontalDistance = Math.abs(platform.x - player.x);
// Check if there's a platform below within reasonable horizontal distance
if (platform.y > player.y && platform.y < player.y + 400 && horizontalDistance < 200) {
foundPlatformBelow = true;
break;
}
}
// If no platform found below and player is falling fast, respawn with 2 life loss
if (!foundPlatformBelow || player.velocityY > 15) {
// Player loses 2 lives when falling into void
playerLives -= 2;
if (playerLives < 0) playerLives = 0;
// Flash effects for void fall
LK.effects.flashObject(player, 0xff0000, 800);
LK.effects.flashScreen(0xff0000, 800);
// Update heart display
createHeartDisplay();
// Check if player is out of lives
if (playerLives <= 0) {
LK.showGameOver();
return;
}
// Find platform closest to Final1 asset and respawn there
if (goalAsset && platforms.length > 0) {
var closestPlatform = null;
var shortestDistance = 999999;
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
var distance = Math.abs(platform.x - goalAsset.x);
if (distance < shortestDistance) {
shortestDistance = distance;
closestPlatform = platform;
}
}
// Position player on the closest platform to Final1
if (closestPlatform) {
player.x = closestPlatform.x;
player.y = closestPlatform.y - 20; // Position slightly above platform
} else {
// Fallback to starting position if no platform found
player.x = 300;
player.y = 2000;
}
} else {
// Fallback to starting position if no Final1 asset exists
player.x = 300;
player.y = 2000;
}
player.velocityX = 0;
player.velocityY = 0;
player.isGrounded = false;
// Start invulnerability period after respawn
playerInvulnerable = true;
invulnerabilityTimer = invulnerabilityDuration;
// Create blinking effect during invulnerability using tween
tween(player, {
alpha: 0.3
}, {
duration: 200,
onFinish: function onFinish() {
tween(player, {
alpha: 1.0
}, {
duration: 200
});
}
});
}
}
// Check if AI is falling into void (no platform below for a significant distance)
if (aiPlayer.y > 2400 && !aiPlayer.isGrounded) {
// Start checking earlier than complete fall off
var foundPlatformBelow = false;
// Look for any platform below the AI within a reasonable distance
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 horizontalDistance = Math.abs(platform.x - aiPlayer.x);
// Check if there's a platform below within reasonable horizontal distance
if (platform.y > aiPlayer.y && platform.y < aiPlayer.y + 400 && horizontalDistance < 200) {
foundPlatformBelow = true;
break;
}
}
// If no platform found below and AI is falling fast, respawn
if (!foundPlatformBelow || aiPlayer.velocityY > 15) {
// Find platform closest to Final1 asset and respawn there
if (goalAsset && platforms.length > 0) {
var closestPlatform = null;
var shortestDistance = 999999;
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
var distance = Math.abs(platform.x - goalAsset.x);
if (distance < shortestDistance) {
shortestDistance = distance;
closestPlatform = platform;
}
}
// Position AI on the closest platform to Final1
if (closestPlatform) {
aiPlayer.x = closestPlatform.x;
aiPlayer.y = closestPlatform.y - 20; // Position slightly above platform
} else {
// Fallback to player position if no platform found
aiPlayer.x = player.x;
aiPlayer.y = player.y;
}
} else {
// Fallback to player position if no Final1 asset exists
aiPlayer.x = player.x;
aiPlayer.y = player.y;
}
aiPlayer.velocityX = 0;
aiPlayer.velocityY = 0;
aiPlayer.isGrounded = false;
LK.effects.flashObject(aiPlayer, 0x00ff00, 1000); // Green flash for regeneration
}
}
// Check if AI2 is falling into void (no platform below for a significant distance)
if (aiPlayer2.y > 2400 && !aiPlayer2.isGrounded) {
// Start checking earlier than complete fall off
var foundPlatformBelow = false;
// Look for any platform below the AI2 within a reasonable distance
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 horizontalDistance = Math.abs(platform.x - aiPlayer2.x);
// Check if there's a platform below within reasonable horizontal distance
if (platform.y > aiPlayer2.y && platform.y < aiPlayer2.y + 400 && horizontalDistance < 200) {
foundPlatformBelow = true;
break;
}
}
// If no platform found below and AI2 is falling fast, respawn
if (!foundPlatformBelow || aiPlayer2.velocityY > 15) {
// Find platform closest to Final1 asset and respawn there
if (goalAsset && platforms.length > 0) {
var closestPlatform = null;
var shortestDistance = 999999;
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
var distance = Math.abs(platform.x - goalAsset.x);
if (distance < shortestDistance) {
shortestDistance = distance;
closestPlatform = platform;
}
}
// Position AI2 on the closest platform to Final1
if (closestPlatform) {
aiPlayer2.x = closestPlatform.x;
aiPlayer2.y = closestPlatform.y - 20; // Position slightly above platform
} else {
// Fallback to player position if no platform found
aiPlayer2.x = player.x;
aiPlayer2.y = player.y;
}
} else {
// Fallback to player position if no Final1 asset exists
aiPlayer2.x = player.x;
aiPlayer2.y = player.y;
}
aiPlayer2.velocityX = 0;
aiPlayer2.velocityY = 0;
aiPlayer2.isGrounded = false;
LK.effects.flashObject(aiPlayer2, 0x00ff00, 1000); // Green flash for regeneration
}
}
// Prevent AI from falling off screen (backup safety check)
if (aiPlayer.y > 2800) {
aiPlayer.y = 2000;
aiPlayer.x = 200; // Reset to independent starting position
aiPlayer.velocityY = 0;
aiPlayer.velocityX = 0;
}
// Prevent AI2 from falling off screen (backup safety check)
if (aiPlayer2.y > 2800) {
aiPlayer2.y = 2000;
aiPlayer2.x = 150; // Reset to independent starting position
aiPlayer2.velocityY = 0;
aiPlayer2.velocityX = 0;
}
// AI goal stays fixed at right center of screen - no movement needed
}
checkCollisions();
updateCamera();
updateAICamera();
generateLevel();
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var AI = Container.expand(function () {
var self = Container.call(this);
var aiGraphics = self.attachAsset('Caminando1', {
anchorX: 0.5,
anchorY: 1.0,
width: 60,
height: 60
});
self.target = null;
self.lastX = 0;
self.lastY = 0;
self.stuckCounter = 0;
self.jumpCooldown = 0;
self.scanDistance = 800;
self.pathfindingCooldown = 0;
self.velocityY = 0;
self.velocityX = 0;
self.isGrounded = false;
self.jumpPower = -20;
self.gravity = 1.2;
self.speed = 4; // Slower than player
self.maxFallSpeed = 20;
self.jumpCount = 0;
self.maxJumps = 2;
// AI health and regeneration system
self.health = 100;
self.maxHealth = 100;
self.regenerationRate = 2; // Health points per second
self.damageCooldown = 0;
self.isRegenerating = false;
self.regenerationDelay = 60; // Frames to wait before starting regeneration after damage
// AI decision making
self.makeDecision = function () {
if (!aiGoalAsset) return;
// Update cooldowns
if (self.jumpCooldown > 0) self.jumpCooldown--;
if (self.pathfindingCooldown > 0) self.pathfindingCooldown--;
// Check if stuck
var aiMovement = Math.abs(self.x - self.lastX) + Math.abs(self.y - self.lastY);
if (aiMovement < 2) {
self.stuckCounter++;
} else {
self.stuckCounter = 0;
}
self.lastX = self.x;
self.lastY = self.y;
// Decision state system - AI can choose between different actions
var decisionState = self.evaluateSituation();
switch (decisionState) {
case 'JUMP':
if (self.isGrounded && self.jumpCooldown <= 0) {
self.jump();
self.jumpCooldown = 20;
}
break;
case 'MOVE_LEFT':
self.moveLeft();
break;
case 'MOVE_RIGHT':
self.moveRight();
break;
case 'STAY_STILL':
// AI decides to stay still - do nothing
self.velocityX *= 0.5; // Slow down gradually
break;
default:
// Default fallback behavior
self.executeDefaultBehavior();
break;
}
};
// Evaluate current situation and decide what action to take
self.evaluateSituation = function () {
if (!aiGoalAsset) return 'MOVE_RIGHT'; // Default if no goal
var goalDistance = aiGoalAsset.x - self.x;
var goalVerticalDistance = aiGoalAsset.y - self.y;
var obstacleAhead = self.checkForObstaclesAhead();
var distanceToGoal = Math.abs(goalDistance);
var goalReachable = distanceToGoal <= 1000; // Check if goal is within reachable distance
// Priority 1: If stuck for too long, jump
if (self.stuckCounter > 30 && self.isGrounded && self.jumpCooldown <= 0) {
return 'JUMP';
}
// Priority 2: Jump over obstacles when moving
if (obstacleAhead && self.isGrounded && self.jumpCooldown <= 0 && Math.abs(self.velocityX) > 1) {
return 'JUMP';
}
// Priority 3: Jump if goal is significantly above and we're close horizontally
if (Math.abs(goalDistance) < 200 && goalVerticalDistance < -100 && self.isGrounded && self.jumpCooldown <= 0) {
return 'JUMP';
}
// Priority 4: Stay still if very close to goal (within 100 units)
if (Math.abs(goalDistance) < 100 && Math.abs(goalVerticalDistance) < 50) {
return 'STAY_STILL';
}
// Priority 5: Stay still if on unstable ground or dangerous situation
if (!self.isGrounded && self.velocityY > 10) {
return 'STAY_STILL'; // Don't make sudden moves while falling fast
}
// Priority 6: Move towards goal only if it's reachable
if (goalReachable) {
if (goalDistance > 50) {
return 'MOVE_RIGHT';
} else if (goalDistance < -50) {
return 'MOVE_LEFT';
}
} else {
// Goal is not reachable due to distance restriction - move towards it to get closer
if (goalDistance > 0) {
return 'MOVE_RIGHT';
} else {
return 'MOVE_LEFT';
}
}
// Priority 7: Stay still if goal is close horizontally and reachable
if (goalReachable && Math.abs(goalDistance) < 100) {
return 'STAY_STILL';
}
return 'MOVE_RIGHT'; // Default movement
};
// Execute default behavior when no specific decision is made
self.executeDefaultBehavior = function () {
// Find target platform to move towards goal
if (self.pathfindingCooldown <= 0) {
self.target = self.findBestPlatform();
self.pathfindingCooldown = 10;
}
// Navigate towards target
if (self.target) {
self.navigateToTarget();
} else {
// Move towards goal as fallback
if (aiGoalAsset) {
var goalDistance = aiGoalAsset.x - self.x;
var goalVerticalDistance = aiGoalAsset.y - self.y;
// Check for obstacles ahead when moving towards goal
var obstacleAhead = self.checkForObstaclesAhead();
if (obstacleAhead && self.isGrounded && self.jumpCooldown <= 0) {
self.jump();
self.jumpCooldown = 20;
}
if (goalDistance > 50) {
self.moveRight();
} else if (goalDistance < -50) {
self.moveLeft();
}
} else {
// No goal yet, move right by default
// Check for obstacles when moving right
var obstacleAhead = self.checkForObstaclesAhead();
if (obstacleAhead && self.isGrounded && self.jumpCooldown <= 0) {
self.jump();
self.jumpCooldown = 20;
}
self.moveRight();
}
}
};
self.findBestPlatform = function () {
var bestPlatform = null;
var bestScore = -999999;
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
// Skip platforms too far away or too close
if (Math.abs(platform.x - self.x) > 400 || Math.abs(platform.x - self.x) < 20) continue;
// Calculate score based on progress towards goal and reachability
var distanceToGoal = Math.abs(platform.x - aiGoalAsset.x);
var progressScore = (platform.x - self.x) * 2; // Favor forward movement
var goalProximityScore = -distanceToGoal * 0.5; // Favor platforms closer to goal
var heightPenalty = Math.abs(platform.y - self.y) * 0.1;
var score = progressScore + goalProximityScore - heightPenalty;
// Check if platform is reachable
if (self.isPlatformReachableByAI(platform)) {
score += 1000; // Bonus for reachable platforms
}
// Check for obstacles on platform
if (self.hasObstacleOnPlatform(platform)) {
score -= 500; // Penalty for obstacles
}
if (score > bestScore) {
bestScore = score;
bestPlatform = platform;
}
}
return bestPlatform;
};
self.isPlatformReachableByAI = function (platform) {
var horizontalDistance = Math.abs(platform.x - self.x);
var verticalDistance = platform.y - self.y;
// Simple reachability check
if (horizontalDistance > 350) return false; // Too far horizontally
if (verticalDistance > 400) return false; // Too high
if (verticalDistance < -600) return false; // Too far below
return true;
};
self.hasObstacleOnPlatform = function (platform) {
for (var i = 0; i < obstacles.length; i++) {
var obstacle = obstacles[i];
var distToPlatform = Math.abs(obstacle.x - platform.x);
var heightDiff = Math.abs(obstacle.y - platform.y);
if (distToPlatform < platform.width / 2 + 50 && heightDiff < 100) {
return true;
}
}
return false;
};
self.navigateToTarget = function () {
if (!self.target) return;
var horizontalDistance = self.target.x - self.x;
var verticalDistance = self.target.y - self.y;
// Check if we need to jump over obstacles
var needsJump = self.checkForObstaclesAhead();
// Jump if target is above us or if there's an obstacle (be more aggressive)
if ((verticalDistance < -50 || needsJump) && self.isGrounded && self.jumpCooldown <= 0) {
self.jump();
self.jumpCooldown = 20; // Reduced cooldown for more responsive jumping
}
// Also jump if we're close to the target horizontally but need to reach it vertically
if (Math.abs(horizontalDistance) < 100 && verticalDistance < -50 && self.isGrounded && self.jumpCooldown <= 0) {
self.jump();
self.jumpCooldown = 20;
}
// Move horizontally towards target
if (horizontalDistance > 30) {
self.moveRight();
} else if (horizontalDistance < -30) {
self.moveLeft();
}
// Check if we've reached the target platform
if (Math.abs(horizontalDistance) < 50 && Math.abs(verticalDistance) < 100) {
self.target = null; // Find new target
}
};
self.checkForObstaclesAhead = function () {
// Look ahead based on current movement speed and direction
var lookAheadDistance = Math.abs(self.velocityX) * 15 + 100; // Scale with speed
var direction = self.velocityX >= 0 ? 1 : -1; // Moving right or left
for (var i = 0; i < obstacles.length; i++) {
var obstacle = obstacles[i];
var obstacleDistance = (obstacle.x - self.x) * direction;
var heightDiff = Math.abs(obstacle.y - self.y);
// Check if obstacle is ahead in movement direction and at similar height
if (obstacleDistance > 0 && obstacleDistance < lookAheadDistance && heightDiff < 120) {
return true;
}
}
return false;
};
self.moveLeft = function () {
self.velocityX = -self.speed;
};
self.moveRight = function () {
self.velocityX = self.speed;
};
self.jump = function () {
if (self.jumpCount < self.maxJumps) {
self.velocityY = self.jumpPower;
self.jumpCount++;
if (self.isGrounded) {
self.isGrounded = false;
}
}
};
// AI takes damage and handles regeneration
self.takeDamage = function (amount) {
amount = amount || 20; // Default damage amount
self.health -= amount;
if (self.health < 0) self.health = 0;
self.damageCooldown = self.regenerationDelay;
self.isRegenerating = false;
// Flash AI red when taking damage
LK.effects.flashObject(self, 0xff0000, 500);
// If health reaches 0, reset AI position and health
if (self.health <= 0) {
self.health = self.maxHealth;
// Find platform closest to Final1 asset and respawn there
if (goalAsset && platforms.length > 0) {
var closestPlatform = null;
var shortestDistance = 999999;
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
var distance = Math.abs(platform.x - goalAsset.x);
if (distance < shortestDistance) {
shortestDistance = distance;
closestPlatform = platform;
}
}
// Position AI on the closest platform to Final1
if (closestPlatform) {
self.x = closestPlatform.x;
self.y = closestPlatform.y - 20; // Position slightly above platform
} else {
// Fallback to player position if no platform found
self.x = player.x;
self.y = player.y;
}
} else {
// Fallback to player position if no Final1 asset exists
self.x = player.x;
self.y = player.y;
}
self.velocityX = 0;
self.velocityY = 0;
LK.effects.flashObject(self, 0x00ff00, 1000); // Green flash for regeneration
}
};
self.update = function () {
// Reset jump count when grounded
if (self.isGrounded) {
self.jumpCount = 0;
}
// Apply gravity
if (!self.isGrounded) {
self.velocityY += self.gravity;
if (self.velocityY > self.maxFallSpeed) {
self.velocityY = self.maxFallSpeed;
}
}
// Check for obstacles before moving and jump if needed
if (self.isGrounded && self.jumpCooldown <= 0 && Math.abs(self.velocityX) > 2) {
var obstacleAhead = self.checkForObstaclesAhead();
if (obstacleAhead) {
self.jump();
self.jumpCooldown = 25;
}
}
// Apply horizontal movement with friction
self.x += self.velocityX;
self.velocityX *= 0.85;
// Apply vertical movement
self.y += self.velocityY;
// Check if AI needs to jump to avoid falling (gap detection)
if (self.isGrounded && self.jumpCooldown <= 0) {
// Look ahead to see if there's a platform to land on
var foundPlatformAhead = false;
var lookAheadX = self.x + (self.velocityX > 0 ? 150 : -150);
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 heightDiff = Math.abs(platform.y - self.y);
// Check if there's a platform ahead at similar height
if (lookAheadX >= platformLeft && lookAheadX <= platformRight && heightDiff < 200) {
foundPlatformAhead = true;
break;
}
}
// If no platform found ahead and we're moving, jump to try to reach something
if (!foundPlatformAhead && Math.abs(self.velocityX) > 2) {
self.jump();
self.jumpCooldown = 40;
}
}
// Handle damage cooldown and regeneration
if (self.damageCooldown > 0) {
self.damageCooldown--;
} else if (self.health < self.maxHealth) {
// Start regenerating when cooldown is over
if (!self.isRegenerating) {
self.isRegenerating = true;
}
// Regenerate health (60 FPS, so divide by 60 for per-second rate)
self.health += self.regenerationRate / 60;
if (self.health > self.maxHealth) {
self.health = self.maxHealth;
self.isRegenerating = false;
}
}
// Make AI decisions
self.makeDecision();
};
return self;
});
var AI2 = Container.expand(function () {
var self = Container.call(this);
var aiGraphics = self.attachAsset('Caminando2', {
anchorX: 0.5,
anchorY: 1.0,
width: 60,
height: 60
});
self.target = null;
self.lastX = 0;
self.lastY = 0;
self.stuckCounter = 0;
self.jumpCooldown = 0;
self.scanDistance = 800;
self.pathfindingCooldown = 0;
self.velocityY = 0;
self.velocityX = 0;
self.isGrounded = false;
self.jumpPower = -18;
self.gravity = 1.2;
self.speed = 6; // Slower than player
self.maxFallSpeed = 20;
self.jumpCount = 0;
self.maxJumps = 2;
// AI health and regeneration system
self.health = 100;
self.maxHealth = 100;
self.regenerationRate = 2; // Health points per second
self.damageCooldown = 0;
self.isRegenerating = false;
self.regenerationDelay = 60; // Frames to wait before starting regeneration after damage
// AI decision making
self.makeDecision = function () {
if (!aiGoalAsset) return;
// Update cooldowns
if (self.jumpCooldown > 0) self.jumpCooldown--;
if (self.pathfindingCooldown > 0) self.pathfindingCooldown--;
// Check if stuck
var aiMovement = Math.abs(self.x - self.lastX) + Math.abs(self.y - self.lastY);
if (aiMovement < 2) {
self.stuckCounter++;
} else {
self.stuckCounter = 0;
}
self.lastX = self.x;
self.lastY = self.y;
// Decision state system - AI can choose between different actions
var decisionState = self.evaluateSituation();
switch (decisionState) {
case 'JUMP':
if (self.isGrounded && self.jumpCooldown <= 0) {
self.jump();
self.jumpCooldown = 20;
}
break;
case 'MOVE_LEFT':
self.moveLeft();
break;
case 'MOVE_RIGHT':
self.moveRight();
break;
case 'STAY_STILL':
// AI decides to stay still - do nothing
self.velocityX *= 0.5; // Slow down gradually
break;
default:
// Default fallback behavior
self.executeDefaultBehavior();
break;
}
};
// Evaluate current situation and decide what action to take
self.evaluateSituation = function () {
if (!aiGoalAsset) return 'MOVE_RIGHT'; // Default if no goal
var goalDistance = aiGoalAsset.x - self.x;
var goalVerticalDistance = aiGoalAsset.y - self.y;
var obstacleAhead = self.checkForObstaclesAhead();
var distanceToGoal = Math.abs(goalDistance);
var goalReachable = distanceToGoal <= 1000; // Check if goal is within reachable distance
// Priority 1: If stuck for too long, jump
if (self.stuckCounter > 30 && self.isGrounded && self.jumpCooldown <= 0) {
return 'JUMP';
}
// Priority 2: Jump over obstacles when moving
if (obstacleAhead && self.isGrounded && self.jumpCooldown <= 0 && Math.abs(self.velocityX) > 1) {
return 'JUMP';
}
// Priority 3: Jump if goal is significantly above and we're close horizontally
if (Math.abs(goalDistance) < 200 && goalVerticalDistance < -100 && self.isGrounded && self.jumpCooldown <= 0) {
return 'JUMP';
}
// Priority 4: Stay still if very close to goal (within 100 units)
if (Math.abs(goalDistance) < 100 && Math.abs(goalVerticalDistance) < 50) {
return 'STAY_STILL';
}
// Priority 5: Stay still if on unstable ground or dangerous situation
if (!self.isGrounded && self.velocityY > 10) {
return 'STAY_STILL'; // Don't make sudden moves while falling fast
}
// Priority 6: Move towards goal only if it's reachable
if (goalReachable) {
if (goalDistance > 50) {
return 'MOVE_RIGHT';
} else if (goalDistance < -50) {
return 'MOVE_LEFT';
}
} else {
// Goal is not reachable due to distance restriction - move towards it to get closer
if (goalDistance > 0) {
return 'MOVE_RIGHT';
} else {
return 'MOVE_LEFT';
}
}
// Priority 7: Stay still if goal is close horizontally and reachable
if (goalReachable && Math.abs(goalDistance) < 100) {
return 'STAY_STILL';
}
return 'MOVE_RIGHT'; // Default movement
};
// Execute default behavior when no specific decision is made
self.executeDefaultBehavior = function () {
// Find target platform to move towards goal
if (self.pathfindingCooldown <= 0) {
self.target = self.findBestPlatform();
self.pathfindingCooldown = 10;
}
// Navigate towards target
if (self.target) {
self.navigateToTarget();
} else {
// Move towards goal as fallback
if (aiGoalAsset) {
var goalDistance = aiGoalAsset.x - self.x;
var goalVerticalDistance = aiGoalAsset.y - self.y;
// Check for obstacles ahead when moving towards goal
var obstacleAhead = self.checkForObstaclesAhead();
if (obstacleAhead && self.isGrounded && self.jumpCooldown <= 0) {
self.jump();
self.jumpCooldown = 20;
}
if (goalDistance > 50) {
self.moveRight();
} else if (goalDistance < -50) {
self.moveLeft();
}
} else {
// No goal yet, move right by default
// Check for obstacles when moving right
var obstacleAhead = self.checkForObstaclesAhead();
if (obstacleAhead && self.isGrounded && self.jumpCooldown <= 0) {
self.jump();
self.jumpCooldown = 20;
}
self.moveRight();
}
}
};
self.findBestPlatform = function () {
var bestPlatform = null;
var bestScore = -999999;
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
// Skip platforms too far away or too close
if (Math.abs(platform.x - self.x) > 400 || Math.abs(platform.x - self.x) < 20) continue;
// Calculate score based on progress towards goal and reachability
var distanceToGoal = Math.abs(platform.x - aiGoalAsset.x);
var progressScore = (platform.x - self.x) * 2; // Favor forward movement
var goalProximityScore = -distanceToGoal * 0.5; // Favor platforms closer to goal
var heightPenalty = Math.abs(platform.y - self.y) * 0.1;
var score = progressScore + goalProximityScore - heightPenalty;
// Check if platform is reachable
if (self.isPlatformReachableByAI(platform)) {
score += 1000; // Bonus for reachable platforms
}
// Check for obstacles on platform
if (self.hasObstacleOnPlatform(platform)) {
score -= 500; // Penalty for obstacles
}
if (score > bestScore) {
bestScore = score;
bestPlatform = platform;
}
}
return bestPlatform;
};
self.isPlatformReachableByAI = function (platform) {
var horizontalDistance = Math.abs(platform.x - self.x);
var verticalDistance = platform.y - self.y;
// Simple reachability check
if (horizontalDistance > 350) return false; // Too far horizontally
if (verticalDistance > 400) return false; // Too high
if (verticalDistance < -600) return false; // Too far below
return true;
};
self.hasObstacleOnPlatform = function (platform) {
for (var i = 0; i < obstacles.length; i++) {
var obstacle = obstacles[i];
var distToPlatform = Math.abs(obstacle.x - platform.x);
var heightDiff = Math.abs(obstacle.y - platform.y);
if (distToPlatform < platform.width / 2 + 50 && heightDiff < 100) {
return true;
}
}
return false;
};
self.navigateToTarget = function () {
if (!self.target) return;
var horizontalDistance = self.target.x - self.x;
var verticalDistance = self.target.y - self.y;
// Check if we need to jump over obstacles
var needsJump = self.checkForObstaclesAhead();
// Jump if target is above us or if there's an obstacle (be more aggressive)
if ((verticalDistance < -50 || needsJump) && self.isGrounded && self.jumpCooldown <= 0) {
self.jump();
self.jumpCooldown = 20; // Reduced cooldown for more responsive jumping
}
// Also jump if we're close to the target horizontally but need to reach it vertically
if (Math.abs(horizontalDistance) < 100 && verticalDistance < -50 && self.isGrounded && self.jumpCooldown <= 0) {
self.jump();
self.jumpCooldown = 20;
}
// Move horizontally towards target
if (horizontalDistance > 30) {
self.moveRight();
} else if (horizontalDistance < -30) {
self.moveLeft();
}
// Check if we've reached the target platform
if (Math.abs(horizontalDistance) < 50 && Math.abs(verticalDistance) < 100) {
self.target = null; // Find new target
}
};
self.checkForObstaclesAhead = function () {
// Look ahead based on current movement speed and direction
var lookAheadDistance = Math.abs(self.velocityX) * 15 + 100; // Scale with speed
var direction = self.velocityX >= 0 ? 1 : -1; // Moving right or left
for (var i = 0; i < obstacles.length; i++) {
var obstacle = obstacles[i];
var obstacleDistance = (obstacle.x - self.x) * direction;
var heightDiff = Math.abs(obstacle.y - self.y);
// Check if obstacle is ahead in movement direction and at similar height
if (obstacleDistance > 0 && obstacleDistance < lookAheadDistance && heightDiff < 120) {
return true;
}
}
return false;
};
self.moveLeft = function () {
self.velocityX = -self.speed;
};
self.moveRight = function () {
self.velocityX = self.speed;
};
self.jump = function () {
if (self.jumpCount < self.maxJumps) {
self.velocityY = self.jumpPower;
self.jumpCount++;
if (self.isGrounded) {
self.isGrounded = false;
}
}
};
// AI takes damage and handles regeneration
self.takeDamage = function (amount) {
amount = amount || 20; // Default damage amount
self.health -= amount;
if (self.health < 0) self.health = 0;
self.damageCooldown = self.regenerationDelay;
self.isRegenerating = false;
// Flash AI red when taking damage
LK.effects.flashObject(self, 0xff0000, 500);
// If health reaches 0, reset AI position and health
if (self.health <= 0) {
self.health = self.maxHealth;
// Find platform closest to Final1 asset and respawn there
if (goalAsset && platforms.length > 0) {
var closestPlatform = null;
var shortestDistance = 999999;
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
var distance = Math.abs(platform.x - goalAsset.x);
if (distance < shortestDistance) {
shortestDistance = distance;
closestPlatform = platform;
}
}
// Position AI on the closest platform to Final1
if (closestPlatform) {
self.x = closestPlatform.x;
self.y = closestPlatform.y - 20; // Position slightly above platform
} else {
// Fallback to player position if no platform found
self.x = player.x;
self.y = player.y;
}
} else {
// Fallback to player position if no Final1 asset exists
self.x = player.x;
self.y = player.y;
}
self.velocityX = 0;
self.velocityY = 0;
LK.effects.flashObject(self, 0x00ff00, 1000); // Green flash for regeneration
}
};
self.update = function () {
// Reset jump count when grounded
if (self.isGrounded) {
self.jumpCount = 0;
}
// Apply gravity
if (!self.isGrounded) {
self.velocityY += self.gravity;
if (self.velocityY > self.maxFallSpeed) {
self.velocityY = self.maxFallSpeed;
}
}
// Check for obstacles before moving and jump if needed
if (self.isGrounded && self.jumpCooldown <= 0 && Math.abs(self.velocityX) > 2) {
var obstacleAhead = self.checkForObstaclesAhead();
if (obstacleAhead) {
self.jump();
self.jumpCooldown = 25;
}
}
// Apply horizontal movement with friction
self.x += self.velocityX;
self.velocityX *= 0.85;
// Apply vertical movement
self.y += self.velocityY;
// Check if AI needs to jump to avoid falling (gap detection)
if (self.isGrounded && self.jumpCooldown <= 0) {
// Look ahead to see if there's a platform to land on
var foundPlatformAhead = false;
var lookAheadX = self.x + (self.velocityX > 0 ? 150 : -150);
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 heightDiff = Math.abs(platform.y - self.y);
// Check if there's a platform ahead at similar height
if (lookAheadX >= platformLeft && lookAheadX <= platformRight && heightDiff < 200) {
foundPlatformAhead = true;
break;
}
}
// If no platform found ahead and we're moving, jump to try to reach something
if (!foundPlatformAhead && Math.abs(self.velocityX) > 2) {
self.jump();
self.jumpCooldown = 40;
}
}
// Handle damage cooldown and regeneration
if (self.damageCooldown > 0) {
self.damageCooldown--;
} else if (self.health < self.maxHealth) {
// Start regenerating when cooldown is over
if (!self.isRegenerating) {
self.isRegenerating = true;
}
// Regenerate health (60 FPS, so divide by 60 for per-second rate)
self.health += self.regenerationRate / 60;
if (self.health > self.maxHealth) {
self.health = self.maxHealth;
self.isRegenerating = false;
}
}
// Make AI decisions
self.makeDecision();
};
return self;
});
var Obstacle = Container.expand(function () {
var self = Container.call(this);
var obstacleGraphics = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 1.0
});
self.width = 30;
self.height = 60;
return self;
});
var Platform = Container.expand(function (width) {
var self = Container.call(this);
width = width || 300;
var platformGraphics = self.attachAsset('platform', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: width / 300
});
self.width = width;
self.height = 40;
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var assetToUse = selectedPlayerAsset || 'player';
var playerGraphics = self.attachAsset(assetToUse, {
anchorX: 0.5,
anchorY: 1.0
});
self.velocityY = 0;
self.velocityX = 0;
self.isGrounded = false;
self.jumpPower = -25;
self.gravity = 1.2;
self.speed = 8;
self.maxFallSpeed = 20;
self.isMoving = false;
self.idleAnimationTime = 0;
self.movementAnimationTime = 0;
self.jumpCount = 0;
self.maxJumps = 2;
self.moveLeft = function () {
self.velocityX = -self.speed;
};
self.moveRight = function () {
self.velocityX = self.speed;
};
self.jump = function () {
if (self.jumpCount < self.maxJumps) {
self.velocityY = self.jumpPower;
self.jumpCount++;
if (self.isGrounded) {
self.isGrounded = false;
}
LK.getSound('jump').play();
}
};
self.update = function () {
// Reset jump count when grounded (do this first)
if (self.isGrounded) {
self.jumpCount = 0;
}
// Apply gravity
if (!self.isGrounded) {
self.velocityY += self.gravity;
if (self.velocityY > self.maxFallSpeed) {
self.velocityY = self.maxFallSpeed;
}
}
// Apply horizontal movement with friction
self.x += self.velocityX;
self.velocityX *= 0.85;
// Apply vertical movement
self.y += self.velocityY;
// Check if player is moving horizontally
self.isMoving = Math.abs(self.velocityX) > 0.5;
// Update animations
if (self.isMoving) {
// Movement animation - slight bouncing effect
self.movementAnimationTime += 0.3;
var bounceOffset = Math.sin(self.movementAnimationTime) * 3;
playerGraphics.y = bounceOffset;
playerGraphics.scaleX = 1 + Math.sin(self.movementAnimationTime * 2) * 0.05;
playerGraphics.scaleY = 1 + Math.cos(self.movementAnimationTime * 2) * 0.05;
// Reset idle animation
self.idleAnimationTime = 0;
} else {
// Idle animation - gentle floating effect
self.idleAnimationTime += 0.1;
var floatOffset = Math.sin(self.idleAnimationTime) * 2;
playerGraphics.y = floatOffset;
playerGraphics.scaleX = 1 + Math.sin(self.idleAnimationTime * 0.5) * 0.02;
playerGraphics.scaleY = 1 + Math.cos(self.idleAnimationTime * 0.5) * 0.02;
// Reset movement animation
self.movementAnimationTime = 0;
}
// No horizontal boundaries - allow infinite movement for side-scrolling
// Check if fallen off screen
if (self.y > 2800) {
LK.showGameOver();
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87CEEB
});
/****
* Game Code
****/
// Set background to Cielo1 asset
var backgroundAsset = LK.getAsset('Cielo1', {
width: 2048,
height: 2732,
anchorX: 0,
anchorY: 0
});
backgroundAsset.x = 0;
backgroundAsset.y = 0;
game.addChildAt(backgroundAsset, 0); // Add as first child so it's behind everything
var gameState = 'start'; // 'start', 'playing', 'paused'
var startButton = null;
var player;
var platforms = [];
var obstacles = [];
var cameraOffset = 0;
var distanceScore = 0;
var lastPlatformX = 0;
var leftButtonPressed = false;
var rightButtonPressed = false;
var jumpButtonPressed = false;
var gameAttempts = 0;
var levelSeed = 0;
var goalReached = false;
var goalAsset = null;
var aiPlayer = null;
var aiCameraOffset = 0;
var aiDistanceScore = 0;
var aiGoalAsset = null;
var aiGoalReached = false;
var aiLastPlatformX = 0;
var aiPlayer2 = null;
var playerLives = 5;
var maxLives = 5;
var hearts = [];
var playerInvulnerable = false;
var invulnerabilityDuration = 120; // 2 seconds at 60 FPS
var invulnerabilityTimer = 0;
var infiniteMode = false;
var infiniteModeButton = null;
// Create score display
var scoreText = new Text2('Distance: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreText);
// Create control instruction text
var instructionText = new Text2('Use Buttons to Move and Jump', {
size: 40,
fill: 0xFFFFFF
});
instructionText.anchor.set(0.5, 1);
LK.gui.bottom.addChild(instructionText);
// Create start screen elements
var titleText = new Text2('Pixel Race', {
size: 120,
fill: 0xFFD700
});
titleText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(titleText);
// Create player selection buttons on the left side
var playerSelectionButtons = [];
var selectedPlayerAsset = 'player'; // Default player asset
// Button 1 - Player2 asset
var button1 = LK.getAsset('Player2', {
width: 120,
height: 120,
anchorX: 0.5,
anchorY: 0.5
});
var button1Text = new Text2('1', {
size: 50,
fill: 0xFFFFFF
});
button1Text.anchor.set(0.5, 0.5);
button1.addChild(button1Text);
button1.x = -400;
button1.y = -200;
button1.alpha = 0.8;
LK.gui.center.addChild(button1);
playerSelectionButtons.push(button1);
button1.down = function () {
selectedPlayerAsset = 'Player2';
updateButtonSelection(0);
};
// Button 2 - Player3 asset
var button2 = LK.getAsset('Player3', {
width: 120,
height: 120,
anchorX: 0.5,
anchorY: 0.5
});
var button2Text = new Text2('2', {
size: 50,
fill: 0xFFFFFF
});
button2Text.anchor.set(0.5, 0.5);
button2.addChild(button2Text);
button2.x = -400;
button2.y = -100;
button2.alpha = 0.8;
LK.gui.center.addChild(button2);
playerSelectionButtons.push(button2);
button2.down = function () {
selectedPlayerAsset = 'Player3';
updateButtonSelection(1);
};
// Button 3 - Player4 asset
var button3 = LK.getAsset('Player4', {
width: 120,
height: 120,
anchorX: 0.5,
anchorY: 0.5
});
var button3Text = new Text2('3', {
size: 50,
fill: 0xFFFFFF
});
button3Text.anchor.set(0.5, 0.5);
button3.addChild(button3Text);
button3.x = -400;
button3.y = 0;
button3.alpha = 0.8;
LK.gui.center.addChild(button3);
playerSelectionButtons.push(button3);
button3.down = function () {
selectedPlayerAsset = 'Player4';
updateButtonSelection(2);
};
// Button 4 - (no asset specified, using default player)
var button4 = LK.getAsset('player', {
width: 120,
height: 120,
anchorX: 0.5,
anchorY: 0.5
});
var button4Text = new Text2('4', {
size: 50,
fill: 0xFFFFFF
});
button4Text.anchor.set(0.5, 0.5);
button4.addChild(button4Text);
button4.x = -400;
button4.y = 100;
button4.alpha = 0.8;
LK.gui.center.addChild(button4);
playerSelectionButtons.push(button4);
button4.down = function () {
selectedPlayerAsset = 'player';
updateButtonSelection(3);
};
// Button 5 - Player5 asset
var button5 = LK.getAsset('Player5', {
width: 120,
height: 120,
anchorX: 0.5,
anchorY: 0.5
});
var button5Text = new Text2('5', {
size: 50,
fill: 0xFFFFFF
});
button5Text.anchor.set(0.5, 0.5);
button5.addChild(button5Text);
button5.x = -400;
button5.y = 200;
button5.alpha = 0.8;
LK.gui.center.addChild(button5);
playerSelectionButtons.push(button5);
button5.down = function () {
selectedPlayerAsset = 'Player5';
updateButtonSelection(4);
};
// Function to update button selection visual feedback
function updateButtonSelection(selectedIndex) {
for (var i = 0; i < playerSelectionButtons.length; i++) {
if (i === selectedIndex) {
playerSelectionButtons[i].alpha = 1.0;
playerSelectionButtons[i].scaleX = 1.2;
playerSelectionButtons[i].scaleY = 1.2;
} else {
playerSelectionButtons[i].alpha = 0.8;
playerSelectionButtons[i].scaleX = 1.0;
playerSelectionButtons[i].scaleY = 1.0;
}
}
}
// Set default selection
updateButtonSelection(4); // Default to button 5 (player asset)
var startButtonText = new Text2('START GAME', {
size: 80,
fill: 0xFFFFFF
});
startButtonText.anchor.set(0.5, 0.5);
// Create start button using Star1 asset as background
startButton = LK.getAsset('Star1', {
width: 300,
height: 120,
anchorX: 0.5,
anchorY: 0.5
});
startButton.tint = 0x32cd32;
startButton.alpha = 0.8;
LK.gui.center.addChild(startButton);
startButton.addChild(startButtonText);
startButton.y = 100;
// Hide game UI elements initially
// Create infinite mode button
var infiniteModeButtonText = new Text2('INFINITE MODE', {
size: 60,
fill: 0xFFFFFF
});
infiniteModeButtonText.anchor.set(0.5, 0.5);
infiniteModeButton = LK.getAsset('Star1', {
width: 280,
height: 100,
anchorX: 0.5,
anchorY: 0.5
});
infiniteModeButton.tint = 0xFF6600;
infiniteModeButton.alpha = 0.8;
LK.gui.center.addChild(infiniteModeButton);
infiniteModeButton.addChild(infiniteModeButtonText);
infiniteModeButton.y = 250;
scoreText.visible = false;
instructionText.visible = false;
// Create heart life display
function createHeartDisplay() {
// Clear existing hearts
for (var i = 0; i < hearts.length; i++) {
hearts[i].destroy();
}
hearts = [];
// Create new hearts based on current lives
for (var i = 0; i < maxLives; i++) {
var heart = LK.getAsset('corason1', {
width: 60,
height: 60,
anchorX: 0.5,
anchorY: 0.5
});
heart.x = 150 + i * 70; // Position hearts horizontally
heart.y = 80; // Position at top
heart.alpha = i < playerLives ? 1.0 : 0.3; // Full opacity for active lives, faded for lost lives
hearts.push(heart);
LK.gui.topLeft.addChild(heart);
}
}
// Initialize heart display
createHeartDisplay();
// Function to initialize the game
function initializeGame() {
// Initialize player
player = new Player();
player.x = 300;
player.y = 2000;
game.addChild(player);
// Initialize AI
aiPlayer = new AI();
aiPlayer.x = 200;
aiPlayer.y = 2000;
game.addChild(aiPlayer);
// Initialize AI2
aiPlayer2 = new AI2();
aiPlayer2.x = 150;
aiPlayer2.y = 2000;
game.addChild(aiPlayer2);
}
// Start button event handlers
startButton.down = function () {
startButton.alpha = 1.0;
startButton.scaleX = 0.95;
startButton.scaleY = 0.95;
};
startButton.up = function () {
startButton.alpha = 0.8;
startButton.scaleX = 1.0;
startButton.scaleY = 1.0;
// Start the game
gameState = 'playing';
// Hide start screen elements
titleText.visible = false;
startButton.visible = false;
infiniteModeButton.visible = false;
// Hide player selection buttons
for (var i = 0; i < playerSelectionButtons.length; i++) {
playerSelectionButtons[i].visible = false;
}
// Show game UI elements
scoreText.visible = true;
instructionText.visible = true;
// Initialize game objects
initializeGame();
};
// Infinite mode button event handlers
infiniteModeButton.down = function () {
infiniteModeButton.alpha = 1.0;
infiniteModeButton.scaleX = 0.95;
infiniteModeButton.scaleY = 0.95;
};
infiniteModeButton.up = function () {
infiniteModeButton.alpha = 0.8;
infiniteModeButton.scaleX = 1.0;
infiniteModeButton.scaleY = 1.0;
// Start infinite mode
infiniteMode = true;
gameState = 'playing';
// Hide start screen elements
titleText.visible = false;
startButton.visible = false;
infiniteModeButton.visible = false;
// Hide player selection buttons
for (var i = 0; i < playerSelectionButtons.length; i++) {
playerSelectionButtons[i].visible = false;
}
// Show game UI elements
scoreText.visible = true;
instructionText.visible = true;
// Initialize game objects without AI and Final1
initializeInfiniteGame();
};
// Function to initialize infinite mode game
function initializeInfiniteGame() {
// Initialize player only
player = new Player();
player.x = 300;
player.y = 2000;
game.addChild(player);
}
// Initialize AI-specific variables
aiCameraOffset = 0;
aiDistanceScore = 0;
aiLastPlatformX = 2200; // Same as initial lastPlatformX
// Create initial platforms
function createPlatform(x, y, width) {
var platform = new Platform(width);
platform.x = x;
platform.y = y;
platforms.push(platform);
game.addChild(platform);
return platform;
}
function createObstacle(x, y) {
var obstacle = new Obstacle();
obstacle.x = x;
obstacle.y = y;
obstacles.push(obstacle);
game.addChild(obstacle);
return obstacle;
}
// Generate initial level
createPlatform(200, 2100, 400); // Starting platform
createPlatform(600, 2000, 300);
createPlatform(1000, 1900, 250);
createPlatform(1400, 1800, 300);
createObstacle(1400, 1800);
createPlatform(1800, 1700, 200);
createPlatform(2200, 1600, 350);
lastPlatformX = 2200;
function generateLevel() {
// Remove platforms that are too far behind
for (var i = platforms.length - 1; i >= 0; i--) {
var platform = platforms[i];
if (platform.x < cameraOffset - 500) {
platform.destroy();
platforms.splice(i, 1);
}
}
// Remove obstacles that are too far behind
for (var i = obstacles.length - 1; i >= 0; i--) {
var obstacle = obstacles[i];
if (obstacle.x < cameraOffset - 500) {
obstacle.destroy();
obstacles.splice(i, 1);
}
}
// Generate new platforms ahead for player
while (lastPlatformX < cameraOffset + 3000) {
var lastPlatform = findLastPlatform();
var newPlatform = generateVerifiedPlatform(lastPlatform);
lastPlatformX = newPlatform.x;
createPlatform(newPlatform.x, newPlatform.y, newPlatform.width);
// Add obstacles with attempt-based variation
var obstacleChance = 0.2 + gameAttempts * 0.05; // Increase difficulty with attempts
if (obstacleChance > 0.5) obstacleChance = 0.5; // Cap at 50%
if (Math.random() < obstacleChance) {
createObstacle(newPlatform.x, newPlatform.y);
}
}
// Generate new platforms ahead for AI
while (aiLastPlatformX < aiCameraOffset + 3000) {
var aiLastPlatform = findLastPlatform();
var aiNewPlatform = generateVerifiedPlatform(aiLastPlatform);
aiLastPlatformX = aiNewPlatform.x;
createPlatform(aiNewPlatform.x, aiNewPlatform.y, aiNewPlatform.width);
// Add obstacles for AI path
if (Math.random() < 0.3) {
createObstacle(aiNewPlatform.x, aiNewPlatform.y);
}
}
}
function findLastPlatform() {
var lastPlatform = null;
var maxX = -999999;
for (var i = 0; i < platforms.length; i++) {
if (platforms[i].x > maxX) {
maxX = platforms[i].x;
lastPlatform = platforms[i];
}
}
return lastPlatform;
}
function generateVerifiedPlatform(lastPlatform) {
var maxJumpDistance = 320; // Maximum horizontal jump distance
var maxJumpHeight = 350; // Maximum jump height (going up)
var maxFallHeight = 600; // Maximum fall height (going down)
// Attempt-based level variation
var difficultyMultiplier = 1 + gameAttempts * 0.1;
if (difficultyMultiplier > 2) difficultyMultiplier = 2; // Cap difficulty
var attemptSeed = gameAttempts * 12345 % 100000;
var pseudoRandom = (attemptSeed + lastPlatformX * 7) % 1000 / 1000;
var gapDistance = 150 + pseudoRandom * 200 * difficultyMultiplier;
var platformWidth = 150 + pseudoRandom * 150;
var heightVariation = -150 + pseudoRandom * 300;
// Ensure gap is not too large
if (gapDistance > maxJumpDistance) {
gapDistance = maxJumpDistance - 20;
}
var newX = lastPlatform.x + gapDistance;
var newY = lastPlatform.y + heightVariation;
// Verify vertical reachability
var heightDifference = newY - lastPlatform.y;
if (heightDifference > maxFallHeight) {
newY = lastPlatform.y + maxFallHeight;
} else if (heightDifference < -maxJumpHeight) {
newY = lastPlatform.y - maxJumpHeight;
}
// Keep platforms within screen bounds - terrain cannot go above middle of screen
if (newY > 2200) newY = 2200;
if (newY < 1366) newY = 1366; // Middle of screen (2732/2 = 1366) - platforms cannot generate above this
// Verify horizontal reachability with physics simulation
if (!isPlatformReachable(lastPlatform, {
x: newX,
y: newY,
width: platformWidth
})) {
// Adjust platform to make it reachable
gapDistance = maxJumpDistance - 50;
newX = lastPlatform.x + gapDistance;
// Also adjust height to be more forgiving
if (heightDifference > 0) {
newY = lastPlatform.y + Math.min(heightDifference, 200);
} else {
newY = lastPlatform.y + Math.max(heightDifference, -250);
}
}
return {
x: newX,
y: newY,
width: platformWidth
};
}
function isPlatformReachable(fromPlatform, toPlatform) {
// Simulate a jump from the edge of fromPlatform to toPlatform
var startX = fromPlatform.x + fromPlatform.width / 2;
var startY = fromPlatform.y;
var targetX = toPlatform.x;
var targetY = toPlatform.y;
var targetLeft = targetX - toPlatform.width / 2;
var targetRight = targetX + toPlatform.width / 2;
// Simulate jump physics
var jumpPower = -25;
var gravity = 1.2;
var speed = 8;
var maxSimulationTime = 100; // Prevent infinite loops
var simX = startX;
var simY = startY;
var velocityY = jumpPower;
var velocityX = speed;
for (var t = 0; t < maxSimulationTime; t++) {
simX += velocityX;
simY += velocityY;
velocityY += gravity;
// Check if we've reached the target platform level
if (simY >= targetY && simX >= targetLeft && simX <= targetRight) {
return true;
}
// If we've fallen too far below the target, we can't reach it
if (simY > targetY + 100) {
break;
}
}
return false;
}
function checkCollisions() {
player.isGrounded = false;
// Check platform collisions
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
var playerBounds = {
left: player.x - 30,
right: player.x + 30,
top: player.y - 60,
bottom: player.y
};
var platformBounds = {
left: platform.x - platform.width / 2,
right: platform.x + platform.width / 2,
top: platform.y - 20,
bottom: platform.y + 20
};
// Check if player overlaps with platform
if (playerBounds.right > platformBounds.left && playerBounds.left < platformBounds.right && playerBounds.bottom > platformBounds.top && playerBounds.top < platformBounds.bottom) {
// Landing on top of platform
if (player.velocityY >= 0 && playerBounds.bottom - platformBounds.top < 20) {
player.y = platformBounds.top;
player.velocityY = 0;
player.isGrounded = true;
player.jumpCount = 0; // Reset jump count immediately when landing
}
}
}
// Check obstacle collisions - only if player is not invulnerable
for (var i = 0; i < obstacles.length; i++) {
var obstacle = obstacles[i];
if (player.intersects(obstacle) && !playerInvulnerable) {
// Player loses one life
playerLives--;
// Start invulnerability period
playerInvulnerable = true;
invulnerabilityTimer = invulnerabilityDuration;
// Flash player red when taking damage
LK.effects.flashObject(player, 0xff0000, 500);
LK.effects.flashScreen(0xff0000, 500);
// Create blinking effect during invulnerability using tween
tween(player, {
alpha: 0.3
}, {
duration: 200,
onFinish: function onFinish() {
tween(player, {
alpha: 1.0
}, {
duration: 200
});
}
});
// Update heart display
createHeartDisplay();
// Check if player is out of lives
if (playerLives <= 0) {
LK.showGameOver();
return;
} else {
// Push player away from obstacle to prevent multiple hits
if (obstacle.x > player.x) {
player.x -= 50; // Push left
} else {
player.x += 50; // Push right
}
player.velocityX = 0; // Stop horizontal movement
}
}
}
// Final1 asset is unreachable - no collision detection needed
}
function updateCamera() {
// Camera follows player with some offset
var targetCameraX = player.x - 400;
cameraOffset += (targetCameraX - cameraOffset) * 0.1;
// Move all game objects relative to camera
game.x = -cameraOffset;
// Make background follow player movement in opposite direction for proper parallax effect
if (backgroundAsset) {
backgroundAsset.x = cameraOffset; // Match exact player movement speed
}
// Update distance score
distanceScore = Math.floor(cameraOffset / 10);
scoreText.setText('Distance: ' + distanceScore);
LK.setScore(distanceScore);
// Create Final1 asset that moves dynamically (skip in infinite mode)
if (!goalAsset && !infiniteMode) {
goalAsset = LK.getAsset('Final1', {
width: 100,
height: 100,
anchorX: 0.5,
anchorY: 1.0
});
goalAsset.moveSpeed = 8; // Much faster movement speed for Final1
goalAsset.moveDirection = 1; // 1 for right, -1 for left
goalAsset.baseY = 800; // Base Y position
goalAsset.floatTime = 0; // For floating animation
game.addChild(goalAsset);
}
// Handle goal asset logic for both modes
if (goalAsset) {
// Move goal asset (Final1 or Moneda3) with floating motion
goalAsset.moveSpeed = goalAsset.moveSpeed || 8;
goalAsset.moveDirection = goalAsset.moveDirection || 1;
goalAsset.baseY = goalAsset.baseY || 800;
goalAsset.floatTime = goalAsset.floatTime || 0;
// Move horizontally only to the right, but stop when goal is reached
if (!goalReached) {
goalAsset.x += goalAsset.moveSpeed;
// Stop at right boundary but don't reverse direction
var screenRight = cameraOffset + 1800;
if (goalAsset.x >= screenRight) {
goalAsset.x = screenRight; // Keep at right boundary
}
}
// Floating vertical motion - only if animation is not deactivated
if (!goalAsset.animationDeactivated) {
goalAsset.floatTime += 0.02;
goalAsset.y = goalAsset.baseY + Math.sin(goalAsset.floatTime) * 50;
} else {
// Keep goal asset fixed at base Y position when animation is deactivated
goalAsset.y = goalAsset.baseY;
}
// Distance-based reachability system
var playerDistance = Math.abs(goalAsset.x - player.x);
var aiDistance = Math.abs(goalAsset.x - (aiPlayer ? aiPlayer.x : 9999));
// Check if goal asset has reached target distance (15000 for normal mode only)
var finalAssetDistance = goalAsset.x - 300; // Subtract initial player position
var targetDistance = 15000;
if (finalAssetDistance >= targetDistance && !goalReached) {
goalReached = true;
// Find nearest platform and position goal asset on it
var nearestPlatform = null;
var shortestDistance = 999999;
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
var distance = Math.abs(platform.x - goalAsset.x);
if (distance < shortestDistance) {
shortestDistance = distance;
nearestPlatform = platform;
}
}
// Position goal asset on the nearest platform
if (nearestPlatform) {
goalAsset.x = nearestPlatform.x;
goalAsset.y = nearestPlatform.y - 20; // Position slightly above platform
goalAsset.baseY = nearestPlatform.y - 20; // Update base Y for floating
// Deactivate floating animation by setting a flag
goalAsset.animationDeactivated = true;
}
LK.effects.flashScreen(0xFFD700, 2000); // Flash gold when Final1 becomes reachable
}
// Check if player touches goal asset when it's reachable
if (goalReached && playerDistance < 100) {
LK.effects.flashScreen(0x00FF00, 1500); // Green flash for player success
LK.showYouWin();
}
}
}
function updateAICamera() {
// AI camera follows AI player
if (!aiPlayer) return; // Skip if AI player doesn't exist (infinite mode)
var aiTargetCameraX = aiPlayer.x - 400;
aiCameraOffset += (aiTargetCameraX - aiCameraOffset) * 0.1;
// Update AI distance score
aiDistanceScore = Math.floor(aiCameraOffset / 10);
// AI uses the same moving Final1 asset as the player's goal
if (!aiGoalAsset && goalAsset) {
aiGoalAsset = goalAsset; // Share the same moving Final1 asset
}
}
// Create control buttons
var leftButton = LK.getAsset('Izquierda1', {
width: 150,
height: 100,
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
leftButton.alpha = 0.7;
var rightButton = LK.getAsset('Derecha2', {
width: 150,
height: 100,
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
rightButton.alpha = 0.7;
var jumpButton = LK.getAsset('Salto1', {
width: 120,
height: 120,
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.2,
scaleY: 2.2
});
jumpButton.tint = 0x32cd32;
jumpButton.alpha = 0.8;
// Position buttons in GUI - left and right buttons together in bottom left
LK.gui.bottomLeft.addChild(leftButton);
leftButton.x = 80;
leftButton.y = -100;
LK.gui.bottomLeft.addChild(rightButton);
rightButton.x = 300;
rightButton.y = -100;
LK.gui.bottomRight.addChild(jumpButton);
jumpButton.x = -130;
jumpButton.y = -100;
// Button event handlers
leftButton.down = function () {
leftButton.alpha = 1.0;
leftButtonPressed = true;
};
leftButton.up = function () {
leftButton.alpha = 0.7;
leftButtonPressed = false;
};
rightButton.down = function () {
rightButton.alpha = 1.0;
rightButtonPressed = true;
};
rightButton.up = function () {
rightButton.alpha = 0.7;
rightButtonPressed = false;
};
jumpButton.down = function () {
jumpButton.alpha = 1.0;
jumpButton.scaleX = 1.3;
jumpButton.scaleY = 1.3;
jumpButtonPressed = true;
if (player && gameState === 'playing') {
player.jump();
}
};
jumpButton.up = function () {
jumpButton.alpha = 0.8;
jumpButton.scaleX = 1.5;
jumpButton.scaleY = 1.5;
jumpButtonPressed = false;
};
// Track when game starts/resets
var originalShowGameOver = LK.showGameOver;
LK.showGameOver = function () {
gameAttempts++;
levelSeed = gameAttempts * 54321; // Change seed each attempt
// Reset player lives when game restarts
playerLives = maxLives;
// Reset invulnerability state
playerInvulnerable = false;
invulnerabilityTimer = 0;
createHeartDisplay();
// Reset game state to start screen
gameState = 'start';
// Show start screen elements
titleText.visible = true;
startButton.visible = true;
infiniteModeButton.visible = true;
// Show player selection buttons
for (var i = 0; i < playerSelectionButtons.length; i++) {
playerSelectionButtons[i].visible = true;
}
// Hide game UI elements
scoreText.visible = false;
instructionText.visible = false;
// Clear game objects
if (player) {
player.destroy();
player = null;
}
if (aiPlayer) {
aiPlayer.destroy();
aiPlayer = null;
}
if (aiPlayer2) {
aiPlayer2.destroy();
aiPlayer2 = null;
}
if (goalAsset) {
goalAsset.destroy();
goalAsset = null;
}
// Clear platforms and obstacles
for (var i = 0; i < platforms.length; i++) {
platforms[i].destroy();
}
platforms = [];
for (var i = 0; i < obstacles.length; i++) {
obstacles[i].destroy();
}
obstacles = [];
// Reset game variables
cameraOffset = 0;
distanceScore = 0;
lastPlatformX = 0;
goalReached = false;
aiCameraOffset = 0;
aiDistanceScore = 0;
aiGoalAsset = null;
aiGoalReached = false;
aiLastPlatformX = 0;
infiniteMode = false;
originalShowGameOver();
};
game.update = function () {
// Only update game when in playing state
if (gameState !== 'playing') {
return;
}
// Handle invulnerability timer
if (playerInvulnerable) {
invulnerabilityTimer--;
if (invulnerabilityTimer <= 0) {
playerInvulnerable = false;
// Ensure player is fully visible when invulnerability ends
tween.stop(player, {
alpha: true
});
player.alpha = 1.0;
}
}
// Handle continuous movement based on button states
if (leftButtonPressed) {
player.moveLeft();
}
if (rightButtonPressed) {
player.moveRight();
}
// Handle continuous jump (allows jump when held down and landing)
if (jumpButtonPressed) {
player.jump();
}
// Update AI if it exists (skip in infinite mode)
if (aiPlayer && !infiniteMode) {
aiPlayer.update();
// Check AI collisions with platforms
aiPlayer.isGrounded = false;
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
var aiBounds = {
left: aiPlayer.x - 50,
right: aiPlayer.x + 50,
top: aiPlayer.y - 100,
bottom: aiPlayer.y
};
var platformBounds = {
left: platform.x - platform.width / 2,
right: platform.x + platform.width / 2,
top: platform.y - 20,
bottom: platform.y + 20
};
// Check if AI overlaps with platform
if (aiBounds.right > platformBounds.left && aiBounds.left < platformBounds.right && aiBounds.bottom > platformBounds.top && aiBounds.top < platformBounds.bottom) {
// Landing on top of platform
if (aiPlayer.velocityY >= 0 && aiBounds.bottom - platformBounds.top < 40) {
aiPlayer.y = platformBounds.top;
aiPlayer.velocityY = 0;
aiPlayer.isGrounded = true;
aiPlayer.jumpCount = 0; // Reset jump count immediately when landing
}
}
}
// Check AI obstacle collisions
for (var i = 0; i < obstacles.length; i++) {
var obstacle = obstacles[i];
if (aiPlayer.intersects(obstacle)) {
// AI takes damage from obstacle and handles it independently
aiPlayer.takeDamage(25); // AI takes damage instead of affecting player
}
}
}
// Update AI2 if it exists (skip in infinite mode)
if (aiPlayer2 && !infiniteMode) {
aiPlayer2.update();
// Check AI2 collisions with platforms
aiPlayer2.isGrounded = false;
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
var ai2Bounds = {
left: aiPlayer2.x - 50,
right: aiPlayer2.x + 50,
top: aiPlayer2.y - 100,
bottom: aiPlayer2.y
};
var platformBounds = {
left: platform.x - platform.width / 2,
right: platform.x + platform.width / 2,
top: platform.y - 20,
bottom: platform.y + 20
};
// Check if AI2 overlaps with platform
if (ai2Bounds.right > platformBounds.left && ai2Bounds.left < platformBounds.right && ai2Bounds.bottom > platformBounds.top && ai2Bounds.top < platformBounds.bottom) {
// Landing on top of platform
if (aiPlayer2.velocityY >= 0 && ai2Bounds.bottom - platformBounds.top < 40) {
aiPlayer2.y = platformBounds.top;
aiPlayer2.velocityY = 0;
aiPlayer2.isGrounded = true;
aiPlayer2.jumpCount = 0; // Reset jump count immediately when landing
}
}
}
// Check AI2 obstacle collisions
for (var i = 0; i < obstacles.length; i++) {
var obstacle = obstacles[i];
if (aiPlayer2.intersects(obstacle)) {
// AI2 takes damage from obstacle and handles it independently
aiPlayer2.takeDamage(25); // AI2 takes damage instead of affecting player
}
}
// Check if player is falling into void (no platform below for a significant distance)
if (player.y > 2400 && !player.isGrounded) {
// Start checking earlier than complete fall off
var foundPlatformBelow = false;
// Look for any platform below the player within a reasonable distance
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
var horizontalDistance = Math.abs(platform.x - player.x);
// Check if there's a platform below within reasonable horizontal distance
if (platform.y > player.y && platform.y < player.y + 400 && horizontalDistance < 200) {
foundPlatformBelow = true;
break;
}
}
// If no platform found below and player is falling fast, respawn with 2 life loss
if (!foundPlatformBelow || player.velocityY > 15) {
// Player loses 2 lives when falling into void
playerLives -= 2;
if (playerLives < 0) playerLives = 0;
// Flash effects for void fall
LK.effects.flashObject(player, 0xff0000, 800);
LK.effects.flashScreen(0xff0000, 800);
// Update heart display
createHeartDisplay();
// Check if player is out of lives
if (playerLives <= 0) {
LK.showGameOver();
return;
}
// Find platform closest to Final1 asset and respawn there
if (goalAsset && platforms.length > 0) {
var closestPlatform = null;
var shortestDistance = 999999;
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
var distance = Math.abs(platform.x - goalAsset.x);
if (distance < shortestDistance) {
shortestDistance = distance;
closestPlatform = platform;
}
}
// Position player on the closest platform to Final1
if (closestPlatform) {
player.x = closestPlatform.x;
player.y = closestPlatform.y - 20; // Position slightly above platform
} else {
// Fallback to starting position if no platform found
player.x = 300;
player.y = 2000;
}
} else {
// Fallback to starting position if no Final1 asset exists
player.x = 300;
player.y = 2000;
}
player.velocityX = 0;
player.velocityY = 0;
player.isGrounded = false;
// Start invulnerability period after respawn
playerInvulnerable = true;
invulnerabilityTimer = invulnerabilityDuration;
// Create blinking effect during invulnerability using tween
tween(player, {
alpha: 0.3
}, {
duration: 200,
onFinish: function onFinish() {
tween(player, {
alpha: 1.0
}, {
duration: 200
});
}
});
}
}
// Check if AI is falling into void (no platform below for a significant distance)
if (aiPlayer.y > 2400 && !aiPlayer.isGrounded) {
// Start checking earlier than complete fall off
var foundPlatformBelow = false;
// Look for any platform below the AI within a reasonable distance
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 horizontalDistance = Math.abs(platform.x - aiPlayer.x);
// Check if there's a platform below within reasonable horizontal distance
if (platform.y > aiPlayer.y && platform.y < aiPlayer.y + 400 && horizontalDistance < 200) {
foundPlatformBelow = true;
break;
}
}
// If no platform found below and AI is falling fast, respawn
if (!foundPlatformBelow || aiPlayer.velocityY > 15) {
// Find platform closest to Final1 asset and respawn there
if (goalAsset && platforms.length > 0) {
var closestPlatform = null;
var shortestDistance = 999999;
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
var distance = Math.abs(platform.x - goalAsset.x);
if (distance < shortestDistance) {
shortestDistance = distance;
closestPlatform = platform;
}
}
// Position AI on the closest platform to Final1
if (closestPlatform) {
aiPlayer.x = closestPlatform.x;
aiPlayer.y = closestPlatform.y - 20; // Position slightly above platform
} else {
// Fallback to player position if no platform found
aiPlayer.x = player.x;
aiPlayer.y = player.y;
}
} else {
// Fallback to player position if no Final1 asset exists
aiPlayer.x = player.x;
aiPlayer.y = player.y;
}
aiPlayer.velocityX = 0;
aiPlayer.velocityY = 0;
aiPlayer.isGrounded = false;
LK.effects.flashObject(aiPlayer, 0x00ff00, 1000); // Green flash for regeneration
}
}
// Check if AI2 is falling into void (no platform below for a significant distance)
if (aiPlayer2.y > 2400 && !aiPlayer2.isGrounded) {
// Start checking earlier than complete fall off
var foundPlatformBelow = false;
// Look for any platform below the AI2 within a reasonable distance
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 horizontalDistance = Math.abs(platform.x - aiPlayer2.x);
// Check if there's a platform below within reasonable horizontal distance
if (platform.y > aiPlayer2.y && platform.y < aiPlayer2.y + 400 && horizontalDistance < 200) {
foundPlatformBelow = true;
break;
}
}
// If no platform found below and AI2 is falling fast, respawn
if (!foundPlatformBelow || aiPlayer2.velocityY > 15) {
// Find platform closest to Final1 asset and respawn there
if (goalAsset && platforms.length > 0) {
var closestPlatform = null;
var shortestDistance = 999999;
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
var distance = Math.abs(platform.x - goalAsset.x);
if (distance < shortestDistance) {
shortestDistance = distance;
closestPlatform = platform;
}
}
// Position AI2 on the closest platform to Final1
if (closestPlatform) {
aiPlayer2.x = closestPlatform.x;
aiPlayer2.y = closestPlatform.y - 20; // Position slightly above platform
} else {
// Fallback to player position if no platform found
aiPlayer2.x = player.x;
aiPlayer2.y = player.y;
}
} else {
// Fallback to player position if no Final1 asset exists
aiPlayer2.x = player.x;
aiPlayer2.y = player.y;
}
aiPlayer2.velocityX = 0;
aiPlayer2.velocityY = 0;
aiPlayer2.isGrounded = false;
LK.effects.flashObject(aiPlayer2, 0x00ff00, 1000); // Green flash for regeneration
}
}
// Prevent AI from falling off screen (backup safety check)
if (aiPlayer.y > 2800) {
aiPlayer.y = 2000;
aiPlayer.x = 200; // Reset to independent starting position
aiPlayer.velocityY = 0;
aiPlayer.velocityX = 0;
}
// Prevent AI2 from falling off screen (backup safety check)
if (aiPlayer2.y > 2800) {
aiPlayer2.y = 2000;
aiPlayer2.x = 150; // Reset to independent starting position
aiPlayer2.velocityY = 0;
aiPlayer2.velocityX = 0;
}
// AI goal stays fixed at right center of screen - no movement needed
}
checkCollisions();
updateCamera();
updateAICamera();
generateLevel();
};
Puedes Aser una persona palito de color negra que este pixelada. In-Game asset
Plataforma flotante con pasto ensima y tierra debajo pixelada. In-Game asset. 2d. High contrast. No shadows
Cuadro gris con bordes negros que dentro tenga una flecha apuntando a la derecha pixelada. In-Game asset. No shadows
Personaje que sea un alpinista pixelado con una gran mochila que este en movimiento caminando In-Game asset. 2d. High contrast. No shadows
Bandera de meta en un gran poste Pixelado. In-Game asset. 2d. High contrast. No shadows
As un corazón pixelado. In-Game asset. High contrast. No shadows
Rectángulo gris con bordes negros que en el sentro tenga la palabra Star pixelada. In-Game asset. High contrast. No shadows
Cielo de fondo pixelado con un sol y nubes. No shadows
As un alpinista caminando que sea una persona real. In-Game asset. 2d. High contrast. No shadows