/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// BackgroundPatterns class to create psychedelic moving patterns
var BackgroundPatterns = Container.expand(function () {
var self = Container.call(this);
// Pattern elements
var patterns = [];
var patternCount = 15;
// Initialize patterns
self.init = function () {
// Create various geometric shapes
for (var i = 0; i < patternCount; i++) {
var pattern = LK.getAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.2 + Math.random() * 0.1,
rotation: Math.random() * Math.PI * 2,
scale: 0.2 + Math.random() * 0.3
});
// Randomize starting positions
pattern.x = Math.random() * 2048;
pattern.y = Math.random() * 2732;
// Set movement parameters
pattern.speedX = (Math.random() - 0.5) * 2;
pattern.speedY = (Math.random() - 0.5) * 2;
pattern.rotSpeed = (Math.random() - 0.5) * 0.05;
pattern.pulseSpeed = 0.02 + Math.random() * 0.03;
pattern.pulsePhase = Math.random() * Math.PI * 2;
patterns.push(pattern);
self.addChild(pattern);
}
};
// Update method for movement and effects
self.update = function () {
for (var i = 0; i < patterns.length; i++) {
var pattern = patterns[i];
// Move pattern
pattern.x += pattern.speedX;
pattern.y += pattern.speedY;
// Rotate pattern
pattern.rotation += pattern.rotSpeed;
// Pulse size
pattern.pulsePhase += pattern.pulseSpeed;
pattern.scale.x = pattern.scale.y = 0.2 + Math.sin(pattern.pulsePhase) * 0.15;
// Cycle colors
pattern.tint = Math.random() * 0xFFFFFF;
// Wrap around screen edges
if (pattern.x < -100) {
pattern.x = 2148;
}
if (pattern.x > 2148) {
pattern.x = -100;
}
if (pattern.y < -100) {
pattern.y = 2832;
}
if (pattern.y > 2832) {
pattern.y = -100;
}
}
};
return self;
});
// Bird class representing the player character
var Bird = Container.expand(function () {
var self = Container.call(this);
// Constants for physics
var GRAVITY = 1.0; // Reduced gravity for easier play
var FLAP_STRENGTH = -20; // Reduced flap strength for smoother control
var MAX_ROTATION = Math.PI / 4; // Max tilt angle (45 degrees)
var MIN_ROTATION = -Math.PI / 6; // Min tilt angle (-30 degrees)
// Bird's vertical velocity
self.velocityY = 0;
// Attach the bird graphic asset
var birdGraphics = self.attachAsset('bird', {
anchorX: 0.5,
anchorY: 0.5
});
// Public method for flapping
self.flap = function () {
self.velocityY = FLAP_STRENGTH;
LK.getSound('flapSound').play(); // Play flap sound
// Optional: Add a quick visual feedback tween on flap
tween.stop(birdGraphics); // Stop previous scale tweens
birdGraphics.scale.set(1.1, 1.1);
tween(birdGraphics.scale, {
x: 1,
y: 1
}, {
duration: 100
});
};
// Flag to track if player is holding down
self.isHolding = false;
// Method to set holding state
self.setHolding = function (holding) {
self.isHolding = holding;
};
// Update method called every frame by the engine
self.update = function () {
// Only apply gravity if the game has started
if (gameStarted) {
// Auto-flap when player is holding down and bird is falling
if (self.isHolding && self.velocityY > 5) {
self.flap();
}
// Apply gravity
self.velocityY += GRAVITY;
self.y += self.velocityY;
// Clamp position to screen bounds (leaving some margin)
if (self.y < birdGraphics.height / 2) {
self.y = birdGraphics.height / 2;
self.velocityY = 0; // Stop moving up if hitting the ceiling
}
// Note: Ground collision is handled in the main game update loop
// Apply rotation based on velocity
var targetRotation = 0;
if (self.velocityY < 0) {
// Moving up
targetRotation = MIN_ROTATION; // Tilt slightly up
} else if (self.velocityY > 10) {
// Moving down fast
// Gradually tilt down based on velocity, capped at MAX_ROTATION
targetRotation = Math.min(MAX_ROTATION, self.velocityY / 50 * MAX_ROTATION);
}
// Smoothly rotate towards the target rotation
birdGraphics.rotation += (targetRotation - birdGraphics.rotation) * 0.1;
}
};
return self;
});
// KaleidoscopeEffect class for creating trippy kaleidoscope-like effects
var KaleidoscopeEffect = Container.expand(function () {
var self = Container.call(this);
// Kaleidoscope properties
self.segments = 6; // Number of reflection segments
self.angleOffset = 0; // Current rotation offset
self.rotationSpeed = 0.01; // Speed of rotation
self.particles = []; // Particles for the effect
self.particleCount = 8; // Number of particles
// Initialize the kaleidoscope effect
self.init = function () {
// Create particles that will be reflected in the kaleidoscope
for (var i = 0; i < self.particleCount; i++) {
var particle = LK.getAsset('bird', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.5,
scale: 0.5 + Math.random() * 0.5,
tint: Math.random() * 0xFFFFFF
});
// Position particles in a circular pattern
var angle = i / self.particleCount * Math.PI * 2;
var distance = 200 + Math.random() * 300;
particle.x = Math.cos(angle) * distance;
particle.y = Math.sin(angle) * distance;
// Movement parameters
particle.orbitSpeed = 0.01 + Math.random() * 0.02;
particle.orbitDistance = distance;
particle.orbitAngle = angle;
particle.pulseSpeed = 0.05 + Math.random() * 0.05;
particle.pulsePhase = Math.random() * Math.PI * 2;
self.particles.push(particle);
self.addChild(particle);
}
};
// Update the kaleidoscope effect
self.update = function () {
// Rotate the entire kaleidoscope
self.angleOffset += self.rotationSpeed;
self.rotation = self.angleOffset;
// Update particles
for (var i = 0; i < self.particles.length; i++) {
var particle = self.particles[i];
// Orbit motion
particle.orbitAngle += particle.orbitSpeed;
particle.x = Math.cos(particle.orbitAngle) * particle.orbitDistance;
particle.y = Math.sin(particle.orbitAngle) * particle.orbitDistance;
// Pulse size
particle.pulsePhase += particle.pulseSpeed;
particle.scale.x = particle.scale.y = 0.5 + Math.sin(particle.pulsePhase) * 0.3;
// Cycle colors
particle.tint = Math.floor(Math.random() * 0xFFFFFF);
}
};
return self;
});
// Flag to track if player is holding down
// ObstaclePair class representing a top and bottom obstacle
var ObstaclePair = Container.expand(function () {
var self = Container.call(this);
// Constants for obstacles
var GAP_HEIGHT = 800; // Space between top and bottom obstacles (increased from 550)
var OBSTACLE_WIDTH = 200; // Keep consistent with asset init
// Create top and bottom obstacles
self.topObstacle = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 1.0 // Anchor at the bottom-center
});
self.bottomObstacle = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.0 // Anchor at the top-center
});
// Property to track if score has been awarded for this pair
self.scored = false;
// Method to set the position and gap
self.setup = function (xPos, gapCenterY) {
self.x = xPos;
// Position obstacles relative to the container's position
self.topObstacle.y = gapCenterY - GAP_HEIGHT / 2;
self.bottomObstacle.y = gapCenterY + GAP_HEIGHT / 2;
// Randomize color for psychedelic effect
var randomColor = Math.random() * 0xFFFFFF;
self.topObstacle.tint = randomColor;
self.bottomObstacle.tint = randomColor;
};
// Method to check collision with the bird using built-in intersects
self.collidesWith = function (bird) {
// Check intersection between the bird container and the top/bottom obstacle assets.
// The intersects() method compares the bounding boxes of the display objects.
if (bird.intersects(self.topObstacle) || bird.intersects(self.bottomObstacle)) {
return true;
}
return false;
};
// Update method for movement
self.update = function (speed) {
self.x -= speed;
};
return self;
});
// StartScreen class to show the intro screen with enhanced animations
var StartScreen = Container.expand(function () {
var self = Container.call(this);
// Create container for title letters
var titleContainer = new Container();
titleContainer.y = -300;
self.addChild(titleContainer);
// Create vortex background effect
var vortexContainer = new Container();
self.addChild(vortexContainer);
vortexContainer.x = 0;
vortexContainer.y = 0;
// Create vortex circles
var vortexCircles = [];
for (var i = 0; i < 12; i++) {
var circle = LK.getAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.15,
scale: 0.1 + i / 10,
tint: Math.random() * 0xFFFFFF
});
circle.rotation = Math.random() * Math.PI * 2;
circle.baseScale = 0.1 + i / 10;
circle.rotSpeed = 0.01 + i * 0.003;
vortexCircles.push(circle);
vortexContainer.addChild(circle);
}
// Individual letter animation for title
var titleText = "TRIPPY FLAP";
var letterSpacing = 80;
var letters = [];
var letterColors = [];
// Create individual letter sprites for animated title
for (var i = 0; i < titleText.length; i++) {
var letter = new Text2(titleText[i], {
size: 220,
fill: 0xFFFFFF
});
letter.anchor.set(0.5, 0.5);
letter.x = (i - titleText.length / 2) * letterSpacing;
letter.y = 0;
letter.alpha = 0;
letter.scale.set(0);
letterColors[i] = Math.random() * 0xFFFFFF;
letter.origY = letter.y;
letters.push(letter);
titleContainer.addChild(letter);
}
// Create glowing effect for title
var glowEffect = new Container();
titleContainer.addChild(glowEffect);
var glowBg = LK.getAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5,
scale: 3,
alpha: 0.3,
tint: 0xFFFF00
});
glowEffect.addChild(glowBg);
glowEffect.y = 30;
// Create instruction text with cool styling
var instructionText = new Text2('Tap to Start', {
size: 140,
fill: 0xFFFFFF
});
instructionText.anchor.set(0.5, 0.5);
instructionText.y = 100;
instructionText.alpha = 0;
self.addChild(instructionText);
// Add pulsing border around instruction text
var instructionBorder = LK.getAsset('bird', {
anchorX: 0.5,
anchorY: 0.5,
scale: 5,
alpha: 0.4,
tint: 0x00FFFF
});
instructionBorder.y = 100;
instructionBorder.alpha = 0;
self.addChild(instructionBorder);
// Create control instructions text
var controlText = new Text2('Tap to flap, hold to auto-flap!', {
size: 80,
fill: 0xFFFFFF
});
controlText.anchor.set(0.5, 0.5);
controlText.y = 250;
controlText.alpha = 0;
self.addChild(controlText);
// Create bird trail container
var birdTrailContainer = new Container();
self.addChild(birdTrailContainer);
// Create a sample bird to show on start screen
var sampleBird = LK.getAsset('bird', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -100,
scale: 0
});
self.addChild(sampleBird);
// Create bird clones for echo effect
var birdEchoes = [];
for (var i = 0; i < 5; i++) {
var echo = LK.getAsset('bird', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -100,
scale: 0,
alpha: 0.15 - i * 0.03
});
birdEchoes.push(echo);
self.addChild(echo);
}
// Create decorative particles
var particles = [];
for (var i = 0; i < 30; i++) {
var particle = LK.getAsset('bird', {
anchorX: 0.5,
anchorY: 0.5,
scale: 0.2 + Math.random() * 0.3,
alpha: 0.2 + Math.random() * 0.3
});
particle.x = (Math.random() - 0.5) * 1500;
particle.y = (Math.random() - 0.5) * 2000;
particle.speedX = (Math.random() - 0.5) * 5;
particle.speedY = (Math.random() - 0.5) * 5;
particle.rotSpeed = (Math.random() - 0.5) * 0.1;
particle.pulseSpeed = Math.random() * 0.05 + 0.02;
particle.pulsePhase = Math.random() * Math.PI * 2;
particles.push(particle);
self.addChild(particle);
}
// Properties for animation effects
var colorPhase = 0;
var floatPhase = 0;
var introAnimationStarted = false;
var introAnimationCompleted = false;
var trailTimer = 0;
var vortexAngle = 0;
// Initialize with intro animation
self.startIntroAnimation = function () {
if (introAnimationStarted) {
return;
}
introAnimationStarted = true;
// Animate vortex to fade in
vortexCircles.forEach(function (circle, index) {
circle.alpha = 0;
LK.setTimeout(function () {
tween(circle, {
alpha: 0.15
}, {
duration: 800,
easing: tween.easeIn
});
}, index * 80);
});
// Animate title letters sequentially
letters.forEach(function (letter, index) {
LK.setTimeout(function () {
tween(letter, {
alpha: 1,
y: letter.origY - 200
}, {
duration: 300,
easing: tween.easeOut
});
tween(letter.scale, {
x: 1.5,
y: 1.5
}, {
duration: 400,
easing: tween.elastic,
onFinish: function onFinish() {
tween(letter.scale, {
x: 1,
y: 1
}, {
duration: 300,
easing: tween.easeOut
});
tween(letter, {
y: letter.origY
}, {
duration: 500,
easing: tween.bounce
});
}
});
}, index * 120);
});
// Animate glow effect
LK.setTimeout(function () {
glowBg.alpha = 0;
tween(glowBg, {
alpha: 0.3
}, {
duration: 1000,
easing: tween.easeIn
});
}, letters.length * 120);
// Animate bird after title
LK.setTimeout(function () {
// Bird dramatic entrance
sampleBird.scaleX = 0;
sampleBird.scaleY = 0;
sampleBird.alpha = 1;
// Setup echoes
birdEchoes.forEach(function (echo) {
echo.scale = {
x: 0,
y: 0
}; // Initialize scale as an object with x and y properties
echo.alpha = 0;
});
tween(sampleBird, {
x: 2.5,
y: 2.5,
scaleX: 0,
scaleY: 0
}, {
duration: 600,
easing: tween.elastic,
onFinish: function onFinish() {
tween(sampleBird, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 400,
easing: tween.easeOut
});
// Animate bird echoes with delay
birdEchoes.forEach(function (echo, idx) {
LK.setTimeout(function () {
echo.alpha = 0.15 - idx * 0.03;
// Ensure echo has proper scale object before tweening
echo.scale = echo.scale || {};
echo.scale.x = echo.scale.x || 0;
echo.scale.y = echo.scale.y || 0;
tween(echo.scale, {
x: 1.3 - idx * 0.15,
y: 1.3 - idx * 0.15
}, {
duration: 300,
easing: tween.easeOut
});
}, idx * 100);
});
// Flash screen for dramatic effect
LK.effects.flashScreen(0xFFFFFF, 300);
// Show instructions after bird appears
tween(instructionText, {
alpha: 1
}, {
duration: 500,
easing: tween.easeIn
});
// Animate instruction border
tween(instructionBorder, {
alpha: 0.4,
rotation: Math.PI * 2
}, {
duration: 800,
easing: tween.easeIn
});
tween(controlText, {
alpha: 1
}, {
duration: 500,
easing: tween.easeIn
});
introAnimationCompleted = true;
}
});
}, letters.length * 120 + 200);
};
self.update = function () {
// Start intro animation if not started yet
if (!introAnimationStarted) {
self.startIntroAnimation();
}
// Update vortex animation
vortexAngle += 0.01;
vortexCircles.forEach(function (circle, index) {
circle.rotation += circle.rotSpeed;
circle.scale.x = circle.scale.y = circle.baseScale + Math.sin(vortexAngle + index * 0.2) * 0.05;
circle.tint = Math.random() * 0xFFFFFF;
});
// Rotate and pulse glow effect
if (glowBg) {
glowBg.rotation += 0.01;
glowBg.scale.x = glowBg.scale.y = 3 + Math.sin(colorPhase * 2) * 0.3;
glowBg.tint = Math.floor(Math.random() * 0xFFFFFF);
}
// Update background particles
particles.forEach(function (particle) {
particle.x += particle.speedX;
particle.y += particle.speedY;
particle.rotation += particle.rotSpeed;
// Wrap particles around screen
if (particle.x < -1000) {
particle.x = 1000;
}
if (particle.x > 1000) {
particle.x = -1000;
}
if (particle.y < -1500) {
particle.y = 1500;
}
if (particle.y > 1500) {
particle.y = -1500;
}
// Pulsate particles with unique timing
particle.pulsePhase += particle.pulseSpeed;
particle.scale.x = particle.scale.y = 0.2 + Math.sin(particle.pulsePhase) * 0.15;
particle.tint = Math.random() * 0xFFFFFF;
});
// Update instruction border
if (instructionBorder) {
instructionBorder.rotation += 0.02;
instructionBorder.scale.x = instructionBorder.scale.y = 5 + Math.sin(floatPhase * 3) * 0.5;
}
// Only run these effects once intro is complete
if (introAnimationCompleted) {
// Floating animation for bird
floatPhase += 0.05;
sampleBird.y = -100 + Math.sin(floatPhase) * 50;
// Update bird echoes to follow with delay
birdEchoes.forEach(function (echo, idx) {
var delay = (idx + 1) * 2;
echo.y = -100 + Math.sin(floatPhase - idx * 0.3) * 50;
echo.rotation = sampleBird.rotation * (1 - idx * 0.2);
echo.tint = Math.floor(Math.random() * 0xFFFFFF);
});
// Color cycling for title letters
colorPhase += 0.02;
letters.forEach(function (letter, index) {
var r = Math.sin(colorPhase + index * 0.3) * 127 + 128;
var g = Math.sin(colorPhase + index * 0.3 + 2) * 127 + 128;
var b = Math.sin(colorPhase + index * 0.3 + 4) * 127 + 128;
var letterColor = Math.floor(r) << 16 | Math.floor(g) << 8 | Math.floor(b);
letter.fill = letterColor;
// Make letters dance with more varied movements
letter.y = letter.origY + Math.sin(floatPhase + index * 0.3) * 20;
letter.rotation = Math.sin(floatPhase * 0.5 + index) * 0.15;
letter.scale.x = 1 + Math.sin(floatPhase * 0.3 + index * 0.2) * 0.1;
letter.scale.y = 1 + Math.cos(floatPhase * 0.3 + index * 0.2) * 0.1;
});
// Pulse the instruction text with glow effect
instructionText.scale.x = 1 + Math.sin(floatPhase * 2) * 0.15;
instructionText.scale.y = 1 + Math.sin(floatPhase * 2) * 0.15;
var pulseColor = Math.floor(Math.sin(floatPhase) * 127 + 128) << 16 | Math.floor(Math.sin(floatPhase + 2) * 127 + 128) << 8 | Math.floor(Math.sin(floatPhase + 4) * 127 + 128);
instructionText.fill = pulseColor;
// Make control text "breathe"
controlText.scale.x = controlText.scale.y = 1 + Math.sin(floatPhase * 1.5) * 0.08;
controlText.rotation = Math.sin(floatPhase * 0.2) * 0.05;
controlText.fill = Math.floor(Math.sin(floatPhase + 1) * 127 + 128) << 16 | Math.floor(Math.sin(floatPhase + 3) * 127 + 128) << 8 | Math.floor(Math.sin(floatPhase + 5) * 127 + 128);
// Apply trippy effect to sample bird
sampleBird.rotation = Math.sin(floatPhase) * 0.2;
sampleBird.tint = Math.floor(Math.random() * 0xFFFFFF);
// Create bird trails
trailTimer++;
if (trailTimer >= 8) {
trailTimer = 0;
var trail = LK.getAsset('bird', {
anchorX: 0.5,
anchorY: 0.5,
x: sampleBird.x,
y: sampleBird.y,
rotation: sampleBird.rotation,
scale: 1.2,
alpha: 0.4,
tint: Math.random() * 0xFFFFFF
});
birdTrailContainer.addChild(trail);
// Animate trail fade out
tween(trail, {
alpha: 0,
scale: 0.5
}, {
duration: 700,
easing: tween.easeOut,
onFinish: function onFinish() {
if (trail.parent) {
trail.parent.removeChild(trail);
}
}
});
}
}
};
return self;
});
// TrailEffect class to create psychedelic trailing effect behind objects
var TrailEffect = Container.expand(function () {
var self = Container.call(this);
// Trail properties
self.maxTrails = 10; // Maximum number of trail elements
self.fadeSpeed = 0.1; // How quickly trails fade out
self.trailElements = []; // Array to store trail elements
self.target = null; // The object to trail
self.trailSpacing = 3; // Frames between trail elements
self.frameCount = 0; // Counter for spacing
// Initialize the trail effect for a target
self.init = function (target, assetId) {
self.target = target;
self.assetId = assetId;
// Clear any existing trails
self.clearTrails();
};
// Clear all trail elements
self.clearTrails = function () {
for (var i = 0; i < self.trailElements.length; i++) {
if (self.trailElements[i].parent) {
self.trailElements[i].parent.removeChild(self.trailElements[i]);
}
}
self.trailElements = [];
};
// Update the trail effect
self.update = function () {
if (!self.target) {
return;
}
self.frameCount++;
// Add new trail element at intervals
if (self.frameCount >= self.trailSpacing) {
self.frameCount = 0;
// Create new trail element
var trail = LK.getAsset(self.assetId, {
anchorX: 0.5,
anchorY: 0.5,
x: self.target.x,
y: self.target.y,
rotation: self.target.rotation,
alpha: 0.6
});
// Apply random psychedelic color
trail.tint = Math.random() * 0xFFFFFF;
// Add to game and track
game.addChild(trail);
self.trailElements.push(trail);
// Remove oldest trail if we exceed max
if (self.trailElements.length > self.maxTrails) {
var oldest = self.trailElements.shift();
if (oldest.parent) {
oldest.parent.removeChild(oldest);
}
}
}
// Fade out all trail elements
for (var i = 0; i < self.trailElements.length; i++) {
self.trailElements[i].alpha -= self.fadeSpeed;
self.trailElements[i].scale.x += 0.03;
self.trailElements[i].scale.y += 0.03;
// Remove if fully faded
if (self.trailElements[i].alpha <= 0) {
if (self.trailElements[i].parent) {
self.trailElements[i].parent.removeChild(self.trailElements[i]);
}
self.trailElements.splice(i, 1);
i--;
}
}
};
return self;
});
// WaveEffect class to create psychedelic visual distortions
var WaveEffect = Container.expand(function () {
var self = Container.call(this);
// Effect properties
self.amplitude = 20; // Wave height
self.frequency = 0.005; // Wave frequency
self.speed = 0.05; // Wave speed
self.colorSpeed = 0.01; // Color change speed
self.phase = 0; // Current phase of the wave
self.colorPhase = 0; // Current phase of the color cycle
// Apply wave distortion to a target object
self.applyDistortion = function (target) {
if (!target) {
return;
}
// Update phases
self.phase += self.speed;
self.colorPhase += self.colorSpeed;
// Apply wave effect to scale/rotation
target.scale.x = 1 + Math.sin(self.phase) * 0.1;
target.scale.y = 1 + Math.cos(self.phase * 1.3) * 0.1;
target.rotation = Math.sin(self.phase * 0.7) * 0.1;
// Apply trippy color effect
var r = Math.sin(self.colorPhase) * 127 + 128;
var g = Math.sin(self.colorPhase + 2) * 127 + 128;
var b = Math.sin(self.colorPhase + 4) * 127 + 128;
var color = r << 16 | g << 8 | b;
target.tint = color;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a001a // Start with a dark purple background
});
/****
* Game Code
****/
// Game constants
// Define assets for the game. These will be created automatically by the engine.
// Bird asset
// Obstacle asset (will be tinted/resized later)
// Sound for flapping (optional, but good practice)
// Sound for passing obstacle (optional)
// Sound for collision (optional)
// Include the tween plugin for animations and effects
// Initialize music for background
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var BASE_OBSTACLE_SPEED = 5; // Base speed that will increase over time
var OBSTACLE_SPEED = BASE_OBSTACLE_SPEED; // Current speed
var MAX_OBSTACLE_SPEED = 15; // Maximum speed cap
var SPEED_INCREASE_RATE = 0.2; // How much to increase speed each time
var SPEED_INCREASE_INTERVAL = 10000; // Increase speed every 10 seconds (in ms)
var OBSTACLE_SPAWN_RATE = 180; // Ticks between spawns (approx 3 seconds at 60fps)
var MIN_OBSTACLE_SPAWN_RATE = 60; // Minimum spawn rate (faster spawning)
var OBSTACLE_START_X = GAME_WIDTH + 150; // Start spawning off-screen right
var OBSTACLE_MIN_Y = 600; // Min center Y for the gap
var OBSTACLE_MAX_Y = GAME_HEIGHT - 600; // Max center Y for the gap
var speedIncreaseTimer = null; // Timer for speed increases
// Game variables
var bird;
var obstacles = [];
var spawnTimer = 0;
var isGameOver = false;
var gameStarted = false; // New variable to track if game has started
var backgroundChangeTimer = 0;
var backgroundTween = null; // To manage background color tween
var startScreen; // Variable to hold the start screen
// Psychedelic effect variables
var waveEffect;
var birdTrail;
var globalDistortionPhase = 0;
var screenShakeIntensity = 0;
// Score display with psychedelic styling
var scoreTxt = new Text2('0', {
size: 180,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Position score text slightly down from the top center, avoiding top-left corner
scoreTxt.y = 100;
// Setup score text color animation
LK.setInterval(function () {
var r = Math.floor(Math.random() * 255);
var g = Math.floor(Math.random() * 255);
var b = Math.floor(Math.random() * 255);
var randomColor = r << 16 | g << 8 | b;
// Animate score text color change
tween(scoreTxt, {
fill: randomColor
}, {
duration: 500,
easing: tween.easeInOut
});
// Add pulsating scale effect
tween(scoreTxt.scale, {
x: 1.2,
y: 1.2
}, {
duration: 250,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(scoreTxt.scale, {
x: 1.0,
y: 1.0
}, {
duration: 250,
easing: tween.easeIn
});
}
});
}, 1000);
// Initialize bird
bird = new Bird();
bird.x = GAME_WIDTH / 4; // Start position
bird.y = GAME_HEIGHT / 2;
game.addChild(bird);
// Initialize psychedelic effects
waveEffect = new WaveEffect();
birdTrail = new TrailEffect();
birdTrail.init(bird, 'bird');
// Handle player input (flap and hold)
game.down = function (x, y, obj) {
if (!gameStarted) {
// Start the game on first tap with enhanced transition
gameStarted = true;
// Create beat pulse effect across the screen
for (var i = 0; i < 4; i++) {
var beatRing = LK.getAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5,
x: startScreen.x,
y: startScreen.y,
scale: 0.1,
alpha: 0.7,
tint: 0xFFFFFF
});
game.addChild(beatRing);
// Animate beat ring expanding outward
tween(beatRing, {
scale: 10,
alpha: 0
}, {
duration: 800 + i * 200,
easing: tween.easeOut,
onFinish: function onFinish() {
if (beatRing.parent) {
beatRing.parent.removeChild(beatRing);
}
}
});
}
// Dramatic zoom effect on the start screen
tween(startScreen.scale, {
x: 1.2,
y: 1.2
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
// Create explosive particle effect with more diversity
for (var i = 0; i < 40; i++) {
// Use different shapes/sizes for particles
var assetType = Math.random() < 0.7 ? 'bird' : 'obstacle';
var particle = LK.getAsset(assetType, {
anchorX: 0.5,
anchorY: 0.5,
x: startScreen.x,
y: startScreen.y,
scale: 0.15 + Math.random() * 0.35,
alpha: 0.7 + Math.random() * 0.3,
tint: Math.random() * 0xFFFFFF,
rotation: Math.random() * Math.PI * 2
});
var angle = Math.random() * Math.PI * 2;
var distance = 500 + Math.random() * 1000;
var duration = 500 + Math.random() * 1000;
game.addChild(particle);
// Create more complex particle animation path
var midX = startScreen.x + Math.cos(angle) * distance * 0.5;
var midY = startScreen.y + Math.sin(angle) * distance * 0.5;
var midScale = 0.4 + Math.random() * 0.6;
// Two-stage animation for more interesting movement
tween(particle, {
x: midX,
y: midY,
alpha: 0.8,
rotation: Math.random() * Math.PI * 2,
scale: midScale
}, {
duration: duration * 0.4,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(particle, {
x: startScreen.x + Math.cos(angle) * distance,
y: startScreen.y + Math.sin(angle) * distance,
alpha: 0,
rotation: particle.rotation + Math.random() * Math.PI * 4
}, {
duration: duration * 0.6,
easing: tween.easeOut,
onFinish: function onFinish() {
if (particle.parent) {
particle.parent.removeChild(particle);
}
}
});
}
});
}
// Multi-color flash sequence for transition
var flashColors = [0xFFFFFF, 0xFF00FF, 0x00FFFF];
flashColors.forEach(function (color, index) {
LK.setTimeout(function () {
LK.effects.flashScreen(color, 150);
}, index * 100);
});
// Create radial wave effect
for (var i = 0; i < 3; i++) {
LK.setTimeout(function () {
// Create wave ring that expands from center
var wave = LK.getAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2,
y: GAME_HEIGHT / 2,
alpha: 0.4,
scale: 0.1,
tint: Math.random() * 0xFFFFFF
});
game.addChild(wave);
tween(wave, {
scale: 15,
alpha: 0
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
if (wave.parent) {
wave.parent.removeChild(wave);
}
}
});
}, i * 200);
}
// Fade out start screen with spiral effect
tween(startScreen, {
alpha: 0,
rotation: Math.PI / 2
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
game.removeChild(startScreen);
}
});
}
});
// Reset score
LK.setScore(0);
scoreTxt.setText('0');
// Start game with a small delay to allow fade transition
LK.setTimeout(function () {
// Create entering animation for score text
scoreTxt.alpha = 0;
scoreTxt.scale.set(3);
scoreTxt.rotation = -0.2;
// Show score text with animation
tween(scoreTxt, {
alpha: 1,
rotation: 0
}, {
duration: 500,
easing: tween.easeOut
});
tween(scoreTxt.scale, {
x: 1,
y: 1
}, {
duration: 800,
easing: tween.elastic
});
// Create colorful score indicator particles
for (var i = 0; i < 10; i++) {
var scoreParticle = LK.getAsset('bird', {
anchorX: 0.5,
anchorY: 0.5,
x: scoreTxt.parent.x,
y: scoreTxt.parent.y + scoreTxt.y,
scale: 0.2,
alpha: 0.7,
tint: Math.random() * 0xFFFFFF
});
LK.gui.top.addChild(scoreParticle);
var angle = Math.random() * Math.PI * 2;
var distance = 100 + Math.random() * 200;
tween(scoreParticle, {
x: scoreParticle.x + Math.cos(angle) * distance,
y: scoreParticle.y + Math.sin(angle) * distance,
alpha: 0,
scale: 0.05
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
if (scoreParticle.parent) {
scoreParticle.parent.removeChild(scoreParticle);
}
}
});
}
}, 600);
} else if (!isGameOver) {
bird.flap();
// Set holding state to true
bird.setHolding(true);
// Add extra psychedelic effects on flap
screenShakeIntensity = 15; // Increased screen shake intensity
// Flash screen with random vivid color
var flashColor = Math.random() * 0xFFFFFF;
LK.effects.flashScreen(flashColor, 150);
// Create a radial burst of colorful particles from the bird position
for (var i = 0; i < 8; i++) {
var angle = i / 8 * Math.PI * 2;
var particle = LK.getAsset('bird', {
anchorX: 0.5,
anchorY: 0.5,
x: bird.x,
y: bird.y,
scale: 0.3,
alpha: 0.7,
tint: Math.random() * 0xFFFFFF
});
game.addChild(particle);
tween(particle, {
x: bird.x + Math.cos(angle) * 200,
y: bird.y + Math.sin(angle) * 200,
alpha: 0,
rotation: Math.random() * Math.PI * 2
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
if (particle.parent) {
particle.parent.removeChild(particle);
}
}
});
}
// Temporary background color flash
var currentBg = game.backgroundColor;
game.backgroundColor = 0xFFFFFF;
LK.setTimeout(function () {
game.backgroundColor = currentBg;
}, 50);
// Start the speed increase timer if it hasn't been started yet
if (!speedIncreaseTimer) {
speedIncreaseTimer = LK.setInterval(function () {
if (!isGameOver && gameStarted) {
// Increase obstacle speed
OBSTACLE_SPEED = Math.min(MAX_OBSTACLE_SPEED, OBSTACLE_SPEED + SPEED_INCREASE_RATE);
// Decrease spawn rate (for more obstacles)
OBSTACLE_SPAWN_RATE = Math.max(MIN_OBSTACLE_SPAWN_RATE, OBSTACLE_SPAWN_RATE - 5);
// Visual feedback for speed increase
LK.effects.flashScreen(0xFFFFFF, 200);
// Show speed increase text
var speedMsg = new Text2("Speed Up!", {
size: 100,
fill: 0xFFFF00
});
speedMsg.anchor.set(0.5, 0.5);
speedMsg.x = GAME_WIDTH / 2;
speedMsg.y = GAME_HEIGHT / 2;
game.addChild(speedMsg);
// Animate and remove the text
tween(speedMsg, {
alpha: 0,
y: speedMsg.y - 100
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
if (speedMsg.parent) {
speedMsg.parent.removeChild(speedMsg);
}
}
});
}
}, SPEED_INCREASE_INTERVAL);
}
}
};
// Handle player releasing the screen
game.up = function (x, y, obj) {
if (gameStarted && !isGameOver) {
// Set holding state to false when player releases
bird.setHolding(false);
}
};
// Function to spawn a new obstacle pair
function spawnObstacle() {
var newObstacle = new ObstaclePair();
var gapCenterY = Math.random() * (OBSTACLE_MAX_Y - OBSTACLE_MIN_Y) + OBSTACLE_MIN_Y;
newObstacle.setup(OBSTACLE_START_X, gapCenterY);
obstacles.push(newObstacle);
game.addChild(newObstacle);
}
// Function for psychedelic background effect
function changeBackgroundColor() {
if (backgroundTween) {
tween.stop(game); // Stop existing background tween if any
}
// Generate more intense psychedelic colors - using ultra high saturation/values
var h = Math.random();
var s = 1.0; // Maximum saturation
var v = 0.9 + Math.random() * 0.1; // High value (0.9-1.0)
// Prefer certain hue ranges for more vibrant psychedelic colors
if (Math.random() < 0.7) {
// 70% chance to use these psychedelic color ranges:
var ranges = [[0.7, 0.9],
// purples
[0.45, 0.55],
// cyans
[0.1, 0.2],
// yellows/greens
[0.95, 1.0] // deep reds
];
var selectedRange = ranges[Math.floor(Math.random() * ranges.length)];
h = selectedRange[0] + Math.random() * (selectedRange[1] - selectedRange[0]);
}
// HSV to RGB conversion for more vibrant colors
var i = Math.floor(h * 6);
var f = h * 6 - i;
var p = v * (1 - s);
var q = v * (1 - f * s);
var t = v * (1 - (1 - f) * s);
var r, g, b;
switch (i % 6) {
case 0:
r = v;
g = t;
b = p;
break;
case 1:
r = q;
g = v;
b = p;
break;
case 2:
r = p;
g = v;
b = t;
break;
case 3:
r = p;
g = q;
b = v;
break;
case 4:
r = t;
g = p;
b = v;
break;
case 5:
r = v;
g = p;
b = q;
break;
}
var nextColor = Math.floor(r * 255) << 16 | Math.floor(g * 255) << 8 | Math.floor(b * 255);
// Ensure a valid color value is used (default to purple if undefined)
nextColor = nextColor || 0x800080;
// Ensure we have a valid color before starting the tween
if (isNaN(nextColor) || nextColor === undefined) {
nextColor = 0x800080; // Default to purple if undefined or NaN
}
// Make sure the current background color is valid
var currentBgColor = game.backgroundColor;
if (isNaN(currentBgColor) || currentBgColor === undefined) {
currentBgColor = 0x800080; // Use purple as fallback
game.backgroundColor = currentBgColor;
}
// Create a direct tween between valid color values
backgroundTween = tween(game, {
backgroundColor: nextColor || 0x800080 // Ensure a valid color is used
}, {
duration: 800,
// Faster transitions for more trippy effect
easing: tween.easeInOut,
onFinish: function onFinish() {
backgroundTween = null;
} // Clear the tween reference on finish
});
}
// Initialize background patterns and kaleidoscope effect
var backgroundPatterns = new BackgroundPatterns();
backgroundPatterns.init();
game.addChild(backgroundPatterns);
// Initialize kaleidoscope effect
var kaleidoscopeEffect = new KaleidoscopeEffect();
kaleidoscopeEffect.init();
kaleidoscopeEffect.x = GAME_WIDTH / 2;
kaleidoscopeEffect.y = GAME_HEIGHT / 2;
game.addChild(kaleidoscopeEffect);
// Game update loop
game.update = function () {
if (!gameStarted) {
// Only update start screen when game hasn't started
startScreen.update();
// Also update background patterns and kaleidoscope for pre-game effects
backgroundPatterns.update();
kaleidoscopeEffect.update();
return;
}
if (isGameOver) {
return;
} // Stop updates if game over
// Update global distortion phase
globalDistortionPhase += 0.05; // Increased for more intense effects
// Apply screen shake effect (gradually reduces intensity)
if (screenShakeIntensity > 0) {
game.x = (Math.random() * 2 - 1) * screenShakeIntensity;
game.y = (Math.random() * 2 - 1) * screenShakeIntensity;
screenShakeIntensity *= 0.9;
if (screenShakeIntensity < 0.5) {
screenShakeIntensity = 0;
game.x = 0;
game.y = 0;
}
}
// Update background patterns and kaleidoscope for more psychedelic effects
backgroundPatterns.update();
kaleidoscopeEffect.update();
// Apply occasional camera zoom effects for trippy sensation
if (Math.random() < 0.01) {
var zoomScale = 0.95 + Math.random() * 0.1; // Random zoom between 0.95 and 1.05
tween(game.scale, {
x: zoomScale,
y: zoomScale
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(game.scale, {
x: 1,
y: 1
}, {
duration: 400,
easing: tween.easeInOut
});
}
});
}
// Update bird and psychedelic effects
bird.update(); // Calls bird's internal update for physics
waveEffect.applyDistortion(bird.children[0]);
birdTrail.update();
// Check ground collision
if (bird.y >= GAME_HEIGHT - bird.children[0].height / 2) {
LK.getSound('hitSound').play();
LK.effects.flashScreen(0xFF0000, 300); // Flash red on hit
isGameOver = true;
// Reset speed-related values for next game
OBSTACLE_SPEED = BASE_OBSTACLE_SPEED;
OBSTACLE_SPAWN_RATE = 180;
if (speedIncreaseTimer) {
LK.clearInterval(speedIncreaseTimer);
speedIncreaseTimer = null;
}
LK.showGameOver();
return; // Stop further processing this frame
}
// Update obstacles
spawnTimer++;
if (spawnTimer >= OBSTACLE_SPAWN_RATE) {
spawnObstacle();
spawnTimer = 0;
}
// Move and check obstacles
for (var i = obstacles.length - 1; i >= 0; i--) {
var obstacle = obstacles[i];
obstacle.update(OBSTACLE_SPEED); // Move obstacle left
// Check for collision with bird
if (obstacle.collidesWith(bird)) {
LK.getSound('hitSound').play();
LK.effects.flashObject(bird, 0xFF0000, 300); // Flash bird red
isGameOver = true;
// Reset speed-related values for next game
OBSTACLE_SPEED = BASE_OBSTACLE_SPEED;
OBSTACLE_SPAWN_RATE = 180;
if (speedIncreaseTimer) {
LK.clearInterval(speedIncreaseTimer);
speedIncreaseTimer = null;
}
LK.showGameOver();
return; // Stop further processing this frame
}
// Check for scoring
if (!obstacle.scored && obstacle.x + obstacle.topObstacle.width / 2 < bird.x) {
obstacle.scored = true;
LK.setScore(LK.getScore() + 1);
scoreTxt.setText(LK.getScore());
LK.getSound('scoreSound').play();
// Optional: Flash score text or screen briefly on score
LK.effects.flashObject(scoreTxt, 0x00FF00, 150);
}
// Remove obstacles that are off-screen left
// Obstacle pair's x is its center. Remove when its right edge (x + width/2) is less than 0.
// Use the actual width of the obstacle graphic instance.
if (obstacle.x + obstacle.topObstacle.width / 2 < 0) {
obstacle.destroy();
obstacles.splice(i, 1);
}
}
// Psychedelic background effect timer - more frequent changes
backgroundChangeTimer++;
if (backgroundChangeTimer >= 30) {
// Twice as frequent (every half second)
// Change color more frequently
changeBackgroundColor();
backgroundChangeTimer = 0;
// Sometimes apply a more dramatic flash effect
if (Math.random() < 0.2) {
var flashColor = Math.random() * 0xFFFFFF;
LK.effects.flashScreen(flashColor, 100);
}
}
// Apply ultra intense psychedelic effects to obstacles
obstacles.forEach(function (obs, index) {
// Even more intense distortion effects
var individualPhase = globalDistortionPhase + index * 0.3;
// Extreme scaling with different frequencies for each obstacle
var pulseScaleX = 1 + Math.sin(individualPhase * 1.5) * 0.2;
var pulseScaleY = 1 + Math.cos(individualPhase * 1.2) * 0.25;
// Apply to top obstacle with warping effect
obs.topObstacle.scale.x = pulseScaleX;
obs.topObstacle.scale.y = pulseScaleY;
obs.topObstacle.rotation = Math.sin(individualPhase * 0.9) * 0.15;
// Apply to bottom obstacle with different phase
obs.bottomObstacle.scale.x = pulseScaleX * 0.9;
obs.bottomObstacle.scale.y = pulseScaleY * 1.1;
obs.bottomObstacle.rotation = Math.cos(individualPhase * 0.8) * 0.18;
// Cycle colors using ultra-saturated neon colors
var r = Math.sin(individualPhase * 1.2) * 127 + 128;
var g = Math.sin(individualPhase * 1.2 + 2) * 127 + 128;
var b = Math.sin(individualPhase * 1.2 + 4) * 127 + 128;
// Enhance color saturation for more vivid results
r = Math.min(255, r * 1.2);
g = Math.min(255, g * 1.2);
b = Math.min(255, b * 1.2);
var topColor = Math.floor(r) << 16 | Math.floor(g) << 8 | Math.floor(b);
var bottomColor = 255 - Math.floor(r) << 16 | 255 - Math.floor(g) << 8 | 255 - Math.floor(b);
// Apply colors with occasional random flashes
if (Math.random() < 0.05) {
// Random flash color
topColor = Math.random() * 0xFFFFFF;
bottomColor = Math.random() * 0xFFFFFF;
}
obs.topObstacle.tint = topColor;
obs.bottomObstacle.tint = bottomColor;
});
};
// Start the first background color change
changeBackgroundColor();
// Play background music with fade in effect
LK.playMusic('bgmusic', {
fade: {
start: 0,
end: 0.7,
duration: 2000
}
});
// Initialize start screen with enhanced animation
startScreen = new StartScreen();
startScreen.x = GAME_WIDTH / 2;
startScreen.y = GAME_HEIGHT / 2;
game.addChild(startScreen);
// Apply psychedelic background pulse effect for start screen
LK.setInterval(function () {
// Only pulse background before game starts
if (!gameStarted) {
var h = Math.random();
var s = 0.7 + Math.random() * 0.3;
var v = 0.6 + Math.random() * 0.4;
// HSV to RGB conversion
var i = Math.floor(h * 6);
var f = h * 6 - i;
var p = v * (1 - s);
var q = v * (1 - f * s);
var t = v * (1 - (1 - f) * s);
var r, g, b;
switch (i % 6) {
case 0:
r = v;
g = t;
b = p;
break;
case 1:
r = q;
g = v;
b = p;
break;
case 2:
r = p;
g = v;
b = t;
break;
case 3:
r = p;
g = q;
b = v;
break;
case 4:
r = t;
g = p;
b = v;
break;
case 5:
r = v;
g = p;
b = q;
break;
}
var targetColor = Math.floor(r * 255) << 16 | Math.floor(g * 255) << 8 | Math.floor(b * 255);
// Animate background color change
tween(game, {
backgroundColor: targetColor
}, {
duration: 1500,
easing: tween.easeInOut
});
}
}, 2000);
// Hide score text initially
scoreTxt.alpha = 0; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// BackgroundPatterns class to create psychedelic moving patterns
var BackgroundPatterns = Container.expand(function () {
var self = Container.call(this);
// Pattern elements
var patterns = [];
var patternCount = 15;
// Initialize patterns
self.init = function () {
// Create various geometric shapes
for (var i = 0; i < patternCount; i++) {
var pattern = LK.getAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.2 + Math.random() * 0.1,
rotation: Math.random() * Math.PI * 2,
scale: 0.2 + Math.random() * 0.3
});
// Randomize starting positions
pattern.x = Math.random() * 2048;
pattern.y = Math.random() * 2732;
// Set movement parameters
pattern.speedX = (Math.random() - 0.5) * 2;
pattern.speedY = (Math.random() - 0.5) * 2;
pattern.rotSpeed = (Math.random() - 0.5) * 0.05;
pattern.pulseSpeed = 0.02 + Math.random() * 0.03;
pattern.pulsePhase = Math.random() * Math.PI * 2;
patterns.push(pattern);
self.addChild(pattern);
}
};
// Update method for movement and effects
self.update = function () {
for (var i = 0; i < patterns.length; i++) {
var pattern = patterns[i];
// Move pattern
pattern.x += pattern.speedX;
pattern.y += pattern.speedY;
// Rotate pattern
pattern.rotation += pattern.rotSpeed;
// Pulse size
pattern.pulsePhase += pattern.pulseSpeed;
pattern.scale.x = pattern.scale.y = 0.2 + Math.sin(pattern.pulsePhase) * 0.15;
// Cycle colors
pattern.tint = Math.random() * 0xFFFFFF;
// Wrap around screen edges
if (pattern.x < -100) {
pattern.x = 2148;
}
if (pattern.x > 2148) {
pattern.x = -100;
}
if (pattern.y < -100) {
pattern.y = 2832;
}
if (pattern.y > 2832) {
pattern.y = -100;
}
}
};
return self;
});
// Bird class representing the player character
var Bird = Container.expand(function () {
var self = Container.call(this);
// Constants for physics
var GRAVITY = 1.0; // Reduced gravity for easier play
var FLAP_STRENGTH = -20; // Reduced flap strength for smoother control
var MAX_ROTATION = Math.PI / 4; // Max tilt angle (45 degrees)
var MIN_ROTATION = -Math.PI / 6; // Min tilt angle (-30 degrees)
// Bird's vertical velocity
self.velocityY = 0;
// Attach the bird graphic asset
var birdGraphics = self.attachAsset('bird', {
anchorX: 0.5,
anchorY: 0.5
});
// Public method for flapping
self.flap = function () {
self.velocityY = FLAP_STRENGTH;
LK.getSound('flapSound').play(); // Play flap sound
// Optional: Add a quick visual feedback tween on flap
tween.stop(birdGraphics); // Stop previous scale tweens
birdGraphics.scale.set(1.1, 1.1);
tween(birdGraphics.scale, {
x: 1,
y: 1
}, {
duration: 100
});
};
// Flag to track if player is holding down
self.isHolding = false;
// Method to set holding state
self.setHolding = function (holding) {
self.isHolding = holding;
};
// Update method called every frame by the engine
self.update = function () {
// Only apply gravity if the game has started
if (gameStarted) {
// Auto-flap when player is holding down and bird is falling
if (self.isHolding && self.velocityY > 5) {
self.flap();
}
// Apply gravity
self.velocityY += GRAVITY;
self.y += self.velocityY;
// Clamp position to screen bounds (leaving some margin)
if (self.y < birdGraphics.height / 2) {
self.y = birdGraphics.height / 2;
self.velocityY = 0; // Stop moving up if hitting the ceiling
}
// Note: Ground collision is handled in the main game update loop
// Apply rotation based on velocity
var targetRotation = 0;
if (self.velocityY < 0) {
// Moving up
targetRotation = MIN_ROTATION; // Tilt slightly up
} else if (self.velocityY > 10) {
// Moving down fast
// Gradually tilt down based on velocity, capped at MAX_ROTATION
targetRotation = Math.min(MAX_ROTATION, self.velocityY / 50 * MAX_ROTATION);
}
// Smoothly rotate towards the target rotation
birdGraphics.rotation += (targetRotation - birdGraphics.rotation) * 0.1;
}
};
return self;
});
// KaleidoscopeEffect class for creating trippy kaleidoscope-like effects
var KaleidoscopeEffect = Container.expand(function () {
var self = Container.call(this);
// Kaleidoscope properties
self.segments = 6; // Number of reflection segments
self.angleOffset = 0; // Current rotation offset
self.rotationSpeed = 0.01; // Speed of rotation
self.particles = []; // Particles for the effect
self.particleCount = 8; // Number of particles
// Initialize the kaleidoscope effect
self.init = function () {
// Create particles that will be reflected in the kaleidoscope
for (var i = 0; i < self.particleCount; i++) {
var particle = LK.getAsset('bird', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.5,
scale: 0.5 + Math.random() * 0.5,
tint: Math.random() * 0xFFFFFF
});
// Position particles in a circular pattern
var angle = i / self.particleCount * Math.PI * 2;
var distance = 200 + Math.random() * 300;
particle.x = Math.cos(angle) * distance;
particle.y = Math.sin(angle) * distance;
// Movement parameters
particle.orbitSpeed = 0.01 + Math.random() * 0.02;
particle.orbitDistance = distance;
particle.orbitAngle = angle;
particle.pulseSpeed = 0.05 + Math.random() * 0.05;
particle.pulsePhase = Math.random() * Math.PI * 2;
self.particles.push(particle);
self.addChild(particle);
}
};
// Update the kaleidoscope effect
self.update = function () {
// Rotate the entire kaleidoscope
self.angleOffset += self.rotationSpeed;
self.rotation = self.angleOffset;
// Update particles
for (var i = 0; i < self.particles.length; i++) {
var particle = self.particles[i];
// Orbit motion
particle.orbitAngle += particle.orbitSpeed;
particle.x = Math.cos(particle.orbitAngle) * particle.orbitDistance;
particle.y = Math.sin(particle.orbitAngle) * particle.orbitDistance;
// Pulse size
particle.pulsePhase += particle.pulseSpeed;
particle.scale.x = particle.scale.y = 0.5 + Math.sin(particle.pulsePhase) * 0.3;
// Cycle colors
particle.tint = Math.floor(Math.random() * 0xFFFFFF);
}
};
return self;
});
// Flag to track if player is holding down
// ObstaclePair class representing a top and bottom obstacle
var ObstaclePair = Container.expand(function () {
var self = Container.call(this);
// Constants for obstacles
var GAP_HEIGHT = 800; // Space between top and bottom obstacles (increased from 550)
var OBSTACLE_WIDTH = 200; // Keep consistent with asset init
// Create top and bottom obstacles
self.topObstacle = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 1.0 // Anchor at the bottom-center
});
self.bottomObstacle = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.0 // Anchor at the top-center
});
// Property to track if score has been awarded for this pair
self.scored = false;
// Method to set the position and gap
self.setup = function (xPos, gapCenterY) {
self.x = xPos;
// Position obstacles relative to the container's position
self.topObstacle.y = gapCenterY - GAP_HEIGHT / 2;
self.bottomObstacle.y = gapCenterY + GAP_HEIGHT / 2;
// Randomize color for psychedelic effect
var randomColor = Math.random() * 0xFFFFFF;
self.topObstacle.tint = randomColor;
self.bottomObstacle.tint = randomColor;
};
// Method to check collision with the bird using built-in intersects
self.collidesWith = function (bird) {
// Check intersection between the bird container and the top/bottom obstacle assets.
// The intersects() method compares the bounding boxes of the display objects.
if (bird.intersects(self.topObstacle) || bird.intersects(self.bottomObstacle)) {
return true;
}
return false;
};
// Update method for movement
self.update = function (speed) {
self.x -= speed;
};
return self;
});
// StartScreen class to show the intro screen with enhanced animations
var StartScreen = Container.expand(function () {
var self = Container.call(this);
// Create container for title letters
var titleContainer = new Container();
titleContainer.y = -300;
self.addChild(titleContainer);
// Create vortex background effect
var vortexContainer = new Container();
self.addChild(vortexContainer);
vortexContainer.x = 0;
vortexContainer.y = 0;
// Create vortex circles
var vortexCircles = [];
for (var i = 0; i < 12; i++) {
var circle = LK.getAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.15,
scale: 0.1 + i / 10,
tint: Math.random() * 0xFFFFFF
});
circle.rotation = Math.random() * Math.PI * 2;
circle.baseScale = 0.1 + i / 10;
circle.rotSpeed = 0.01 + i * 0.003;
vortexCircles.push(circle);
vortexContainer.addChild(circle);
}
// Individual letter animation for title
var titleText = "TRIPPY FLAP";
var letterSpacing = 80;
var letters = [];
var letterColors = [];
// Create individual letter sprites for animated title
for (var i = 0; i < titleText.length; i++) {
var letter = new Text2(titleText[i], {
size: 220,
fill: 0xFFFFFF
});
letter.anchor.set(0.5, 0.5);
letter.x = (i - titleText.length / 2) * letterSpacing;
letter.y = 0;
letter.alpha = 0;
letter.scale.set(0);
letterColors[i] = Math.random() * 0xFFFFFF;
letter.origY = letter.y;
letters.push(letter);
titleContainer.addChild(letter);
}
// Create glowing effect for title
var glowEffect = new Container();
titleContainer.addChild(glowEffect);
var glowBg = LK.getAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5,
scale: 3,
alpha: 0.3,
tint: 0xFFFF00
});
glowEffect.addChild(glowBg);
glowEffect.y = 30;
// Create instruction text with cool styling
var instructionText = new Text2('Tap to Start', {
size: 140,
fill: 0xFFFFFF
});
instructionText.anchor.set(0.5, 0.5);
instructionText.y = 100;
instructionText.alpha = 0;
self.addChild(instructionText);
// Add pulsing border around instruction text
var instructionBorder = LK.getAsset('bird', {
anchorX: 0.5,
anchorY: 0.5,
scale: 5,
alpha: 0.4,
tint: 0x00FFFF
});
instructionBorder.y = 100;
instructionBorder.alpha = 0;
self.addChild(instructionBorder);
// Create control instructions text
var controlText = new Text2('Tap to flap, hold to auto-flap!', {
size: 80,
fill: 0xFFFFFF
});
controlText.anchor.set(0.5, 0.5);
controlText.y = 250;
controlText.alpha = 0;
self.addChild(controlText);
// Create bird trail container
var birdTrailContainer = new Container();
self.addChild(birdTrailContainer);
// Create a sample bird to show on start screen
var sampleBird = LK.getAsset('bird', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -100,
scale: 0
});
self.addChild(sampleBird);
// Create bird clones for echo effect
var birdEchoes = [];
for (var i = 0; i < 5; i++) {
var echo = LK.getAsset('bird', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -100,
scale: 0,
alpha: 0.15 - i * 0.03
});
birdEchoes.push(echo);
self.addChild(echo);
}
// Create decorative particles
var particles = [];
for (var i = 0; i < 30; i++) {
var particle = LK.getAsset('bird', {
anchorX: 0.5,
anchorY: 0.5,
scale: 0.2 + Math.random() * 0.3,
alpha: 0.2 + Math.random() * 0.3
});
particle.x = (Math.random() - 0.5) * 1500;
particle.y = (Math.random() - 0.5) * 2000;
particle.speedX = (Math.random() - 0.5) * 5;
particle.speedY = (Math.random() - 0.5) * 5;
particle.rotSpeed = (Math.random() - 0.5) * 0.1;
particle.pulseSpeed = Math.random() * 0.05 + 0.02;
particle.pulsePhase = Math.random() * Math.PI * 2;
particles.push(particle);
self.addChild(particle);
}
// Properties for animation effects
var colorPhase = 0;
var floatPhase = 0;
var introAnimationStarted = false;
var introAnimationCompleted = false;
var trailTimer = 0;
var vortexAngle = 0;
// Initialize with intro animation
self.startIntroAnimation = function () {
if (introAnimationStarted) {
return;
}
introAnimationStarted = true;
// Animate vortex to fade in
vortexCircles.forEach(function (circle, index) {
circle.alpha = 0;
LK.setTimeout(function () {
tween(circle, {
alpha: 0.15
}, {
duration: 800,
easing: tween.easeIn
});
}, index * 80);
});
// Animate title letters sequentially
letters.forEach(function (letter, index) {
LK.setTimeout(function () {
tween(letter, {
alpha: 1,
y: letter.origY - 200
}, {
duration: 300,
easing: tween.easeOut
});
tween(letter.scale, {
x: 1.5,
y: 1.5
}, {
duration: 400,
easing: tween.elastic,
onFinish: function onFinish() {
tween(letter.scale, {
x: 1,
y: 1
}, {
duration: 300,
easing: tween.easeOut
});
tween(letter, {
y: letter.origY
}, {
duration: 500,
easing: tween.bounce
});
}
});
}, index * 120);
});
// Animate glow effect
LK.setTimeout(function () {
glowBg.alpha = 0;
tween(glowBg, {
alpha: 0.3
}, {
duration: 1000,
easing: tween.easeIn
});
}, letters.length * 120);
// Animate bird after title
LK.setTimeout(function () {
// Bird dramatic entrance
sampleBird.scaleX = 0;
sampleBird.scaleY = 0;
sampleBird.alpha = 1;
// Setup echoes
birdEchoes.forEach(function (echo) {
echo.scale = {
x: 0,
y: 0
}; // Initialize scale as an object with x and y properties
echo.alpha = 0;
});
tween(sampleBird, {
x: 2.5,
y: 2.5,
scaleX: 0,
scaleY: 0
}, {
duration: 600,
easing: tween.elastic,
onFinish: function onFinish() {
tween(sampleBird, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 400,
easing: tween.easeOut
});
// Animate bird echoes with delay
birdEchoes.forEach(function (echo, idx) {
LK.setTimeout(function () {
echo.alpha = 0.15 - idx * 0.03;
// Ensure echo has proper scale object before tweening
echo.scale = echo.scale || {};
echo.scale.x = echo.scale.x || 0;
echo.scale.y = echo.scale.y || 0;
tween(echo.scale, {
x: 1.3 - idx * 0.15,
y: 1.3 - idx * 0.15
}, {
duration: 300,
easing: tween.easeOut
});
}, idx * 100);
});
// Flash screen for dramatic effect
LK.effects.flashScreen(0xFFFFFF, 300);
// Show instructions after bird appears
tween(instructionText, {
alpha: 1
}, {
duration: 500,
easing: tween.easeIn
});
// Animate instruction border
tween(instructionBorder, {
alpha: 0.4,
rotation: Math.PI * 2
}, {
duration: 800,
easing: tween.easeIn
});
tween(controlText, {
alpha: 1
}, {
duration: 500,
easing: tween.easeIn
});
introAnimationCompleted = true;
}
});
}, letters.length * 120 + 200);
};
self.update = function () {
// Start intro animation if not started yet
if (!introAnimationStarted) {
self.startIntroAnimation();
}
// Update vortex animation
vortexAngle += 0.01;
vortexCircles.forEach(function (circle, index) {
circle.rotation += circle.rotSpeed;
circle.scale.x = circle.scale.y = circle.baseScale + Math.sin(vortexAngle + index * 0.2) * 0.05;
circle.tint = Math.random() * 0xFFFFFF;
});
// Rotate and pulse glow effect
if (glowBg) {
glowBg.rotation += 0.01;
glowBg.scale.x = glowBg.scale.y = 3 + Math.sin(colorPhase * 2) * 0.3;
glowBg.tint = Math.floor(Math.random() * 0xFFFFFF);
}
// Update background particles
particles.forEach(function (particle) {
particle.x += particle.speedX;
particle.y += particle.speedY;
particle.rotation += particle.rotSpeed;
// Wrap particles around screen
if (particle.x < -1000) {
particle.x = 1000;
}
if (particle.x > 1000) {
particle.x = -1000;
}
if (particle.y < -1500) {
particle.y = 1500;
}
if (particle.y > 1500) {
particle.y = -1500;
}
// Pulsate particles with unique timing
particle.pulsePhase += particle.pulseSpeed;
particle.scale.x = particle.scale.y = 0.2 + Math.sin(particle.pulsePhase) * 0.15;
particle.tint = Math.random() * 0xFFFFFF;
});
// Update instruction border
if (instructionBorder) {
instructionBorder.rotation += 0.02;
instructionBorder.scale.x = instructionBorder.scale.y = 5 + Math.sin(floatPhase * 3) * 0.5;
}
// Only run these effects once intro is complete
if (introAnimationCompleted) {
// Floating animation for bird
floatPhase += 0.05;
sampleBird.y = -100 + Math.sin(floatPhase) * 50;
// Update bird echoes to follow with delay
birdEchoes.forEach(function (echo, idx) {
var delay = (idx + 1) * 2;
echo.y = -100 + Math.sin(floatPhase - idx * 0.3) * 50;
echo.rotation = sampleBird.rotation * (1 - idx * 0.2);
echo.tint = Math.floor(Math.random() * 0xFFFFFF);
});
// Color cycling for title letters
colorPhase += 0.02;
letters.forEach(function (letter, index) {
var r = Math.sin(colorPhase + index * 0.3) * 127 + 128;
var g = Math.sin(colorPhase + index * 0.3 + 2) * 127 + 128;
var b = Math.sin(colorPhase + index * 0.3 + 4) * 127 + 128;
var letterColor = Math.floor(r) << 16 | Math.floor(g) << 8 | Math.floor(b);
letter.fill = letterColor;
// Make letters dance with more varied movements
letter.y = letter.origY + Math.sin(floatPhase + index * 0.3) * 20;
letter.rotation = Math.sin(floatPhase * 0.5 + index) * 0.15;
letter.scale.x = 1 + Math.sin(floatPhase * 0.3 + index * 0.2) * 0.1;
letter.scale.y = 1 + Math.cos(floatPhase * 0.3 + index * 0.2) * 0.1;
});
// Pulse the instruction text with glow effect
instructionText.scale.x = 1 + Math.sin(floatPhase * 2) * 0.15;
instructionText.scale.y = 1 + Math.sin(floatPhase * 2) * 0.15;
var pulseColor = Math.floor(Math.sin(floatPhase) * 127 + 128) << 16 | Math.floor(Math.sin(floatPhase + 2) * 127 + 128) << 8 | Math.floor(Math.sin(floatPhase + 4) * 127 + 128);
instructionText.fill = pulseColor;
// Make control text "breathe"
controlText.scale.x = controlText.scale.y = 1 + Math.sin(floatPhase * 1.5) * 0.08;
controlText.rotation = Math.sin(floatPhase * 0.2) * 0.05;
controlText.fill = Math.floor(Math.sin(floatPhase + 1) * 127 + 128) << 16 | Math.floor(Math.sin(floatPhase + 3) * 127 + 128) << 8 | Math.floor(Math.sin(floatPhase + 5) * 127 + 128);
// Apply trippy effect to sample bird
sampleBird.rotation = Math.sin(floatPhase) * 0.2;
sampleBird.tint = Math.floor(Math.random() * 0xFFFFFF);
// Create bird trails
trailTimer++;
if (trailTimer >= 8) {
trailTimer = 0;
var trail = LK.getAsset('bird', {
anchorX: 0.5,
anchorY: 0.5,
x: sampleBird.x,
y: sampleBird.y,
rotation: sampleBird.rotation,
scale: 1.2,
alpha: 0.4,
tint: Math.random() * 0xFFFFFF
});
birdTrailContainer.addChild(trail);
// Animate trail fade out
tween(trail, {
alpha: 0,
scale: 0.5
}, {
duration: 700,
easing: tween.easeOut,
onFinish: function onFinish() {
if (trail.parent) {
trail.parent.removeChild(trail);
}
}
});
}
}
};
return self;
});
// TrailEffect class to create psychedelic trailing effect behind objects
var TrailEffect = Container.expand(function () {
var self = Container.call(this);
// Trail properties
self.maxTrails = 10; // Maximum number of trail elements
self.fadeSpeed = 0.1; // How quickly trails fade out
self.trailElements = []; // Array to store trail elements
self.target = null; // The object to trail
self.trailSpacing = 3; // Frames between trail elements
self.frameCount = 0; // Counter for spacing
// Initialize the trail effect for a target
self.init = function (target, assetId) {
self.target = target;
self.assetId = assetId;
// Clear any existing trails
self.clearTrails();
};
// Clear all trail elements
self.clearTrails = function () {
for (var i = 0; i < self.trailElements.length; i++) {
if (self.trailElements[i].parent) {
self.trailElements[i].parent.removeChild(self.trailElements[i]);
}
}
self.trailElements = [];
};
// Update the trail effect
self.update = function () {
if (!self.target) {
return;
}
self.frameCount++;
// Add new trail element at intervals
if (self.frameCount >= self.trailSpacing) {
self.frameCount = 0;
// Create new trail element
var trail = LK.getAsset(self.assetId, {
anchorX: 0.5,
anchorY: 0.5,
x: self.target.x,
y: self.target.y,
rotation: self.target.rotation,
alpha: 0.6
});
// Apply random psychedelic color
trail.tint = Math.random() * 0xFFFFFF;
// Add to game and track
game.addChild(trail);
self.trailElements.push(trail);
// Remove oldest trail if we exceed max
if (self.trailElements.length > self.maxTrails) {
var oldest = self.trailElements.shift();
if (oldest.parent) {
oldest.parent.removeChild(oldest);
}
}
}
// Fade out all trail elements
for (var i = 0; i < self.trailElements.length; i++) {
self.trailElements[i].alpha -= self.fadeSpeed;
self.trailElements[i].scale.x += 0.03;
self.trailElements[i].scale.y += 0.03;
// Remove if fully faded
if (self.trailElements[i].alpha <= 0) {
if (self.trailElements[i].parent) {
self.trailElements[i].parent.removeChild(self.trailElements[i]);
}
self.trailElements.splice(i, 1);
i--;
}
}
};
return self;
});
// WaveEffect class to create psychedelic visual distortions
var WaveEffect = Container.expand(function () {
var self = Container.call(this);
// Effect properties
self.amplitude = 20; // Wave height
self.frequency = 0.005; // Wave frequency
self.speed = 0.05; // Wave speed
self.colorSpeed = 0.01; // Color change speed
self.phase = 0; // Current phase of the wave
self.colorPhase = 0; // Current phase of the color cycle
// Apply wave distortion to a target object
self.applyDistortion = function (target) {
if (!target) {
return;
}
// Update phases
self.phase += self.speed;
self.colorPhase += self.colorSpeed;
// Apply wave effect to scale/rotation
target.scale.x = 1 + Math.sin(self.phase) * 0.1;
target.scale.y = 1 + Math.cos(self.phase * 1.3) * 0.1;
target.rotation = Math.sin(self.phase * 0.7) * 0.1;
// Apply trippy color effect
var r = Math.sin(self.colorPhase) * 127 + 128;
var g = Math.sin(self.colorPhase + 2) * 127 + 128;
var b = Math.sin(self.colorPhase + 4) * 127 + 128;
var color = r << 16 | g << 8 | b;
target.tint = color;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a001a // Start with a dark purple background
});
/****
* Game Code
****/
// Game constants
// Define assets for the game. These will be created automatically by the engine.
// Bird asset
// Obstacle asset (will be tinted/resized later)
// Sound for flapping (optional, but good practice)
// Sound for passing obstacle (optional)
// Sound for collision (optional)
// Include the tween plugin for animations and effects
// Initialize music for background
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var BASE_OBSTACLE_SPEED = 5; // Base speed that will increase over time
var OBSTACLE_SPEED = BASE_OBSTACLE_SPEED; // Current speed
var MAX_OBSTACLE_SPEED = 15; // Maximum speed cap
var SPEED_INCREASE_RATE = 0.2; // How much to increase speed each time
var SPEED_INCREASE_INTERVAL = 10000; // Increase speed every 10 seconds (in ms)
var OBSTACLE_SPAWN_RATE = 180; // Ticks between spawns (approx 3 seconds at 60fps)
var MIN_OBSTACLE_SPAWN_RATE = 60; // Minimum spawn rate (faster spawning)
var OBSTACLE_START_X = GAME_WIDTH + 150; // Start spawning off-screen right
var OBSTACLE_MIN_Y = 600; // Min center Y for the gap
var OBSTACLE_MAX_Y = GAME_HEIGHT - 600; // Max center Y for the gap
var speedIncreaseTimer = null; // Timer for speed increases
// Game variables
var bird;
var obstacles = [];
var spawnTimer = 0;
var isGameOver = false;
var gameStarted = false; // New variable to track if game has started
var backgroundChangeTimer = 0;
var backgroundTween = null; // To manage background color tween
var startScreen; // Variable to hold the start screen
// Psychedelic effect variables
var waveEffect;
var birdTrail;
var globalDistortionPhase = 0;
var screenShakeIntensity = 0;
// Score display with psychedelic styling
var scoreTxt = new Text2('0', {
size: 180,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Position score text slightly down from the top center, avoiding top-left corner
scoreTxt.y = 100;
// Setup score text color animation
LK.setInterval(function () {
var r = Math.floor(Math.random() * 255);
var g = Math.floor(Math.random() * 255);
var b = Math.floor(Math.random() * 255);
var randomColor = r << 16 | g << 8 | b;
// Animate score text color change
tween(scoreTxt, {
fill: randomColor
}, {
duration: 500,
easing: tween.easeInOut
});
// Add pulsating scale effect
tween(scoreTxt.scale, {
x: 1.2,
y: 1.2
}, {
duration: 250,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(scoreTxt.scale, {
x: 1.0,
y: 1.0
}, {
duration: 250,
easing: tween.easeIn
});
}
});
}, 1000);
// Initialize bird
bird = new Bird();
bird.x = GAME_WIDTH / 4; // Start position
bird.y = GAME_HEIGHT / 2;
game.addChild(bird);
// Initialize psychedelic effects
waveEffect = new WaveEffect();
birdTrail = new TrailEffect();
birdTrail.init(bird, 'bird');
// Handle player input (flap and hold)
game.down = function (x, y, obj) {
if (!gameStarted) {
// Start the game on first tap with enhanced transition
gameStarted = true;
// Create beat pulse effect across the screen
for (var i = 0; i < 4; i++) {
var beatRing = LK.getAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5,
x: startScreen.x,
y: startScreen.y,
scale: 0.1,
alpha: 0.7,
tint: 0xFFFFFF
});
game.addChild(beatRing);
// Animate beat ring expanding outward
tween(beatRing, {
scale: 10,
alpha: 0
}, {
duration: 800 + i * 200,
easing: tween.easeOut,
onFinish: function onFinish() {
if (beatRing.parent) {
beatRing.parent.removeChild(beatRing);
}
}
});
}
// Dramatic zoom effect on the start screen
tween(startScreen.scale, {
x: 1.2,
y: 1.2
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
// Create explosive particle effect with more diversity
for (var i = 0; i < 40; i++) {
// Use different shapes/sizes for particles
var assetType = Math.random() < 0.7 ? 'bird' : 'obstacle';
var particle = LK.getAsset(assetType, {
anchorX: 0.5,
anchorY: 0.5,
x: startScreen.x,
y: startScreen.y,
scale: 0.15 + Math.random() * 0.35,
alpha: 0.7 + Math.random() * 0.3,
tint: Math.random() * 0xFFFFFF,
rotation: Math.random() * Math.PI * 2
});
var angle = Math.random() * Math.PI * 2;
var distance = 500 + Math.random() * 1000;
var duration = 500 + Math.random() * 1000;
game.addChild(particle);
// Create more complex particle animation path
var midX = startScreen.x + Math.cos(angle) * distance * 0.5;
var midY = startScreen.y + Math.sin(angle) * distance * 0.5;
var midScale = 0.4 + Math.random() * 0.6;
// Two-stage animation for more interesting movement
tween(particle, {
x: midX,
y: midY,
alpha: 0.8,
rotation: Math.random() * Math.PI * 2,
scale: midScale
}, {
duration: duration * 0.4,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(particle, {
x: startScreen.x + Math.cos(angle) * distance,
y: startScreen.y + Math.sin(angle) * distance,
alpha: 0,
rotation: particle.rotation + Math.random() * Math.PI * 4
}, {
duration: duration * 0.6,
easing: tween.easeOut,
onFinish: function onFinish() {
if (particle.parent) {
particle.parent.removeChild(particle);
}
}
});
}
});
}
// Multi-color flash sequence for transition
var flashColors = [0xFFFFFF, 0xFF00FF, 0x00FFFF];
flashColors.forEach(function (color, index) {
LK.setTimeout(function () {
LK.effects.flashScreen(color, 150);
}, index * 100);
});
// Create radial wave effect
for (var i = 0; i < 3; i++) {
LK.setTimeout(function () {
// Create wave ring that expands from center
var wave = LK.getAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5,
x: GAME_WIDTH / 2,
y: GAME_HEIGHT / 2,
alpha: 0.4,
scale: 0.1,
tint: Math.random() * 0xFFFFFF
});
game.addChild(wave);
tween(wave, {
scale: 15,
alpha: 0
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
if (wave.parent) {
wave.parent.removeChild(wave);
}
}
});
}, i * 200);
}
// Fade out start screen with spiral effect
tween(startScreen, {
alpha: 0,
rotation: Math.PI / 2
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
game.removeChild(startScreen);
}
});
}
});
// Reset score
LK.setScore(0);
scoreTxt.setText('0');
// Start game with a small delay to allow fade transition
LK.setTimeout(function () {
// Create entering animation for score text
scoreTxt.alpha = 0;
scoreTxt.scale.set(3);
scoreTxt.rotation = -0.2;
// Show score text with animation
tween(scoreTxt, {
alpha: 1,
rotation: 0
}, {
duration: 500,
easing: tween.easeOut
});
tween(scoreTxt.scale, {
x: 1,
y: 1
}, {
duration: 800,
easing: tween.elastic
});
// Create colorful score indicator particles
for (var i = 0; i < 10; i++) {
var scoreParticle = LK.getAsset('bird', {
anchorX: 0.5,
anchorY: 0.5,
x: scoreTxt.parent.x,
y: scoreTxt.parent.y + scoreTxt.y,
scale: 0.2,
alpha: 0.7,
tint: Math.random() * 0xFFFFFF
});
LK.gui.top.addChild(scoreParticle);
var angle = Math.random() * Math.PI * 2;
var distance = 100 + Math.random() * 200;
tween(scoreParticle, {
x: scoreParticle.x + Math.cos(angle) * distance,
y: scoreParticle.y + Math.sin(angle) * distance,
alpha: 0,
scale: 0.05
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
if (scoreParticle.parent) {
scoreParticle.parent.removeChild(scoreParticle);
}
}
});
}
}, 600);
} else if (!isGameOver) {
bird.flap();
// Set holding state to true
bird.setHolding(true);
// Add extra psychedelic effects on flap
screenShakeIntensity = 15; // Increased screen shake intensity
// Flash screen with random vivid color
var flashColor = Math.random() * 0xFFFFFF;
LK.effects.flashScreen(flashColor, 150);
// Create a radial burst of colorful particles from the bird position
for (var i = 0; i < 8; i++) {
var angle = i / 8 * Math.PI * 2;
var particle = LK.getAsset('bird', {
anchorX: 0.5,
anchorY: 0.5,
x: bird.x,
y: bird.y,
scale: 0.3,
alpha: 0.7,
tint: Math.random() * 0xFFFFFF
});
game.addChild(particle);
tween(particle, {
x: bird.x + Math.cos(angle) * 200,
y: bird.y + Math.sin(angle) * 200,
alpha: 0,
rotation: Math.random() * Math.PI * 2
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
if (particle.parent) {
particle.parent.removeChild(particle);
}
}
});
}
// Temporary background color flash
var currentBg = game.backgroundColor;
game.backgroundColor = 0xFFFFFF;
LK.setTimeout(function () {
game.backgroundColor = currentBg;
}, 50);
// Start the speed increase timer if it hasn't been started yet
if (!speedIncreaseTimer) {
speedIncreaseTimer = LK.setInterval(function () {
if (!isGameOver && gameStarted) {
// Increase obstacle speed
OBSTACLE_SPEED = Math.min(MAX_OBSTACLE_SPEED, OBSTACLE_SPEED + SPEED_INCREASE_RATE);
// Decrease spawn rate (for more obstacles)
OBSTACLE_SPAWN_RATE = Math.max(MIN_OBSTACLE_SPAWN_RATE, OBSTACLE_SPAWN_RATE - 5);
// Visual feedback for speed increase
LK.effects.flashScreen(0xFFFFFF, 200);
// Show speed increase text
var speedMsg = new Text2("Speed Up!", {
size: 100,
fill: 0xFFFF00
});
speedMsg.anchor.set(0.5, 0.5);
speedMsg.x = GAME_WIDTH / 2;
speedMsg.y = GAME_HEIGHT / 2;
game.addChild(speedMsg);
// Animate and remove the text
tween(speedMsg, {
alpha: 0,
y: speedMsg.y - 100
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
if (speedMsg.parent) {
speedMsg.parent.removeChild(speedMsg);
}
}
});
}
}, SPEED_INCREASE_INTERVAL);
}
}
};
// Handle player releasing the screen
game.up = function (x, y, obj) {
if (gameStarted && !isGameOver) {
// Set holding state to false when player releases
bird.setHolding(false);
}
};
// Function to spawn a new obstacle pair
function spawnObstacle() {
var newObstacle = new ObstaclePair();
var gapCenterY = Math.random() * (OBSTACLE_MAX_Y - OBSTACLE_MIN_Y) + OBSTACLE_MIN_Y;
newObstacle.setup(OBSTACLE_START_X, gapCenterY);
obstacles.push(newObstacle);
game.addChild(newObstacle);
}
// Function for psychedelic background effect
function changeBackgroundColor() {
if (backgroundTween) {
tween.stop(game); // Stop existing background tween if any
}
// Generate more intense psychedelic colors - using ultra high saturation/values
var h = Math.random();
var s = 1.0; // Maximum saturation
var v = 0.9 + Math.random() * 0.1; // High value (0.9-1.0)
// Prefer certain hue ranges for more vibrant psychedelic colors
if (Math.random() < 0.7) {
// 70% chance to use these psychedelic color ranges:
var ranges = [[0.7, 0.9],
// purples
[0.45, 0.55],
// cyans
[0.1, 0.2],
// yellows/greens
[0.95, 1.0] // deep reds
];
var selectedRange = ranges[Math.floor(Math.random() * ranges.length)];
h = selectedRange[0] + Math.random() * (selectedRange[1] - selectedRange[0]);
}
// HSV to RGB conversion for more vibrant colors
var i = Math.floor(h * 6);
var f = h * 6 - i;
var p = v * (1 - s);
var q = v * (1 - f * s);
var t = v * (1 - (1 - f) * s);
var r, g, b;
switch (i % 6) {
case 0:
r = v;
g = t;
b = p;
break;
case 1:
r = q;
g = v;
b = p;
break;
case 2:
r = p;
g = v;
b = t;
break;
case 3:
r = p;
g = q;
b = v;
break;
case 4:
r = t;
g = p;
b = v;
break;
case 5:
r = v;
g = p;
b = q;
break;
}
var nextColor = Math.floor(r * 255) << 16 | Math.floor(g * 255) << 8 | Math.floor(b * 255);
// Ensure a valid color value is used (default to purple if undefined)
nextColor = nextColor || 0x800080;
// Ensure we have a valid color before starting the tween
if (isNaN(nextColor) || nextColor === undefined) {
nextColor = 0x800080; // Default to purple if undefined or NaN
}
// Make sure the current background color is valid
var currentBgColor = game.backgroundColor;
if (isNaN(currentBgColor) || currentBgColor === undefined) {
currentBgColor = 0x800080; // Use purple as fallback
game.backgroundColor = currentBgColor;
}
// Create a direct tween between valid color values
backgroundTween = tween(game, {
backgroundColor: nextColor || 0x800080 // Ensure a valid color is used
}, {
duration: 800,
// Faster transitions for more trippy effect
easing: tween.easeInOut,
onFinish: function onFinish() {
backgroundTween = null;
} // Clear the tween reference on finish
});
}
// Initialize background patterns and kaleidoscope effect
var backgroundPatterns = new BackgroundPatterns();
backgroundPatterns.init();
game.addChild(backgroundPatterns);
// Initialize kaleidoscope effect
var kaleidoscopeEffect = new KaleidoscopeEffect();
kaleidoscopeEffect.init();
kaleidoscopeEffect.x = GAME_WIDTH / 2;
kaleidoscopeEffect.y = GAME_HEIGHT / 2;
game.addChild(kaleidoscopeEffect);
// Game update loop
game.update = function () {
if (!gameStarted) {
// Only update start screen when game hasn't started
startScreen.update();
// Also update background patterns and kaleidoscope for pre-game effects
backgroundPatterns.update();
kaleidoscopeEffect.update();
return;
}
if (isGameOver) {
return;
} // Stop updates if game over
// Update global distortion phase
globalDistortionPhase += 0.05; // Increased for more intense effects
// Apply screen shake effect (gradually reduces intensity)
if (screenShakeIntensity > 0) {
game.x = (Math.random() * 2 - 1) * screenShakeIntensity;
game.y = (Math.random() * 2 - 1) * screenShakeIntensity;
screenShakeIntensity *= 0.9;
if (screenShakeIntensity < 0.5) {
screenShakeIntensity = 0;
game.x = 0;
game.y = 0;
}
}
// Update background patterns and kaleidoscope for more psychedelic effects
backgroundPatterns.update();
kaleidoscopeEffect.update();
// Apply occasional camera zoom effects for trippy sensation
if (Math.random() < 0.01) {
var zoomScale = 0.95 + Math.random() * 0.1; // Random zoom between 0.95 and 1.05
tween(game.scale, {
x: zoomScale,
y: zoomScale
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(game.scale, {
x: 1,
y: 1
}, {
duration: 400,
easing: tween.easeInOut
});
}
});
}
// Update bird and psychedelic effects
bird.update(); // Calls bird's internal update for physics
waveEffect.applyDistortion(bird.children[0]);
birdTrail.update();
// Check ground collision
if (bird.y >= GAME_HEIGHT - bird.children[0].height / 2) {
LK.getSound('hitSound').play();
LK.effects.flashScreen(0xFF0000, 300); // Flash red on hit
isGameOver = true;
// Reset speed-related values for next game
OBSTACLE_SPEED = BASE_OBSTACLE_SPEED;
OBSTACLE_SPAWN_RATE = 180;
if (speedIncreaseTimer) {
LK.clearInterval(speedIncreaseTimer);
speedIncreaseTimer = null;
}
LK.showGameOver();
return; // Stop further processing this frame
}
// Update obstacles
spawnTimer++;
if (spawnTimer >= OBSTACLE_SPAWN_RATE) {
spawnObstacle();
spawnTimer = 0;
}
// Move and check obstacles
for (var i = obstacles.length - 1; i >= 0; i--) {
var obstacle = obstacles[i];
obstacle.update(OBSTACLE_SPEED); // Move obstacle left
// Check for collision with bird
if (obstacle.collidesWith(bird)) {
LK.getSound('hitSound').play();
LK.effects.flashObject(bird, 0xFF0000, 300); // Flash bird red
isGameOver = true;
// Reset speed-related values for next game
OBSTACLE_SPEED = BASE_OBSTACLE_SPEED;
OBSTACLE_SPAWN_RATE = 180;
if (speedIncreaseTimer) {
LK.clearInterval(speedIncreaseTimer);
speedIncreaseTimer = null;
}
LK.showGameOver();
return; // Stop further processing this frame
}
// Check for scoring
if (!obstacle.scored && obstacle.x + obstacle.topObstacle.width / 2 < bird.x) {
obstacle.scored = true;
LK.setScore(LK.getScore() + 1);
scoreTxt.setText(LK.getScore());
LK.getSound('scoreSound').play();
// Optional: Flash score text or screen briefly on score
LK.effects.flashObject(scoreTxt, 0x00FF00, 150);
}
// Remove obstacles that are off-screen left
// Obstacle pair's x is its center. Remove when its right edge (x + width/2) is less than 0.
// Use the actual width of the obstacle graphic instance.
if (obstacle.x + obstacle.topObstacle.width / 2 < 0) {
obstacle.destroy();
obstacles.splice(i, 1);
}
}
// Psychedelic background effect timer - more frequent changes
backgroundChangeTimer++;
if (backgroundChangeTimer >= 30) {
// Twice as frequent (every half second)
// Change color more frequently
changeBackgroundColor();
backgroundChangeTimer = 0;
// Sometimes apply a more dramatic flash effect
if (Math.random() < 0.2) {
var flashColor = Math.random() * 0xFFFFFF;
LK.effects.flashScreen(flashColor, 100);
}
}
// Apply ultra intense psychedelic effects to obstacles
obstacles.forEach(function (obs, index) {
// Even more intense distortion effects
var individualPhase = globalDistortionPhase + index * 0.3;
// Extreme scaling with different frequencies for each obstacle
var pulseScaleX = 1 + Math.sin(individualPhase * 1.5) * 0.2;
var pulseScaleY = 1 + Math.cos(individualPhase * 1.2) * 0.25;
// Apply to top obstacle with warping effect
obs.topObstacle.scale.x = pulseScaleX;
obs.topObstacle.scale.y = pulseScaleY;
obs.topObstacle.rotation = Math.sin(individualPhase * 0.9) * 0.15;
// Apply to bottom obstacle with different phase
obs.bottomObstacle.scale.x = pulseScaleX * 0.9;
obs.bottomObstacle.scale.y = pulseScaleY * 1.1;
obs.bottomObstacle.rotation = Math.cos(individualPhase * 0.8) * 0.18;
// Cycle colors using ultra-saturated neon colors
var r = Math.sin(individualPhase * 1.2) * 127 + 128;
var g = Math.sin(individualPhase * 1.2 + 2) * 127 + 128;
var b = Math.sin(individualPhase * 1.2 + 4) * 127 + 128;
// Enhance color saturation for more vivid results
r = Math.min(255, r * 1.2);
g = Math.min(255, g * 1.2);
b = Math.min(255, b * 1.2);
var topColor = Math.floor(r) << 16 | Math.floor(g) << 8 | Math.floor(b);
var bottomColor = 255 - Math.floor(r) << 16 | 255 - Math.floor(g) << 8 | 255 - Math.floor(b);
// Apply colors with occasional random flashes
if (Math.random() < 0.05) {
// Random flash color
topColor = Math.random() * 0xFFFFFF;
bottomColor = Math.random() * 0xFFFFFF;
}
obs.topObstacle.tint = topColor;
obs.bottomObstacle.tint = bottomColor;
});
};
// Start the first background color change
changeBackgroundColor();
// Play background music with fade in effect
LK.playMusic('bgmusic', {
fade: {
start: 0,
end: 0.7,
duration: 2000
}
});
// Initialize start screen with enhanced animation
startScreen = new StartScreen();
startScreen.x = GAME_WIDTH / 2;
startScreen.y = GAME_HEIGHT / 2;
game.addChild(startScreen);
// Apply psychedelic background pulse effect for start screen
LK.setInterval(function () {
// Only pulse background before game starts
if (!gameStarted) {
var h = Math.random();
var s = 0.7 + Math.random() * 0.3;
var v = 0.6 + Math.random() * 0.4;
// HSV to RGB conversion
var i = Math.floor(h * 6);
var f = h * 6 - i;
var p = v * (1 - s);
var q = v * (1 - f * s);
var t = v * (1 - (1 - f) * s);
var r, g, b;
switch (i % 6) {
case 0:
r = v;
g = t;
b = p;
break;
case 1:
r = q;
g = v;
b = p;
break;
case 2:
r = p;
g = v;
b = t;
break;
case 3:
r = p;
g = q;
b = v;
break;
case 4:
r = t;
g = p;
b = v;
break;
case 5:
r = v;
g = p;
b = q;
break;
}
var targetColor = Math.floor(r * 255) << 16 | Math.floor(g * 255) << 8 | Math.floor(b * 255);
// Animate background color change
tween(game, {
backgroundColor: targetColor
}, {
duration: 1500,
easing: tween.easeInOut
});
}
}, 2000);
// Hide score text initially
scoreTxt.alpha = 0;