User prompt
debe pestañar cada 2 segundos
User prompt
hagamoslo que cada tantos segundos cierre sus ojos ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
las abejas no deben tocar nunca su cabeza estando en el piso
User prompt
haz que tenga doble salto al hacer doble click ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
cuando saltaa me gustaria que el personaje gire un poco paraa atras y cuando cae se haga para adelante y en el piso quede en su posicion original para darle dinamismo ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
quisiera que el fondo sea una imagen y cuando pasen los 1000 puntos se haga de noche y tenga una transicion y a los proximos mil puntos de dia y asi susesivamente ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
el salto esta quedando corto debera ser mas alto para que llegue adapta todo
User prompt
Ensure the game elements (character, obstacles, ground) are scaled to occupy a significant portion of the screen width and height, providing a clear and impactful visual presence for hypercasual play on mobile devices.
Code edit (1 edits merged)
Please save this source code
User prompt
Fluffball's Endless Adventure
Initial prompt
¡Absolutamente! Aquí tienes un prompt extremadamente detallado, diseñado para un generador de juegos de IA (o para ser tu plan maestro), cubriendo todos los aspectos esenciales para un juego hipercasual genial, adorable y adictivo, al estilo del dinosaurio de Google pero con tu toque único. Prompt de Diseño de Juego: "Aventura Adorable Sin Fin" (The Adorable Endless Journey) [Start Game Design Prompt] Game Title: The Adorable Endless Journey (o un nombre similar como "Fluffball Dash", "Bloop's Big Run") Genre: Hypercasual Endless Runner (Side-Scrolling) Core Concept: A charming, visually rich, and highly addictive endless runner. The player controls an adorable, auto-running character, navigating an increasingly challenging world by performing precise jumps and ducks to avoid cute obstacles. The goal is to achieve the highest possible score by surviving the longest distance. Art Style & Aesthetics: Primary Style: High-quality, vibrant, and clean 2D digital illustration / cartoon art, with a touch of pixel art influence for crispness in movement, but overall a modern, polished look. Aesthetic Tone: Whimsical, heartwarming, joyful, and slightly goofy. Emphasizes cuteness and charm over complexity. Color Palette: Bright, pastel, and saturated colors. Focus on harmonious combinations (e.g., candy pinks, sky blues, mint greens, sunny yellows). Character Design: Main Character: A unique, custom-designed, spherical or pill-shaped creature (e.g., a "Fluffball," "Bloop," or a tiny round animal like a "Pudgy Panda Cub"). Must be incredibly cute and instantly lovable, with expressive eyes and simple, fluid animations. Animations: Smooth, distinct run cycle, an exaggerated arc jump (with a momentary hang at the apex), a quick, comical squish/roll for ducking, and a charming, non-frustrating "stumble/poof" animation for Game Over. Obstacle Design: Visuals: Reimagined, non-threatening obstacles that are visually amusing. Examples: "Wobbly Jelly Mounds" (requiring jumps), "Low-Flying Gummy Bees" (requiring ducks), "Grumpy Mushroom Towers" (varied heights), "Spinning Lollipop Gates" (requiring precise timing). Animation: Simple, cyclical animations (e.g., jelly wobbles, bees flap, lollipops spin). Looping Background: Visual Richness: Multi-layered parallax scrolling background to create depth. Layers should move at different speeds. Themes: Transition between visually distinct, charming themes (e.g., "Candy Cloud Kingdom," "Sparkle Forest," "Gummy Desert Oasis"). Each theme features unique, adorable environmental elements (e.g., ice cream mountains, marshmallow clouds, candy cane trees, giant smiling flowers, twinkling stars). Seamless Loop: The background segments must seamlessly loop, ensuring no visible seams or jarring transitions. The environment should feel endless and continuously evolving. User Interface (UI): Clean & Minimalist: Non-intrusive UI elements that complement the art style. Score Display: Large, clear, and cute numeric font for the current score at the top center/right. High Score Indicator: Subtle "HI" alongside the highest score achieved. Game Over Screen: Clean layout with "Game Over" message, Final Score, High Score, and prominent "Retry" and "Main Menu" buttons. Menu Buttons: Large, rounded, soft-colored buttons with clear icons/text. Gameplay Mechanics (Core Loop): Auto-Running: The main character continuously runs from left to right across the screen. Jump: Player taps anywhere on the screen (or presses spacebar on keyboard) to make the character jump. Jump height and distance are fixed and optimized for clearing obstacles. Duck/Crouch: Player taps and holds down on the screen (or presses down arrow key on keyboard) to make the character duck/crouch. Releasing the tap/key returns the character to a running state. Obstacle Avoidance: Player must time jumps and ducks to avoid colliding with various cute obstacles. Collision results in Game Over. Scoring: Score increases continuously based on distance run (e.g., 1 point per pixel or fixed increment). Perfect Pass Bonus: Award bonus points (e.g., +50 points) for "perfectly" clearing an obstacle (e.g., jumping/ducking at the last possible moment without collision). Visually indicate with a small "PERFECT!" pop-up. Collectible Items (Optional, but adds depth): Sparkle Gems: Small, glittering objects appearing occasionally. Collecting them grants bonus points and a small burst of positive visual/sound effects. Rainbow Power-up: Rare item granting temporary invincibility or a speed boost, with a vibrant visual effect. Progression & Difficulty: Gradual Speed Increase: The character's running speed (and thus obstacle frequency) gradually increases over time, making the game progressively more challenging. Obstacle Variety: New obstacle types and increasingly complex patterns (e.g., double jumps, jump-then-duck sequences) are introduced as the game speed increases. Theme Transitions: After a certain score threshold or time, the background theme should subtly transition to a new one, offering visual refreshment. Sound Design: Background Music (BGM): Style: Upbeat, cheerful, whimsical, and highly loopable instrumental track. Utilizes light orchestral instruments (flute, pizzicato strings), bells, xylophone, and a simple, catchy melody. Dynamic: Music should subtly increase in tempo/intensity as the game speed increases, then seamlessly return to a base loop. Sound Effects (SFX): Jump: A bouncy "boing!" or a light, airy "whoosh." Duck: A cute "squish" or a quick "roll" sound. Collision: A gentle, comical "bonk," "thump," or "swoosh-fall" sound – must not be harsh or frustrating. Perfect Pass: A crisp "ding!" or "chime" sound. Collectible: A sparkling "bling" or "twinkle." Game Over: A short, gentle "wah-wah-waaah" or a soft, sighing sound. Volume Control: Simple toggle for Music On/Off and SFX On/Off in settings. Adictiveness Factors: Simple & Intuitive Controls: One-touch/one-button gameplay for instant pick-up-and-play. Instant Replayability: Quick "Retry" after Game Over encourages immediate re-engagement. High Score Chase: Fundamental motivation for repeated play. Satisfying Feedback: Visual and audio cues for successful jumps, ducks, and combos. Charming Aesthetic: Cute characters and vibrant worlds create an emotional connection. Unlocking Content: Potential for unlocking new adorable characters or background themes with high scores/in-game currency. Monetization Strategy (Standard Hypercasual): Rewarded Video Ads: "Get a second chance" after Game Over (e.g., continue from current score). Double final score. Unlock cosmetic items (characters/themes) without earning currency. Interstitial Ads: Placed sparingly between game sessions (e.g., every 3-5 runs) to minimize disruption. In-App Purchase (IAP): "Remove All Ads" option for a one-time purchase. Soft Currency: Earned passively through distance run, used to unlock new characters or background themes. Key Performance Indicators (KPIs): Session Length: Aim for short, highly replayable sessions (1-3 minutes average). Retention: High D1, D7, D30 retention driven by addictive core loop, charming visuals, and progression. Ad Impressions per User / eCPM: Track ad effectiveness. Playtime: Total time spent in game by users. [End Game Design Prompt]
/****
* 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