/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var facekit = LK.import("@upit/facekit.v1");
/****
* Classes
****/
// Collectible class
var Collectible = Container.expand(function () {
var self = Container.call(this);
var col = self.attachAsset('collectible', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 7 + Math.random() * 3.5;
self.update = function () {
self.y += self.speed * (typeof speedMultiplier !== "undefined" ? speedMultiplier : 1);
};
// Helper for tight bounds (returns Rectangle)
self.getTightBounds = function () {
// Use a slightly smaller box than the image
var margin = col.width * 0.18;
return new Rectangle(self.x - col.width / 2 + margin, self.y - col.height / 2 + margin, col.width - margin * 2, col.height - margin * 2);
};
return self;
});
// Obstacle class
var Obstacle = Container.expand(function () {
var self = Container.call(this);
var obs = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 9 + Math.random() * 4; // pixels per frame
self.update = function () {
self.y += self.speed * (typeof speedMultiplier !== "undefined" ? speedMultiplier : 1);
};
// Helper for tight bounds (returns Rectangle)
self.getTightBounds = function () {
// Use a slightly smaller box than the image
var margin = obs.width * 0.18;
return new Rectangle(self.x - obs.width / 2 + margin, self.y - obs.height / 2 + margin, obs.width - margin * 2, obs.height - margin * 2);
};
return self;
});
// Player class (face-controlled)
var Player = Container.expand(function () {
var self = Container.call(this);
var face = self.attachAsset('playerFace', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = face.width * 0.5;
// Helper for tight bounds (returns Rectangle)
self.getTightBounds = function () {
// For player, use a slightly smaller box than the face image
var margin = face.width * 0.18;
return new Rectangle(self.x - face.width / 2 + margin, self.y - face.height / 2 + margin, face.width - margin * 2, face.height - margin * 2);
};
self.update = function () {
// Facekit controls
// Use mouthCenter for X, Y
// Clamp to game area
var targetX = facekit.mouthCenter && typeof facekit.mouthCenter.x === 'number' ? facekit.mouthCenter.x : 2048 / 2;
var targetY = facekit.mouthCenter && typeof facekit.mouthCenter.y === 'number' ? facekit.mouthCenter.y : 2732 * 0.75;
// Smooth follow for less jitter
self.x += (targetX - self.x) * 0.3;
self.y += (targetY - self.y) * 0.3;
// Clamp to bounds
if (self.x < self.radius) self.x = self.radius;
if (self.x > 2048 - self.radius) self.x = 2048 - self.radius;
if (self.y < self.radius + 200) self.y = self.radius + 200;
if (self.y > 2732 - self.radius) self.y = 2732 - self.radius;
// Animate scale with mouth open
if (facekit.mouthOpen) {
tween.stop(face);
tween(face, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 120,
easing: tween.easeOut
});
} else {
tween.stop(face);
tween(face, {
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.easeOut
});
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181c2c
});
/****
* Game Code
****/
// Heart shape: ellipse for heart base, overlayed with another ellipse for the other lobe, and a triangle for the tip
// Music (background)
// Sound for hitting obstacle
// Sound for collecting
// Collectible: a green ellipse
// Obstacle: a red box
// Character: a simple ellipse (face)
// Play background music
LK.playMusic('bgmusic', {
fade: {
start: 0,
end: 0.7,
duration: 1200
}
});
// Score
var score = 0;
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFF700
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Speed multiplier for falling objects
var speedMultiplier = 1;
// How much to increase per apple
var speedIncreasePerApple = 0.07;
// Maximum speed multiplier
var maxSpeedMultiplier = 2.5;
// Player
var player = new Player();
game.addChild(player);
player.x = 2048 / 2;
player.y = 2732 * 0.75;
// Arrays for obstacles and collectibles
var obstacles = [];
var collectibles = [];
// Timers for spawning
var obstacleTimer = 0;
var collectibleTimer = 0;
// For collision state
var lastHit = false;
// Heart (life) system
var maxHearts = 3;
var hearts = maxHearts;
// Heart UI
var heartIcons = [];
function drawHearts() {
// Remove old icons
for (var i = 0; i < heartIcons.length; i++) {
if (heartIcons[i].parent) heartIcons[i].parent.removeChild(heartIcons[i]);
heartIcons[i].destroy();
}
heartIcons = [];
// Draw new icons
for (var i = 0; i < hearts; i++) {
var heartImg = LK.getAsset('heartMario', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0,
scaleX: 1,
scaleY: 1
});
var heartContainer = new Container();
heartContainer.addChild(heartImg);
// Position the heart container
heartContainer.x = 180 + i * 120;
heartContainer.y = 120;
heartContainer.scale.set(0.7, 0.7);
LK.gui.top.addChild(heartContainer);
heartIcons.push(heartContainer);
}
}
drawHearts();
// Helper: spawn obstacle
function spawnObstacle() {
var obs = new Obstacle();
obs.x = 200 + Math.random() * (2048 - 400);
obs.y = -100;
obstacles.push(obs);
game.addChild(obs);
}
// Helper: spawn collectible
function spawnCollectible() {
var col = new Collectible();
col.x = 150 + Math.random() * (2048 - 300);
col.y = -80;
collectibles.push(col);
game.addChild(col);
}
// Main update loop
game.update = function () {
// Update player (face tracking)
player.update();
// Spawn obstacles
obstacleTimer--;
if (obstacleTimer <= 0) {
spawnObstacle();
obstacleTimer = 60 + Math.floor(Math.random() * 40); // ~1s, less frequent
}
// Spawn collectibles
collectibleTimer--;
if (collectibleTimer <= 0) {
spawnCollectible();
collectibleTimer = 90 + Math.floor(Math.random() * 60); // ~1.5s
}
// Update obstacles
for (var i = obstacles.length - 1; i >= 0; i--) {
var obs = obstacles[i];
obs.update();
// Remove if off screen
if (obs.y > 2732 + 100) {
obs.destroy();
obstacles.splice(i, 1);
continue;
}
// Collision with player (tight bounds)
var hit = false;
if (player.getTightBounds && obs.getTightBounds) {
var pb = player.getTightBounds();
var ob = obs.getTightBounds();
hit = pb.x + pb.width > ob.x && pb.x < ob.x + ob.width && pb.y + pb.height > ob.y && pb.y < ob.y + ob.height;
} else {
hit = player.intersects(obs);
}
if (hit && !lastHit) {
LK.effects.flashScreen(0xff0000, 800);
LK.getSound('hit').play();
// Show "BOOOM" text in the center of the screen
var boomTxt = new Text2('BOOOM', {
size: 300,
fill: 0xFF2222,
font: "Impact, Arial Black, Tahoma"
});
boomTxt.anchor.set(0.5, 0.5);
boomTxt.x = 2048 / 2;
boomTxt.y = 2732 / 2;
game.addChild(boomTxt);
tween(boomTxt, {
alpha: 0
}, {
duration: 1000,
onComplete: function onComplete() {
boomTxt.destroy();
}
});
// Decrement hearts and update UI
hearts--;
drawHearts();
if (hearts <= 0) {
// End game immediately when hearts reach 0
LK.showGameOver();
lastHit = true;
break;
}
lastHit = true;
break;
}
}
// Reset lastHit if no collision
if (!obstacles.some(function (o) {
return player.intersects(o);
})) {
lastHit = false;
}
// Update collectibles
for (var j = collectibles.length - 1; j >= 0; j--) {
var col = collectibles[j];
col.update();
// Remove if off screen
if (col.y > 2732 + 80) {
col.destroy();
collectibles.splice(j, 1);
continue;
}
// Collect (tight bounds)
var collectHit = false;
if (player.getTightBounds && col.getTightBounds) {
var pb = player.getTightBounds();
var cb = col.getTightBounds();
collectHit = pb.x + pb.width > cb.x && pb.x < cb.x + cb.width && pb.y + pb.height > cb.y && pb.y < cb.y + cb.height;
} else {
collectHit = player.intersects(col);
}
if (collectHit) {
LK.getSound('collect').play();
score += 1;
scoreTxt.setText(score);
LK.setScore(score);
// Increase speed multiplier, capped at max
speedMultiplier += speedIncreasePerApple;
if (speedMultiplier > maxSpeedMultiplier) speedMultiplier = maxSpeedMultiplier;
// Flash green
LK.effects.flashObject(player, 0x44de83, 300);
col.destroy();
collectibles.splice(j, 1);
continue;
}
}
};
// On game reset, reset score and clear arrays
game.on('reset', function () {
score = 0;
scoreTxt.setText(score);
LK.setScore(score);
for (var i = 0; i < obstacles.length; i++) obstacles[i].destroy();
for (var j = 0; j < collectibles.length; j++) collectibles[j].destroy();
obstacles = [];
collectibles = [];
player.x = 2048 / 2;
player.y = 2732 * 0.75;
lastHit = false;
obstacleTimer = 0;
collectibleTimer = 0;
// Reset hearts
hearts = maxHearts;
drawHearts();
// Reset speed multiplier
speedMultiplier = 1;
});
// Touch fallback: allow dragging player if facekit not available
var dragActive = false;
game.down = function (x, y, obj) {
if (!facekit.mouthCenter) {
dragActive = true;
player.x = x;
player.y = y;
}
};
game.move = function (x, y, obj) {
if (dragActive && !facekit.mouthCenter) {
player.x = x;
player.y = y;
}
};
game.up = function (x, y, obj) {
dragActive = false;
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var facekit = LK.import("@upit/facekit.v1");
/****
* Classes
****/
// Collectible class
var Collectible = Container.expand(function () {
var self = Container.call(this);
var col = self.attachAsset('collectible', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 7 + Math.random() * 3.5;
self.update = function () {
self.y += self.speed * (typeof speedMultiplier !== "undefined" ? speedMultiplier : 1);
};
// Helper for tight bounds (returns Rectangle)
self.getTightBounds = function () {
// Use a slightly smaller box than the image
var margin = col.width * 0.18;
return new Rectangle(self.x - col.width / 2 + margin, self.y - col.height / 2 + margin, col.width - margin * 2, col.height - margin * 2);
};
return self;
});
// Obstacle class
var Obstacle = Container.expand(function () {
var self = Container.call(this);
var obs = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 9 + Math.random() * 4; // pixels per frame
self.update = function () {
self.y += self.speed * (typeof speedMultiplier !== "undefined" ? speedMultiplier : 1);
};
// Helper for tight bounds (returns Rectangle)
self.getTightBounds = function () {
// Use a slightly smaller box than the image
var margin = obs.width * 0.18;
return new Rectangle(self.x - obs.width / 2 + margin, self.y - obs.height / 2 + margin, obs.width - margin * 2, obs.height - margin * 2);
};
return self;
});
// Player class (face-controlled)
var Player = Container.expand(function () {
var self = Container.call(this);
var face = self.attachAsset('playerFace', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = face.width * 0.5;
// Helper for tight bounds (returns Rectangle)
self.getTightBounds = function () {
// For player, use a slightly smaller box than the face image
var margin = face.width * 0.18;
return new Rectangle(self.x - face.width / 2 + margin, self.y - face.height / 2 + margin, face.width - margin * 2, face.height - margin * 2);
};
self.update = function () {
// Facekit controls
// Use mouthCenter for X, Y
// Clamp to game area
var targetX = facekit.mouthCenter && typeof facekit.mouthCenter.x === 'number' ? facekit.mouthCenter.x : 2048 / 2;
var targetY = facekit.mouthCenter && typeof facekit.mouthCenter.y === 'number' ? facekit.mouthCenter.y : 2732 * 0.75;
// Smooth follow for less jitter
self.x += (targetX - self.x) * 0.3;
self.y += (targetY - self.y) * 0.3;
// Clamp to bounds
if (self.x < self.radius) self.x = self.radius;
if (self.x > 2048 - self.radius) self.x = 2048 - self.radius;
if (self.y < self.radius + 200) self.y = self.radius + 200;
if (self.y > 2732 - self.radius) self.y = 2732 - self.radius;
// Animate scale with mouth open
if (facekit.mouthOpen) {
tween.stop(face);
tween(face, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 120,
easing: tween.easeOut
});
} else {
tween.stop(face);
tween(face, {
scaleX: 1,
scaleY: 1
}, {
duration: 120,
easing: tween.easeOut
});
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181c2c
});
/****
* Game Code
****/
// Heart shape: ellipse for heart base, overlayed with another ellipse for the other lobe, and a triangle for the tip
// Music (background)
// Sound for hitting obstacle
// Sound for collecting
// Collectible: a green ellipse
// Obstacle: a red box
// Character: a simple ellipse (face)
// Play background music
LK.playMusic('bgmusic', {
fade: {
start: 0,
end: 0.7,
duration: 1200
}
});
// Score
var score = 0;
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFF700
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Speed multiplier for falling objects
var speedMultiplier = 1;
// How much to increase per apple
var speedIncreasePerApple = 0.07;
// Maximum speed multiplier
var maxSpeedMultiplier = 2.5;
// Player
var player = new Player();
game.addChild(player);
player.x = 2048 / 2;
player.y = 2732 * 0.75;
// Arrays for obstacles and collectibles
var obstacles = [];
var collectibles = [];
// Timers for spawning
var obstacleTimer = 0;
var collectibleTimer = 0;
// For collision state
var lastHit = false;
// Heart (life) system
var maxHearts = 3;
var hearts = maxHearts;
// Heart UI
var heartIcons = [];
function drawHearts() {
// Remove old icons
for (var i = 0; i < heartIcons.length; i++) {
if (heartIcons[i].parent) heartIcons[i].parent.removeChild(heartIcons[i]);
heartIcons[i].destroy();
}
heartIcons = [];
// Draw new icons
for (var i = 0; i < hearts; i++) {
var heartImg = LK.getAsset('heartMario', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0,
scaleX: 1,
scaleY: 1
});
var heartContainer = new Container();
heartContainer.addChild(heartImg);
// Position the heart container
heartContainer.x = 180 + i * 120;
heartContainer.y = 120;
heartContainer.scale.set(0.7, 0.7);
LK.gui.top.addChild(heartContainer);
heartIcons.push(heartContainer);
}
}
drawHearts();
// Helper: spawn obstacle
function spawnObstacle() {
var obs = new Obstacle();
obs.x = 200 + Math.random() * (2048 - 400);
obs.y = -100;
obstacles.push(obs);
game.addChild(obs);
}
// Helper: spawn collectible
function spawnCollectible() {
var col = new Collectible();
col.x = 150 + Math.random() * (2048 - 300);
col.y = -80;
collectibles.push(col);
game.addChild(col);
}
// Main update loop
game.update = function () {
// Update player (face tracking)
player.update();
// Spawn obstacles
obstacleTimer--;
if (obstacleTimer <= 0) {
spawnObstacle();
obstacleTimer = 60 + Math.floor(Math.random() * 40); // ~1s, less frequent
}
// Spawn collectibles
collectibleTimer--;
if (collectibleTimer <= 0) {
spawnCollectible();
collectibleTimer = 90 + Math.floor(Math.random() * 60); // ~1.5s
}
// Update obstacles
for (var i = obstacles.length - 1; i >= 0; i--) {
var obs = obstacles[i];
obs.update();
// Remove if off screen
if (obs.y > 2732 + 100) {
obs.destroy();
obstacles.splice(i, 1);
continue;
}
// Collision with player (tight bounds)
var hit = false;
if (player.getTightBounds && obs.getTightBounds) {
var pb = player.getTightBounds();
var ob = obs.getTightBounds();
hit = pb.x + pb.width > ob.x && pb.x < ob.x + ob.width && pb.y + pb.height > ob.y && pb.y < ob.y + ob.height;
} else {
hit = player.intersects(obs);
}
if (hit && !lastHit) {
LK.effects.flashScreen(0xff0000, 800);
LK.getSound('hit').play();
// Show "BOOOM" text in the center of the screen
var boomTxt = new Text2('BOOOM', {
size: 300,
fill: 0xFF2222,
font: "Impact, Arial Black, Tahoma"
});
boomTxt.anchor.set(0.5, 0.5);
boomTxt.x = 2048 / 2;
boomTxt.y = 2732 / 2;
game.addChild(boomTxt);
tween(boomTxt, {
alpha: 0
}, {
duration: 1000,
onComplete: function onComplete() {
boomTxt.destroy();
}
});
// Decrement hearts and update UI
hearts--;
drawHearts();
if (hearts <= 0) {
// End game immediately when hearts reach 0
LK.showGameOver();
lastHit = true;
break;
}
lastHit = true;
break;
}
}
// Reset lastHit if no collision
if (!obstacles.some(function (o) {
return player.intersects(o);
})) {
lastHit = false;
}
// Update collectibles
for (var j = collectibles.length - 1; j >= 0; j--) {
var col = collectibles[j];
col.update();
// Remove if off screen
if (col.y > 2732 + 80) {
col.destroy();
collectibles.splice(j, 1);
continue;
}
// Collect (tight bounds)
var collectHit = false;
if (player.getTightBounds && col.getTightBounds) {
var pb = player.getTightBounds();
var cb = col.getTightBounds();
collectHit = pb.x + pb.width > cb.x && pb.x < cb.x + cb.width && pb.y + pb.height > cb.y && pb.y < cb.y + cb.height;
} else {
collectHit = player.intersects(col);
}
if (collectHit) {
LK.getSound('collect').play();
score += 1;
scoreTxt.setText(score);
LK.setScore(score);
// Increase speed multiplier, capped at max
speedMultiplier += speedIncreasePerApple;
if (speedMultiplier > maxSpeedMultiplier) speedMultiplier = maxSpeedMultiplier;
// Flash green
LK.effects.flashObject(player, 0x44de83, 300);
col.destroy();
collectibles.splice(j, 1);
continue;
}
}
};
// On game reset, reset score and clear arrays
game.on('reset', function () {
score = 0;
scoreTxt.setText(score);
LK.setScore(score);
for (var i = 0; i < obstacles.length; i++) obstacles[i].destroy();
for (var j = 0; j < collectibles.length; j++) collectibles[j].destroy();
obstacles = [];
collectibles = [];
player.x = 2048 / 2;
player.y = 2732 * 0.75;
lastHit = false;
obstacleTimer = 0;
collectibleTimer = 0;
// Reset hearts
hearts = maxHearts;
drawHearts();
// Reset speed multiplier
speedMultiplier = 1;
});
// Touch fallback: allow dragging player if facekit not available
var dragActive = false;
game.down = function (x, y, obj) {
if (!facekit.mouthCenter) {
dragActive = true;
player.x = x;
player.y = y;
}
};
game.move = function (x, y, obj) {
if (dragActive && !facekit.mouthCenter) {
player.x = x;
player.y = y;
}
};
game.up = function (x, y, obj) {
dragActive = false;
};