Code edit (2 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Button2 is not defined' in or related to this line: 'var startButton = new Button2("Start Game", function () {' Line Number: 574
User prompt
Please fix the bug: 'clearScene is not defined' in or related to this line: 'clearScene();' Line Number: 555
Code edit (2 edits merged)
Please save this source code
User prompt
Please fix the bug: 'Button2 is not defined' in or related to this line: 'var startButton = new Button2("Start Game", function () {' Line Number: 563
User prompt
Please fix the bug: 'clearScene is not defined' in or related to this line: 'clearScene();' Line Number: 555
Code edit (1 edits merged)
Please save this source code
User prompt
Roll Souls
Initial prompt
reate a complete 2D meme parody game called Roll Souls using only placeholder graphics (squares, text, colors). The game should have a full flow from main menu to final boss and ending, all based around rolling to survive. Do not include custom art. Only use simple placeholder visuals like white square = player, red square = boss, gray = background, orange = grill, etc. The game flow should work like this: Main Menu Scene Name: mainMenuScene Show dark background Text: Welcome, Unchosen One. Button: Start Game → goes to introScene Intro Scene Name: introScene Show text: From the creators of FromSoftware... After 5 seconds, replace with: ...I have no idea. I don’t know them. After 2 more seconds → go to howToScene Fake Tutorial Scene Name: howToScene Show black screen Text: Press E to block. Or don’t press. If player presses E → Show YOU DIED Text: Did you check the title of the game? Button: How to Play AGAIN → goes to realTutorialScene If player does nothing for 6 seconds → auto go to realTutorialScene Real Tutorial Scene Name: realTutorialScene Show 3 tutorial messages: Click Left Mouse Button to ROLL toward the cursor. You can roll up to 3 times before stamina runs out. That’s it. That’s the whole game. Button: Let’s Roll → goes to boss1Scene Boss 1 Scene Name: boss1Scene Player: white square Player always moves slowly toward the mouse cursor Left-click = roll in direction of cursor Max 3 rolls, then cooldown Boss: red square, stands still, shoots blue bullets at player Player dies in 1 hit unless they have extra hearts Boss HP goes down slowly over 2 minutes If player survives 2 minutes → show GRILL LIT, go to grillScene HP/Heart System: Player starts with 1 heart = dies in 1 hit Every 5 total deaths, the player gains +1 heart (permanent) After 5 deaths = 2 hearts After 10 deaths = 3 hearts After 15 deaths = 4 hearts Taking damage removes 1 heart. If hearts = 0, show YOU DIED Grill Scene (Hub) Name: grillScene Background with orange square = grill Text: GRILL LIT Show 3 buttons: Upgrade Roll → shows random silly text like “You feel... no different.” Rest at Grill → shows “Resting...” Final Boss → goes to boss2Scene Final Boss Scene Name: boss2Scene Harder boss: faster bullets, less warning Same roll mechanics, same 2-minute survival Player movement = same: follows mouse If player survives → go to endingScene Ending Scene Name: endingScene Zoom in on player at grill Show text: TRUE ENDING UNLOCKED You have conquered all... but was it worth it? Thanks for playing Roll Souls. Try touching grass now. Optional fake buttons: New Game+, Summon Friend (does nothing) ✅ Use only placeholder graphics ✅ The game starts at mainMenuScene and must flow all the way to endingScene ✅ Player always moves toward cursor ✅ Full heart system included ✅ Every scene and transition should be implemented
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { totalDeaths: 0, maxHearts: 3, currentLevel: 0, bossesDefeated: 0 }); /**** * Classes ****/ var Boss = Container.expand(function () { var self = Container.call(this); var bossGraphics = self.attachAsset('boss', { anchorX: 0.5, anchorY: 0.5 }); self.health = 100; self.maxHealth = 100; self.speed = 5; self.attackCooldown = 0; self.attackPattern = 0; self.attacks = []; self.stunned = false; self.stunDuration = 0; self.dead = false; self.phase = 1; self.startAttackPattern = function () { if (self.dead) { return; } self.attackPattern = (self.attackPattern + 1) % 3; switch (self.attackPattern) { case 0: self.circleAttack(); break; case 1: self.lineAttack(); break; case 2: self.chargeAttack(); break; } // Schedule next attack self.attackCooldown = 90 + Math.floor(Math.random() * 60); // 1.5-2.5 seconds }; self.circleAttack = function () { LK.getSound('bossAttack').play(); var center = { x: self.x, y: self.y }; var count = 8; var radius = 300; for (var i = 0; i < count; i++) { var angle = i / count * Math.PI * 2; var posX = center.x + Math.cos(angle) * radius; var posY = center.y + Math.sin(angle) * radius; self.createAttack(posX, posY, 3000); } }; self.lineAttack = function () { LK.getSound('bossAttack').play(); // Create a line of attacks from boss to player var targetX = player.x; var targetY = player.y; var count = 5; for (var i = 0; i < count; i++) { var t = i / (count - 1); var posX = self.x + (targetX - self.x) * t; var posY = self.y + (targetY - self.y) * t; var delay = i * 200; LK.setTimeout(function (x, y) { return function () { self.createAttack(x, y, 2000); }; }(posX, posY), delay); } }; self.chargeAttack = function () { LK.getSound('bossAttack').play(); // Calculate direction to player var dx = player.x - self.x; var dy = player.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { dx /= distance; dy /= distance; } // Save original position var startX = self.x; var startY = self.y; // Charge animation tween(self, { x: self.x + dx * 500, y: self.y + dy * 500 }, { duration: 800, easing: tween.easeIn, onFinish: function onFinish() { // Return to original position tween(self, { x: startX, y: startY }, { duration: 1000, easing: tween.easeOut }); // Create attacks along the charge path for (var i = 0; i < 5; i++) { var t = i / 4; var posX = startX + (self.x - startX) * t; var posY = startY + (self.y - startY) * t; self.createAttack(posX, posY, 1500); } } }); }; self.createAttack = function (x, y, duration) { // Create attack warning var warning = game.addChild(LK.getAsset('attack', { anchorX: 0.5, anchorY: 0.5, x: x, y: y, alpha: 0.3, tint: 0xFFFF00 })); // Warning animation tween(warning, { alpha: 0.6 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { // Change to actual attack warning.tint = 0xFF0000; tween(warning, { alpha: 0.8 }, { duration: 200, onFinish: function onFinish() { // Add to active attacks var attack = { x: warning.x, y: warning.y, radius: warning.width / 2, visual: warning, lifeTime: duration }; self.attacks.push(attack); // Remove after duration LK.setTimeout(function () { var index = self.attacks.indexOf(attack); if (index !== -1) { self.attacks.splice(index, 1); } // Fade out and destroy tween(warning, { alpha: 0 }, { duration: 300, onFinish: function onFinish() { warning.destroy(); } }); }, duration); } }); } }); }; self.takeDamage = function (amount) { if (self.dead) { return; } self.health -= amount; // Visual feedback LK.effects.flashObject(self, 0xFFFFFF, 200); // Phase transition at 50% health if (self.health <= self.maxHealth / 2 && self.phase === 1) { self.phase = 2; self.speed += 2; // Visual phase transition tween(self, { tint: 0xFF3300 }, { duration: 1000, easing: tween.easeInOut }); } // Check if boss is defeated if (self.health <= 0) { self.die(); } }; self.die = function () { if (self.dead) { return; } self.dead = true; LK.getSound('victory').play(); // Death animation tween(self, { alpha: 0, scaleX: 2, scaleY: 2 }, { duration: 2000, easing: tween.easeOut, onFinish: function onFinish() { self.destroy(); storage.bossesDefeated = (storage.bossesDefeated || 0) + 1; gameState.victory(); } }); }; self.update = function () { if (self.dead) { return; } // Update attack cooldown if (self.attackCooldown > 0) { self.attackCooldown--; if (self.attackCooldown === 0) { self.startAttackPattern(); } } // Move toward player if not stunned if (!self.stunned) { var dx = player.x - self.x; var dy = player.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 200) { // Keep some distance self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } } else { // Update stun duration self.stunDuration--; if (self.stunDuration <= 0) { self.stunned = false; } } // Process active attacks for (var i = self.attacks.length - 1; i >= 0; i--) { var attack = self.attacks[i]; attack.lifeTime--; // Check for collision with player var dx = player.x - attack.x; var dy = player.y - attack.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < attack.radius + player.width / 2 && player.takeDamage(1)) { // Visual feedback LK.effects.flashObject(attack.visual, 0xFFFFFF, 200); } // Remove expired attacks if (attack.lifeTime <= 0) { self.attacks.splice(i, 1); attack.visual.destroy(); } } }; return self; }); var Player = Container.expand(function () { var self = Container.call(this); var playerGraphics = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 12; self.rolling = false; self.rollCooldown = 0; self.rollDuration = 0; self.rollDirection = { x: 0, y: 0 }; self.rollDistance = 300; self.rollSpeed = 20; self.invulnerable = false; self.invulnerabilityFrames = 0; self.health = storage.maxHearts || 3; self.dead = false; self.roll = function (direction) { if (self.rolling || self.rollCooldown > 0 || self.dead) { return; } self.rolling = true; self.rollDuration = self.rollDistance / self.rollSpeed; self.rollDirection = direction; self.invulnerable = true; // Visual effect for rolling var rollEffect = game.addChild(LK.getAsset('roll', { anchorX: 0.5, anchorY: 0.5, x: self.x, y: self.y, alpha: 0.7 })); // Start the roll tween LK.getSound('roll').play(); // Cleanup after roll LK.setTimeout(function () { self.rolling = false; self.rollCooldown = 30; // 30 frames cooldown (0.5 seconds) LK.setTimeout(function () { self.invulnerable = false; }, 100); // Small buffer of invulnerability after roll tween(rollEffect, { alpha: 0 }, { duration: 200, onFinish: function onFinish() { rollEffect.destroy(); } }); }, self.rollDuration * (1000 / 60)); // Convert frames to ms }; self.takeDamage = function (amount) { if (self.invulnerable || self.dead) { return false; } self.health -= amount; LK.getSound('hit').play(); if (self.health <= 0) { self.die(); return true; } // Invulnerability period self.invulnerable = true; self.invulnerabilityFrames = 45; // 45 frames (0.75 seconds) // Flash effect LK.effects.flashObject(self, 0xFF0000, 500); return true; }; self.die = function () { if (self.dead) { return; } self.dead = true; LK.getSound('death').play(); // Death visual effect tween(self, { alpha: 0, rotation: Math.PI * 4 }, { duration: 1500, easing: tween.easeInOut }); // Update total deaths storage.totalDeaths = (storage.totalDeaths || 0) + 1; // Check if player should get an upgrade if (storage.totalDeaths % 3 === 0) { storage.maxHearts = (storage.maxHearts || 3) + 1; } // Restart the level after delay LK.setTimeout(function () { gameState.gameOver(); }, 2000); }; self.update = function () { // Handle roll cooldown if (self.rollCooldown > 0) { self.rollCooldown--; } // Handle invulnerability frames if (self.invulnerable && self.invulnerabilityFrames > 0) { self.invulnerabilityFrames--; // Blinking effect self.alpha = self.invulnerabilityFrames % 6 > 2 ? 0.5 : 1; if (self.invulnerabilityFrames <= 0) { self.invulnerable = false; self.alpha = 1; } } // Handle rolling movement if (self.rolling) { self.x += self.rollDirection.x * self.rollSpeed; self.y += self.rollDirection.y * self.rollSpeed; } // Keep player within bounds self.x = Math.max(100, Math.min(self.x, 2048 - 100)); self.y = Math.max(100, Math.min(self.y, 2732 - 100)); }; return self; }); var UI = Container.expand(function () { var self = Container.call(this); // Create heart containers self.hearts = []; self.heartContainer = new Container(); self.addChild(self.heartContainer); // Title and messages self.titleText = new Text2("ROLL SOULS", { size: 150, fill: 0xFFFFFF }); self.titleText.anchor.set(0.5, 0.5); self.addChild(self.titleText); self.messageText = new Text2("", { size: 60, fill: 0xFFFFFF }); self.messageText.anchor.set(0.5, 0.5); self.addChild(self.messageText); // Deaths counter self.deathsText = new Text2("Deaths: 0", { size: 40, fill: 0xFFFFFF }); self.deathsText.anchor.set(1, 0); self.addChild(self.deathsText); // Tutorial text self.tutorialText = new Text2("", { size: 50, fill: 0xFFFFFF }); self.tutorialText.anchor.set(0.5, 0); self.addChild(self.tutorialText); self.updateHearts = function (current, max) { // Clear existing hearts while (self.hearts.length > 0) { var heart = self.hearts.pop(); heart.destroy(); } self.heartContainer.removeChildren(); // Create new hearts for (var i = 0; i < max; i++) { var heart = LK.getAsset('heart', { anchorX: 0.5, anchorY: 0.5, x: i * 50, y: 0, tint: i < current ? 0xFF0000 : 0x555555 }); self.hearts.push(heart); self.heartContainer.addChild(heart); } // Center heart container self.heartContainer.x = (2048 - max * 50) / 2; self.heartContainer.y = 100; }; self.showMessage = function (message, duration) { self.messageText.setText(message); self.messageText.alpha = 1; // Clear any existing timeout if (self.messageTimeout) { LK.clearTimeout(self.messageTimeout); } // Fade out after duration if (duration) { self.messageTimeout = LK.setTimeout(function () { tween(self.messageText, { alpha: 0 }, { duration: 500 }); }, duration); } }; self.showTutorial = function (text) { self.tutorialText.setText(text); self.tutorialText.alpha = 1; }; self.hideTutorial = function () { tween(self.tutorialText, { alpha: 0 }, { duration: 500 }); }; self.updateDeathsCounter = function () { self.deathsText.setText("Deaths: " + storage.totalDeaths); }; self.positionElements = function (state) { // Position based on game state switch (state) { case "title": self.titleText.x = 2048 / 2; self.titleText.y = 800; self.messageText.x = 2048 / 2; self.messageText.y = 1000; self.tutorialText.x = 2048 / 2; self.tutorialText.y = 1200; self.deathsText.x = 2048 - 50; self.deathsText.y = 50; break; case "game": self.titleText.alpha = 0; self.messageText.x = 2048 / 2; self.messageText.y = 1500; self.tutorialText.x = 2048 / 2; self.tutorialText.y = 200; self.deathsText.x = 2048 - 50; self.deathsText.y = 50; break; case "victory": self.titleText.x = 2048 / 2; self.titleText.y = 800; self.messageText.x = 2048 / 2; self.messageText.y = 1000; self.tutorialText.x = 2048 / 2; self.tutorialText.y = 1200; self.deathsText.x = 2048 - 50; self.deathsText.y = 50; break; } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x111111 }); /**** * Game Code ****/ // Game variables var player; var boss; var ui; var walls = []; var gameState = { currentState: "title", touchStart: { x: 0, y: 0 }, touchEnd: { x: 0, y: 0 }, init: function init() { // Create background var bg = game.addChild(LK.getAsset('bg', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 })); // Create walls this.createWalls(); // Create UI ui = game.addChild(new UI()); ui.positionElements("title"); ui.updateDeathsCounter(); // Show title screen this.showTitleScreen(); // Start background music LK.playMusic('bgMusic', { fade: { start: 0, end: 0.3, duration: 1000 } }); }, createWalls: function createWalls() { // Left wall var leftWall = game.addChild(LK.getAsset('wall', { anchorX: 0, anchorY: 0, x: 0, y: 0 })); walls.push(leftWall); // Right wall var rightWall = game.addChild(LK.getAsset('wall', { anchorX: 0, anchorY: 0, x: 2048 - 100, y: 0 })); walls.push(rightWall); // Top wall var topWall = game.addChild(LK.getAsset('floor', { anchorX: 0, anchorY: 0, x: 0, y: 0 })); walls.push(topWall); // Bottom wall var bottomWall = game.addChild(LK.getAsset('floor', { anchorX: 0, anchorY: 0, x: 0, y: 2732 - 100 })); walls.push(bottomWall); }, showTitleScreen: function showTitleScreen() { this.currentState = "title"; ui.positionElements("title"); ui.titleText.alpha = 1; ui.showMessage("Tap to Start", 0); ui.showTutorial("Swipe to Roll - Death is Progress"); }, startGame: function startGame() { this.currentState = "game"; ui.positionElements("game"); // Create player player = game.addChild(new Player()); player.x = 2048 / 2; player.y = 2732 / 2 + 400; player.health = storage.maxHearts || 3; // Create boss boss = game.addChild(new Boss()); boss.x = 2048 / 2; boss.y = 2732 / 2 - 400; // Update UI ui.updateHearts(player.health, storage.maxHearts); ui.showTutorial("Swipe to roll away from attacks!"); LK.setTimeout(function () { ui.hideTutorial(); boss.startAttackPattern(); }, 3000); }, gameOver: function gameOver() { // Show game over message ui.showMessage("YOU DIED", 3000); // Restart after delay LK.setTimeout(function () { // Clean up if (player) { player.destroy(); } if (boss) { boss.destroy(); } // Restart gameState.startGame(); }, 3000); }, victory: function victory() { this.currentState = "victory"; ui.positionElements("victory"); ui.titleText.setText("VICTORY ACHIEVED"); ui.titleText.alpha = 1; ui.showMessage("Bosses Defeated: " + storage.bossesDefeated, 0); ui.showTutorial("Tap to Continue"); // Clean up player if (player) { player.destroy(); } LK.setTimeout(function () { // Return to title after delay LK.setTimeout(function () { gameState.showTitleScreen(); }, 5000); }, 3000); }, processTouchGesture: function processTouchGesture() { if (this.currentState === "title") { this.startGame(); return; } if (this.currentState === "victory") { this.showTitleScreen(); return; } // Only process roll gestures during gameplay if (this.currentState !== "game" || !player || player.dead) { return; } // Calculate swipe direction and distance var dx = this.touchEnd.x - this.touchStart.x; var dy = this.touchEnd.y - this.touchStart.y; var distance = Math.sqrt(dx * dx + dy * dy); // Minimum swipe distance if (distance < 50) { return; } // Normalize direction var direction = { x: dx / distance, y: dy / distance }; // Execute roll player.roll(direction); } }; // Event handlers game.down = function (x, y, obj) { gameState.touchStart.x = x; gameState.touchStart.y = y; }; game.up = function (x, y, obj) { gameState.touchEnd.x = x; gameState.touchEnd.y = y; gameState.processTouchGesture(); }; game.move = function (x, y, obj) { // Only used for tracking the current touch position gameState.touchEnd.x = x; gameState.touchEnd.y = y; }; // Main update loop game.update = function () { // Only update game objects during gameplay if (gameState.currentState === "game") { if (player) { player.update(); } if (boss) { boss.update(); } // Update hearts UI if (player && ui) { ui.updateHearts(player.health, storage.maxHearts); } } // Always update deaths counter if (ui) { ui.updateDeathsCounter(); } }; // Initialize the game gameState.init();
===================================================================
--- original.js
+++ change.js
@@ -1,6 +1,729 @@
-/****
+/****
+* Plugins
+****/
+var tween = LK.import("@upit/tween.v1");
+var storage = LK.import("@upit/storage.v1", {
+ totalDeaths: 0,
+ maxHearts: 3,
+ currentLevel: 0,
+ bossesDefeated: 0
+});
+
+/****
+* Classes
+****/
+var Boss = Container.expand(function () {
+ var self = Container.call(this);
+ var bossGraphics = self.attachAsset('boss', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ self.health = 100;
+ self.maxHealth = 100;
+ self.speed = 5;
+ self.attackCooldown = 0;
+ self.attackPattern = 0;
+ self.attacks = [];
+ self.stunned = false;
+ self.stunDuration = 0;
+ self.dead = false;
+ self.phase = 1;
+ self.startAttackPattern = function () {
+ if (self.dead) {
+ return;
+ }
+ self.attackPattern = (self.attackPattern + 1) % 3;
+ switch (self.attackPattern) {
+ case 0:
+ self.circleAttack();
+ break;
+ case 1:
+ self.lineAttack();
+ break;
+ case 2:
+ self.chargeAttack();
+ break;
+ }
+ // Schedule next attack
+ self.attackCooldown = 90 + Math.floor(Math.random() * 60); // 1.5-2.5 seconds
+ };
+ self.circleAttack = function () {
+ LK.getSound('bossAttack').play();
+ var center = {
+ x: self.x,
+ y: self.y
+ };
+ var count = 8;
+ var radius = 300;
+ for (var i = 0; i < count; i++) {
+ var angle = i / count * Math.PI * 2;
+ var posX = center.x + Math.cos(angle) * radius;
+ var posY = center.y + Math.sin(angle) * radius;
+ self.createAttack(posX, posY, 3000);
+ }
+ };
+ self.lineAttack = function () {
+ LK.getSound('bossAttack').play();
+ // Create a line of attacks from boss to player
+ var targetX = player.x;
+ var targetY = player.y;
+ var count = 5;
+ for (var i = 0; i < count; i++) {
+ var t = i / (count - 1);
+ var posX = self.x + (targetX - self.x) * t;
+ var posY = self.y + (targetY - self.y) * t;
+ var delay = i * 200;
+ LK.setTimeout(function (x, y) {
+ return function () {
+ self.createAttack(x, y, 2000);
+ };
+ }(posX, posY), delay);
+ }
+ };
+ self.chargeAttack = function () {
+ LK.getSound('bossAttack').play();
+ // Calculate direction to player
+ var dx = player.x - self.x;
+ var dy = player.y - self.y;
+ var distance = Math.sqrt(dx * dx + dy * dy);
+ if (distance > 0) {
+ dx /= distance;
+ dy /= distance;
+ }
+ // Save original position
+ var startX = self.x;
+ var startY = self.y;
+ // Charge animation
+ tween(self, {
+ x: self.x + dx * 500,
+ y: self.y + dy * 500
+ }, {
+ duration: 800,
+ easing: tween.easeIn,
+ onFinish: function onFinish() {
+ // Return to original position
+ tween(self, {
+ x: startX,
+ y: startY
+ }, {
+ duration: 1000,
+ easing: tween.easeOut
+ });
+ // Create attacks along the charge path
+ for (var i = 0; i < 5; i++) {
+ var t = i / 4;
+ var posX = startX + (self.x - startX) * t;
+ var posY = startY + (self.y - startY) * t;
+ self.createAttack(posX, posY, 1500);
+ }
+ }
+ });
+ };
+ self.createAttack = function (x, y, duration) {
+ // Create attack warning
+ var warning = game.addChild(LK.getAsset('attack', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ x: x,
+ y: y,
+ alpha: 0.3,
+ tint: 0xFFFF00
+ }));
+ // Warning animation
+ tween(warning, {
+ alpha: 0.6
+ }, {
+ duration: 1000,
+ easing: tween.easeInOut,
+ onFinish: function onFinish() {
+ // Change to actual attack
+ warning.tint = 0xFF0000;
+ tween(warning, {
+ alpha: 0.8
+ }, {
+ duration: 200,
+ onFinish: function onFinish() {
+ // Add to active attacks
+ var attack = {
+ x: warning.x,
+ y: warning.y,
+ radius: warning.width / 2,
+ visual: warning,
+ lifeTime: duration
+ };
+ self.attacks.push(attack);
+ // Remove after duration
+ LK.setTimeout(function () {
+ var index = self.attacks.indexOf(attack);
+ if (index !== -1) {
+ self.attacks.splice(index, 1);
+ }
+ // Fade out and destroy
+ tween(warning, {
+ alpha: 0
+ }, {
+ duration: 300,
+ onFinish: function onFinish() {
+ warning.destroy();
+ }
+ });
+ }, duration);
+ }
+ });
+ }
+ });
+ };
+ self.takeDamage = function (amount) {
+ if (self.dead) {
+ return;
+ }
+ self.health -= amount;
+ // Visual feedback
+ LK.effects.flashObject(self, 0xFFFFFF, 200);
+ // Phase transition at 50% health
+ if (self.health <= self.maxHealth / 2 && self.phase === 1) {
+ self.phase = 2;
+ self.speed += 2;
+ // Visual phase transition
+ tween(self, {
+ tint: 0xFF3300
+ }, {
+ duration: 1000,
+ easing: tween.easeInOut
+ });
+ }
+ // Check if boss is defeated
+ if (self.health <= 0) {
+ self.die();
+ }
+ };
+ self.die = function () {
+ if (self.dead) {
+ return;
+ }
+ self.dead = true;
+ LK.getSound('victory').play();
+ // Death animation
+ tween(self, {
+ alpha: 0,
+ scaleX: 2,
+ scaleY: 2
+ }, {
+ duration: 2000,
+ easing: tween.easeOut,
+ onFinish: function onFinish() {
+ self.destroy();
+ storage.bossesDefeated = (storage.bossesDefeated || 0) + 1;
+ gameState.victory();
+ }
+ });
+ };
+ self.update = function () {
+ if (self.dead) {
+ return;
+ }
+ // Update attack cooldown
+ if (self.attackCooldown > 0) {
+ self.attackCooldown--;
+ if (self.attackCooldown === 0) {
+ self.startAttackPattern();
+ }
+ }
+ // Move toward player if not stunned
+ if (!self.stunned) {
+ var dx = player.x - self.x;
+ var dy = player.y - self.y;
+ var distance = Math.sqrt(dx * dx + dy * dy);
+ if (distance > 200) {
+ // Keep some distance
+ self.x += dx / distance * self.speed;
+ self.y += dy / distance * self.speed;
+ }
+ } else {
+ // Update stun duration
+ self.stunDuration--;
+ if (self.stunDuration <= 0) {
+ self.stunned = false;
+ }
+ }
+ // Process active attacks
+ for (var i = self.attacks.length - 1; i >= 0; i--) {
+ var attack = self.attacks[i];
+ attack.lifeTime--;
+ // Check for collision with player
+ var dx = player.x - attack.x;
+ var dy = player.y - attack.y;
+ var distance = Math.sqrt(dx * dx + dy * dy);
+ if (distance < attack.radius + player.width / 2 && player.takeDamage(1)) {
+ // Visual feedback
+ LK.effects.flashObject(attack.visual, 0xFFFFFF, 200);
+ }
+ // Remove expired attacks
+ if (attack.lifeTime <= 0) {
+ self.attacks.splice(i, 1);
+ attack.visual.destroy();
+ }
+ }
+ };
+ return self;
+});
+var Player = Container.expand(function () {
+ var self = Container.call(this);
+ var playerGraphics = self.attachAsset('player', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ self.speed = 12;
+ self.rolling = false;
+ self.rollCooldown = 0;
+ self.rollDuration = 0;
+ self.rollDirection = {
+ x: 0,
+ y: 0
+ };
+ self.rollDistance = 300;
+ self.rollSpeed = 20;
+ self.invulnerable = false;
+ self.invulnerabilityFrames = 0;
+ self.health = storage.maxHearts || 3;
+ self.dead = false;
+ self.roll = function (direction) {
+ if (self.rolling || self.rollCooldown > 0 || self.dead) {
+ return;
+ }
+ self.rolling = true;
+ self.rollDuration = self.rollDistance / self.rollSpeed;
+ self.rollDirection = direction;
+ self.invulnerable = true;
+ // Visual effect for rolling
+ var rollEffect = game.addChild(LK.getAsset('roll', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ x: self.x,
+ y: self.y,
+ alpha: 0.7
+ }));
+ // Start the roll tween
+ LK.getSound('roll').play();
+ // Cleanup after roll
+ LK.setTimeout(function () {
+ self.rolling = false;
+ self.rollCooldown = 30; // 30 frames cooldown (0.5 seconds)
+ LK.setTimeout(function () {
+ self.invulnerable = false;
+ }, 100); // Small buffer of invulnerability after roll
+ tween(rollEffect, {
+ alpha: 0
+ }, {
+ duration: 200,
+ onFinish: function onFinish() {
+ rollEffect.destroy();
+ }
+ });
+ }, self.rollDuration * (1000 / 60)); // Convert frames to ms
+ };
+ self.takeDamage = function (amount) {
+ if (self.invulnerable || self.dead) {
+ return false;
+ }
+ self.health -= amount;
+ LK.getSound('hit').play();
+ if (self.health <= 0) {
+ self.die();
+ return true;
+ }
+ // Invulnerability period
+ self.invulnerable = true;
+ self.invulnerabilityFrames = 45; // 45 frames (0.75 seconds)
+ // Flash effect
+ LK.effects.flashObject(self, 0xFF0000, 500);
+ return true;
+ };
+ self.die = function () {
+ if (self.dead) {
+ return;
+ }
+ self.dead = true;
+ LK.getSound('death').play();
+ // Death visual effect
+ tween(self, {
+ alpha: 0,
+ rotation: Math.PI * 4
+ }, {
+ duration: 1500,
+ easing: tween.easeInOut
+ });
+ // Update total deaths
+ storage.totalDeaths = (storage.totalDeaths || 0) + 1;
+ // Check if player should get an upgrade
+ if (storage.totalDeaths % 3 === 0) {
+ storage.maxHearts = (storage.maxHearts || 3) + 1;
+ }
+ // Restart the level after delay
+ LK.setTimeout(function () {
+ gameState.gameOver();
+ }, 2000);
+ };
+ self.update = function () {
+ // Handle roll cooldown
+ if (self.rollCooldown > 0) {
+ self.rollCooldown--;
+ }
+ // Handle invulnerability frames
+ if (self.invulnerable && self.invulnerabilityFrames > 0) {
+ self.invulnerabilityFrames--;
+ // Blinking effect
+ self.alpha = self.invulnerabilityFrames % 6 > 2 ? 0.5 : 1;
+ if (self.invulnerabilityFrames <= 0) {
+ self.invulnerable = false;
+ self.alpha = 1;
+ }
+ }
+ // Handle rolling movement
+ if (self.rolling) {
+ self.x += self.rollDirection.x * self.rollSpeed;
+ self.y += self.rollDirection.y * self.rollSpeed;
+ }
+ // Keep player within bounds
+ self.x = Math.max(100, Math.min(self.x, 2048 - 100));
+ self.y = Math.max(100, Math.min(self.y, 2732 - 100));
+ };
+ return self;
+});
+var UI = Container.expand(function () {
+ var self = Container.call(this);
+ // Create heart containers
+ self.hearts = [];
+ self.heartContainer = new Container();
+ self.addChild(self.heartContainer);
+ // Title and messages
+ self.titleText = new Text2("ROLL SOULS", {
+ size: 150,
+ fill: 0xFFFFFF
+ });
+ self.titleText.anchor.set(0.5, 0.5);
+ self.addChild(self.titleText);
+ self.messageText = new Text2("", {
+ size: 60,
+ fill: 0xFFFFFF
+ });
+ self.messageText.anchor.set(0.5, 0.5);
+ self.addChild(self.messageText);
+ // Deaths counter
+ self.deathsText = new Text2("Deaths: 0", {
+ size: 40,
+ fill: 0xFFFFFF
+ });
+ self.deathsText.anchor.set(1, 0);
+ self.addChild(self.deathsText);
+ // Tutorial text
+ self.tutorialText = new Text2("", {
+ size: 50,
+ fill: 0xFFFFFF
+ });
+ self.tutorialText.anchor.set(0.5, 0);
+ self.addChild(self.tutorialText);
+ self.updateHearts = function (current, max) {
+ // Clear existing hearts
+ while (self.hearts.length > 0) {
+ var heart = self.hearts.pop();
+ heart.destroy();
+ }
+ self.heartContainer.removeChildren();
+ // Create new hearts
+ for (var i = 0; i < max; i++) {
+ var heart = LK.getAsset('heart', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ x: i * 50,
+ y: 0,
+ tint: i < current ? 0xFF0000 : 0x555555
+ });
+ self.hearts.push(heart);
+ self.heartContainer.addChild(heart);
+ }
+ // Center heart container
+ self.heartContainer.x = (2048 - max * 50) / 2;
+ self.heartContainer.y = 100;
+ };
+ self.showMessage = function (message, duration) {
+ self.messageText.setText(message);
+ self.messageText.alpha = 1;
+ // Clear any existing timeout
+ if (self.messageTimeout) {
+ LK.clearTimeout(self.messageTimeout);
+ }
+ // Fade out after duration
+ if (duration) {
+ self.messageTimeout = LK.setTimeout(function () {
+ tween(self.messageText, {
+ alpha: 0
+ }, {
+ duration: 500
+ });
+ }, duration);
+ }
+ };
+ self.showTutorial = function (text) {
+ self.tutorialText.setText(text);
+ self.tutorialText.alpha = 1;
+ };
+ self.hideTutorial = function () {
+ tween(self.tutorialText, {
+ alpha: 0
+ }, {
+ duration: 500
+ });
+ };
+ self.updateDeathsCounter = function () {
+ self.deathsText.setText("Deaths: " + storage.totalDeaths);
+ };
+ self.positionElements = function (state) {
+ // Position based on game state
+ switch (state) {
+ case "title":
+ self.titleText.x = 2048 / 2;
+ self.titleText.y = 800;
+ self.messageText.x = 2048 / 2;
+ self.messageText.y = 1000;
+ self.tutorialText.x = 2048 / 2;
+ self.tutorialText.y = 1200;
+ self.deathsText.x = 2048 - 50;
+ self.deathsText.y = 50;
+ break;
+ case "game":
+ self.titleText.alpha = 0;
+ self.messageText.x = 2048 / 2;
+ self.messageText.y = 1500;
+ self.tutorialText.x = 2048 / 2;
+ self.tutorialText.y = 200;
+ self.deathsText.x = 2048 - 50;
+ self.deathsText.y = 50;
+ break;
+ case "victory":
+ self.titleText.x = 2048 / 2;
+ self.titleText.y = 800;
+ self.messageText.x = 2048 / 2;
+ self.messageText.y = 1000;
+ self.tutorialText.x = 2048 / 2;
+ self.tutorialText.y = 1200;
+ self.deathsText.x = 2048 - 50;
+ self.deathsText.y = 50;
+ break;
+ }
+ };
+ return self;
+});
+
+/****
* Initialize Game
-****/
+****/
var game = new LK.Game({
- backgroundColor: 0x000000
-});
\ No newline at end of file
+ backgroundColor: 0x111111
+});
+
+/****
+* Game Code
+****/
+// Game variables
+var player;
+var boss;
+var ui;
+var walls = [];
+var gameState = {
+ currentState: "title",
+ touchStart: {
+ x: 0,
+ y: 0
+ },
+ touchEnd: {
+ x: 0,
+ y: 0
+ },
+ init: function init() {
+ // Create background
+ var bg = game.addChild(LK.getAsset('bg', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ x: 2048 / 2,
+ y: 2732 / 2
+ }));
+ // Create walls
+ this.createWalls();
+ // Create UI
+ ui = game.addChild(new UI());
+ ui.positionElements("title");
+ ui.updateDeathsCounter();
+ // Show title screen
+ this.showTitleScreen();
+ // Start background music
+ LK.playMusic('bgMusic', {
+ fade: {
+ start: 0,
+ end: 0.3,
+ duration: 1000
+ }
+ });
+ },
+ createWalls: function createWalls() {
+ // Left wall
+ var leftWall = game.addChild(LK.getAsset('wall', {
+ anchorX: 0,
+ anchorY: 0,
+ x: 0,
+ y: 0
+ }));
+ walls.push(leftWall);
+ // Right wall
+ var rightWall = game.addChild(LK.getAsset('wall', {
+ anchorX: 0,
+ anchorY: 0,
+ x: 2048 - 100,
+ y: 0
+ }));
+ walls.push(rightWall);
+ // Top wall
+ var topWall = game.addChild(LK.getAsset('floor', {
+ anchorX: 0,
+ anchorY: 0,
+ x: 0,
+ y: 0
+ }));
+ walls.push(topWall);
+ // Bottom wall
+ var bottomWall = game.addChild(LK.getAsset('floor', {
+ anchorX: 0,
+ anchorY: 0,
+ x: 0,
+ y: 2732 - 100
+ }));
+ walls.push(bottomWall);
+ },
+ showTitleScreen: function showTitleScreen() {
+ this.currentState = "title";
+ ui.positionElements("title");
+ ui.titleText.alpha = 1;
+ ui.showMessage("Tap to Start", 0);
+ ui.showTutorial("Swipe to Roll - Death is Progress");
+ },
+ startGame: function startGame() {
+ this.currentState = "game";
+ ui.positionElements("game");
+ // Create player
+ player = game.addChild(new Player());
+ player.x = 2048 / 2;
+ player.y = 2732 / 2 + 400;
+ player.health = storage.maxHearts || 3;
+ // Create boss
+ boss = game.addChild(new Boss());
+ boss.x = 2048 / 2;
+ boss.y = 2732 / 2 - 400;
+ // Update UI
+ ui.updateHearts(player.health, storage.maxHearts);
+ ui.showTutorial("Swipe to roll away from attacks!");
+ LK.setTimeout(function () {
+ ui.hideTutorial();
+ boss.startAttackPattern();
+ }, 3000);
+ },
+ gameOver: function gameOver() {
+ // Show game over message
+ ui.showMessage("YOU DIED", 3000);
+ // Restart after delay
+ LK.setTimeout(function () {
+ // Clean up
+ if (player) {
+ player.destroy();
+ }
+ if (boss) {
+ boss.destroy();
+ }
+ // Restart
+ gameState.startGame();
+ }, 3000);
+ },
+ victory: function victory() {
+ this.currentState = "victory";
+ ui.positionElements("victory");
+ ui.titleText.setText("VICTORY ACHIEVED");
+ ui.titleText.alpha = 1;
+ ui.showMessage("Bosses Defeated: " + storage.bossesDefeated, 0);
+ ui.showTutorial("Tap to Continue");
+ // Clean up player
+ if (player) {
+ player.destroy();
+ }
+ LK.setTimeout(function () {
+ // Return to title after delay
+ LK.setTimeout(function () {
+ gameState.showTitleScreen();
+ }, 5000);
+ }, 3000);
+ },
+ processTouchGesture: function processTouchGesture() {
+ if (this.currentState === "title") {
+ this.startGame();
+ return;
+ }
+ if (this.currentState === "victory") {
+ this.showTitleScreen();
+ return;
+ }
+ // Only process roll gestures during gameplay
+ if (this.currentState !== "game" || !player || player.dead) {
+ return;
+ }
+ // Calculate swipe direction and distance
+ var dx = this.touchEnd.x - this.touchStart.x;
+ var dy = this.touchEnd.y - this.touchStart.y;
+ var distance = Math.sqrt(dx * dx + dy * dy);
+ // Minimum swipe distance
+ if (distance < 50) {
+ return;
+ }
+ // Normalize direction
+ var direction = {
+ x: dx / distance,
+ y: dy / distance
+ };
+ // Execute roll
+ player.roll(direction);
+ }
+};
+// Event handlers
+game.down = function (x, y, obj) {
+ gameState.touchStart.x = x;
+ gameState.touchStart.y = y;
+};
+game.up = function (x, y, obj) {
+ gameState.touchEnd.x = x;
+ gameState.touchEnd.y = y;
+ gameState.processTouchGesture();
+};
+game.move = function (x, y, obj) {
+ // Only used for tracking the current touch position
+ gameState.touchEnd.x = x;
+ gameState.touchEnd.y = y;
+};
+// Main update loop
+game.update = function () {
+ // Only update game objects during gameplay
+ if (gameState.currentState === "game") {
+ if (player) {
+ player.update();
+ }
+ if (boss) {
+ boss.update();
+ }
+ // Update hearts UI
+ if (player && ui) {
+ ui.updateHearts(player.health, storage.maxHearts);
+ }
+ }
+ // Always update deaths counter
+ if (ui) {
+ ui.updateDeathsCounter();
+ }
+};
+// Initialize the game
+gameState.init();
\ No newline at end of file