User prompt
make the background of those labels opaque and reduce the size by half (just the bg not the label)
User prompt
double the height of the lable don't touch the width
User prompt
no keep the height the same but make them half as wide
User prompt
I mean reduce the size by 200%
User prompt
squeeze the label from sides 200%
User prompt
reduce the size of the platform labeland add a black background 0.3 alpha transparent to make them more visible
User prompt
It still shows constantly 0. review the code carefully and come up with a scoring logic number of floors reached was my idea but you fail to show it
User prompt
now it constantly shows 0. Is it that hard to show the number on the platform label whatever platform player has reached?
User prompt
now it constantly shows 1
User prompt
player is jumping on platform 50 but score shows 28 as it jumped from 28 platforms to reach the 50th. The score should be 50 in this case please fix the bug
User prompt
score logic does not work properly please review it carefully and solve the problem
User prompt
no need for landing you can show the maximum platform index number label's text that the player has passed from the level of that platform
User prompt
fix score calculation
User prompt
count the ones it didn't land on as well, the ones that are skipped
User prompt
use whatever but show the score properly as the number of platforms player passes so far, current logic fails to provide the correct number
User prompt
use platform count index to caluclate the score
User prompt
each theme should have 50 platforms and not more
User prompt
themes should change after every 50 platforms are passed please fix this
User prompt
perfectly synchronize logic that triggers the theme change with the platform index counter
User prompt
latest platform number the player has passed should be shown for Score
User prompt
not the platformsPassed number, Score should show platform index counter. So if the player is jumping on the 15th platform it should show 15
User prompt
use platform index counter to calculate the score, if player reached that platform s/he should see platform index counter label in the score up in the UI as well
User prompt
perfectly synchronize them then
User prompt
In every 50 platform the theme should change, please use platform index counter for that purpose
User prompt
it again fails to show correct values for the ones that are not initially visible
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Cloud class: background only, moves with camera, not collidable
var Cloud = Container.expand(function () {
var self = Container.call(this);
// Pick a random cloud asset
var cloudAssets = ['cloudBlue', 'cloudBright', 'cloudFluffy', 'cloudGray', 'cloudSoft'];
var assetId = cloudAssets[Math.floor(Math.random() * cloudAssets.length)];
var assetInfo = LK.getAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Scale clouds to a reasonable size for background
var targetHeight = 180 + Math.random() * 80; // 180-260px
var scale = targetHeight / assetInfo.height;
var cloudGfx = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: scale,
scaleY: scale,
alpha: 0.45 + Math.random() * 0.15 // more transparent clouds
});
// Set initial position (x, y) and speed
self.x = Math.random() * 2048;
self.y = Math.random() * 2048;
self.speedY = 0; // Will be set by camera movement
// Give some clouds a gentle horizontal drift
self.driftX = (Math.random() < 0.5 ? -1 : 1) * (0.2 + Math.random() * 0.5);
// Clouds are always behind everything
self.setToBack = function () {
if (self.parent && self.parent.children.indexOf(self) > 0) {
self.parent.setChildIndex(self, 0);
}
};
// No collision, no input
self.update = function () {
// Track lastX for border collision detection
if (typeof self.lastX === "undefined") self.lastX = self.x;
// Horizontal drift
self.x += self.driftX;
// Prevent clouds from reaching within 1/30 of the screen X from any edge
var minCloudX = BORDER_LEFT_X;
var maxCloudX = BORDER_RIGHT_X;
// Bounce off left transparent border (minCloudX)
if (self.lastX > minCloudX && self.x <= minCloudX) {
self.x = minCloudX;
self.driftX = Math.abs(self.driftX); // move right
}
// If cloud is ever inside the left transparent border, force it back and reverse
if (self.x < minCloudX) {
self.x = minCloudX;
self.driftX = Math.abs(self.driftX); // move right
}
// Bounce off right transparent border (maxCloudX)
if (self.lastX < maxCloudX && self.x >= maxCloudX) {
self.x = maxCloudX;
self.driftX = -Math.abs(self.driftX); // move left
}
// If cloud is ever inside the right transparent border, force it back and reverse
if (self.x > maxCloudX) {
self.x = maxCloudX;
self.driftX = -Math.abs(self.driftX); // move left
}
// Remove wrap horizontally (clouds never go past borders)
// Update lastX for next frame
self.lastX = self.x;
// Vertical movement is handled by camera diff in game.update
};
return self;
});
// --- Coin class: collectible, always same visual size, shows value popup ---
var Coin = Container.expand(function () {
var self = Container.call(this);
// Coin type: 1, 2, or 3 (for +1, +3, +5)
self.coinType = 1;
self.value = 1;
self.assetId = 'chipCoin1';
// Set asset and value based on type
if (typeof arguments[0] === "number") {
if (arguments[0] === 1) {
self.coinType = 1;
self.value = 1;
self.assetId = 'chipCoin1';
} else if (arguments[0] === 2) {
self.coinType = 2;
self.value = 3;
self.assetId = 'chipCoin2';
} else if (arguments[0] === 3) {
self.coinType = 3;
self.value = 5;
self.assetId = 'chipCoin3';
}
}
// Always render coins at the same visual size (height 80px)
var targetHeight = 80;
var assetInfo = LK.getAsset(self.assetId, {
anchorX: 0.5,
anchorY: 0.5
});
var scale = targetHeight / assetInfo.height;
var coinGfx = self.attachAsset(self.assetId, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: scale,
scaleY: scale
});
// For collision
self.radius = assetInfo.width * scale / 2;
// Show value popup when collected
self.showValuePopup = function () {
var txt = new Text2('+' + self.value, {
size: 60,
fill: self.value === 1 ? "#ffe066" : self.value === 3 ? "#a78bfa" : "#38bdf8",
font: "Impact"
});
txt.anchor.set(0.5, 0.5);
txt.x = self.x;
txt.y = self.y - 40;
game.addChild(txt);
// Ensure popup is above player
if (txt.parent && player && txt.parent.children.indexOf(txt) < txt.parent.children.indexOf(player)) {
txt.parent.setChildIndex(txt, txt.parent.children.length - 1);
}
tween(txt, {
y: txt.y - 80,
alpha: 0
}, {
duration: 700,
onFinish: function onFinish() {
txt.destroy();
}
});
};
return self;
});
// --- PlatformSign class: shows a sign with platforms passed on the last platform of each theme ---
var PlatformSign = Container.expand(function () {
var self = Container.call(this);
// Use a simple box as the sign background
var signW = 220,
signH = 110;
var signBg = self.attachAsset('chipPlatform0', {
anchorX: 0.5,
anchorY: 1,
scaleX: signW / 820,
scaleY: signH / 110,
color: 0x22223b
});
// Text label for platforms passed
var signTxt = new Text2('0', {
size: 60,
fill: "#fff",
font: "Impact"
});
signTxt.anchor.set(0.5, 0.5);
signTxt.x = 0;
signTxt.y = -signH / 2;
self.addChild(signTxt);
// Set the value to display
self.setPlatformsPassed = function (val) {
signTxt.setText(val + "");
};
return self;
});
// PokerChip class: simple collectible poker chip (not draggable, not rotatable)
var PokerChip = Container.expand(function () {
var self = Container.call(this);
// Asset id and color are passed in, default to 'chipCoin1'
self.assetId = self.assetId || 'chipCoin1';
var chipAssetInfo = LK.getAsset(self.assetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Always render poker chips at a consistent visual size (height 80px)
var targetHeight = 80;
var scale = targetHeight / chipAssetInfo.height;
var chip = self.attachAsset(self.assetId, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: scale,
scaleY: scale
});
// For hit testing
self.radius = chipAssetInfo.width * scale / 2;
// Show value popup when collected (optional, can be customized)
self.value = 1;
self.showValuePopup = function () {
var txt = new Text2('+1', {
size: 60,
fill: 0xFFE066,
font: "Impact"
});
txt.anchor.set(0.5, 0.5);
txt.x = self.x;
txt.y = self.y - 40;
game.addChild(txt);
tween(txt, {
y: txt.y - 80,
alpha: 0
}, {
duration: 700,
onFinish: function onFinish() {
txt.destroy();
}
});
};
return self;
});
// --- Star class: collectible, animated, optimized for performance ---
var Star = Container.expand(function () {
var self = Container.call(this);
// Use the new collectibleStar asset
self.assetId = 'collectibleStar';
self.value = 10; // Stars are worth +10
var assetInfo = LK.getAsset(self.assetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Always render stars at the same visual size (height 70px)
var targetHeight = 70;
var scale = targetHeight / assetInfo.height;
var starGfx = self.attachAsset(self.assetId, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: scale,
scaleY: scale,
alpha: 0.92
});
// For collision
self.radius = assetInfo.width * scale / 2;
// Animate star: slow rotation and pulsing
self.update = function () {
if (starGfx) {
starGfx.rotation += 0.09;
var pulse = 0.95 + 0.08 * Math.sin(Date.now() / 180 + self.x);
starGfx.scale.x = scale * pulse;
starGfx.scale.y = scale * pulse;
}
};
// Show value popup when collected
self.showValuePopup = function () {
var txt = new Text2('+10', {
size: 60,
fill: 0xFFF7B2,
font: "Impact"
});
txt.anchor.set(0.5, 0.5);
txt.x = self.x;
txt.y = self.y - 40;
game.addChild(txt);
if (txt.parent && player && txt.parent.children.indexOf(txt) < txt.parent.children.indexOf(player)) {
txt.parent.setChildIndex(txt, txt.parent.children.length - 1);
}
tween(txt, {
y: txt.y - 80,
alpha: 0
}, {
duration: 700,
onFinish: function onFinish() {
txt.destroy();
}
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x18181b
});
/****
* Game Code
****/
// (Cloud update and spawn logic removed)
// purple coin, value 3
// gold coin, value 1
// Gold
// Neon
// Metal
// Ice
// Stone
// Night
// Candy
// Magic
// Mud
// Forest
// Grass
// was chipWhite
// was chipOrange
// was chipGreen
// was chipYellow
// was chipPurple
// was chipPink
// was chipCyan
// was chipBrown
// was chipBlack
// Additional chip assets for more levels
// --- Icy Tower Constants ---
// Cloud shape assets for customizable cloud looks
// Collectible star asset (yellow star shape)
var highScore = storage.highScore || 0;
var GAME_W = 2048;
var GAME_H = 2732;
var PLATFORM_W = 400;
// Define new left/right border X values at the intersection of opaque and transparent background
var BORDER_LEFT_X = Math.floor(GAME_W / 30);
var BORDER_RIGHT_X = GAME_W - Math.floor(GAME_W / 30);
var PLATFORM_H = 110;
var PLATFORM_SPACING_MIN = 320;
var PLATFORM_SPACING_MAX = 440;
var PLAYER_W = 120;
var PLAYER_H = 120;
var GRAVITY = 2.2;
var JUMP_VELOCITY = -48;
var MOVE_SPEED = 22;
var PLATFORM_X_MARGIN = 120;
var CAMERA_OFFSET = 900; // How far from bottom the player is kept
// --- Cloud background state ---
var clouds = [];
var NUM_CLOUDS = 8; // Number of clouds to show in background
// --- State ---
var platforms = [];
var coins = []; // All active coins
var stars = []; // All active stars
var player = null;
var vy = 0;
var vx = 0;
var isJumping = false;
var isTouching = false;
var touchStartX = 0;
// Track all active touches (for multi-touch)
var activeTouches = [];
var cameraY = 0;
var maxHeight = 0;
var gameOver = false;
game.hasDoubleJumped = false;
// --- Assets ---
var playerAsset = LK.getAsset('chipCharacter', {
anchorX: 0.5,
anchorY: 1,
scaleX: PLAYER_W / 320,
scaleY: PLAYER_H / 320
});
// Level themes: background color and platform asset per level
// Platform color is always high-contrast with background for visibility
var LEVEL_THEMES = [{
// 1 Grass
bg: 0x18181b,
platformAsset: 'chipPlatform1',
platformColor: 0xfacc15
}, {
// 2 Forest
bg: 0x1e293b,
platformAsset: 'chipPlatform2',
platformColor: 0xf1f5f9
}, {
// 3 Mud
bg: 0x3b1e1e,
platformAsset: 'chipPlatform3',
platformColor: 0xffffff
}, {
// 4 Magic
bg: 0x2d1e3b,
platformAsset: 'chipPlatform4',
platformColor: 0xffe066
}, {
// 5 Candy
bg: 0x3b2d1e,
platformAsset: 'chipPlatform5',
platformColor: 0x22223b
}, {
// 6 Night
bg: 0x1e3b2d,
platformAsset: 'chipPlatform6',
platformColor: 0xf8fafc
}, {
// 7 Stone
bg: 0x3b1e2d,
platformAsset: 'chipPlatform7',
platformColor: 0x22223b
}, {
// 8 Ice
bg: 0x1e2d3b,
platformAsset: 'chipPlatform8',
platformColor: 0x22223b
}, {
// 9 Metal
bg: 0x2d3b1e,
platformAsset: 'chipPlatform9',
platformColor: 0xf8fafc
}, {
// 10 Neon
bg: 0x000000,
platformAsset: 'chipPlatform10',
platformColor: 0xfacc15
}, {
// 11 Special (chipPlatform11)
bg: 0x22223b,
platformAsset: 'chipPlatform11',
platformColor: 0xffffff
}];
// Helper to get current theme index based on platformsPassed/level
function getThemeIndex(score) {
// Every level lasts for 50 platforms
var idx = Math.floor(platformsPassed / 50);
if (idx < 0) idx = 0;
if (idx >= LEVEL_THEMES.length) idx = LEVEL_THEMES.length - 1;
return idx;
}
// Helper to get current theme object
function getCurrentTheme(score) {
return LEVEL_THEMES[getThemeIndex(score)];
}
// Used for initial platform asset (will be replaced in createPlatform)
var platformAsset = LK.getAsset('chipPlatform8', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: PLATFORM_W / 1100,
scaleY: PLATFORM_H / 700
});
// --- UI ---
// Platform pass counter
var platformsPassed = 0;
// Track total coins collected
var coinsCollected = 0;
// --- Score & Coins UI ---
// Create a container for the UI background and labels
var uiContainer = new Container();
// Add a black rectangle background behind the UI (covers full top width, rectangle shape)
var uiBgRect = LK.getAsset('uiTopBgRect', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
scaleX: GAME_W / 100,
scaleY: 1
});
uiContainer.addChild(uiBgRect);
var uiBgWidth = GAME_W;
var uiBgHeight = 110;
// "Coins: <number>" label (first, left)
var coinsLabelTxt = new Text2('Coins: 0', {
size: 70,
fill: 0xFFE066,
font: "Impact"
});
coinsLabelTxt.anchor.set(0, 0.5); // left aligned, vertically centered
coinsLabelTxt.x = 60;
coinsLabelTxt.y = uiBgHeight / 2;
// "Score:" label (right of coins, with extra spacing)
var scoreLabelTxt = new Text2('Score:', {
size: 70,
fill: "#fff",
font: "Impact"
});
scoreLabelTxt.anchor.set(0, 0.5); // left aligned, vertically centered
scoreLabelTxt.x = coinsLabelTxt.x + coinsLabelTxt.width + 80;
scoreLabelTxt.y = uiBgHeight / 2;
// Score value (right of label, with spacing)
var scoreTxt = new Text2('0', {
size: 80,
fill: "#fff",
font: "Impact"
});
scoreTxt.anchor.set(0, 0.5); // left aligned, vertically centered
scoreTxt.x = scoreLabelTxt.x + scoreLabelTxt.width + 30;
scoreTxt.y = uiBgHeight / 2;
// Add all to container in new order: coins, score label, score value
uiContainer.addChild(coinsLabelTxt);
uiContainer.addChild(scoreLabelTxt);
uiContainer.addChild(scoreTxt);
// Center the UI container at the top of the screen
uiContainer.x = 0;
uiContainer.y = 0;
// Add to GUI overlay (top center)
LK.gui.top.addChild(uiContainer);
// Proper high score text object for updating best score
var highScoreTxt = new Text2('', {
size: 60,
fill: "#fff",
font: "Impact"
});
highScoreTxt.anchor.set(1, 0.5); // right aligned, vertically centered
highScoreTxt.x = GAME_W - 60;
highScoreTxt.y = uiBgHeight / 2;
uiContainer.addChild(highScoreTxt);
// Listen for game over and update final score in the popup
LK.on('gameover', function () {
// Show the correct final score in the game over popup
if (typeof LK.setFinalScore === "function") {
LK.setFinalScore(platformsPassed);
}
});
// --- Helper: create a platform at (x, y) ---
function createPlatform(x, y, width) {
var plat = new Container();
// Make each new platform a bit harder as level increases
// Find the last platform's width if any, otherwise use PLATFORM_W
var lastPlat = platforms.length > 0 ? platforms[platforms.length - 1] : null;
var prevW = lastPlat && lastPlat.width ? lastPlat.width : PLATFORM_W;
var level = getThemeIndex(platformsPassed);
// Platform width shrinks with level, min 220, max 1100
// Make width decrease more gradually per level (every 20 platforms)
var baseW = 820 - level * 40;
if (baseW < 220) baseW = 220;
var w;
// Platform width logic (no checkpoint logic)
var platNum = platformsPassed + platforms.length;
var themeLength = 50; // Each theme has 50 platforms
var w;
if (typeof width === "number") {
w = width;
} else {
// Each new platform is 4% wider than the previous, but capped by baseW for the level
w = Math.min(prevW * 1.04, baseW);
}
// Determine theme based on platform index counter for new platforms
// Ensure each theme has at most 50 platforms
var platformIdx = typeof createPlatform.platformIndex === "number" ? createPlatform.platformIndex - 1 : platformsPassed;
var themeIdx = Math.floor(platformIdx / 50);
if (themeIdx >= LEVEL_THEMES.length) themeIdx = LEVEL_THEMES.length - 1;
var theme = LEVEL_THEMES[themeIdx];
var assetId = theme.platformAsset;
var platformColor = theme.platformColor;
// Get original asset size for aspect ratio
var assetInfo = LK.getAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
var origW = assetInfo.width;
var origH = assetInfo.height;
var scaleX = w / origW;
var scaleY = PLATFORM_H / origH;
// To preserve aspect ratio, use the smaller scale
var scale = Math.min(scaleX, scaleY);
var platGfx = plat.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: scale,
scaleY: scale,
color: platformColor
});
plat.x = x;
plat.y = y;
plat.width = w;
plat.height = PLATFORM_H;
plat.PLATFORM_H = PLATFORM_H;
game.addChild(plat);
platforms.push(plat);
// --- Add platform number label to each platform except platform0 ---
// Prevent more than 50 platforms per theme
if (platforms.length > 1) {
if (typeof createPlatform.platformIndex === "undefined") createPlatform.platformIndex = 1;
var platformNumber = createPlatform.platformIndex;
var themeIdxForPlat = Math.floor((platformNumber - 1) / 50);
var themePlatformNumber = (platformNumber - 1) % 50 + 1;
// Only allow up to 50 platforms per theme
if (themePlatformNumber > 50) {
return plat;
}
createPlatform.platformIndex++;
// Add a black opaque background behind the platform label, half size (background only)
var labelBgW = 70;
var labelBgH = 48;
// Make label background half as wide, double the height, and fully opaque
var scaleX = 0.5 * (labelBgW / 100);
var scaleY = 2 * (labelBgH / 110);
var labelBg = LK.getAsset('uiTopBgRect', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: scaleX,
scaleY: scaleY,
alpha: 1
});
labelBg.x = 0;
labelBg.y = 0;
plat.addChild(labelBg);
var counterTxt = new Text2(platformNumber + "", {
size: 18,
fill: "#fff",
font: "Impact"
});
counterTxt.anchor.set(0.5, 0.5);
counterTxt.x = 0;
counterTxt.y = 0;
counterTxt.scale.x = 0.5;
counterTxt.scale.y = 2;
plat.addChild(counterTxt);
}
// --- Coin and Star placement logic ---
// Don't place coins or stars on the first platform (player start)
// Also, do not spawn collectibles in the first theme (first 40 platforms)
if (platforms.length > 1 && getThemeIndex(platformsPassed) > 0) {
// 50% chance to spawn a coin on this platform
if (Math.random() < 0.5) {
// Randomly pick coin type: 60% +1, 30% +3, 10% +5
var r = Math.random();
var coinType = 1;
if (r > 0.9) coinType = 3;else if (r > 0.6) coinType = 2;
// Place coin at center of platform, slightly above
var coin = new Coin(coinType);
coin.x = plat.x;
coin.y = plat.y - PLATFORM_H / 2 - 40;
game.addChild(coin);
coins.push(coin);
}
// 15% chance to spawn a star (but not if a coin is already placed)
if (Math.random() < 0.15 && coins.length > 0 && coins[coins.length - 1].x !== plat.x) {
var star = new Star();
star.x = plat.x + (Math.random() - 0.5) * (plat.width * 0.4); // randomize a bit
star.y = plat.y - PLATFORM_H / 2 - 100;
game.addChild(star);
stars.push(star);
}
}
// --- Add a sign to the last platform of each theme ---
var themeIdxForPlat = getThemeIndex(platformsPassed + platforms.length - 1);
var isLastOfTheme = (platformsPassed + platforms.length) % 50 === 0;
if (isLastOfTheme) {
var sign = new PlatformSign();
sign.x = plat.x;
sign.y = plat.y - PLATFORM_H / 2 - 10;
sign.setPlatformsPassed(platformsPassed + platforms.length);
game.addChild(sign);
plat._themeSign = sign;
}
return plat;
}
// --- Helper: find a safe X for a new platform, given previous platform ---
function getSafePlatformX(prevPlat, width) {
// Always keep new platform horizontally reachable from previous
var minX = Math.max(PLATFORM_X_MARGIN + width / 2, prevPlat ? prevPlat.x - 400 : PLATFORM_X_MARGIN + width / 2);
var maxX = Math.min(GAME_W - PLATFORM_X_MARGIN - width / 2, prevPlat ? prevPlat.x + 400 : GAME_W - PLATFORM_X_MARGIN - width / 2);
if (minX > maxX) minX = maxX = prevPlat ? prevPlat.x : GAME_W / 2;
return minX + Math.random() * (maxX - minX);
}
// --- Helper: reset game state ---
function resetGame() {
// Remove old coins
for (var i = coins.length - 1; i >= 0; --i) {
if (coins[i] && typeof coins[i].destroy === "function") {
coins[i].destroy();
}
}
coins = [];
// Remove old stars
for (var i = stars.length - 1; i >= 0; --i) {
if (stars[i] && typeof stars[i].destroy === "function") {
stars[i].destroy();
}
}
stars = [];
// Remove old platforms
for (var i = 0; i < platforms.length; ++i) {
platforms[i].destroy();
}
platforms = [];
// Remove player
if (player) player.destroy();
// Create player
player = new Container();
var pGfx = player.attachAsset('chipCharacter', {
anchorX: 0.5,
anchorY: 1,
scaleX: PLAYER_W / 320,
scaleY: PLAYER_H / 320
});
player.x = GAME_W / 2;
player.y = GAME_H - 400;
player.width = PLAYER_W;
player.height = PLAYER_H;
game.addChild(player);
vy = 0;
vx = 0;
isJumping = false;
isTouching = false;
cameraY = 0;
maxHeight = 0;
gameOver = false;
// Create initial platforms
var y = GAME_H - 120;
// --- Add platform0 as the very first platform ---
var platform0AssetId = 'chipPlatform0';
var platform0AssetInfo = LK.getAsset(platform0AssetId, {
anchorX: 0.5,
anchorY: 0.5
});
var platform0W = platform0AssetInfo.width;
var platform0H = platform0AssetInfo.height;
var platform0Scale = PLATFORM_H / platform0H;
var platform0 = new Container();
var platform0Gfx = platform0.attachAsset(platform0AssetId, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: platform0Scale,
scaleY: platform0Scale
});
platform0.x = GAME_W / 2;
platform0.y = y;
platform0.width = platform0W * platform0Scale;
platform0.height = PLATFORM_H;
platform0.PLATFORM_H = PLATFORM_H;
game.addChild(platform0);
platforms.push(platform0);
// Place player directly above platform0, so the game starts after landing on this platform
player.x = platform0.x;
player.y = platform0.y - PLATFORM_H / 2 - PLAYER_H / 2 + 10; // Center player above platform, slightly above
// Create initial platforms
createPlatform.platformIndex = 1; // Reset platform index counter at game start
var level = getThemeIndex(platformsPassed);
var EASY_PLATFORM_W = 820 - level * 40;
if (EASY_PLATFORM_W < 220) EASY_PLATFORM_W = 220;
// Keep vertical spacing constant for all levels
var EASY_PLATFORM_SPACING = 240;
var prevPlat = platform0;
y -= EASY_PLATFORM_SPACING;
for (var i = 1; i < 12; ++i) {
var px = getSafePlatformX(prevPlat, EASY_PLATFORM_W);
var plat = createPlatform(px, y, EASY_PLATFORM_W);
prevPlat = plat;
y -= EASY_PLATFORM_SPACING;
}
// Sort platforms by y
platforms.sort(function (a, b) {
return a.y - b.y;
});
// Score
platformsPassed = 0;
coinsCollected = 0;
scoreTxt.setText('0');
highScoreTxt.setText('Best: ' + highScore);
// Reset theme and background
game.lastThemeIndex = -1;
// Use bgTheme asset classes for background
var themeIdx = getThemeIndex(platformsPassed);
// Map for each theme: [opaqueAsset, transparentAsset]
var bgThemeAssets = [['bgThemeGrassOpaque', 'bgThemeGrassTrans'],
// 0 Grass
['bgThemeForestOpaque', 'bgThemeForestTrans'],
// 1 Forest
['bgThemeMudOpaque', 'bgThemeMudTrans'],
// 2 Mud
['bgThemeMagicOpaque', 'bgThemeMagicTrans'],
// 3 Magic
['bgThemeCandyOpaque', 'bgThemeCandyTrans'],
// 4 Candy
['bgThemeNightOpaque', 'bgThemeNightTrans'],
// 5 Night
['bgThemeStoneOpaque', 'bgThemeStoneTrans'],
// 6 Stone
['bgThemeIceOpaque', 'bgThemeIceTrans'],
// 7 Ice
['bgThemeMetalOpaque', 'bgThemeMetalTrans'],
// 8 Metal
['bgThemeNeonOpaque', 'bgThemeNeonTrans'],
// 9 Neon
['bgThemeSpecialOpaque', 'bgThemeSpecialTrans'] // 10 Special
];
if (themeIdx < 0) themeIdx = 0;
if (themeIdx >= bgThemeAssets.length) themeIdx = bgThemeAssets.length - 1;
// Remove previous background asset if any
if (game._bgThemeAsset) {
if (typeof game._bgThemeAsset.destroy === "function") {
game._bgThemeAsset.destroy();
}
game._bgThemeAsset = null;
}
// Add new background asset as the first child (behind everything)
var bgOpaqueAssetId = bgThemeAssets[themeIdx][0];
var bgTransAssetId = bgThemeAssets[themeIdx][1];
// Create a container for the background with three segments: left, center, right
var bgThemeContainer = new Container();
var bgOpaqueAssetInfo = LK.getAsset(bgOpaqueAssetId, {
anchorX: 0,
anchorY: 0
});
var bgTransAssetInfo = LK.getAsset(bgTransAssetId, {
anchorX: 0,
anchorY: 0
});
var bgWidth = GAME_W;
var leftW = Math.floor(bgWidth / 30);
var rightW = Math.floor(bgWidth / 30);
var centerW = bgWidth - leftW - rightW;
// Left 1/30 (opaque)
var bgLeft = LK.getAsset(bgOpaqueAssetId, {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
scaleX: leftW / bgOpaqueAssetInfo.width,
scaleY: GAME_H / bgOpaqueAssetInfo.height,
alpha: 1
});
bgThemeContainer.addChild(bgLeft);
// Center (transparent)
var bgCenter = LK.getAsset(bgTransAssetId, {
anchorX: 0,
anchorY: 0,
x: leftW,
y: 0,
scaleX: centerW / bgTransAssetInfo.width,
scaleY: GAME_H / bgTransAssetInfo.height,
alpha: 0.3
});
bgThemeContainer.addChild(bgCenter);
// Right 1/30 (opaque)
var bgRight = LK.getAsset(bgOpaqueAssetId, {
anchorX: 0,
anchorY: 0,
x: leftW + centerW,
y: 0,
scaleX: rightW / bgOpaqueAssetInfo.width,
scaleY: GAME_H / bgOpaqueAssetInfo.height,
alpha: 1
});
bgThemeContainer.addChild(bgRight);
game.addChild(bgThemeContainer);
if (game.children && game.children.length > 1) {
game.setChildIndex(bgThemeContainer, 0);
}
game._bgThemeAsset = bgThemeContainer;
// Remove old clouds
for (var i = clouds.length - 1; i >= 0; --i) {
if (clouds[i] && typeof clouds[i].destroy === "function") {
clouds[i].destroy();
}
}
clouds = [];
// Add new clouds to background
for (var i = 0; i < NUM_CLOUDS; ++i) {
var cloud = new Cloud();
// Distribute vertically in the upper 2/3 of the screen
cloud.y = Math.random() * (GAME_H * 0.66);
cloud.x = Math.random() * GAME_W;
game.addChild(cloud);
cloud.setToBack && cloud.setToBack();
clouds.push(cloud);
}
// (Cloud background reset removed)
}
// --- Helper: check collision between player and platform ---
function playerOnPlatform() {
for (var i = 0; i < platforms.length; ++i) {
var plat = platforms[i];
// Only check if player is falling
if (vy >= 0) {
var px = player.x;
var py = player.y;
var platTop = plat.y - (plat.PLATFORM_H ? plat.PLATFORM_H : PLATFORM_H) / 2;
var platLeft = plat.x - plat.width / 2;
var platRight = plat.x + plat.width / 2;
// Use previous player y for more reliable collision
if (typeof player.lastY === "undefined") player.lastY = py - vy;
// Check if player's feet crossed the platform top this frame
if (player.lastY <= platTop && py >= platTop) {
// Require player to be horizontally within platform bounds (with a small margin)
if (px > platLeft + 10 && px < platRight - 10) {
player.lastY = py; // update for next frame
return plat;
}
}
}
}
if (typeof player !== "undefined") player.lastY = player.y;
return null;
}
// --- Touch controls: left/right jump ---
game.down = function (x, y, obj) {
if (gameOver) return;
// Add this touch to activeTouches
if (obj && obj.event && typeof obj.event.identifier !== "undefined") {
// Remove if already present (shouldn't happen, but for safety)
for (var i = 0; i < activeTouches.length; ++i) {
if (activeTouches[i].id === obj.event.identifier) {
activeTouches.splice(i, 1);
break;
}
}
activeTouches.push({
id: obj.event.identifier,
x: x,
y: y
});
} else {
// Fallback for mouse or single touch
activeTouches = [{
id: 0,
x: x,
y: y
}];
}
isTouching = true;
touchStartX = x;
// Always use the last finger down for direction, even after double jump
var lastTouch = activeTouches[activeTouches.length - 1];
if (lastTouch && lastTouch.x < GAME_W / 2) {
vx = -MOVE_SPEED;
} else {
vx = MOVE_SPEED;
}
// If player is on ground/platform, jump
if (!isJumping && !game.hasDoubleJumped) {
vy = JUMP_VELOCITY;
isJumping = true;
}
// No-op: direction change after double jump is handled in move handler
};
game.up = function (x, y, obj) {
// Remove this touch from activeTouches
if (obj && obj.event && typeof obj.event.identifier !== "undefined") {
for (var i = 0; i < activeTouches.length; ++i) {
if (activeTouches[i].id === obj.event.identifier) {
activeTouches.splice(i, 1);
break;
}
}
} else {
// Fallback for mouse or single touch
activeTouches = [];
}
if (activeTouches.length === 0) {
isTouching = false;
vx = 0;
} else {
// Use the new last finger for direction, even after double jump
var lastTouch = activeTouches[activeTouches.length - 1];
if (lastTouch && lastTouch.x < GAME_W / 2) {
vx = -MOVE_SPEED;
} else {
vx = MOVE_SPEED;
}
}
};
game.move = function (x, y, obj) {
// Update the position of the moving finger in activeTouches
if (obj && obj.event && typeof obj.event.identifier !== "undefined") {
for (var i = 0; i < activeTouches.length; ++i) {
if (activeTouches[i].id === obj.event.identifier) {
activeTouches[i].x = x;
activeTouches[i].y = y;
break;
}
}
} else if (activeTouches.length > 0) {
// Fallback for mouse or single touch
activeTouches[activeTouches.length - 1].x = x;
activeTouches[activeTouches.length - 1].y = y;
}
// Always use the last finger for direction, even after double jump or wall hit
if (activeTouches.length > 0 && !gameOver) {
var lastTouch = activeTouches[activeTouches.length - 1];
if (lastTouch && lastTouch.x < GAME_W / 2) {
vx = -MOVE_SPEED;
} else {
vx = MOVE_SPEED;
}
} else if (!isTouching) {
vx = 0;
}
// No else: vx is always set while touching, even after double jump
};
// --- Main update loop ---
game.update = function () {
if (gameOver) return;
// Physics
if (typeof player.lastVy === "undefined") player.lastVy = vy;
if (typeof player.lastVx === "undefined") player.lastVx = vx;
// Smooth velocity interpolation for mobile performance
// Use a simple inertia/lerp for vx to avoid abrupt changes
var targetVx = vx;
if (typeof player.smoothVx === "undefined") player.smoothVx = vx;
// Increase lerp factor for even smoother and more responsive movement
player.smoothVx += (targetVx - player.smoothVx) * 0.32; // 0.32 is more responsive and smooth
vy += GRAVITY;
player.x += player.smoothVx;
player.y += vy;
player.lastVx = player.smoothVx;
// Clamp player to new left/right border and tumble on border hit
if (player && typeof player.x === "number" && player.x < BORDER_LEFT_X + PLAYER_W / 2) {
player.x = BORDER_LEFT_X + PLAYER_W / 2;
// Tumble: rotate player quickly
if (player && player.children && player.children.length > 0) {
var pGfx = player.children[0];
tween(pGfx, {
rotation: pGfx.rotation - Math.PI * 2
}, {
duration: 400,
onFinish: function onFinish() {
pGfx.rotation = 0;
}
});
}
// Play shout sound when hitting left border, with cooldown
if (typeof game.lastShoutTime === "undefined") game.lastShoutTime = 0;
var nowWall = Date.now();
if (nowWall - game.lastShoutTime > 500) {
game.lastShoutTime = nowWall;
}
// Double jump when hitting the border, but only if not already double jumped without release
if (!game.hasDoubleJumped) {
vy = JUMP_VELOCITY * 2;
isJumping = true;
game.hasDoubleJumped = true;
// Emit 18-24 animated stars from player after double jump, with color cycling and rainbow effect
var numEmitStars = 18 + Math.floor(Math.random() * 7); // 18-24
// Rainbow arc effect: create a rainbow container behind the player
var rainbowArc = new Container();
rainbowArc.x = player.x;
rainbowArc.y = player.y - PLAYER_H / 2;
rainbowArc._life = 0;
rainbowArc._maxLife = 36;
rainbowArc._rainbowSegments = [];
var rainbowColors = [0xff3b30, 0xff9500, 0xffcc00, 0x4cd964, 0x5ac8fa, 0x007aff, 0x5856d6];
var rainbowRadius = 110;
var rainbowWidth = 22;
for (var seg = 0; seg < rainbowColors.length; ++seg) {
var segAsset = LK.getAsset('collectibleStar', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: rainbowWidth / 90 * (1.1 + seg * 0.08),
scaleY: rainbowWidth / 90 * (1.1 + seg * 0.08),
tint: rainbowColors[seg],
alpha: 0.38
});
// Position in an arc behind the player
var arcAngle = Math.PI * (0.5 + 0.7 * (seg / (rainbowColors.length - 1)));
segAsset.x = Math.cos(arcAngle) * rainbowRadius;
segAsset.y = Math.sin(arcAngle) * rainbowRadius * 0.7;
rainbowArc.addChild(segAsset);
rainbowArc._rainbowSegments.push(segAsset);
}
game.addChild(rainbowArc);
// Animate rainbow arc fade and scale
rainbowArc.update = function () {
rainbowArc._life++;
var t = rainbowArc._life / rainbowArc._maxLife;
rainbowArc.alpha = 0.7 * (1 - t);
rainbowArc.scale.x = 1 + 0.25 * t;
rainbowArc.scale.y = 1 + 0.25 * t;
rainbowArc.x = player.x;
rainbowArc.y = player.y - PLAYER_H / 2;
if (rainbowArc._life > rainbowArc._maxLife) {
if (typeof rainbowArc.destroy === "function") rainbowArc.destroy();
}
};
stars.push(rainbowArc);
for (var emitIdx = 0; emitIdx < numEmitStars; ++emitIdx) {
var emitStar = new Star();
emitStar._emittedAnimation = true; // Mark as animation-only, not collectible
emitStar.x = player.x;
emitStar.y = player.y - PLAYER_H / 2;
// Give each star a random direction and speed
var angle = Math.PI * 2 * (emitIdx / numEmitStars) + (Math.random() - 0.5) * 0.5;
var speed = 18 + Math.random() * 8;
emitStar._vx = Math.cos(angle) * speed;
emitStar._vy = Math.sin(angle) * speed - 6;
emitStar._life = 0;
emitStar._maxLife = 32 + Math.random() * 10;
// Color cycling: pick a random start color and cycle through a palette
var colorCycle = [0xfff7b2, 0xffe066, 0xffb347, 0xff3b30, 0x4cd964, 0x5ac8fa, 0x007aff, 0x5856d6, 0xffcc00];
var colorIdx = Math.floor(Math.random() * colorCycle.length);
// Override update for animation
emitStar.update = function (star, colorIdx) {
return function () {
star.x += star._vx;
star.y += star._vy;
star._vy += 1.1; // gravity
star._vx *= 0.96; // friction
star._life++;
// Color cycling: change color every 6 frames
if (star.children && star.children[0]) {
var cycleStep = Math.floor(star._life / 6);
var nextColor = colorCycle[(colorIdx + cycleStep) % colorCycle.length];
star.children[0].tint = nextColor;
// Fade out
star.children[0].alpha = Math.max(0, 0.92 * (1 - star._life / star._maxLife));
}
if (star._life > star._maxLife) {
if (typeof star.destroy === "function") star.destroy();
var idx = stars.indexOf(star);
if (idx !== -1) stars.splice(idx, 1);
}
};
}(emitStar, colorIdx);
game.addChild(emitStar);
stars.push(emitStar);
}
// Play shout sound only if double jump is successful
LK.getSound('shout').play();
}
}
if (player.x > BORDER_RIGHT_X - PLAYER_W / 2) {
player.x = BORDER_RIGHT_X - PLAYER_W / 2;
// Tumble: rotate player quickly
if (player && player.children && player.children.length > 0) {
var pGfx = player.children[0];
tween(pGfx, {
rotation: pGfx.rotation + Math.PI * 2
}, {
duration: 400,
onFinish: function onFinish() {
pGfx.rotation = 0;
}
});
}
// Play shout sound when hitting right border, with cooldown
if (typeof game.lastShoutTime === "undefined") game.lastShoutTime = 0;
var nowWall = Date.now();
if (nowWall - game.lastShoutTime > 500) {
game.lastShoutTime = nowWall;
}
// Double jump when hitting the border, but only if not already double jumped without release
if (!game.hasDoubleJumped) {
vy = JUMP_VELOCITY * 2;
isJumping = true;
game.hasDoubleJumped = true;
// Emit 18-24 animated stars from player after double jump, with color cycling and rainbow effect
var numEmitStars = 18 + Math.floor(Math.random() * 7); // 18-24
// Rainbow arc effect: create a rainbow container behind the player
var rainbowArc = new Container();
rainbowArc.x = player.x;
rainbowArc.y = player.y - PLAYER_H / 2;
rainbowArc._life = 0;
rainbowArc._maxLife = 36;
rainbowArc._rainbowSegments = [];
var rainbowColors = [0xff3b30, 0xff9500, 0xffcc00, 0x4cd964, 0x5ac8fa, 0x007aff, 0x5856d6];
var rainbowRadius = 110;
var rainbowWidth = 22;
for (var seg = 0; seg < rainbowColors.length; ++seg) {
var segAsset = LK.getAsset('collectibleStar', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: rainbowWidth / 90 * (1.1 + seg * 0.08),
scaleY: rainbowWidth / 90 * (1.1 + seg * 0.08),
tint: rainbowColors[seg],
alpha: 0.38
});
// Position in an arc behind the player
var arcAngle = Math.PI * (0.5 + 0.7 * (seg / (rainbowColors.length - 1)));
segAsset.x = Math.cos(arcAngle) * rainbowRadius;
segAsset.y = Math.sin(arcAngle) * rainbowRadius * 0.7;
rainbowArc.addChild(segAsset);
rainbowArc._rainbowSegments.push(segAsset);
}
game.addChild(rainbowArc);
// Animate rainbow arc fade and scale
rainbowArc.update = function () {
rainbowArc._life++;
var t = rainbowArc._life / rainbowArc._maxLife;
rainbowArc.alpha = 0.7 * (1 - t);
rainbowArc.scale.x = 1 + 0.25 * t;
rainbowArc.scale.y = 1 + 0.25 * t;
rainbowArc.x = player.x;
rainbowArc.y = player.y - PLAYER_H / 2;
if (rainbowArc._life > rainbowArc._maxLife) {
if (typeof rainbowArc.destroy === "function") rainbowArc.destroy();
}
};
stars.push(rainbowArc);
for (var emitIdx = 0; emitIdx < numEmitStars; ++emitIdx) {
var emitStar = new Star();
emitStar._emittedAnimation = true; // Mark as animation-only, not collectible
emitStar.x = player.x;
emitStar.y = player.y - PLAYER_H / 2;
// Give each star a random direction and speed
var angle = Math.PI * 2 * (emitIdx / numEmitStars) + (Math.random() - 0.5) * 0.5;
var speed = 18 + Math.random() * 8;
emitStar._vx = Math.cos(angle) * speed;
emitStar._vy = Math.sin(angle) * speed - 6;
emitStar._life = 0;
emitStar._maxLife = 32 + Math.random() * 10;
// Color cycling: pick a random start color and cycle through a palette
var colorCycle = [0xfff7b2, 0xffe066, 0xffb347, 0xff3b30, 0x4cd964, 0x5ac8fa, 0x007aff, 0x5856d6, 0xffcc00];
var colorIdx = Math.floor(Math.random() * colorCycle.length);
// Override update for animation
emitStar.update = function (star, colorIdx) {
return function () {
star.x += star._vx;
star.y += star._vy;
star._vy += 1.1; // gravity
star._vx *= 0.96; // friction
star._life++;
// Color cycling: change color every 6 frames
if (star.children && star.children[0]) {
var cycleStep = Math.floor(star._life / 6);
var nextColor = colorCycle[(colorIdx + cycleStep) % colorCycle.length];
star.children[0].tint = nextColor;
// Fade out
star.children[0].alpha = Math.max(0, 0.92 * (1 - star._life / star._maxLife));
}
if (star._life > star._maxLife) {
if (typeof star.destroy === "function") star.destroy();
var idx = stars.indexOf(star);
if (idx !== -1) stars.splice(idx, 1);
}
};
}(emitStar, colorIdx);
game.addChild(emitStar);
stars.push(emitStar);
}
// Play shout sound only if double jump is successful
LK.getSound('shout').play();
}
}
// Platform collision
var plat = playerOnPlatform();
// (Removed logic that made checkpoint platforms appear after passing. Now, checkpoint platforms always come down from the top like other platforms.)
// --- Increment platformsPassed when player passes a platform (crosses its top Y going down) ---
// Use the platform number label (if present) to determine the current platform index
if (typeof player.lastPlatformY === "undefined") player.lastPlatformY = null;
if (plat && vy > 0) {
// Try to get the platform number from the label (if present)
var platformNumber = 0;
for (var i = 0; i < plat.children.length; ++i) {
var child = plat.children[i];
if (child && typeof child.setText === "function" && typeof child.text === "string") {
var num = parseInt(child.text, 10);
if (!isNaN(num)) {
platformNumber = num;
break;
}
}
}
// If no label, check if this is the very first platform (platform0)
if (platformNumber === 0 && platforms.length > 0 && plat === platforms[0]) {
platformNumber = 0;
}
// Always set platformsPassed to the number on the platform label the player has reached
// For platform0, show 0. For all others, show the label number.
platformsPassed = platformNumber;
scoreTxt.setText(platformsPassed.toString());
plat._countedPassed = true;
player.y = plat.y - PLATFORM_H / 2;
vy = JUMP_VELOCITY;
isJumping = false;
game.hasDoubleJumped = false; // Reset double jump lock only when landing
// Always play jump animation here for consistency
if (player && player.children && player.children.length > 0) {
var pGfx = player.children[0];
// Squash down, stretch wide, add a little rotation (no color change)
tween(pGfx, {
scaleY: 0.7,
scaleX: 1.25,
rotation: 0.18
}, {
duration: 90,
onFinish: function onFinish() {
// Stretch up, squash in, overshoot a bit for bounce
tween(pGfx, {
scaleY: 1.18 * PLAYER_H / 320,
scaleX: 0.88 * PLAYER_W / 320,
rotation: -0.08
}, {
duration: 90,
onFinish: function onFinish() {
// Return to normal
tween(pGfx, {
scaleY: PLAYER_H / 320,
scaleX: PLAYER_W / 320,
rotation: 0
}, {
duration: 90
});
}
});
}
});
}
} else {
isJumping = true;
// No shout or fall sound here
}
// --- Coin collection ---
// Use last position to detect passing through coins, not just landing on top
if (typeof player.lastX === "undefined") player.lastX = player.x;
if (typeof player.lastY === "undefined") player.lastY = player.y;
for (var j = coins.length - 1; j >= 0; --j) {
var coin = coins[j];
// Simple circle collision, check both current and previous position for pass-through
var dx = player.x - coin.x;
var dy = player.y - PLAYER_H / 2 - coin.y;
var dist = Math.sqrt(dx * dx + dy * dy);
var lastDx = player.lastX - coin.x;
var lastDy = player.lastY - PLAYER_H / 2 - coin.y;
var lastDist = Math.sqrt(lastDx * lastDx + lastDy * lastDy);
var collectRadius = (PLAYER_W / 2 + coin.radius) * 0.7;
// If player is within collect radius now or passed through it this frame
if (dist < collectRadius || lastDist > collectRadius && dist < collectRadius || lastDist < collectRadius && dist < collectRadius) {
// Collect coin
coin.showValuePopup();
// Only add to coins collected, not platformsPassed
coinsCollected += coin.value;
coins.splice(j, 1);
coin.destroy();
}
}
// --- Star collection ---
// Use last position to detect passing through stars, not just landing on top
for (var j = stars.length - 1; j >= 0; --j) {
var star = stars[j];
var dx = player.x - star.x;
var dy = player.y - PLAYER_H / 2 - star.y;
var dist = Math.sqrt(dx * dx + dy * dy);
var lastDx = player.lastX - star.x;
var lastDy = player.lastY - PLAYER_H / 2 - star.y;
var lastDist = Math.sqrt(lastDx * lastDx + lastDy * lastDy);
var collectRadius = (PLAYER_W / 2 + star.radius) * 0.7;
// Only collect stars that are not "emitted" animation stars (i.e., only collectible platform stars)
// We'll mark emitted stars with a flag: star._emittedAnimation === true
if ((dist < collectRadius || lastDist > collectRadius && dist < collectRadius || lastDist < collectRadius && dist < collectRadius) && !star._emittedAnimation) {
// Collect star
star.showValuePopup();
// Only add to coins collected, not platformsPassed
coinsCollected += star.value;
stars.splice(j, 1);
star.destroy();
}
}
// Update lastX/lastY for next frame
player.lastX = player.x;
player.lastY = player.y;
// Camera follows player upward
if (player.y < GAME_H - CAMERA_OFFSET) {
var diff = GAME_H - CAMERA_OFFSET - player.y;
cameraY += diff;
// Move all platforms and player down by diff
for (var i = 0; i < platforms.length; ++i) {
platforms[i].y += diff;
}
// Move all coins down by diff so they stay visually attached to their platform
for (var i = 0; i < coins.length; ++i) {
coins[i].y += diff;
}
// Move all stars down by diff so they stay visually attached to their platform
for (var i = 0; i < stars.length; ++i) {
stars[i].y += diff;
}
// Move all clouds down by diff, and update their position
for (var i = clouds.length - 1; i >= 0; --i) {
var cloud = clouds[i];
cloud.y += diff * 0.6; // Parallax: move slower than platforms for depth
cloud.update && cloud.update();
// Remove cloud if it goes off bottom, add new one at top
if (cloud.y > GAME_H + 200) {
cloud.destroy();
clouds.splice(i, 1);
// Add new cloud at top
var newCloud = new Cloud();
newCloud.x = Math.random() * GAME_W;
newCloud.y = -100 - Math.random() * 200;
game.addChild(newCloud);
newCloud.setToBack && newCloud.setToBack();
clouds.push(newCloud);
}
}
player.y += diff;
maxHeight += diff;
}
// Always keep player in front of platforms
if (player.parent && player.parent.children.indexOf(player) !== player.parent.children.length - 1) {
player.parent.setChildIndex(player, player.parent.children.length - 1);
}
// Remove platforms that are off screen, add new ones at top
for (var i = platforms.length - 1; i >= 0; --i) {
if (platforms[i].y > GAME_H + 100) {
// Remove any coins that are visually on this platform (within platform width and just above it)
for (var j = coins.length - 1; j >= 0; --j) {
var coin = coins[j];
// Coin is considered on this platform if its x is within platform width and y is just above platform
if (coin.x >= platforms[i].x - platforms[i].width / 2 && coin.x <= platforms[i].x + platforms[i].width / 2 && Math.abs(coin.y - (platforms[i].y - PLATFORM_H / 2 - 40)) < 60) {
coin.destroy();
coins.splice(j, 1);
}
}
// Remove any stars that are visually on this platform (within platform width and just above it)
for (var j = stars.length - 1; j >= 0; --j) {
var star = stars[j];
if (star.x >= platforms[i].x - platforms[i].width / 2 && star.x <= platforms[i].x + platforms[i].width / 2 && Math.abs(star.y - (platforms[i].y - PLATFORM_H / 2 - 100)) < 80) {
star.destroy();
stars.splice(j, 1);
}
}
platforms[i].destroy();
platforms.splice(i, 1);
}
}
// Add new platforms if needed
while (platforms.length < 12) {
var topY = platforms[0].y;
// Make platform width and spacing harder as level increases
var level = getThemeIndex(platformsPassed);
var EASY_PLATFORM_W = 820 - level * 40;
if (EASY_PLATFORM_W < 220) EASY_PLATFORM_W = 220;
// Keep vertical spacing constant for all levels
var EASY_PLATFORM_SPACING = 240;
var newY = topY - EASY_PLATFORM_SPACING;
var prevPlat = platforms[0];
var px = getSafePlatformX(prevPlat, EASY_PLATFORM_W);
var plat = createPlatform(px, newY, EASY_PLATFORM_W);
platforms.sort(function (a, b) {
return a.y - b.y;
});
}
// Score: show the number of unique platforms passed by the player (do not include coins/stars)
scoreTxt.setText(platformsPassed.toString());
// Update coins label to show current coins collected
coinsLabelTxt.setText('Coins: ' + coinsCollected);
// Update the sign on the last platform of each theme to show current platforms passed
for (var i = 0; i < platforms.length; ++i) {
var plat = platforms[i];
if (plat._themeSign && typeof plat._themeSign.setPlatformsPassed === "function") {
plat._themeSign.setPlatformsPassed(platformsPassed);
}
}
// Change background color if level changes, using platform index counter for perfect sync
var themeIndex = getThemeIndex(platformsPassed);
// Always keep track of lastThemeIndex for perfect sync
if (typeof game.lastThemeIndex === "undefined") game.lastThemeIndex = getThemeIndex(0);
// Only change theme on the exact frame when platformsPassed crosses into a new theme (every 50 platforms passed)
if (Math.floor(game.lastThemeIndex) !== Math.floor(themeIndex) && platformsPassed % 50 === 0) {
// Use bgTheme asset classes for background on level change
var bgThemeAssets = [['bgThemeGrassOpaque', 'bgThemeGrassTrans'],
// 0 Grass
['bgThemeForestOpaque', 'bgThemeForestTrans'],
// 1 Forest
['bgThemeMudOpaque', 'bgThemeMudTrans'],
// 2 Mud
['bgThemeMagicOpaque', 'bgThemeMagicTrans'],
// 3 Magic
['bgThemeCandyOpaque', 'bgThemeCandyTrans'],
// 4 Candy
['bgThemeNightOpaque', 'bgThemeNightTrans'],
// 5 Night
['bgThemeStoneOpaque', 'bgThemeStoneTrans'],
// 6 Stone
['bgThemeIceOpaque', 'bgThemeIceTrans'],
// 7 Ice
['bgThemeMetalOpaque', 'bgThemeMetalTrans'],
// 8 Metal
['bgThemeNeonOpaque', 'bgThemeNeonTrans'],
// 9 Neon
['bgThemeSpecialOpaque', 'bgThemeSpecialTrans'] // 10 Special
];
var idx = themeIndex;
if (idx < 0) idx = 0;
if (idx >= bgThemeAssets.length) idx = bgThemeAssets.length - 1;
// Remove previous background asset if any
if (game._bgThemeAsset) {
if (typeof game._bgThemeAsset.destroy === "function") {
game._bgThemeAsset.destroy();
}
game._bgThemeAsset = null;
}
// Add new background asset as the first child (behind everything)
var bgOpaqueAssetId = bgThemeAssets[idx][0];
var bgTransAssetId = bgThemeAssets[idx][1];
// Create a container for the background with three segments: left, center, right
var bgThemeContainer = new Container();
var bgOpaqueAssetInfo = LK.getAsset(bgOpaqueAssetId, {
anchorX: 0,
anchorY: 0
});
var bgTransAssetInfo = LK.getAsset(bgTransAssetId, {
anchorX: 0,
anchorY: 0
});
var bgWidth = GAME_W;
var leftW = Math.floor(bgWidth / 30);
var rightW = Math.floor(bgWidth / 30);
var centerW = bgWidth - leftW - rightW;
// Left 1/30 (opaque)
var bgLeft = LK.getAsset(bgOpaqueAssetId, {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
scaleX: leftW / bgOpaqueAssetInfo.width,
scaleY: GAME_H / bgOpaqueAssetInfo.height,
alpha: 1
});
bgThemeContainer.addChild(bgLeft);
// Center (transparent)
var bgCenter = LK.getAsset(bgTransAssetId, {
anchorX: 0,
anchorY: 0,
x: leftW,
y: 0,
scaleX: centerW / bgTransAssetInfo.width,
scaleY: GAME_H / bgTransAssetInfo.height,
alpha: 0.3
});
bgThemeContainer.addChild(bgCenter);
// Right 1/30 (opaque)
var bgRight = LK.getAsset(bgOpaqueAssetId, {
anchorX: 0,
anchorY: 0,
x: leftW + centerW,
y: 0,
scaleX: rightW / bgOpaqueAssetInfo.width,
scaleY: GAME_H / bgOpaqueAssetInfo.height,
alpha: 1
});
bgThemeContainer.addChild(bgRight);
game.addChild(bgThemeContainer);
if (game.children && game.children.length > 1) {
game.setChildIndex(bgThemeContainer, 0);
}
game._bgThemeAsset = bgThemeContainer;
game.lastThemeIndex = themeIndex;
}
// High score logic (still based on maxHeight climbed)
var score = Math.floor(maxHeight / 10);
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
highScoreTxt.setText('Best: ' + highScore);
}
// Game over: fall below screen
if (player.y > GAME_H + 200) {
gameOver = true;
// Play fall sound when it's certain the player is going to die
LK.getSound('fall').play();
// Show game over after short delay
LK.setTimeout(function () {
LK.showGameOver();
}, 1200);
}
// Update lastVy for next frame
player.lastVy = vy;
// Update clouds for horizontal drift (even if camera doesn't move)
for (var i = 0; i < clouds.length; ++i) {
if (clouds[i] && typeof clouds[i].update === "function") {
clouds[i].update();
}
}
// Update stars for animation
for (var i = 0; i < stars.length; ++i) {
if (stars[i] && typeof stars[i].update === "function") {
stars[i].update();
}
}
// (Cloud update and spawn logic removed)
};
// --- Start game ---
resetGame(); ===================================================================
--- original.js
+++ change.js
@@ -568,20 +568,20 @@
if (themePlatformNumber > 50) {
return plat;
}
createPlatform.platformIndex++;
- // Add a black semi-transparent background behind the platform label
+ // Add a black opaque background behind the platform label, half size (background only)
var labelBgW = 70;
var labelBgH = 48;
- // Make label half as wide, double the height
+ // Make label background half as wide, double the height, and fully opaque
var scaleX = 0.5 * (labelBgW / 100);
var scaleY = 2 * (labelBgH / 110);
var labelBg = LK.getAsset('uiTopBgRect', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: scaleX,
scaleY: scaleY,
- alpha: 0.3
+ alpha: 1
});
labelBg.x = 0;
labelBg.y = 0;
plat.addChild(labelBg);
icy tower guy. In-Game asset. 2d. High contrast. No shadows
mario or icy tower like platforms. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
icy tower advanced level platform. In-Game asset. 2d. High contrast. No shadows
diamond. In-Game asset. 2d. High contrast. No shadows
dollar. In-Game asset. 2d. High contrast. No shadows
Design a single floating 2D game platform made of levitating crystal shards, connected by glowing magical runes or light energy. No ice or snow. The platform should feel arcane and unique. No background.. In-Game asset. 2d. High contrast. No shadows
rectangle shape jumping platform for a simple 2D game. In-Game asset. 2d. High contrast. No shadows
super mario facing camera. In-Game asset. 2d. High contrast. No shadows
blue transparent cloud. In-Game asset. 2d. High contrast. No shadows
bright transparent cloud. In-Game asset. 2d. High contrast. No shadows
fluffy transparent cloud. In-Game asset. 2d. High contrast. No shadows
orange transparent cloud. In-Game asset. 2d. High contrast. No shadows
grey transparent cloud. In-Game asset. 2d. High contrast. No shadows
star. In-Game asset. 2d. High contrast. No shadows
icy tower background without platforms, just walls. In-Game asset. 2d. High contrast. No shadows
just a start line without any text. In-Game asset. 2d. High contrast. No shadows
stuart little jumping and raised its arms. In-Game asset. 2d. High contrast. No shadows. facing camera
shout
Sound effect
fall
Sound effect
darara
Music
garavel-1
Sound effect
garavel-2
Sound effect
garavel-3
Sound effect
garavel-4
Sound effect
garavel-5
Sound effect
death-1
Sound effect
death-2
Sound effect
opening-sound
Sound effect
opening-music
Music
game-theme-song-1
Music
game-theme-song-2
Music
game-theme-song-3
Music
game-theme-song-4
Music