/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var facekit = LK.import("@upit/facekit.v1"); /**** * Classes ****/ // Obstacle class: moves downward, destroys itself off-screen var Obstacle = Container.expand(function () { var self = Container.call(this); var obstacleSprite = self.attachAsset('obstacle', { anchorX: 0.5, anchorY: 0.5 }); // Speed (randomized) self.speed = 10 + Math.random() * 8; // For collision detection self.obstacleSprite = obstacleSprite; self.update = function () { self.y += self.speed; }; return self; }); // Player class: follows mouth position, shows shield if mouth open var Player = Container.expand(function () { var self = Container.call(this); // Player body var playerSprite = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); // Shield (hidden by default) var shieldSprite = self.attachAsset('shield', { anchorX: 0.5, anchorY: 0.5, alpha: 0.0 }); // Show/hide shield self.setShield = function (active) { if (active) { shieldSprite.alpha = 0.5; } else { shieldSprite.alpha = 0.0; } }; // For collision detection, expose playerSprite and shieldSprite self.playerSprite = playerSprite; self.shieldSprite = shieldSprite; return self; }); // Point class: moves downward, destroys itself off-screen var Point = Container.expand(function () { var self = Container.call(this); var pointSprite = self.attachAsset('point', { anchorX: 0.5, anchorY: 0.5 }); // Speed (slower than obstacles) self.speed = 7 + Math.random() * 4; // For collision detection self.pointSprite = pointSprite; self.update = function () { self.y += self.speed; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ // No title, no description // Always backgroundColor is black backgroundColor: 0x000000 }); /**** * Game Code ****/ // --- Face Dash: Smile to Survive --- // Game area: 2048x2732 // Player setup var player = new Player(); player.x = 2048 / 2; player.y = 2732 * 0.7; game.addChild(player); // Score and lives var score = 0; var scoreTxt = new Text2('0', { size: 120, fill: 0xFFF700 }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); var lives = 3; var livesTxt = new Text2('❤❤❤', { size: 90, fill: 0xFF3333 }); livesTxt.anchor.set(0.5, 0); LK.gui.topRight.addChild(livesTxt); // Game objects var obstacles = []; var points = []; // Timers var obstacleTimer = 0; var pointTimer = 0; // Invulnerability var invulnTicks = 0; var INVULN_DURATION = 60; // Helper: update lives text function updateLivesText() { var s = ''; for (var i = 0; i < lives; i++) s += '❤'; livesTxt.setText(s); } // Helper: clamp player position inside game area function clampPlayerPosition() { var r = 100; if (player.x < r) player.x = r; if (player.x > 2048 - r) player.x = 2048 - r; if (player.y < r + 100) player.y = r + 100; if (player.y > 2732 - r) player.y = 2732 - r; } // --- Main update loop --- game.update = function () { // 1. Update player position from facekit var mouth = facekit.mouthCenter; if (mouth && typeof mouth.x === 'number' && typeof mouth.y === 'number') { player.x += (mouth.x - player.x) * 0.35; player.y += (mouth.y - player.y) * 0.35; clampPlayerPosition(); } // 2. Shield: mouth open var shieldActive = !!facekit.mouthOpen; player.setShield(shieldActive); // 3. Spawn obstacles obstacleTimer--; if (obstacleTimer <= 0) { var obs = new Obstacle(); obs.x = 180 + Math.random() * (2048 - 360); obs.y = -80; obs.lastY = obs.y; obs.lastWasIntersecting = false; obstacles.push(obs); game.addChild(obs); obstacleTimer = 30 + Math.floor(Math.random() * 30); } // 4. Spawn points pointTimer--; if (pointTimer <= 0) { var pt = new Point(); pt.x = 180 + Math.random() * (2048 - 360); pt.y = -60; pt.lastY = pt.y; pt.lastWasIntersecting = false; points.push(pt); game.addChild(pt); pointTimer = 80 + Math.floor(Math.random() * 80); } // 5. Update obstacles for (var i = obstacles.length - 1; i >= 0; i--) { var obs = obstacles[i]; obs.update(); // Remove if off-screen if (obs.lastY <= 2732 + 100 && obs.y > 2732 + 100) { obs.destroy(); obstacles.splice(i, 1); continue; } // Collision with player: only trigger on collision moment var isIntersecting = obs.obstacleSprite.intersects(player.playerSprite); if (!obs.lastWasIntersecting && isIntersecting) { if (shieldActive || invulnTicks > 0) { // Blocked: flash obstacle, destroy tween(obs.obstacleSprite, { alpha: 0 }, { duration: 200, onFinish: function onFinish() { obs.destroy(); } }); obstacles.splice(i, 1); continue; } else { // Hit: lose life, invuln, flash player lives--; updateLivesText(); invulnTicks = INVULN_DURATION; LK.effects.flashObject(player, 0xff0000, 600); if (lives <= 0) { LK.effects.flashScreen(0xff0000, 1000); LK.showGameOver(); return; } obs.destroy(); obstacles.splice(i, 1); continue; } } obs.lastY = obs.y; obs.lastWasIntersecting = isIntersecting; } // 6. Update points for (var j = points.length - 1; j >= 0; j--) { var pt = points[j]; pt.update(); // Remove if off-screen if (pt.lastY <= 2732 + 80 && pt.y > 2732 + 80) { pt.destroy(); points.splice(j, 1); continue; } // Collision with player: only trigger on collision moment var isIntersecting = pt.pointSprite.intersects(player.playerSprite); if (!pt.lastWasIntersecting && isIntersecting) { // Collect point score += 1; LK.setScore(score); scoreTxt.setText(score); tween(pt.pointSprite, { alpha: 0 }, { duration: 200, onFinish: function onFinish() { pt.destroy(); } }); points.splice(j, 1); continue; } pt.lastY = pt.y; pt.lastWasIntersecting = isIntersecting; } // 7. Invulnerability timer if (invulnTicks > 0) { invulnTicks--; player.playerSprite.alpha = invulnTicks % 8 < 4 ? 0.4 : 1.0; } else { player.playerSprite.alpha = 1.0; } }; // On game start, reset everything function resetGame() { score = 0; LK.setScore(0); scoreTxt.setText('0'); lives = 3; updateLivesText(); invulnTicks = 0; for (var i = 0; i < obstacles.length; i++) obstacles[i].destroy(); for (var j = 0; j < points.length; j++) points[j].destroy(); obstacles = []; points = []; player.x = 2048 / 2; player.y = 2732 * 0.7; player.setShield(false); } resetGame(); // No touch/mouse controls needed; all via facekit // End of MVP;
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var facekit = LK.import("@upit/facekit.v1");
/****
* Classes
****/
// Obstacle class: moves downward, destroys itself off-screen
var Obstacle = Container.expand(function () {
var self = Container.call(this);
var obstacleSprite = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5
});
// Speed (randomized)
self.speed = 10 + Math.random() * 8;
// For collision detection
self.obstacleSprite = obstacleSprite;
self.update = function () {
self.y += self.speed;
};
return self;
});
// Player class: follows mouth position, shows shield if mouth open
var Player = Container.expand(function () {
var self = Container.call(this);
// Player body
var playerSprite = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
// Shield (hidden by default)
var shieldSprite = self.attachAsset('shield', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.0
});
// Show/hide shield
self.setShield = function (active) {
if (active) {
shieldSprite.alpha = 0.5;
} else {
shieldSprite.alpha = 0.0;
}
};
// For collision detection, expose playerSprite and shieldSprite
self.playerSprite = playerSprite;
self.shieldSprite = shieldSprite;
return self;
});
// Point class: moves downward, destroys itself off-screen
var Point = Container.expand(function () {
var self = Container.call(this);
var pointSprite = self.attachAsset('point', {
anchorX: 0.5,
anchorY: 0.5
});
// Speed (slower than obstacles)
self.speed = 7 + Math.random() * 4;
// For collision detection
self.pointSprite = pointSprite;
self.update = function () {
self.y += self.speed;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
// No title, no description
// Always backgroundColor is black
backgroundColor: 0x000000
});
/****
* Game Code
****/
// --- Face Dash: Smile to Survive ---
// Game area: 2048x2732
// Player setup
var player = new Player();
player.x = 2048 / 2;
player.y = 2732 * 0.7;
game.addChild(player);
// Score and lives
var score = 0;
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFF700
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var lives = 3;
var livesTxt = new Text2('❤❤❤', {
size: 90,
fill: 0xFF3333
});
livesTxt.anchor.set(0.5, 0);
LK.gui.topRight.addChild(livesTxt);
// Game objects
var obstacles = [];
var points = [];
// Timers
var obstacleTimer = 0;
var pointTimer = 0;
// Invulnerability
var invulnTicks = 0;
var INVULN_DURATION = 60;
// Helper: update lives text
function updateLivesText() {
var s = '';
for (var i = 0; i < lives; i++) s += '❤';
livesTxt.setText(s);
}
// Helper: clamp player position inside game area
function clampPlayerPosition() {
var r = 100;
if (player.x < r) player.x = r;
if (player.x > 2048 - r) player.x = 2048 - r;
if (player.y < r + 100) player.y = r + 100;
if (player.y > 2732 - r) player.y = 2732 - r;
}
// --- Main update loop ---
game.update = function () {
// 1. Update player position from facekit
var mouth = facekit.mouthCenter;
if (mouth && typeof mouth.x === 'number' && typeof mouth.y === 'number') {
player.x += (mouth.x - player.x) * 0.35;
player.y += (mouth.y - player.y) * 0.35;
clampPlayerPosition();
}
// 2. Shield: mouth open
var shieldActive = !!facekit.mouthOpen;
player.setShield(shieldActive);
// 3. Spawn obstacles
obstacleTimer--;
if (obstacleTimer <= 0) {
var obs = new Obstacle();
obs.x = 180 + Math.random() * (2048 - 360);
obs.y = -80;
obs.lastY = obs.y;
obs.lastWasIntersecting = false;
obstacles.push(obs);
game.addChild(obs);
obstacleTimer = 30 + Math.floor(Math.random() * 30);
}
// 4. Spawn points
pointTimer--;
if (pointTimer <= 0) {
var pt = new Point();
pt.x = 180 + Math.random() * (2048 - 360);
pt.y = -60;
pt.lastY = pt.y;
pt.lastWasIntersecting = false;
points.push(pt);
game.addChild(pt);
pointTimer = 80 + Math.floor(Math.random() * 80);
}
// 5. Update obstacles
for (var i = obstacles.length - 1; i >= 0; i--) {
var obs = obstacles[i];
obs.update();
// Remove if off-screen
if (obs.lastY <= 2732 + 100 && obs.y > 2732 + 100) {
obs.destroy();
obstacles.splice(i, 1);
continue;
}
// Collision with player: only trigger on collision moment
var isIntersecting = obs.obstacleSprite.intersects(player.playerSprite);
if (!obs.lastWasIntersecting && isIntersecting) {
if (shieldActive || invulnTicks > 0) {
// Blocked: flash obstacle, destroy
tween(obs.obstacleSprite, {
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
obs.destroy();
}
});
obstacles.splice(i, 1);
continue;
} else {
// Hit: lose life, invuln, flash player
lives--;
updateLivesText();
invulnTicks = INVULN_DURATION;
LK.effects.flashObject(player, 0xff0000, 600);
if (lives <= 0) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
return;
}
obs.destroy();
obstacles.splice(i, 1);
continue;
}
}
obs.lastY = obs.y;
obs.lastWasIntersecting = isIntersecting;
}
// 6. Update points
for (var j = points.length - 1; j >= 0; j--) {
var pt = points[j];
pt.update();
// Remove if off-screen
if (pt.lastY <= 2732 + 80 && pt.y > 2732 + 80) {
pt.destroy();
points.splice(j, 1);
continue;
}
// Collision with player: only trigger on collision moment
var isIntersecting = pt.pointSprite.intersects(player.playerSprite);
if (!pt.lastWasIntersecting && isIntersecting) {
// Collect point
score += 1;
LK.setScore(score);
scoreTxt.setText(score);
tween(pt.pointSprite, {
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
pt.destroy();
}
});
points.splice(j, 1);
continue;
}
pt.lastY = pt.y;
pt.lastWasIntersecting = isIntersecting;
}
// 7. Invulnerability timer
if (invulnTicks > 0) {
invulnTicks--;
player.playerSprite.alpha = invulnTicks % 8 < 4 ? 0.4 : 1.0;
} else {
player.playerSprite.alpha = 1.0;
}
};
// On game start, reset everything
function resetGame() {
score = 0;
LK.setScore(0);
scoreTxt.setText('0');
lives = 3;
updateLivesText();
invulnTicks = 0;
for (var i = 0; i < obstacles.length; i++) obstacles[i].destroy();
for (var j = 0; j < points.length; j++) points[j].destroy();
obstacles = [];
points = [];
player.x = 2048 / 2;
player.y = 2732 * 0.7;
player.setShield(false);
}
resetGame();
// No touch/mouse controls needed; all via facekit
// End of MVP;