/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var AirPocket = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('airPocket', {
anchorX: 0.5,
anchorY: 0.5
});
graphics.alpha = 0.3;
self.fallSpeed = 0; // For falling direction
self.update = function () {
// Support falling movement
if (self.fallSpeed > 0) {
self.y += self.fallSpeed;
}
graphics.alpha = 0.3 + Math.sin(LK.ticks * 0.1) * 0.2;
};
return self;
});
var Bird = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('bird', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = Math.random() * 5 + 3;
self.direction = Math.random() > 0.5 ? 1 : -1;
self.speedY = 0; // For falling direction
// Birds spawn from left or right edge and move in parallel lines
self.x = self.direction > 0 ? -100 : 2148; // Start from left or right edge
self.update = function () {
self.x += self.speed * self.direction;
if (self.speedY > 0) {
self.y += self.speedY; // Support falling movement
}
graphics.rotation += 0.1;
// Slight vertical bobbing
graphics.y = Math.sin(LK.ticks * 0.08) * 15;
};
return self;
});
var Cloud = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('cloud', {
anchorX: 0.5,
anchorY: 0.5
});
graphics.alpha = 0.6;
self.speed = Math.random() * 0.5 + 0.2;
self.fallSpeed = 0; // For falling direction support
self.update = function () {
// Support falling movement
if (self.fallSpeed > 0) {
self.y += self.fallSpeed;
}
// Clouds are now stationary horizontally - no horizontal movement
};
return self;
});
var Collectible = Container.expand(function (type) {
var self = Container.call(this);
self.type = type;
var graphics = self.attachAsset(type, {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
if (self.type === 'star') {
graphics.rotation += 0.15;
graphics.scaleX = 1 + Math.sin(LK.ticks * 0.1) * 0.3;
graphics.scaleY = 1 + Math.sin(LK.ticks * 0.1) * 0.3;
} else if (self.type === 'windChime') {
graphics.x = Math.sin(LK.ticks * 0.08) * 10;
}
};
return self;
});
var LifePowerUp = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('heart', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
graphics.rotation += 0.1;
graphics.scaleX = 1 + Math.sin(LK.ticks * 0.12) * 0.2;
graphics.scaleY = 1 + Math.sin(LK.ticks * 0.12) * 0.2;
};
return self;
});
var Lightning = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('lightning', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = Math.random() * 3 + 2;
// Lightning flicker effect
tween(graphics, {
alpha: 0.3
}, {
duration: 100,
easing: tween.easeInOut
});
LK.setTimeout(function () {
tween(graphics, {
alpha: 1
}, {
duration: 50
});
}, 100);
self.update = function () {
self.y += self.speed;
// Lightning sway
graphics.x = Math.sin(LK.ticks * 0.12) * 10;
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var umbrella = self.attachAsset('umbrella', {
anchorX: 0.5,
anchorY: 0.5,
y: -60
});
var character = self.attachAsset('character', {
anchorX: 0.5,
anchorY: 0.5,
y: 20
});
self.fallSpeed = 3;
self.horizontalSpeed = 0;
self.windEffect = 0;
self.maxHorizontalSpeed = 8;
self.update = function () {
self.y += self.fallSpeed;
// Apply horizontal movement with wind effect
self.x += self.horizontalSpeed + self.windEffect;
// Enhanced umbrella wind animation with smooth transitions
var baseRotation = Math.sin(LK.ticks * 0.05) * 0.1;
var windRotation = self.windEffect * 0.02;
var horizontalRotation = self.horizontalSpeed * 0.05;
var targetRotation = baseRotation + windRotation + horizontalRotation;
// Smooth umbrella rotation transition
if (Math.abs(umbrella.rotation - targetRotation) > 0.01) {
tween(umbrella, {
rotation: targetRotation
}, {
duration: 200,
easing: tween.easeOut
});
}
// Wind-affected umbrella bobbing with smooth motion
var targetUmbrellaY = -60 + Math.sin(LK.ticks * 0.08) * 3 + Math.abs(self.windEffect) * 0.5;
if (Math.abs(umbrella.y - targetUmbrellaY) > 1) {
tween(umbrella, {
y: targetUmbrellaY
}, {
duration: 150,
easing: tween.easeOut
});
}
// Enhanced character body physics with wind-responsive motion
var windResistance = Math.abs(self.windEffect) * 0.4;
var umbrellaInfluence = Math.abs(umbrella.rotation) * 2;
var horizontalInfluence = Math.abs(self.horizontalSpeed) * 0.3;
// Body tilting based on combined forces
var targetCharacterRotation = umbrella.rotation * 0.5 + self.windEffect * 0.03 + self.horizontalSpeed * 0.02;
var targetCharacterX = Math.sin(LK.ticks * 0.06) * 2 + self.windEffect * 0.15 + Math.sin(LK.ticks * 0.08) * windResistance;
var targetCharacterY = 20 + Math.sin(LK.ticks * 0.04) * 1.5 + windResistance * 0.8 + umbrellaInfluence * 0.3;
// Wind-affected body leaning with umbrella compensation
if (Math.abs(character.rotation - targetCharacterRotation) > 0.005) {
tween(character, {
rotation: targetCharacterRotation
}, {
duration: 200,
easing: tween.easeInOut
});
}
// Enhanced horizontal body sway with wind resistance
if (Math.abs(character.x - targetCharacterX) > 0.5) {
tween(character, {
x: targetCharacterX
}, {
duration: 180,
easing: tween.easeOut
});
}
// Vertical body motion affected by umbrella physics
if (Math.abs(character.y - targetCharacterY) > 0.8) {
tween(character, {
y: targetCharacterY
}, {
duration: 160,
easing: tween.easeInOut
});
}
// Body deformation from wind pressure and umbrella strain
var targetScaleX = 1 + Math.sin(LK.ticks * 0.03) * 0.02 + windResistance * 0.03 + Math.sin(LK.ticks * 0.1) * horizontalInfluence * 0.02;
var targetScaleY = 1 - windResistance * 0.02 + Math.sin(LK.ticks * 0.05) * 0.015 + umbrellaInfluence * 0.015;
if (Math.abs(character.scaleX - targetScaleX) > 0.01 || Math.abs(character.scaleY - targetScaleY) > 0.01) {
tween(character, {
scaleX: targetScaleX,
scaleY: targetScaleY
}, {
duration: 300,
easing: tween.easeInOut
});
}
// Additional body trembling effect during strong wind
if (Math.abs(self.windEffect) > 3) {
var trembleX = (Math.random() - 0.5) * 2;
var trembleY = (Math.random() - 0.5) * 1.5;
tween(character, {
x: character.x + trembleX,
y: character.y + trembleY
}, {
duration: 50,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(character, {
x: targetCharacterX,
y: targetCharacterY
}, {
duration: 100,
easing: tween.easeOut
});
}
});
}
// Keep player on screen horizontally with smooth boundary correction
if (self.x < 70) {
tween(self, {
x: 70
}, {
duration: 200,
easing: tween.easeOut
});
self.horizontalSpeed = 0;
}
if (self.x > 2048 - 70) {
tween(self, {
x: 2048 - 70
}, {
duration: 200,
easing: tween.easeOut
});
self.horizontalSpeed = 0;
}
};
return self;
});
var Star = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('star', {
anchorX: 0.5,
anchorY: 0.5
});
graphics.tint = 0xff6666;
// Stars spawn diagonally - choose random diagonal direction
var diagonalDirection = Math.random() > 0.5 ? 1 : -1;
self.speedX = diagonalDirection * (Math.random() * 2 + 2);
self.speedY = Math.random() * 2 + 3; // Moves downward in game flow
// Set starting position based on diagonal direction
if (diagonalDirection > 0) {
self.x = -50; // Start from left edge
} else {
self.x = 2098; // Start from right edge
}
self.update = function () {
self.x += self.speedX;
self.y += self.speedY;
graphics.rotation += 0.2;
graphics.scaleX = 1 + Math.sin(LK.ticks * 0.15) * 0.3;
graphics.scaleY = 1 + Math.sin(LK.ticks * 0.15) * 0.3;
};
return self;
});
var WindChime = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('windChime', {
anchorX: 0.5,
anchorY: 0.5
});
self.visible = true;
self.flickerTimer = 0;
self.fallSpeed = 0; // For falling direction
// Wind chimes spawn anywhere on screen and suddenly appear/disappear
self.x = Math.random() * (2048 - 100) + 50;
self.update = function () {
self.flickerTimer++;
// Support falling movement
if (self.fallSpeed > 0) {
self.y += self.fallSpeed;
}
// Sudden appear/disappear effect every second
if (self.flickerTimer % 60 === 0) {
self.visible = !self.visible;
graphics.alpha = self.visible ? 1 : 0;
}
// Gentle swaying when visible
if (self.visible) {
graphics.x = Math.sin(LK.ticks * 0.08) * 8;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87ceeb
});
/****
* Game Code
****/
// Gradient sky effect
game.setBackgroundColor(0x87ceeb);
// Game variables
var player;
var obstacles = [];
var collectibles = [];
var clouds = [];
var lifePowerUps = [];
var windTimer = 0;
var spawnTimer = 0;
var difficultyTimer = 0;
var distanceFallen = 0;
var gameSpeed = 1;
var cameraY = 0;
var targetCameraY = 0;
var playerLives = 3;
var maxLives = 5;
// UI Elements
var distanceTxt = new Text2('Distance: 0m', {
size: 70,
fill: 0xF0F8FF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
distanceTxt.anchor.set(0.5, 0);
distanceTxt.y = 0;
LK.gui.top.addChild(distanceTxt);
// Lives display
var livesContainer = new Container();
var lifeHearts = [];
for (var h = 0; h < maxLives; h++) {
var heart = LK.getAsset('heart', {
anchorX: 0.5,
anchorY: 0.5
});
heart.x = h * 60 - 120;
heart.y = 0;
heart.scaleX = 1.2;
heart.scaleY = 1.5;
livesContainer.addChild(heart);
lifeHearts.push(heart);
}
livesContainer.y = 120;
LK.gui.top.addChild(livesContainer);
// Update lives display function
function updateLivesDisplay() {
for (var i = 0; i < lifeHearts.length; i++) {
if (i < playerLives) {
lifeHearts[i].alpha = 1;
} else {
lifeHearts[i].alpha = 0.3;
}
}
}
updateLivesDisplay();
// Initialize player
player = game.addChild(new Player());
player.x = 2048 / 2;
player.y = 600;
// Create initial clouds
for (var i = 0; i < 8; i++) {
var cloud = game.addChild(new Cloud());
cloud.x = Math.random() * 2048;
cloud.y = Math.random() * 2732;
clouds.push(cloud);
}
// Touch controls
var isDragging = false;
var lastTouchX = 0;
game.down = function (x, y, obj) {
isDragging = true;
lastTouchX = x;
};
game.move = function (x, y, obj) {
if (isDragging) {
var deltaX = x - lastTouchX;
player.horizontalSpeed = Math.max(-player.maxHorizontalSpeed, Math.min(player.maxHorizontalSpeed, deltaX * 0.3));
lastTouchX = x;
}
};
game.up = function (x, y, obj) {
isDragging = false;
// Gradually reduce horizontal speed when not dragging
tween(player, {
horizontalSpeed: 0
}, {
duration: 500,
easing: tween.easeOut
});
};
// Main game loop
game.update = function () {
// Update distance fallen
distanceFallen += player.fallSpeed;
distanceTxt.setText('Distance: ' + Math.floor(distanceFallen / 10) + 'm');
// Camera tracking - follow player with smooth movement
targetCameraY = player.y - 1366; // Keep player in center vertically
var cameraDiff = targetCameraY - cameraY;
cameraY += cameraDiff * 0.08; // Smooth camera movement
game.y = -cameraY;
// Progressive difficulty every 100 meters, easier first 500 meters
var currentDistance = Math.floor(distanceFallen / 10);
var difficultyLevel = Math.floor(currentDistance / 100);
if (currentDistance >= 500) {
// After 500m, increase difficulty more rapidly
gameSpeed = 1 + (difficultyLevel - 5) * 0.2;
player.fallSpeed = Math.min(8, 3 + (difficultyLevel - 5) * 0.3);
} else if (currentDistance >= 100) {
// First 500m: gentle difficulty increase
gameSpeed = 1 + difficultyLevel * 0.05;
player.fallSpeed = Math.min(5, 3 + difficultyLevel * 0.1);
}
// Enhanced wind effect that actively guides the player
windTimer++;
if (windTimer % 150 === 0) {
// Every 2.5 seconds - more frequent wind
var windStrength = (Math.random() - 0.5) * 8 * gameSpeed; // Stronger wind
// Visual wind burst effect on umbrella
var umbrella = player.children[0]; // Get umbrella reference
tween(umbrella, {
scaleX: 1.3 + Math.abs(windStrength) * 0.15,
scaleY: 0.8 - Math.abs(windStrength) * 0.1,
rotation: umbrella.rotation + windStrength * 0.1
}, {
duration: 150,
easing: tween.easeOut
});
// Return umbrella to normal size
LK.setTimeout(function () {
tween(umbrella, {
scaleX: 1,
scaleY: 1
}, {
duration: 600,
easing: tween.easeOut
});
}, 150);
tween(player, {
windEffect: windStrength
}, {
duration: 150
});
LK.setTimeout(function () {
tween(player, {
windEffect: windStrength * 0.3
}, {
duration: 800,
easing: tween.easeOut
});
}, 600);
// Complete wind fade after longer period
LK.setTimeout(function () {
tween(player, {
windEffect: 0
}, {
duration: 400,
easing: tween.easeOut
});
}, 1400);
}
// Continuous cloud spawning - spawn clouds regularly as primary obstacles
spawnTimer++;
var baseSpawnRate = currentDistance < 500 ? 80 : 60; // Much slower spawn for better escape routes
var cloudSpawnRate = Math.max(25, baseSpawnRate - Math.floor(difficultyLevel * 2));
if (spawnTimer % cloudSpawnRate === 0) {
// Calculate spawn position relative to player's current position
var spawnX = Math.random() * (2048 - 200) + 100;
var spawnY;
// 50% chance to spawn from above or below the player
if (Math.random() < 0.5) {
spawnY = player.y - 800 - Math.random() * 400; // Spawn above player
} else {
spawnY = player.y + 800 + Math.random() * 400; // Spawn below player
}
var cloud = game.addChild(new Cloud());
cloud.x = spawnX;
cloud.y = spawnY;
// Support falling direction for clouds
if (Math.random() < 0.3) {
cloud.fallSpeed = Math.random() * 1 + 0.5;
}
obstacles.push(cloud);
}
// Separate life power-up spawning (increased spawn rate)
if (Math.random() < 0.005 * gameSpeed) {
var lifePowerUp = game.addChild(new LifePowerUp());
lifePowerUp.x = Math.random() * (2048 - 200) + 100;
lifePowerUp.y = player.y - Math.random() * 500 - 400;
lifePowerUps.push(lifePowerUp);
}
// Separate collectible spawning (moderate frequency)
if (Math.random() < 0.002 * gameSpeed) {
var collectible = game.addChild(new Collectible('windChime'));
collectible.x = Math.random() * (2048 - 200) + 100;
collectible.y = player.y - Math.random() * 500 - 400;
collectibles.push(collectible);
}
// Independent bird spawning (moderate frequency, parallel lines from screen edges and falling direction)
var birdSpawnChance = currentDistance < 500 ? 0.001 * gameSpeed : 0.003 * gameSpeed;
if (Math.random() < birdSpawnChance) {
var bird = game.addChild(new Bird());
// 50% chance to spawn from above (falling direction) or from edges
if (Math.random() < 0.5) {
bird.x = Math.random() * 2048;
bird.y = player.y - Math.random() * 600 - 400;
bird.speedY = Math.random() * 2 + 1;
} else {
bird.y = player.y - Math.random() * 600 - 200;
}
obstacles.push(bird);
}
// Independent star spawning (moderate frequency, diagonal movement and falling direction)
var starSpawnChance = currentDistance < 500 ? 0.0008 * gameSpeed : 0.0025 * gameSpeed;
if (Math.random() < starSpawnChance) {
var star = game.addChild(new Star());
// 50% chance to spawn from above (falling direction) or diagonally
if (Math.random() < 0.5) {
star.x = Math.random() * 2048;
star.y = player.y - Math.random() * 500 - 400;
star.speedX = (Math.random() - 0.5) * 4;
star.speedY = Math.random() * 2 + 2;
} else {
star.y = player.y - Math.random() * 500 - 300;
}
obstacles.push(star);
}
// Independent lightning spawning (moderate frequency, vertical path from above)
var lightningSpawnChance = currentDistance < 500 ? 0.0005 * gameSpeed : 0.002 * gameSpeed;
if (Math.random() < lightningSpawnChance) {
var lightning = game.addChild(new Lightning());
lightning.x = Math.random() * (2048 - 100) + 50;
lightning.y = player.y - Math.random() * 700 - 400;
obstacles.push(lightning);
}
// Independent wind chime spawning (low frequency, appear/disappear anywhere and falling)
var windChimeSpawnChance = currentDistance < 500 ? 0.0005 * gameSpeed : 0.0015 * gameSpeed;
if (Math.random() < windChimeSpawnChance) {
var windChime = game.addChild(new WindChime());
// 50% chance to spawn from above (falling direction) or statically
if (Math.random() < 0.5) {
windChime.x = Math.random() * 2048;
windChime.y = player.y - Math.random() * 600 - 400;
windChime.fallSpeed = Math.random() * 1.5 + 0.5;
} else {
windChime.y = player.y - Math.random() * 600 - 200;
}
obstacles.push(windChime);
}
// Independent air pocket spawning (low frequency, spawn anywhere and falling direction)
var airPocketSpawnChance = currentDistance < 500 ? 0.0003 * gameSpeed : 0.0012 * gameSpeed;
if (Math.random() < airPocketSpawnChance) {
var airPocket = game.addChild(new AirPocket());
airPocket.x = Math.random() * (2048 - 200) + 100;
// 50% chance to spawn from above (falling direction) or statically
if (Math.random() < 0.5) {
airPocket.y = player.y - Math.random() * 500 - 400;
airPocket.fallSpeed = Math.random() * 1 + 0.3;
} else {
airPocket.y = player.y - Math.random() * 500 - 300;
}
obstacles.push(airPocket);
}
// Update and check obstacles
for (var i = obstacles.length - 1; i >= 0; i--) {
var obstacle = obstacles[i];
if (obstacle.lastY === undefined) obstacle.lastY = obstacle.y;
if (obstacle.lastIntersecting === undefined) obstacle.lastIntersecting = false;
// Remove obstacles that are too far behind the player
if (obstacle.lastY < player.y + 1000 && obstacle.y >= player.y + 1000) {
obstacle.destroy();
obstacles.splice(i, 1);
continue;
}
// Check collision with player
var currentIntersecting = obstacle.intersects(player);
if (!obstacle.lastIntersecting && currentIntersecting) {
// Only lose hearts after first 50 meters
if (currentDistance >= 50) {
playerLives--;
updateLivesDisplay();
LK.getSound('hit').play();
LK.effects.flashScreen(0xff0000, 500);
// Check if game over
if (playerLives <= 0) {
LK.showGameOver();
return;
}
} else {
// Flash screen but no heart loss for first 50 meters
LK.effects.flashScreen(0xffff00, 300);
}
// Remove the obstacle that was hit
obstacle.destroy();
obstacles.splice(i, 1);
continue;
}
obstacle.lastY = obstacle.y;
obstacle.lastIntersecting = currentIntersecting;
}
// Update and check collectibles
for (var j = collectibles.length - 1; j >= 0; j--) {
var collectible = collectibles[j];
if (collectible.lastY === undefined) collectible.lastY = collectible.y;
if (collectible.lastIntersecting === undefined) collectible.lastIntersecting = false;
// Remove collectibles that are too far behind the player
if (collectible.lastY < player.y + 1000 && collectible.y >= player.y + 1000) {
collectible.destroy();
collectibles.splice(j, 1);
continue;
}
// Check collection
var currentIntersecting = collectible.intersects(player);
if (!collectible.lastIntersecting && currentIntersecting) {
LK.getSound('collect').play();
// Visual effect
tween(collectible, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
collectible.destroy();
}
});
collectibles.splice(j, 1);
continue;
}
collectible.lastY = collectible.y;
collectible.lastIntersecting = currentIntersecting;
}
// Update and check life power-ups
for (var l = lifePowerUps.length - 1; l >= 0; l--) {
var lifePowerUp = lifePowerUps[l];
if (lifePowerUp.lastY === undefined) lifePowerUp.lastY = lifePowerUp.y;
if (lifePowerUp.lastIntersecting === undefined) lifePowerUp.lastIntersecting = false;
// Remove life power-ups that are too far behind the player
if (lifePowerUp.lastY < player.y + 1000 && lifePowerUp.y >= player.y + 1000) {
lifePowerUp.destroy();
lifePowerUps.splice(l, 1);
continue;
}
// Check collection
var currentIntersecting = lifePowerUp.intersects(player);
if (!lifePowerUp.lastIntersecting && currentIntersecting) {
if (playerLives < maxLives) {
playerLives++;
updateLivesDisplay();
LK.getSound('collect').play();
// Visual effect
tween(lifePowerUp, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
lifePowerUp.destroy();
}
});
lifePowerUps.splice(l, 1);
continue;
}
}
lifePowerUp.lastY = lifePowerUp.y;
lifePowerUp.lastIntersecting = currentIntersecting;
}
// Check collision with clouds
for (var k = 0; k < clouds.length; k++) {
var cloud = clouds[k];
if (cloud.lastIntersecting === undefined) cloud.lastIntersecting = false;
var currentIntersecting = cloud.intersects(player);
if (!cloud.lastIntersecting && currentIntersecting) {
// Only lose hearts after first 50 meters
if (currentDistance >= 50) {
playerLives--;
updateLivesDisplay();
LK.getSound('hit').play();
LK.effects.flashScreen(0xffffff, 500);
// Check if game over
if (playerLives <= 0) {
LK.showGameOver();
return;
}
} else {
// Flash screen but no heart loss for first 50 meters
LK.effects.flashScreen(0xffff88, 300);
}
}
cloud.lastIntersecting = currentIntersecting;
}
// Remove game over condition for falling off screen - infinite fall game
// Game only ends when hitting obstacles or clouds
};
// Start ambient music
LK.playMusic('ambient'); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var AirPocket = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('airPocket', {
anchorX: 0.5,
anchorY: 0.5
});
graphics.alpha = 0.3;
self.fallSpeed = 0; // For falling direction
self.update = function () {
// Support falling movement
if (self.fallSpeed > 0) {
self.y += self.fallSpeed;
}
graphics.alpha = 0.3 + Math.sin(LK.ticks * 0.1) * 0.2;
};
return self;
});
var Bird = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('bird', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = Math.random() * 5 + 3;
self.direction = Math.random() > 0.5 ? 1 : -1;
self.speedY = 0; // For falling direction
// Birds spawn from left or right edge and move in parallel lines
self.x = self.direction > 0 ? -100 : 2148; // Start from left or right edge
self.update = function () {
self.x += self.speed * self.direction;
if (self.speedY > 0) {
self.y += self.speedY; // Support falling movement
}
graphics.rotation += 0.1;
// Slight vertical bobbing
graphics.y = Math.sin(LK.ticks * 0.08) * 15;
};
return self;
});
var Cloud = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('cloud', {
anchorX: 0.5,
anchorY: 0.5
});
graphics.alpha = 0.6;
self.speed = Math.random() * 0.5 + 0.2;
self.fallSpeed = 0; // For falling direction support
self.update = function () {
// Support falling movement
if (self.fallSpeed > 0) {
self.y += self.fallSpeed;
}
// Clouds are now stationary horizontally - no horizontal movement
};
return self;
});
var Collectible = Container.expand(function (type) {
var self = Container.call(this);
self.type = type;
var graphics = self.attachAsset(type, {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
if (self.type === 'star') {
graphics.rotation += 0.15;
graphics.scaleX = 1 + Math.sin(LK.ticks * 0.1) * 0.3;
graphics.scaleY = 1 + Math.sin(LK.ticks * 0.1) * 0.3;
} else if (self.type === 'windChime') {
graphics.x = Math.sin(LK.ticks * 0.08) * 10;
}
};
return self;
});
var LifePowerUp = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('heart', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
graphics.rotation += 0.1;
graphics.scaleX = 1 + Math.sin(LK.ticks * 0.12) * 0.2;
graphics.scaleY = 1 + Math.sin(LK.ticks * 0.12) * 0.2;
};
return self;
});
var Lightning = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('lightning', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = Math.random() * 3 + 2;
// Lightning flicker effect
tween(graphics, {
alpha: 0.3
}, {
duration: 100,
easing: tween.easeInOut
});
LK.setTimeout(function () {
tween(graphics, {
alpha: 1
}, {
duration: 50
});
}, 100);
self.update = function () {
self.y += self.speed;
// Lightning sway
graphics.x = Math.sin(LK.ticks * 0.12) * 10;
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var umbrella = self.attachAsset('umbrella', {
anchorX: 0.5,
anchorY: 0.5,
y: -60
});
var character = self.attachAsset('character', {
anchorX: 0.5,
anchorY: 0.5,
y: 20
});
self.fallSpeed = 3;
self.horizontalSpeed = 0;
self.windEffect = 0;
self.maxHorizontalSpeed = 8;
self.update = function () {
self.y += self.fallSpeed;
// Apply horizontal movement with wind effect
self.x += self.horizontalSpeed + self.windEffect;
// Enhanced umbrella wind animation with smooth transitions
var baseRotation = Math.sin(LK.ticks * 0.05) * 0.1;
var windRotation = self.windEffect * 0.02;
var horizontalRotation = self.horizontalSpeed * 0.05;
var targetRotation = baseRotation + windRotation + horizontalRotation;
// Smooth umbrella rotation transition
if (Math.abs(umbrella.rotation - targetRotation) > 0.01) {
tween(umbrella, {
rotation: targetRotation
}, {
duration: 200,
easing: tween.easeOut
});
}
// Wind-affected umbrella bobbing with smooth motion
var targetUmbrellaY = -60 + Math.sin(LK.ticks * 0.08) * 3 + Math.abs(self.windEffect) * 0.5;
if (Math.abs(umbrella.y - targetUmbrellaY) > 1) {
tween(umbrella, {
y: targetUmbrellaY
}, {
duration: 150,
easing: tween.easeOut
});
}
// Enhanced character body physics with wind-responsive motion
var windResistance = Math.abs(self.windEffect) * 0.4;
var umbrellaInfluence = Math.abs(umbrella.rotation) * 2;
var horizontalInfluence = Math.abs(self.horizontalSpeed) * 0.3;
// Body tilting based on combined forces
var targetCharacterRotation = umbrella.rotation * 0.5 + self.windEffect * 0.03 + self.horizontalSpeed * 0.02;
var targetCharacterX = Math.sin(LK.ticks * 0.06) * 2 + self.windEffect * 0.15 + Math.sin(LK.ticks * 0.08) * windResistance;
var targetCharacterY = 20 + Math.sin(LK.ticks * 0.04) * 1.5 + windResistance * 0.8 + umbrellaInfluence * 0.3;
// Wind-affected body leaning with umbrella compensation
if (Math.abs(character.rotation - targetCharacterRotation) > 0.005) {
tween(character, {
rotation: targetCharacterRotation
}, {
duration: 200,
easing: tween.easeInOut
});
}
// Enhanced horizontal body sway with wind resistance
if (Math.abs(character.x - targetCharacterX) > 0.5) {
tween(character, {
x: targetCharacterX
}, {
duration: 180,
easing: tween.easeOut
});
}
// Vertical body motion affected by umbrella physics
if (Math.abs(character.y - targetCharacterY) > 0.8) {
tween(character, {
y: targetCharacterY
}, {
duration: 160,
easing: tween.easeInOut
});
}
// Body deformation from wind pressure and umbrella strain
var targetScaleX = 1 + Math.sin(LK.ticks * 0.03) * 0.02 + windResistance * 0.03 + Math.sin(LK.ticks * 0.1) * horizontalInfluence * 0.02;
var targetScaleY = 1 - windResistance * 0.02 + Math.sin(LK.ticks * 0.05) * 0.015 + umbrellaInfluence * 0.015;
if (Math.abs(character.scaleX - targetScaleX) > 0.01 || Math.abs(character.scaleY - targetScaleY) > 0.01) {
tween(character, {
scaleX: targetScaleX,
scaleY: targetScaleY
}, {
duration: 300,
easing: tween.easeInOut
});
}
// Additional body trembling effect during strong wind
if (Math.abs(self.windEffect) > 3) {
var trembleX = (Math.random() - 0.5) * 2;
var trembleY = (Math.random() - 0.5) * 1.5;
tween(character, {
x: character.x + trembleX,
y: character.y + trembleY
}, {
duration: 50,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(character, {
x: targetCharacterX,
y: targetCharacterY
}, {
duration: 100,
easing: tween.easeOut
});
}
});
}
// Keep player on screen horizontally with smooth boundary correction
if (self.x < 70) {
tween(self, {
x: 70
}, {
duration: 200,
easing: tween.easeOut
});
self.horizontalSpeed = 0;
}
if (self.x > 2048 - 70) {
tween(self, {
x: 2048 - 70
}, {
duration: 200,
easing: tween.easeOut
});
self.horizontalSpeed = 0;
}
};
return self;
});
var Star = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('star', {
anchorX: 0.5,
anchorY: 0.5
});
graphics.tint = 0xff6666;
// Stars spawn diagonally - choose random diagonal direction
var diagonalDirection = Math.random() > 0.5 ? 1 : -1;
self.speedX = diagonalDirection * (Math.random() * 2 + 2);
self.speedY = Math.random() * 2 + 3; // Moves downward in game flow
// Set starting position based on diagonal direction
if (diagonalDirection > 0) {
self.x = -50; // Start from left edge
} else {
self.x = 2098; // Start from right edge
}
self.update = function () {
self.x += self.speedX;
self.y += self.speedY;
graphics.rotation += 0.2;
graphics.scaleX = 1 + Math.sin(LK.ticks * 0.15) * 0.3;
graphics.scaleY = 1 + Math.sin(LK.ticks * 0.15) * 0.3;
};
return self;
});
var WindChime = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('windChime', {
anchorX: 0.5,
anchorY: 0.5
});
self.visible = true;
self.flickerTimer = 0;
self.fallSpeed = 0; // For falling direction
// Wind chimes spawn anywhere on screen and suddenly appear/disappear
self.x = Math.random() * (2048 - 100) + 50;
self.update = function () {
self.flickerTimer++;
// Support falling movement
if (self.fallSpeed > 0) {
self.y += self.fallSpeed;
}
// Sudden appear/disappear effect every second
if (self.flickerTimer % 60 === 0) {
self.visible = !self.visible;
graphics.alpha = self.visible ? 1 : 0;
}
// Gentle swaying when visible
if (self.visible) {
graphics.x = Math.sin(LK.ticks * 0.08) * 8;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87ceeb
});
/****
* Game Code
****/
// Gradient sky effect
game.setBackgroundColor(0x87ceeb);
// Game variables
var player;
var obstacles = [];
var collectibles = [];
var clouds = [];
var lifePowerUps = [];
var windTimer = 0;
var spawnTimer = 0;
var difficultyTimer = 0;
var distanceFallen = 0;
var gameSpeed = 1;
var cameraY = 0;
var targetCameraY = 0;
var playerLives = 3;
var maxLives = 5;
// UI Elements
var distanceTxt = new Text2('Distance: 0m', {
size: 70,
fill: 0xF0F8FF,
font: "'GillSans-Bold',Impact,'Arial Black',Tahoma"
});
distanceTxt.anchor.set(0.5, 0);
distanceTxt.y = 0;
LK.gui.top.addChild(distanceTxt);
// Lives display
var livesContainer = new Container();
var lifeHearts = [];
for (var h = 0; h < maxLives; h++) {
var heart = LK.getAsset('heart', {
anchorX: 0.5,
anchorY: 0.5
});
heart.x = h * 60 - 120;
heart.y = 0;
heart.scaleX = 1.2;
heart.scaleY = 1.5;
livesContainer.addChild(heart);
lifeHearts.push(heart);
}
livesContainer.y = 120;
LK.gui.top.addChild(livesContainer);
// Update lives display function
function updateLivesDisplay() {
for (var i = 0; i < lifeHearts.length; i++) {
if (i < playerLives) {
lifeHearts[i].alpha = 1;
} else {
lifeHearts[i].alpha = 0.3;
}
}
}
updateLivesDisplay();
// Initialize player
player = game.addChild(new Player());
player.x = 2048 / 2;
player.y = 600;
// Create initial clouds
for (var i = 0; i < 8; i++) {
var cloud = game.addChild(new Cloud());
cloud.x = Math.random() * 2048;
cloud.y = Math.random() * 2732;
clouds.push(cloud);
}
// Touch controls
var isDragging = false;
var lastTouchX = 0;
game.down = function (x, y, obj) {
isDragging = true;
lastTouchX = x;
};
game.move = function (x, y, obj) {
if (isDragging) {
var deltaX = x - lastTouchX;
player.horizontalSpeed = Math.max(-player.maxHorizontalSpeed, Math.min(player.maxHorizontalSpeed, deltaX * 0.3));
lastTouchX = x;
}
};
game.up = function (x, y, obj) {
isDragging = false;
// Gradually reduce horizontal speed when not dragging
tween(player, {
horizontalSpeed: 0
}, {
duration: 500,
easing: tween.easeOut
});
};
// Main game loop
game.update = function () {
// Update distance fallen
distanceFallen += player.fallSpeed;
distanceTxt.setText('Distance: ' + Math.floor(distanceFallen / 10) + 'm');
// Camera tracking - follow player with smooth movement
targetCameraY = player.y - 1366; // Keep player in center vertically
var cameraDiff = targetCameraY - cameraY;
cameraY += cameraDiff * 0.08; // Smooth camera movement
game.y = -cameraY;
// Progressive difficulty every 100 meters, easier first 500 meters
var currentDistance = Math.floor(distanceFallen / 10);
var difficultyLevel = Math.floor(currentDistance / 100);
if (currentDistance >= 500) {
// After 500m, increase difficulty more rapidly
gameSpeed = 1 + (difficultyLevel - 5) * 0.2;
player.fallSpeed = Math.min(8, 3 + (difficultyLevel - 5) * 0.3);
} else if (currentDistance >= 100) {
// First 500m: gentle difficulty increase
gameSpeed = 1 + difficultyLevel * 0.05;
player.fallSpeed = Math.min(5, 3 + difficultyLevel * 0.1);
}
// Enhanced wind effect that actively guides the player
windTimer++;
if (windTimer % 150 === 0) {
// Every 2.5 seconds - more frequent wind
var windStrength = (Math.random() - 0.5) * 8 * gameSpeed; // Stronger wind
// Visual wind burst effect on umbrella
var umbrella = player.children[0]; // Get umbrella reference
tween(umbrella, {
scaleX: 1.3 + Math.abs(windStrength) * 0.15,
scaleY: 0.8 - Math.abs(windStrength) * 0.1,
rotation: umbrella.rotation + windStrength * 0.1
}, {
duration: 150,
easing: tween.easeOut
});
// Return umbrella to normal size
LK.setTimeout(function () {
tween(umbrella, {
scaleX: 1,
scaleY: 1
}, {
duration: 600,
easing: tween.easeOut
});
}, 150);
tween(player, {
windEffect: windStrength
}, {
duration: 150
});
LK.setTimeout(function () {
tween(player, {
windEffect: windStrength * 0.3
}, {
duration: 800,
easing: tween.easeOut
});
}, 600);
// Complete wind fade after longer period
LK.setTimeout(function () {
tween(player, {
windEffect: 0
}, {
duration: 400,
easing: tween.easeOut
});
}, 1400);
}
// Continuous cloud spawning - spawn clouds regularly as primary obstacles
spawnTimer++;
var baseSpawnRate = currentDistance < 500 ? 80 : 60; // Much slower spawn for better escape routes
var cloudSpawnRate = Math.max(25, baseSpawnRate - Math.floor(difficultyLevel * 2));
if (spawnTimer % cloudSpawnRate === 0) {
// Calculate spawn position relative to player's current position
var spawnX = Math.random() * (2048 - 200) + 100;
var spawnY;
// 50% chance to spawn from above or below the player
if (Math.random() < 0.5) {
spawnY = player.y - 800 - Math.random() * 400; // Spawn above player
} else {
spawnY = player.y + 800 + Math.random() * 400; // Spawn below player
}
var cloud = game.addChild(new Cloud());
cloud.x = spawnX;
cloud.y = spawnY;
// Support falling direction for clouds
if (Math.random() < 0.3) {
cloud.fallSpeed = Math.random() * 1 + 0.5;
}
obstacles.push(cloud);
}
// Separate life power-up spawning (increased spawn rate)
if (Math.random() < 0.005 * gameSpeed) {
var lifePowerUp = game.addChild(new LifePowerUp());
lifePowerUp.x = Math.random() * (2048 - 200) + 100;
lifePowerUp.y = player.y - Math.random() * 500 - 400;
lifePowerUps.push(lifePowerUp);
}
// Separate collectible spawning (moderate frequency)
if (Math.random() < 0.002 * gameSpeed) {
var collectible = game.addChild(new Collectible('windChime'));
collectible.x = Math.random() * (2048 - 200) + 100;
collectible.y = player.y - Math.random() * 500 - 400;
collectibles.push(collectible);
}
// Independent bird spawning (moderate frequency, parallel lines from screen edges and falling direction)
var birdSpawnChance = currentDistance < 500 ? 0.001 * gameSpeed : 0.003 * gameSpeed;
if (Math.random() < birdSpawnChance) {
var bird = game.addChild(new Bird());
// 50% chance to spawn from above (falling direction) or from edges
if (Math.random() < 0.5) {
bird.x = Math.random() * 2048;
bird.y = player.y - Math.random() * 600 - 400;
bird.speedY = Math.random() * 2 + 1;
} else {
bird.y = player.y - Math.random() * 600 - 200;
}
obstacles.push(bird);
}
// Independent star spawning (moderate frequency, diagonal movement and falling direction)
var starSpawnChance = currentDistance < 500 ? 0.0008 * gameSpeed : 0.0025 * gameSpeed;
if (Math.random() < starSpawnChance) {
var star = game.addChild(new Star());
// 50% chance to spawn from above (falling direction) or diagonally
if (Math.random() < 0.5) {
star.x = Math.random() * 2048;
star.y = player.y - Math.random() * 500 - 400;
star.speedX = (Math.random() - 0.5) * 4;
star.speedY = Math.random() * 2 + 2;
} else {
star.y = player.y - Math.random() * 500 - 300;
}
obstacles.push(star);
}
// Independent lightning spawning (moderate frequency, vertical path from above)
var lightningSpawnChance = currentDistance < 500 ? 0.0005 * gameSpeed : 0.002 * gameSpeed;
if (Math.random() < lightningSpawnChance) {
var lightning = game.addChild(new Lightning());
lightning.x = Math.random() * (2048 - 100) + 50;
lightning.y = player.y - Math.random() * 700 - 400;
obstacles.push(lightning);
}
// Independent wind chime spawning (low frequency, appear/disappear anywhere and falling)
var windChimeSpawnChance = currentDistance < 500 ? 0.0005 * gameSpeed : 0.0015 * gameSpeed;
if (Math.random() < windChimeSpawnChance) {
var windChime = game.addChild(new WindChime());
// 50% chance to spawn from above (falling direction) or statically
if (Math.random() < 0.5) {
windChime.x = Math.random() * 2048;
windChime.y = player.y - Math.random() * 600 - 400;
windChime.fallSpeed = Math.random() * 1.5 + 0.5;
} else {
windChime.y = player.y - Math.random() * 600 - 200;
}
obstacles.push(windChime);
}
// Independent air pocket spawning (low frequency, spawn anywhere and falling direction)
var airPocketSpawnChance = currentDistance < 500 ? 0.0003 * gameSpeed : 0.0012 * gameSpeed;
if (Math.random() < airPocketSpawnChance) {
var airPocket = game.addChild(new AirPocket());
airPocket.x = Math.random() * (2048 - 200) + 100;
// 50% chance to spawn from above (falling direction) or statically
if (Math.random() < 0.5) {
airPocket.y = player.y - Math.random() * 500 - 400;
airPocket.fallSpeed = Math.random() * 1 + 0.3;
} else {
airPocket.y = player.y - Math.random() * 500 - 300;
}
obstacles.push(airPocket);
}
// Update and check obstacles
for (var i = obstacles.length - 1; i >= 0; i--) {
var obstacle = obstacles[i];
if (obstacle.lastY === undefined) obstacle.lastY = obstacle.y;
if (obstacle.lastIntersecting === undefined) obstacle.lastIntersecting = false;
// Remove obstacles that are too far behind the player
if (obstacle.lastY < player.y + 1000 && obstacle.y >= player.y + 1000) {
obstacle.destroy();
obstacles.splice(i, 1);
continue;
}
// Check collision with player
var currentIntersecting = obstacle.intersects(player);
if (!obstacle.lastIntersecting && currentIntersecting) {
// Only lose hearts after first 50 meters
if (currentDistance >= 50) {
playerLives--;
updateLivesDisplay();
LK.getSound('hit').play();
LK.effects.flashScreen(0xff0000, 500);
// Check if game over
if (playerLives <= 0) {
LK.showGameOver();
return;
}
} else {
// Flash screen but no heart loss for first 50 meters
LK.effects.flashScreen(0xffff00, 300);
}
// Remove the obstacle that was hit
obstacle.destroy();
obstacles.splice(i, 1);
continue;
}
obstacle.lastY = obstacle.y;
obstacle.lastIntersecting = currentIntersecting;
}
// Update and check collectibles
for (var j = collectibles.length - 1; j >= 0; j--) {
var collectible = collectibles[j];
if (collectible.lastY === undefined) collectible.lastY = collectible.y;
if (collectible.lastIntersecting === undefined) collectible.lastIntersecting = false;
// Remove collectibles that are too far behind the player
if (collectible.lastY < player.y + 1000 && collectible.y >= player.y + 1000) {
collectible.destroy();
collectibles.splice(j, 1);
continue;
}
// Check collection
var currentIntersecting = collectible.intersects(player);
if (!collectible.lastIntersecting && currentIntersecting) {
LK.getSound('collect').play();
// Visual effect
tween(collectible, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
collectible.destroy();
}
});
collectibles.splice(j, 1);
continue;
}
collectible.lastY = collectible.y;
collectible.lastIntersecting = currentIntersecting;
}
// Update and check life power-ups
for (var l = lifePowerUps.length - 1; l >= 0; l--) {
var lifePowerUp = lifePowerUps[l];
if (lifePowerUp.lastY === undefined) lifePowerUp.lastY = lifePowerUp.y;
if (lifePowerUp.lastIntersecting === undefined) lifePowerUp.lastIntersecting = false;
// Remove life power-ups that are too far behind the player
if (lifePowerUp.lastY < player.y + 1000 && lifePowerUp.y >= player.y + 1000) {
lifePowerUp.destroy();
lifePowerUps.splice(l, 1);
continue;
}
// Check collection
var currentIntersecting = lifePowerUp.intersects(player);
if (!lifePowerUp.lastIntersecting && currentIntersecting) {
if (playerLives < maxLives) {
playerLives++;
updateLivesDisplay();
LK.getSound('collect').play();
// Visual effect
tween(lifePowerUp, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
lifePowerUp.destroy();
}
});
lifePowerUps.splice(l, 1);
continue;
}
}
lifePowerUp.lastY = lifePowerUp.y;
lifePowerUp.lastIntersecting = currentIntersecting;
}
// Check collision with clouds
for (var k = 0; k < clouds.length; k++) {
var cloud = clouds[k];
if (cloud.lastIntersecting === undefined) cloud.lastIntersecting = false;
var currentIntersecting = cloud.intersects(player);
if (!cloud.lastIntersecting && currentIntersecting) {
// Only lose hearts after first 50 meters
if (currentDistance >= 50) {
playerLives--;
updateLivesDisplay();
LK.getSound('hit').play();
LK.effects.flashScreen(0xffffff, 500);
// Check if game over
if (playerLives <= 0) {
LK.showGameOver();
return;
}
} else {
// Flash screen but no heart loss for first 50 meters
LK.effects.flashScreen(0xffff88, 300);
}
}
cloud.lastIntersecting = currentIntersecting;
}
// Remove game over condition for falling off screen - infinite fall game
// Game only ends when hitting obstacles or clouds
};
// Start ambient music
LK.playMusic('ambient');