User prompt
Okey quiero que arregles que el texto de arriba de los diálogos se ponga por encima de la barra de ansiedad y credibilidad
User prompt
Así está bien
User prompt
Puedes quitar el cuadro de texto que sale aveces? no me gusta eso
User prompt
Bueno arreglalo jajaj
User prompt
Vuelve a intentar
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of undefined (reading 'parent')' in or related to this line: 'if (!ansiedadBarBg.parent) {' Line Number: 317
User prompt
bien quiero que los ataques sean un poco más rápidos. Te voy a tratar de comunicar toda la idea de una vez. Quiero que el juego tenga 5 niveles y quiero que sea un estilo narrativo que salgan dialogos de texto antes de empezar el nivel en parte superior de la pantalla, que vaya contanto la historia de un tutor que va avanzando en sus tutorias donde a medida que avanza con los niveles sea más dificiles de esquivar los ataques ↪💡 Consider importing and using the following plugins: @upit/tween.v1, @upit/storage.v1
User prompt
Perfecto, te decia que quiero que los ataques tengan unos bordes bien definidos, en este caso no lo tienen aún, tambien quiero haya palabras que vengan hacia el jugador pero en color verde y que suban la barra de credibilidad
User prompt
Try again
User prompt
You are UPIT’s AI assistant. I have an existing prototype for a 2D “Undertale‐style” tutor training game in JavaScript using the LK framework. Please refactor and adapt the code to focus exclusively on: 1. **Core Classes** - Keep only the `Heart`, `TextAttack`, and `TechniqueMenu` classes. - Remove all other attack/enemy classes (`CyanAreaAttack`, `LaserAttack`, `Enemy`) and their assets. 2. **Define Constants** - Extract all “magic numbers” into descriptive constants at the top of the file, for example: ```js const ATTACK_INTERVAL = 90; const LEVEL_DIFF = 5; const MENU_INTERVAL = 600; const ANSIEDAD_RATE = 180; const ANSIEDAD_PASSIVE = 2; const MAX_LEVEL = 10; const WIN_CRED = 60; const SCORE_RATE = 60; const SCORE_PER_LEVEL = 50; const LEVEL_CRED_BOOST = 5; const GAME_LEFT = 300; const GAME_RIGHT = 1748; const GAME_TOP = 200; const GAME_BOTTOM = 2732; const GAME_WIDTH = GAME_RIGHT - GAME_LEFT; ``` 3. **UI Initialization** - Retain the UI setup for `ansiedad`, `credibilidad`, level text, and implement `initializeUI()` and `updateBars()` functions as before. 4. **Keyboard Controls** - Add `window.addEventListener('keydown', ...)` and `keyup` handlers for `ArrowLeft` and `ArrowRight` to control the heart movement, in addition to touch input. 5. **Simplify Game Update Loop** - Replace the current multi-attack logic with only `TextAttack` spawning and collision detection: - Spawn a `TextAttack` every `ATTACK_INTERVAL - level * LEVEL_DIFF` ticks. - Move and destroy off-screen attacks. - Detect `heart.intersects(atk)` to call `heart.takeDamage()`. - Show the `TechniqueMenu` every `MENU_INTERVAL` ticks. - Increase `ansiedad` passively every `ANSIEDAD_RATE` ticks by `ANSIEDAD_PASSIVE`. - Update score every `SCORE_RATE` ticks; increment `level` when score ≥ `level * SCORE_PER_LEVEL`, then boost `credibilidad` by `LEVEL_CRED_BOOST`. - Check for game over when `ansiedad >= 100` and win when `level >= MAX_LEVEL && credibilidad >= WIN_CRED`. 6. **Modularize (Optional)** - If possible, separate stats management and attack spawning into `StatsManager` and `AttackSpawner` modules. Provide the complete refactored JavaScript code snippet using the LK framework, with comments explaining each change. All in‐game text and UI labels should remain in Spanish.``` ::contentReference[oaicite:0]{index=0}
Remix started
Copy Undertale
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Character = Container.expand(function () {
var self = Container.call(this);
var characterGraphics = self.attachAsset('character', {
anchorX: 0.5,
anchorY: 0.5
});
// Target position for smooth movement
self.targetX = 0;
self.targetY = 0;
// Movement speed factor for smooth following
self.followSpeed = 0.15;
// Square boundary limits (centered square area)
var squareSize = 1200;
var centerX = 2048 / 2;
var centerY = 2732 / 2;
self.boundaryLeft = centerX - squareSize / 2;
self.boundaryRight = centerX + squareSize / 2;
self.boundaryTop = centerY - squareSize / 2;
self.boundaryBottom = centerY + squareSize / 2;
var characterRadius = 60;
self.update = function () {
// Calculate next position
var deltaX = self.targetX - self.x;
var deltaY = self.targetY - self.y;
var nextX = self.x + deltaX * self.followSpeed;
var nextY = self.y + deltaY * self.followSpeed;
// Check wall collisions and prevent movement beyond boundaries
if (nextX - characterRadius < self.boundaryLeft) {
nextX = self.boundaryLeft + characterRadius;
}
if (nextX + characterRadius > self.boundaryRight) {
nextX = self.boundaryRight - characterRadius;
}
if (nextY - characterRadius < self.boundaryTop) {
nextY = self.boundaryTop + characterRadius;
}
if (nextY + characterRadius > self.boundaryBottom) {
nextY = self.boundaryBottom - characterRadius;
}
// Apply the constrained position
self.x = nextX;
self.y = nextY;
};
// Method to set target position
self.setTarget = function (x, y) {
self.targetX = x;
self.targetY = y;
};
return self;
});
var CyanAreaAttack = Container.expand(function () {
var self = Container.call(this);
var attackGraphics = self.attachAsset('cyanAreaAttack', {
anchorX: 0,
anchorY: 0.5
});
self.duration = 0;
self.maxDuration = 3000; // 3 seconds
self.isActive = false;
self.startX = -600; // Start from left side of combat box
self.endX = 1200; // End at right side of combat box
self.speed = 5; // Movement speed from left to right
// Start with low alpha and fade in
attackGraphics.alpha = 0;
// Position at left edge initially
self.x = self.startX;
self.update = function () {
self.duration += 16.67;
// Move from left to right across combat box
if (self.duration > 500 && self.x < self.endX + 600) {
// Move until completely off screen
self.x += self.speed;
}
// Fade in effect at the beginning
if (self.duration < 500) {
attackGraphics.alpha = self.duration / 500 * 0.6; // Max alpha of 0.6
} else if (self.x < self.endX + 600) {
// Stay visible and active while crossing combat box
attackGraphics.alpha = 0.6;
self.isActive = true;
} else {
// Mark as finished when completely crossed
self.isActive = false;
attackGraphics.alpha = 0;
}
};
return self;
});
var Enemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8;
self.targetX = 0;
self.targetY = 0;
self.update = function () {
// Move toward target (player)
var deltaX = self.targetX - self.x;
var deltaY = self.targetY - self.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance > 0) {
self.x += deltaX / distance * self.speed;
self.y += deltaY / distance * self.speed;
}
// Add rotation effect (visual only, doesn't affect movement)
enemyGraphics.rotation += 0.1;
};
self.setTarget = function (x, y) {
self.targetX = x;
self.targetY = y;
};
return self;
});
var LaserAttack = Container.expand(function () {
var self = Container.call(this);
var chargerGraphics = self.attachAsset('laserCharger', {
anchorX: 0.5,
anchorY: 0.5
});
self.targetX = 0;
self.targetY = 0;
self.state = 'charging'; // 'charging', 'firing', 'finished'
self.chargeTime = 0;
self.fireTime = 0;
self.laserBeam = null;
self.hasDealtDamage = false;
self.update = function () {
if (self.state === 'charging') {
self.chargeTime += 16.67;
// Charging animation - pulsing effect
chargerGraphics.alpha = 0.5 + 0.5 * Math.sin(self.chargeTime * 0.01);
if (self.chargeTime >= 1200) {
// 1.2 seconds
self.state = 'firing';
self.fireLaser();
}
} else if (self.state === 'firing') {
self.fireTime += 16.67;
if (self.fireTime >= 3000) {
// 3 seconds
self.state = 'finished';
}
}
};
self.fireLaser = function () {
// Hide charger
chargerGraphics.alpha = 0;
// Create laser beam
self.laserBeam = self.addChild(LK.getAsset('laserAttack', {
anchorX: 0,
anchorY: 0.5
}));
// Position laser to fire across the screen toward target
var angle = Math.atan2(self.targetY - self.y, self.targetX - self.x);
self.laserBeam.rotation = angle;
self.laserBeam.x = 0;
self.laserBeam.y = 0;
};
self.setTarget = function (x, y) {
self.targetX = x;
self.targetY = y;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Game state management
var gameState = 'menu'; // 'menu' or 'playing'
var menuContainer = game.addChild(new Container());
// Create menu elements
var logo = menuContainer.addChild(LK.getAsset('breathingSprite3', {
anchorX: 0.5,
anchorY: 0.5
}));
logo.x = 2048 / 2;
logo.y = 2732 / 2 - 250;
// Create play button
var playButton = menuContainer.addChild(LK.getAsset('playButton', {
anchorX: 0.5,
anchorY: 0.5
}));
playButton.x = 2048 / 2;
playButton.y = 2732 / 2 + 100;
// Play button text
var playText = menuContainer.addChild(new Text2('JUGAR', {
size: 70,
fill: 0xFFFFFF
}));
playText.anchor.set(0.5, 0.5);
playText.x = playButton.x;
playText.y = playButton.y;
// Play button interaction
playButton.down = function (x, y, obj) {
// Flash button on press
tween(playButton, {
tint: 0xffffff
}, {
duration: 100,
onFinish: function onFinish() {
tween(playButton, {
tint: 0xffffff
}, {
duration: 100
});
}
});
};
playButton.up = function (x, y, obj) {
// Start the game
gameState = 'playing';
menuContainer.visible = false;
// Show game elements
leftWall.visible = true;
rightWall.visible = true;
topWall.visible = true;
bottomWall.visible = true;
character.visible = true;
healthBarBackground.visible = true;
healthLabel.visible = true;
breathingSprite1.visible = true;
breathingSprite2.visible = true;
for (var i = 0; i < healthSegments.length; i++) {
healthSegments[i].visible = true;
}
};
// Create visual boundary walls
var squareSize = 1200;
var centerX = 2048 / 2;
var centerY = 2732 / 2;
var boundaryLeft = centerX - squareSize / 2;
var boundaryRight = centerX + squareSize / 2;
var boundaryTop = centerY - squareSize / 2;
var boundaryBottom = centerY + squareSize / 2;
// Left wall
var leftWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5
}));
leftWall.x = boundaryLeft;
leftWall.y = centerY;
leftWall.visible = false;
// Right wall
var rightWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5
}));
rightWall.x = boundaryRight;
rightWall.y = centerY;
rightWall.visible = false;
// Top wall
var topWall = game.addChild(LK.getAsset('wallHorizontal', {
anchorX: 0.5,
anchorY: 0.5
}));
topWall.x = centerX;
topWall.y = boundaryTop;
topWall.visible = false;
// Bottom wall
var bottomWall = game.addChild(LK.getAsset('wallHorizontal', {
anchorX: 0.5,
anchorY: 0.5
}));
bottomWall.x = centerX;
bottomWall.y = boundaryBottom;
bottomWall.visible = false;
// Create the character at center of screen
var character = game.addChild(new Character());
character.x = 2048 / 2;
character.y = 2732 / 2;
character.visible = false;
// Set initial target position to character's starting position
character.setTarget(character.x, character.y);
// Handle mouse/touch movement
game.move = function (x, y, obj) {
if (gameState === 'playing') {
// Set character target to mouse position
character.setTarget(x, y);
}
};
// Handle mouse/touch down - also update position immediately
game.down = function (x, y, obj) {
if (gameState === 'playing') {
// Set character target to touch/click position
character.setTarget(x, y);
}
};
// Create health bar interface below the square
var healthBarY = boundaryBottom + 100;
// Health bar background
var healthBarBackground = game.addChild(LK.getAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5
}));
healthBarBackground.x = centerX;
healthBarBackground.y = healthBarY;
healthBarBackground.visible = false;
// Health bar segments (10 parts)
var healthSegments = [];
var segmentWidth = 36;
var segmentSpacing = 2;
var totalWidth = (segmentWidth + segmentSpacing) * 10 - segmentSpacing;
var startX = centerX - totalWidth / 2;
for (var i = 0; i < 10; i++) {
var segment = game.addChild(LK.getAsset('healthSegment', {
anchorX: 0,
anchorY: 0.5
}));
segment.x = startX + i * (segmentWidth + segmentSpacing);
segment.y = healthBarY;
segment.tint = 0xffff00; // Yellow by default
segment.visible = false;
healthSegments.push(segment);
}
// Health bar label
var healthLabel = new Text2('VIDA', {
size: 30,
fill: 0xFFFFFF
});
healthLabel.anchor.set(0.5, 1);
healthLabel.x = centerX;
healthLabel.y = healthBarY - 30;
healthLabel.visible = false;
game.addChild(healthLabel);
// Health variables
var maxHealth = 100;
var currentHealth = 100;
// Invulnerability system
var isInvulnerable = false;
var invulnerabilityDuration = 3000; // 3 seconds
// Function to update health bar segments
function updateHealthBar() {
var healthSegmentsRemaining = Math.ceil(currentHealth / 10);
for (var i = 0; i < 10; i++) {
if (i < healthSegmentsRemaining) {
healthSegments[i].tint = 0xffff00; // Yellow for remaining health
} else {
healthSegments[i].tint = 0xff0000; // Red for lost health
}
}
}
// Initial health bar update
updateHealthBar();
// Create breathing sprites above combat box
var breathingSprite1 = game.addChild(LK.getAsset('breathingSprite1', {
anchorX: 0.5,
anchorY: 0.5
}));
breathingSprite1.x = centerX;
breathingSprite1.y = boundaryTop - 300;
breathingSprite1.visible = false;
var breathingSprite2 = game.addChild(LK.getAsset('breathingSprite2', {
anchorX: 0.5,
anchorY: 0.5
}));
breathingSprite2.x = centerX;
// Position sprite2 directly on top of sprite1 to form a tower
breathingSprite2.y = breathingSprite1.y - (breathingSprite1.height / 2 + breathingSprite2.height / 2) + 200;
breathingSprite2.visible = false;
// Start breathing animation for both sprites
function startBreathingAnimation() {
// Animate first sprite
tween(breathingSprite1, {
y: breathingSprite1.y - 15
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(breathingSprite1, {
y: breathingSprite1.y + 15
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: startBreathingAnimation
});
}
});
// Animate second sprite with slight delay
LK.setTimeout(function () {
tween(breathingSprite2, {
y: breathingSprite2.y - 12
}, {
duration: 1600,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(breathingSprite2, {
y: breathingSprite2.y + 12
}, {
duration: 1600,
easing: tween.easeInOut
});
}
});
}, 200);
}
startBreathingAnimation();
// Player position tracking for laser attacks
var playerPositions = [];
var maxStoredPositions = 3;
// Enemy management
var enemies = [];
var enemySpawnTimer = 0;
var enemySpawnInterval = 2000; // 2 seconds
// Laser attack management
var laserAttacks = [];
var laserAttackTimer = 0;
var laserAttackInterval = 10000 + Math.random() * 2000; // 10-12 seconds
// Cyan area attack management
var cyanAreaAttacks = [];
var cyanAttackTimer = 0;
var cyanAttackInterval = 20000; // 20 seconds initial spawn
// Player movement tracking
var lastPlayerX = character.x;
var lastPlayerY = character.y;
var isPlayerMoving = false;
// Function to spawn enemy at random position outside the square
function spawnEnemy() {
var enemy = new Enemy();
// Spawn at random position outside the boundary square
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
// Top
enemy.x = boundaryLeft + Math.random() * squareSize;
enemy.y = boundaryTop - 100;
break;
case 1:
// Right
enemy.x = boundaryRight + 100;
enemy.y = boundaryTop + Math.random() * squareSize;
break;
case 2:
// Bottom
enemy.x = boundaryLeft + Math.random() * squareSize;
enemy.y = boundaryBottom + 100;
break;
case 3:
// Left
enemy.x = boundaryLeft - 100;
enemy.y = boundaryTop + Math.random() * squareSize;
break;
}
enemy.setTarget(character.x, character.y);
enemies.push(enemy);
game.addChild(enemy);
}
// Function to spawn laser attack targeting player's previous positions
function spawnLaserAttack() {
if (playerPositions.length === 0) return;
// Choose a random previous position to target
var targetIndex = Math.floor(Math.random() * playerPositions.length);
var targetPos = playerPositions[targetIndex];
var laserAttack = new LaserAttack();
// Position laser attack outside the combat box
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
// Top
laserAttack.x = boundaryLeft + Math.random() * squareSize;
laserAttack.y = boundaryTop - 150;
break;
case 1:
// Right
laserAttack.x = boundaryRight + 150;
laserAttack.y = boundaryTop + Math.random() * squareSize;
break;
case 2:
// Bottom
laserAttack.x = boundaryLeft + Math.random() * squareSize;
laserAttack.y = boundaryBottom + 150;
break;
case 3:
// Left
laserAttack.x = boundaryLeft - 150;
laserAttack.y = boundaryTop + Math.random() * squareSize;
break;
}
laserAttack.setTarget(targetPos.x, targetPos.y);
laserAttacks.push(laserAttack);
game.addChild(laserAttack);
}
// Function to spawn cyan area attack covering the entire combat box
function spawnCyanAreaAttack() {
var cyanAttack = new CyanAreaAttack();
cyanAttack.x = boundaryLeft - 600; // Start from left side
cyanAttack.y = centerY; // Center vertically in combat box
cyanAreaAttacks.push(cyanAttack);
game.addChild(cyanAttack);
}
// Function to damage player
function damagePlayer(damage) {
if (damage === undefined) damage = 10; // Default 10% damage
currentHealth = Math.max(0, currentHealth - damage);
updateHealthBar();
// Camera shake effect for 1.2 seconds
var originalX = game.x;
var originalY = game.y;
var shakeIntensity = 10;
var shakeDuration = 1200; // 1.2 seconds
var shakeStartTime = Date.now();
function shakeCamera() {
var elapsed = Date.now() - shakeStartTime;
if (elapsed < shakeDuration) {
game.x = originalX + (Math.random() - 0.5) * shakeIntensity;
game.y = originalY + (Math.random() - 0.5) * shakeIntensity;
LK.setTimeout(shakeCamera, 16); // ~60fps
} else {
game.x = originalX;
game.y = originalY;
}
}
shakeCamera();
// Activate invulnerability
isInvulnerable = true;
// Flash character red when damaged and add blinking effect during invulnerability
tween(character, {
tint: 0xff0000
}, {
duration: 200,
onFinish: function onFinish() {
tween(character, {
tint: 0xffffff
}, {
duration: 200,
onFinish: function onFinish() {
// Start blinking effect for remaining invulnerability time
var blinkCount = 0;
var maxBlinks = 12; // Blink for about 2.4 seconds (remaining invulnerability time)
function blink() {
if (blinkCount < maxBlinks && isInvulnerable) {
character.alpha = character.alpha === 1 ? 0.3 : 1;
blinkCount++;
LK.setTimeout(blink, 200);
} else {
character.alpha = 1; // Ensure character is fully visible
}
}
blink();
}
});
}
});
// Set timeout to end invulnerability
LK.setTimeout(function () {
isInvulnerable = false;
character.alpha = 1; // Ensure character is fully visible
}, invulnerabilityDuration);
// Check game over - when all 10 segments are red (health is 0)
if (currentHealth <= 0) {
LK.showGameOver();
}
}
// Main game update loop
game.update = function () {
// Only update game logic when playing
if (gameState !== 'playing') return;
// Track player movement
var deltaX = character.x - lastPlayerX;
var deltaY = character.y - lastPlayerY;
var movementThreshold = 1; // Minimum movement to consider as "moving"
isPlayerMoving = Math.abs(deltaX) > movementThreshold || Math.abs(deltaY) > movementThreshold;
lastPlayerX = character.x;
lastPlayerY = character.y;
// Track player positions for laser attacks
if (LK.ticks % 10 === 0) {
// Store position every 10 ticks
playerPositions.push({
x: character.x,
y: character.y
});
if (playerPositions.length > maxStoredPositions) {
playerPositions.shift(); // Remove oldest position
}
}
// Enemy spawning
enemySpawnTimer += 16.67; // Approximate milliseconds per frame at 60fps
if (enemySpawnTimer >= enemySpawnInterval) {
spawnEnemy();
enemySpawnTimer = 0;
enemySpawnInterval = 2000; // Reset to 2 seconds
}
// Laser attack spawning
laserAttackTimer += 16.67;
if (laserAttackTimer >= laserAttackInterval) {
spawnLaserAttack();
laserAttackTimer = 0;
laserAttackInterval = 10000 + Math.random() * 2000; // Random 10-12 seconds
}
// Cyan area attack spawning - every 20 seconds
cyanAttackTimer += 16.67;
if (cyanAttackTimer >= cyanAttackInterval) {
spawnCyanAreaAttack();
cyanAttackTimer = 0;
cyanAttackInterval = 20000; // Every 20 seconds
}
// Update cyan area attacks and check collisions
for (var k = cyanAreaAttacks.length - 1; k >= 0; k--) {
var cyanAttack = cyanAreaAttacks[k];
// Check collision with player only if player is moving and attack is active
if (cyanAttack.isActive && cyanAttack.intersects(character) && isPlayerMoving && !isInvulnerable) {
damagePlayer(10); // 10% damage
}
// Remove finished cyan area attacks when they've crossed the combat box
if (cyanAttack.x >= cyanAttack.endX + 600) {
cyanAttack.destroy();
cyanAreaAttacks.splice(k, 1);
}
}
// Update laser attacks and check collisions
for (var j = laserAttacks.length - 1; j >= 0; j--) {
var laserAttack = laserAttacks[j];
// Check collision with player during firing state
if (laserAttack.state === 'firing' && laserAttack.laserBeam && !laserAttack.hasDealtDamage) {
if (laserAttack.laserBeam.intersects(character)) {
if (!isInvulnerable) {
damagePlayer(20); // 20% damage
laserAttack.hasDealtDamage = true;
}
}
}
// Remove finished laser attacks
if (laserAttack.state === 'finished') {
laserAttack.destroy();
laserAttacks.splice(j, 1);
}
}
// Update enemies and check collisions
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
enemy.setTarget(character.x, character.y);
// Check collision with player
if (enemy.intersects(character)) {
if (!isInvulnerable) {
damagePlayer();
}
// Destroy enemy regardless of invulnerability state
enemy.destroy();
enemies.splice(i, 1);
}
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Character = Container.expand(function () {
var self = Container.call(this);
var characterGraphics = self.attachAsset('character', {
anchorX: 0.5,
anchorY: 0.5
});
// Target position for smooth movement
self.targetX = 0;
self.targetY = 0;
// Movement speed factor for smooth following
self.followSpeed = 0.15;
// Square boundary limits (centered square area)
var squareSize = 1200;
var centerX = 2048 / 2;
var centerY = 2732 / 2;
self.boundaryLeft = centerX - squareSize / 2;
self.boundaryRight = centerX + squareSize / 2;
self.boundaryTop = centerY - squareSize / 2;
self.boundaryBottom = centerY + squareSize / 2;
var characterRadius = 60;
self.update = function () {
// Calculate next position
var deltaX = self.targetX - self.x;
var deltaY = self.targetY - self.y;
var nextX = self.x + deltaX * self.followSpeed;
var nextY = self.y + deltaY * self.followSpeed;
// Check wall collisions and prevent movement beyond boundaries
if (nextX - characterRadius < self.boundaryLeft) {
nextX = self.boundaryLeft + characterRadius;
}
if (nextX + characterRadius > self.boundaryRight) {
nextX = self.boundaryRight - characterRadius;
}
if (nextY - characterRadius < self.boundaryTop) {
nextY = self.boundaryTop + characterRadius;
}
if (nextY + characterRadius > self.boundaryBottom) {
nextY = self.boundaryBottom - characterRadius;
}
// Apply the constrained position
self.x = nextX;
self.y = nextY;
};
// Method to set target position
self.setTarget = function (x, y) {
self.targetX = x;
self.targetY = y;
};
return self;
});
var CyanAreaAttack = Container.expand(function () {
var self = Container.call(this);
var attackGraphics = self.attachAsset('cyanAreaAttack', {
anchorX: 0,
anchorY: 0.5
});
self.duration = 0;
self.maxDuration = 3000; // 3 seconds
self.isActive = false;
self.startX = -600; // Start from left side of combat box
self.endX = 1200; // End at right side of combat box
self.speed = 5; // Movement speed from left to right
// Start with low alpha and fade in
attackGraphics.alpha = 0;
// Position at left edge initially
self.x = self.startX;
self.update = function () {
self.duration += 16.67;
// Move from left to right across combat box
if (self.duration > 500 && self.x < self.endX + 600) {
// Move until completely off screen
self.x += self.speed;
}
// Fade in effect at the beginning
if (self.duration < 500) {
attackGraphics.alpha = self.duration / 500 * 0.6; // Max alpha of 0.6
} else if (self.x < self.endX + 600) {
// Stay visible and active while crossing combat box
attackGraphics.alpha = 0.6;
self.isActive = true;
} else {
// Mark as finished when completely crossed
self.isActive = false;
attackGraphics.alpha = 0;
}
};
return self;
});
var Enemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8;
self.targetX = 0;
self.targetY = 0;
self.update = function () {
// Move toward target (player)
var deltaX = self.targetX - self.x;
var deltaY = self.targetY - self.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance > 0) {
self.x += deltaX / distance * self.speed;
self.y += deltaY / distance * self.speed;
}
// Add rotation effect (visual only, doesn't affect movement)
enemyGraphics.rotation += 0.1;
};
self.setTarget = function (x, y) {
self.targetX = x;
self.targetY = y;
};
return self;
});
var LaserAttack = Container.expand(function () {
var self = Container.call(this);
var chargerGraphics = self.attachAsset('laserCharger', {
anchorX: 0.5,
anchorY: 0.5
});
self.targetX = 0;
self.targetY = 0;
self.state = 'charging'; // 'charging', 'firing', 'finished'
self.chargeTime = 0;
self.fireTime = 0;
self.laserBeam = null;
self.hasDealtDamage = false;
self.update = function () {
if (self.state === 'charging') {
self.chargeTime += 16.67;
// Charging animation - pulsing effect
chargerGraphics.alpha = 0.5 + 0.5 * Math.sin(self.chargeTime * 0.01);
if (self.chargeTime >= 1200) {
// 1.2 seconds
self.state = 'firing';
self.fireLaser();
}
} else if (self.state === 'firing') {
self.fireTime += 16.67;
if (self.fireTime >= 3000) {
// 3 seconds
self.state = 'finished';
}
}
};
self.fireLaser = function () {
// Hide charger
chargerGraphics.alpha = 0;
// Create laser beam
self.laserBeam = self.addChild(LK.getAsset('laserAttack', {
anchorX: 0,
anchorY: 0.5
}));
// Position laser to fire across the screen toward target
var angle = Math.atan2(self.targetY - self.y, self.targetX - self.x);
self.laserBeam.rotation = angle;
self.laserBeam.x = 0;
self.laserBeam.y = 0;
};
self.setTarget = function (x, y) {
self.targetX = x;
self.targetY = y;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Game state management
var gameState = 'menu'; // 'menu' or 'playing'
var menuContainer = game.addChild(new Container());
// Create menu elements
var logo = menuContainer.addChild(LK.getAsset('breathingSprite3', {
anchorX: 0.5,
anchorY: 0.5
}));
logo.x = 2048 / 2;
logo.y = 2732 / 2 - 250;
// Create play button
var playButton = menuContainer.addChild(LK.getAsset('playButton', {
anchorX: 0.5,
anchorY: 0.5
}));
playButton.x = 2048 / 2;
playButton.y = 2732 / 2 + 100;
// Play button text
var playText = menuContainer.addChild(new Text2('JUGAR', {
size: 70,
fill: 0xFFFFFF
}));
playText.anchor.set(0.5, 0.5);
playText.x = playButton.x;
playText.y = playButton.y;
// Play button interaction
playButton.down = function (x, y, obj) {
// Flash button on press
tween(playButton, {
tint: 0xffffff
}, {
duration: 100,
onFinish: function onFinish() {
tween(playButton, {
tint: 0xffffff
}, {
duration: 100
});
}
});
};
playButton.up = function (x, y, obj) {
// Start the game
gameState = 'playing';
menuContainer.visible = false;
// Show game elements
leftWall.visible = true;
rightWall.visible = true;
topWall.visible = true;
bottomWall.visible = true;
character.visible = true;
healthBarBackground.visible = true;
healthLabel.visible = true;
breathingSprite1.visible = true;
breathingSprite2.visible = true;
for (var i = 0; i < healthSegments.length; i++) {
healthSegments[i].visible = true;
}
};
// Create visual boundary walls
var squareSize = 1200;
var centerX = 2048 / 2;
var centerY = 2732 / 2;
var boundaryLeft = centerX - squareSize / 2;
var boundaryRight = centerX + squareSize / 2;
var boundaryTop = centerY - squareSize / 2;
var boundaryBottom = centerY + squareSize / 2;
// Left wall
var leftWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5
}));
leftWall.x = boundaryLeft;
leftWall.y = centerY;
leftWall.visible = false;
// Right wall
var rightWall = game.addChild(LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5
}));
rightWall.x = boundaryRight;
rightWall.y = centerY;
rightWall.visible = false;
// Top wall
var topWall = game.addChild(LK.getAsset('wallHorizontal', {
anchorX: 0.5,
anchorY: 0.5
}));
topWall.x = centerX;
topWall.y = boundaryTop;
topWall.visible = false;
// Bottom wall
var bottomWall = game.addChild(LK.getAsset('wallHorizontal', {
anchorX: 0.5,
anchorY: 0.5
}));
bottomWall.x = centerX;
bottomWall.y = boundaryBottom;
bottomWall.visible = false;
// Create the character at center of screen
var character = game.addChild(new Character());
character.x = 2048 / 2;
character.y = 2732 / 2;
character.visible = false;
// Set initial target position to character's starting position
character.setTarget(character.x, character.y);
// Handle mouse/touch movement
game.move = function (x, y, obj) {
if (gameState === 'playing') {
// Set character target to mouse position
character.setTarget(x, y);
}
};
// Handle mouse/touch down - also update position immediately
game.down = function (x, y, obj) {
if (gameState === 'playing') {
// Set character target to touch/click position
character.setTarget(x, y);
}
};
// Create health bar interface below the square
var healthBarY = boundaryBottom + 100;
// Health bar background
var healthBarBackground = game.addChild(LK.getAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5
}));
healthBarBackground.x = centerX;
healthBarBackground.y = healthBarY;
healthBarBackground.visible = false;
// Health bar segments (10 parts)
var healthSegments = [];
var segmentWidth = 36;
var segmentSpacing = 2;
var totalWidth = (segmentWidth + segmentSpacing) * 10 - segmentSpacing;
var startX = centerX - totalWidth / 2;
for (var i = 0; i < 10; i++) {
var segment = game.addChild(LK.getAsset('healthSegment', {
anchorX: 0,
anchorY: 0.5
}));
segment.x = startX + i * (segmentWidth + segmentSpacing);
segment.y = healthBarY;
segment.tint = 0xffff00; // Yellow by default
segment.visible = false;
healthSegments.push(segment);
}
// Health bar label
var healthLabel = new Text2('VIDA', {
size: 30,
fill: 0xFFFFFF
});
healthLabel.anchor.set(0.5, 1);
healthLabel.x = centerX;
healthLabel.y = healthBarY - 30;
healthLabel.visible = false;
game.addChild(healthLabel);
// Health variables
var maxHealth = 100;
var currentHealth = 100;
// Invulnerability system
var isInvulnerable = false;
var invulnerabilityDuration = 3000; // 3 seconds
// Function to update health bar segments
function updateHealthBar() {
var healthSegmentsRemaining = Math.ceil(currentHealth / 10);
for (var i = 0; i < 10; i++) {
if (i < healthSegmentsRemaining) {
healthSegments[i].tint = 0xffff00; // Yellow for remaining health
} else {
healthSegments[i].tint = 0xff0000; // Red for lost health
}
}
}
// Initial health bar update
updateHealthBar();
// Create breathing sprites above combat box
var breathingSprite1 = game.addChild(LK.getAsset('breathingSprite1', {
anchorX: 0.5,
anchorY: 0.5
}));
breathingSprite1.x = centerX;
breathingSprite1.y = boundaryTop - 300;
breathingSprite1.visible = false;
var breathingSprite2 = game.addChild(LK.getAsset('breathingSprite2', {
anchorX: 0.5,
anchorY: 0.5
}));
breathingSprite2.x = centerX;
// Position sprite2 directly on top of sprite1 to form a tower
breathingSprite2.y = breathingSprite1.y - (breathingSprite1.height / 2 + breathingSprite2.height / 2) + 200;
breathingSprite2.visible = false;
// Start breathing animation for both sprites
function startBreathingAnimation() {
// Animate first sprite
tween(breathingSprite1, {
y: breathingSprite1.y - 15
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(breathingSprite1, {
y: breathingSprite1.y + 15
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: startBreathingAnimation
});
}
});
// Animate second sprite with slight delay
LK.setTimeout(function () {
tween(breathingSprite2, {
y: breathingSprite2.y - 12
}, {
duration: 1600,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(breathingSprite2, {
y: breathingSprite2.y + 12
}, {
duration: 1600,
easing: tween.easeInOut
});
}
});
}, 200);
}
startBreathingAnimation();
// Player position tracking for laser attacks
var playerPositions = [];
var maxStoredPositions = 3;
// Enemy management
var enemies = [];
var enemySpawnTimer = 0;
var enemySpawnInterval = 2000; // 2 seconds
// Laser attack management
var laserAttacks = [];
var laserAttackTimer = 0;
var laserAttackInterval = 10000 + Math.random() * 2000; // 10-12 seconds
// Cyan area attack management
var cyanAreaAttacks = [];
var cyanAttackTimer = 0;
var cyanAttackInterval = 20000; // 20 seconds initial spawn
// Player movement tracking
var lastPlayerX = character.x;
var lastPlayerY = character.y;
var isPlayerMoving = false;
// Function to spawn enemy at random position outside the square
function spawnEnemy() {
var enemy = new Enemy();
// Spawn at random position outside the boundary square
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
// Top
enemy.x = boundaryLeft + Math.random() * squareSize;
enemy.y = boundaryTop - 100;
break;
case 1:
// Right
enemy.x = boundaryRight + 100;
enemy.y = boundaryTop + Math.random() * squareSize;
break;
case 2:
// Bottom
enemy.x = boundaryLeft + Math.random() * squareSize;
enemy.y = boundaryBottom + 100;
break;
case 3:
// Left
enemy.x = boundaryLeft - 100;
enemy.y = boundaryTop + Math.random() * squareSize;
break;
}
enemy.setTarget(character.x, character.y);
enemies.push(enemy);
game.addChild(enemy);
}
// Function to spawn laser attack targeting player's previous positions
function spawnLaserAttack() {
if (playerPositions.length === 0) return;
// Choose a random previous position to target
var targetIndex = Math.floor(Math.random() * playerPositions.length);
var targetPos = playerPositions[targetIndex];
var laserAttack = new LaserAttack();
// Position laser attack outside the combat box
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
// Top
laserAttack.x = boundaryLeft + Math.random() * squareSize;
laserAttack.y = boundaryTop - 150;
break;
case 1:
// Right
laserAttack.x = boundaryRight + 150;
laserAttack.y = boundaryTop + Math.random() * squareSize;
break;
case 2:
// Bottom
laserAttack.x = boundaryLeft + Math.random() * squareSize;
laserAttack.y = boundaryBottom + 150;
break;
case 3:
// Left
laserAttack.x = boundaryLeft - 150;
laserAttack.y = boundaryTop + Math.random() * squareSize;
break;
}
laserAttack.setTarget(targetPos.x, targetPos.y);
laserAttacks.push(laserAttack);
game.addChild(laserAttack);
}
// Function to spawn cyan area attack covering the entire combat box
function spawnCyanAreaAttack() {
var cyanAttack = new CyanAreaAttack();
cyanAttack.x = boundaryLeft - 600; // Start from left side
cyanAttack.y = centerY; // Center vertically in combat box
cyanAreaAttacks.push(cyanAttack);
game.addChild(cyanAttack);
}
// Function to damage player
function damagePlayer(damage) {
if (damage === undefined) damage = 10; // Default 10% damage
currentHealth = Math.max(0, currentHealth - damage);
updateHealthBar();
// Camera shake effect for 1.2 seconds
var originalX = game.x;
var originalY = game.y;
var shakeIntensity = 10;
var shakeDuration = 1200; // 1.2 seconds
var shakeStartTime = Date.now();
function shakeCamera() {
var elapsed = Date.now() - shakeStartTime;
if (elapsed < shakeDuration) {
game.x = originalX + (Math.random() - 0.5) * shakeIntensity;
game.y = originalY + (Math.random() - 0.5) * shakeIntensity;
LK.setTimeout(shakeCamera, 16); // ~60fps
} else {
game.x = originalX;
game.y = originalY;
}
}
shakeCamera();
// Activate invulnerability
isInvulnerable = true;
// Flash character red when damaged and add blinking effect during invulnerability
tween(character, {
tint: 0xff0000
}, {
duration: 200,
onFinish: function onFinish() {
tween(character, {
tint: 0xffffff
}, {
duration: 200,
onFinish: function onFinish() {
// Start blinking effect for remaining invulnerability time
var blinkCount = 0;
var maxBlinks = 12; // Blink for about 2.4 seconds (remaining invulnerability time)
function blink() {
if (blinkCount < maxBlinks && isInvulnerable) {
character.alpha = character.alpha === 1 ? 0.3 : 1;
blinkCount++;
LK.setTimeout(blink, 200);
} else {
character.alpha = 1; // Ensure character is fully visible
}
}
blink();
}
});
}
});
// Set timeout to end invulnerability
LK.setTimeout(function () {
isInvulnerable = false;
character.alpha = 1; // Ensure character is fully visible
}, invulnerabilityDuration);
// Check game over - when all 10 segments are red (health is 0)
if (currentHealth <= 0) {
LK.showGameOver();
}
}
// Main game update loop
game.update = function () {
// Only update game logic when playing
if (gameState !== 'playing') return;
// Track player movement
var deltaX = character.x - lastPlayerX;
var deltaY = character.y - lastPlayerY;
var movementThreshold = 1; // Minimum movement to consider as "moving"
isPlayerMoving = Math.abs(deltaX) > movementThreshold || Math.abs(deltaY) > movementThreshold;
lastPlayerX = character.x;
lastPlayerY = character.y;
// Track player positions for laser attacks
if (LK.ticks % 10 === 0) {
// Store position every 10 ticks
playerPositions.push({
x: character.x,
y: character.y
});
if (playerPositions.length > maxStoredPositions) {
playerPositions.shift(); // Remove oldest position
}
}
// Enemy spawning
enemySpawnTimer += 16.67; // Approximate milliseconds per frame at 60fps
if (enemySpawnTimer >= enemySpawnInterval) {
spawnEnemy();
enemySpawnTimer = 0;
enemySpawnInterval = 2000; // Reset to 2 seconds
}
// Laser attack spawning
laserAttackTimer += 16.67;
if (laserAttackTimer >= laserAttackInterval) {
spawnLaserAttack();
laserAttackTimer = 0;
laserAttackInterval = 10000 + Math.random() * 2000; // Random 10-12 seconds
}
// Cyan area attack spawning - every 20 seconds
cyanAttackTimer += 16.67;
if (cyanAttackTimer >= cyanAttackInterval) {
spawnCyanAreaAttack();
cyanAttackTimer = 0;
cyanAttackInterval = 20000; // Every 20 seconds
}
// Update cyan area attacks and check collisions
for (var k = cyanAreaAttacks.length - 1; k >= 0; k--) {
var cyanAttack = cyanAreaAttacks[k];
// Check collision with player only if player is moving and attack is active
if (cyanAttack.isActive && cyanAttack.intersects(character) && isPlayerMoving && !isInvulnerable) {
damagePlayer(10); // 10% damage
}
// Remove finished cyan area attacks when they've crossed the combat box
if (cyanAttack.x >= cyanAttack.endX + 600) {
cyanAttack.destroy();
cyanAreaAttacks.splice(k, 1);
}
}
// Update laser attacks and check collisions
for (var j = laserAttacks.length - 1; j >= 0; j--) {
var laserAttack = laserAttacks[j];
// Check collision with player during firing state
if (laserAttack.state === 'firing' && laserAttack.laserBeam && !laserAttack.hasDealtDamage) {
if (laserAttack.laserBeam.intersects(character)) {
if (!isInvulnerable) {
damagePlayer(20); // 20% damage
laserAttack.hasDealtDamage = true;
}
}
}
// Remove finished laser attacks
if (laserAttack.state === 'finished') {
laserAttack.destroy();
laserAttacks.splice(j, 1);
}
}
// Update enemies and check collisions
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
enemy.setTarget(character.x, character.y);
// Check collision with player
if (enemy.intersects(character)) {
if (!isInvulnerable) {
damagePlayer();
}
// Destroy enemy regardless of invulnerability state
enemy.destroy();
enemies.splice(i, 1);
}
}
};