User prompt
Let the anchor be as long as the box ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Let the anchor be Huge and come from the other side of the box to the other side and only 1 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Add an anchor, sometimes it's blue, sometimes it's orange (When it's blue you have to stop, when it's orange you have to move. Damage:2) ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Delete The Bone outline
User prompt
Let the text be at the top of the screen and be black
User prompt
Add lives (let us have 10 lives and bones hit 1)
User prompt
Let the bones come like an Undertale sans fight and draw a black line around the bones ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Let the bones come and try to attack us, and we will run away ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Speed up character
User prompt
Let the player control
User prompt
Add movement to character
Code edit (1 edits merged)
Please save this source code
User prompt
Heart in a Box
Initial prompt
Create a box and our character (Heart) Let him travel, but not go outside the box
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Anchor = Container.expand(function () { var self = Container.call(this); // Create anchor graphic var anchorGraphics = self.attachAsset('anchor', { anchorX: 0.5, anchorY: 0.5, scaleX: 1, scaleY: 10 }); self.isBlue = true; // true = blue (stop), false = orange (move) self.damage = 6; self.lastX = 0; self.lastY = 0; self.colorChangeTimer = 0; self.colorChangeInterval = 180; // Change color every 3 seconds at 60fps self.speed = 3; self.movingRight = true; // Update anchor color self.updateColor = function () { if (self.isBlue) { anchorGraphics.tint = 0x0066ff; // Blue } else { anchorGraphics.tint = 0xff6600; // Orange } }; self.update = function () { self.lastX = self.x; self.lastY = self.y; // Move horizontally within the box boundaries var anchorHalfWidth = 60; // Half of 120px anchor width var minX = boxX + 8 + anchorHalfWidth; var maxX = boxX + boxWidth - 8 - anchorHalfWidth; if (self.movingRight) { self.x += self.speed; // Move right if (self.x >= maxX) { self.x = maxX; self.movingRight = false; // Switch to moving left } } else { self.x -= self.speed; // Move left if (self.x <= minX) { self.x = minX; self.movingRight = true; // Switch to moving right } } // Keep anchor within vertical bounds of the box var anchorHalfHeight = 600; // Half of 1200px anchor height var minY = boxY + 8 + anchorHalfHeight; var maxY = boxY + boxHeight - 8 - anchorHalfHeight; if (self.y < minY) { self.y = minY; } if (self.y > maxY) { self.y = maxY; } // Change color periodically self.colorChangeTimer++; if (self.colorChangeTimer >= self.colorChangeInterval) { self.isBlue = !self.isBlue; self.updateColor(); self.colorChangeTimer = 0; } }; // Initialize with blue color self.updateColor(); return self; }); var Asgore = Container.expand(function () { var self = Container.call(this); // Create Asgore using proper Asgore image asset var asgoreGraphics = self.attachAsset('asgore', { anchorX: 0.5, anchorY: 0.5, scaleX: 1, scaleY: 1 }); self.attackTimer = 0; self.attackInterval = 240; // Attack every 4 seconds self.blocksLeft = 12; self.isDead = false; self.block = function () { if (self.blocksLeft > 0 && !self.isDead) { self.blocksLeft--; // Flash blue to indicate block tween(asgoreGraphics, { tint: 0x0066ff }, { duration: 100, onFinish: function onFinish() { tween(asgoreGraphics, { tint: 0xffffff }, { duration: 100 }); } }); if (self.blocksLeft <= 0) { self.isDead = true; tween(asgoreGraphics, { alpha: 0.3 }, { duration: 500 }); } return true; } return false; }; return self; }); var BoxBoundary = Container.expand(function () { var self = Container.call(this); // Create four walls for the box var topWall = self.attachAsset('boxBorder', { anchorX: 0, anchorY: 0, width: 1600, height: 8 }); var bottomWall = self.attachAsset('boxBorder', { anchorX: 0, anchorY: 0, width: 1600, height: 8 }); var leftWall = self.attachAsset('boxBorder', { anchorX: 0, anchorY: 0, width: 8, height: 1200 }); var rightWall = self.attachAsset('boxBorder', { anchorX: 0, anchorY: 0, width: 8, height: 1200 }); // Position walls topWall.x = 0; topWall.y = 0; bottomWall.x = 0; bottomWall.y = 1200 - 8; leftWall.x = 0; leftWall.y = 0; rightWall.x = 1600 - 8; rightWall.y = 0; return self; }); var Bullet = Container.expand(function () { var self = Container.call(this); var bulletGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 15; self.directionX = 0; self.directionY = 0; self.lastX = 0; self.lastY = 0; self.setDirection = function (targetX, targetY, startX, startY) { var deltaX = targetX - startX; var deltaY = targetY - startY; var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); if (distance > 0) { self.directionX = deltaX / distance; self.directionY = deltaY / distance; } }; self.update = function () { self.lastX = self.x; self.lastY = self.y; self.x += self.directionX * self.speed; self.y += self.directionY * self.speed; }; return self; }); var Flowey = Container.expand(function () { var self = Container.call(this); // Create Flowey using proper Flowey image asset var floweyGraphics = self.attachAsset('flowey', { anchorX: 0.5, anchorY: 0.5, scaleX: 1, scaleY: 1 }); self.attackTimer = 0; self.attackInterval = 600; // Attack every 10 seconds self.destroysLeft = 999; // Infinite until transformation self.finalFormDestroysLeft = 5; self.isFinalForm = false; self.isDead = false; self.destroyAttack = function () { if (self.isDead) return false; var destroysToUse = self.isFinalForm ? self.finalFormDestroysLeft : self.destroysLeft; if (destroysToUse > 0) { if (self.isFinalForm) { self.finalFormDestroysLeft--; if (self.finalFormDestroysLeft <= 0) { self.isDead = true; tween(floweyGraphics, { alpha: 0.3 }, { duration: 500 }); } } // Flash red to indicate destruction tween(floweyGraphics, { tint: 0xff0000 }, { duration: 100, onFinish: function onFinish() { tween(floweyGraphics, { tint: 0xffffff }, { duration: 100 }); } }); return true; } return false; }; self.transformToFinalForm = function () { if (!self.isFinalForm) { self.isFinalForm = true; self.attackInterval = 300; // Attack more frequently // Transform animation tween(floweyGraphics, { scaleX: 1.5, scaleY: 1.5, tint: 0xff00ff }, { duration: 1000, easing: tween.easeOut }); } }; return self; }); var GasterBeam = Container.expand(function () { var self = Container.call(this); // Create black border first (behind white beam) var beamBorder = self.attachAsset('beamBorder', { anchorX: 0.5, anchorY: 0.5 }); // Create white beam on top var whiteBeam = self.attachAsset('whiteBeam', { anchorX: 0.5, anchorY: 0.5 }); self.damage = 12; // Gaster beam damage self.speed = 4; self.lastX = 0; self.lastY = 0; self.targetX = 0; self.targetY = 0; self.isActive = false; self.fireBeam = function (targetX, targetY) { if (self.isActive) return; self.isActive = true; self.targetX = targetX; self.targetY = targetY; // Calculate direction to target var deltaX = targetX - self.x; var deltaY = targetY - self.y; var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); // Set rotation to point towards target self.rotation = Math.atan2(deltaY, deltaX) + Math.PI / 2; // Add 90 degrees because beam asset points up // Move towards target tween(self, { x: targetX, y: targetY }, { duration: distance / self.speed * 16, // Adjust duration based on distance easing: tween.linear, onFinish: function onFinish() { self.isActive = false; } }); }; self.update = function () { self.lastX = self.x; self.lastY = self.y; }; return self; }); var GasterBlaster = Container.expand(function () { var self = Container.call(this); // Create Gaster Blaster using image asset var blasterGraphics = self.attachAsset('gasterBlaster', { anchorX: 0.5, anchorY: 0.5, scaleX: 1, scaleY: 1 }); self.chargeTime = 60; // 1 second charge time at 60fps self.chargeTimer = 0; self.isCharging = false; self.isReady = false; self.beamDuration = 120; // 2 seconds beam duration self.beamTimer = 0; self.targetX = 0; self.targetY = 0; self.startCharge = function (targetX, targetY) { if (self.isCharging || self.isReady) return; self.isCharging = true; self.targetX = targetX; self.targetY = targetY; self.chargeTimer = 0; // Point towards target during charge var deltaX = targetX - self.x; var deltaY = targetY - self.y; self.rotation = Math.atan2(deltaY, deltaX); // Flash effect during charge tween(blasterGraphics, { alpha: 0.3 }, { duration: 100, easing: tween.linear, onFinish: function onFinish() { tween(blasterGraphics, { alpha: 1 }, { duration: 100, easing: tween.linear }); } }); }; self.fireBeam = function () { if (!self.isReady) return null; var beam = new GasterBeam(); beam.x = self.x; beam.y = self.y; beam.lastX = beam.x; beam.lastY = beam.y; beam.fireBeam(self.targetX, self.targetY); return beam; }; self.update = function () { if (self.isCharging) { self.chargeTimer++; if (self.chargeTimer >= self.chargeTime) { self.isCharging = false; self.isReady = true; self.beamTimer = 0; } } else if (self.isReady) { self.beamTimer++; if (self.beamTimer >= self.beamDuration) { self.isReady = false; } } }; return self; }); var Heart = Container.expand(function () { var self = Container.call(this); var heartGraphics = self.attachAsset('heart', { anchorX: 0.5, anchorY: 0.5 }); var blueHeartGraphics = self.attachAsset('blueHeart', { anchorX: 0.5, anchorY: 0.5, alpha: 0 }); var greenHeartGraphics = self.attachAsset('greenHeart', { anchorX: 0.5, anchorY: 0.5, alpha: 0 }); var yellowHeartGraphics = self.attachAsset('yellowHeart', { anchorX: 0.5, anchorY: 0.5, alpha: 0 }); var shieldGraphics = self.attachAsset('shield', { anchorX: 0.5, anchorY: 0.5, alpha: 0, scaleX: 2.0, scaleY: 2.0 }); self.isBlue = false; self.isGreen = false; self.isYellow = false; self.velocityY = 0; self.gravity = 0.3; self.jumpPower = -18; self.onGround = false; self.groundY = 0; self.shieldX = 0; self.shieldY = 0; self.shieldRadius = 150; // Distance from heart center to shield center self.setBlueMode = function (isBlue) { self.isBlue = isBlue; self.isGreen = false; self.isYellow = false; if (isBlue) { heartGraphics.alpha = 0; blueHeartGraphics.alpha = 1; greenHeartGraphics.alpha = 0; yellowHeartGraphics.alpha = 0; shieldGraphics.alpha = 0; } else { heartGraphics.alpha = 1; blueHeartGraphics.alpha = 0; greenHeartGraphics.alpha = 0; yellowHeartGraphics.alpha = 0; shieldGraphics.alpha = 0; } }; self.setGreenMode = function (isGreen) { self.isGreen = isGreen; self.isBlue = false; self.isYellow = false; if (isGreen) { heartGraphics.alpha = 0; blueHeartGraphics.alpha = 0; greenHeartGraphics.alpha = 1; yellowHeartGraphics.alpha = 0; shieldGraphics.alpha = 1; } else { heartGraphics.alpha = 1; blueHeartGraphics.alpha = 0; greenHeartGraphics.alpha = 0; yellowHeartGraphics.alpha = 0; shieldGraphics.alpha = 0; } }; self.setYellowMode = function (isYellow) { self.isYellow = isYellow; self.isBlue = false; self.isGreen = false; if (isYellow) { heartGraphics.alpha = 0; blueHeartGraphics.alpha = 0; greenHeartGraphics.alpha = 0; yellowHeartGraphics.alpha = 1; shieldGraphics.alpha = 0; } else { heartGraphics.alpha = 1; blueHeartGraphics.alpha = 0; greenHeartGraphics.alpha = 0; yellowHeartGraphics.alpha = 0; shieldGraphics.alpha = 0; } }; self.updateShieldPosition = function (targetX, targetY) { if (self.isGreen) { // Calculate angle from heart to target var deltaX = targetX - self.x; var deltaY = targetY - self.y; var angle = Math.atan2(deltaY, deltaX); // Position shield at fixed radius around heart self.shieldX = Math.cos(angle) * self.shieldRadius; self.shieldY = Math.sin(angle) * self.shieldRadius; // Update shield graphics position shieldGraphics.x = self.shieldX; shieldGraphics.y = self.shieldY; } }; self.getShieldWorldPosition = function () { return { x: self.x + self.shieldX, y: self.y + self.shieldY }; }; return self; }); var Ivy = Container.expand(function () { var self = Container.call(this); // Create ivy graphic - anchor from top to hang down var ivyGraphics = self.attachAsset('ivy', { anchorX: 0.5, anchorY: 0 }); self.damage = 5; self.speed = 3; self.lastX = 0; self.lastY = 0; self.movingRight = true; self.update = function () { self.lastX = self.x; self.lastY = self.y; // Move downward from top to bottom of box self.y += self.speed; // Always move down }; return self; }); var Sans = Container.expand(function () { var self = Container.call(this); // Create Sans using proper Sans image asset var sansGraphics = self.attachAsset('sans', { anchorX: 0.5, anchorY: 0.5, scaleX: 1, scaleY: 1 }); self.attackTimer = 0; self.attackInterval = 720; // Attack every 12 seconds self.greenAttackTimer = 0; self.greenAttackInterval = 10800; // Start at 12:00 timer (180 seconds * 60 fps) self.greenAttackDuration = 7200; // 2 minutes duration from 12:00 to 10:00 (120 seconds * 60 fps) self.isGreenAttack = false; self.yellowAttackTimer = 0; self.yellowAttackInterval = 21600; // Start at 9:00 timer (360 seconds * 60 fps) self.yellowAttackDuration = 7200; // 2 minute duration from 9:00 to 7:00 (120 seconds * 60 fps) self.isYellowAttack = false; self.dodgesLeft = 10; self.isDead = false; self.originalX = 0; self.originalY = 0; self.dodge = function () { if (self.dodgesLeft > 0 && !self.isDead) { self.dodgesLeft--; // Move to random position near original location var dodgeDistance = 100; var angle = Math.random() * Math.PI * 2; var newX = self.originalX + Math.cos(angle) * dodgeDistance; var newY = self.originalY + Math.sin(angle) * dodgeDistance; tween(self, { x: newX, y: newY }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { // Return to original position tween(self, { x: self.originalX, y: self.originalY }, { duration: 300, easing: tween.easeIn }); } }); if (self.dodgesLeft <= 0) { self.isDead = true; tween(sansGraphics, { alpha: 0.3 }, { duration: 500 }); } return true; } return false; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0xf0f8ff }); /**** * Game Code ****/ // Box dimensions var boxX = 224; // Center the 1600px box in 2048px screen var boxY = 766; // Center the 1200px box in 2732px screen var boxWidth = 1600; var boxHeight = 1200; // Create box boundary var boundary = game.addChild(new BoxBoundary()); boundary.x = boxX; boundary.y = boxY; // Create heart character var heart = game.addChild(new Heart()); heart.x = boxX + boxWidth / 2; // Center of box heart.y = boxY + boxHeight / 2; // Center of box // Create the three characters // Sans on the left side (bone thrower) var sans = game.addChild(new Sans()); sans.x = boxX - 150; // Left side of box sans.y = boxY + boxHeight / 2; // Middle height // Asgore in the middle (anchor owner) var asgore = game.addChild(new Asgore()); asgore.x = boxX + boxWidth / 2; // Center of box horizontally asgore.y = boxY - 150; // Above the box // Flowey on the right side (ivy sender) var flowey = game.addChild(new Flowey()); flowey.x = boxX + boxWidth + 150; // Right side of box flowey.y = boxY + boxHeight / 2; // Middle height // Create lives display var livesTxt = new Text2('Lives: 92', { size: 80, fill: 0x000000 }); livesTxt.anchor.set(0.5, 0); LK.gui.top.addChild(livesTxt); // Create timer display var gameTime = 900; // 15 minutes in seconds var timerTxt = new Text2('Time: 15:00', { size: 80, fill: 0x000000 }); timerTxt.anchor.set(1, 0); // Right align LK.gui.topRight.addChild(timerTxt); // Create "THREE LEFT" text in red under timer var threeTxt = new Text2('THREE LEFT', { size: 60, fill: 0xff0000 }); threeTxt.anchor.set(1, 0); // Right align threeTxt.y = 100; // Position below timer LK.gui.topRight.addChild(threeTxt); // Create health button var healthButton = LK.getAsset('healthButton', { anchorX: 0.5, anchorY: 0.5 }); healthButton.x = boxX + boxWidth / 2; // Center horizontally with the box healthButton.y = boxY + boxHeight + 100; // Position below the box (outside) game.addChild(healthButton); // Create health button text var healthButtonText = new Text2('HEALTH +20', { size: 40, fill: 0x000000 }); healthButtonText.anchor.set(0.5, 0.5); healthButtonText.x = healthButton.x; healthButtonText.y = healthButton.y; game.addChild(healthButtonText); // Create yellow heart special button var yellowButton = LK.getAsset('healthButton', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5 }); yellowButton.x = boxX + boxWidth / 2; // Position directly below health button yellowButton.y = boxY + boxHeight + 200; // Position below the health button yellowButton.tint = 0xffff00; // Yellow color game.addChild(yellowButton); // Create yellow button text var yellowButtonText = new Text2('YELLOW SHOT', { size: 40, fill: 0x000000 }); yellowButtonText.anchor.set(0.5, 0.5); yellowButtonText.x = yellowButton.x; yellowButtonText.y = yellowButton.y; game.addChild(yellowButtonText); // Health button cooldown variables var healthButtonCooldown = 0; var healthButtonMaxCooldown = 1200; // 20 seconds at 60fps var healthButtonEnabled = true; // Store original positions for characters sans.originalX = sans.x; sans.originalY = sans.y; // Dragging variables var isDragging = false; var dragOffsetX = 0; var dragOffsetY = 0; // Constraint function to keep heart within boundaries function constrainHeartPosition() { var heartRadius = 50; // Half of heart width/height // Left boundary if (heart.x - heartRadius < boxX + 8) { heart.x = boxX + 8 + heartRadius; } // Right boundary if (heart.x + heartRadius > boxX + boxWidth - 8) { heart.x = boxX + boxWidth - 8 - heartRadius; } // Top boundary if (heart.y - heartRadius < boxY + 8) { heart.y = boxY + 8 + heartRadius; } // Bottom boundary if (heart.y + heartRadius > boxY + boxHeight - 8) { heart.y = boxY + boxHeight - 8 - heartRadius; } } // Player control variables var targetX = 0; var targetY = 0; var isPlayerControlling = false; // Touch/mouse down event game.down = function (x, y, obj) { // Check if health button was clicked var buttonDistance = Math.sqrt(Math.pow(x - healthButton.x, 2) + Math.pow(y - healthButton.y, 2)); if (buttonDistance < 100 && healthButtonEnabled) { // Increase health by 20 lives += 20; livesTxt.setText('Lives: ' + lives); // Start cooldown healthButtonCooldown = healthButtonMaxCooldown; healthButtonEnabled = false; // Visual feedback - flash button tween(healthButton, { tint: 0xffff00 }, { duration: 200, onFinish: function onFinish() { tween(healthButton, { tint: 0x888888 }, { duration: 200 }); } }); // Update button text to show cooldown healthButtonText.setText('COOLDOWN'); return; // Don't process normal movement } // Check if yellow button was clicked var yellowButtonDistance = Math.sqrt(Math.pow(x - yellowButton.x, 2) + Math.pow(y - yellowButton.y, 2)); if (yellowButtonDistance < 100 && heart.isYellow && bullets.length < maxBullets) { var bullet = new Bullet(); bullet.x = heart.x; bullet.y = heart.y; bullet.lastX = bullet.x; bullet.lastY = bullet.y; // Set direction upward bullet.setDirection(heart.x, heart.y - 100, heart.x, heart.y); bullets.push(bullet); game.addChild(bullet); // Visual feedback - flash yellow button tween(yellowButton, { tint: 0xffffff }, { duration: 100, onFinish: function onFinish() { tween(yellowButton, { tint: 0xffff00 }, { duration: 100 }); } }); return; // Don't process normal movement } // Handle yellow heart shooting if (heart.isYellow && bullets.length < maxBullets) { var bullet = new Bullet(); bullet.x = heart.x; bullet.y = heart.y; bullet.lastX = bullet.x; bullet.lastY = bullet.y; bullet.setDirection(x, y, heart.x, heart.y); bullets.push(bullet); game.addChild(bullet); return; // Don't process normal movement when shooting } isPlayerControlling = true; targetX = x; targetY = y; }; // Touch/mouse move event game.move = function (x, y, obj) { if (isPlayerControlling) { targetX = x; targetY = y; } }; // Touch/mouse up event game.up = function (x, y, obj) { isPlayerControlling = false; }; // Lives system var lives = 92; // Movement variables var heartSpeed = 8; var movementDirection = { x: 1, y: 0.5 }; // Initial movement direction var lastDirectionChangeTime = 0; var directionChangeInterval = 2000; // Change direction every 2 seconds // Gaster Blaster and beam system var gasterBlasters = []; var gasterBeams = []; var maxBlasters = 10; var blasterSpawnTimer = 0; var blasterSpawnInterval = 720; // Spawn every 12 seconds at 60fps var waveIntensity = 1; // Anchor system var anchors = []; var maxAnchors = 2; var anchorSpawnTimer = 0; var anchorSpawnInterval = 240; // Spawn every 4 seconds at 60fps // Ivy system var ivies = []; var maxIvies = 5; var ivySpawnTimer = 0; var ivySpawnInterval = 600; // Spawn every 10 seconds at 60fps // Slow effect system var isSlowed = false; var slowEndTime = 0; var normalHeartSpeed = 8; // Bullet system var bullets = []; var maxBullets = 20; // Function to spawn a new Gaster Blaster at random edge position function spawnGasterBlaster() { if (gasterBlasters.length >= maxBlasters) return; var blaster = new GasterBlaster(); var edge = Math.floor(Math.random() * 4); // 0=top, 1=right, 2=bottom, 3=left // Position blaster at random point on selected edge switch (edge) { case 0: // Top edge blaster.x = boxX + Math.random() * boxWidth; blaster.y = boxY - 100; break; case 1: // Right edge blaster.x = boxX + boxWidth + 100; blaster.y = boxY + Math.random() * boxHeight; break; case 2: // Bottom edge blaster.x = boxX + Math.random() * boxWidth; blaster.y = boxY + boxHeight + 100; break; case 3: // Left edge blaster.x = boxX - 100; blaster.y = boxY + Math.random() * boxHeight; break; } gasterBlasters.push(blaster); game.addChild(blaster); // Start charging to attack the heart blaster.startCharge(heart.x, heart.y); } // Function to spawn a new anchor at random position within the box function spawnAnchor() { if (anchors.length >= maxAnchors) return; var anchor = new Anchor(); // Position anchor within the box boundaries for horizontal movement var anchorHalfWidth = 60; // Half of 120px anchor width var startFromLeft = Math.random() < 0.5; if (startFromLeft) { anchor.x = boxX + 8 + anchorHalfWidth; // Start from left edge inside box anchor.movingRight = true; // Moving right } else { anchor.x = boxX + boxWidth - 8 - anchorHalfWidth; // Start from right edge inside box anchor.movingRight = false; // Moving left } // Random Y position within the box, constrained to keep full anchor height inside var anchorHalfHeight = 600; // Half of 1200px anchor height anchor.y = boxY + anchorHalfHeight + Math.random() * (boxHeight - 2 * anchorHalfHeight); anchor.lastX = anchor.x; anchor.lastY = anchor.y; anchors.push(anchor); game.addChild(anchor); } // Function to spawn a new ivy hanging from top of box like an anchor function spawnIvy() { if (ivies.length >= maxIvies) return; var ivy = new Ivy(); // Position ivy at the very top of box to move downward (anchor point is at top of ivy) ivy.y = boxY + 8; // Start from top edge inside box // Random X position within the box, constrained to keep full ivy width inside var ivyHalfWidth = 60; // Half of 120px ivy width ivy.x = boxX + ivyHalfWidth + Math.random() * (boxWidth - 2 * ivyHalfWidth); ivy.lastX = ivy.x; ivy.lastY = ivy.y; ivies.push(ivy); game.addChild(ivy); } // Game update loop game.update = function () { // Update timer every second (60 ticks = 1 second at 60fps) if (LK.ticks % 60 === 0 && gameTime > 0) { gameTime--; var minutes = Math.floor(gameTime / 60); var seconds = gameTime % 60; var timeString = minutes + ':' + (seconds < 10 ? '0' : '') + seconds; timerTxt.setText('Time: ' + timeString); } // Update health button cooldown if (!healthButtonEnabled) { healthButtonCooldown--; if (healthButtonCooldown <= 0) { healthButtonEnabled = true; healthButtonText.setText('HEALTH +20'); tween(healthButton, { tint: 0x00ff00 }, { duration: 300 }); } else { var cooldownSeconds = Math.ceil(healthButtonCooldown / 60); healthButtonText.setText('COOLDOWN ' + cooldownSeconds + 's'); } } // Check if Sans and Asgore are both dead for Flowey transformation if (sans.isDead && asgore.isDead && !flowey.isFinalForm && !flowey.isDead) { flowey.transformToFinalForm(); // Update ivy system for final form maxIvies = 10; flowey.attackInterval = 300; } // Increase wave intensity over time if (LK.ticks % 600 === 0) { // Every 10 seconds waveIntensity = Math.min(waveIntensity + 0.5, 4); maxBlasters = Math.min(Math.floor(2 + waveIntensity), 10); blasterSpawnInterval = Math.max(Math.floor(720 - waveIntensity * 120), 300); } // Update Sans attack timer and spawn Gaster Blasters (only if alive) if (!sans.isDead) { sans.attackTimer++; sans.greenAttackTimer++; // Check for green heart attack when timer reaches 12:00 (180 seconds elapsed) var elapsedTime = 900 - gameTime; // Calculate elapsed time in seconds var elapsedTicks = elapsedTime * 60; // Convert to ticks if (elapsedTicks >= sans.greenAttackInterval && elapsedTicks < sans.greenAttackInterval + sans.greenAttackDuration && !sans.isGreenAttack) { sans.isGreenAttack = true; heart.setGreenMode(true); // Position heart in center of box and disable movement heart.x = boxX + boxWidth / 2; heart.y = boxY + boxHeight / 2; heart.shieldX = 0; heart.shieldY = 0; } // End green heart attack when timer reaches 10:00 (300 seconds elapsed) if (sans.isGreenAttack && elapsedTicks >= sans.greenAttackInterval + sans.greenAttackDuration) { sans.isGreenAttack = false; heart.setGreenMode(false); } // Check for yellow heart attack when timer reaches 9:00 (360 seconds elapsed) if (elapsedTicks >= sans.yellowAttackInterval && elapsedTicks < sans.yellowAttackInterval + sans.yellowAttackDuration && !sans.isYellowAttack) { sans.isYellowAttack = true; heart.setYellowMode(true); } // End yellow heart attack when timer reaches 7:00 (480 seconds elapsed) if (sans.isYellowAttack && elapsedTicks >= sans.yellowAttackInterval + sans.yellowAttackDuration) { sans.isYellowAttack = false; heart.setYellowMode(false); } if (sans.attackTimer >= sans.attackInterval && !sans.isGreenAttack) { // Sans attack: make heart blue and enable physics heart.setBlueMode(true); heart.velocityY = 0; heart.onGround = false; heart.groundY = boxY + boxHeight - 8 - 50; // Ground level accounting for heart radius // Sans spawns Gaster Blasters var spawnCount = Math.random() < 0.4 ? Math.floor(waveIntensity) : 1; for (var s = 0; s < spawnCount && gasterBlasters.length < maxBlasters; s++) { spawnGasterBlaster(); } sans.attackTimer = 0; } } // Update Asgore attack timer and spawn anchors (only if alive) if (!asgore.isDead) { asgore.attackTimer++; if (asgore.attackTimer >= asgore.attackInterval) { // Asgore sends anchors spawnAnchor(); asgore.attackTimer = 0; } } // Update Flowey attack timer and spawn ivies (only if alive) if (!flowey.isDead) { flowey.attackTimer++; if (flowey.attackTimer >= flowey.attackInterval) { // Flowey sends ivies var spawnCount = flowey.isFinalForm ? 10 : Math.floor(Math.random() * 2) + 2; // Final form sends 10, normal sends 2-3 for (var i = 0; i < spawnCount && ivies.length < maxIvies; i++) { spawnIvy(); } flowey.attackTimer = 0; } } // Handle slow effect expiry if (isSlowed && LK.ticks >= slowEndTime) { isSlowed = false; heartSpeed = normalHeartSpeed; } // Update all Gaster Blasters and spawn beams when ready for (var i = gasterBlasters.length - 1; i >= 0; i--) { var blaster = gasterBlasters[i]; // Check if blaster is ready to fire if (blaster.isReady && blaster.beamTimer === 0) { var beam = blaster.fireBeam(); if (beam) { gasterBeams.push(beam); game.addChild(beam); } } // Remove blasters that have finished their attack cycle if (!blaster.isCharging && !blaster.isReady) { blaster.destroy(); gasterBlasters.splice(i, 1); continue; } } // Update all beams and check for collisions for (var j = gasterBeams.length - 1; j >= 0; j--) { var beam = gasterBeams[j]; var beamBlockedByShield = false; // Check if shield destroyed the beam in green mode if (heart.isGreen) { var shieldPos = heart.getShieldWorldPosition(); var shieldDistance = Math.sqrt(Math.pow(beam.x - shieldPos.x, 2) + Math.pow(beam.y - shieldPos.y, 2)); if (shieldDistance < 100) { beam.destroy(); gasterBeams.splice(j, 1); beamBlockedByShield = true; continue; } } // Check if beam hit the heart if (beam.intersects(heart) && !beamBlockedByShield) { // In green mode, don't take damage as shield should protect if (!heart.isGreen) { // Normal mode - always take damage lives -= beam.damage; livesTxt.setText('Lives: ' + lives); // Flash screen white to indicate beam damage LK.effects.flashScreen(0xffffff, 600); // Check for game over if (lives <= 0) { LK.showGameOver(); return; } } // Remove the beam that hit us beam.destroy(); gasterBeams.splice(j, 1); continue; } // Remove beams that are too far from the play area or inactive var distanceFromCenter = Math.sqrt(Math.pow(beam.x - (boxX + boxWidth / 2), 2) + Math.pow(beam.y - (boxY + boxHeight / 2), 2)); if (distanceFromCenter > 2000 || !beam.isActive) { beam.destroy(); gasterBeams.splice(j, 1); continue; } } // Update all anchors and check for collisions for (var j = anchors.length - 1; j >= 0; j--) { var anchor = anchors[j]; var anchorBlockedByShield = false; // Check if shield destroyed the anchor in green mode if (heart.isGreen) { var shieldPos = heart.getShieldWorldPosition(); var shieldDistance = Math.sqrt(Math.pow(anchor.x - shieldPos.x, 2) + Math.pow(anchor.y - shieldPos.y, 2)); if (shieldDistance < 100) { anchor.destroy(); anchors.splice(j, 1); anchorBlockedByShield = true; continue; } } // Check if anchor hit the heart if (anchor.intersects(heart) && !anchorBlockedByShield) { // In green mode, don't take damage as shield should protect if (!heart.isGreen) { var shouldTakeDamage = false; // Blue anchor: damage if player is moving if (anchor.isBlue && isPlayerControlling) { shouldTakeDamage = true; } // Orange anchor: damage if player is not moving else if (!anchor.isBlue && !isPlayerControlling) { shouldTakeDamage = true; } if (shouldTakeDamage) { // Reduce lives by anchor damage (2) lives -= anchor.damage; livesTxt.setText('Lives: ' + lives); // Flash screen purple to indicate anchor damage LK.effects.flashScreen(0x8800ff, 700); // Check for game over if (lives <= 0) { LK.showGameOver(); return; } } } // Remove the anchor that hit us anchor.destroy(); anchors.splice(j, 1); continue; } // Anchors now stay within box bounds, so no need to remove them for going off screen // They will only be removed when hitting the heart } // Update all ivies and check for collisions for (var k = ivies.length - 1; k >= 0; k--) { var ivy = ivies[k]; var ivyBlockedByShield = false; // Check if shield destroyed the ivy in green mode if (heart.isGreen) { var shieldPos = heart.getShieldWorldPosition(); var shieldDistance = Math.sqrt(Math.pow(ivy.x - shieldPos.x, 2) + Math.pow(ivy.y - shieldPos.y, 2)); if (shieldDistance < 100) { ivy.destroy(); ivies.splice(k, 1); ivyBlockedByShield = true; continue; } } // Check if ivy hit the heart if (ivy.intersects(heart) && !ivyBlockedByShield) { // In green mode, don't take damage as shield should protect if (!heart.isGreen) { // Normal mode - take damage and apply slow effect lives -= ivy.damage; livesTxt.setText('Lives: ' + lives); // Apply slow effect (25% speed reduction) isSlowed = true; heartSpeed = normalHeartSpeed * 0.75; slowEndTime = LK.ticks + 300; // Slow for 5 seconds at 60fps // Flash screen green to indicate ivy damage and slow LK.effects.flashScreen(0x00ff00, 600); // Check for game over if (lives <= 0) { LK.showGameOver(); return; } } // Remove the ivy that hit us ivy.destroy(); ivies.splice(k, 1); continue; } // Remove ivies that have reached the bottom of the box // Since ivy anchor is at top (anchorY: 0), check when ivy top + height reaches bottom if (ivy.y + 800 >= boxY + boxHeight - 8) { ivy.destroy(); ivies.splice(k, 1); continue; } } // Update all bullets and check for collisions with attacks for (var b = bullets.length - 1; b >= 0; b--) { var bullet = bullets[b]; // Check bullet collision with beams for (var beam_i = gasterBeams.length - 1; beam_i >= 0; beam_i--) { var beam = gasterBeams[beam_i]; if (bullet.intersects(beam)) { // Destroy both bullet and beam bullet.destroy(); bullets.splice(b, 1); beam.destroy(); gasterBeams.splice(beam_i, 1); continue; } } // Skip further checks if bullet was destroyed if (b >= bullets.length) continue; // Check bullet collision with anchors for (var anchor_i = anchors.length - 1; anchor_i >= 0; anchor_i--) { var anchor = anchors[anchor_i]; if (bullet.intersects(anchor)) { // Destroy both bullet and anchor bullet.destroy(); bullets.splice(b, 1); anchor.destroy(); anchors.splice(anchor_i, 1); continue; } } // Skip further checks if bullet was destroyed if (b >= bullets.length) continue; // Check bullet collision with ivies for (var ivy_i = ivies.length - 1; ivy_i >= 0; ivy_i--) { var ivy = ivies[ivy_i]; if (bullet.intersects(ivy)) { // Destroy both bullet and ivy bullet.destroy(); bullets.splice(b, 1); ivy.destroy(); ivies.splice(ivy_i, 1); continue; } } // Skip further checks if bullet was destroyed if (b >= bullets.length) continue; // Remove bullets that are too far from play area var bulletDistanceFromCenter = Math.sqrt(Math.pow(bullet.x - (boxX + boxWidth / 2), 2) + Math.pow(bullet.y - (boxY + boxHeight / 2), 2)); if (bulletDistanceFromCenter > 2000) { bullet.destroy(); bullets.splice(b, 1); continue; } } if (isPlayerControlling) { if (heart.isGreen) { // Green heart mode: heart stays in center, only shield moves heart.updateShieldPosition(targetX, targetY); } else if (heart.isYellow) { // Yellow heart mode: normal movement but shooting on click var deltaX = targetX - heart.x; var deltaY = targetY - heart.y; var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); if (distance > 5) { movementDirection.x = deltaX / distance; movementDirection.y = deltaY / distance; heart.x += movementDirection.x * heartSpeed; heart.y += movementDirection.y * heartSpeed; } } else if (heart.isBlue) { // Blue heart physics: left/right movement and jumping var deltaX = targetX - heart.x; // Horizontal movement if (Math.abs(deltaX) > 5) { var horizontalDirection = deltaX > 0 ? 1 : -1; heart.x += horizontalDirection * heartSpeed; } // Jump if touching upward and on ground if (targetY < heart.y - 20 && heart.onGround) { heart.velocityY = heart.jumpPower; heart.onGround = false; } } else { // Normal movement when not blue // Calculate direction toward target var deltaX = targetX - heart.x; var deltaY = targetY - heart.y; var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY); // Move toward target if not already there if (distance > 5) { // Normalize direction and apply speed movementDirection.x = deltaX / distance; movementDirection.y = deltaY / distance; heart.x += movementDirection.x * heartSpeed; heart.y += movementDirection.y * heartSpeed; } } } // Apply blue heart physics if (heart.isBlue) { // Apply gravity heart.velocityY += heart.gravity; heart.y += heart.velocityY; // Ground collision if (heart.y >= heart.groundY) { heart.y = heart.groundY; heart.velocityY = 0; heart.onGround = true; } // Reset blue heart after 4 seconds if (sans.attackTimer > 240) { // 4 seconds at 60fps heart.setBlueMode(false); sans.attackTimer = 0; // Reset timer to prevent staying blue } } // Ensure heart stays within bounds each frame constrainHeartPosition(); };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Anchor = Container.expand(function () {
var self = Container.call(this);
// Create anchor graphic
var anchorGraphics = self.attachAsset('anchor', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1,
scaleY: 10
});
self.isBlue = true; // true = blue (stop), false = orange (move)
self.damage = 6;
self.lastX = 0;
self.lastY = 0;
self.colorChangeTimer = 0;
self.colorChangeInterval = 180; // Change color every 3 seconds at 60fps
self.speed = 3;
self.movingRight = true;
// Update anchor color
self.updateColor = function () {
if (self.isBlue) {
anchorGraphics.tint = 0x0066ff; // Blue
} else {
anchorGraphics.tint = 0xff6600; // Orange
}
};
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
// Move horizontally within the box boundaries
var anchorHalfWidth = 60; // Half of 120px anchor width
var minX = boxX + 8 + anchorHalfWidth;
var maxX = boxX + boxWidth - 8 - anchorHalfWidth;
if (self.movingRight) {
self.x += self.speed; // Move right
if (self.x >= maxX) {
self.x = maxX;
self.movingRight = false; // Switch to moving left
}
} else {
self.x -= self.speed; // Move left
if (self.x <= minX) {
self.x = minX;
self.movingRight = true; // Switch to moving right
}
}
// Keep anchor within vertical bounds of the box
var anchorHalfHeight = 600; // Half of 1200px anchor height
var minY = boxY + 8 + anchorHalfHeight;
var maxY = boxY + boxHeight - 8 - anchorHalfHeight;
if (self.y < minY) {
self.y = minY;
}
if (self.y > maxY) {
self.y = maxY;
}
// Change color periodically
self.colorChangeTimer++;
if (self.colorChangeTimer >= self.colorChangeInterval) {
self.isBlue = !self.isBlue;
self.updateColor();
self.colorChangeTimer = 0;
}
};
// Initialize with blue color
self.updateColor();
return self;
});
var Asgore = Container.expand(function () {
var self = Container.call(this);
// Create Asgore using proper Asgore image asset
var asgoreGraphics = self.attachAsset('asgore', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1,
scaleY: 1
});
self.attackTimer = 0;
self.attackInterval = 240; // Attack every 4 seconds
self.blocksLeft = 12;
self.isDead = false;
self.block = function () {
if (self.blocksLeft > 0 && !self.isDead) {
self.blocksLeft--;
// Flash blue to indicate block
tween(asgoreGraphics, {
tint: 0x0066ff
}, {
duration: 100,
onFinish: function onFinish() {
tween(asgoreGraphics, {
tint: 0xffffff
}, {
duration: 100
});
}
});
if (self.blocksLeft <= 0) {
self.isDead = true;
tween(asgoreGraphics, {
alpha: 0.3
}, {
duration: 500
});
}
return true;
}
return false;
};
return self;
});
var BoxBoundary = Container.expand(function () {
var self = Container.call(this);
// Create four walls for the box
var topWall = self.attachAsset('boxBorder', {
anchorX: 0,
anchorY: 0,
width: 1600,
height: 8
});
var bottomWall = self.attachAsset('boxBorder', {
anchorX: 0,
anchorY: 0,
width: 1600,
height: 8
});
var leftWall = self.attachAsset('boxBorder', {
anchorX: 0,
anchorY: 0,
width: 8,
height: 1200
});
var rightWall = self.attachAsset('boxBorder', {
anchorX: 0,
anchorY: 0,
width: 8,
height: 1200
});
// Position walls
topWall.x = 0;
topWall.y = 0;
bottomWall.x = 0;
bottomWall.y = 1200 - 8;
leftWall.x = 0;
leftWall.y = 0;
rightWall.x = 1600 - 8;
rightWall.y = 0;
return self;
});
var Bullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 15;
self.directionX = 0;
self.directionY = 0;
self.lastX = 0;
self.lastY = 0;
self.setDirection = function (targetX, targetY, startX, startY) {
var deltaX = targetX - startX;
var deltaY = targetY - startY;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance > 0) {
self.directionX = deltaX / distance;
self.directionY = deltaY / distance;
}
};
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
self.x += self.directionX * self.speed;
self.y += self.directionY * self.speed;
};
return self;
});
var Flowey = Container.expand(function () {
var self = Container.call(this);
// Create Flowey using proper Flowey image asset
var floweyGraphics = self.attachAsset('flowey', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1,
scaleY: 1
});
self.attackTimer = 0;
self.attackInterval = 600; // Attack every 10 seconds
self.destroysLeft = 999; // Infinite until transformation
self.finalFormDestroysLeft = 5;
self.isFinalForm = false;
self.isDead = false;
self.destroyAttack = function () {
if (self.isDead) return false;
var destroysToUse = self.isFinalForm ? self.finalFormDestroysLeft : self.destroysLeft;
if (destroysToUse > 0) {
if (self.isFinalForm) {
self.finalFormDestroysLeft--;
if (self.finalFormDestroysLeft <= 0) {
self.isDead = true;
tween(floweyGraphics, {
alpha: 0.3
}, {
duration: 500
});
}
}
// Flash red to indicate destruction
tween(floweyGraphics, {
tint: 0xff0000
}, {
duration: 100,
onFinish: function onFinish() {
tween(floweyGraphics, {
tint: 0xffffff
}, {
duration: 100
});
}
});
return true;
}
return false;
};
self.transformToFinalForm = function () {
if (!self.isFinalForm) {
self.isFinalForm = true;
self.attackInterval = 300; // Attack more frequently
// Transform animation
tween(floweyGraphics, {
scaleX: 1.5,
scaleY: 1.5,
tint: 0xff00ff
}, {
duration: 1000,
easing: tween.easeOut
});
}
};
return self;
});
var GasterBeam = Container.expand(function () {
var self = Container.call(this);
// Create black border first (behind white beam)
var beamBorder = self.attachAsset('beamBorder', {
anchorX: 0.5,
anchorY: 0.5
});
// Create white beam on top
var whiteBeam = self.attachAsset('whiteBeam', {
anchorX: 0.5,
anchorY: 0.5
});
self.damage = 12; // Gaster beam damage
self.speed = 4;
self.lastX = 0;
self.lastY = 0;
self.targetX = 0;
self.targetY = 0;
self.isActive = false;
self.fireBeam = function (targetX, targetY) {
if (self.isActive) return;
self.isActive = true;
self.targetX = targetX;
self.targetY = targetY;
// Calculate direction to target
var deltaX = targetX - self.x;
var deltaY = targetY - self.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
// Set rotation to point towards target
self.rotation = Math.atan2(deltaY, deltaX) + Math.PI / 2; // Add 90 degrees because beam asset points up
// Move towards target
tween(self, {
x: targetX,
y: targetY
}, {
duration: distance / self.speed * 16,
// Adjust duration based on distance
easing: tween.linear,
onFinish: function onFinish() {
self.isActive = false;
}
});
};
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
};
return self;
});
var GasterBlaster = Container.expand(function () {
var self = Container.call(this);
// Create Gaster Blaster using image asset
var blasterGraphics = self.attachAsset('gasterBlaster', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1,
scaleY: 1
});
self.chargeTime = 60; // 1 second charge time at 60fps
self.chargeTimer = 0;
self.isCharging = false;
self.isReady = false;
self.beamDuration = 120; // 2 seconds beam duration
self.beamTimer = 0;
self.targetX = 0;
self.targetY = 0;
self.startCharge = function (targetX, targetY) {
if (self.isCharging || self.isReady) return;
self.isCharging = true;
self.targetX = targetX;
self.targetY = targetY;
self.chargeTimer = 0;
// Point towards target during charge
var deltaX = targetX - self.x;
var deltaY = targetY - self.y;
self.rotation = Math.atan2(deltaY, deltaX);
// Flash effect during charge
tween(blasterGraphics, {
alpha: 0.3
}, {
duration: 100,
easing: tween.linear,
onFinish: function onFinish() {
tween(blasterGraphics, {
alpha: 1
}, {
duration: 100,
easing: tween.linear
});
}
});
};
self.fireBeam = function () {
if (!self.isReady) return null;
var beam = new GasterBeam();
beam.x = self.x;
beam.y = self.y;
beam.lastX = beam.x;
beam.lastY = beam.y;
beam.fireBeam(self.targetX, self.targetY);
return beam;
};
self.update = function () {
if (self.isCharging) {
self.chargeTimer++;
if (self.chargeTimer >= self.chargeTime) {
self.isCharging = false;
self.isReady = true;
self.beamTimer = 0;
}
} else if (self.isReady) {
self.beamTimer++;
if (self.beamTimer >= self.beamDuration) {
self.isReady = false;
}
}
};
return self;
});
var Heart = Container.expand(function () {
var self = Container.call(this);
var heartGraphics = self.attachAsset('heart', {
anchorX: 0.5,
anchorY: 0.5
});
var blueHeartGraphics = self.attachAsset('blueHeart', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
var greenHeartGraphics = self.attachAsset('greenHeart', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
var yellowHeartGraphics = self.attachAsset('yellowHeart', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
var shieldGraphics = self.attachAsset('shield', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0,
scaleX: 2.0,
scaleY: 2.0
});
self.isBlue = false;
self.isGreen = false;
self.isYellow = false;
self.velocityY = 0;
self.gravity = 0.3;
self.jumpPower = -18;
self.onGround = false;
self.groundY = 0;
self.shieldX = 0;
self.shieldY = 0;
self.shieldRadius = 150; // Distance from heart center to shield center
self.setBlueMode = function (isBlue) {
self.isBlue = isBlue;
self.isGreen = false;
self.isYellow = false;
if (isBlue) {
heartGraphics.alpha = 0;
blueHeartGraphics.alpha = 1;
greenHeartGraphics.alpha = 0;
yellowHeartGraphics.alpha = 0;
shieldGraphics.alpha = 0;
} else {
heartGraphics.alpha = 1;
blueHeartGraphics.alpha = 0;
greenHeartGraphics.alpha = 0;
yellowHeartGraphics.alpha = 0;
shieldGraphics.alpha = 0;
}
};
self.setGreenMode = function (isGreen) {
self.isGreen = isGreen;
self.isBlue = false;
self.isYellow = false;
if (isGreen) {
heartGraphics.alpha = 0;
blueHeartGraphics.alpha = 0;
greenHeartGraphics.alpha = 1;
yellowHeartGraphics.alpha = 0;
shieldGraphics.alpha = 1;
} else {
heartGraphics.alpha = 1;
blueHeartGraphics.alpha = 0;
greenHeartGraphics.alpha = 0;
yellowHeartGraphics.alpha = 0;
shieldGraphics.alpha = 0;
}
};
self.setYellowMode = function (isYellow) {
self.isYellow = isYellow;
self.isBlue = false;
self.isGreen = false;
if (isYellow) {
heartGraphics.alpha = 0;
blueHeartGraphics.alpha = 0;
greenHeartGraphics.alpha = 0;
yellowHeartGraphics.alpha = 1;
shieldGraphics.alpha = 0;
} else {
heartGraphics.alpha = 1;
blueHeartGraphics.alpha = 0;
greenHeartGraphics.alpha = 0;
yellowHeartGraphics.alpha = 0;
shieldGraphics.alpha = 0;
}
};
self.updateShieldPosition = function (targetX, targetY) {
if (self.isGreen) {
// Calculate angle from heart to target
var deltaX = targetX - self.x;
var deltaY = targetY - self.y;
var angle = Math.atan2(deltaY, deltaX);
// Position shield at fixed radius around heart
self.shieldX = Math.cos(angle) * self.shieldRadius;
self.shieldY = Math.sin(angle) * self.shieldRadius;
// Update shield graphics position
shieldGraphics.x = self.shieldX;
shieldGraphics.y = self.shieldY;
}
};
self.getShieldWorldPosition = function () {
return {
x: self.x + self.shieldX,
y: self.y + self.shieldY
};
};
return self;
});
var Ivy = Container.expand(function () {
var self = Container.call(this);
// Create ivy graphic - anchor from top to hang down
var ivyGraphics = self.attachAsset('ivy', {
anchorX: 0.5,
anchorY: 0
});
self.damage = 5;
self.speed = 3;
self.lastX = 0;
self.lastY = 0;
self.movingRight = true;
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
// Move downward from top to bottom of box
self.y += self.speed; // Always move down
};
return self;
});
var Sans = Container.expand(function () {
var self = Container.call(this);
// Create Sans using proper Sans image asset
var sansGraphics = self.attachAsset('sans', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1,
scaleY: 1
});
self.attackTimer = 0;
self.attackInterval = 720; // Attack every 12 seconds
self.greenAttackTimer = 0;
self.greenAttackInterval = 10800; // Start at 12:00 timer (180 seconds * 60 fps)
self.greenAttackDuration = 7200; // 2 minutes duration from 12:00 to 10:00 (120 seconds * 60 fps)
self.isGreenAttack = false;
self.yellowAttackTimer = 0;
self.yellowAttackInterval = 21600; // Start at 9:00 timer (360 seconds * 60 fps)
self.yellowAttackDuration = 7200; // 2 minute duration from 9:00 to 7:00 (120 seconds * 60 fps)
self.isYellowAttack = false;
self.dodgesLeft = 10;
self.isDead = false;
self.originalX = 0;
self.originalY = 0;
self.dodge = function () {
if (self.dodgesLeft > 0 && !self.isDead) {
self.dodgesLeft--;
// Move to random position near original location
var dodgeDistance = 100;
var angle = Math.random() * Math.PI * 2;
var newX = self.originalX + Math.cos(angle) * dodgeDistance;
var newY = self.originalY + Math.sin(angle) * dodgeDistance;
tween(self, {
x: newX,
y: newY
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
// Return to original position
tween(self, {
x: self.originalX,
y: self.originalY
}, {
duration: 300,
easing: tween.easeIn
});
}
});
if (self.dodgesLeft <= 0) {
self.isDead = true;
tween(sansGraphics, {
alpha: 0.3
}, {
duration: 500
});
}
return true;
}
return false;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xf0f8ff
});
/****
* Game Code
****/
// Box dimensions
var boxX = 224; // Center the 1600px box in 2048px screen
var boxY = 766; // Center the 1200px box in 2732px screen
var boxWidth = 1600;
var boxHeight = 1200;
// Create box boundary
var boundary = game.addChild(new BoxBoundary());
boundary.x = boxX;
boundary.y = boxY;
// Create heart character
var heart = game.addChild(new Heart());
heart.x = boxX + boxWidth / 2; // Center of box
heart.y = boxY + boxHeight / 2; // Center of box
// Create the three characters
// Sans on the left side (bone thrower)
var sans = game.addChild(new Sans());
sans.x = boxX - 150; // Left side of box
sans.y = boxY + boxHeight / 2; // Middle height
// Asgore in the middle (anchor owner)
var asgore = game.addChild(new Asgore());
asgore.x = boxX + boxWidth / 2; // Center of box horizontally
asgore.y = boxY - 150; // Above the box
// Flowey on the right side (ivy sender)
var flowey = game.addChild(new Flowey());
flowey.x = boxX + boxWidth + 150; // Right side of box
flowey.y = boxY + boxHeight / 2; // Middle height
// Create lives display
var livesTxt = new Text2('Lives: 92', {
size: 80,
fill: 0x000000
});
livesTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(livesTxt);
// Create timer display
var gameTime = 900; // 15 minutes in seconds
var timerTxt = new Text2('Time: 15:00', {
size: 80,
fill: 0x000000
});
timerTxt.anchor.set(1, 0); // Right align
LK.gui.topRight.addChild(timerTxt);
// Create "THREE LEFT" text in red under timer
var threeTxt = new Text2('THREE LEFT', {
size: 60,
fill: 0xff0000
});
threeTxt.anchor.set(1, 0); // Right align
threeTxt.y = 100; // Position below timer
LK.gui.topRight.addChild(threeTxt);
// Create health button
var healthButton = LK.getAsset('healthButton', {
anchorX: 0.5,
anchorY: 0.5
});
healthButton.x = boxX + boxWidth / 2; // Center horizontally with the box
healthButton.y = boxY + boxHeight + 100; // Position below the box (outside)
game.addChild(healthButton);
// Create health button text
var healthButtonText = new Text2('HEALTH +20', {
size: 40,
fill: 0x000000
});
healthButtonText.anchor.set(0.5, 0.5);
healthButtonText.x = healthButton.x;
healthButtonText.y = healthButton.y;
game.addChild(healthButtonText);
// Create yellow heart special button
var yellowButton = LK.getAsset('healthButton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
yellowButton.x = boxX + boxWidth / 2; // Position directly below health button
yellowButton.y = boxY + boxHeight + 200; // Position below the health button
yellowButton.tint = 0xffff00; // Yellow color
game.addChild(yellowButton);
// Create yellow button text
var yellowButtonText = new Text2('YELLOW SHOT', {
size: 40,
fill: 0x000000
});
yellowButtonText.anchor.set(0.5, 0.5);
yellowButtonText.x = yellowButton.x;
yellowButtonText.y = yellowButton.y;
game.addChild(yellowButtonText);
// Health button cooldown variables
var healthButtonCooldown = 0;
var healthButtonMaxCooldown = 1200; // 20 seconds at 60fps
var healthButtonEnabled = true;
// Store original positions for characters
sans.originalX = sans.x;
sans.originalY = sans.y;
// Dragging variables
var isDragging = false;
var dragOffsetX = 0;
var dragOffsetY = 0;
// Constraint function to keep heart within boundaries
function constrainHeartPosition() {
var heartRadius = 50; // Half of heart width/height
// Left boundary
if (heart.x - heartRadius < boxX + 8) {
heart.x = boxX + 8 + heartRadius;
}
// Right boundary
if (heart.x + heartRadius > boxX + boxWidth - 8) {
heart.x = boxX + boxWidth - 8 - heartRadius;
}
// Top boundary
if (heart.y - heartRadius < boxY + 8) {
heart.y = boxY + 8 + heartRadius;
}
// Bottom boundary
if (heart.y + heartRadius > boxY + boxHeight - 8) {
heart.y = boxY + boxHeight - 8 - heartRadius;
}
}
// Player control variables
var targetX = 0;
var targetY = 0;
var isPlayerControlling = false;
// Touch/mouse down event
game.down = function (x, y, obj) {
// Check if health button was clicked
var buttonDistance = Math.sqrt(Math.pow(x - healthButton.x, 2) + Math.pow(y - healthButton.y, 2));
if (buttonDistance < 100 && healthButtonEnabled) {
// Increase health by 20
lives += 20;
livesTxt.setText('Lives: ' + lives);
// Start cooldown
healthButtonCooldown = healthButtonMaxCooldown;
healthButtonEnabled = false;
// Visual feedback - flash button
tween(healthButton, {
tint: 0xffff00
}, {
duration: 200,
onFinish: function onFinish() {
tween(healthButton, {
tint: 0x888888
}, {
duration: 200
});
}
});
// Update button text to show cooldown
healthButtonText.setText('COOLDOWN');
return; // Don't process normal movement
}
// Check if yellow button was clicked
var yellowButtonDistance = Math.sqrt(Math.pow(x - yellowButton.x, 2) + Math.pow(y - yellowButton.y, 2));
if (yellowButtonDistance < 100 && heart.isYellow && bullets.length < maxBullets) {
var bullet = new Bullet();
bullet.x = heart.x;
bullet.y = heart.y;
bullet.lastX = bullet.x;
bullet.lastY = bullet.y;
// Set direction upward
bullet.setDirection(heart.x, heart.y - 100, heart.x, heart.y);
bullets.push(bullet);
game.addChild(bullet);
// Visual feedback - flash yellow button
tween(yellowButton, {
tint: 0xffffff
}, {
duration: 100,
onFinish: function onFinish() {
tween(yellowButton, {
tint: 0xffff00
}, {
duration: 100
});
}
});
return; // Don't process normal movement
}
// Handle yellow heart shooting
if (heart.isYellow && bullets.length < maxBullets) {
var bullet = new Bullet();
bullet.x = heart.x;
bullet.y = heart.y;
bullet.lastX = bullet.x;
bullet.lastY = bullet.y;
bullet.setDirection(x, y, heart.x, heart.y);
bullets.push(bullet);
game.addChild(bullet);
return; // Don't process normal movement when shooting
}
isPlayerControlling = true;
targetX = x;
targetY = y;
};
// Touch/mouse move event
game.move = function (x, y, obj) {
if (isPlayerControlling) {
targetX = x;
targetY = y;
}
};
// Touch/mouse up event
game.up = function (x, y, obj) {
isPlayerControlling = false;
};
// Lives system
var lives = 92;
// Movement variables
var heartSpeed = 8;
var movementDirection = {
x: 1,
y: 0.5
}; // Initial movement direction
var lastDirectionChangeTime = 0;
var directionChangeInterval = 2000; // Change direction every 2 seconds
// Gaster Blaster and beam system
var gasterBlasters = [];
var gasterBeams = [];
var maxBlasters = 10;
var blasterSpawnTimer = 0;
var blasterSpawnInterval = 720; // Spawn every 12 seconds at 60fps
var waveIntensity = 1;
// Anchor system
var anchors = [];
var maxAnchors = 2;
var anchorSpawnTimer = 0;
var anchorSpawnInterval = 240; // Spawn every 4 seconds at 60fps
// Ivy system
var ivies = [];
var maxIvies = 5;
var ivySpawnTimer = 0;
var ivySpawnInterval = 600; // Spawn every 10 seconds at 60fps
// Slow effect system
var isSlowed = false;
var slowEndTime = 0;
var normalHeartSpeed = 8;
// Bullet system
var bullets = [];
var maxBullets = 20;
// Function to spawn a new Gaster Blaster at random edge position
function spawnGasterBlaster() {
if (gasterBlasters.length >= maxBlasters) return;
var blaster = new GasterBlaster();
var edge = Math.floor(Math.random() * 4); // 0=top, 1=right, 2=bottom, 3=left
// Position blaster at random point on selected edge
switch (edge) {
case 0:
// Top edge
blaster.x = boxX + Math.random() * boxWidth;
blaster.y = boxY - 100;
break;
case 1:
// Right edge
blaster.x = boxX + boxWidth + 100;
blaster.y = boxY + Math.random() * boxHeight;
break;
case 2:
// Bottom edge
blaster.x = boxX + Math.random() * boxWidth;
blaster.y = boxY + boxHeight + 100;
break;
case 3:
// Left edge
blaster.x = boxX - 100;
blaster.y = boxY + Math.random() * boxHeight;
break;
}
gasterBlasters.push(blaster);
game.addChild(blaster);
// Start charging to attack the heart
blaster.startCharge(heart.x, heart.y);
}
// Function to spawn a new anchor at random position within the box
function spawnAnchor() {
if (anchors.length >= maxAnchors) return;
var anchor = new Anchor();
// Position anchor within the box boundaries for horizontal movement
var anchorHalfWidth = 60; // Half of 120px anchor width
var startFromLeft = Math.random() < 0.5;
if (startFromLeft) {
anchor.x = boxX + 8 + anchorHalfWidth; // Start from left edge inside box
anchor.movingRight = true; // Moving right
} else {
anchor.x = boxX + boxWidth - 8 - anchorHalfWidth; // Start from right edge inside box
anchor.movingRight = false; // Moving left
}
// Random Y position within the box, constrained to keep full anchor height inside
var anchorHalfHeight = 600; // Half of 1200px anchor height
anchor.y = boxY + anchorHalfHeight + Math.random() * (boxHeight - 2 * anchorHalfHeight);
anchor.lastX = anchor.x;
anchor.lastY = anchor.y;
anchors.push(anchor);
game.addChild(anchor);
}
// Function to spawn a new ivy hanging from top of box like an anchor
function spawnIvy() {
if (ivies.length >= maxIvies) return;
var ivy = new Ivy();
// Position ivy at the very top of box to move downward (anchor point is at top of ivy)
ivy.y = boxY + 8; // Start from top edge inside box
// Random X position within the box, constrained to keep full ivy width inside
var ivyHalfWidth = 60; // Half of 120px ivy width
ivy.x = boxX + ivyHalfWidth + Math.random() * (boxWidth - 2 * ivyHalfWidth);
ivy.lastX = ivy.x;
ivy.lastY = ivy.y;
ivies.push(ivy);
game.addChild(ivy);
}
// Game update loop
game.update = function () {
// Update timer every second (60 ticks = 1 second at 60fps)
if (LK.ticks % 60 === 0 && gameTime > 0) {
gameTime--;
var minutes = Math.floor(gameTime / 60);
var seconds = gameTime % 60;
var timeString = minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
timerTxt.setText('Time: ' + timeString);
}
// Update health button cooldown
if (!healthButtonEnabled) {
healthButtonCooldown--;
if (healthButtonCooldown <= 0) {
healthButtonEnabled = true;
healthButtonText.setText('HEALTH +20');
tween(healthButton, {
tint: 0x00ff00
}, {
duration: 300
});
} else {
var cooldownSeconds = Math.ceil(healthButtonCooldown / 60);
healthButtonText.setText('COOLDOWN ' + cooldownSeconds + 's');
}
}
// Check if Sans and Asgore are both dead for Flowey transformation
if (sans.isDead && asgore.isDead && !flowey.isFinalForm && !flowey.isDead) {
flowey.transformToFinalForm();
// Update ivy system for final form
maxIvies = 10;
flowey.attackInterval = 300;
}
// Increase wave intensity over time
if (LK.ticks % 600 === 0) {
// Every 10 seconds
waveIntensity = Math.min(waveIntensity + 0.5, 4);
maxBlasters = Math.min(Math.floor(2 + waveIntensity), 10);
blasterSpawnInterval = Math.max(Math.floor(720 - waveIntensity * 120), 300);
}
// Update Sans attack timer and spawn Gaster Blasters (only if alive)
if (!sans.isDead) {
sans.attackTimer++;
sans.greenAttackTimer++;
// Check for green heart attack when timer reaches 12:00 (180 seconds elapsed)
var elapsedTime = 900 - gameTime; // Calculate elapsed time in seconds
var elapsedTicks = elapsedTime * 60; // Convert to ticks
if (elapsedTicks >= sans.greenAttackInterval && elapsedTicks < sans.greenAttackInterval + sans.greenAttackDuration && !sans.isGreenAttack) {
sans.isGreenAttack = true;
heart.setGreenMode(true);
// Position heart in center of box and disable movement
heart.x = boxX + boxWidth / 2;
heart.y = boxY + boxHeight / 2;
heart.shieldX = 0;
heart.shieldY = 0;
}
// End green heart attack when timer reaches 10:00 (300 seconds elapsed)
if (sans.isGreenAttack && elapsedTicks >= sans.greenAttackInterval + sans.greenAttackDuration) {
sans.isGreenAttack = false;
heart.setGreenMode(false);
}
// Check for yellow heart attack when timer reaches 9:00 (360 seconds elapsed)
if (elapsedTicks >= sans.yellowAttackInterval && elapsedTicks < sans.yellowAttackInterval + sans.yellowAttackDuration && !sans.isYellowAttack) {
sans.isYellowAttack = true;
heart.setYellowMode(true);
}
// End yellow heart attack when timer reaches 7:00 (480 seconds elapsed)
if (sans.isYellowAttack && elapsedTicks >= sans.yellowAttackInterval + sans.yellowAttackDuration) {
sans.isYellowAttack = false;
heart.setYellowMode(false);
}
if (sans.attackTimer >= sans.attackInterval && !sans.isGreenAttack) {
// Sans attack: make heart blue and enable physics
heart.setBlueMode(true);
heart.velocityY = 0;
heart.onGround = false;
heart.groundY = boxY + boxHeight - 8 - 50; // Ground level accounting for heart radius
// Sans spawns Gaster Blasters
var spawnCount = Math.random() < 0.4 ? Math.floor(waveIntensity) : 1;
for (var s = 0; s < spawnCount && gasterBlasters.length < maxBlasters; s++) {
spawnGasterBlaster();
}
sans.attackTimer = 0;
}
}
// Update Asgore attack timer and spawn anchors (only if alive)
if (!asgore.isDead) {
asgore.attackTimer++;
if (asgore.attackTimer >= asgore.attackInterval) {
// Asgore sends anchors
spawnAnchor();
asgore.attackTimer = 0;
}
}
// Update Flowey attack timer and spawn ivies (only if alive)
if (!flowey.isDead) {
flowey.attackTimer++;
if (flowey.attackTimer >= flowey.attackInterval) {
// Flowey sends ivies
var spawnCount = flowey.isFinalForm ? 10 : Math.floor(Math.random() * 2) + 2; // Final form sends 10, normal sends 2-3
for (var i = 0; i < spawnCount && ivies.length < maxIvies; i++) {
spawnIvy();
}
flowey.attackTimer = 0;
}
}
// Handle slow effect expiry
if (isSlowed && LK.ticks >= slowEndTime) {
isSlowed = false;
heartSpeed = normalHeartSpeed;
}
// Update all Gaster Blasters and spawn beams when ready
for (var i = gasterBlasters.length - 1; i >= 0; i--) {
var blaster = gasterBlasters[i];
// Check if blaster is ready to fire
if (blaster.isReady && blaster.beamTimer === 0) {
var beam = blaster.fireBeam();
if (beam) {
gasterBeams.push(beam);
game.addChild(beam);
}
}
// Remove blasters that have finished their attack cycle
if (!blaster.isCharging && !blaster.isReady) {
blaster.destroy();
gasterBlasters.splice(i, 1);
continue;
}
}
// Update all beams and check for collisions
for (var j = gasterBeams.length - 1; j >= 0; j--) {
var beam = gasterBeams[j];
var beamBlockedByShield = false;
// Check if shield destroyed the beam in green mode
if (heart.isGreen) {
var shieldPos = heart.getShieldWorldPosition();
var shieldDistance = Math.sqrt(Math.pow(beam.x - shieldPos.x, 2) + Math.pow(beam.y - shieldPos.y, 2));
if (shieldDistance < 100) {
beam.destroy();
gasterBeams.splice(j, 1);
beamBlockedByShield = true;
continue;
}
}
// Check if beam hit the heart
if (beam.intersects(heart) && !beamBlockedByShield) {
// In green mode, don't take damage as shield should protect
if (!heart.isGreen) {
// Normal mode - always take damage
lives -= beam.damage;
livesTxt.setText('Lives: ' + lives);
// Flash screen white to indicate beam damage
LK.effects.flashScreen(0xffffff, 600);
// Check for game over
if (lives <= 0) {
LK.showGameOver();
return;
}
}
// Remove the beam that hit us
beam.destroy();
gasterBeams.splice(j, 1);
continue;
}
// Remove beams that are too far from the play area or inactive
var distanceFromCenter = Math.sqrt(Math.pow(beam.x - (boxX + boxWidth / 2), 2) + Math.pow(beam.y - (boxY + boxHeight / 2), 2));
if (distanceFromCenter > 2000 || !beam.isActive) {
beam.destroy();
gasterBeams.splice(j, 1);
continue;
}
}
// Update all anchors and check for collisions
for (var j = anchors.length - 1; j >= 0; j--) {
var anchor = anchors[j];
var anchorBlockedByShield = false;
// Check if shield destroyed the anchor in green mode
if (heart.isGreen) {
var shieldPos = heart.getShieldWorldPosition();
var shieldDistance = Math.sqrt(Math.pow(anchor.x - shieldPos.x, 2) + Math.pow(anchor.y - shieldPos.y, 2));
if (shieldDistance < 100) {
anchor.destroy();
anchors.splice(j, 1);
anchorBlockedByShield = true;
continue;
}
}
// Check if anchor hit the heart
if (anchor.intersects(heart) && !anchorBlockedByShield) {
// In green mode, don't take damage as shield should protect
if (!heart.isGreen) {
var shouldTakeDamage = false;
// Blue anchor: damage if player is moving
if (anchor.isBlue && isPlayerControlling) {
shouldTakeDamage = true;
}
// Orange anchor: damage if player is not moving
else if (!anchor.isBlue && !isPlayerControlling) {
shouldTakeDamage = true;
}
if (shouldTakeDamage) {
// Reduce lives by anchor damage (2)
lives -= anchor.damage;
livesTxt.setText('Lives: ' + lives);
// Flash screen purple to indicate anchor damage
LK.effects.flashScreen(0x8800ff, 700);
// Check for game over
if (lives <= 0) {
LK.showGameOver();
return;
}
}
}
// Remove the anchor that hit us
anchor.destroy();
anchors.splice(j, 1);
continue;
}
// Anchors now stay within box bounds, so no need to remove them for going off screen
// They will only be removed when hitting the heart
}
// Update all ivies and check for collisions
for (var k = ivies.length - 1; k >= 0; k--) {
var ivy = ivies[k];
var ivyBlockedByShield = false;
// Check if shield destroyed the ivy in green mode
if (heart.isGreen) {
var shieldPos = heart.getShieldWorldPosition();
var shieldDistance = Math.sqrt(Math.pow(ivy.x - shieldPos.x, 2) + Math.pow(ivy.y - shieldPos.y, 2));
if (shieldDistance < 100) {
ivy.destroy();
ivies.splice(k, 1);
ivyBlockedByShield = true;
continue;
}
}
// Check if ivy hit the heart
if (ivy.intersects(heart) && !ivyBlockedByShield) {
// In green mode, don't take damage as shield should protect
if (!heart.isGreen) {
// Normal mode - take damage and apply slow effect
lives -= ivy.damage;
livesTxt.setText('Lives: ' + lives);
// Apply slow effect (25% speed reduction)
isSlowed = true;
heartSpeed = normalHeartSpeed * 0.75;
slowEndTime = LK.ticks + 300; // Slow for 5 seconds at 60fps
// Flash screen green to indicate ivy damage and slow
LK.effects.flashScreen(0x00ff00, 600);
// Check for game over
if (lives <= 0) {
LK.showGameOver();
return;
}
}
// Remove the ivy that hit us
ivy.destroy();
ivies.splice(k, 1);
continue;
}
// Remove ivies that have reached the bottom of the box
// Since ivy anchor is at top (anchorY: 0), check when ivy top + height reaches bottom
if (ivy.y + 800 >= boxY + boxHeight - 8) {
ivy.destroy();
ivies.splice(k, 1);
continue;
}
}
// Update all bullets and check for collisions with attacks
for (var b = bullets.length - 1; b >= 0; b--) {
var bullet = bullets[b];
// Check bullet collision with beams
for (var beam_i = gasterBeams.length - 1; beam_i >= 0; beam_i--) {
var beam = gasterBeams[beam_i];
if (bullet.intersects(beam)) {
// Destroy both bullet and beam
bullet.destroy();
bullets.splice(b, 1);
beam.destroy();
gasterBeams.splice(beam_i, 1);
continue;
}
}
// Skip further checks if bullet was destroyed
if (b >= bullets.length) continue;
// Check bullet collision with anchors
for (var anchor_i = anchors.length - 1; anchor_i >= 0; anchor_i--) {
var anchor = anchors[anchor_i];
if (bullet.intersects(anchor)) {
// Destroy both bullet and anchor
bullet.destroy();
bullets.splice(b, 1);
anchor.destroy();
anchors.splice(anchor_i, 1);
continue;
}
}
// Skip further checks if bullet was destroyed
if (b >= bullets.length) continue;
// Check bullet collision with ivies
for (var ivy_i = ivies.length - 1; ivy_i >= 0; ivy_i--) {
var ivy = ivies[ivy_i];
if (bullet.intersects(ivy)) {
// Destroy both bullet and ivy
bullet.destroy();
bullets.splice(b, 1);
ivy.destroy();
ivies.splice(ivy_i, 1);
continue;
}
}
// Skip further checks if bullet was destroyed
if (b >= bullets.length) continue;
// Remove bullets that are too far from play area
var bulletDistanceFromCenter = Math.sqrt(Math.pow(bullet.x - (boxX + boxWidth / 2), 2) + Math.pow(bullet.y - (boxY + boxHeight / 2), 2));
if (bulletDistanceFromCenter > 2000) {
bullet.destroy();
bullets.splice(b, 1);
continue;
}
}
if (isPlayerControlling) {
if (heart.isGreen) {
// Green heart mode: heart stays in center, only shield moves
heart.updateShieldPosition(targetX, targetY);
} else if (heart.isYellow) {
// Yellow heart mode: normal movement but shooting on click
var deltaX = targetX - heart.x;
var deltaY = targetY - heart.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance > 5) {
movementDirection.x = deltaX / distance;
movementDirection.y = deltaY / distance;
heart.x += movementDirection.x * heartSpeed;
heart.y += movementDirection.y * heartSpeed;
}
} else if (heart.isBlue) {
// Blue heart physics: left/right movement and jumping
var deltaX = targetX - heart.x;
// Horizontal movement
if (Math.abs(deltaX) > 5) {
var horizontalDirection = deltaX > 0 ? 1 : -1;
heart.x += horizontalDirection * heartSpeed;
}
// Jump if touching upward and on ground
if (targetY < heart.y - 20 && heart.onGround) {
heart.velocityY = heart.jumpPower;
heart.onGround = false;
}
} else {
// Normal movement when not blue
// Calculate direction toward target
var deltaX = targetX - heart.x;
var deltaY = targetY - heart.y;
var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
// Move toward target if not already there
if (distance > 5) {
// Normalize direction and apply speed
movementDirection.x = deltaX / distance;
movementDirection.y = deltaY / distance;
heart.x += movementDirection.x * heartSpeed;
heart.y += movementDirection.y * heartSpeed;
}
}
}
// Apply blue heart physics
if (heart.isBlue) {
// Apply gravity
heart.velocityY += heart.gravity;
heart.y += heart.velocityY;
// Ground collision
if (heart.y >= heart.groundY) {
heart.y = heart.groundY;
heart.velocityY = 0;
heart.onGround = true;
}
// Reset blue heart after 4 seconds
if (sans.attackTimer > 240) {
// 4 seconds at 60fps
heart.setBlueMode(false);
sans.attackTimer = 0; // Reset timer to prevent staying blue
}
}
// Ensure heart stays within bounds each frame
constrainHeartPosition();
};
Undertale Heart. In-Game asset. 2d. High contrast. No shadows
Undertale bone with black outline. In-Game asset. 2d. High contrast. No shadows
Undertale asgore spear. In-Game asset. 2d. High contrast. No shadows
Ivy. In-Game asset. 2d. High contrast. No shadows
Sans Undertale. In-Game asset. 2d. High contrast. No shadows
Asgore Undertale. In-Game asset. 2d. High contrast. No shadows
Flowey Undertale but with 6 souls (Orange,Green,Yellow,blue,Purple, Light Blue). In-Game asset. 2d. High contrast. No shadows
Flat staring gaster blaster Undertale. In-Game asset. 2d. High contrast. No shadows
Undertale heart but blue. In-Game asset. 2d. High contrast. No shadows
Green Heart Undertale. In-Game asset. 2d. High contrast. No shadows
Yellow heart Undertale. In-Game asset. 2d. High contrast. No shadows