/****
* 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();
});