/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var facekit = LK.import("@upit/facekit.v1");
/****
* Classes
****/
// Player character controlled by face
var FaceChar = Container.expand(function () {
var self = Container.call(this);
var _char = self.attachAsset('faceChar', {
anchorX: 0.5,
anchorY: 0.5
});
// For hit feedback
self.flash = function () {
tween(self, {
alpha: 0.3
}, {
duration: 80,
onFinish: function onFinish() {
tween(self, {
alpha: 1
}, {
duration: 120
});
}
});
};
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
});
// Speed is set on creation
self.speed = 0;
self.update = function () {
self.y += self.speed;
};
return self;
});
// Point collectible class
var PointItem = Container.expand(function () {
var self = Container.call(this);
var pt = self.attachAsset('point', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0;
self.update = function () {
self.y += self.speed;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181c24
});
/****
* Game Code
****/
/*
We use simple shapes for the character, obstacles, and points.
*/
// Game area: 2048x2732
// Center Y for spawn
var GAME_W = 2048;
var GAME_H = 2732;
// Main character
var faceChar = new FaceChar();
faceChar.x = GAME_W / 2;
faceChar.y = GAME_H - 400;
game.addChild(faceChar);
// Arrays for obstacles and points
var obstacles = [];
var points = [];
// Score display
var scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Best score display (optional, not persistent)
var bestScore = 0;
// Game state
var isDead = false;
var lastScore = 0;
// Spawning timers
var obstacleTimer = 0;
var pointTimer = 0;
// For face smoothing
var lastFaceX = faceChar.x;
var lastFaceY = faceChar.y;
// Helper: clamp
function clamp(val, min, max) {
return Math.max(min, Math.min(max, val));
}
// Main update loop
game.update = function () {
if (isDead) return;
// --- Face tracking movement ---
// Use mouthCenter if available, else fallback to noseTip
var fx = facekit.mouthCenter && facekit.mouthCenter.x ? facekit.mouthCenter.x : facekit.noseTip && facekit.noseTip.x ? facekit.noseTip.x : GAME_W / 2;
var fy = facekit.mouthCenter && facekit.mouthCenter.y ? facekit.mouthCenter.y : facekit.noseTip && facekit.noseTip.y ? facekit.noseTip.y : GAME_H - 400;
// Clamp to game area, keep character fully visible
var charR = faceChar.width / 2;
fx = clamp(fx, charR, GAME_W - charR);
fy = clamp(fy, charR + 120, GAME_H - charR - 40);
// Smooth movement (lerp)
lastFaceX += (fx - lastFaceX) * 0.35;
lastFaceY += (fy - lastFaceY) * 0.35;
faceChar.x = lastFaceX;
faceChar.y = lastFaceY;
// --- Spawning obstacles ---
obstacleTimer--;
if (obstacleTimer <= 0) {
var obs = new Obstacle();
// Random X, avoid left 100px (menu)
var minX = 120 + obs.width / 2;
var maxX = GAME_W - obs.width / 2;
obs.x = minX + Math.random() * (maxX - minX);
obs.y = -obs.height / 2;
// Speed increases with score
obs.speed = 12 + LK.getScore() * 0.12;
obstacles.push(obs);
game.addChild(obs);
// Next spawn: 36-60 ticks
obstacleTimer = 36 + Math.floor(Math.random() * 24);
}
// --- Spawning points ---
pointTimer--;
if (pointTimer <= 0) {
var pt = new PointItem();
var minX = 120 + pt.width / 2;
var maxX = GAME_W - pt.width / 2;
pt.x = minX + Math.random() * (maxX - minX);
pt.y = -pt.height / 2;
pt.speed = 10 + LK.getScore() * 0.10;
points.push(pt);
game.addChild(pt);
// Next spawn: 60-120 ticks
pointTimer = 60 + Math.floor(Math.random() * 60);
}
// --- Update obstacles ---
for (var i = obstacles.length - 1; i >= 0; i--) {
var o = obstacles[i];
o.update();
// Remove if off screen
if (o.y - o.height / 2 > GAME_H + 40) {
o.destroy();
obstacles.splice(i, 1);
continue;
}
// Collision with player
if (o.intersects(faceChar)) {
// Game over
isDead = true;
faceChar.flash();
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
return;
}
}
// --- Update points ---
for (var j = points.length - 1; j >= 0; j--) {
var p = points[j];
p.update();
// Remove if off screen
if (p.y - p.height / 2 > GAME_H + 40) {
p.destroy();
points.splice(j, 1);
continue;
}
// Collect
if (p.intersects(faceChar)) {
LK.setScore(LK.getScore() + 1);
scoreTxt.setText(LK.getScore());
// Play point collect sound
LK.getSound('pointCollect').play();
// Feedback
tween(p, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 180,
onFinish: function onFinish() {
p.destroy();
}
});
points.splice(j, 1);
continue;
}
}
};
// Reset state on new game
game.on('reset', function () {
// Remove all obstacles and points
for (var i = 0; i < obstacles.length; i++) obstacles[i].destroy();
for (var j = 0; j < points.length; j++) points[j].destroy();
obstacles = [];
points = [];
isDead = false;
LK.setScore(0);
scoreTxt.setText('0');
// Reset faceChar position
faceChar.x = GAME_W / 2;
faceChar.y = GAME_H - 400;
lastFaceX = faceChar.x;
lastFaceY = faceChar.y;
obstacleTimer = 30;
pointTimer = 60;
});
// Also reset on game over (for safety)
game.on('gameover', function () {
isDead = true;
});
// Touch fallback: allow dragging character if facekit not available
var dragNode = null;
function handleMove(x, y, obj) {
if (dragNode) {
// Clamp to game area
var charR = faceChar.width / 2;
var nx = clamp(x, charR, GAME_W - charR);
var ny = clamp(y, charR + 120, GAME_H - charR - 40);
faceChar.x = nx;
faceChar.y = ny;
lastFaceX = nx;
lastFaceY = ny;
}
}
game.move = handleMove;
game.down = function (x, y, obj) {
// Only allow drag if facekit not available
if (!facekit.mouthCenter || !facekit.mouthCenter.x) {
dragNode = faceChar;
handleMove(x, y, obj);
}
};
game.up = function (x, y, obj) {
dragNode = null;
}; ===================================================================
--- original.js
+++ change.js
@@ -185,8 +185,10 @@
// Collect
if (p.intersects(faceChar)) {
LK.setScore(LK.getScore() + 1);
scoreTxt.setText(LK.getScore());
+ // Play point collect sound
+ LK.getSound('pointCollect').play();
// Feedback
tween(p, {
scaleX: 1.5,
scaleY: 1.5,