/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // BirdFriend class var BirdFriend = Container.expand(function () { var self = Container.call(this); // Attach asset (ellipse, color varies by number) var colors = [0xcccccc, // 0 (unused) 0xffe066, // 1 - Geoffrey the Eagle (yellow) 0x8ecae6, // 2 - Quinn the Chough (blue) 0xff595e, // 3 - Nina the Robin (red) 0x90be6d, // 4 - Astrid the Stork (green) 0x6d597a, // 5 - Martin the Duck (purple) 0xffca3a, // 6 - Ronald the Macaw (orange) 0x43aa8b, // 7 - Graham the Seagull (teal) 0x577590, // 8 - Jones the Hummingbird (navy) 0xffffff // 9 - Swen the Swan (white) ]; self.number = 1; // Will be set after creation var birdSprite = self.attachAsset('birdfriend', { anchorX: 0.5, anchorY: 0.5, color: colors[self.number], width: 120, height: 120, shape: 'ellipse' }); // Add number label var numberText = new Text2('1', { size: 70, fill: 0x222222 }); numberText.anchor.set(0.5, 0.5); self.addChild(numberText); // Set number and color self.setNumber = function (n) { self.number = n; birdSprite.color = colors[n]; numberText.setText('' + n); }; // Animate collection self.collect = function (_onFinish) { tween(self, { scaleX: 1.5, scaleY: 1.5, alpha: 0 }, { duration: 400, easing: tween.easeIn, onFinish: function onFinish() { if (_onFinish) _onFinish(); } }); }; return self; }); // Falcon (Player) class var Falcon = Container.expand(function () { var self = Container.call(this); // Attach falcon asset (red box, will be replaced with image later) var falconSprite = self.attachAsset('falcon', { anchorX: 0.5, anchorY: 0.5 }); // Movement speed self.speed = 18; // Current velocity self.vx = 0; self.vy = 0; // Update method called every tick self.update = function () { self.x += self.vx; self.y += self.vy; // Clamp to game area if (self.x < 100 + falconSprite.width / 2) self.x = 100 + falconSprite.width / 2; if (self.x > 2048 - falconSprite.width / 2) self.x = 2048 - falconSprite.width / 2; if (self.y < falconSprite.height / 2) self.y = falconSprite.height / 2; if (self.y > 2732 - falconSprite.height / 2) self.y = 2732 - falconSprite.height / 2; }; return self; }); // Joystick class (on-screen) var Joystick = Container.expand(function () { var self = Container.call(this); // Outer circle var base = self.attachAsset('joystickBase', { anchorX: 0.5, anchorY: 0.5, color: 0xaaaaaa, width: 260, height: 260, shape: 'ellipse' }); // Inner knob var knob = self.attachAsset('joystickKnob', { anchorX: 0.5, anchorY: 0.5, color: 0x333333, width: 120, height: 120, shape: 'ellipse' }); knob.x = 0; knob.y = 0; self.addChild(knob); // Joystick state self.active = false; self.startX = 0; self.startY = 0; self.deltaX = 0; self.deltaY = 0; // Get normalized direction (-1 to 1) self.getDirection = function () { var dx = self.deltaX / 100; var dy = self.deltaY / 100; // Clamp if (dx < -1) dx = -1; if (dx > 1) dx = 1; if (dy < -1) dy = -1; if (dy > 1) dy = 1; return { x: dx, y: dy }; }; // Reset knob self.reset = function () { self.deltaX = 0; self.deltaY = 0; knob.x = 0; knob.y = 0; }; // Touch/mouse events self.down = function (x, y, obj) { self.active = true; self.startX = x; self.startY = y; self.deltaX = 0; self.deltaY = 0; }; self.move = function (x, y, obj) { if (!self.active) return; self.deltaX = x - self.startX; self.deltaY = y - self.startY; // Clamp to radius 100 var dist = Math.sqrt(self.deltaX * self.deltaX + self.deltaY * self.deltaY); if (dist > 100) { var scale = 100 / dist; self.deltaX *= scale; self.deltaY *= scale; } knob.x = self.deltaX; knob.y = self.deltaY; }; self.up = function (x, y, obj) { self.active = false; self.reset(); }; return self; }); // Obstacle class var Obstacle = Container.expand(function () { var self = Container.call(this); // Attach asset (gray box) var obsSprite = self.attachAsset('obstacle', { anchorX: 0.5, anchorY: 0.5, color: 0x444444, width: 180, height: 60, shape: 'box' }); // Movement speed self.speed = 10 + Math.random() * 8; // Direction: 1 = right, -1 = left self.dir = Math.random() < 0.5 ? 1 : -1; // Update method self.update = function () { self.x += self.speed * self.dir; // Bounce off edges if (self.x < 100 + obsSprite.width / 2) { self.x = 100 + obsSprite.width / 2; self.dir = 1; } if (self.x > 2048 - obsSprite.width / 2) { self.x = 2048 - obsSprite.width / 2; self.dir = -1; } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x7ec8e3 // Light blue sky }); /**** * Game Code ****/ // --- Game variables --- var falcon; var joystick; var birdFriends = []; var obstacles = []; var nextBird = 1; var scoreTxt; var timerTxt; var timeElapsed = 0; var timerInterval; var gameActive = true; var lastFalconX = 0, lastFalconY = 0; var lastIntersectingBird = false; var lastIntersectingObstacle = false; // --- Setup --- // Set background color game.setBackgroundColor(0x7ec8e3); // Add Falcon (player) falcon = new Falcon(); falcon.x = 2048 / 2; falcon.y = 2732 - 400; game.addChild(falcon); // Add BirdFriends (1-9) at random positions, not overlapping function randomBirdPos() { var margin = 200; var x = margin + Math.random() * (2048 - 2 * margin); var y = margin + Math.random() * (2732 - 2 * margin - 600); return { x: x, y: y }; } for (var i = 1; i <= 9; i++) { var bird = new BirdFriend(); bird.setNumber(i); var pos; var tries = 0; do { pos = randomBirdPos(); var overlap = false; for (var j = 0; j < birdFriends.length; j++) { var dx = pos.x - birdFriends[j].x; var dy = pos.y - birdFriends[j].y; if (Math.sqrt(dx * dx + dy * dy) < 220) { overlap = true; break; } } tries++; } while (overlap && tries < 20); bird.x = pos.x; bird.y = pos.y + 200; // leave top margin for timer bird.scaleX = 1; bird.scaleY = 1; bird.alpha = 1; birdFriends.push(bird); game.addChild(bird); } // Add obstacles (4 obstacles) for (var o = 0; o < 4; o++) { var obs = new Obstacle(); obs.x = 300 + Math.random() * (2048 - 600); obs.y = 600 + Math.random() * (2732 - 1200); obstacles.push(obs); game.addChild(obs); } // Add joystick (bottom left, outside 100x100 menu area) joystick = new Joystick(); joystick.x = 220; joystick.y = 2732 - 220; game.addChild(joystick); // Add score text (top center) scoreTxt = new Text2('Next: 1', { size: 120, fill: 0x222222 }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Add timer text (top right) timerTxt = new Text2('0.0s', { size: 90, fill: 0x222222 }); timerTxt.anchor.set(1, 0); LK.gui.topRight.addChild(timerTxt); // --- Timer --- function updateTimer() { if (!gameActive) return; timeElapsed += 0.1; timerTxt.setText(timeElapsed.toFixed(1) + 's'); } timerInterval = LK.setInterval(updateTimer, 100); // --- Joystick events --- joystick.down = function (x, y, obj) { joystick.active = true; joystick.startX = x; joystick.startY = y; joystick.deltaX = 0; joystick.deltaY = 0; }; joystick.move = function (x, y, obj) { if (!joystick.active) return; joystick.deltaX = x - joystick.startX; joystick.deltaY = y - joystick.startY; var dist = Math.sqrt(joystick.deltaX * joystick.deltaX + joystick.deltaY * joystick.deltaY); if (dist > 100) { var scale = 100 / dist; joystick.deltaX *= scale; joystick.deltaY *= scale; } joystick.children[1].x = joystick.deltaX; joystick.children[1].y = joystick.deltaY; }; joystick.up = function (x, y, obj) { joystick.active = false; joystick.reset(); }; // --- Game move/up/down events (for joystick) --- game.down = function (x, y, obj) { // If touch is inside joystick area, activate joystick var dx = x - joystick.x; var dy = y - joystick.y; if (dx * dx + dy * dy < 130 * 130) { joystick.down(x, y, obj); } }; game.move = function (x, y, obj) { if (joystick.active) { joystick.move(x, y, obj); } }; game.up = function (x, y, obj) { if (joystick.active) { joystick.up(x, y, obj); } }; // --- Main game update --- game.update = function () { if (!gameActive) return; // Joystick controls Falcon var dir = joystick.getDirection(); falcon.vx = dir.x * falcon.speed; falcon.vy = dir.y * falcon.speed; // Update Falcon falcon.update(); // Update obstacles for (var i = 0; i < obstacles.length; i++) { obstacles[i].update(); } // Check collision with next bird var nextBirdObj = null; for (var i = 0; i < birdFriends.length; i++) { if (birdFriends[i].number === nextBird) { nextBirdObj = birdFriends[i]; break; } } if (nextBirdObj) { var intersecting = falcon.intersects(nextBirdObj); if (!lastIntersectingBird && intersecting) { // Collect bird nextBirdObj.collect(function () { nextBirdObj.destroy(); }); nextBird++; if (nextBird > 9) { // Win! gameActive = false; LK.clearInterval(timerInterval); LK.setScore(Math.max(0, Math.round(10000 - timeElapsed * 100))); LK.showYouWin(); return; } scoreTxt.setText('Next: ' + nextBird); } lastIntersectingBird = intersecting; } // Check collision with obstacles var hitObstacle = false; for (var i = 0; i < obstacles.length; i++) { if (falcon.intersects(obstacles[i])) { hitObstacle = true; break; } } if (!lastIntersectingObstacle && hitObstacle) { // Game over gameActive = false; LK.clearInterval(timerInterval); LK.effects.flashScreen(0xff0000, 800); LK.showGameOver(); return; } lastIntersectingObstacle = hitObstacle; }; // --- Clean up on game end --- function cleanup() { LK.clearInterval(timerInterval); }
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// BirdFriend class
var BirdFriend = Container.expand(function () {
var self = Container.call(this);
// Attach asset (ellipse, color varies by number)
var colors = [0xcccccc,
// 0 (unused)
0xffe066,
// 1 - Geoffrey the Eagle (yellow)
0x8ecae6,
// 2 - Quinn the Chough (blue)
0xff595e,
// 3 - Nina the Robin (red)
0x90be6d,
// 4 - Astrid the Stork (green)
0x6d597a,
// 5 - Martin the Duck (purple)
0xffca3a,
// 6 - Ronald the Macaw (orange)
0x43aa8b,
// 7 - Graham the Seagull (teal)
0x577590,
// 8 - Jones the Hummingbird (navy)
0xffffff // 9 - Swen the Swan (white)
];
self.number = 1; // Will be set after creation
var birdSprite = self.attachAsset('birdfriend', {
anchorX: 0.5,
anchorY: 0.5,
color: colors[self.number],
width: 120,
height: 120,
shape: 'ellipse'
});
// Add number label
var numberText = new Text2('1', {
size: 70,
fill: 0x222222
});
numberText.anchor.set(0.5, 0.5);
self.addChild(numberText);
// Set number and color
self.setNumber = function (n) {
self.number = n;
birdSprite.color = colors[n];
numberText.setText('' + n);
};
// Animate collection
self.collect = function (_onFinish) {
tween(self, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 400,
easing: tween.easeIn,
onFinish: function onFinish() {
if (_onFinish) _onFinish();
}
});
};
return self;
});
// Falcon (Player) class
var Falcon = Container.expand(function () {
var self = Container.call(this);
// Attach falcon asset (red box, will be replaced with image later)
var falconSprite = self.attachAsset('falcon', {
anchorX: 0.5,
anchorY: 0.5
});
// Movement speed
self.speed = 18;
// Current velocity
self.vx = 0;
self.vy = 0;
// Update method called every tick
self.update = function () {
self.x += self.vx;
self.y += self.vy;
// Clamp to game area
if (self.x < 100 + falconSprite.width / 2) self.x = 100 + falconSprite.width / 2;
if (self.x > 2048 - falconSprite.width / 2) self.x = 2048 - falconSprite.width / 2;
if (self.y < falconSprite.height / 2) self.y = falconSprite.height / 2;
if (self.y > 2732 - falconSprite.height / 2) self.y = 2732 - falconSprite.height / 2;
};
return self;
});
// Joystick class (on-screen)
var Joystick = Container.expand(function () {
var self = Container.call(this);
// Outer circle
var base = self.attachAsset('joystickBase', {
anchorX: 0.5,
anchorY: 0.5,
color: 0xaaaaaa,
width: 260,
height: 260,
shape: 'ellipse'
});
// Inner knob
var knob = self.attachAsset('joystickKnob', {
anchorX: 0.5,
anchorY: 0.5,
color: 0x333333,
width: 120,
height: 120,
shape: 'ellipse'
});
knob.x = 0;
knob.y = 0;
self.addChild(knob);
// Joystick state
self.active = false;
self.startX = 0;
self.startY = 0;
self.deltaX = 0;
self.deltaY = 0;
// Get normalized direction (-1 to 1)
self.getDirection = function () {
var dx = self.deltaX / 100;
var dy = self.deltaY / 100;
// Clamp
if (dx < -1) dx = -1;
if (dx > 1) dx = 1;
if (dy < -1) dy = -1;
if (dy > 1) dy = 1;
return {
x: dx,
y: dy
};
};
// Reset knob
self.reset = function () {
self.deltaX = 0;
self.deltaY = 0;
knob.x = 0;
knob.y = 0;
};
// Touch/mouse events
self.down = function (x, y, obj) {
self.active = true;
self.startX = x;
self.startY = y;
self.deltaX = 0;
self.deltaY = 0;
};
self.move = function (x, y, obj) {
if (!self.active) return;
self.deltaX = x - self.startX;
self.deltaY = y - self.startY;
// Clamp to radius 100
var dist = Math.sqrt(self.deltaX * self.deltaX + self.deltaY * self.deltaY);
if (dist > 100) {
var scale = 100 / dist;
self.deltaX *= scale;
self.deltaY *= scale;
}
knob.x = self.deltaX;
knob.y = self.deltaY;
};
self.up = function (x, y, obj) {
self.active = false;
self.reset();
};
return self;
});
// Obstacle class
var Obstacle = Container.expand(function () {
var self = Container.call(this);
// Attach asset (gray box)
var obsSprite = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5,
color: 0x444444,
width: 180,
height: 60,
shape: 'box'
});
// Movement speed
self.speed = 10 + Math.random() * 8;
// Direction: 1 = right, -1 = left
self.dir = Math.random() < 0.5 ? 1 : -1;
// Update method
self.update = function () {
self.x += self.speed * self.dir;
// Bounce off edges
if (self.x < 100 + obsSprite.width / 2) {
self.x = 100 + obsSprite.width / 2;
self.dir = 1;
}
if (self.x > 2048 - obsSprite.width / 2) {
self.x = 2048 - obsSprite.width / 2;
self.dir = -1;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x7ec8e3 // Light blue sky
});
/****
* Game Code
****/
// --- Game variables ---
var falcon;
var joystick;
var birdFriends = [];
var obstacles = [];
var nextBird = 1;
var scoreTxt;
var timerTxt;
var timeElapsed = 0;
var timerInterval;
var gameActive = true;
var lastFalconX = 0,
lastFalconY = 0;
var lastIntersectingBird = false;
var lastIntersectingObstacle = false;
// --- Setup ---
// Set background color
game.setBackgroundColor(0x7ec8e3);
// Add Falcon (player)
falcon = new Falcon();
falcon.x = 2048 / 2;
falcon.y = 2732 - 400;
game.addChild(falcon);
// Add BirdFriends (1-9) at random positions, not overlapping
function randomBirdPos() {
var margin = 200;
var x = margin + Math.random() * (2048 - 2 * margin);
var y = margin + Math.random() * (2732 - 2 * margin - 600);
return {
x: x,
y: y
};
}
for (var i = 1; i <= 9; i++) {
var bird = new BirdFriend();
bird.setNumber(i);
var pos;
var tries = 0;
do {
pos = randomBirdPos();
var overlap = false;
for (var j = 0; j < birdFriends.length; j++) {
var dx = pos.x - birdFriends[j].x;
var dy = pos.y - birdFriends[j].y;
if (Math.sqrt(dx * dx + dy * dy) < 220) {
overlap = true;
break;
}
}
tries++;
} while (overlap && tries < 20);
bird.x = pos.x;
bird.y = pos.y + 200; // leave top margin for timer
bird.scaleX = 1;
bird.scaleY = 1;
bird.alpha = 1;
birdFriends.push(bird);
game.addChild(bird);
}
// Add obstacles (4 obstacles)
for (var o = 0; o < 4; o++) {
var obs = new Obstacle();
obs.x = 300 + Math.random() * (2048 - 600);
obs.y = 600 + Math.random() * (2732 - 1200);
obstacles.push(obs);
game.addChild(obs);
}
// Add joystick (bottom left, outside 100x100 menu area)
joystick = new Joystick();
joystick.x = 220;
joystick.y = 2732 - 220;
game.addChild(joystick);
// Add score text (top center)
scoreTxt = new Text2('Next: 1', {
size: 120,
fill: 0x222222
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Add timer text (top right)
timerTxt = new Text2('0.0s', {
size: 90,
fill: 0x222222
});
timerTxt.anchor.set(1, 0);
LK.gui.topRight.addChild(timerTxt);
// --- Timer ---
function updateTimer() {
if (!gameActive) return;
timeElapsed += 0.1;
timerTxt.setText(timeElapsed.toFixed(1) + 's');
}
timerInterval = LK.setInterval(updateTimer, 100);
// --- Joystick events ---
joystick.down = function (x, y, obj) {
joystick.active = true;
joystick.startX = x;
joystick.startY = y;
joystick.deltaX = 0;
joystick.deltaY = 0;
};
joystick.move = function (x, y, obj) {
if (!joystick.active) return;
joystick.deltaX = x - joystick.startX;
joystick.deltaY = y - joystick.startY;
var dist = Math.sqrt(joystick.deltaX * joystick.deltaX + joystick.deltaY * joystick.deltaY);
if (dist > 100) {
var scale = 100 / dist;
joystick.deltaX *= scale;
joystick.deltaY *= scale;
}
joystick.children[1].x = joystick.deltaX;
joystick.children[1].y = joystick.deltaY;
};
joystick.up = function (x, y, obj) {
joystick.active = false;
joystick.reset();
};
// --- Game move/up/down events (for joystick) ---
game.down = function (x, y, obj) {
// If touch is inside joystick area, activate joystick
var dx = x - joystick.x;
var dy = y - joystick.y;
if (dx * dx + dy * dy < 130 * 130) {
joystick.down(x, y, obj);
}
};
game.move = function (x, y, obj) {
if (joystick.active) {
joystick.move(x, y, obj);
}
};
game.up = function (x, y, obj) {
if (joystick.active) {
joystick.up(x, y, obj);
}
};
// --- Main game update ---
game.update = function () {
if (!gameActive) return;
// Joystick controls Falcon
var dir = joystick.getDirection();
falcon.vx = dir.x * falcon.speed;
falcon.vy = dir.y * falcon.speed;
// Update Falcon
falcon.update();
// Update obstacles
for (var i = 0; i < obstacles.length; i++) {
obstacles[i].update();
}
// Check collision with next bird
var nextBirdObj = null;
for (var i = 0; i < birdFriends.length; i++) {
if (birdFriends[i].number === nextBird) {
nextBirdObj = birdFriends[i];
break;
}
}
if (nextBirdObj) {
var intersecting = falcon.intersects(nextBirdObj);
if (!lastIntersectingBird && intersecting) {
// Collect bird
nextBirdObj.collect(function () {
nextBirdObj.destroy();
});
nextBird++;
if (nextBird > 9) {
// Win!
gameActive = false;
LK.clearInterval(timerInterval);
LK.setScore(Math.max(0, Math.round(10000 - timeElapsed * 100)));
LK.showYouWin();
return;
}
scoreTxt.setText('Next: ' + nextBird);
}
lastIntersectingBird = intersecting;
}
// Check collision with obstacles
var hitObstacle = false;
for (var i = 0; i < obstacles.length; i++) {
if (falcon.intersects(obstacles[i])) {
hitObstacle = true;
break;
}
}
if (!lastIntersectingObstacle && hitObstacle) {
// Game over
gameActive = false;
LK.clearInterval(timerInterval);
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
return;
}
lastIntersectingObstacle = hitObstacle;
};
// --- Clean up on game end ---
function cleanup() {
LK.clearInterval(timerInterval);
}