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