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