/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var AirCube = Container.expand(function () {
var self = Container.call(this);
// Create floating cube obstacle
var cubeGraphics = self.attachAsset('airCube', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
// No shadow for air cube
self.speed = 0;
self.passed = false;
self.floatOffset = Math.random() * Math.PI * 2;
self.baseY = 0; // Will be set when spawned
self.update = function () {
self.x -= self.speed;
// Gentle floating animation
self.y = self.baseY + Math.sin(LK.ticks * 0.04 + self.floatOffset) * 8;
// Subtle rotation
cubeGraphics.rotation = Math.sin(LK.ticks * 0.03) * 0.1;
// No shadow updates needed
};
return self;
});
var BackgroundElement = Container.expand(function () {
var self = Container.call(this);
// Create different types of background elements with all 10 house and 10 tree varieties
var elementTypes = ['house1', 'house2', 'house3', 'house4', 'house5', 'house6', 'house7', 'house8', 'house9', 'house10', 'tree', 'tree2', 'tree3', 'tree4', 'tree5', 'tree6', 'tree7', 'tree8', 'tree9', 'tree10'];
var elementType = elementTypes[Math.floor(Math.random() * elementTypes.length)];
// Create depth-based layering for background elements
var isBackground = Math.random() < 0.5; // 50% chance to be background (far)
// Determine element size and speed based on depth
var elementScale;
var speed;
if (isBackground) {
// Far background elements - smaller, slower, full opacity
elementScale = 0.4 + Math.random() * 0.3; // 0.4 to 0.7
speed = 0.8 + Math.random() * 0.7; // 0.8 to 1.5
} else {
// Near background elements - larger, faster, full opacity
elementScale = 0.8 + Math.random() * 0.4; // 0.8 to 1.2
speed = 1.5 + Math.random() * 1.0; // 1.5 to 2.5
}
// Create background element graphics with full opacity
var elementGraphics = self.attachAsset(elementType, {
anchorX: 0.5,
anchorY: 1.0,
// Anchor at bottom for ground placement
scaleX: elementScale,
scaleY: elementScale,
alpha: 1.0
});
// Apply initial tint based on current theme
var baseTint = 0xffffff;
if (currentTheme === 'night') {
baseTint = 0x4d4d4d; // 70% darkness for night
} else {
baseTint = 0xd9d9d9; // 85% brightness for day (clearer houses)
}
elementGraphics.tint = baseTint;
// Store reference to graphics for tint updates
self.elementGraphics = elementGraphics;
self.speed = speed;
self.elementType = elementType;
self.elementScale = elementScale;
self.update = function () {
self.x -= self.speed;
};
return self;
});
var Cloud = Container.expand(function () {
var self = Container.call(this);
// Choose cloud type (0, 1, or 2)
var cloudType = Math.floor(Math.random() * 3);
// Create depth-based layering for clouds
var isBackground = Math.random() < 0.4; // 40% chance to be background (far)
var isMidground = !isBackground && Math.random() < 0.6; // 60% of remaining are midground
// Determine cloud size, alpha, and speed based on depth
var cloudScale;
var alpha;
var speed;
if (isBackground) {
// Far clouds - smallest, slowest, most transparent
cloudScale = 0.3 + Math.random() * 0.2; // 0.3 to 0.5
alpha = 0.3 + Math.random() * 0.2; // 0.3 to 0.5
speed = 0.5 + Math.random() * 0.5; // 0.5 to 1.0
} else if (isMidground) {
// Mid clouds - medium size, speed, and alpha
cloudScale = 0.6 + Math.random() * 0.3; // 0.6 to 0.9
alpha = 0.5 + Math.random() * 0.2; // 0.5 to 0.7
speed = 1.2 + Math.random() * 0.6; // 1.2 to 1.8
} else {
// Near clouds - largest, fastest, most opaque
cloudScale = 1.0 + Math.random() * 0.4; // 1.0 to 1.4
alpha = 0.7 + Math.random() * 0.2; // 0.7 to 0.9
speed = 2.0 + Math.random() * 1.0; // 2.0 to 3.0
}
// Choose cloud asset based on type
var cloudAssetId;
if (cloudType === 0) {
cloudAssetId = 'cloud1';
} else if (cloudType === 1) {
cloudAssetId = 'cloud2';
} else {
cloudAssetId = 'cloud3';
}
// Create single cloud image with proper scaling
var cloudGraphics = self.attachAsset(cloudAssetId, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: cloudScale,
scaleY: cloudScale,
alpha: alpha
});
// Apply slight tint variation for more natural look
var tintVariations = [0xffffff, 0xf8f8ff, 0xf0f8ff, 0xfafaff];
cloudGraphics.tint = tintVariations[Math.floor(Math.random() * tintVariations.length)];
self.speed = speed;
self.cloudScale = cloudScale;
self.cloudType = cloudType;
self.update = function () {
self.x -= self.speed;
};
return self;
});
var Fluffball = Container.expand(function () {
var self = Container.call(this);
var fluffballGraphics = self.attachAsset('fluffball', {
anchorX: 0.5,
anchorY: 0.5
});
// No shadow for character
self.velocityY = 0;
self.isJumping = false;
self.isDucking = false;
self.groundY = 0;
self.jumpPower = -35;
self.gravity = 0.8;
self.duckScale = 0.6;
self.normalScale = 1.0;
self.canDoubleJump = false;
self.hasDoubleJumped = false;
self.blinkTimer = 0;
self.isBlinking = false;
self.nextBlinkTime = 180; // Blink every 3 seconds (180 ticks at 60fps)
self.jump = function () {
if (!self.isJumping) {
self.velocityY = self.jumpPower;
self.isJumping = true;
self.canDoubleJump = true;
self.hasDoubleJumped = false;
LK.getSound('jump').play();
// Rotate backward during jump
tween(fluffballGraphics, {
rotation: -0.5
}, {
duration: 200,
easing: tween.easeOut
});
} else if (self.canDoubleJump && !self.hasDoubleJumped) {
// Double jump
self.velocityY = self.jumpPower * 0.8; // Slightly weaker second jump
self.hasDoubleJumped = true;
self.canDoubleJump = false;
LK.getSound('jump').play();
// Extra rotation for double jump
tween(fluffballGraphics, {
rotation: -0.8
}, {
duration: 150,
easing: tween.easeOut
});
}
};
self.startDuck = function () {
if (!self.isJumping) {
self.isDucking = true;
tween(fluffballGraphics, {
scaleY: self.duckScale
}, {
duration: 100
});
}
};
self.stopDuck = function () {
self.isDucking = false;
tween(fluffballGraphics, {
scaleY: self.normalScale
}, {
duration: 100
});
};
self.update = function () {
if (self.isJumping) {
self.velocityY += self.gravity;
self.y += self.velocityY;
// Rotate forward when falling (positive velocity)
if (self.velocityY > 5) {
tween(fluffballGraphics, {
rotation: 0.3
}, {
duration: 150,
easing: tween.easeIn
});
}
if (self.y >= self.groundY) {
self.y = self.groundY;
self.velocityY = 0;
self.isJumping = false;
self.canDoubleJump = false;
self.hasDoubleJumped = false;
// Return to original rotation when landing
tween(fluffballGraphics, {
rotation: 0
}, {
duration: 200,
easing: tween.easeOut
});
}
} else {
// Add subtle swaying animation when walking (not jumping)
var swayAmount = Math.sin(LK.ticks * 0.15) * 0.08; // Very subtle sway
fluffballGraphics.rotation = swayAmount;
// Add slight vertical bob while walking
var bobAmount = Math.sin(LK.ticks * 0.2) * 2; // Small vertical movement
fluffballGraphics.y = bobAmount;
}
// Blinking animation
self.blinkTimer++;
if (self.blinkTimer >= self.nextBlinkTime && !self.isBlinking) {
self.isBlinking = true;
self.blinkTimer = 0;
self.nextBlinkTime = 180; // Reset timer for next blink (3 seconds)
// Remove current graphics and add blinking graphics
self.removeChild(fluffballGraphics);
fluffballGraphics = self.attachAsset('fluffballBlink', {
anchorX: 0.5,
anchorY: 0.5
});
// Switch back after blink duration
LK.setTimeout(function () {
if (self.isBlinking) {
// Remove blinking graphics and restore normal graphics
self.removeChild(fluffballGraphics);
fluffballGraphics = self.attachAsset('fluffball', {
anchorX: 0.5,
anchorY: 0.5
});
self.isBlinking = false;
}
}, 250); // Blink duration of 250ms (0.25 seconds)
}
// No shadow updates needed
// Bounce animation
if (!self.isJumping && LK.ticks % 30 == 0) {
tween(fluffballGraphics, {
scaleY: 1.1
}, {
duration: 150,
easing: tween.easeOut
});
tween(fluffballGraphics, {
scaleY: 1.0
}, {
duration: 150,
easing: tween.easeIn
});
}
};
return self;
});
var GummyBee = Container.expand(function () {
var self = Container.call(this);
var beeGraphics = self.attachAsset('gummyBee', {
anchorX: 0.5,
anchorY: 0.5
});
// No shadow for bee
self.speed = 0;
self.passed = false;
self.bobOffset = Math.random() * Math.PI * 2;
self.baseY = 0; // Will be set when spawned
self.verticalRange = 25 + Math.random() * 30; // Reduced vertical movement range
self.verticalSpeed = 0.08 + Math.random() * 0.05; // Variable vertical speed
self.lastY = 0;
self.update = function () {
self.x -= self.speed;
// Initialize lastY if not set
if (self.lastY === undefined) self.lastY = self.y;
// Varied flight pattern - some bees fly high (requiring jumps), others fly low (safe at ground)
var verticalOffset = Math.sin(LK.ticks * self.verticalSpeed + self.bobOffset) * self.verticalRange;
self.y = self.baseY + verticalOffset;
// Wing rotation animation
beeGraphics.rotation = Math.sin(LK.ticks * 0.1) * 0.2;
// Scale animation for wing flapping
beeGraphics.scaleY = 1.0 + Math.sin(LK.ticks * 0.3) * 0.1;
// No shadow updates needed
// Update lastY
self.lastY = self.y;
};
return self;
});
var JellyObstacle = Container.expand(function () {
var self = Container.call(this);
var jellyGraphics = self.attachAsset('jellyObstacle', {
anchorX: 0.5,
anchorY: 1.0
});
// No shadow for jelly obstacle
self.speed = 0;
self.passed = false;
self.update = function () {
self.x -= self.speed;
// Wobble animation
jellyGraphics.scaleX = 1 + Math.sin(LK.ticks * 0.1) * 0.1;
jellyGraphics.scaleY = 1 + Math.cos(LK.ticks * 0.08) * 0.05;
// No shadow updates needed
};
return self;
});
var ShadowBat = Container.expand(function () {
var self = Container.call(this);
var batGraphics = self.attachAsset('shadowBat', {
anchorX: 0.5,
anchorY: 0.5
});
// No shadow for bat
self.speed = 0;
self.passed = false;
self.zigzagDirection = 1; // 1 for up, -1 for down
self.zigzagSpeed = 6; // Increased vertical movement speed for faster zigzag
self.zigzagRange = 150; // How far up/down to move
self.startY = 0; // Starting Y position
self.lastY = 0;
self.update = function () {
self.x -= self.speed;
// Initialize lastY if not set
if (self.lastY === undefined) self.lastY = self.y;
// Initialize startY if not set
if (self.startY === 0) self.startY = self.y;
// Enhanced zigzag movement - goes much closer to ground with faster movement
self.y += self.zigzagDirection * self.zigzagSpeed;
// Calculate ground proximity - bat can get within 120px of ground
var maxGroundApproach = self.groundY - 120;
// Change direction when reaching limits - goes closer to ground now
if (self.y <= self.startY - self.zigzagRange) {
self.zigzagDirection = 1; // Change to downward
} else if (self.y >= maxGroundApproach) {
self.zigzagDirection = -1; // Change to upward - return to original position
}
// More pronounced wing animation for faster zigzag effect
batGraphics.rotation = Math.sin(LK.ticks * 0.4) * 0.15; // More visible rotation for zigzag effect
// No shadow updates needed
// Update lastY
self.lastY = self.y;
};
return self;
});
var SparkleGem = Container.expand(function () {
var self = Container.call(this);
var gemGraphics = self.attachAsset('sparkleGem', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0;
self.collected = false;
self.update = function () {
self.x -= self.speed;
// Sparkle animation
gemGraphics.rotation += 0.1;
gemGraphics.scaleX = 1 + Math.sin(LK.ticks * 0.2) * 0.3;
gemGraphics.scaleY = 1 + Math.cos(LK.ticks * 0.2) * 0.3;
};
return self;
});
var Star = Container.expand(function () {
var self = Container.call(this);
var starGraphics = self.attachAsset('star', {
anchorX: 0.5,
anchorY: 0.5
});
self.twinkleSpeed = 0.02 + Math.random() * 0.03;
self.twinkleOffset = Math.random() * Math.PI * 2;
self.minAlpha = 0.3;
self.maxAlpha = 1.0;
self.minScale = 0.3;
self.maxScale = 1.5;
self.scaleSpeed = 0.01 + Math.random() * 0.02;
self.scaleOffset = Math.random() * Math.PI * 2;
self.disappearTimer = Math.random() * 600 + 300; // Random time between 5-15 seconds
self.isDisappearing = false;
self.disappearDuration = 0;
self.reappearDelay = 0;
self.update = function () {
// Create twinkling effect with random timing
var twinkle = Math.sin(LK.ticks * self.twinkleSpeed + self.twinkleOffset);
starGraphics.alpha = self.minAlpha + (self.maxAlpha - self.minAlpha) * (twinkle + 1) / 2;
// Dynamic scale changes
var scaleVariation = Math.sin(LK.ticks * self.scaleSpeed + self.scaleOffset);
var targetScale = self.minScale + (self.maxScale - self.minScale) * (scaleVariation + 1) / 2;
starGraphics.scaleX = targetScale;
starGraphics.scaleY = targetScale;
// Disappear and reappear cycle
self.disappearTimer--;
if (self.disappearTimer <= 0 && !self.isDisappearing) {
self.isDisappearing = true;
self.disappearDuration = Math.random() * 120 + 60; // Disappear for 1-3 seconds
self.reappearDelay = Math.random() * 300 + 180; // Reappear after 3-8 seconds
// Fade out
tween(starGraphics, {
alpha: 0,
scaleX: 0,
scaleY: 0
}, {
duration: 500 + Math.random() * 1000,
easing: tween.easeOut
});
}
if (self.isDisappearing) {
self.disappearDuration--;
if (self.disappearDuration <= 0) {
self.reappearDelay--;
if (self.reappearDelay <= 0) {
// Reset star state and fade back in
self.isDisappearing = false;
self.disappearTimer = Math.random() * 900 + 600; // Next disappear cycle
// Fade in with new random properties
self.twinkleSpeed = 0.02 + Math.random() * 0.03;
self.scaleSpeed = 0.01 + Math.random() * 0.02;
tween(starGraphics, {
alpha: self.maxAlpha,
scaleX: 1,
scaleY: 1
}, {
duration: 800 + Math.random() * 1200,
easing: tween.easeIn
});
}
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87ceeb
});
/****
* Game Code
****/
// Game variables
// Note: LK engine doesn't support video assets, using placeholder shape instead
var gameSpeed = 6;
var speedIncrement = 0.002;
var maxSpeed = 12;
var isGameRunning = true;
var perfectPassBonus = 10;
var gemValue = 5;
// Background variables
var currentTheme = 'day';
var isTransitioning = false;
var stars = [];
var starsContainer;
// Background setup
var dayBg = game.addChild(LK.getAsset('dayBackground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
}));
var nightBg = game.addChild(LK.getAsset('nightBackground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
alpha: 0
}));
// Ground setup
var groundY = 2732 - 500;
var ground = game.addChild(LK.getAsset('ground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: groundY
}));
// Underground brown rectangle
var underground = game.addChild(LK.getAsset('underground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: groundY + 200
}));
// Create stars container for night theme
starsContainer = game.addChild(new Container());
starsContainer.alpha = 0;
// Generate twinkling stars
for (var i = 0; i < 150; i++) {
var star = starsContainer.addChild(new Star());
star.x = Math.random() * 2048;
star.y = Math.random() * (2732 * 0.6); // Stars only in upper 60% of screen
stars.push(star);
}
// Create clouds container for day theme (before player)
var cloudsContainer = game.addChild(new Container());
// Create background elements container (behind character)
var backgroundElementsContainer = game.addChild(new Container());
// Player setup
var fluffball = game.addChild(new Fluffball());
fluffball.scaleX = 1.2;
fluffball.scaleY = 1.2;
fluffball.x = 300;
fluffball.y = groundY - 80;
fluffball.groundY = groundY - 80;
// Arrays for game objects
var jellyObstacles = [];
var gummyBees = [];
var sparkleGems = [];
var shadowBats = [];
var airCubes = [];
var clouds = [];
var backgroundElements = [];
// Timers for spawning
var obstacleSpawnTimer = 0;
var gemSpawnTimer = 0;
// Input tracking
var isHolding = false;
var lastClickTime = 0;
var doubleClickDelay = 300; // Maximum time between clicks for double click (ms)
// Score display
var scoreTxt = new Text2('0', {
size: 80,
fill: 0xFFFFFF,
stroke: 0x000000,
strokeThickness: 8,
font: "'Arial Black', 'Helvetica', sans-serif"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
scoreTxt.y = 100;
// Speed display
var speedTxt = new Text2('SPEED: 1x', {
size: 40,
fill: 0xFFFFFF,
stroke: 0x000000,
strokeThickness: 3
});
speedTxt.anchor.set(1, 0);
LK.gui.topRight.addChild(speedTxt);
speedTxt.x = -50;
speedTxt.y = 50;
speedTxt.alpha = 0; // Hidden during intro
// Functions
function spawnJellyObstacle() {
var obstacle = game.addChild(new JellyObstacle());
obstacle.scaleX = 1.3;
obstacle.scaleY = 1.3;
obstacle.x = 2048 + 100;
obstacle.y = groundY;
obstacle.speed = gameSpeed;
jellyObstacles.push(obstacle);
}
function spawnGummyBee() {
var bee = game.addChild(new GummyBee());
bee.scaleX = 1.3;
bee.scaleY = 1.3;
bee.x = 2048 + 100;
// Create varied flight patterns - some high (requiring jumps), some low (safe at ground)
var flightType = Math.random();
if (flightType < 0.4) {
// High flying bees that require jumping (40% chance)
var baseHeight = groundY - 250 - Math.random() * 150;
bee.verticalRange = 15 + Math.random() * 20; // Much smaller range for high flyers
} else {
// Low flying bees that are safe when character is on ground (60% chance)
var baseHeight = groundY - 150 - Math.random() * 100;
bee.verticalRange = 20 + Math.random() * 30; // Reduced range for low flyers
}
bee.y = baseHeight;
bee.baseY = baseHeight;
bee.speed = gameSpeed;
gummyBees.push(bee);
}
function spawnSparkleGem() {
var gem = game.addChild(new SparkleGem());
gem.scaleX = 1.2;
gem.scaleY = 1.2;
gem.x = 2048 + 100;
gem.y = groundY - 150 - Math.random() * 350;
gem.speed = gameSpeed;
sparkleGems.push(gem);
}
function spawnShadowBat() {
var bat = game.addChild(new ShadowBat());
bat.x = 2048 + 100;
bat.y = groundY - 350 - Math.random() * 200;
bat.speed = gameSpeed;
bat.groundY = groundY; // Set ground reference for diving
bat.zigzagRange = 200 + Math.random() * 100; // Increased range for more dramatic movement
bat.zigzagSpeed = 4 + Math.random() * 2; // Slightly faster zigzag
shadowBats.push(bat);
}
function spawnAirCube() {
var cube = game.addChild(new AirCube());
cube.x = 2048 + 100;
cube.y = groundY - 280; // Much higher position for character to pass underneath
cube.baseY = groundY - 280;
cube.speed = gameSpeed;
airCubes.push(cube);
}
function spawnCloud() {
var cloud = cloudsContainer.addChild(new Cloud());
cloud.x = 2048 + 200;
cloud.y = 200 + Math.random() * 800; // Clouds in upper portion of sky
clouds.push(cloud);
}
function spawnBackgroundElement() {
var element = backgroundElementsContainer.addChild(new BackgroundElement());
element.x = 2048 + 300;
element.y = groundY; // Position at ground level
// Apply current theme tint to newly spawned elements
if (element.elementGraphics) {
if (currentTheme === 'night') {
element.elementGraphics.tint = 0x4d4d4d; // 70% darkness for night
} else {
element.elementGraphics.tint = 0xd9d9d9; // 85% brightness for day (clearer houses)
}
}
backgroundElements.push(element);
}
function transitionBackground(newTheme) {
if (isTransitioning || currentTheme === newTheme) return;
isTransitioning = true;
if (newTheme === 'night') {
// Transition to night
tween(dayBg, {
alpha: 0
}, {
duration: 2000,
easing: tween.easeInOut
});
tween(nightBg, {
alpha: 1
}, {
duration: 2000,
easing: tween.easeInOut
});
// Show twinkling stars during night
tween(starsContainer, {
alpha: 1
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: function onFinish() {
currentTheme = 'night';
isTransitioning = false;
}
});
// Hide clouds during night
tween(cloudsContainer, {
alpha: 0
}, {
duration: 2000,
easing: tween.easeInOut
});
// Darken background elements during night (70% darker)
tween(backgroundElementsContainer, {
alpha: 1,
tint: 0x4d4d4d
}, {
duration: 2000,
easing: tween.easeInOut
});
// Apply 70% darkness to all individual background elements (houses and trees)
for (var i = 0; i < backgroundElements.length; i++) {
var element = backgroundElements[i];
if (element && (element.elementType.indexOf('house') !== -1 || element.elementType.indexOf('tree') !== -1)) {
tween(element, {
tint: 0x4d4d4d
}, {
duration: 2000,
easing: tween.easeInOut
});
}
}
} else {
// Transition to day
tween(nightBg, {
alpha: 0
}, {
duration: 2000,
easing: tween.easeInOut
});
tween(dayBg, {
alpha: 1
}, {
duration: 2000,
easing: tween.easeInOut
});
// Hide twinkling stars during day
tween(starsContainer, {
alpha: 0
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: function onFinish() {
currentTheme = 'day';
isTransitioning = false;
}
});
// Show clouds during day
tween(cloudsContainer, {
alpha: 1
}, {
duration: 2000,
easing: tween.easeInOut
});
// Brighten all houses and trees during day (85% brightness for clearer houses)
tween(backgroundElementsContainer, {
alpha: 1,
tint: 0xd9d9d9
}, {
duration: 2000,
easing: tween.easeInOut
});
// Apply 85% brightness to all individual background elements (houses and trees)
for (var i = 0; i < backgroundElements.length; i++) {
var element = backgroundElements[i];
if (element && (element.elementType.indexOf('house') !== -1 || element.elementType.indexOf('tree') !== -1)) {
tween(element, {
tint: 0xd9d9d9
}, {
duration: 2000,
easing: tween.easeInOut
});
}
}
}
}
function restartGame() {
// Reset player position
fluffball.x = 300;
fluffball.y = groundY - 80;
fluffball.velocityY = 0;
fluffball.isJumping = false;
fluffball.isDucking = false;
fluffball.canDoubleJump = false;
fluffball.hasDoubleJumped = false;
// Reset game speed
gameSpeed = 6;
// Clear all obstacles and collectibles
for (var i = jellyObstacles.length - 1; i >= 0; i--) {
jellyObstacles[i].destroy();
}
jellyObstacles = [];
for (var i = gummyBees.length - 1; i >= 0; i--) {
gummyBees[i].destroy();
}
gummyBees = [];
for (var i = sparkleGems.length - 1; i >= 0; i--) {
sparkleGems[i].destroy();
}
sparkleGems = [];
for (var i = shadowBats.length - 1; i >= 0; i--) {
shadowBats[i].destroy();
}
shadowBats = [];
for (var i = airCubes.length - 1; i >= 0; i--) {
airCubes[i].destroy();
}
airCubes = [];
for (var i = clouds.length - 1; i >= 0; i--) {
clouds[i].destroy();
}
clouds = [];
for (var i = backgroundElements.length - 1; i >= 0; i--) {
backgroundElements[i].destroy();
}
backgroundElements = [];
// Reset spawn timers
obstacleSpawnTimer = 0;
gemSpawnTimer = 0;
// Reset theme to day
currentTheme = 'day';
dayBg.alpha = 1;
nightBg.alpha = 0;
starsContainer.alpha = 0;
cloudsContainer.alpha = 1;
}
function checkCollisions() {
// Check jelly obstacles
for (var i = jellyObstacles.length - 1; i >= 0; i--) {
var obstacle = jellyObstacles[i];
// Initialize last intersection state if not set
if (obstacle.lastIntersecting === undefined) {
obstacle.lastIntersecting = false;
}
var currentIntersecting = fluffball.intersects(obstacle);
// Only trigger collision on the exact frame when intersection starts
if (!obstacle.lastIntersecting && currentIntersecting) {
// Lose a life
lives--;
livesText.setText('♥ ' + lives);
LK.getSound('hit').play();
LK.effects.flashScreen(0xff0000, 500);
// Flash lives text
LK.effects.flashObject(livesText, 0xFFFFFF, 500);
// Check if game over
if (lives <= 0) {
LK.showGameOver();
return;
} else {
// Restart game while preserving score
restartGame();
return;
}
}
// Update last intersection state
obstacle.lastIntersecting = currentIntersecting;
// Perfect pass bonus
if (!obstacle.passed && obstacle.x < fluffball.x - 50) {
obstacle.passed = true;
LK.setScore(LK.getScore() + perfectPassBonus);
// Animate score text
tween(scoreTxt, {
tint: 0xFF0000,
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(scoreTxt, {
tint: 0xFFFFFF,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 150,
easing: tween.easeIn
});
}
});
}
// Remove off-screen obstacles
if (obstacle.x < -100) {
obstacle.destroy();
jellyObstacles.splice(i, 1);
}
}
// Check gummy bees
for (var i = gummyBees.length - 1; i >= 0; i--) {
var bee = gummyBees[i];
var canCollide = true;
// If fluffball is on ground, prevent collision with bees flying too high
if (!fluffball.isJumping) {
// Character collision area when on ground - extends from feet to head
var fluffballTop = fluffball.y - 80; // Top of character
var fluffballBottom = fluffball.y + 80; // Bottom of character
// Only collide if bee is within the character's ground-level collision zone
if (bee.y < fluffballTop - 20) {
// Bee is too high above character when on ground - no collision
canCollide = false;
}
}
// Initialize last intersection state if not set
if (bee.lastIntersecting === undefined) {
bee.lastIntersecting = false;
}
var currentIntersecting = fluffball.intersects(bee) && canCollide;
// Only trigger collision on the exact frame when intersection starts
if (!bee.lastIntersecting && currentIntersecting) {
// Lose a life
lives--;
livesText.setText('♥ ' + lives);
LK.getSound('hit').play();
LK.effects.flashScreen(0xff0000, 500);
// Flash lives text
LK.effects.flashObject(livesText, 0xFFFFFF, 500);
// Check if game over
if (lives <= 0) {
LK.showGameOver();
return;
} else {
// Restart game while preserving score
restartGame();
return;
}
}
// Update last intersection state
bee.lastIntersecting = currentIntersecting;
// Perfect pass bonus
if (!bee.passed && bee.x < fluffball.x - 50) {
bee.passed = true;
LK.setScore(LK.getScore() + perfectPassBonus);
// Animate score text
tween(scoreTxt, {
tint: 0xFF0000,
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(scoreTxt, {
tint: 0xFFFFFF,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 150,
easing: tween.easeIn
});
}
});
}
// Remove off-screen bees
if (bee.x < -100) {
bee.destroy();
gummyBees.splice(i, 1);
}
}
// Check sparkle gems
for (var i = sparkleGems.length - 1; i >= 0; i--) {
var gem = sparkleGems[i];
// Initialize last intersection state if not set
if (gem.lastIntersecting === undefined) {
gem.lastIntersecting = false;
}
var currentIntersecting = fluffball.intersects(gem) && !gem.collected;
// Only trigger collection on the exact frame when intersection starts
if (!gem.lastIntersecting && currentIntersecting) {
gem.collected = true;
LK.setScore(LK.getScore() + gemValue);
LK.getSound('collect').play();
LK.effects.flashObject(gem, 0xffffff, 200);
// Animate score text
tween(scoreTxt, {
tint: 0xFF0000,
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(scoreTxt, {
tint: 0xFFFFFF,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 150,
easing: tween.easeIn
});
}
});
// Remove gem with tween
tween(gem, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 200,
onFinish: function onFinish() {
gem.destroy();
}
});
sparkleGems.splice(i, 1);
} else if (gem.x < -100) {
gem.destroy();
sparkleGems.splice(i, 1);
}
// Update last intersection state
gem.lastIntersecting = currentIntersecting;
}
// Check shadow bats
for (var i = shadowBats.length - 1; i >= 0; i--) {
var bat = shadowBats[i];
// Initialize last intersection state if not set
if (bat.lastIntersecting === undefined) {
bat.lastIntersecting = false;
}
var currentIntersecting = fluffball.intersects(bat);
// Only trigger collision on the exact frame when intersection starts
if (!bat.lastIntersecting && currentIntersecting) {
// Lose a life
lives--;
livesText.setText('♥ ' + lives);
LK.getSound('hit').play();
LK.effects.flashScreen(0xff0000, 500);
// Flash lives text
LK.effects.flashObject(livesText, 0xFFFFFF, 500);
// Check if game over
if (lives <= 0) {
LK.showGameOver();
return;
} else {
// Restart game while preserving score
restartGame();
return;
}
}
// Update last intersection state
bat.lastIntersecting = currentIntersecting;
// Perfect pass bonus
if (!bat.passed && bat.x < fluffball.x - 50) {
bat.passed = true;
LK.setScore(LK.getScore() + perfectPassBonus);
// Animate score text
tween(scoreTxt, {
tint: 0xFF0000,
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(scoreTxt, {
tint: 0xFFFFFF,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 150,
easing: tween.easeIn
});
}
});
}
// Remove off-screen bats
if (bat.x < -100) {
bat.destroy();
shadowBats.splice(i, 1);
}
}
// Check air cubes - with collision detection enabled
for (var i = airCubes.length - 1; i >= 0; i--) {
var cube = airCubes[i];
// Initialize last intersection state if not set
if (cube.lastIntersecting === undefined) {
cube.lastIntersecting = false;
}
var currentIntersecting = fluffball.intersects(cube);
// Only trigger collision on the exact frame when intersection starts
if (!cube.lastIntersecting && currentIntersecting) {
// Lose a life
lives--;
livesText.setText('♥ ' + lives);
LK.getSound('hit').play();
LK.effects.flashScreen(0xff0000, 500);
// Flash lives text
LK.effects.flashObject(livesText, 0xFFFFFF, 500);
// Check if game over
if (lives <= 0) {
LK.showGameOver();
return;
} else {
// Restart game while preserving score
restartGame();
return;
}
}
// Update last intersection state
cube.lastIntersecting = currentIntersecting;
// Perfect pass bonus
if (!cube.passed && cube.x < fluffball.x - 50) {
cube.passed = true;
LK.setScore(LK.getScore() + perfectPassBonus);
// Animate score text
tween(scoreTxt, {
tint: 0xFF0000,
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(scoreTxt, {
tint: 0xFFFFFF,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 150,
easing: tween.easeIn
});
}
});
}
// Remove off-screen cubes
if (cube.x < -100) {
cube.destroy();
airCubes.splice(i, 1);
}
}
}
// Input handlers
game.down = function (x, y, obj) {
if (isGameRunning) {
var currentTime = Date.now();
var timeSinceLastClick = currentTime - lastClickTime;
// Check for double click
if (timeSinceLastClick < doubleClickDelay) {
// Double click detected - trigger double jump immediately
fluffball.jump();
} else {
// Single click - trigger jump immediately
fluffball.jump();
}
lastClickTime = currentTime;
isHolding = true;
// Start ducking after a short delay if still holding
LK.setTimeout(function () {
if (isHolding) {
fluffball.startDuck();
}
}, 200);
}
};
game.up = function (x, y, obj) {
isHolding = false;
fluffball.stopDuck();
};
// Main game loop
game.update = function () {
// Handle intro video timing
if (isIntroPlaying) {
var currentTime = Date.now();
var elapsedTime = currentTime - introStartTime;
// Show "COMENZAR" button after 10 seconds
if (elapsedTime >= introDuration && introStartBtn.alpha === 0) {
tween(introStartBtn, {
alpha: 1
}, {
duration: 500,
easing: tween.easeOut
});
}
// Auto-skip intro after video ends (if it ends before manual skip)
if (elapsedTime >= introDuration + 2000) {
// Give 2 extra seconds buffer
endIntro();
}
return; // Don't run game logic during intro
}
if (!isGameRunning) return;
// Increase speed gradually
if (gameSpeed < maxSpeed) {
gameSpeed += speedIncrement;
}
// Update speed display
var speedMultiplier = Math.floor(gameSpeed / 6 * 10) / 10;
speedTxt.setText('SPEED: ' + speedMultiplier + 'x');
// Update score display
scoreTxt.setText(LK.getScore().toString());
// Spawn obstacles with increased spacing
obstacleSpawnTimer++;
if (obstacleSpawnTimer > 160 - gameSpeed * 4) {
obstacleSpawnTimer = 0;
var randomValue = Math.random();
if (randomValue < 0.4) {
spawnJellyObstacle();
} else if (randomValue < 0.75) {
spawnGummyBee();
} else if (randomValue < 0.9) {
spawnShadowBat();
} else {
spawnAirCube();
}
}
// Spawn gems with increased spacing
gemSpawnTimer++;
if (gemSpawnTimer > 220) {
gemSpawnTimer = 0;
if (Math.random() < 0.4) {
spawnSparkleGem();
}
}
// Update all objects speeds
for (var i = 0; i < jellyObstacles.length; i++) {
jellyObstacles[i].speed = gameSpeed;
}
for (var i = 0; i < gummyBees.length; i++) {
gummyBees[i].speed = gameSpeed;
}
for (var i = 0; i < sparkleGems.length; i++) {
sparkleGems[i].speed = gameSpeed;
}
for (var i = 0; i < shadowBats.length; i++) {
shadowBats[i].speed = gameSpeed;
}
for (var i = 0; i < airCubes.length; i++) {
airCubes[i].speed = gameSpeed;
}
// Spawn clouds only during day theme
if (currentTheme === 'day' && Math.random() < 0.005) {
// Low spawn rate for decoration
spawnCloud();
}
// Spawn background elements at ground level
if (Math.random() < 0.008) {
// Spawn rate for ground-level scenery
spawnBackgroundElement();
}
// Update and manage clouds
for (var i = clouds.length - 1; i >= 0; i--) {
var cloud = clouds[i];
if (cloud.x < -300) {
// Remove off-screen clouds
cloud.destroy();
clouds.splice(i, 1);
}
}
// Update and manage background elements
for (var i = backgroundElements.length - 1; i >= 0; i--) {
var element = backgroundElements[i];
if (element.x < -400) {
// Remove off-screen background elements
element.destroy();
backgroundElements.splice(i, 1);
}
}
// Check collisions
checkCollisions();
// Background transition based on score every 100 points
var score = LK.getScore();
var scoreThreshold = Math.floor(score / 100);
if (scoreThreshold % 2 === 1) {
// Odd hundreds (100, 300, 500...) = night
transitionBackground('night');
} else {
// Even hundreds (0, 200, 400...) = day
transitionBackground('day');
}
};
// Game logo setup
var gameLogo = game.addChild(LK.getAsset('gameLogo', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2 - 800,
alpha: 0.7,
scaleX: 0.6,
scaleY: 0.6
}));
// Start breathing animation for logo
function startLogoBreathing() {
tween(gameLogo, {
scaleX: 1.02,
scaleY: 1.02
}, {
duration: 1200,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(gameLogo, {
scaleX: 0.98,
scaleY: 0.98
}, {
duration: 1200,
easing: tween.easeInOut,
onFinish: function onFinish() {
startLogoBreathing(); // Loop the animation
}
});
}
});
}
// Start the breathing animation
startLogoBreathing();
// Initialize music state from storage (default to true if not set)
var isMusicEnabled = storage.musicEnabled !== undefined ? storage.musicEnabled : true;
// Lives system
var lives = 3;
var livesText = new Text2('♥ ' + lives, {
size: 60,
fill: 0xFF0000,
stroke: 0x000000,
strokeThickness: 4,
font: "'Arial Black', 'Helvetica', sans-serif"
});
livesText.anchor.set(0.5, 0.5);
LK.gui.topLeft.addChild(livesText);
livesText.x = 150;
livesText.y = 120;
livesText.alpha = 0; // Hidden during intro
// Music control button
var musicBtn = new Text2(isMusicEnabled ? '♪' : '♪✗', {
size: 50,
fill: isMusicEnabled ? 0x00FF00 : 0xFFFFFF,
stroke: 0x000000,
strokeThickness: 3,
font: "'Arial Black', 'Helvetica', sans-serif"
});
musicBtn.anchor.set(0.5, 0.5);
LK.gui.topRight.addChild(musicBtn);
musicBtn.x = -200;
musicBtn.y = 120;
musicBtn.alpha = 0; // Hidden during intro
// Intro video system
var isIntroPlaying = true;
var introStartTime = 0;
var introDuration = 10000; // 10 seconds in milliseconds
// Show intro video at game start
isGameRunning = false; // Start with game paused
// Create intro overlay
var introOverlay = game.addChild(new Container());
// Add black background for intro
var introBlackBg = introOverlay.attachAsset('underground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
scaleX: 1,
scaleY: 5.464
});
introBlackBg.tint = 0x000000;
// Add video placeholder background (using existing shape)
var introVideo = introOverlay.attachAsset('introVideo', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 20.48,
scaleY: 27.32
});
// Create stars container for intro background effect
var introStarsContainer = introOverlay.addChild(new Container());
var introStars = [];
// Generate twinkling stars for intro background covering full screen
for (var i = 0; i < 150; i++) {
var introStar = introStarsContainer.addChild(new Star());
introStar.x = Math.random() * 2048;
introStar.y = Math.random() * 2732; // Stars covering full screen
introStars.push(introStar);
}
// Add game logo to intro
var introGameLogo = introOverlay.attachAsset('gameLogo', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 800,
scaleX: 0.3,
scaleY: 0.3,
alpha: 0
});
// Animate logo to appear gradually from background
tween(introGameLogo, {
alpha: 0.9,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 3000,
easing: tween.easeOut
});
// Skip intro button (always visible)
var skipBtn = new Text2('TOXIK STUDIO Games', {
size: 40,
fill: 0xFFFFFF,
stroke: 0x000000,
strokeThickness: 3,
font: "'Arial Black', 'Helvetica', sans-serif"
});
skipBtn.anchor.set(0.5, 0.5);
introOverlay.addChild(skipBtn);
skipBtn.x = 1024;
skipBtn.y = 1366;
// Start button (appears after 10 seconds)
var introStartBtn = new Text2('PLAY', {
size: 80,
fill: 0x00FF00,
stroke: 0x000000,
strokeThickness: 6,
font: "'Arial Black', 'Helvetica', sans-serif"
});
introStartBtn.anchor.set(0.5, 0.5);
introOverlay.addChild(introStartBtn);
introStartBtn.x = 1024;
introStartBtn.y = 2048; // Center of lower half (2732 * 0.75)
introStartBtn.alpha = 0; // Start hidden
// Function to end intro and show tutorial
function endIntro() {
isIntroPlaying = false;
// Show UI elements after intro
livesText.alpha = 1;
speedTxt.alpha = 1;
musicBtn.alpha = 1;
// Start background music if enabled
if (isMusicEnabled) {
try {
LK.playMusic('backgroundMusic');
} catch (e) {
console.log('Music failed to play:', e);
}
}
// Fade out intro overlay
tween(introOverlay, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
introOverlay.destroy();
showTutorial(); // Show tutorial after intro
}
});
}
// Function to show tutorial (moved from inline code)
function showTutorial() {
// Show tutorial at game start
isGameRunning = false; // Start with game paused
// Create tutorial overlay
var tutorialOverlay = game.addChild(new Container());
// Semi-transparent background
var overlayBg = tutorialOverlay.attachAsset('underground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732,
alpha: 0.8,
scaleX: 1,
scaleY: 5.464
});
overlayBg.tint = 0x000000;
// Tutorial title
var titleText = new Text2('HOW TO PLAY', {
size: 80,
fill: 0xFFFFFF,
stroke: 0x000000,
strokeThickness: 6,
font: "'Arial Black', 'Helvetica', sans-serif"
});
titleText.anchor.set(0.5, 0.5);
tutorialOverlay.addChild(titleText);
titleText.x = 1024;
titleText.y = 400;
// Tutorial instructions in English
var instructionTexts = ['TAP TO JUMP', 'HOLD TO DUCK', 'DOUBLE TAP FOR DOUBLE JUMP', 'AVOID OBSTACLES', 'COLLECT GEMS ✦', 'PASS CLOSE TO ENEMIES = +10 POINTS'];
for (var i = 0; i < instructionTexts.length; i++) {
var instructText = new Text2(instructionTexts[i], {
size: 50,
fill: 0xFFFFFF,
stroke: 0x000000,
strokeThickness: 3,
font: "'Arial Black', 'Helvetica', sans-serif"
});
instructText.anchor.set(0.5, 0.5);
tutorialOverlay.addChild(instructText);
instructText.x = 1024;
instructText.y = 600 + i * 80;
}
// Start game button
var startBtn = new Text2('START GAME', {
size: 60,
fill: 0x00FF00,
stroke: 0x000000,
strokeThickness: 4,
font: "'Arial Black', 'Helvetica', sans-serif"
});
startBtn.anchor.set(0.5, 0.5);
tutorialOverlay.addChild(startBtn);
startBtn.x = 1024;
startBtn.y = 1200;
// Start button interaction
startBtn.down = function () {
LK.effects.flashObject(startBtn, 0xFFFFFF, 200);
// Start the game
isGameRunning = true;
// Fade out and remove tutorial
tween(tutorialOverlay, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
tutorialOverlay.destroy();
}
});
};
// Allow starting by tapping background
overlayBg.down = function () {
// Start the game
isGameRunning = true;
tween(tutorialOverlay, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
tutorialOverlay.destroy();
}
});
};
// Fade in tutorial
tutorialOverlay.alpha = 0;
tween(tutorialOverlay, {
alpha: 1
}, {
duration: 300
});
}
// Skip intro button interaction
skipBtn.down = function () {
LK.effects.flashObject(skipBtn, 0xFFFF00, 200);
endIntro();
};
// Start button interaction (appears after 10 seconds)
introStartBtn.down = function () {
LK.effects.flashObject(introStartBtn, 0xFFFFFF, 200);
endIntro();
};
// Start intro timing (no video playback needed)
introStartTime = Date.now();
// Fade in intro overlay
introOverlay.alpha = 0;
tween(introOverlay, {
alpha: 1
}, {
duration: 500
});
// Music button interaction
musicBtn.down = function (x, y, obj) {
// Toggle music state
isMusicEnabled = !isMusicEnabled;
// Save to storage
storage.musicEnabled = isMusicEnabled;
// Update button appearance and text
musicBtn.setText(isMusicEnabled ? '♪' : '♪✗');
musicBtn.fill = isMusicEnabled ? 0x00FF00 : 0xFFFFFF;
// Flash button to show it was pressed
LK.effects.flashObject(musicBtn, 0xFFFF00, 300);
// Control music playback
if (isMusicEnabled) {
try {
LK.playMusic('backgroundMusic');
} catch (e) {
console.log('Music failed to play:', e);
}
} else {
try {
LK.stopMusic();
} catch (e) {
console.log('Music failed to stop:', e);
}
}
};
// Music will start after intro ends
;
; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var AirCube = Container.expand(function () {
var self = Container.call(this);
// Create floating cube obstacle
var cubeGraphics = self.attachAsset('airCube', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
// No shadow for air cube
self.speed = 0;
self.passed = false;
self.floatOffset = Math.random() * Math.PI * 2;
self.baseY = 0; // Will be set when spawned
self.update = function () {
self.x -= self.speed;
// Gentle floating animation
self.y = self.baseY + Math.sin(LK.ticks * 0.04 + self.floatOffset) * 8;
// Subtle rotation
cubeGraphics.rotation = Math.sin(LK.ticks * 0.03) * 0.1;
// No shadow updates needed
};
return self;
});
var BackgroundElement = Container.expand(function () {
var self = Container.call(this);
// Create different types of background elements with all 10 house and 10 tree varieties
var elementTypes = ['house1', 'house2', 'house3', 'house4', 'house5', 'house6', 'house7', 'house8', 'house9', 'house10', 'tree', 'tree2', 'tree3', 'tree4', 'tree5', 'tree6', 'tree7', 'tree8', 'tree9', 'tree10'];
var elementType = elementTypes[Math.floor(Math.random() * elementTypes.length)];
// Create depth-based layering for background elements
var isBackground = Math.random() < 0.5; // 50% chance to be background (far)
// Determine element size and speed based on depth
var elementScale;
var speed;
if (isBackground) {
// Far background elements - smaller, slower, full opacity
elementScale = 0.4 + Math.random() * 0.3; // 0.4 to 0.7
speed = 0.8 + Math.random() * 0.7; // 0.8 to 1.5
} else {
// Near background elements - larger, faster, full opacity
elementScale = 0.8 + Math.random() * 0.4; // 0.8 to 1.2
speed = 1.5 + Math.random() * 1.0; // 1.5 to 2.5
}
// Create background element graphics with full opacity
var elementGraphics = self.attachAsset(elementType, {
anchorX: 0.5,
anchorY: 1.0,
// Anchor at bottom for ground placement
scaleX: elementScale,
scaleY: elementScale,
alpha: 1.0
});
// Apply initial tint based on current theme
var baseTint = 0xffffff;
if (currentTheme === 'night') {
baseTint = 0x4d4d4d; // 70% darkness for night
} else {
baseTint = 0xd9d9d9; // 85% brightness for day (clearer houses)
}
elementGraphics.tint = baseTint;
// Store reference to graphics for tint updates
self.elementGraphics = elementGraphics;
self.speed = speed;
self.elementType = elementType;
self.elementScale = elementScale;
self.update = function () {
self.x -= self.speed;
};
return self;
});
var Cloud = Container.expand(function () {
var self = Container.call(this);
// Choose cloud type (0, 1, or 2)
var cloudType = Math.floor(Math.random() * 3);
// Create depth-based layering for clouds
var isBackground = Math.random() < 0.4; // 40% chance to be background (far)
var isMidground = !isBackground && Math.random() < 0.6; // 60% of remaining are midground
// Determine cloud size, alpha, and speed based on depth
var cloudScale;
var alpha;
var speed;
if (isBackground) {
// Far clouds - smallest, slowest, most transparent
cloudScale = 0.3 + Math.random() * 0.2; // 0.3 to 0.5
alpha = 0.3 + Math.random() * 0.2; // 0.3 to 0.5
speed = 0.5 + Math.random() * 0.5; // 0.5 to 1.0
} else if (isMidground) {
// Mid clouds - medium size, speed, and alpha
cloudScale = 0.6 + Math.random() * 0.3; // 0.6 to 0.9
alpha = 0.5 + Math.random() * 0.2; // 0.5 to 0.7
speed = 1.2 + Math.random() * 0.6; // 1.2 to 1.8
} else {
// Near clouds - largest, fastest, most opaque
cloudScale = 1.0 + Math.random() * 0.4; // 1.0 to 1.4
alpha = 0.7 + Math.random() * 0.2; // 0.7 to 0.9
speed = 2.0 + Math.random() * 1.0; // 2.0 to 3.0
}
// Choose cloud asset based on type
var cloudAssetId;
if (cloudType === 0) {
cloudAssetId = 'cloud1';
} else if (cloudType === 1) {
cloudAssetId = 'cloud2';
} else {
cloudAssetId = 'cloud3';
}
// Create single cloud image with proper scaling
var cloudGraphics = self.attachAsset(cloudAssetId, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: cloudScale,
scaleY: cloudScale,
alpha: alpha
});
// Apply slight tint variation for more natural look
var tintVariations = [0xffffff, 0xf8f8ff, 0xf0f8ff, 0xfafaff];
cloudGraphics.tint = tintVariations[Math.floor(Math.random() * tintVariations.length)];
self.speed = speed;
self.cloudScale = cloudScale;
self.cloudType = cloudType;
self.update = function () {
self.x -= self.speed;
};
return self;
});
var Fluffball = Container.expand(function () {
var self = Container.call(this);
var fluffballGraphics = self.attachAsset('fluffball', {
anchorX: 0.5,
anchorY: 0.5
});
// No shadow for character
self.velocityY = 0;
self.isJumping = false;
self.isDucking = false;
self.groundY = 0;
self.jumpPower = -35;
self.gravity = 0.8;
self.duckScale = 0.6;
self.normalScale = 1.0;
self.canDoubleJump = false;
self.hasDoubleJumped = false;
self.blinkTimer = 0;
self.isBlinking = false;
self.nextBlinkTime = 180; // Blink every 3 seconds (180 ticks at 60fps)
self.jump = function () {
if (!self.isJumping) {
self.velocityY = self.jumpPower;
self.isJumping = true;
self.canDoubleJump = true;
self.hasDoubleJumped = false;
LK.getSound('jump').play();
// Rotate backward during jump
tween(fluffballGraphics, {
rotation: -0.5
}, {
duration: 200,
easing: tween.easeOut
});
} else if (self.canDoubleJump && !self.hasDoubleJumped) {
// Double jump
self.velocityY = self.jumpPower * 0.8; // Slightly weaker second jump
self.hasDoubleJumped = true;
self.canDoubleJump = false;
LK.getSound('jump').play();
// Extra rotation for double jump
tween(fluffballGraphics, {
rotation: -0.8
}, {
duration: 150,
easing: tween.easeOut
});
}
};
self.startDuck = function () {
if (!self.isJumping) {
self.isDucking = true;
tween(fluffballGraphics, {
scaleY: self.duckScale
}, {
duration: 100
});
}
};
self.stopDuck = function () {
self.isDucking = false;
tween(fluffballGraphics, {
scaleY: self.normalScale
}, {
duration: 100
});
};
self.update = function () {
if (self.isJumping) {
self.velocityY += self.gravity;
self.y += self.velocityY;
// Rotate forward when falling (positive velocity)
if (self.velocityY > 5) {
tween(fluffballGraphics, {
rotation: 0.3
}, {
duration: 150,
easing: tween.easeIn
});
}
if (self.y >= self.groundY) {
self.y = self.groundY;
self.velocityY = 0;
self.isJumping = false;
self.canDoubleJump = false;
self.hasDoubleJumped = false;
// Return to original rotation when landing
tween(fluffballGraphics, {
rotation: 0
}, {
duration: 200,
easing: tween.easeOut
});
}
} else {
// Add subtle swaying animation when walking (not jumping)
var swayAmount = Math.sin(LK.ticks * 0.15) * 0.08; // Very subtle sway
fluffballGraphics.rotation = swayAmount;
// Add slight vertical bob while walking
var bobAmount = Math.sin(LK.ticks * 0.2) * 2; // Small vertical movement
fluffballGraphics.y = bobAmount;
}
// Blinking animation
self.blinkTimer++;
if (self.blinkTimer >= self.nextBlinkTime && !self.isBlinking) {
self.isBlinking = true;
self.blinkTimer = 0;
self.nextBlinkTime = 180; // Reset timer for next blink (3 seconds)
// Remove current graphics and add blinking graphics
self.removeChild(fluffballGraphics);
fluffballGraphics = self.attachAsset('fluffballBlink', {
anchorX: 0.5,
anchorY: 0.5
});
// Switch back after blink duration
LK.setTimeout(function () {
if (self.isBlinking) {
// Remove blinking graphics and restore normal graphics
self.removeChild(fluffballGraphics);
fluffballGraphics = self.attachAsset('fluffball', {
anchorX: 0.5,
anchorY: 0.5
});
self.isBlinking = false;
}
}, 250); // Blink duration of 250ms (0.25 seconds)
}
// No shadow updates needed
// Bounce animation
if (!self.isJumping && LK.ticks % 30 == 0) {
tween(fluffballGraphics, {
scaleY: 1.1
}, {
duration: 150,
easing: tween.easeOut
});
tween(fluffballGraphics, {
scaleY: 1.0
}, {
duration: 150,
easing: tween.easeIn
});
}
};
return self;
});
var GummyBee = Container.expand(function () {
var self = Container.call(this);
var beeGraphics = self.attachAsset('gummyBee', {
anchorX: 0.5,
anchorY: 0.5
});
// No shadow for bee
self.speed = 0;
self.passed = false;
self.bobOffset = Math.random() * Math.PI * 2;
self.baseY = 0; // Will be set when spawned
self.verticalRange = 25 + Math.random() * 30; // Reduced vertical movement range
self.verticalSpeed = 0.08 + Math.random() * 0.05; // Variable vertical speed
self.lastY = 0;
self.update = function () {
self.x -= self.speed;
// Initialize lastY if not set
if (self.lastY === undefined) self.lastY = self.y;
// Varied flight pattern - some bees fly high (requiring jumps), others fly low (safe at ground)
var verticalOffset = Math.sin(LK.ticks * self.verticalSpeed + self.bobOffset) * self.verticalRange;
self.y = self.baseY + verticalOffset;
// Wing rotation animation
beeGraphics.rotation = Math.sin(LK.ticks * 0.1) * 0.2;
// Scale animation for wing flapping
beeGraphics.scaleY = 1.0 + Math.sin(LK.ticks * 0.3) * 0.1;
// No shadow updates needed
// Update lastY
self.lastY = self.y;
};
return self;
});
var JellyObstacle = Container.expand(function () {
var self = Container.call(this);
var jellyGraphics = self.attachAsset('jellyObstacle', {
anchorX: 0.5,
anchorY: 1.0
});
// No shadow for jelly obstacle
self.speed = 0;
self.passed = false;
self.update = function () {
self.x -= self.speed;
// Wobble animation
jellyGraphics.scaleX = 1 + Math.sin(LK.ticks * 0.1) * 0.1;
jellyGraphics.scaleY = 1 + Math.cos(LK.ticks * 0.08) * 0.05;
// No shadow updates needed
};
return self;
});
var ShadowBat = Container.expand(function () {
var self = Container.call(this);
var batGraphics = self.attachAsset('shadowBat', {
anchorX: 0.5,
anchorY: 0.5
});
// No shadow for bat
self.speed = 0;
self.passed = false;
self.zigzagDirection = 1; // 1 for up, -1 for down
self.zigzagSpeed = 6; // Increased vertical movement speed for faster zigzag
self.zigzagRange = 150; // How far up/down to move
self.startY = 0; // Starting Y position
self.lastY = 0;
self.update = function () {
self.x -= self.speed;
// Initialize lastY if not set
if (self.lastY === undefined) self.lastY = self.y;
// Initialize startY if not set
if (self.startY === 0) self.startY = self.y;
// Enhanced zigzag movement - goes much closer to ground with faster movement
self.y += self.zigzagDirection * self.zigzagSpeed;
// Calculate ground proximity - bat can get within 120px of ground
var maxGroundApproach = self.groundY - 120;
// Change direction when reaching limits - goes closer to ground now
if (self.y <= self.startY - self.zigzagRange) {
self.zigzagDirection = 1; // Change to downward
} else if (self.y >= maxGroundApproach) {
self.zigzagDirection = -1; // Change to upward - return to original position
}
// More pronounced wing animation for faster zigzag effect
batGraphics.rotation = Math.sin(LK.ticks * 0.4) * 0.15; // More visible rotation for zigzag effect
// No shadow updates needed
// Update lastY
self.lastY = self.y;
};
return self;
});
var SparkleGem = Container.expand(function () {
var self = Container.call(this);
var gemGraphics = self.attachAsset('sparkleGem', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0;
self.collected = false;
self.update = function () {
self.x -= self.speed;
// Sparkle animation
gemGraphics.rotation += 0.1;
gemGraphics.scaleX = 1 + Math.sin(LK.ticks * 0.2) * 0.3;
gemGraphics.scaleY = 1 + Math.cos(LK.ticks * 0.2) * 0.3;
};
return self;
});
var Star = Container.expand(function () {
var self = Container.call(this);
var starGraphics = self.attachAsset('star', {
anchorX: 0.5,
anchorY: 0.5
});
self.twinkleSpeed = 0.02 + Math.random() * 0.03;
self.twinkleOffset = Math.random() * Math.PI * 2;
self.minAlpha = 0.3;
self.maxAlpha = 1.0;
self.minScale = 0.3;
self.maxScale = 1.5;
self.scaleSpeed = 0.01 + Math.random() * 0.02;
self.scaleOffset = Math.random() * Math.PI * 2;
self.disappearTimer = Math.random() * 600 + 300; // Random time between 5-15 seconds
self.isDisappearing = false;
self.disappearDuration = 0;
self.reappearDelay = 0;
self.update = function () {
// Create twinkling effect with random timing
var twinkle = Math.sin(LK.ticks * self.twinkleSpeed + self.twinkleOffset);
starGraphics.alpha = self.minAlpha + (self.maxAlpha - self.minAlpha) * (twinkle + 1) / 2;
// Dynamic scale changes
var scaleVariation = Math.sin(LK.ticks * self.scaleSpeed + self.scaleOffset);
var targetScale = self.minScale + (self.maxScale - self.minScale) * (scaleVariation + 1) / 2;
starGraphics.scaleX = targetScale;
starGraphics.scaleY = targetScale;
// Disappear and reappear cycle
self.disappearTimer--;
if (self.disappearTimer <= 0 && !self.isDisappearing) {
self.isDisappearing = true;
self.disappearDuration = Math.random() * 120 + 60; // Disappear for 1-3 seconds
self.reappearDelay = Math.random() * 300 + 180; // Reappear after 3-8 seconds
// Fade out
tween(starGraphics, {
alpha: 0,
scaleX: 0,
scaleY: 0
}, {
duration: 500 + Math.random() * 1000,
easing: tween.easeOut
});
}
if (self.isDisappearing) {
self.disappearDuration--;
if (self.disappearDuration <= 0) {
self.reappearDelay--;
if (self.reappearDelay <= 0) {
// Reset star state and fade back in
self.isDisappearing = false;
self.disappearTimer = Math.random() * 900 + 600; // Next disappear cycle
// Fade in with new random properties
self.twinkleSpeed = 0.02 + Math.random() * 0.03;
self.scaleSpeed = 0.01 + Math.random() * 0.02;
tween(starGraphics, {
alpha: self.maxAlpha,
scaleX: 1,
scaleY: 1
}, {
duration: 800 + Math.random() * 1200,
easing: tween.easeIn
});
}
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87ceeb
});
/****
* Game Code
****/
// Game variables
// Note: LK engine doesn't support video assets, using placeholder shape instead
var gameSpeed = 6;
var speedIncrement = 0.002;
var maxSpeed = 12;
var isGameRunning = true;
var perfectPassBonus = 10;
var gemValue = 5;
// Background variables
var currentTheme = 'day';
var isTransitioning = false;
var stars = [];
var starsContainer;
// Background setup
var dayBg = game.addChild(LK.getAsset('dayBackground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
}));
var nightBg = game.addChild(LK.getAsset('nightBackground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
alpha: 0
}));
// Ground setup
var groundY = 2732 - 500;
var ground = game.addChild(LK.getAsset('ground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: groundY
}));
// Underground brown rectangle
var underground = game.addChild(LK.getAsset('underground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: groundY + 200
}));
// Create stars container for night theme
starsContainer = game.addChild(new Container());
starsContainer.alpha = 0;
// Generate twinkling stars
for (var i = 0; i < 150; i++) {
var star = starsContainer.addChild(new Star());
star.x = Math.random() * 2048;
star.y = Math.random() * (2732 * 0.6); // Stars only in upper 60% of screen
stars.push(star);
}
// Create clouds container for day theme (before player)
var cloudsContainer = game.addChild(new Container());
// Create background elements container (behind character)
var backgroundElementsContainer = game.addChild(new Container());
// Player setup
var fluffball = game.addChild(new Fluffball());
fluffball.scaleX = 1.2;
fluffball.scaleY = 1.2;
fluffball.x = 300;
fluffball.y = groundY - 80;
fluffball.groundY = groundY - 80;
// Arrays for game objects
var jellyObstacles = [];
var gummyBees = [];
var sparkleGems = [];
var shadowBats = [];
var airCubes = [];
var clouds = [];
var backgroundElements = [];
// Timers for spawning
var obstacleSpawnTimer = 0;
var gemSpawnTimer = 0;
// Input tracking
var isHolding = false;
var lastClickTime = 0;
var doubleClickDelay = 300; // Maximum time between clicks for double click (ms)
// Score display
var scoreTxt = new Text2('0', {
size: 80,
fill: 0xFFFFFF,
stroke: 0x000000,
strokeThickness: 8,
font: "'Arial Black', 'Helvetica', sans-serif"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
scoreTxt.y = 100;
// Speed display
var speedTxt = new Text2('SPEED: 1x', {
size: 40,
fill: 0xFFFFFF,
stroke: 0x000000,
strokeThickness: 3
});
speedTxt.anchor.set(1, 0);
LK.gui.topRight.addChild(speedTxt);
speedTxt.x = -50;
speedTxt.y = 50;
speedTxt.alpha = 0; // Hidden during intro
// Functions
function spawnJellyObstacle() {
var obstacle = game.addChild(new JellyObstacle());
obstacle.scaleX = 1.3;
obstacle.scaleY = 1.3;
obstacle.x = 2048 + 100;
obstacle.y = groundY;
obstacle.speed = gameSpeed;
jellyObstacles.push(obstacle);
}
function spawnGummyBee() {
var bee = game.addChild(new GummyBee());
bee.scaleX = 1.3;
bee.scaleY = 1.3;
bee.x = 2048 + 100;
// Create varied flight patterns - some high (requiring jumps), some low (safe at ground)
var flightType = Math.random();
if (flightType < 0.4) {
// High flying bees that require jumping (40% chance)
var baseHeight = groundY - 250 - Math.random() * 150;
bee.verticalRange = 15 + Math.random() * 20; // Much smaller range for high flyers
} else {
// Low flying bees that are safe when character is on ground (60% chance)
var baseHeight = groundY - 150 - Math.random() * 100;
bee.verticalRange = 20 + Math.random() * 30; // Reduced range for low flyers
}
bee.y = baseHeight;
bee.baseY = baseHeight;
bee.speed = gameSpeed;
gummyBees.push(bee);
}
function spawnSparkleGem() {
var gem = game.addChild(new SparkleGem());
gem.scaleX = 1.2;
gem.scaleY = 1.2;
gem.x = 2048 + 100;
gem.y = groundY - 150 - Math.random() * 350;
gem.speed = gameSpeed;
sparkleGems.push(gem);
}
function spawnShadowBat() {
var bat = game.addChild(new ShadowBat());
bat.x = 2048 + 100;
bat.y = groundY - 350 - Math.random() * 200;
bat.speed = gameSpeed;
bat.groundY = groundY; // Set ground reference for diving
bat.zigzagRange = 200 + Math.random() * 100; // Increased range for more dramatic movement
bat.zigzagSpeed = 4 + Math.random() * 2; // Slightly faster zigzag
shadowBats.push(bat);
}
function spawnAirCube() {
var cube = game.addChild(new AirCube());
cube.x = 2048 + 100;
cube.y = groundY - 280; // Much higher position for character to pass underneath
cube.baseY = groundY - 280;
cube.speed = gameSpeed;
airCubes.push(cube);
}
function spawnCloud() {
var cloud = cloudsContainer.addChild(new Cloud());
cloud.x = 2048 + 200;
cloud.y = 200 + Math.random() * 800; // Clouds in upper portion of sky
clouds.push(cloud);
}
function spawnBackgroundElement() {
var element = backgroundElementsContainer.addChild(new BackgroundElement());
element.x = 2048 + 300;
element.y = groundY; // Position at ground level
// Apply current theme tint to newly spawned elements
if (element.elementGraphics) {
if (currentTheme === 'night') {
element.elementGraphics.tint = 0x4d4d4d; // 70% darkness for night
} else {
element.elementGraphics.tint = 0xd9d9d9; // 85% brightness for day (clearer houses)
}
}
backgroundElements.push(element);
}
function transitionBackground(newTheme) {
if (isTransitioning || currentTheme === newTheme) return;
isTransitioning = true;
if (newTheme === 'night') {
// Transition to night
tween(dayBg, {
alpha: 0
}, {
duration: 2000,
easing: tween.easeInOut
});
tween(nightBg, {
alpha: 1
}, {
duration: 2000,
easing: tween.easeInOut
});
// Show twinkling stars during night
tween(starsContainer, {
alpha: 1
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: function onFinish() {
currentTheme = 'night';
isTransitioning = false;
}
});
// Hide clouds during night
tween(cloudsContainer, {
alpha: 0
}, {
duration: 2000,
easing: tween.easeInOut
});
// Darken background elements during night (70% darker)
tween(backgroundElementsContainer, {
alpha: 1,
tint: 0x4d4d4d
}, {
duration: 2000,
easing: tween.easeInOut
});
// Apply 70% darkness to all individual background elements (houses and trees)
for (var i = 0; i < backgroundElements.length; i++) {
var element = backgroundElements[i];
if (element && (element.elementType.indexOf('house') !== -1 || element.elementType.indexOf('tree') !== -1)) {
tween(element, {
tint: 0x4d4d4d
}, {
duration: 2000,
easing: tween.easeInOut
});
}
}
} else {
// Transition to day
tween(nightBg, {
alpha: 0
}, {
duration: 2000,
easing: tween.easeInOut
});
tween(dayBg, {
alpha: 1
}, {
duration: 2000,
easing: tween.easeInOut
});
// Hide twinkling stars during day
tween(starsContainer, {
alpha: 0
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: function onFinish() {
currentTheme = 'day';
isTransitioning = false;
}
});
// Show clouds during day
tween(cloudsContainer, {
alpha: 1
}, {
duration: 2000,
easing: tween.easeInOut
});
// Brighten all houses and trees during day (85% brightness for clearer houses)
tween(backgroundElementsContainer, {
alpha: 1,
tint: 0xd9d9d9
}, {
duration: 2000,
easing: tween.easeInOut
});
// Apply 85% brightness to all individual background elements (houses and trees)
for (var i = 0; i < backgroundElements.length; i++) {
var element = backgroundElements[i];
if (element && (element.elementType.indexOf('house') !== -1 || element.elementType.indexOf('tree') !== -1)) {
tween(element, {
tint: 0xd9d9d9
}, {
duration: 2000,
easing: tween.easeInOut
});
}
}
}
}
function restartGame() {
// Reset player position
fluffball.x = 300;
fluffball.y = groundY - 80;
fluffball.velocityY = 0;
fluffball.isJumping = false;
fluffball.isDucking = false;
fluffball.canDoubleJump = false;
fluffball.hasDoubleJumped = false;
// Reset game speed
gameSpeed = 6;
// Clear all obstacles and collectibles
for (var i = jellyObstacles.length - 1; i >= 0; i--) {
jellyObstacles[i].destroy();
}
jellyObstacles = [];
for (var i = gummyBees.length - 1; i >= 0; i--) {
gummyBees[i].destroy();
}
gummyBees = [];
for (var i = sparkleGems.length - 1; i >= 0; i--) {
sparkleGems[i].destroy();
}
sparkleGems = [];
for (var i = shadowBats.length - 1; i >= 0; i--) {
shadowBats[i].destroy();
}
shadowBats = [];
for (var i = airCubes.length - 1; i >= 0; i--) {
airCubes[i].destroy();
}
airCubes = [];
for (var i = clouds.length - 1; i >= 0; i--) {
clouds[i].destroy();
}
clouds = [];
for (var i = backgroundElements.length - 1; i >= 0; i--) {
backgroundElements[i].destroy();
}
backgroundElements = [];
// Reset spawn timers
obstacleSpawnTimer = 0;
gemSpawnTimer = 0;
// Reset theme to day
currentTheme = 'day';
dayBg.alpha = 1;
nightBg.alpha = 0;
starsContainer.alpha = 0;
cloudsContainer.alpha = 1;
}
function checkCollisions() {
// Check jelly obstacles
for (var i = jellyObstacles.length - 1; i >= 0; i--) {
var obstacle = jellyObstacles[i];
// Initialize last intersection state if not set
if (obstacle.lastIntersecting === undefined) {
obstacle.lastIntersecting = false;
}
var currentIntersecting = fluffball.intersects(obstacle);
// Only trigger collision on the exact frame when intersection starts
if (!obstacle.lastIntersecting && currentIntersecting) {
// Lose a life
lives--;
livesText.setText('♥ ' + lives);
LK.getSound('hit').play();
LK.effects.flashScreen(0xff0000, 500);
// Flash lives text
LK.effects.flashObject(livesText, 0xFFFFFF, 500);
// Check if game over
if (lives <= 0) {
LK.showGameOver();
return;
} else {
// Restart game while preserving score
restartGame();
return;
}
}
// Update last intersection state
obstacle.lastIntersecting = currentIntersecting;
// Perfect pass bonus
if (!obstacle.passed && obstacle.x < fluffball.x - 50) {
obstacle.passed = true;
LK.setScore(LK.getScore() + perfectPassBonus);
// Animate score text
tween(scoreTxt, {
tint: 0xFF0000,
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(scoreTxt, {
tint: 0xFFFFFF,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 150,
easing: tween.easeIn
});
}
});
}
// Remove off-screen obstacles
if (obstacle.x < -100) {
obstacle.destroy();
jellyObstacles.splice(i, 1);
}
}
// Check gummy bees
for (var i = gummyBees.length - 1; i >= 0; i--) {
var bee = gummyBees[i];
var canCollide = true;
// If fluffball is on ground, prevent collision with bees flying too high
if (!fluffball.isJumping) {
// Character collision area when on ground - extends from feet to head
var fluffballTop = fluffball.y - 80; // Top of character
var fluffballBottom = fluffball.y + 80; // Bottom of character
// Only collide if bee is within the character's ground-level collision zone
if (bee.y < fluffballTop - 20) {
// Bee is too high above character when on ground - no collision
canCollide = false;
}
}
// Initialize last intersection state if not set
if (bee.lastIntersecting === undefined) {
bee.lastIntersecting = false;
}
var currentIntersecting = fluffball.intersects(bee) && canCollide;
// Only trigger collision on the exact frame when intersection starts
if (!bee.lastIntersecting && currentIntersecting) {
// Lose a life
lives--;
livesText.setText('♥ ' + lives);
LK.getSound('hit').play();
LK.effects.flashScreen(0xff0000, 500);
// Flash lives text
LK.effects.flashObject(livesText, 0xFFFFFF, 500);
// Check if game over
if (lives <= 0) {
LK.showGameOver();
return;
} else {
// Restart game while preserving score
restartGame();
return;
}
}
// Update last intersection state
bee.lastIntersecting = currentIntersecting;
// Perfect pass bonus
if (!bee.passed && bee.x < fluffball.x - 50) {
bee.passed = true;
LK.setScore(LK.getScore() + perfectPassBonus);
// Animate score text
tween(scoreTxt, {
tint: 0xFF0000,
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(scoreTxt, {
tint: 0xFFFFFF,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 150,
easing: tween.easeIn
});
}
});
}
// Remove off-screen bees
if (bee.x < -100) {
bee.destroy();
gummyBees.splice(i, 1);
}
}
// Check sparkle gems
for (var i = sparkleGems.length - 1; i >= 0; i--) {
var gem = sparkleGems[i];
// Initialize last intersection state if not set
if (gem.lastIntersecting === undefined) {
gem.lastIntersecting = false;
}
var currentIntersecting = fluffball.intersects(gem) && !gem.collected;
// Only trigger collection on the exact frame when intersection starts
if (!gem.lastIntersecting && currentIntersecting) {
gem.collected = true;
LK.setScore(LK.getScore() + gemValue);
LK.getSound('collect').play();
LK.effects.flashObject(gem, 0xffffff, 200);
// Animate score text
tween(scoreTxt, {
tint: 0xFF0000,
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(scoreTxt, {
tint: 0xFFFFFF,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 150,
easing: tween.easeIn
});
}
});
// Remove gem with tween
tween(gem, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 200,
onFinish: function onFinish() {
gem.destroy();
}
});
sparkleGems.splice(i, 1);
} else if (gem.x < -100) {
gem.destroy();
sparkleGems.splice(i, 1);
}
// Update last intersection state
gem.lastIntersecting = currentIntersecting;
}
// Check shadow bats
for (var i = shadowBats.length - 1; i >= 0; i--) {
var bat = shadowBats[i];
// Initialize last intersection state if not set
if (bat.lastIntersecting === undefined) {
bat.lastIntersecting = false;
}
var currentIntersecting = fluffball.intersects(bat);
// Only trigger collision on the exact frame when intersection starts
if (!bat.lastIntersecting && currentIntersecting) {
// Lose a life
lives--;
livesText.setText('♥ ' + lives);
LK.getSound('hit').play();
LK.effects.flashScreen(0xff0000, 500);
// Flash lives text
LK.effects.flashObject(livesText, 0xFFFFFF, 500);
// Check if game over
if (lives <= 0) {
LK.showGameOver();
return;
} else {
// Restart game while preserving score
restartGame();
return;
}
}
// Update last intersection state
bat.lastIntersecting = currentIntersecting;
// Perfect pass bonus
if (!bat.passed && bat.x < fluffball.x - 50) {
bat.passed = true;
LK.setScore(LK.getScore() + perfectPassBonus);
// Animate score text
tween(scoreTxt, {
tint: 0xFF0000,
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(scoreTxt, {
tint: 0xFFFFFF,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 150,
easing: tween.easeIn
});
}
});
}
// Remove off-screen bats
if (bat.x < -100) {
bat.destroy();
shadowBats.splice(i, 1);
}
}
// Check air cubes - with collision detection enabled
for (var i = airCubes.length - 1; i >= 0; i--) {
var cube = airCubes[i];
// Initialize last intersection state if not set
if (cube.lastIntersecting === undefined) {
cube.lastIntersecting = false;
}
var currentIntersecting = fluffball.intersects(cube);
// Only trigger collision on the exact frame when intersection starts
if (!cube.lastIntersecting && currentIntersecting) {
// Lose a life
lives--;
livesText.setText('♥ ' + lives);
LK.getSound('hit').play();
LK.effects.flashScreen(0xff0000, 500);
// Flash lives text
LK.effects.flashObject(livesText, 0xFFFFFF, 500);
// Check if game over
if (lives <= 0) {
LK.showGameOver();
return;
} else {
// Restart game while preserving score
restartGame();
return;
}
}
// Update last intersection state
cube.lastIntersecting = currentIntersecting;
// Perfect pass bonus
if (!cube.passed && cube.x < fluffball.x - 50) {
cube.passed = true;
LK.setScore(LK.getScore() + perfectPassBonus);
// Animate score text
tween(scoreTxt, {
tint: 0xFF0000,
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(scoreTxt, {
tint: 0xFFFFFF,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 150,
easing: tween.easeIn
});
}
});
}
// Remove off-screen cubes
if (cube.x < -100) {
cube.destroy();
airCubes.splice(i, 1);
}
}
}
// Input handlers
game.down = function (x, y, obj) {
if (isGameRunning) {
var currentTime = Date.now();
var timeSinceLastClick = currentTime - lastClickTime;
// Check for double click
if (timeSinceLastClick < doubleClickDelay) {
// Double click detected - trigger double jump immediately
fluffball.jump();
} else {
// Single click - trigger jump immediately
fluffball.jump();
}
lastClickTime = currentTime;
isHolding = true;
// Start ducking after a short delay if still holding
LK.setTimeout(function () {
if (isHolding) {
fluffball.startDuck();
}
}, 200);
}
};
game.up = function (x, y, obj) {
isHolding = false;
fluffball.stopDuck();
};
// Main game loop
game.update = function () {
// Handle intro video timing
if (isIntroPlaying) {
var currentTime = Date.now();
var elapsedTime = currentTime - introStartTime;
// Show "COMENZAR" button after 10 seconds
if (elapsedTime >= introDuration && introStartBtn.alpha === 0) {
tween(introStartBtn, {
alpha: 1
}, {
duration: 500,
easing: tween.easeOut
});
}
// Auto-skip intro after video ends (if it ends before manual skip)
if (elapsedTime >= introDuration + 2000) {
// Give 2 extra seconds buffer
endIntro();
}
return; // Don't run game logic during intro
}
if (!isGameRunning) return;
// Increase speed gradually
if (gameSpeed < maxSpeed) {
gameSpeed += speedIncrement;
}
// Update speed display
var speedMultiplier = Math.floor(gameSpeed / 6 * 10) / 10;
speedTxt.setText('SPEED: ' + speedMultiplier + 'x');
// Update score display
scoreTxt.setText(LK.getScore().toString());
// Spawn obstacles with increased spacing
obstacleSpawnTimer++;
if (obstacleSpawnTimer > 160 - gameSpeed * 4) {
obstacleSpawnTimer = 0;
var randomValue = Math.random();
if (randomValue < 0.4) {
spawnJellyObstacle();
} else if (randomValue < 0.75) {
spawnGummyBee();
} else if (randomValue < 0.9) {
spawnShadowBat();
} else {
spawnAirCube();
}
}
// Spawn gems with increased spacing
gemSpawnTimer++;
if (gemSpawnTimer > 220) {
gemSpawnTimer = 0;
if (Math.random() < 0.4) {
spawnSparkleGem();
}
}
// Update all objects speeds
for (var i = 0; i < jellyObstacles.length; i++) {
jellyObstacles[i].speed = gameSpeed;
}
for (var i = 0; i < gummyBees.length; i++) {
gummyBees[i].speed = gameSpeed;
}
for (var i = 0; i < sparkleGems.length; i++) {
sparkleGems[i].speed = gameSpeed;
}
for (var i = 0; i < shadowBats.length; i++) {
shadowBats[i].speed = gameSpeed;
}
for (var i = 0; i < airCubes.length; i++) {
airCubes[i].speed = gameSpeed;
}
// Spawn clouds only during day theme
if (currentTheme === 'day' && Math.random() < 0.005) {
// Low spawn rate for decoration
spawnCloud();
}
// Spawn background elements at ground level
if (Math.random() < 0.008) {
// Spawn rate for ground-level scenery
spawnBackgroundElement();
}
// Update and manage clouds
for (var i = clouds.length - 1; i >= 0; i--) {
var cloud = clouds[i];
if (cloud.x < -300) {
// Remove off-screen clouds
cloud.destroy();
clouds.splice(i, 1);
}
}
// Update and manage background elements
for (var i = backgroundElements.length - 1; i >= 0; i--) {
var element = backgroundElements[i];
if (element.x < -400) {
// Remove off-screen background elements
element.destroy();
backgroundElements.splice(i, 1);
}
}
// Check collisions
checkCollisions();
// Background transition based on score every 100 points
var score = LK.getScore();
var scoreThreshold = Math.floor(score / 100);
if (scoreThreshold % 2 === 1) {
// Odd hundreds (100, 300, 500...) = night
transitionBackground('night');
} else {
// Even hundreds (0, 200, 400...) = day
transitionBackground('day');
}
};
// Game logo setup
var gameLogo = game.addChild(LK.getAsset('gameLogo', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2 - 800,
alpha: 0.7,
scaleX: 0.6,
scaleY: 0.6
}));
// Start breathing animation for logo
function startLogoBreathing() {
tween(gameLogo, {
scaleX: 1.02,
scaleY: 1.02
}, {
duration: 1200,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(gameLogo, {
scaleX: 0.98,
scaleY: 0.98
}, {
duration: 1200,
easing: tween.easeInOut,
onFinish: function onFinish() {
startLogoBreathing(); // Loop the animation
}
});
}
});
}
// Start the breathing animation
startLogoBreathing();
// Initialize music state from storage (default to true if not set)
var isMusicEnabled = storage.musicEnabled !== undefined ? storage.musicEnabled : true;
// Lives system
var lives = 3;
var livesText = new Text2('♥ ' + lives, {
size: 60,
fill: 0xFF0000,
stroke: 0x000000,
strokeThickness: 4,
font: "'Arial Black', 'Helvetica', sans-serif"
});
livesText.anchor.set(0.5, 0.5);
LK.gui.topLeft.addChild(livesText);
livesText.x = 150;
livesText.y = 120;
livesText.alpha = 0; // Hidden during intro
// Music control button
var musicBtn = new Text2(isMusicEnabled ? '♪' : '♪✗', {
size: 50,
fill: isMusicEnabled ? 0x00FF00 : 0xFFFFFF,
stroke: 0x000000,
strokeThickness: 3,
font: "'Arial Black', 'Helvetica', sans-serif"
});
musicBtn.anchor.set(0.5, 0.5);
LK.gui.topRight.addChild(musicBtn);
musicBtn.x = -200;
musicBtn.y = 120;
musicBtn.alpha = 0; // Hidden during intro
// Intro video system
var isIntroPlaying = true;
var introStartTime = 0;
var introDuration = 10000; // 10 seconds in milliseconds
// Show intro video at game start
isGameRunning = false; // Start with game paused
// Create intro overlay
var introOverlay = game.addChild(new Container());
// Add black background for intro
var introBlackBg = introOverlay.attachAsset('underground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
scaleX: 1,
scaleY: 5.464
});
introBlackBg.tint = 0x000000;
// Add video placeholder background (using existing shape)
var introVideo = introOverlay.attachAsset('introVideo', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 20.48,
scaleY: 27.32
});
// Create stars container for intro background effect
var introStarsContainer = introOverlay.addChild(new Container());
var introStars = [];
// Generate twinkling stars for intro background covering full screen
for (var i = 0; i < 150; i++) {
var introStar = introStarsContainer.addChild(new Star());
introStar.x = Math.random() * 2048;
introStar.y = Math.random() * 2732; // Stars covering full screen
introStars.push(introStar);
}
// Add game logo to intro
var introGameLogo = introOverlay.attachAsset('gameLogo', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 800,
scaleX: 0.3,
scaleY: 0.3,
alpha: 0
});
// Animate logo to appear gradually from background
tween(introGameLogo, {
alpha: 0.9,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 3000,
easing: tween.easeOut
});
// Skip intro button (always visible)
var skipBtn = new Text2('TOXIK STUDIO Games', {
size: 40,
fill: 0xFFFFFF,
stroke: 0x000000,
strokeThickness: 3,
font: "'Arial Black', 'Helvetica', sans-serif"
});
skipBtn.anchor.set(0.5, 0.5);
introOverlay.addChild(skipBtn);
skipBtn.x = 1024;
skipBtn.y = 1366;
// Start button (appears after 10 seconds)
var introStartBtn = new Text2('PLAY', {
size: 80,
fill: 0x00FF00,
stroke: 0x000000,
strokeThickness: 6,
font: "'Arial Black', 'Helvetica', sans-serif"
});
introStartBtn.anchor.set(0.5, 0.5);
introOverlay.addChild(introStartBtn);
introStartBtn.x = 1024;
introStartBtn.y = 2048; // Center of lower half (2732 * 0.75)
introStartBtn.alpha = 0; // Start hidden
// Function to end intro and show tutorial
function endIntro() {
isIntroPlaying = false;
// Show UI elements after intro
livesText.alpha = 1;
speedTxt.alpha = 1;
musicBtn.alpha = 1;
// Start background music if enabled
if (isMusicEnabled) {
try {
LK.playMusic('backgroundMusic');
} catch (e) {
console.log('Music failed to play:', e);
}
}
// Fade out intro overlay
tween(introOverlay, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
introOverlay.destroy();
showTutorial(); // Show tutorial after intro
}
});
}
// Function to show tutorial (moved from inline code)
function showTutorial() {
// Show tutorial at game start
isGameRunning = false; // Start with game paused
// Create tutorial overlay
var tutorialOverlay = game.addChild(new Container());
// Semi-transparent background
var overlayBg = tutorialOverlay.attachAsset('underground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 2732,
alpha: 0.8,
scaleX: 1,
scaleY: 5.464
});
overlayBg.tint = 0x000000;
// Tutorial title
var titleText = new Text2('HOW TO PLAY', {
size: 80,
fill: 0xFFFFFF,
stroke: 0x000000,
strokeThickness: 6,
font: "'Arial Black', 'Helvetica', sans-serif"
});
titleText.anchor.set(0.5, 0.5);
tutorialOverlay.addChild(titleText);
titleText.x = 1024;
titleText.y = 400;
// Tutorial instructions in English
var instructionTexts = ['TAP TO JUMP', 'HOLD TO DUCK', 'DOUBLE TAP FOR DOUBLE JUMP', 'AVOID OBSTACLES', 'COLLECT GEMS ✦', 'PASS CLOSE TO ENEMIES = +10 POINTS'];
for (var i = 0; i < instructionTexts.length; i++) {
var instructText = new Text2(instructionTexts[i], {
size: 50,
fill: 0xFFFFFF,
stroke: 0x000000,
strokeThickness: 3,
font: "'Arial Black', 'Helvetica', sans-serif"
});
instructText.anchor.set(0.5, 0.5);
tutorialOverlay.addChild(instructText);
instructText.x = 1024;
instructText.y = 600 + i * 80;
}
// Start game button
var startBtn = new Text2('START GAME', {
size: 60,
fill: 0x00FF00,
stroke: 0x000000,
strokeThickness: 4,
font: "'Arial Black', 'Helvetica', sans-serif"
});
startBtn.anchor.set(0.5, 0.5);
tutorialOverlay.addChild(startBtn);
startBtn.x = 1024;
startBtn.y = 1200;
// Start button interaction
startBtn.down = function () {
LK.effects.flashObject(startBtn, 0xFFFFFF, 200);
// Start the game
isGameRunning = true;
// Fade out and remove tutorial
tween(tutorialOverlay, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
tutorialOverlay.destroy();
}
});
};
// Allow starting by tapping background
overlayBg.down = function () {
// Start the game
isGameRunning = true;
tween(tutorialOverlay, {
alpha: 0
}, {
duration: 300,
onFinish: function onFinish() {
tutorialOverlay.destroy();
}
});
};
// Fade in tutorial
tutorialOverlay.alpha = 0;
tween(tutorialOverlay, {
alpha: 1
}, {
duration: 300
});
}
// Skip intro button interaction
skipBtn.down = function () {
LK.effects.flashObject(skipBtn, 0xFFFF00, 200);
endIntro();
};
// Start button interaction (appears after 10 seconds)
introStartBtn.down = function () {
LK.effects.flashObject(introStartBtn, 0xFFFFFF, 200);
endIntro();
};
// Start intro timing (no video playback needed)
introStartTime = Date.now();
// Fade in intro overlay
introOverlay.alpha = 0;
tween(introOverlay, {
alpha: 1
}, {
duration: 500
});
// Music button interaction
musicBtn.down = function (x, y, obj) {
// Toggle music state
isMusicEnabled = !isMusicEnabled;
// Save to storage
storage.musicEnabled = isMusicEnabled;
// Update button appearance and text
musicBtn.setText(isMusicEnabled ? '♪' : '♪✗');
musicBtn.fill = isMusicEnabled ? 0x00FF00 : 0xFFFFFF;
// Flash button to show it was pressed
LK.effects.flashObject(musicBtn, 0xFFFF00, 300);
// Control music playback
if (isMusicEnabled) {
try {
LK.playMusic('backgroundMusic');
} catch (e) {
console.log('Music failed to play:', e);
}
} else {
try {
LK.stopMusic();
} catch (e) {
console.log('Music failed to stop:', e);
}
}
};
// Music will start after intro ends
;
;
Chibi style, incredibly cute, perfectly round, puffy creature. **It has no visible legs or limbs, appearing as a soft, adorable ball with a friendly, expressive face (big, innocent eyes and a small smile).** Implied motion is a gentle bounce or glide. Predominant color: light pink with subtle fluffy texture. Minimalist, clean lines, vibrant colors. Solid light blue background. Mobile game character asset, running/gliding animation frame.. In-Game asset. 2d. High contrast. No shadows
ojos cerrados
Kawaii, cute, pink logo for 'Fluffball's Endless Adventure'. The design should feature a whimsical, bubbly, and rounded font for the text, with soft, sweet aesthetics and subtle playful elements integrated into the typography itself (e.g., tiny sparkles, soft gradients, or a slight bouncy feel to the letters). No character or drawing should be included, only the stylized text.. In-Game asset. 2d. High contrast. No shadows
nube blanca sin bordes minimalista, silueta unica y original. In-Game asset. 2d. High contrast. No shadows
circulo blanco sin bordes. In-Game asset. 2d. High contrast. No shadows
kawaii shadown bat de lado. In-Game asset. 2d. High contrast. No shadows
pink cute rec tangle kawaii. In-Game asset. 2d. High contrast. No shadows
a single simple kawaii tree without outlines and no face, for a non-distracting game background. In-Game asset. 2d. High contrast. No shadows
a single simple kawaii house without outlines and no face, for a non-distracting game background. In-Game asset. 2d. High contrast. No shadows
a single simple kawaii house without outlines and no face, for a non-distracting game background. In-Game asset. 2d. High contrast. No shadows
a single simple kawaii house without outlines and no face, for a non-distracting game background. In-Game asset. 2d. High contrast. No shadows