/**** * Plugins ****/ var facekit = LK.import("@upit/facekit.v1"); var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Chicken class var Chicken = Container.expand(function () { var self = Container.call(this); // Chicken body var chickenBody = self.attachAsset('chicken', { anchorX: 0.5, anchorY: 1 }); // Chicken shadow var shadow = self.attachAsset('shadow', { anchorX: 0.5, anchorY: 0.5, y: 20 }); // Physics self.groundY = 0; // Set after adding to game self.velY = 0; self.isJumping = false; self.jumpPower = -82; // Even higher jump for easier play self.gravity = 5.2; self.jumpCooldown = 0; // Prevents double jumps // Jump method self.jump = function () { if (!self.isJumping && self.jumpCooldown <= 0) { self.velY = self.jumpPower; self.isJumping = true; self.jumpCooldown = 12; // ~0.2s lockout LK.getSound('jump').play(); // Animate chicken up tween(self, { scaleX: 1.08, scaleY: 0.92 }, { duration: 120, easing: tween.easeOut }); tween(shadow, { scaleX: 0.7, scaleY: 0.7 }, { duration: 120, easing: tween.easeOut }); } }; // Update method self.update = function () { // Cooldown if (self.jumpCooldown > 0) self.jumpCooldown--; // Gravity if (self.isJumping) { self.y += self.velY; self.velY += self.gravity; // Land if (self.y >= self.groundY) { self.y = self.groundY; self.velY = 0; self.isJumping = false; // Animate squash tween(self, { scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.easeOut }); tween(shadow, { scaleX: 1, scaleY: 1 }, { duration: 120, easing: tween.easeOut }); } } }; return self; }); // Obstacle class var Obstacle = Container.expand(function () { var self = Container.call(this); var obs = self.attachAsset('obstacle', { anchorX: 0.5, anchorY: 1 }); self.speed = 0; // Set by game self.passed = false; // For scoring self.update = function () { self.x -= self.speed; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0xb3e0ff // Light blue sky }); /**** * Game Code ****/ // Tween plugin // Facekit plugin // Hit sound // Jump sound // Shadow // Ground // Obstacle (box) // Chicken (player) // Game constants var GROUND_Y = 2300; var CHICKEN_X = 500; var OBSTACLE_MIN_GAP = 950; // Wider minimum gap var OBSTACLE_MAX_GAP = 1450; // Wider maximum gap var OBSTACLE_MIN_HEIGHT = 140; // Lower minimum height var OBSTACLE_MAX_HEIGHT = 220; // Lower maximum height var OBSTACLE_WIDTH = 120; var OBSTACLE_SPEED_START = 16; // Slower start speed for easier play var OBSTACLE_SPEED_INC = 0.006; // Slower speed increase var OBSTACLE_MAX_SPEED = 28; // Lower max speed // Game state var chicken; var ground; var obstacles = []; var score = 0; var distance = 0; var gameSpeed = OBSTACLE_SPEED_START; var lastObstacleX = 0; var isGameOver = false; var jumpTriggered = false; var scoreTxt; var distTxt; var dragNode = null; var lives = 6; // Number of lives (increased for easier gameplay) var livesTxt; // Text2 for lives // Add ground ground = LK.getAsset('ground', { anchorX: 0, anchorY: 0, x: 0, y: GROUND_Y + 60 }); game.addChild(ground); // Add chicken chicken = new Chicken(); chicken.x = CHICKEN_X; chicken.groundY = GROUND_Y; chicken.y = GROUND_Y; game.addChild(chicken); // Score text scoreTxt = new Text2('0', { size: 120, fill: 0xFFF700 }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Lives text (top right) livesTxt = new Text2('❤ x' + lives, { size: 90, fill: 0xFF4444 }); livesTxt.anchor.set(1, 0); LK.gui.topRight.addChild(livesTxt); // Distance text (bottom center) distTxt = new Text2('0m', { size: 70, fill: 0xFFFFFF }); distTxt.anchor.set(0.5, 1); LK.gui.bottom.addChild(distTxt); // Helper: spawn obstacle function spawnObstacle() { var obs = new Obstacle(); obs.speed = gameSpeed; obs.x = 2048 + OBSTACLE_WIDTH; obs.y = GROUND_Y; // Randomize height var h = OBSTACLE_MIN_HEIGHT + Math.floor(Math.random() * (OBSTACLE_MAX_HEIGHT - OBSTACLE_MIN_HEIGHT)); obs.children[0].height = h; obs.children[0].y = 0; // AnchorY=1, so bottom at y=0 obstacles.push(obs); game.addChild(obs); lastObstacleX = obs.x; } // Helper: reset game state function resetGame() { // Remove obstacles for (var i = 0; i < obstacles.length; ++i) { obstacles[i].destroy(); } obstacles = []; score = 0; distance = 0; gameSpeed = OBSTACLE_SPEED_START; lastObstacleX = 0; isGameOver = false; lives = 3; if (livesTxt) livesTxt.setText('❤ x' + lives); chicken.x = CHICKEN_X; chicken.y = GROUND_Y; chicken.velY = 0; chicken.isJumping = false; chicken.jumpCooldown = 0; scoreTxt.setText('0'); distTxt.setText('0m'); } // Face control: triggers jump if mouth opens or head moves up function shouldJump() { // If mouth open if (facekit.mouthOpen) return true; // If head moves up (nose tip y decreases quickly) // We'll use a simple delta check if (typeof shouldJump.lastNoseY === 'undefined') { shouldJump.lastNoseY = facekit.noseTip ? facekit.noseTip.y : 0; return false; } var currY = facekit.noseTip ? facekit.noseTip.y : 0; var delta = shouldJump.lastNoseY - currY; shouldJump.lastNoseY = currY; if (delta > 22) return true; // Quick upward nod return false; } // Main update loop game.update = function () { if (isGameOver) return; // Chicken update chicken.update(); // Facekit: check for jump if (shouldJump() && !jumpTriggered) { chicken.jump(); jumpTriggered = true; } if (!facekit.mouthOpen && !shouldJump()) { jumpTriggered = false; } // Obstacles update for (var i = obstacles.length - 1; i >= 0; --i) { var obs = obstacles[i]; obs.speed = gameSpeed; obs.update(); // Remove if off screen if (obs.x < -OBSTACLE_WIDTH) { obs.destroy(); obstacles.splice(i, 1); continue; } // Collision if (!isGameOver && chicken.y + 10 > obs.y - obs.children[0].height + 30) { // Chicken feet near ground // Simple AABB var cLeft = chicken.x - chicken.children[0].width / 2 + 30; var cRight = chicken.x + chicken.children[0].width / 2 - 30; var oLeft = obs.x - OBSTACLE_WIDTH / 2; var oRight = obs.x + OBSTACLE_WIDTH / 2; if (cRight > oLeft && cLeft < oRight) { // Hit! lives--; livesTxt.setText('❤ x' + lives); LK.getSound('hit').play(); LK.effects.flashScreen(0xff0000, 800); tween(chicken, { rotation: 0.7 }, { duration: 300, easing: tween.easeOut }); if (lives <= 0) { isGameOver = true; LK.setTimeout(function () { LK.showGameOver(); }, 900); return; } else { // Remove the obstacle and reset chicken position for next life obs.destroy(); obstacles.splice(i, 1); chicken.x = CHICKEN_X; chicken.y = GROUND_Y; chicken.velY = 0; chicken.isJumping = false; chicken.jumpCooldown = 0; tween(chicken, { rotation: 0 }, { duration: 200, easing: tween.easeOut }); // Brief invulnerability (skip further collision this frame) break; } } } // Score: passed obstacle if (!obs.passed && obs.x + OBSTACLE_WIDTH / 2 < chicken.x - chicken.children[0].width / 2) { obs.passed = true; score++; scoreTxt.setText(score); } } // Distance distance += gameSpeed / 8; distTxt.setText(Math.floor(distance) + "m"); // Spawn new obstacle if (obstacles.length === 0 || 2048 - lastObstacleX > OBSTACLE_MIN_GAP + Math.random() * (OBSTACLE_MAX_GAP - OBSTACLE_MIN_GAP)) { spawnObstacle(); } // Increase speed if (gameSpeed < OBSTACLE_MAX_SPEED) { gameSpeed += OBSTACLE_SPEED_INC; } }; // Touch control: tap anywhere to jump (always enabled) game.down = function (x, y, obj) { if (!isGameOver && !chicken.isJumping && chicken.jumpCooldown <= 0) { chicken.jump(); } // Drag chicken for debug/touch (optional, for mobile fallback) if (!facekit.mouthOpen && !facekit.noseTip) { if (x > chicken.x - 120 && x < chicken.x + 120 && y > chicken.y - 180 && y < chicken.y) { dragNode = chicken; } } }; game.move = function (x, y, obj) { if (dragNode) { dragNode.y = Math.max(Math.min(y, GROUND_Y), GROUND_Y - 400); } }; game.up = function (x, y, obj) { dragNode = null; }; // Reset game on showGameOver LK.on('gameover', function () { resetGame(); }); // Reset game on showYouWin (not used, but for completeness) LK.on('youwin', function () { resetGame(); });
/****
* Plugins
****/
var facekit = LK.import("@upit/facekit.v1");
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Chicken class
var Chicken = Container.expand(function () {
var self = Container.call(this);
// Chicken body
var chickenBody = self.attachAsset('chicken', {
anchorX: 0.5,
anchorY: 1
});
// Chicken shadow
var shadow = self.attachAsset('shadow', {
anchorX: 0.5,
anchorY: 0.5,
y: 20
});
// Physics
self.groundY = 0; // Set after adding to game
self.velY = 0;
self.isJumping = false;
self.jumpPower = -82; // Even higher jump for easier play
self.gravity = 5.2;
self.jumpCooldown = 0; // Prevents double jumps
// Jump method
self.jump = function () {
if (!self.isJumping && self.jumpCooldown <= 0) {
self.velY = self.jumpPower;
self.isJumping = true;
self.jumpCooldown = 12; // ~0.2s lockout
LK.getSound('jump').play();
// Animate chicken up
tween(self, {
scaleX: 1.08,
scaleY: 0.92
}, {
duration: 120,
easing: tween.easeOut
});
tween(shadow, {
scaleX: 0.7,
scaleY: 0.7
}, {
duration: 120,
easing: tween.easeOut
});
}
};
// Update method
self.update = function () {
// Cooldown
if (self.jumpCooldown > 0) self.jumpCooldown--;
// Gravity
if (self.isJumping) {
self.y += self.velY;
self.velY += self.gravity;
// Land
if (self.y >= self.groundY) {
self.y = self.groundY;
self.velY = 0;
self.isJumping = false;
// Animate squash
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.easeOut
});
tween(shadow, {
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.easeOut
});
}
}
};
return self;
});
// Obstacle class
var Obstacle = Container.expand(function () {
var self = Container.call(this);
var obs = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 1
});
self.speed = 0; // Set by game
self.passed = false; // For scoring
self.update = function () {
self.x -= self.speed;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0xb3e0ff // Light blue sky
});
/****
* Game Code
****/
// Tween plugin
// Facekit plugin
// Hit sound
// Jump sound
// Shadow
// Ground
// Obstacle (box)
// Chicken (player)
// Game constants
var GROUND_Y = 2300;
var CHICKEN_X = 500;
var OBSTACLE_MIN_GAP = 950; // Wider minimum gap
var OBSTACLE_MAX_GAP = 1450; // Wider maximum gap
var OBSTACLE_MIN_HEIGHT = 140; // Lower minimum height
var OBSTACLE_MAX_HEIGHT = 220; // Lower maximum height
var OBSTACLE_WIDTH = 120;
var OBSTACLE_SPEED_START = 16; // Slower start speed for easier play
var OBSTACLE_SPEED_INC = 0.006; // Slower speed increase
var OBSTACLE_MAX_SPEED = 28; // Lower max speed
// Game state
var chicken;
var ground;
var obstacles = [];
var score = 0;
var distance = 0;
var gameSpeed = OBSTACLE_SPEED_START;
var lastObstacleX = 0;
var isGameOver = false;
var jumpTriggered = false;
var scoreTxt;
var distTxt;
var dragNode = null;
var lives = 6; // Number of lives (increased for easier gameplay)
var livesTxt; // Text2 for lives
// Add ground
ground = LK.getAsset('ground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: GROUND_Y + 60
});
game.addChild(ground);
// Add chicken
chicken = new Chicken();
chicken.x = CHICKEN_X;
chicken.groundY = GROUND_Y;
chicken.y = GROUND_Y;
game.addChild(chicken);
// Score text
scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFF700
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Lives text (top right)
livesTxt = new Text2('❤ x' + lives, {
size: 90,
fill: 0xFF4444
});
livesTxt.anchor.set(1, 0);
LK.gui.topRight.addChild(livesTxt);
// Distance text (bottom center)
distTxt = new Text2('0m', {
size: 70,
fill: 0xFFFFFF
});
distTxt.anchor.set(0.5, 1);
LK.gui.bottom.addChild(distTxt);
// Helper: spawn obstacle
function spawnObstacle() {
var obs = new Obstacle();
obs.speed = gameSpeed;
obs.x = 2048 + OBSTACLE_WIDTH;
obs.y = GROUND_Y;
// Randomize height
var h = OBSTACLE_MIN_HEIGHT + Math.floor(Math.random() * (OBSTACLE_MAX_HEIGHT - OBSTACLE_MIN_HEIGHT));
obs.children[0].height = h;
obs.children[0].y = 0; // AnchorY=1, so bottom at y=0
obstacles.push(obs);
game.addChild(obs);
lastObstacleX = obs.x;
}
// Helper: reset game state
function resetGame() {
// Remove obstacles
for (var i = 0; i < obstacles.length; ++i) {
obstacles[i].destroy();
}
obstacles = [];
score = 0;
distance = 0;
gameSpeed = OBSTACLE_SPEED_START;
lastObstacleX = 0;
isGameOver = false;
lives = 3;
if (livesTxt) livesTxt.setText('❤ x' + lives);
chicken.x = CHICKEN_X;
chicken.y = GROUND_Y;
chicken.velY = 0;
chicken.isJumping = false;
chicken.jumpCooldown = 0;
scoreTxt.setText('0');
distTxt.setText('0m');
}
// Face control: triggers jump if mouth opens or head moves up
function shouldJump() {
// If mouth open
if (facekit.mouthOpen) return true;
// If head moves up (nose tip y decreases quickly)
// We'll use a simple delta check
if (typeof shouldJump.lastNoseY === 'undefined') {
shouldJump.lastNoseY = facekit.noseTip ? facekit.noseTip.y : 0;
return false;
}
var currY = facekit.noseTip ? facekit.noseTip.y : 0;
var delta = shouldJump.lastNoseY - currY;
shouldJump.lastNoseY = currY;
if (delta > 22) return true; // Quick upward nod
return false;
}
// Main update loop
game.update = function () {
if (isGameOver) return;
// Chicken update
chicken.update();
// Facekit: check for jump
if (shouldJump() && !jumpTriggered) {
chicken.jump();
jumpTriggered = true;
}
if (!facekit.mouthOpen && !shouldJump()) {
jumpTriggered = false;
}
// Obstacles update
for (var i = obstacles.length - 1; i >= 0; --i) {
var obs = obstacles[i];
obs.speed = gameSpeed;
obs.update();
// Remove if off screen
if (obs.x < -OBSTACLE_WIDTH) {
obs.destroy();
obstacles.splice(i, 1);
continue;
}
// Collision
if (!isGameOver && chicken.y + 10 > obs.y - obs.children[0].height + 30) {
// Chicken feet near ground
// Simple AABB
var cLeft = chicken.x - chicken.children[0].width / 2 + 30;
var cRight = chicken.x + chicken.children[0].width / 2 - 30;
var oLeft = obs.x - OBSTACLE_WIDTH / 2;
var oRight = obs.x + OBSTACLE_WIDTH / 2;
if (cRight > oLeft && cLeft < oRight) {
// Hit!
lives--;
livesTxt.setText('❤ x' + lives);
LK.getSound('hit').play();
LK.effects.flashScreen(0xff0000, 800);
tween(chicken, {
rotation: 0.7
}, {
duration: 300,
easing: tween.easeOut
});
if (lives <= 0) {
isGameOver = true;
LK.setTimeout(function () {
LK.showGameOver();
}, 900);
return;
} else {
// Remove the obstacle and reset chicken position for next life
obs.destroy();
obstacles.splice(i, 1);
chicken.x = CHICKEN_X;
chicken.y = GROUND_Y;
chicken.velY = 0;
chicken.isJumping = false;
chicken.jumpCooldown = 0;
tween(chicken, {
rotation: 0
}, {
duration: 200,
easing: tween.easeOut
});
// Brief invulnerability (skip further collision this frame)
break;
}
}
}
// Score: passed obstacle
if (!obs.passed && obs.x + OBSTACLE_WIDTH / 2 < chicken.x - chicken.children[0].width / 2) {
obs.passed = true;
score++;
scoreTxt.setText(score);
}
}
// Distance
distance += gameSpeed / 8;
distTxt.setText(Math.floor(distance) + "m");
// Spawn new obstacle
if (obstacles.length === 0 || 2048 - lastObstacleX > OBSTACLE_MIN_GAP + Math.random() * (OBSTACLE_MAX_GAP - OBSTACLE_MIN_GAP)) {
spawnObstacle();
}
// Increase speed
if (gameSpeed < OBSTACLE_MAX_SPEED) {
gameSpeed += OBSTACLE_SPEED_INC;
}
};
// Touch control: tap anywhere to jump (always enabled)
game.down = function (x, y, obj) {
if (!isGameOver && !chicken.isJumping && chicken.jumpCooldown <= 0) {
chicken.jump();
}
// Drag chicken for debug/touch (optional, for mobile fallback)
if (!facekit.mouthOpen && !facekit.noseTip) {
if (x > chicken.x - 120 && x < chicken.x + 120 && y > chicken.y - 180 && y < chicken.y) {
dragNode = chicken;
}
}
};
game.move = function (x, y, obj) {
if (dragNode) {
dragNode.y = Math.max(Math.min(y, GROUND_Y), GROUND_Y - 400);
}
};
game.up = function (x, y, obj) {
dragNode = null;
};
// Reset game on showGameOver
LK.on('gameover', function () {
resetGame();
});
// Reset game on showYouWin (not used, but for completeness)
LK.on('youwin', function () {
resetGame();
});