User prompt
Please fix the bug: 'Script error.' in or related to this line: 'tween.to(playerSprite, {' Line Number: 75 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
When I jump make the icon do a 360
User prompt
When I press back it just shows the text on the screen fix it
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'playerSprite.setColor(skinColors[storage.selectedSkin]);' Line Number: 50
User prompt
Do it
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'storage.remove('lastCompletedLevel');' Line Number: 295
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'storage.lastCompletedLevel = undefined;' Line Number: 295
User prompt
Please fix the bug: 'Script error.' in or related to this line: 'player.x = PLAYER_START_X;' Line Number: 375
User prompt
And every time you beat each level you get coins in the first level you get +20 in the second +50 and in the last level +100 you can claim the coins every time you beat the level ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
Make a main menu and the play button puts you in a new menu and there’s multiple levels and there is 3 and the first one is called first steps make it an easy level with the difficulty easy and the second level is called Erin’s atventure and the difficulty is medium and the third level is called Final steps and make the difficulty hard
Code edit (1 edits merged)
Please save this source code
User prompt
Geometry Dash: Jump & Dodge
Initial prompt
Make geometry dash
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
coins: 0,
claimed: {}
});
/****
* Classes
****/
// FlyingObstacle class (flying block for wave mode)
var FlyingObstacle = Container.expand(function () {
var self = Container.call(this);
var obsSprite = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 28;
self.amplitude = 350 + Math.random() * 250;
self.frequency = 0.0015 + Math.random() * 0.001;
self.baseY = 1366 + (Math.random() - 0.5) * 900;
self.phase = Math.random() * Math.PI * 2;
self.update = function () {
self.x -= self.speed;
self.y = self.baseY + Math.cos(self.x * self.frequency + self.phase) * self.amplitude;
};
return self;
});
// Obstacle class (tall block)
var Obstacle = Container.expand(function () {
var self = Container.call(this);
var obsSprite = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 1
});
self.speed = 22;
self.update = function () {
self.x -= self.speed;
};
return self;
});
// Player class
var Player = Container.expand(function () {
var self = Container.call(this);
// Use image asset for red, green, or gold skin, otherwise use shape asset and tint for other skins
var playerSprite;
if (storage.selectedSkin === "skin_red") {
playerSprite = self.attachAsset('Red', {
anchorX: 0.5,
anchorY: 1
});
} else if (storage.selectedSkin === "skin_green") {
playerSprite = self.attachAsset('Green', {
anchorX: 0.5,
anchorY: 1
});
} else if (storage.selectedSkin === "skin_gold") {
playerSprite = self.attachAsset('Gold', {
anchorX: 0.5,
anchorY: 1
});
} else {
playerSprite = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 1
});
// Apply skin color if selected and not red, green, or gold
var skinColors = {
"skin_red": 0xe74c3c,
"skin_green": 0x2ecc71,
"skin_gold": 0xf1c40f
};
if (storage.selectedSkin && skinColors[storage.selectedSkin] && storage.selectedSkin !== "skin_red" && storage.selectedSkin !== "skin_green" && storage.selectedSkin !== "skin_gold") {
playerSprite.color = skinColors[storage.selectedSkin];
}
}
// Physics
self.vy = 0;
self.isJumping = false;
self.gravity = 2.2;
self.jumpStrength = -48;
self.groundY = 0; // Set after ground is created
self.isWave = false; // Always initialize wave mode state
// Update method
self.update = function () {
if (self.isWave) {
// Wave mode: apply constant gravity, but allow upward thrust on press
self.vy += 3.2; // slightly higher gravity for wave feel
self.y += self.vy;
// Clamp to screen bounds (not off top or bottom)
if (self.y < 60) {
self.y = 60;
self.vy = 0;
}
if (self.y > 2732 - 60) {
self.y = 2732 - 60;
self.vy = 0;
}
} else {
// Normal mode
self.vy += self.gravity;
self.y += self.vy;
// Ground collision
if (self.y > self.groundY) {
self.y = self.groundY;
self.vy = 0;
self.isJumping = false;
}
}
};
// Jump method
self.jump = function () {
if (!self.isJumping && self.y >= self.groundY) {
self.vy = self.jumpStrength;
self.isJumping = true;
// Start 360-degree rotation tween on playerSprite
tween(playerSprite, {
rotation: playerSprite.rotation + Math.PI * 2
}, {
duration: 400,
easing: tween.linear
});
}
};
return self;
});
// Spike class (obstacle)
var Spike = Container.expand(function () {
var self = Container.call(this);
var spikeSprite = self.attachAsset('spike', {
anchorX: 0.5,
anchorY: 1
});
self.speed = 22; // Moves left
self.update = function () {
self.x -= self.speed;
};
return self;
});
// WaveSpike class (flying spike for wave mode)
var WaveSpike = Container.expand(function () {
var self = Container.call(this);
var spikeSprite = self.attachAsset('spike', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 28;
self.amplitude = 400 + Math.random() * 300; // vertical wave amplitude
self.frequency = 0.002 + Math.random() * 0.0015; // wave frequency
self.baseY = 1366 + (Math.random() - 0.5) * 900; // center Y
self.phase = Math.random() * Math.PI * 2;
self.update = function () {
self.x -= self.speed;
self.y = self.baseY + Math.sin(self.x * self.frequency + self.phase) * self.amplitude;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222831
});
/****
* Game Code
****/
// Obstacle: purple rectangle
// Spike: red triangle (approximate with a tall, thin box for MVP)
// Ground: gray rectangle
// Player: blue square
var LEVELS = [{
name: "First Steps",
difficulty: "Easy",
color: 0x2ecc71,
minGap: 500,
maxGap: 900,
types: ['spike', 'obstacle', 'spike'],
intro: "Welcome to your first steps!"
}, {
name: "Erin's Adventure",
difficulty: "Medium",
color: 0xf1c40f,
minGap: 500,
maxGap: 900,
types: ['spike', 'obstacle', 'spike'],
intro: "A trickier journey awaits."
}, {
name: "Final Steps",
difficulty: "Hard",
color: 0xe74c3c,
minGap: 500,
maxGap: 900,
types: ['spike', 'obstacle', 'spike'],
intro: "Only the brave survive!"
}, {
name: "Erin’s atventure continuation",
difficulty: "Hard",
color: 0x8e44ad,
minGap: 320,
maxGap: 480,
types: ['spike', 'spike', 'obstacle', 'spike', 'spike'],
intro: "The spikes are closer than ever. Time your jumps perfectly!"
}, {
name: "Dash",
difficulty: "Extreme",
color: 0x16a085,
minGap: 220,
maxGap: 350,
types: ['spike', 'spike', 'spike', 'obstacle', 'spike', 'spike', 'spike'],
intro: "Dash through a gauntlet of spikes! Can you survive?"
}, {
name: "Secret",
difficulty: "Demon",
color: 0x000000,
minGap: 220,
maxGap: 350,
types: ['spike', 'obstacle', 'spike', 'spike', 'obstacle', 'spike', 'spike', 'obstacle', 'spike', 'spike', 'obstacle', 'spike', 'spike', 'obstacle'],
intro: "The secret Demon level. Only the best will survive. Prepare for the hardest challenge!"
}, {
name: "THE REAL SECRET",
difficulty: "EXTREME DEMON",
color: 0x8B0000,
minGap: 180,
maxGap: 280,
types: ['spike', 'spike', 'spike', 'obstacle', 'spike', 'spike', 'spike', 'obstacle', 'spike', 'spike', 'spike', 'obstacle', 'spike', 'spike', 'spike'],
intro: "THE REAL SECRET revealed! This is the ultimate EXTREME DEMON challenge. Only legends survive here!"
}];
// --- MENU STATE ---
var MENU_STATE = {
MAIN: 0,
LEVEL_SELECT: 1,
PLAYING: 2
};
var menuState = MENU_STATE.MAIN;
var selectedLevel = 0;
// --- UI ELEMENTS ---
var menuContainer = new Container();
var levelSelectContainer = new Container();
var introText = null;
// --- GAME CONSTANTS ---
var GROUND_HEIGHT = 120;
var PLAYER_SIZE = 120;
var PLAYER_START_X = 400;
var OBSTACLE_MIN_GAP = 600;
var OBSTACLE_MAX_GAP = 1100;
var OBSTACLE_TYPES = ['spike', 'obstacle'];
var OBSTACLE_Y = 2732 - GROUND_HEIGHT;
// --- GAME STATE ---
var player;
var ground;
var obstacles = [];
var score = 0;
var scoreTxt;
var lastObstacleX = 0;
var isGameOver = false;
// --- WAVE MODE STATE (Dash level only) ---
var waveMode = false; // true if in wave mode
var waveObstacles = []; // flying spikes/obstacles in wave mode
// --- UI HELPERS ---
function clearMenuUI() {
if (menuContainer.parent) {
menuContainer.parent.removeChild(menuContainer);
}
if (levelSelectContainer.parent) {
levelSelectContainer.parent.removeChild(levelSelectContainer);
}
if (introText && introText.parent) {
introText.parent.removeChild(introText);
}
}
// --- MAIN MENU ---
function showMainMenu() {
clearMenuUI();
menuState = MENU_STATE.MAIN;
menuContainer.removeChildren();
// Title
var title = new Text2("Geometry Dash", {
size: 180,
fill: 0xFFFFFF,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 12
});
title.anchor.set(0.5, 0);
title.x = 2048 / 2;
title.y = 400;
menuContainer.addChild(title);
// Play button
var playBtn = new Text2("Play", {
size: 140,
fill: 0x2ECC71,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 10
});
playBtn.anchor.set(0.5, 0.5);
playBtn.x = 2048 / 2;
playBtn.y = 1000;
playBtn.interactive = true;
playBtn.buttonMode = true;
playBtn.down = function () {
showLevelSelect();
};
menuContainer.addChild(playBtn);
// Shop button
var shopBtn = new Text2("Shop", {
size: 120,
fill: 0xf1c40f,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 8
});
shopBtn.anchor.set(0.5, 0.5);
shopBtn.x = 2048 / 2;
shopBtn.y = 1200;
shopBtn.interactive = true;
shopBtn.buttonMode = true;
shopBtn.down = function () {
showShop();
};
menuContainer.addChild(shopBtn);
// NEW UPDATES COMING SOON text
var updatesText = new Text2("NEW UPDATES COMING SOON...", {
size: 100,
fill: 0xFFD700,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 7
});
updatesText.anchor.set(0.5, 0.5);
updatesText.x = 2048 / 2;
updatesText.y = 1400;
menuContainer.addChild(updatesText);
// THE REAL SECRET button
var realSecretBtn = new Text2("THE REAL SECRET", {
size: 120,
fill: 0x8B0000,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 8
});
realSecretBtn.anchor.set(0.5, 0.5);
realSecretBtn.x = 2048 / 2;
realSecretBtn.y = 1550;
realSecretBtn.interactive = true;
realSecretBtn.buttonMode = true;
realSecretBtn.down = function () {
// Check if player has 200 coins to access THE REAL SECRET
var coins = storage.coins || 0;
if (coins >= 200) {
// Start THE REAL SECRET level (index 6)
selectedLevel = 6;
showLevelIntro();
} else {
// Show insufficient coins message
var insufficientText = new Text2("Need 200 coins to access THE REAL SECRET!\nYou have: " + coins, {
size: 100,
fill: 0xff0000,
align: "center",
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 7
});
insufficientText.anchor.set(0.5, 0.5);
insufficientText.x = 2048 / 2;
insufficientText.y = 1800;
menuContainer.addChild(insufficientText);
// Remove message after 3 seconds
LK.setTimeout(function () {
if (insufficientText.parent) {
insufficientText.parent.removeChild(insufficientText);
}
}, 3000);
}
};
menuContainer.addChild(realSecretBtn);
// Add to game
game.addChild(menuContainer);
// Remove 'Back to Main Menu' button if present
if (game.goBackBtn && game.goBackBtn.parent) {
game.goBackBtn.parent.removeChild(game.goBackBtn);
game.goBackBtn.visible = false;
}
// Hide score text on main menu
if (scoreTxt) {
scoreTxt.visible = false;
// Always ensure scoreTxt is in GUI
if (!scoreTxt.parent) {
LK.gui.top.addChild(scoreTxt);
}
}
// Always ensure goBackBtn is in GUI and hidden
if (game.goBackBtn && !game.goBackBtn.parent) {
LK.gui.topRight.addChild(game.goBackBtn);
}
game.goBackBtn.visible = false;
}
// --- SHOP ---
var shopContainer = new Container();
var SHOP_ITEMS = [{
id: "skin_red",
name: "Red Skin",
price: 30,
color: 0xe74c3c
}, {
id: "skin_green",
name: "Green Skin",
price: 30,
color: 0x2ecc71
}, {
id: "skin_gold",
name: "Gold Skin",
price: 100,
color: 0xf1c40f
}];
function showShop() {
clearMenuUI();
menuState = MENU_STATE.MAIN;
shopContainer.removeChildren();
// Title
var shopTitle = new Text2("Shop", {
size: 150,
fill: 0xFFFFFF,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 10
});
shopTitle.anchor.set(0.5, 0);
shopTitle.x = 2048 / 2;
shopTitle.y = 300;
shopContainer.addChild(shopTitle);
// Coin display
var coins = storage.coins || 0;
var coinTxt = new Text2("Coins: " + coins, {
size: 100,
fill: 0xf1c40f,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 7
});
coinTxt.anchor.set(0.5, 0);
coinTxt.x = 2048 / 2;
coinTxt.y = 500;
shopContainer.addChild(coinTxt);
// List items
for (var i = 0; i < SHOP_ITEMS.length; i++) {
(function (idx) {
var item = SHOP_ITEMS[idx];
var owned = storage["owned_" + item.id] === true;
var y = 700 + idx * 220;
var itemTxt = new Text2(item.name, {
size: 100,
fill: "#" + item.color.toString(16).padStart(6, "0"),
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 7
});
itemTxt.anchor.set(0, 0.5);
itemTxt.x = 400;
itemTxt.y = y;
shopContainer.addChild(itemTxt);
var priceTxt = new Text2(owned ? "Owned" : item.price + " coins", {
size: 90,
fill: owned ? 0x2ecc71 : 0xffffff,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 6
});
priceTxt.anchor.set(0.5, 0.5);
priceTxt.x = 1200;
priceTxt.y = y;
shopContainer.addChild(priceTxt);
// Buy/select button
var btnTxt = owned ? storage.selectedSkin === item.id ? "Selected" : "Select" : "Buy";
var btn = new Text2(btnTxt, {
size: 90,
fill: owned ? storage.selectedSkin === item.id ? 0x2ecc71 : 0x3498db : 0xf1c40f,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 6
});
btn.anchor.set(0.5, 0.5);
btn.x = 1700;
btn.y = y;
btn.interactive = true;
btn.buttonMode = true;
btn.down = function () {
if (owned) {
// Select skin
storage.selectedSkin = item.id;
// If player exists, update its color immediately
if (player && player.childAt && typeof player.childAt === "function") {
// Remove old sprite
var oldSprite = player.childAt(0);
if (oldSprite) {
player.removeChild(oldSprite);
}
var newSprite;
if (item.id === "skin_red") {
newSprite = player.attachAsset('Red', {
anchorX: 0.5,
anchorY: 1
});
} else if (item.id === "skin_green") {
newSprite = player.attachAsset('Green', {
anchorX: 0.5,
anchorY: 1
});
} else if (item.id === "skin_gold") {
newSprite = player.attachAsset('Gold', {
anchorX: 0.5,
anchorY: 1
});
} else {
newSprite = player.attachAsset('player', {
anchorX: 0.5,
anchorY: 1
});
var skinColors = {
"skin_red": 0xe74c3c,
"skin_green": 0x2ecc71,
"skin_gold": 0xf1c40f
};
if (skinColors[item.id] && item.id !== "skin_red" && item.id !== "skin_green" && item.id !== "skin_gold") {
newSprite.color = skinColors[item.id];
}
}
}
showShop();
} else {
// Try to buy
var coins = storage.coins || 0;
if (coins >= item.price) {
storage.coins = coins - item.price;
storage["owned_" + item.id] = true;
storage.selectedSkin = item.id;
showShop();
} else {
priceTxt.setText("Not enough!");
priceTxt.fill = 0xe74c3c;
}
}
};
shopContainer.addChild(btn);
})(i);
}
// Back button
var backBtn = new Text2("Back", {
size: 90,
fill: 0x888888,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 6
});
backBtn.anchor.set(0.5, 0.5);
backBtn.x = 2048 / 2;
backBtn.y = 700 + SHOP_ITEMS.length * 220 + 100;
backBtn.interactive = true;
backBtn.buttonMode = true;
backBtn.down = function () {
// Remove shop UI before showing main menu
if (shopContainer.parent) {
shopContainer.parent.removeChild(shopContainer);
}
showMainMenu();
};
shopContainer.addChild(backBtn);
game.addChild(shopContainer);
// Always ensure scoreTxt is in GUI and hidden
if (scoreTxt && !scoreTxt.parent) {
LK.gui.top.addChild(scoreTxt);
}
scoreTxt.visible = false;
// Always ensure goBackBtn is in GUI and hidden
if (game.goBackBtn && !game.goBackBtn.parent) {
LK.gui.topRight.addChild(game.goBackBtn);
}
game.goBackBtn.visible = false;
}
// --- LEVEL SELECT MENU ---
function showLevelSelect() {
clearMenuUI();
menuState = MENU_STATE.LEVEL_SELECT;
levelSelectContainer.removeChildren();
// Title
var selTitle = new Text2("Select Level", {
size: 150,
fill: 0xFFFFFF,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 10
});
selTitle.anchor.set(0.5, 0);
selTitle.x = 2048 / 2;
selTitle.y = 300;
levelSelectContainer.addChild(selTitle);
// Level buttons
for (var i = 0; i < LEVELS.length; i++) {
(function (idx) {
var lvl = LEVELS[idx];
var btn = new Text2(lvl.name + " (" + lvl.difficulty + ")", {
size: 110,
fill: "#" + lvl.color.toString(16).padStart(6, "0"),
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 8
});
btn.anchor.set(0.5, 0.5);
btn.x = 2048 / 2;
btn.y = 600 + idx * 250;
btn.interactive = true;
btn.buttonMode = true;
btn.down = function () {
selectedLevel = idx;
showLevelIntro();
};
levelSelectContainer.addChild(btn);
// Insert "NEW UPDATES COMING SOON..." after Erin’s atventure continuation (index 3)
if (idx === 3) {
var updatesText = new Text2("NEW UPDATES COMING SOON...", {
size: 100,
fill: 0xFFD700,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 7
});
updatesText.anchor.set(0.5, 0.5);
updatesText.x = 2048 / 2;
updatesText.y = btn.y + 180;
levelSelectContainer.addChild(updatesText);
}
// Highlight Secret Demon level in red and with a demon icon if desired
if (idx === 5) {
btn.setText(lvl.name + " (" + lvl.difficulty + " 😈)");
btn.fill = "#ff0000";
}
})(i);
}
// THE REAL SECRET button
var realSecretBtn = new Text2("THE REAL SECRET", {
size: 120,
fill: 0x8B0000,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 8
});
realSecretBtn.anchor.set(0.5, 0.5);
realSecretBtn.x = 2048 / 2;
realSecretBtn.y = 600 + LEVELS.length * 250 + 50;
realSecretBtn.interactive = true;
realSecretBtn.buttonMode = true;
realSecretBtn.down = function () {
// Check if player has 200 coins to access THE REAL SECRET
var coins = storage.coins || 0;
if (coins >= 200) {
// Start THE REAL SECRET level (index 6)
selectedLevel = 6;
showLevelIntro();
} else {
// Show insufficient coins message
var insufficientText = new Text2("Need 200 coins to access THE REAL SECRET!\nYou have: " + coins, {
size: 100,
fill: 0xff0000,
align: "center",
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 7
});
insufficientText.anchor.set(0.5, 0.5);
insufficientText.x = 2048 / 2;
insufficientText.y = 1800;
levelSelectContainer.addChild(insufficientText);
// Remove message after 3 seconds
LK.setTimeout(function () {
if (insufficientText.parent) {
insufficientText.parent.removeChild(insufficientText);
}
}, 3000);
}
};
levelSelectContainer.addChild(realSecretBtn);
// Back button
var backBtn = new Text2("Back", {
size: 90,
fill: 0x888888,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 6
});
backBtn.anchor.set(0.5, 0.5);
backBtn.x = 2048 / 2;
backBtn.y = 600 + LEVELS.length * 250 + 200;
backBtn.interactive = true;
backBtn.buttonMode = true;
backBtn.down = function () {
showMainMenu();
};
levelSelectContainer.addChild(backBtn);
game.addChild(levelSelectContainer);
// Remove 'Back to Main Menu' button if present
if (game.goBackBtn && game.goBackBtn.parent) {
game.goBackBtn.parent.removeChild(game.goBackBtn);
game.goBackBtn.visible = false;
}
// Always ensure scoreTxt is in GUI and hidden
if (scoreTxt && !scoreTxt.parent) {
LK.gui.top.addChild(scoreTxt);
}
scoreTxt.visible = false;
// Always ensure goBackBtn is in GUI and hidden
if (game.goBackBtn && !game.goBackBtn.parent) {
LK.gui.topRight.addChild(game.goBackBtn);
}
game.goBackBtn.visible = false;
}
// --- LEVEL INTRO ---
function showLevelIntro() {
clearMenuUI();
menuState = MENU_STATE.LEVEL_SELECT;
var lvl = LEVELS[selectedLevel];
// Coin rewards per level
var COIN_REWARDS = [20, 50, 100, 150, 100, 200, 300];
var rewardText = lvl.name + "\nDifficulty: " + lvl.difficulty + "\n\n" + lvl.intro + "\n\nTap to Start";
var showClaim = false;
var coinsToClaim = 0;
if (storage.claimed && storage.claimed[selectedLevel] === false && storage.lastCompletedLevel !== undefined && storage.lastCompletedLevel == selectedLevel) {
// Player just beat this level and hasn't claimed coins yet
showClaim = true;
coinsToClaim = COIN_REWARDS[selectedLevel];
rewardText += "\n\n🎉 Level Complete! 🎉\nClaim +" + coinsToClaim + " coins!";
}
introText = new Text2(rewardText, {
size: 100,
fill: "#" + lvl.color.toString(16).padStart(6, "0"),
align: "center",
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 7
});
introText.anchor.set(0.5, 0.5);
introText.x = 2048 / 2;
introText.y = 1200;
introText.interactive = true;
introText.buttonMode = true;
introText.down = function () {
if (showClaim) {
return;
} // Don't start level if claim is available
startLevel(selectedLevel);
};
game.addChild(introText);
// Show claim button if eligible
if (showClaim) {
var claimBtn = new Text2("Claim +" + coinsToClaim + " coins", {
size: 110,
fill: 0xFFD700,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 8
});
claimBtn.anchor.set(0.5, 0.5);
claimBtn.x = 2048 / 2;
claimBtn.y = introText.y + 350;
claimBtn.interactive = true;
claimBtn.buttonMode = true;
claimBtn.down = function () {
// Add coins and mark as claimed
storage.coins = (storage.coins || 0) + coinsToClaim;
if (!storage.claimed) {
storage.claimed = {};
}
storage.claimed[selectedLevel] = true;
// Remove lastCompletedLevel so claim can't be repeated
delete storage.lastCompletedLevel;
// Update UI
claimBtn.setText("Claimed!");
claimBtn.interactive = false;
claimBtn.buttonMode = false;
};
game.addChild(claimBtn);
}
// Always show 'Back to Main Menu' button on every level intro
var backToMenuBtn = new Text2("Back to Main Menu", {
size: 90,
fill: 0xFFD700,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 6
});
// Anchor to center, place further down under "Tap to Start" and Claim button if present
backToMenuBtn.anchor.set(0.5, 0.5);
// If claimBtn is present, place below it, else below introText
var btnY = introText.y + 250;
// Find claimBtn if present
var claimBtnY = null;
var children = game.children ? game.children.slice() : [];
for (var i = 0; i < children.length; i++) {
if (children[i] && children[i].text && typeof children[i].text === "string" && children[i].text.indexOf("Claim") === 0) {
claimBtnY = children[i].y;
break;
}
}
if (claimBtnY !== null) {
btnY = claimBtnY + 250;
}
// Move button a little to the left (was +700, now +550) and even higher (was btnY - 200, then btnY - 350, then btnY - 500, then btnY - 650, then btnY - 800, then btnY - 950, then btnY - 1100, now btnY - 1250)
backToMenuBtn.x = 2048 / 2 + 550;
backToMenuBtn.y = btnY - 1250;
backToMenuBtn.interactive = true;
backToMenuBtn.buttonMode = true;
backToMenuBtn.visible = true;
backToMenuBtn.down = function () {
// Remove all game children (reset game state)
while (game.children && game.children.length > 0) {
var child = game.children[0];
if (child && child.parent) {
child.parent.removeChild(child);
}
}
// Remove score text from GUI if present
if (scoreTxt && scoreTxt.parent) {
scoreTxt.parent.removeChild(scoreTxt);
}
// Remove goBackBtn from GUI if present
if (game.goBackBtn && game.goBackBtn.parent) {
game.goBackBtn.parent.removeChild(game.goBackBtn);
game.goBackBtn.visible = false;
}
// Reset gameplay state variables
isGameOver = false;
menuState = MENU_STATE.MAIN;
// Show main menu
showMainMenu();
};
game.addChild(backToMenuBtn);
// Remove 'Back to Main Menu' button if present (from gameplay)
if (game.goBackBtn && game.goBackBtn.parent) {
game.goBackBtn.parent.removeChild(game.goBackBtn);
game.goBackBtn.visible = false;
}
// Hide score text on level intro
if (scoreTxt) {
scoreTxt.visible = false;
// Always ensure scoreTxt is in GUI
if (!scoreTxt.parent) {
LK.gui.top.addChild(scoreTxt);
}
}
// Always ensure goBackBtn is in GUI and hidden
if (game.goBackBtn && !game.goBackBtn.parent) {
LK.gui.topRight.addChild(game.goBackBtn);
}
game.goBackBtn.visible = false;
}
// --- START LEVEL ---
function startLevel(levelIdx) {
clearMenuUI();
menuState = MENU_STATE.PLAYING;
// Set level params
var lvl = LEVELS[levelIdx];
OBSTACLE_MIN_GAP = lvl.minGap;
OBSTACLE_MAX_GAP = lvl.maxGap;
OBSTACLE_TYPES = lvl.types;
// Set background color to a slightly darker blue for First Steps level
if (levelIdx === 0) {
game.setBackgroundColor(0x2176ae);
// Stop any music before playing Stereo Madness to ensure it starts
LK.stopMusic();
LK.playMusic('stereo_madness');
} else {
game.setBackgroundColor(0x222831);
// Stop Stereo Madness if switching away from First Steps
LK.stopMusic();
}
// Reset game state
resetGame();
// Show score
score = 0;
scoreTxt.setText("0");
scoreTxt.visible = true;
// Show ground and player
if (!ground) {
ground = LK.getAsset('ground', {
anchorX: 0,
anchorY: 1,
x: 0,
y: 2732
});
game.addChild(ground);
} else if (!ground.parent) {
game.addChild(ground);
}
if (!player) {
player = new Player();
player.groundY = 2732 - GROUND_HEIGHT;
if (levelIdx === 4) {
// Dash level: start in center for wave countdown
player.x = 2048 / 2;
player.y = 1366;
} else if (levelIdx === 5) {
// Secret Demon: start in center of screen, in air, always wave mode
player.x = 2048 / 2;
player.y = 1366; // center vertically
player.vy = 0;
player.isWave = true;
} else {
player.x = PLAYER_START_X;
player.y = player.groundY;
}
game.addChild(player);
} else if (!player.parent) {
game.addChild(player);
if (levelIdx === 4) {
player.x = 2048 / 2;
player.y = 1366;
} else if (levelIdx === 5) {
// Secret Demon: start in center, in air, always wave mode
player.x = 2048 / 2;
player.y = 700;
player.vy = 0;
player.isWave = true;
} else if (levelIdx === 6) {
// THE REAL SECRET: start in center, in air, always wave mode, even more extreme
player.x = 2048 / 2;
player.y = 1366;
player.vy = 0;
player.isWave = true;
} else {
player.x = PLAYER_START_X;
player.y = player.groundY;
}
}
// --- Dash level: prepare for 5s countdown at 20 points (do not show countdown yet) ---
if (levelIdx === 4) {
// Set player to center for Dash level start, do not enable wave mode yet
player.x = 2048 / 2;
player.y = 1366;
player.vy = 0;
player.isWave = false;
// Remove all obstacles for clean start
for (var i = 0; i < obstacles.length; i++) {
obstacles[i].destroy();
}
obstacles = [];
lastObstacleX = 1200 + 700;
// --- Dash level: 5s countdown at start ---
game._dashCountdownActive = true;
var dashCountdown = 5;
var dashCountdownText = new Text2(dashCountdown + "", {
size: 220,
fill: 0xFFD700,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 12
});
dashCountdownText.anchor.set(0.5, 0.5);
dashCountdownText.x = 2048 / 2;
dashCountdownText.y = 900;
game.addChild(dashCountdownText);
var dashCountdownTimer = LK.setInterval(function () {
dashCountdown--;
if (dashCountdown > 0) {
dashCountdownText.setText(dashCountdown + "");
} else {
LK.clearInterval(dashCountdownTimer);
if (dashCountdownText.parent) dashCountdownText.parent.removeChild(dashCountdownText);
game._dashCountdownActive = false;
// After countdown, enter wave mode
waveMode = true;
// Remove all ground obstacles
for (var i = 0; i < obstacles.length; i++) {
obstacles[i].destroy();
}
obstacles = [];
// Move player to center Y for wave mode (start in mid air at y=700)
player.y = 700;
player.vy = 0;
// Change player sprite to wave asset
if (player.childAt && typeof player.childAt === "function") {
var oldSprite = player.childAt(0);
if (oldSprite) {
player.removeChild(oldSprite);
}
// Attach wave asset, anchor center
var waveSprite = player.attachAsset('Wave', {
anchorX: 0.5,
anchorY: 0.5
});
}
// Enable wave mode controls
player.isWave = true;
// Set player.groundY for after wave mode
if (typeof player.groundY !== "number") {
player.groundY = 2732 - GROUND_HEIGHT;
}
}
}, 1000);
// Remove all obstacles and prevent spawning until countdown ends
for (var i = 0; i < obstacles.length; i++) {
obstacles[i].destroy();
}
obstacles = [];
lastObstacleX = 1200 + 700;
// Set player to wave mode start position, but do not enable wave mode yet
player.x = 2048 / 2;
player.y = 1366;
player.vy = 0;
player.isWave = false;
// Block update and input until countdown ends
}
// --- Secret Demon: always wave mode, no countdown, clear obstacles, set up wave obstacles ---
if (levelIdx === 5) {
// Remove all obstacles for clean start
for (var i = 0; i < obstacles.length; i++) {
obstacles[i].destroy();
}
obstacles = [];
lastObstacleX = 1200 + 700;
// Remove all wave obstacles
if (typeof waveObstacles !== "undefined") {
for (var i = 0; i < waveObstacles.length; i++) {
waveObstacles[i].destroy && waveObstacles[i].destroy();
}
waveObstacles = [];
}
// Set player to wave mode, attach wave sprite
if (player.childAt && typeof player.childAt === "function") {
var oldSprite = player.childAt(0);
if (oldSprite) {
player.removeChild(oldSprite);
}
// Attach wave asset, anchor center
var waveSprite = player.attachAsset('Wave', {
anchorX: 0.5,
anchorY: 0.5
});
}
player.isWave = true;
// --- Add static ground spikes for Secret Demon level ---
// Place even fewer spikes, more space between them, leaving a small gap at left/right
var spikeW = 100;
var spikeH = 150;
var groundY = 2732 - GROUND_HEIGHT + 2; // +2 to ensure overlap
var startX = 60; // leave a gap at left
var endX = 2048 - 60;
// Increase spacing to every 4 spike widths (fewer spikes)
for (var sx = startX; sx < endX; sx += spikeW * 4) {
var groundSpike = new Spike();
groundSpike.x = sx + spikeW / 2;
groundSpike.y = groundY;
groundSpike.speed = 0; // static
// Mark as ground spike for collision logic if needed
groundSpike.isGroundSpike = true;
obstacles.push(groundSpike);
game.addChild(groundSpike);
}
}
// --- THE REAL SECRET Level: always wave mode, more extreme than Secret Demon ---
if (levelIdx === 6) {
// Remove all obstacles for clean start
for (var i = 0; i < obstacles.length; i++) {
obstacles[i].destroy();
}
obstacles = [];
lastObstacleX = 1200 + 700;
// Remove all wave obstacles
if (typeof waveObstacles !== "undefined") {
for (var i = 0; i < waveObstacles.length; i++) {
waveObstacles[i].destroy && waveObstacles[i].destroy();
}
waveObstacles = [];
}
// Set player to wave mode, attach wave sprite
if (player.childAt && typeof player.childAt === "function") {
var oldSprite = player.childAt(0);
if (oldSprite) {
player.removeChild(oldSprite);
}
// Attach wave asset, anchor center
var waveSprite = player.attachAsset('Wave', {
anchorX: 0.5,
anchorY: 0.5
});
}
player.isWave = true;
// Add even fewer static ground spikes for THE REAL SECRET level
var spikeW = 100;
var spikeH = 150;
var groundY = 2732 - GROUND_HEIGHT + 2;
var startX = 80; // more gap at left
var endX = 2048 - 80;
// Much more spacing - every 5 spike widths (extremely few spikes)
for (var sx = startX; sx < endX; sx += spikeW * 5) {
var groundSpike = new Spike();
groundSpike.x = sx + spikeW / 2;
groundSpike.y = groundY;
groundSpike.speed = 0; // static
groundSpike.isGroundSpike = true;
obstacles.push(groundSpike);
game.addChild(groundSpike);
}
}
// Always ensure scoreTxt is in GUI and visible
if (scoreTxt && !scoreTxt.parent) {
LK.gui.top.addChild(scoreTxt);
}
scoreTxt.visible = true;
// Always ensure goBackBtn is in GUI and visible at gameplay start
if (game.goBackBtn && !game.goBackBtn.parent) {
LK.gui.topRight.addChild(game.goBackBtn);
}
game.goBackBtn.visible = true;
// Initial obstacle
lastObstacleX = 1200 + 700; //{2I} // Add extra distance for a longer start
spawnObstacle();
// Remove 'Back to Main Menu' button if present (should only show after level complete)
if (game.goBackBtn && game.goBackBtn.parent) {
game.goBackBtn.parent.removeChild(game.goBackBtn);
game.goBackBtn.visible = false;
}
// Do not show 'Back to Main Menu' button in gameplay
game.goBackBtn.visible = false;
}
// --- SCORE TEXT ---
scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 8
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
scoreTxt.visible = true;
// --- GO BACK TO MAIN MENU BUTTON (persistent for gameplay) ---
game.goBackBtn = new Text2("Back to Main Menu", {
size: 90,
fill: 0xFFD700,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 6
});
game.goBackBtn.anchor.set(1, 0);
game.goBackBtn.x = 2048 - 40;
game.goBackBtn.y = 380;
game.goBackBtn.interactive = true;
game.goBackBtn.buttonMode = true;
game.goBackBtn.visible = false;
game.goBackBtn.down = function () {
// Remove gameplay UI and go to main menu
if (scoreTxt) {
scoreTxt.visible = false;
}
if (game.goBackBtn && game.goBackBtn.parent) {
game.goBackBtn.parent.removeChild(game.goBackBtn);
}
game.goBackBtn.visible = false;
showMainMenu();
};
LK.gui.topRight.addChild(game.goBackBtn);
// --- NEW UPDATES COMING SOON... ---
var sampleText = new Text2("NEW UPDATES COMING SOON...", {
size: 100,
fill: 0xFFD700,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 7
});
sampleText.anchor.set(0.5, 0.5);
sampleText.x = 2048 / 2;
sampleText.y = 2732 / 2;
sampleText.interactive = true;
sampleText.buttonMode = true;
sampleText.down = function () {
// Secret action: show a message or perform a hidden action
LK.showMessage && LK.showMessage("You found the secret button! 🎉");
};
game.addChild(sampleText);
// --- INITIALIZE: show main menu ---
showMainMenu();
// Helper: spawn obstacle
function spawnObstacle() {
// Randomly pick type
var type = OBSTACLE_TYPES[Math.floor(Math.random() * OBSTACLE_TYPES.length)];
var obs;
if (type === 'spike') {
obs = new Spike();
obs.x = 2048 + 100;
obs.y = OBSTACLE_Y;
} else {
obs = new Obstacle();
obs.x = 2048 + 100;
obs.y = OBSTACLE_Y;
}
obstacles.push(obs);
game.addChild(obs);
lastObstacleX = obs.x;
}
// Helper: reset game state
function resetGame() {
// Remove obstacles
for (var i = 0; i < obstacles.length; i++) {
obstacles[i].destroy();
}
obstacles = [];
// Remove wave obstacles
if (typeof waveObstacles !== "undefined") {
for (var i = 0; i < waveObstacles.length; i++) {
waveObstacles[i].destroy && waveObstacles[i].destroy();
}
waveObstacles = [];
}
// Reset wave mode
waveMode = false;
// Reset player
if (player) {
player.x = PLAYER_START_X;
player.y = player.groundY;
player.vy = 0;
player.isJumping = false;
player.visible = true; // Make player visible again after explosion
player.isWave = false; // Always reset wave mode state
// Always restore player sprite to correct asset in Dash level (or after wave mode)
if (player.childAt && typeof player.childAt === "function") {
var oldSprite = player.childAt(0);
if (selectedLevel === 4) {
if (oldSprite && oldSprite.assetId === "Wave") {
player.removeChild(oldSprite);
oldSprite = null;
}
}
if (!oldSprite || selectedLevel === 4 && oldSprite.assetId === "Wave") {
var newSprite;
if (storage.selectedSkin === "skin_red") {
newSprite = player.attachAsset('Red', {
anchorX: 0.5,
anchorY: 1
});
} else if (storage.selectedSkin === "skin_green") {
newSprite = player.attachAsset('Green', {
anchorX: 0.5,
anchorY: 1
});
} else if (storage.selectedSkin === "skin_gold") {
newSprite = player.attachAsset('Gold', {
anchorX: 0.5,
anchorY: 1
});
} else {
newSprite = player.attachAsset('player', {
anchorX: 0.5,
anchorY: 1
});
var skinColors = {
"skin_red": 0xe74c3c,
"skin_green": 0x2ecc71,
"skin_gold": 0xf1c40f
};
if (storage.selectedSkin && skinColors[storage.selectedSkin] && storage.selectedSkin !== "skin_red" && storage.selectedSkin !== "skin_green" && storage.selectedSkin !== "skin_gold") {
newSprite.color = skinColors[storage.selectedSkin];
}
}
}
}
}
// Reset score
score = 0;
if (scoreTxt) {
scoreTxt.setText(score);
scoreTxt.visible = true;
if (!scoreTxt.parent) {
LK.gui.top.addChild(scoreTxt);
}
}
// Always ensure goBackBtn is in GUI and visible
if (game.goBackBtn && !game.goBackBtn.parent) {
LK.gui.topRight.addChild(game.goBackBtn);
}
if (game.goBackBtn) {
game.goBackBtn.visible = true;
}
// Always ensure scoreTxt is in GUI and visible
if (scoreTxt && !scoreTxt.parent) {
LK.gui.top.addChild(scoreTxt);
}
if (scoreTxt) {
scoreTxt.visible = true;
}
lastObstacleX = 1200;
isGameOver = false;
}
// Touch/click to jump
game.down = function (x, y, obj) {
if (menuState !== MENU_STATE.PLAYING) {
return;
}
if (isGameOver) {
return;
}
// Block input during Dash level countdown
if (selectedLevel === 4 && game._dashCountdownActive) {
return;
}
if (player.isWave) {
// In wave mode, pressing makes you go up
player.vy = -38;
// Optionally, rotate the wave asset for a little feedback
var waveSprite = player.childAt && player.childAt(0);
if (waveSprite) {
tween(waveSprite, {
rotation: waveSprite.rotation + Math.PI * 2
}, {
duration: 400,
easing: tween.linear
});
}
return;
} else {
player.jump();
}
};
// Main update loop
game.update = function () {
if (menuState !== MENU_STATE.PLAYING) {
return;
}
if (isGameOver) {
return;
}
// --- Block update, obstacle spawn, and wave mode entry during Dash level 5s countdown ---
if (selectedLevel === 4 && game._dashCountdownActive) {
// Only update player position (so countdown looks correct), skip all obstacle logic and wave mode
if (player && typeof player.update === "function") player.update();
return;
}
// --- WAVE MODE TRIGGER (Dash level only) ---
// Dash level is index 4, Secret Demon is index 5
if (selectedLevel === 4) {
// --- 5s countdown at 20 points ---
if (!game._dashCountdownActive && !waveMode && score >= 20) {
// Start 5s countdown, block input and update
game._dashCountdownActive = true;
var countdown = 5;
var countdownText = new Text2(countdown + "", {
size: 220,
fill: 0xFFD700,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 12
});
countdownText.anchor.set(0.5, 0.5);
countdownText.x = 2048 / 2;
countdownText.y = 900;
game.addChild(countdownText);
var countdownTimer = LK.setInterval(function () {
countdown--;
if (countdown > 0) {
countdownText.setText(countdown + "");
} else {
LK.clearInterval(countdownTimer);
if (countdownText.parent) countdownText.parent.removeChild(countdownText);
game._dashCountdownActive = false;
// After countdown, enter wave mode
waveMode = true;
// Remove all ground obstacles
for (var i = 0; i < obstacles.length; i++) {
obstacles[i].destroy();
}
obstacles = [];
// Move player to center Y for wave mode (start in mid air at y=700)
player.y = 700;
player.vy = 0;
// Change player sprite to wave asset
if (player.childAt && typeof player.childAt === "function") {
var oldSprite = player.childAt(0);
if (oldSprite) {
player.removeChild(oldSprite);
}
// Attach wave asset, anchor center
var waveSprite = player.attachAsset('Wave', {
anchorX: 0.5,
anchorY: 0.5
});
}
// Enable wave mode controls
player.isWave = true;
// Set player.groundY for after wave mode
if (typeof player.groundY !== "number") {
player.groundY = 2732 - GROUND_HEIGHT;
}
}
}, 1000);
// Remove all obstacles and prevent spawning until countdown ends
for (var i = 0; i < obstacles.length; i++) {
obstacles[i].destroy();
}
obstacles = [];
lastObstacleX = 1200 + 700;
// Set player to wave mode start position, but do not enable wave mode yet
player.x = 2048 / 2;
player.y = 1366;
player.vy = 0;
player.isWave = false;
// Block update and input until countdown ends
return;
}
// Block update and input during countdown
if (game._dashCountdownActive) {
if (player && typeof player.update === "function") player.update();
return;
}
// End wave mode at 75 points
if (waveMode && score >= 75) {
waveMode = false;
// Remove all wave obstacles
for (var i = 0; i < waveObstacles.length; i++) {
waveObstacles[i].destroy && waveObstacles[i].destroy();
}
waveObstacles = [];
// Reset player to ground
player.y = player.groundY;
player.vy = 0;
// Restore player sprite to selected skin
if (player.childAt && typeof player.childAt === "function") {
var oldSprite = player.childAt(0);
if (oldSprite) {
player.removeChild(oldSprite);
}
var newSprite;
if (storage.selectedSkin === "skin_red") {
newSprite = player.attachAsset('Red', {
anchorX: 0.5,
anchorY: 1
});
} else if (storage.selectedSkin === "skin_green") {
newSprite = player.attachAsset('Green', {
anchorX: 0.5,
anchorY: 1
});
} else if (storage.selectedSkin === "skin_gold") {
newSprite = player.attachAsset('Gold', {
anchorX: 0.5,
anchorY: 1
});
} else {
newSprite = player.attachAsset('player', {
anchorX: 0.5,
anchorY: 1
});
var skinColors = {
"skin_red": 0xe74c3c,
"skin_green": 0x2ecc71,
"skin_gold": 0xf1c40f
};
if (storage.selectedSkin && skinColors[storage.selectedSkin] && storage.selectedSkin !== "skin_red" && storage.selectedSkin !== "skin_green" && storage.selectedSkin !== "skin_gold") {
newSprite.color = skinColors[storage.selectedSkin];
}
}
}
// Disable wave mode controls
player.isWave = false;
}
}
// --- Secret Demon Level: always wave mode, no countdown, more/faster obstacles, never disables wave mode ---
if (selectedLevel === 5) {
waveMode = true;
player.isWave = true;
// Always keep player in wave mode, never disable
// Remove all ground obstacles (should be empty, but just in case)
for (var i = 0; i < obstacles.length; i++) {
obstacles[i].destroy();
}
obstacles = [];
// Optionally, increase flying obstacle spawn rate and speed
// (handled below in obstacle spawn section)
}
// --- THE REAL SECRET Level: always wave mode, even more extreme than Secret Demon ---
if (selectedLevel === 6) {
waveMode = true;
player.isWave = true;
// Always keep player in wave mode, never disable
// Remove all ground obstacles
for (var i = 0; i < obstacles.length; i++) {
obstacles[i].destroy();
}
obstacles = [];
}
// Update player
player.update();
// --- NORMAL OBSTACLES ---
if (!waveMode) {
// Update obstacles
for (var i = obstacles.length - 1; i >= 0; i--) {
var obs = obstacles[i];
obs.update();
// Remove if off screen
if (obs.x < -200) {
obs.destroy();
obstacles.splice(i, 1);
continue;
}
// Collision detection
if (player.intersects(obs)) {
// Explode player into small pieces
if (player && player.parent) {
var numPieces = 12;
var centerX = player.x;
var centerY = player.y;
var pieceSize = 32;
for (var p = 0; p < numPieces; p++) {
(function (pieceIdx) {
var angle = Math.PI * 2 / numPieces * pieceIdx;
var px = centerX;
var py = centerY - PLAYER_SIZE / 2 + pieceSize / 2;
var piece = new Container();
var pieceSprite = piece.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
width: pieceSize,
height: pieceSize
});
// Use same color as player
var playerSpriteRef = player && player.childAt ? player.childAt(0) : null;
if (playerSpriteRef && playerSpriteRef.color) {
pieceSprite.color = playerSpriteRef.color;
}
piece.x = px;
piece.y = py;
game.addChild(piece);
// Animate outward with random velocity and fade out
var speed = 32 + Math.random() * 32;
var dx = Math.cos(angle) * speed;
var dy = Math.sin(angle) * speed - 10 + Math.random() * 20;
tween(piece, {
x: px + dx * 16,
y: py + dy * 16,
alpha: 0
}, {
duration: 700 + Math.random() * 200,
easing: tween.easeOutCubic,
onComplete: function onComplete() {
piece.destroy();
}
});
})(p);
}
// Hide player sprite immediately
player.visible = false;
}
isGameOver = true;
// Delay restart until after explosion animation
LK.setTimeout(function () {
startLevel(selectedLevel);
}, 800);
return;
}
}
}
// --- WAVE MODE OBSTACLES ---
if (waveMode) {
// Kill player if they touch the ground in Dash wave mode (ground is death)
if (selectedLevel === 4 && player.y >= 2732 - GROUND_HEIGHT) {
// Explode player into small pieces
if (player && player.parent) {
var numPieces = 12;
var centerX = player.x;
var centerY = player.y;
var pieceSize = 32;
for (var p = 0; p < numPieces; p++) {
(function (pieceIdx) {
var angle = Math.PI * 2 / numPieces * pieceIdx;
var px = centerX;
var py = centerY - PLAYER_SIZE / 2 + pieceSize / 2;
var piece = new Container();
var pieceSprite = piece.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
width: pieceSize,
height: pieceSize
});
var playerSpriteRef = player && player.childAt ? player.childAt(0) : null;
if (playerSpriteRef && playerSpriteRef.color) {
pieceSprite.color = playerSpriteRef.color;
}
piece.x = px;
piece.y = py;
game.addChild(piece);
var speed = 32 + Math.random() * 32;
var dx = Math.cos(angle) * speed;
var dy = Math.sin(angle) * speed - 10 + Math.random() * 20;
tween(piece, {
x: px + dx * 16,
y: py + dy * 16,
alpha: 0
}, {
duration: 700 + Math.random() * 200,
easing: tween.easeOutCubic,
onComplete: function onComplete() {
piece.destroy();
}
});
})(p);
}
player.visible = false;
}
isGameOver = true;
LK.setTimeout(function () {
startLevel(selectedLevel);
}, 800);
return;
}
// Update flying obstacles
for (var i = waveObstacles.length - 1; i >= 0; i--) {
var wobs = waveObstacles[i];
wobs.update && wobs.update();
// Remove if off screen
if (wobs.x < -200) {
wobs.destroy && wobs.destroy();
waveObstacles.splice(i, 1);
continue;
}
// Collision detection
if (player.intersects(wobs)) {
// Explode player into small pieces
if (player && player.parent) {
var numPieces = 12;
var centerX = player.x;
var centerY = player.y;
var pieceSize = 32;
for (var p = 0; p < numPieces; p++) {
(function (pieceIdx) {
var angle = Math.PI * 2 / numPieces * pieceIdx;
var px = centerX;
var py = centerY - PLAYER_SIZE / 2 + pieceSize / 2;
var piece = new Container();
var pieceSprite = piece.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
width: pieceSize,
height: pieceSize
});
var playerSpriteRef = player && player.childAt ? player.childAt(0) : null;
if (playerSpriteRef && playerSpriteRef.color) {
pieceSprite.color = playerSpriteRef.color;
}
piece.x = px;
piece.y = py;
game.addChild(piece);
var speed = 32 + Math.random() * 32;
var dx = Math.cos(angle) * speed;
var dy = Math.sin(angle) * speed - 10 + Math.random() * 20;
tween(piece, {
x: px + dx * 16,
y: py + dy * 16,
alpha: 0
}, {
duration: 700 + Math.random() * 200,
easing: tween.easeOutCubic,
onComplete: function onComplete() {
piece.destroy();
}
});
})(p);
}
player.visible = false;
}
isGameOver = true;
LK.setTimeout(function () {
startLevel(selectedLevel);
}, 800);
return;
}
}
}
// Score: increase as player passes obstacles
if (!waveMode) {
for (var j = 0; j < obstacles.length; j++) {
var o = obstacles[j];
if (!o.passed && o.x + 60 < player.x) {
o.passed = true;
score += 1;
if (scoreTxt) scoreTxt.setText(score);
}
}
} else {
// In wave mode, score increases as player passes flying obstacles
for (var j = 0; j < waveObstacles.length; j++) {
var wo = waveObstacles[j];
if (!wo.passed && wo.x + 60 < player.x) {
wo.passed = true;
score += 1;
if (scoreTxt) scoreTxt.setText(score);
}
}
}
// Win condition: pass 10 obstacles (easy), 15 (medium), 20 (hard), 30 (Erin's atventure continuation), 30 (Dash), 50 (Secret Demon), 100 (THE REAL SECRET)
var winScores = [10, 15, 20, 30, 30, 50, 100];
if (score >= winScores[selectedLevel]) {
isGameOver = true;
// Mark level as completed for coin claim
storage.lastCompletedLevel = selectedLevel;
if (!storage.claimed) {
storage.claimed = {};
}
storage.claimed[selectedLevel] = storage.claimed[selectedLevel] || false;
// Award +150 coins if Erin’s atventure continuation (index 3)
// (Now handled in claim button, not here)
LK.setTimeout(function () {
showLevelIntro();
}, 600);
return;
}
// Spawn new obstacles
if (!waveMode) {
if (obstacles.length === 0 || 2048 - lastObstacleX > OBSTACLE_MIN_GAP + Math.floor(Math.random() * (OBSTACLE_MAX_GAP - OBSTACLE_MIN_GAP))) {
spawnObstacle();
}
} else if (waveMode && selectedLevel === 4 && score < 75) {
// In wave mode, spawn flying spikes/obstacles at random intervals
if (waveObstacles.length === 0 || waveObstacles.length < 4 && Math.random() < 0.04) {
// Randomly choose between flying spike and flying obstacle
var wtype = Math.random() < 0.6 ? 'spike' : 'obstacle';
var wobs;
if (wtype === 'spike') {
wobs = new WaveSpike();
} else {
wobs = new FlyingObstacle();
}
wobs.x = 2048 + 100;
// Random Y in upper/lower half, but not too close to edge
wobs.y = 400 + Math.random() * (2732 - 800);
waveObstacles.push(wobs);
game.addChild(wobs);
}
} else if (waveMode && selectedLevel === 5) {
// Secret Demon: spawn up to 2 flying obstacles, much lower spawn chance, still SLOWER speed
if (waveObstacles.length === 0 || waveObstacles.length < 2 && Math.random() < 0.025) {
var wtype = Math.random() < 0.7 ? 'spike' : 'obstacle';
var wobs;
if (wtype === 'spike') {
wobs = new WaveSpike();
wobs.speed = 18 + Math.random() * 4; // much slower than Dash
} else {
wobs = new FlyingObstacle();
wobs.speed = 18 + Math.random() * 4; // much slower than Dash
}
wobs.x = 2048 + 100;
// Random Y, but allow more vertical spread
wobs.y = 200 + Math.random() * (2732 - 400);
waveObstacles.push(wobs);
game.addChild(wobs);
}
} else if (waveMode && selectedLevel === 6) {
// THE REAL SECRET: spawn up to 3 flying obstacles, higher spawn chance, EXTREME speed
if (waveObstacles.length === 0 || waveObstacles.length < 3 && Math.random() < 0.035) {
var wtype = Math.random() < 0.8 ? 'spike' : 'obstacle';
var wobs;
if (wtype === 'spike') {
wobs = new WaveSpike();
wobs.speed = 35 + Math.random() * 8; // EXTREME speed - faster than Dash
} else {
wobs = new FlyingObstacle();
wobs.speed = 35 + Math.random() * 8; // EXTREME speed - faster than Dash
}
wobs.x = 2048 + 100;
// Random Y with full vertical range for maximum difficulty
wobs.y = 100 + Math.random() * (2732 - 200);
waveObstacles.push(wobs);
game.addChild(wobs);
}
}
};
// (gameover event handler removed, as game over popup is never shown) /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
coins: 0,
claimed: {}
});
/****
* Classes
****/
// FlyingObstacle class (flying block for wave mode)
var FlyingObstacle = Container.expand(function () {
var self = Container.call(this);
var obsSprite = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 28;
self.amplitude = 350 + Math.random() * 250;
self.frequency = 0.0015 + Math.random() * 0.001;
self.baseY = 1366 + (Math.random() - 0.5) * 900;
self.phase = Math.random() * Math.PI * 2;
self.update = function () {
self.x -= self.speed;
self.y = self.baseY + Math.cos(self.x * self.frequency + self.phase) * self.amplitude;
};
return self;
});
// Obstacle class (tall block)
var Obstacle = Container.expand(function () {
var self = Container.call(this);
var obsSprite = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 1
});
self.speed = 22;
self.update = function () {
self.x -= self.speed;
};
return self;
});
// Player class
var Player = Container.expand(function () {
var self = Container.call(this);
// Use image asset for red, green, or gold skin, otherwise use shape asset and tint for other skins
var playerSprite;
if (storage.selectedSkin === "skin_red") {
playerSprite = self.attachAsset('Red', {
anchorX: 0.5,
anchorY: 1
});
} else if (storage.selectedSkin === "skin_green") {
playerSprite = self.attachAsset('Green', {
anchorX: 0.5,
anchorY: 1
});
} else if (storage.selectedSkin === "skin_gold") {
playerSprite = self.attachAsset('Gold', {
anchorX: 0.5,
anchorY: 1
});
} else {
playerSprite = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 1
});
// Apply skin color if selected and not red, green, or gold
var skinColors = {
"skin_red": 0xe74c3c,
"skin_green": 0x2ecc71,
"skin_gold": 0xf1c40f
};
if (storage.selectedSkin && skinColors[storage.selectedSkin] && storage.selectedSkin !== "skin_red" && storage.selectedSkin !== "skin_green" && storage.selectedSkin !== "skin_gold") {
playerSprite.color = skinColors[storage.selectedSkin];
}
}
// Physics
self.vy = 0;
self.isJumping = false;
self.gravity = 2.2;
self.jumpStrength = -48;
self.groundY = 0; // Set after ground is created
self.isWave = false; // Always initialize wave mode state
// Update method
self.update = function () {
if (self.isWave) {
// Wave mode: apply constant gravity, but allow upward thrust on press
self.vy += 3.2; // slightly higher gravity for wave feel
self.y += self.vy;
// Clamp to screen bounds (not off top or bottom)
if (self.y < 60) {
self.y = 60;
self.vy = 0;
}
if (self.y > 2732 - 60) {
self.y = 2732 - 60;
self.vy = 0;
}
} else {
// Normal mode
self.vy += self.gravity;
self.y += self.vy;
// Ground collision
if (self.y > self.groundY) {
self.y = self.groundY;
self.vy = 0;
self.isJumping = false;
}
}
};
// Jump method
self.jump = function () {
if (!self.isJumping && self.y >= self.groundY) {
self.vy = self.jumpStrength;
self.isJumping = true;
// Start 360-degree rotation tween on playerSprite
tween(playerSprite, {
rotation: playerSprite.rotation + Math.PI * 2
}, {
duration: 400,
easing: tween.linear
});
}
};
return self;
});
// Spike class (obstacle)
var Spike = Container.expand(function () {
var self = Container.call(this);
var spikeSprite = self.attachAsset('spike', {
anchorX: 0.5,
anchorY: 1
});
self.speed = 22; // Moves left
self.update = function () {
self.x -= self.speed;
};
return self;
});
// WaveSpike class (flying spike for wave mode)
var WaveSpike = Container.expand(function () {
var self = Container.call(this);
var spikeSprite = self.attachAsset('spike', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 28;
self.amplitude = 400 + Math.random() * 300; // vertical wave amplitude
self.frequency = 0.002 + Math.random() * 0.0015; // wave frequency
self.baseY = 1366 + (Math.random() - 0.5) * 900; // center Y
self.phase = Math.random() * Math.PI * 2;
self.update = function () {
self.x -= self.speed;
self.y = self.baseY + Math.sin(self.x * self.frequency + self.phase) * self.amplitude;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222831
});
/****
* Game Code
****/
// Obstacle: purple rectangle
// Spike: red triangle (approximate with a tall, thin box for MVP)
// Ground: gray rectangle
// Player: blue square
var LEVELS = [{
name: "First Steps",
difficulty: "Easy",
color: 0x2ecc71,
minGap: 500,
maxGap: 900,
types: ['spike', 'obstacle', 'spike'],
intro: "Welcome to your first steps!"
}, {
name: "Erin's Adventure",
difficulty: "Medium",
color: 0xf1c40f,
minGap: 500,
maxGap: 900,
types: ['spike', 'obstacle', 'spike'],
intro: "A trickier journey awaits."
}, {
name: "Final Steps",
difficulty: "Hard",
color: 0xe74c3c,
minGap: 500,
maxGap: 900,
types: ['spike', 'obstacle', 'spike'],
intro: "Only the brave survive!"
}, {
name: "Erin’s atventure continuation",
difficulty: "Hard",
color: 0x8e44ad,
minGap: 320,
maxGap: 480,
types: ['spike', 'spike', 'obstacle', 'spike', 'spike'],
intro: "The spikes are closer than ever. Time your jumps perfectly!"
}, {
name: "Dash",
difficulty: "Extreme",
color: 0x16a085,
minGap: 220,
maxGap: 350,
types: ['spike', 'spike', 'spike', 'obstacle', 'spike', 'spike', 'spike'],
intro: "Dash through a gauntlet of spikes! Can you survive?"
}, {
name: "Secret",
difficulty: "Demon",
color: 0x000000,
minGap: 220,
maxGap: 350,
types: ['spike', 'obstacle', 'spike', 'spike', 'obstacle', 'spike', 'spike', 'obstacle', 'spike', 'spike', 'obstacle', 'spike', 'spike', 'obstacle'],
intro: "The secret Demon level. Only the best will survive. Prepare for the hardest challenge!"
}, {
name: "THE REAL SECRET",
difficulty: "EXTREME DEMON",
color: 0x8B0000,
minGap: 180,
maxGap: 280,
types: ['spike', 'spike', 'spike', 'obstacle', 'spike', 'spike', 'spike', 'obstacle', 'spike', 'spike', 'spike', 'obstacle', 'spike', 'spike', 'spike'],
intro: "THE REAL SECRET revealed! This is the ultimate EXTREME DEMON challenge. Only legends survive here!"
}];
// --- MENU STATE ---
var MENU_STATE = {
MAIN: 0,
LEVEL_SELECT: 1,
PLAYING: 2
};
var menuState = MENU_STATE.MAIN;
var selectedLevel = 0;
// --- UI ELEMENTS ---
var menuContainer = new Container();
var levelSelectContainer = new Container();
var introText = null;
// --- GAME CONSTANTS ---
var GROUND_HEIGHT = 120;
var PLAYER_SIZE = 120;
var PLAYER_START_X = 400;
var OBSTACLE_MIN_GAP = 600;
var OBSTACLE_MAX_GAP = 1100;
var OBSTACLE_TYPES = ['spike', 'obstacle'];
var OBSTACLE_Y = 2732 - GROUND_HEIGHT;
// --- GAME STATE ---
var player;
var ground;
var obstacles = [];
var score = 0;
var scoreTxt;
var lastObstacleX = 0;
var isGameOver = false;
// --- WAVE MODE STATE (Dash level only) ---
var waveMode = false; // true if in wave mode
var waveObstacles = []; // flying spikes/obstacles in wave mode
// --- UI HELPERS ---
function clearMenuUI() {
if (menuContainer.parent) {
menuContainer.parent.removeChild(menuContainer);
}
if (levelSelectContainer.parent) {
levelSelectContainer.parent.removeChild(levelSelectContainer);
}
if (introText && introText.parent) {
introText.parent.removeChild(introText);
}
}
// --- MAIN MENU ---
function showMainMenu() {
clearMenuUI();
menuState = MENU_STATE.MAIN;
menuContainer.removeChildren();
// Title
var title = new Text2("Geometry Dash", {
size: 180,
fill: 0xFFFFFF,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 12
});
title.anchor.set(0.5, 0);
title.x = 2048 / 2;
title.y = 400;
menuContainer.addChild(title);
// Play button
var playBtn = new Text2("Play", {
size: 140,
fill: 0x2ECC71,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 10
});
playBtn.anchor.set(0.5, 0.5);
playBtn.x = 2048 / 2;
playBtn.y = 1000;
playBtn.interactive = true;
playBtn.buttonMode = true;
playBtn.down = function () {
showLevelSelect();
};
menuContainer.addChild(playBtn);
// Shop button
var shopBtn = new Text2("Shop", {
size: 120,
fill: 0xf1c40f,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 8
});
shopBtn.anchor.set(0.5, 0.5);
shopBtn.x = 2048 / 2;
shopBtn.y = 1200;
shopBtn.interactive = true;
shopBtn.buttonMode = true;
shopBtn.down = function () {
showShop();
};
menuContainer.addChild(shopBtn);
// NEW UPDATES COMING SOON text
var updatesText = new Text2("NEW UPDATES COMING SOON...", {
size: 100,
fill: 0xFFD700,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 7
});
updatesText.anchor.set(0.5, 0.5);
updatesText.x = 2048 / 2;
updatesText.y = 1400;
menuContainer.addChild(updatesText);
// THE REAL SECRET button
var realSecretBtn = new Text2("THE REAL SECRET", {
size: 120,
fill: 0x8B0000,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 8
});
realSecretBtn.anchor.set(0.5, 0.5);
realSecretBtn.x = 2048 / 2;
realSecretBtn.y = 1550;
realSecretBtn.interactive = true;
realSecretBtn.buttonMode = true;
realSecretBtn.down = function () {
// Check if player has 200 coins to access THE REAL SECRET
var coins = storage.coins || 0;
if (coins >= 200) {
// Start THE REAL SECRET level (index 6)
selectedLevel = 6;
showLevelIntro();
} else {
// Show insufficient coins message
var insufficientText = new Text2("Need 200 coins to access THE REAL SECRET!\nYou have: " + coins, {
size: 100,
fill: 0xff0000,
align: "center",
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 7
});
insufficientText.anchor.set(0.5, 0.5);
insufficientText.x = 2048 / 2;
insufficientText.y = 1800;
menuContainer.addChild(insufficientText);
// Remove message after 3 seconds
LK.setTimeout(function () {
if (insufficientText.parent) {
insufficientText.parent.removeChild(insufficientText);
}
}, 3000);
}
};
menuContainer.addChild(realSecretBtn);
// Add to game
game.addChild(menuContainer);
// Remove 'Back to Main Menu' button if present
if (game.goBackBtn && game.goBackBtn.parent) {
game.goBackBtn.parent.removeChild(game.goBackBtn);
game.goBackBtn.visible = false;
}
// Hide score text on main menu
if (scoreTxt) {
scoreTxt.visible = false;
// Always ensure scoreTxt is in GUI
if (!scoreTxt.parent) {
LK.gui.top.addChild(scoreTxt);
}
}
// Always ensure goBackBtn is in GUI and hidden
if (game.goBackBtn && !game.goBackBtn.parent) {
LK.gui.topRight.addChild(game.goBackBtn);
}
game.goBackBtn.visible = false;
}
// --- SHOP ---
var shopContainer = new Container();
var SHOP_ITEMS = [{
id: "skin_red",
name: "Red Skin",
price: 30,
color: 0xe74c3c
}, {
id: "skin_green",
name: "Green Skin",
price: 30,
color: 0x2ecc71
}, {
id: "skin_gold",
name: "Gold Skin",
price: 100,
color: 0xf1c40f
}];
function showShop() {
clearMenuUI();
menuState = MENU_STATE.MAIN;
shopContainer.removeChildren();
// Title
var shopTitle = new Text2("Shop", {
size: 150,
fill: 0xFFFFFF,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 10
});
shopTitle.anchor.set(0.5, 0);
shopTitle.x = 2048 / 2;
shopTitle.y = 300;
shopContainer.addChild(shopTitle);
// Coin display
var coins = storage.coins || 0;
var coinTxt = new Text2("Coins: " + coins, {
size: 100,
fill: 0xf1c40f,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 7
});
coinTxt.anchor.set(0.5, 0);
coinTxt.x = 2048 / 2;
coinTxt.y = 500;
shopContainer.addChild(coinTxt);
// List items
for (var i = 0; i < SHOP_ITEMS.length; i++) {
(function (idx) {
var item = SHOP_ITEMS[idx];
var owned = storage["owned_" + item.id] === true;
var y = 700 + idx * 220;
var itemTxt = new Text2(item.name, {
size: 100,
fill: "#" + item.color.toString(16).padStart(6, "0"),
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 7
});
itemTxt.anchor.set(0, 0.5);
itemTxt.x = 400;
itemTxt.y = y;
shopContainer.addChild(itemTxt);
var priceTxt = new Text2(owned ? "Owned" : item.price + " coins", {
size: 90,
fill: owned ? 0x2ecc71 : 0xffffff,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 6
});
priceTxt.anchor.set(0.5, 0.5);
priceTxt.x = 1200;
priceTxt.y = y;
shopContainer.addChild(priceTxt);
// Buy/select button
var btnTxt = owned ? storage.selectedSkin === item.id ? "Selected" : "Select" : "Buy";
var btn = new Text2(btnTxt, {
size: 90,
fill: owned ? storage.selectedSkin === item.id ? 0x2ecc71 : 0x3498db : 0xf1c40f,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 6
});
btn.anchor.set(0.5, 0.5);
btn.x = 1700;
btn.y = y;
btn.interactive = true;
btn.buttonMode = true;
btn.down = function () {
if (owned) {
// Select skin
storage.selectedSkin = item.id;
// If player exists, update its color immediately
if (player && player.childAt && typeof player.childAt === "function") {
// Remove old sprite
var oldSprite = player.childAt(0);
if (oldSprite) {
player.removeChild(oldSprite);
}
var newSprite;
if (item.id === "skin_red") {
newSprite = player.attachAsset('Red', {
anchorX: 0.5,
anchorY: 1
});
} else if (item.id === "skin_green") {
newSprite = player.attachAsset('Green', {
anchorX: 0.5,
anchorY: 1
});
} else if (item.id === "skin_gold") {
newSprite = player.attachAsset('Gold', {
anchorX: 0.5,
anchorY: 1
});
} else {
newSprite = player.attachAsset('player', {
anchorX: 0.5,
anchorY: 1
});
var skinColors = {
"skin_red": 0xe74c3c,
"skin_green": 0x2ecc71,
"skin_gold": 0xf1c40f
};
if (skinColors[item.id] && item.id !== "skin_red" && item.id !== "skin_green" && item.id !== "skin_gold") {
newSprite.color = skinColors[item.id];
}
}
}
showShop();
} else {
// Try to buy
var coins = storage.coins || 0;
if (coins >= item.price) {
storage.coins = coins - item.price;
storage["owned_" + item.id] = true;
storage.selectedSkin = item.id;
showShop();
} else {
priceTxt.setText("Not enough!");
priceTxt.fill = 0xe74c3c;
}
}
};
shopContainer.addChild(btn);
})(i);
}
// Back button
var backBtn = new Text2("Back", {
size: 90,
fill: 0x888888,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 6
});
backBtn.anchor.set(0.5, 0.5);
backBtn.x = 2048 / 2;
backBtn.y = 700 + SHOP_ITEMS.length * 220 + 100;
backBtn.interactive = true;
backBtn.buttonMode = true;
backBtn.down = function () {
// Remove shop UI before showing main menu
if (shopContainer.parent) {
shopContainer.parent.removeChild(shopContainer);
}
showMainMenu();
};
shopContainer.addChild(backBtn);
game.addChild(shopContainer);
// Always ensure scoreTxt is in GUI and hidden
if (scoreTxt && !scoreTxt.parent) {
LK.gui.top.addChild(scoreTxt);
}
scoreTxt.visible = false;
// Always ensure goBackBtn is in GUI and hidden
if (game.goBackBtn && !game.goBackBtn.parent) {
LK.gui.topRight.addChild(game.goBackBtn);
}
game.goBackBtn.visible = false;
}
// --- LEVEL SELECT MENU ---
function showLevelSelect() {
clearMenuUI();
menuState = MENU_STATE.LEVEL_SELECT;
levelSelectContainer.removeChildren();
// Title
var selTitle = new Text2("Select Level", {
size: 150,
fill: 0xFFFFFF,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 10
});
selTitle.anchor.set(0.5, 0);
selTitle.x = 2048 / 2;
selTitle.y = 300;
levelSelectContainer.addChild(selTitle);
// Level buttons
for (var i = 0; i < LEVELS.length; i++) {
(function (idx) {
var lvl = LEVELS[idx];
var btn = new Text2(lvl.name + " (" + lvl.difficulty + ")", {
size: 110,
fill: "#" + lvl.color.toString(16).padStart(6, "0"),
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 8
});
btn.anchor.set(0.5, 0.5);
btn.x = 2048 / 2;
btn.y = 600 + idx * 250;
btn.interactive = true;
btn.buttonMode = true;
btn.down = function () {
selectedLevel = idx;
showLevelIntro();
};
levelSelectContainer.addChild(btn);
// Insert "NEW UPDATES COMING SOON..." after Erin’s atventure continuation (index 3)
if (idx === 3) {
var updatesText = new Text2("NEW UPDATES COMING SOON...", {
size: 100,
fill: 0xFFD700,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 7
});
updatesText.anchor.set(0.5, 0.5);
updatesText.x = 2048 / 2;
updatesText.y = btn.y + 180;
levelSelectContainer.addChild(updatesText);
}
// Highlight Secret Demon level in red and with a demon icon if desired
if (idx === 5) {
btn.setText(lvl.name + " (" + lvl.difficulty + " 😈)");
btn.fill = "#ff0000";
}
})(i);
}
// THE REAL SECRET button
var realSecretBtn = new Text2("THE REAL SECRET", {
size: 120,
fill: 0x8B0000,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 8
});
realSecretBtn.anchor.set(0.5, 0.5);
realSecretBtn.x = 2048 / 2;
realSecretBtn.y = 600 + LEVELS.length * 250 + 50;
realSecretBtn.interactive = true;
realSecretBtn.buttonMode = true;
realSecretBtn.down = function () {
// Check if player has 200 coins to access THE REAL SECRET
var coins = storage.coins || 0;
if (coins >= 200) {
// Start THE REAL SECRET level (index 6)
selectedLevel = 6;
showLevelIntro();
} else {
// Show insufficient coins message
var insufficientText = new Text2("Need 200 coins to access THE REAL SECRET!\nYou have: " + coins, {
size: 100,
fill: 0xff0000,
align: "center",
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 7
});
insufficientText.anchor.set(0.5, 0.5);
insufficientText.x = 2048 / 2;
insufficientText.y = 1800;
levelSelectContainer.addChild(insufficientText);
// Remove message after 3 seconds
LK.setTimeout(function () {
if (insufficientText.parent) {
insufficientText.parent.removeChild(insufficientText);
}
}, 3000);
}
};
levelSelectContainer.addChild(realSecretBtn);
// Back button
var backBtn = new Text2("Back", {
size: 90,
fill: 0x888888,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 6
});
backBtn.anchor.set(0.5, 0.5);
backBtn.x = 2048 / 2;
backBtn.y = 600 + LEVELS.length * 250 + 200;
backBtn.interactive = true;
backBtn.buttonMode = true;
backBtn.down = function () {
showMainMenu();
};
levelSelectContainer.addChild(backBtn);
game.addChild(levelSelectContainer);
// Remove 'Back to Main Menu' button if present
if (game.goBackBtn && game.goBackBtn.parent) {
game.goBackBtn.parent.removeChild(game.goBackBtn);
game.goBackBtn.visible = false;
}
// Always ensure scoreTxt is in GUI and hidden
if (scoreTxt && !scoreTxt.parent) {
LK.gui.top.addChild(scoreTxt);
}
scoreTxt.visible = false;
// Always ensure goBackBtn is in GUI and hidden
if (game.goBackBtn && !game.goBackBtn.parent) {
LK.gui.topRight.addChild(game.goBackBtn);
}
game.goBackBtn.visible = false;
}
// --- LEVEL INTRO ---
function showLevelIntro() {
clearMenuUI();
menuState = MENU_STATE.LEVEL_SELECT;
var lvl = LEVELS[selectedLevel];
// Coin rewards per level
var COIN_REWARDS = [20, 50, 100, 150, 100, 200, 300];
var rewardText = lvl.name + "\nDifficulty: " + lvl.difficulty + "\n\n" + lvl.intro + "\n\nTap to Start";
var showClaim = false;
var coinsToClaim = 0;
if (storage.claimed && storage.claimed[selectedLevel] === false && storage.lastCompletedLevel !== undefined && storage.lastCompletedLevel == selectedLevel) {
// Player just beat this level and hasn't claimed coins yet
showClaim = true;
coinsToClaim = COIN_REWARDS[selectedLevel];
rewardText += "\n\n🎉 Level Complete! 🎉\nClaim +" + coinsToClaim + " coins!";
}
introText = new Text2(rewardText, {
size: 100,
fill: "#" + lvl.color.toString(16).padStart(6, "0"),
align: "center",
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 7
});
introText.anchor.set(0.5, 0.5);
introText.x = 2048 / 2;
introText.y = 1200;
introText.interactive = true;
introText.buttonMode = true;
introText.down = function () {
if (showClaim) {
return;
} // Don't start level if claim is available
startLevel(selectedLevel);
};
game.addChild(introText);
// Show claim button if eligible
if (showClaim) {
var claimBtn = new Text2("Claim +" + coinsToClaim + " coins", {
size: 110,
fill: 0xFFD700,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 8
});
claimBtn.anchor.set(0.5, 0.5);
claimBtn.x = 2048 / 2;
claimBtn.y = introText.y + 350;
claimBtn.interactive = true;
claimBtn.buttonMode = true;
claimBtn.down = function () {
// Add coins and mark as claimed
storage.coins = (storage.coins || 0) + coinsToClaim;
if (!storage.claimed) {
storage.claimed = {};
}
storage.claimed[selectedLevel] = true;
// Remove lastCompletedLevel so claim can't be repeated
delete storage.lastCompletedLevel;
// Update UI
claimBtn.setText("Claimed!");
claimBtn.interactive = false;
claimBtn.buttonMode = false;
};
game.addChild(claimBtn);
}
// Always show 'Back to Main Menu' button on every level intro
var backToMenuBtn = new Text2("Back to Main Menu", {
size: 90,
fill: 0xFFD700,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 6
});
// Anchor to center, place further down under "Tap to Start" and Claim button if present
backToMenuBtn.anchor.set(0.5, 0.5);
// If claimBtn is present, place below it, else below introText
var btnY = introText.y + 250;
// Find claimBtn if present
var claimBtnY = null;
var children = game.children ? game.children.slice() : [];
for (var i = 0; i < children.length; i++) {
if (children[i] && children[i].text && typeof children[i].text === "string" && children[i].text.indexOf("Claim") === 0) {
claimBtnY = children[i].y;
break;
}
}
if (claimBtnY !== null) {
btnY = claimBtnY + 250;
}
// Move button a little to the left (was +700, now +550) and even higher (was btnY - 200, then btnY - 350, then btnY - 500, then btnY - 650, then btnY - 800, then btnY - 950, then btnY - 1100, now btnY - 1250)
backToMenuBtn.x = 2048 / 2 + 550;
backToMenuBtn.y = btnY - 1250;
backToMenuBtn.interactive = true;
backToMenuBtn.buttonMode = true;
backToMenuBtn.visible = true;
backToMenuBtn.down = function () {
// Remove all game children (reset game state)
while (game.children && game.children.length > 0) {
var child = game.children[0];
if (child && child.parent) {
child.parent.removeChild(child);
}
}
// Remove score text from GUI if present
if (scoreTxt && scoreTxt.parent) {
scoreTxt.parent.removeChild(scoreTxt);
}
// Remove goBackBtn from GUI if present
if (game.goBackBtn && game.goBackBtn.parent) {
game.goBackBtn.parent.removeChild(game.goBackBtn);
game.goBackBtn.visible = false;
}
// Reset gameplay state variables
isGameOver = false;
menuState = MENU_STATE.MAIN;
// Show main menu
showMainMenu();
};
game.addChild(backToMenuBtn);
// Remove 'Back to Main Menu' button if present (from gameplay)
if (game.goBackBtn && game.goBackBtn.parent) {
game.goBackBtn.parent.removeChild(game.goBackBtn);
game.goBackBtn.visible = false;
}
// Hide score text on level intro
if (scoreTxt) {
scoreTxt.visible = false;
// Always ensure scoreTxt is in GUI
if (!scoreTxt.parent) {
LK.gui.top.addChild(scoreTxt);
}
}
// Always ensure goBackBtn is in GUI and hidden
if (game.goBackBtn && !game.goBackBtn.parent) {
LK.gui.topRight.addChild(game.goBackBtn);
}
game.goBackBtn.visible = false;
}
// --- START LEVEL ---
function startLevel(levelIdx) {
clearMenuUI();
menuState = MENU_STATE.PLAYING;
// Set level params
var lvl = LEVELS[levelIdx];
OBSTACLE_MIN_GAP = lvl.minGap;
OBSTACLE_MAX_GAP = lvl.maxGap;
OBSTACLE_TYPES = lvl.types;
// Set background color to a slightly darker blue for First Steps level
if (levelIdx === 0) {
game.setBackgroundColor(0x2176ae);
// Stop any music before playing Stereo Madness to ensure it starts
LK.stopMusic();
LK.playMusic('stereo_madness');
} else {
game.setBackgroundColor(0x222831);
// Stop Stereo Madness if switching away from First Steps
LK.stopMusic();
}
// Reset game state
resetGame();
// Show score
score = 0;
scoreTxt.setText("0");
scoreTxt.visible = true;
// Show ground and player
if (!ground) {
ground = LK.getAsset('ground', {
anchorX: 0,
anchorY: 1,
x: 0,
y: 2732
});
game.addChild(ground);
} else if (!ground.parent) {
game.addChild(ground);
}
if (!player) {
player = new Player();
player.groundY = 2732 - GROUND_HEIGHT;
if (levelIdx === 4) {
// Dash level: start in center for wave countdown
player.x = 2048 / 2;
player.y = 1366;
} else if (levelIdx === 5) {
// Secret Demon: start in center of screen, in air, always wave mode
player.x = 2048 / 2;
player.y = 1366; // center vertically
player.vy = 0;
player.isWave = true;
} else {
player.x = PLAYER_START_X;
player.y = player.groundY;
}
game.addChild(player);
} else if (!player.parent) {
game.addChild(player);
if (levelIdx === 4) {
player.x = 2048 / 2;
player.y = 1366;
} else if (levelIdx === 5) {
// Secret Demon: start in center, in air, always wave mode
player.x = 2048 / 2;
player.y = 700;
player.vy = 0;
player.isWave = true;
} else if (levelIdx === 6) {
// THE REAL SECRET: start in center, in air, always wave mode, even more extreme
player.x = 2048 / 2;
player.y = 1366;
player.vy = 0;
player.isWave = true;
} else {
player.x = PLAYER_START_X;
player.y = player.groundY;
}
}
// --- Dash level: prepare for 5s countdown at 20 points (do not show countdown yet) ---
if (levelIdx === 4) {
// Set player to center for Dash level start, do not enable wave mode yet
player.x = 2048 / 2;
player.y = 1366;
player.vy = 0;
player.isWave = false;
// Remove all obstacles for clean start
for (var i = 0; i < obstacles.length; i++) {
obstacles[i].destroy();
}
obstacles = [];
lastObstacleX = 1200 + 700;
// --- Dash level: 5s countdown at start ---
game._dashCountdownActive = true;
var dashCountdown = 5;
var dashCountdownText = new Text2(dashCountdown + "", {
size: 220,
fill: 0xFFD700,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 12
});
dashCountdownText.anchor.set(0.5, 0.5);
dashCountdownText.x = 2048 / 2;
dashCountdownText.y = 900;
game.addChild(dashCountdownText);
var dashCountdownTimer = LK.setInterval(function () {
dashCountdown--;
if (dashCountdown > 0) {
dashCountdownText.setText(dashCountdown + "");
} else {
LK.clearInterval(dashCountdownTimer);
if (dashCountdownText.parent) dashCountdownText.parent.removeChild(dashCountdownText);
game._dashCountdownActive = false;
// After countdown, enter wave mode
waveMode = true;
// Remove all ground obstacles
for (var i = 0; i < obstacles.length; i++) {
obstacles[i].destroy();
}
obstacles = [];
// Move player to center Y for wave mode (start in mid air at y=700)
player.y = 700;
player.vy = 0;
// Change player sprite to wave asset
if (player.childAt && typeof player.childAt === "function") {
var oldSprite = player.childAt(0);
if (oldSprite) {
player.removeChild(oldSprite);
}
// Attach wave asset, anchor center
var waveSprite = player.attachAsset('Wave', {
anchorX: 0.5,
anchorY: 0.5
});
}
// Enable wave mode controls
player.isWave = true;
// Set player.groundY for after wave mode
if (typeof player.groundY !== "number") {
player.groundY = 2732 - GROUND_HEIGHT;
}
}
}, 1000);
// Remove all obstacles and prevent spawning until countdown ends
for (var i = 0; i < obstacles.length; i++) {
obstacles[i].destroy();
}
obstacles = [];
lastObstacleX = 1200 + 700;
// Set player to wave mode start position, but do not enable wave mode yet
player.x = 2048 / 2;
player.y = 1366;
player.vy = 0;
player.isWave = false;
// Block update and input until countdown ends
}
// --- Secret Demon: always wave mode, no countdown, clear obstacles, set up wave obstacles ---
if (levelIdx === 5) {
// Remove all obstacles for clean start
for (var i = 0; i < obstacles.length; i++) {
obstacles[i].destroy();
}
obstacles = [];
lastObstacleX = 1200 + 700;
// Remove all wave obstacles
if (typeof waveObstacles !== "undefined") {
for (var i = 0; i < waveObstacles.length; i++) {
waveObstacles[i].destroy && waveObstacles[i].destroy();
}
waveObstacles = [];
}
// Set player to wave mode, attach wave sprite
if (player.childAt && typeof player.childAt === "function") {
var oldSprite = player.childAt(0);
if (oldSprite) {
player.removeChild(oldSprite);
}
// Attach wave asset, anchor center
var waveSprite = player.attachAsset('Wave', {
anchorX: 0.5,
anchorY: 0.5
});
}
player.isWave = true;
// --- Add static ground spikes for Secret Demon level ---
// Place even fewer spikes, more space between them, leaving a small gap at left/right
var spikeW = 100;
var spikeH = 150;
var groundY = 2732 - GROUND_HEIGHT + 2; // +2 to ensure overlap
var startX = 60; // leave a gap at left
var endX = 2048 - 60;
// Increase spacing to every 4 spike widths (fewer spikes)
for (var sx = startX; sx < endX; sx += spikeW * 4) {
var groundSpike = new Spike();
groundSpike.x = sx + spikeW / 2;
groundSpike.y = groundY;
groundSpike.speed = 0; // static
// Mark as ground spike for collision logic if needed
groundSpike.isGroundSpike = true;
obstacles.push(groundSpike);
game.addChild(groundSpike);
}
}
// --- THE REAL SECRET Level: always wave mode, more extreme than Secret Demon ---
if (levelIdx === 6) {
// Remove all obstacles for clean start
for (var i = 0; i < obstacles.length; i++) {
obstacles[i].destroy();
}
obstacles = [];
lastObstacleX = 1200 + 700;
// Remove all wave obstacles
if (typeof waveObstacles !== "undefined") {
for (var i = 0; i < waveObstacles.length; i++) {
waveObstacles[i].destroy && waveObstacles[i].destroy();
}
waveObstacles = [];
}
// Set player to wave mode, attach wave sprite
if (player.childAt && typeof player.childAt === "function") {
var oldSprite = player.childAt(0);
if (oldSprite) {
player.removeChild(oldSprite);
}
// Attach wave asset, anchor center
var waveSprite = player.attachAsset('Wave', {
anchorX: 0.5,
anchorY: 0.5
});
}
player.isWave = true;
// Add even fewer static ground spikes for THE REAL SECRET level
var spikeW = 100;
var spikeH = 150;
var groundY = 2732 - GROUND_HEIGHT + 2;
var startX = 80; // more gap at left
var endX = 2048 - 80;
// Much more spacing - every 5 spike widths (extremely few spikes)
for (var sx = startX; sx < endX; sx += spikeW * 5) {
var groundSpike = new Spike();
groundSpike.x = sx + spikeW / 2;
groundSpike.y = groundY;
groundSpike.speed = 0; // static
groundSpike.isGroundSpike = true;
obstacles.push(groundSpike);
game.addChild(groundSpike);
}
}
// Always ensure scoreTxt is in GUI and visible
if (scoreTxt && !scoreTxt.parent) {
LK.gui.top.addChild(scoreTxt);
}
scoreTxt.visible = true;
// Always ensure goBackBtn is in GUI and visible at gameplay start
if (game.goBackBtn && !game.goBackBtn.parent) {
LK.gui.topRight.addChild(game.goBackBtn);
}
game.goBackBtn.visible = true;
// Initial obstacle
lastObstacleX = 1200 + 700; //{2I} // Add extra distance for a longer start
spawnObstacle();
// Remove 'Back to Main Menu' button if present (should only show after level complete)
if (game.goBackBtn && game.goBackBtn.parent) {
game.goBackBtn.parent.removeChild(game.goBackBtn);
game.goBackBtn.visible = false;
}
// Do not show 'Back to Main Menu' button in gameplay
game.goBackBtn.visible = false;
}
// --- SCORE TEXT ---
scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 8
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
scoreTxt.visible = true;
// --- GO BACK TO MAIN MENU BUTTON (persistent for gameplay) ---
game.goBackBtn = new Text2("Back to Main Menu", {
size: 90,
fill: 0xFFD700,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 6
});
game.goBackBtn.anchor.set(1, 0);
game.goBackBtn.x = 2048 - 40;
game.goBackBtn.y = 380;
game.goBackBtn.interactive = true;
game.goBackBtn.buttonMode = true;
game.goBackBtn.visible = false;
game.goBackBtn.down = function () {
// Remove gameplay UI and go to main menu
if (scoreTxt) {
scoreTxt.visible = false;
}
if (game.goBackBtn && game.goBackBtn.parent) {
game.goBackBtn.parent.removeChild(game.goBackBtn);
}
game.goBackBtn.visible = false;
showMainMenu();
};
LK.gui.topRight.addChild(game.goBackBtn);
// --- NEW UPDATES COMING SOON... ---
var sampleText = new Text2("NEW UPDATES COMING SOON...", {
size: 100,
fill: 0xFFD700,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 7
});
sampleText.anchor.set(0.5, 0.5);
sampleText.x = 2048 / 2;
sampleText.y = 2732 / 2;
sampleText.interactive = true;
sampleText.buttonMode = true;
sampleText.down = function () {
// Secret action: show a message or perform a hidden action
LK.showMessage && LK.showMessage("You found the secret button! 🎉");
};
game.addChild(sampleText);
// --- INITIALIZE: show main menu ---
showMainMenu();
// Helper: spawn obstacle
function spawnObstacle() {
// Randomly pick type
var type = OBSTACLE_TYPES[Math.floor(Math.random() * OBSTACLE_TYPES.length)];
var obs;
if (type === 'spike') {
obs = new Spike();
obs.x = 2048 + 100;
obs.y = OBSTACLE_Y;
} else {
obs = new Obstacle();
obs.x = 2048 + 100;
obs.y = OBSTACLE_Y;
}
obstacles.push(obs);
game.addChild(obs);
lastObstacleX = obs.x;
}
// Helper: reset game state
function resetGame() {
// Remove obstacles
for (var i = 0; i < obstacles.length; i++) {
obstacles[i].destroy();
}
obstacles = [];
// Remove wave obstacles
if (typeof waveObstacles !== "undefined") {
for (var i = 0; i < waveObstacles.length; i++) {
waveObstacles[i].destroy && waveObstacles[i].destroy();
}
waveObstacles = [];
}
// Reset wave mode
waveMode = false;
// Reset player
if (player) {
player.x = PLAYER_START_X;
player.y = player.groundY;
player.vy = 0;
player.isJumping = false;
player.visible = true; // Make player visible again after explosion
player.isWave = false; // Always reset wave mode state
// Always restore player sprite to correct asset in Dash level (or after wave mode)
if (player.childAt && typeof player.childAt === "function") {
var oldSprite = player.childAt(0);
if (selectedLevel === 4) {
if (oldSprite && oldSprite.assetId === "Wave") {
player.removeChild(oldSprite);
oldSprite = null;
}
}
if (!oldSprite || selectedLevel === 4 && oldSprite.assetId === "Wave") {
var newSprite;
if (storage.selectedSkin === "skin_red") {
newSprite = player.attachAsset('Red', {
anchorX: 0.5,
anchorY: 1
});
} else if (storage.selectedSkin === "skin_green") {
newSprite = player.attachAsset('Green', {
anchorX: 0.5,
anchorY: 1
});
} else if (storage.selectedSkin === "skin_gold") {
newSprite = player.attachAsset('Gold', {
anchorX: 0.5,
anchorY: 1
});
} else {
newSprite = player.attachAsset('player', {
anchorX: 0.5,
anchorY: 1
});
var skinColors = {
"skin_red": 0xe74c3c,
"skin_green": 0x2ecc71,
"skin_gold": 0xf1c40f
};
if (storage.selectedSkin && skinColors[storage.selectedSkin] && storage.selectedSkin !== "skin_red" && storage.selectedSkin !== "skin_green" && storage.selectedSkin !== "skin_gold") {
newSprite.color = skinColors[storage.selectedSkin];
}
}
}
}
}
// Reset score
score = 0;
if (scoreTxt) {
scoreTxt.setText(score);
scoreTxt.visible = true;
if (!scoreTxt.parent) {
LK.gui.top.addChild(scoreTxt);
}
}
// Always ensure goBackBtn is in GUI and visible
if (game.goBackBtn && !game.goBackBtn.parent) {
LK.gui.topRight.addChild(game.goBackBtn);
}
if (game.goBackBtn) {
game.goBackBtn.visible = true;
}
// Always ensure scoreTxt is in GUI and visible
if (scoreTxt && !scoreTxt.parent) {
LK.gui.top.addChild(scoreTxt);
}
if (scoreTxt) {
scoreTxt.visible = true;
}
lastObstacleX = 1200;
isGameOver = false;
}
// Touch/click to jump
game.down = function (x, y, obj) {
if (menuState !== MENU_STATE.PLAYING) {
return;
}
if (isGameOver) {
return;
}
// Block input during Dash level countdown
if (selectedLevel === 4 && game._dashCountdownActive) {
return;
}
if (player.isWave) {
// In wave mode, pressing makes you go up
player.vy = -38;
// Optionally, rotate the wave asset for a little feedback
var waveSprite = player.childAt && player.childAt(0);
if (waveSprite) {
tween(waveSprite, {
rotation: waveSprite.rotation + Math.PI * 2
}, {
duration: 400,
easing: tween.linear
});
}
return;
} else {
player.jump();
}
};
// Main update loop
game.update = function () {
if (menuState !== MENU_STATE.PLAYING) {
return;
}
if (isGameOver) {
return;
}
// --- Block update, obstacle spawn, and wave mode entry during Dash level 5s countdown ---
if (selectedLevel === 4 && game._dashCountdownActive) {
// Only update player position (so countdown looks correct), skip all obstacle logic and wave mode
if (player && typeof player.update === "function") player.update();
return;
}
// --- WAVE MODE TRIGGER (Dash level only) ---
// Dash level is index 4, Secret Demon is index 5
if (selectedLevel === 4) {
// --- 5s countdown at 20 points ---
if (!game._dashCountdownActive && !waveMode && score >= 20) {
// Start 5s countdown, block input and update
game._dashCountdownActive = true;
var countdown = 5;
var countdownText = new Text2(countdown + "", {
size: 220,
fill: 0xFFD700,
fontWeight: "bold",
stroke: 0x000000,
strokeThickness: 12
});
countdownText.anchor.set(0.5, 0.5);
countdownText.x = 2048 / 2;
countdownText.y = 900;
game.addChild(countdownText);
var countdownTimer = LK.setInterval(function () {
countdown--;
if (countdown > 0) {
countdownText.setText(countdown + "");
} else {
LK.clearInterval(countdownTimer);
if (countdownText.parent) countdownText.parent.removeChild(countdownText);
game._dashCountdownActive = false;
// After countdown, enter wave mode
waveMode = true;
// Remove all ground obstacles
for (var i = 0; i < obstacles.length; i++) {
obstacles[i].destroy();
}
obstacles = [];
// Move player to center Y for wave mode (start in mid air at y=700)
player.y = 700;
player.vy = 0;
// Change player sprite to wave asset
if (player.childAt && typeof player.childAt === "function") {
var oldSprite = player.childAt(0);
if (oldSprite) {
player.removeChild(oldSprite);
}
// Attach wave asset, anchor center
var waveSprite = player.attachAsset('Wave', {
anchorX: 0.5,
anchorY: 0.5
});
}
// Enable wave mode controls
player.isWave = true;
// Set player.groundY for after wave mode
if (typeof player.groundY !== "number") {
player.groundY = 2732 - GROUND_HEIGHT;
}
}
}, 1000);
// Remove all obstacles and prevent spawning until countdown ends
for (var i = 0; i < obstacles.length; i++) {
obstacles[i].destroy();
}
obstacles = [];
lastObstacleX = 1200 + 700;
// Set player to wave mode start position, but do not enable wave mode yet
player.x = 2048 / 2;
player.y = 1366;
player.vy = 0;
player.isWave = false;
// Block update and input until countdown ends
return;
}
// Block update and input during countdown
if (game._dashCountdownActive) {
if (player && typeof player.update === "function") player.update();
return;
}
// End wave mode at 75 points
if (waveMode && score >= 75) {
waveMode = false;
// Remove all wave obstacles
for (var i = 0; i < waveObstacles.length; i++) {
waveObstacles[i].destroy && waveObstacles[i].destroy();
}
waveObstacles = [];
// Reset player to ground
player.y = player.groundY;
player.vy = 0;
// Restore player sprite to selected skin
if (player.childAt && typeof player.childAt === "function") {
var oldSprite = player.childAt(0);
if (oldSprite) {
player.removeChild(oldSprite);
}
var newSprite;
if (storage.selectedSkin === "skin_red") {
newSprite = player.attachAsset('Red', {
anchorX: 0.5,
anchorY: 1
});
} else if (storage.selectedSkin === "skin_green") {
newSprite = player.attachAsset('Green', {
anchorX: 0.5,
anchorY: 1
});
} else if (storage.selectedSkin === "skin_gold") {
newSprite = player.attachAsset('Gold', {
anchorX: 0.5,
anchorY: 1
});
} else {
newSprite = player.attachAsset('player', {
anchorX: 0.5,
anchorY: 1
});
var skinColors = {
"skin_red": 0xe74c3c,
"skin_green": 0x2ecc71,
"skin_gold": 0xf1c40f
};
if (storage.selectedSkin && skinColors[storage.selectedSkin] && storage.selectedSkin !== "skin_red" && storage.selectedSkin !== "skin_green" && storage.selectedSkin !== "skin_gold") {
newSprite.color = skinColors[storage.selectedSkin];
}
}
}
// Disable wave mode controls
player.isWave = false;
}
}
// --- Secret Demon Level: always wave mode, no countdown, more/faster obstacles, never disables wave mode ---
if (selectedLevel === 5) {
waveMode = true;
player.isWave = true;
// Always keep player in wave mode, never disable
// Remove all ground obstacles (should be empty, but just in case)
for (var i = 0; i < obstacles.length; i++) {
obstacles[i].destroy();
}
obstacles = [];
// Optionally, increase flying obstacle spawn rate and speed
// (handled below in obstacle spawn section)
}
// --- THE REAL SECRET Level: always wave mode, even more extreme than Secret Demon ---
if (selectedLevel === 6) {
waveMode = true;
player.isWave = true;
// Always keep player in wave mode, never disable
// Remove all ground obstacles
for (var i = 0; i < obstacles.length; i++) {
obstacles[i].destroy();
}
obstacles = [];
}
// Update player
player.update();
// --- NORMAL OBSTACLES ---
if (!waveMode) {
// Update obstacles
for (var i = obstacles.length - 1; i >= 0; i--) {
var obs = obstacles[i];
obs.update();
// Remove if off screen
if (obs.x < -200) {
obs.destroy();
obstacles.splice(i, 1);
continue;
}
// Collision detection
if (player.intersects(obs)) {
// Explode player into small pieces
if (player && player.parent) {
var numPieces = 12;
var centerX = player.x;
var centerY = player.y;
var pieceSize = 32;
for (var p = 0; p < numPieces; p++) {
(function (pieceIdx) {
var angle = Math.PI * 2 / numPieces * pieceIdx;
var px = centerX;
var py = centerY - PLAYER_SIZE / 2 + pieceSize / 2;
var piece = new Container();
var pieceSprite = piece.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
width: pieceSize,
height: pieceSize
});
// Use same color as player
var playerSpriteRef = player && player.childAt ? player.childAt(0) : null;
if (playerSpriteRef && playerSpriteRef.color) {
pieceSprite.color = playerSpriteRef.color;
}
piece.x = px;
piece.y = py;
game.addChild(piece);
// Animate outward with random velocity and fade out
var speed = 32 + Math.random() * 32;
var dx = Math.cos(angle) * speed;
var dy = Math.sin(angle) * speed - 10 + Math.random() * 20;
tween(piece, {
x: px + dx * 16,
y: py + dy * 16,
alpha: 0
}, {
duration: 700 + Math.random() * 200,
easing: tween.easeOutCubic,
onComplete: function onComplete() {
piece.destroy();
}
});
})(p);
}
// Hide player sprite immediately
player.visible = false;
}
isGameOver = true;
// Delay restart until after explosion animation
LK.setTimeout(function () {
startLevel(selectedLevel);
}, 800);
return;
}
}
}
// --- WAVE MODE OBSTACLES ---
if (waveMode) {
// Kill player if they touch the ground in Dash wave mode (ground is death)
if (selectedLevel === 4 && player.y >= 2732 - GROUND_HEIGHT) {
// Explode player into small pieces
if (player && player.parent) {
var numPieces = 12;
var centerX = player.x;
var centerY = player.y;
var pieceSize = 32;
for (var p = 0; p < numPieces; p++) {
(function (pieceIdx) {
var angle = Math.PI * 2 / numPieces * pieceIdx;
var px = centerX;
var py = centerY - PLAYER_SIZE / 2 + pieceSize / 2;
var piece = new Container();
var pieceSprite = piece.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
width: pieceSize,
height: pieceSize
});
var playerSpriteRef = player && player.childAt ? player.childAt(0) : null;
if (playerSpriteRef && playerSpriteRef.color) {
pieceSprite.color = playerSpriteRef.color;
}
piece.x = px;
piece.y = py;
game.addChild(piece);
var speed = 32 + Math.random() * 32;
var dx = Math.cos(angle) * speed;
var dy = Math.sin(angle) * speed - 10 + Math.random() * 20;
tween(piece, {
x: px + dx * 16,
y: py + dy * 16,
alpha: 0
}, {
duration: 700 + Math.random() * 200,
easing: tween.easeOutCubic,
onComplete: function onComplete() {
piece.destroy();
}
});
})(p);
}
player.visible = false;
}
isGameOver = true;
LK.setTimeout(function () {
startLevel(selectedLevel);
}, 800);
return;
}
// Update flying obstacles
for (var i = waveObstacles.length - 1; i >= 0; i--) {
var wobs = waveObstacles[i];
wobs.update && wobs.update();
// Remove if off screen
if (wobs.x < -200) {
wobs.destroy && wobs.destroy();
waveObstacles.splice(i, 1);
continue;
}
// Collision detection
if (player.intersects(wobs)) {
// Explode player into small pieces
if (player && player.parent) {
var numPieces = 12;
var centerX = player.x;
var centerY = player.y;
var pieceSize = 32;
for (var p = 0; p < numPieces; p++) {
(function (pieceIdx) {
var angle = Math.PI * 2 / numPieces * pieceIdx;
var px = centerX;
var py = centerY - PLAYER_SIZE / 2 + pieceSize / 2;
var piece = new Container();
var pieceSprite = piece.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
width: pieceSize,
height: pieceSize
});
var playerSpriteRef = player && player.childAt ? player.childAt(0) : null;
if (playerSpriteRef && playerSpriteRef.color) {
pieceSprite.color = playerSpriteRef.color;
}
piece.x = px;
piece.y = py;
game.addChild(piece);
var speed = 32 + Math.random() * 32;
var dx = Math.cos(angle) * speed;
var dy = Math.sin(angle) * speed - 10 + Math.random() * 20;
tween(piece, {
x: px + dx * 16,
y: py + dy * 16,
alpha: 0
}, {
duration: 700 + Math.random() * 200,
easing: tween.easeOutCubic,
onComplete: function onComplete() {
piece.destroy();
}
});
})(p);
}
player.visible = false;
}
isGameOver = true;
LK.setTimeout(function () {
startLevel(selectedLevel);
}, 800);
return;
}
}
}
// Score: increase as player passes obstacles
if (!waveMode) {
for (var j = 0; j < obstacles.length; j++) {
var o = obstacles[j];
if (!o.passed && o.x + 60 < player.x) {
o.passed = true;
score += 1;
if (scoreTxt) scoreTxt.setText(score);
}
}
} else {
// In wave mode, score increases as player passes flying obstacles
for (var j = 0; j < waveObstacles.length; j++) {
var wo = waveObstacles[j];
if (!wo.passed && wo.x + 60 < player.x) {
wo.passed = true;
score += 1;
if (scoreTxt) scoreTxt.setText(score);
}
}
}
// Win condition: pass 10 obstacles (easy), 15 (medium), 20 (hard), 30 (Erin's atventure continuation), 30 (Dash), 50 (Secret Demon), 100 (THE REAL SECRET)
var winScores = [10, 15, 20, 30, 30, 50, 100];
if (score >= winScores[selectedLevel]) {
isGameOver = true;
// Mark level as completed for coin claim
storage.lastCompletedLevel = selectedLevel;
if (!storage.claimed) {
storage.claimed = {};
}
storage.claimed[selectedLevel] = storage.claimed[selectedLevel] || false;
// Award +150 coins if Erin’s atventure continuation (index 3)
// (Now handled in claim button, not here)
LK.setTimeout(function () {
showLevelIntro();
}, 600);
return;
}
// Spawn new obstacles
if (!waveMode) {
if (obstacles.length === 0 || 2048 - lastObstacleX > OBSTACLE_MIN_GAP + Math.floor(Math.random() * (OBSTACLE_MAX_GAP - OBSTACLE_MIN_GAP))) {
spawnObstacle();
}
} else if (waveMode && selectedLevel === 4 && score < 75) {
// In wave mode, spawn flying spikes/obstacles at random intervals
if (waveObstacles.length === 0 || waveObstacles.length < 4 && Math.random() < 0.04) {
// Randomly choose between flying spike and flying obstacle
var wtype = Math.random() < 0.6 ? 'spike' : 'obstacle';
var wobs;
if (wtype === 'spike') {
wobs = new WaveSpike();
} else {
wobs = new FlyingObstacle();
}
wobs.x = 2048 + 100;
// Random Y in upper/lower half, but not too close to edge
wobs.y = 400 + Math.random() * (2732 - 800);
waveObstacles.push(wobs);
game.addChild(wobs);
}
} else if (waveMode && selectedLevel === 5) {
// Secret Demon: spawn up to 2 flying obstacles, much lower spawn chance, still SLOWER speed
if (waveObstacles.length === 0 || waveObstacles.length < 2 && Math.random() < 0.025) {
var wtype = Math.random() < 0.7 ? 'spike' : 'obstacle';
var wobs;
if (wtype === 'spike') {
wobs = new WaveSpike();
wobs.speed = 18 + Math.random() * 4; // much slower than Dash
} else {
wobs = new FlyingObstacle();
wobs.speed = 18 + Math.random() * 4; // much slower than Dash
}
wobs.x = 2048 + 100;
// Random Y, but allow more vertical spread
wobs.y = 200 + Math.random() * (2732 - 400);
waveObstacles.push(wobs);
game.addChild(wobs);
}
} else if (waveMode && selectedLevel === 6) {
// THE REAL SECRET: spawn up to 3 flying obstacles, higher spawn chance, EXTREME speed
if (waveObstacles.length === 0 || waveObstacles.length < 3 && Math.random() < 0.035) {
var wtype = Math.random() < 0.8 ? 'spike' : 'obstacle';
var wobs;
if (wtype === 'spike') {
wobs = new WaveSpike();
wobs.speed = 35 + Math.random() * 8; // EXTREME speed - faster than Dash
} else {
wobs = new FlyingObstacle();
wobs.speed = 35 + Math.random() * 8; // EXTREME speed - faster than Dash
}
wobs.x = 2048 + 100;
// Random Y with full vertical range for maximum difficulty
wobs.y = 100 + Math.random() * (2732 - 200);
waveObstacles.push(wobs);
game.addChild(wobs);
}
}
};
// (gameover event handler removed, as game over popup is never shown)