/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Bird = Container.expand(function () {
var self = Container.call(this);
var birdGraphics = self.attachAsset('bird', {
anchorX: 0.5,
anchorY: 0.5
});
self.targetY = 2732 / 2;
self.currentPath = 'center';
self.update = function () {
// Smooth movement towards target position
var diff = self.targetY - self.y;
self.y += diff * 0.1;
// Enhanced floating animation
var floatOffset = Math.sin(LK.ticks * 0.05) * 15;
var currentY = self.y;
self.y = currentY + floatOffset;
// Wing flap simulation with rotation
if (!birdGraphics.isFlapping) {
birdGraphics.rotation = Math.sin(LK.ticks * 0.2) * 0.15;
}
// Scale breathing effect
var breathScale = 1 + Math.sin(LK.ticks * 0.08) * 0.05;
birdGraphics.scaleX = breathScale;
birdGraphics.scaleY = breathScale;
};
self.setPath = function (path) {
self.currentPath = path;
var newTargetY;
if (path === 'left') {
newTargetY = 2732 * 0.3;
} else if (path === 'right') {
newTargetY = 2732 * 0.7;
} else {
newTargetY = 2732 / 2;
}
// Animate bird to new position with bounce effect
tween(self, {
targetY: newTargetY
}, {
duration: 400,
easing: tween.easeOut
});
// Add slight tilt animation based on direction
var targetRotation = 0;
if (path === 'left') {
targetRotation = -0.2;
} else if (path === 'right') {
targetRotation = 0.2;
}
tween(birdGraphics, {
rotation: targetRotation
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(birdGraphics, {
rotation: 0
}, {
duration: 300,
easing: tween.easeOut
});
}
});
};
return self;
});
var Obstacle = Container.expand(function (gapSide) {
var self = Container.call(this);
self.gapSide = gapSide || 'both';
self.speed = -gameSpeed;
self.passed = false;
var gapSize = 350;
// Create obstacles based on gap configuration
if (self.gapSide === 'left' || self.gapSide === 'both') {
// Top obstacle for left gap
var topLeft = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 1,
x: 0,
y: 2732 * 0.3 - gapSize / 2
});
// Bottom obstacle for left gap
var bottomLeft = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0,
x: 0,
y: 2732 * 0.3 + gapSize / 2
});
}
if (self.gapSide === 'right' || self.gapSide === 'both') {
// Top obstacle for right gap
var topRight = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 1,
x: 0,
y: 2732 * 0.7 - gapSize / 2
});
// Bottom obstacle for right gap
var bottomRight = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0,
x: 0,
y: 2732 * 0.7 + gapSize / 2
});
}
// Add middle obstacle if only one gap
if (self.gapSide === 'left') {
var rightBlock = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 2732 * 0.7,
height: 800
});
}
if (self.gapSide === 'right') {
var leftBlock = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 2732 * 0.3,
height: 800
});
}
// Start obstacles scaled down for entrance animation
self.scaleX = 0.1;
self.scaleY = 0.1;
// Animate entrance
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.bounceOut
});
self.update = function () {
self.x += self.speed;
};
return self;
});
var PathIndicator = Container.expand(function (side) {
var self = Container.call(this);
var indicator = self.attachAsset('pathIndicator', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.3
});
self.side = side;
if (side === 'left') {
self.y = 2732 * 0.3;
} else {
self.y = 2732 * 0.7;
}
// Start pulsing animation
tween(indicator, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 0.6
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(indicator, {
scaleX: 0.8,
scaleY: 0.8,
alpha: 0.2
}, {
duration: 500,
easing: tween.easeInOut
});
}
});
self.update = function () {
self.x += -gameSpeed;
// Fade out as it moves
if (self.x < 1000) {
indicator.alpha = Math.max(0, indicator.alpha - 0.02);
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87CEEB
});
/****
* Game Code
****/
// Game variables
var bird;
var obstacles = [];
var pathIndicators = [];
var gameSpeed = 8;
var obstacleSpawnTimer = 0;
var obstacleSpawnDelay = 180; // 3 seconds at 60fps
var nextObstacleGap = 'both';
// UI Elements
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Create bird
bird = game.addChild(new Bird());
bird.x = 300;
bird.y = 2732 / 2;
// Mouse controls - bird follows mouse Y position
game.move = function (x, y, obj) {
// Convert mouse Y position to bird path
var centerY = 2732 / 2;
var threshold = 200;
var currentPath;
if (y < centerY - threshold) {
currentPath = 'left';
} else if (y > centerY + threshold) {
currentPath = 'right';
} else {
currentPath = 'center';
}
// Only change path if it's different from current
if (bird.currentPath !== currentPath) {
bird.setPath(currentPath);
// Add flap animation on path change
var birdGraphics = bird.children[0];
birdGraphics.isFlapping = true;
tween(birdGraphics, {
scaleY: 1.3
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(birdGraphics, {
scaleY: 1
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
birdGraphics.isFlapping = false;
}
});
}
});
LK.getSound('flap').play();
}
};
// Generate random gap configuration
function getRandomGapConfig() {
var configs = ['left', 'right', 'both'];
return configs[Math.floor(Math.random() * configs.length)];
}
// Check collision between bird and obstacles
function checkCollisions() {
for (var i = 0; i < obstacles.length; i++) {
var obstacle = obstacles[i];
if (bird.intersects(obstacle)) {
// Game over
LK.getSound('collision').play();
LK.effects.flashScreen(0xff0000, 500);
LK.showGameOver();
return true;
}
// Check if bird passed obstacle for scoring
if (!obstacle.passed && obstacle.x < bird.x - 100) {
obstacle.passed = true;
LK.setScore(LK.getScore() + 1);
scoreTxt.setText(LK.getScore());
LK.getSound('score').play();
// Animate score text
tween(scoreTxt, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(scoreTxt, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeOut
});
}
});
// Increase speed every 5 points
if (LK.getScore() % 5 === 0) {
gameSpeed += 0.5;
obstacleSpawnDelay = Math.max(120, obstacleSpawnDelay - 5);
}
}
}
return false;
}
// Spawn new obstacle
function spawnObstacle() {
var gapConfig = getRandomGapConfig();
var newObstacle = new Obstacle(gapConfig);
newObstacle.x = 2048 + 200;
obstacles.push(newObstacle);
game.addChild(newObstacle);
// Add path indicators
if (gapConfig === 'left' || gapConfig === 'both') {
var leftIndicator = new PathIndicator('left');
leftIndicator.x = 2048 + 100;
pathIndicators.push(leftIndicator);
game.addChild(leftIndicator);
}
if (gapConfig === 'right' || gapConfig === 'both') {
var rightIndicator = new PathIndicator('right');
rightIndicator.x = 2048 + 100;
pathIndicators.push(rightIndicator);
game.addChild(rightIndicator);
}
}
// Main game loop
game.update = function () {
// Spawn obstacles
obstacleSpawnTimer++;
if (obstacleSpawnTimer >= obstacleSpawnDelay) {
spawnObstacle();
obstacleSpawnTimer = 0;
}
// Update and clean up obstacles
for (var i = obstacles.length - 1; i >= 0; i--) {
var obstacle = obstacles[i];
if (obstacle.x < -300) {
obstacle.destroy();
obstacles.splice(i, 1);
}
}
// Update and clean up path indicators
for (var j = pathIndicators.length - 1; j >= 0; j--) {
var indicator = pathIndicators[j];
if (indicator.x < -200 || indicator.children[0].alpha <= 0) {
indicator.destroy();
pathIndicators.splice(j, 1);
}
}
// Check for collisions
checkCollisions();
// Keep bird in bounds
if (bird.y < 50) bird.y = 50;
if (bird.y > 2732 - 50) bird.y = 2732 - 50;
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Bird = Container.expand(function () {
var self = Container.call(this);
var birdGraphics = self.attachAsset('bird', {
anchorX: 0.5,
anchorY: 0.5
});
self.targetY = 2732 / 2;
self.currentPath = 'center';
self.update = function () {
// Smooth movement towards target position
var diff = self.targetY - self.y;
self.y += diff * 0.1;
// Enhanced floating animation
var floatOffset = Math.sin(LK.ticks * 0.05) * 15;
var currentY = self.y;
self.y = currentY + floatOffset;
// Wing flap simulation with rotation
if (!birdGraphics.isFlapping) {
birdGraphics.rotation = Math.sin(LK.ticks * 0.2) * 0.15;
}
// Scale breathing effect
var breathScale = 1 + Math.sin(LK.ticks * 0.08) * 0.05;
birdGraphics.scaleX = breathScale;
birdGraphics.scaleY = breathScale;
};
self.setPath = function (path) {
self.currentPath = path;
var newTargetY;
if (path === 'left') {
newTargetY = 2732 * 0.3;
} else if (path === 'right') {
newTargetY = 2732 * 0.7;
} else {
newTargetY = 2732 / 2;
}
// Animate bird to new position with bounce effect
tween(self, {
targetY: newTargetY
}, {
duration: 400,
easing: tween.easeOut
});
// Add slight tilt animation based on direction
var targetRotation = 0;
if (path === 'left') {
targetRotation = -0.2;
} else if (path === 'right') {
targetRotation = 0.2;
}
tween(birdGraphics, {
rotation: targetRotation
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(birdGraphics, {
rotation: 0
}, {
duration: 300,
easing: tween.easeOut
});
}
});
};
return self;
});
var Obstacle = Container.expand(function (gapSide) {
var self = Container.call(this);
self.gapSide = gapSide || 'both';
self.speed = -gameSpeed;
self.passed = false;
var gapSize = 350;
// Create obstacles based on gap configuration
if (self.gapSide === 'left' || self.gapSide === 'both') {
// Top obstacle for left gap
var topLeft = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 1,
x: 0,
y: 2732 * 0.3 - gapSize / 2
});
// Bottom obstacle for left gap
var bottomLeft = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0,
x: 0,
y: 2732 * 0.3 + gapSize / 2
});
}
if (self.gapSide === 'right' || self.gapSide === 'both') {
// Top obstacle for right gap
var topRight = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 1,
x: 0,
y: 2732 * 0.7 - gapSize / 2
});
// Bottom obstacle for right gap
var bottomRight = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0,
x: 0,
y: 2732 * 0.7 + gapSize / 2
});
}
// Add middle obstacle if only one gap
if (self.gapSide === 'left') {
var rightBlock = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 2732 * 0.7,
height: 800
});
}
if (self.gapSide === 'right') {
var leftBlock = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 2732 * 0.3,
height: 800
});
}
// Start obstacles scaled down for entrance animation
self.scaleX = 0.1;
self.scaleY = 0.1;
// Animate entrance
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.bounceOut
});
self.update = function () {
self.x += self.speed;
};
return self;
});
var PathIndicator = Container.expand(function (side) {
var self = Container.call(this);
var indicator = self.attachAsset('pathIndicator', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.3
});
self.side = side;
if (side === 'left') {
self.y = 2732 * 0.3;
} else {
self.y = 2732 * 0.7;
}
// Start pulsing animation
tween(indicator, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 0.6
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(indicator, {
scaleX: 0.8,
scaleY: 0.8,
alpha: 0.2
}, {
duration: 500,
easing: tween.easeInOut
});
}
});
self.update = function () {
self.x += -gameSpeed;
// Fade out as it moves
if (self.x < 1000) {
indicator.alpha = Math.max(0, indicator.alpha - 0.02);
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87CEEB
});
/****
* Game Code
****/
// Game variables
var bird;
var obstacles = [];
var pathIndicators = [];
var gameSpeed = 8;
var obstacleSpawnTimer = 0;
var obstacleSpawnDelay = 180; // 3 seconds at 60fps
var nextObstacleGap = 'both';
// UI Elements
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Create bird
bird = game.addChild(new Bird());
bird.x = 300;
bird.y = 2732 / 2;
// Mouse controls - bird follows mouse Y position
game.move = function (x, y, obj) {
// Convert mouse Y position to bird path
var centerY = 2732 / 2;
var threshold = 200;
var currentPath;
if (y < centerY - threshold) {
currentPath = 'left';
} else if (y > centerY + threshold) {
currentPath = 'right';
} else {
currentPath = 'center';
}
// Only change path if it's different from current
if (bird.currentPath !== currentPath) {
bird.setPath(currentPath);
// Add flap animation on path change
var birdGraphics = bird.children[0];
birdGraphics.isFlapping = true;
tween(birdGraphics, {
scaleY: 1.3
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(birdGraphics, {
scaleY: 1
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
birdGraphics.isFlapping = false;
}
});
}
});
LK.getSound('flap').play();
}
};
// Generate random gap configuration
function getRandomGapConfig() {
var configs = ['left', 'right', 'both'];
return configs[Math.floor(Math.random() * configs.length)];
}
// Check collision between bird and obstacles
function checkCollisions() {
for (var i = 0; i < obstacles.length; i++) {
var obstacle = obstacles[i];
if (bird.intersects(obstacle)) {
// Game over
LK.getSound('collision').play();
LK.effects.flashScreen(0xff0000, 500);
LK.showGameOver();
return true;
}
// Check if bird passed obstacle for scoring
if (!obstacle.passed && obstacle.x < bird.x - 100) {
obstacle.passed = true;
LK.setScore(LK.getScore() + 1);
scoreTxt.setText(LK.getScore());
LK.getSound('score').play();
// Animate score text
tween(scoreTxt, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(scoreTxt, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeOut
});
}
});
// Increase speed every 5 points
if (LK.getScore() % 5 === 0) {
gameSpeed += 0.5;
obstacleSpawnDelay = Math.max(120, obstacleSpawnDelay - 5);
}
}
}
return false;
}
// Spawn new obstacle
function spawnObstacle() {
var gapConfig = getRandomGapConfig();
var newObstacle = new Obstacle(gapConfig);
newObstacle.x = 2048 + 200;
obstacles.push(newObstacle);
game.addChild(newObstacle);
// Add path indicators
if (gapConfig === 'left' || gapConfig === 'both') {
var leftIndicator = new PathIndicator('left');
leftIndicator.x = 2048 + 100;
pathIndicators.push(leftIndicator);
game.addChild(leftIndicator);
}
if (gapConfig === 'right' || gapConfig === 'both') {
var rightIndicator = new PathIndicator('right');
rightIndicator.x = 2048 + 100;
pathIndicators.push(rightIndicator);
game.addChild(rightIndicator);
}
}
// Main game loop
game.update = function () {
// Spawn obstacles
obstacleSpawnTimer++;
if (obstacleSpawnTimer >= obstacleSpawnDelay) {
spawnObstacle();
obstacleSpawnTimer = 0;
}
// Update and clean up obstacles
for (var i = obstacles.length - 1; i >= 0; i--) {
var obstacle = obstacles[i];
if (obstacle.x < -300) {
obstacle.destroy();
obstacles.splice(i, 1);
}
}
// Update and clean up path indicators
for (var j = pathIndicators.length - 1; j >= 0; j--) {
var indicator = pathIndicators[j];
if (indicator.x < -200 || indicator.children[0].alpha <= 0) {
indicator.destroy();
pathIndicators.splice(j, 1);
}
}
// Check for collisions
checkCollisions();
// Keep bird in bounds
if (bird.y < 50) bird.y = 50;
if (bird.y > 2732 - 50) bird.y = 2732 - 50;
};