/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var facekit = LK.import("@upit/facekit.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Obstacle class
var Obstacle = Container.expand(function () {
var self = Container.call(this);
var obsAsset = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 12; // Will be increased as game progresses
self.update = function () {
self.y += self.speed;
};
return self;
});
// Player class
var Player = Container.expand(function () {
var self = Container.call(this);
var playerAsset = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
// For hit feedback
self.flash = function () {
tween(self, {
alpha: 0.5
}, {
duration: 80,
onFinish: function onFinish() {
tween(self, {
alpha: 1
}, {
duration: 120
});
}
});
};
return self;
});
// Point collectible class
var PointItem = Container.expand(function () {
var self = Container.call(this);
var pointAsset = self.attachAsset('point', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 12;
self.update = function () {
self.y += self.speed;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181c2c
});
/****
* Game Code
****/
// Game area: 2048x2732
// Player character: a bright ellipse
// Obstacle: a red box
// Point collectible: a yellow ellipse
// Sound for collecting a point
// Sound for hitting an obstacle
// Music (background, optional)
game.setBackgroundColor(0x181c2c);
// Score display
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFF700
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// High score variable and display
var highScore = storage.highScore || 0;
var highScoreTxt = new Text2('Best Score: ' + highScore, {
size: 60,
fill: 0xFFFFFF
});
highScoreTxt.anchor.set(0.5, 0);
highScoreTxt.y = scoreTxt.height + 10;
LK.gui.top.addChild(highScoreTxt);
// Start music
LK.playMusic('bgmusic', {
fade: {
start: 0,
end: 1,
duration: 1200
}
});
// Player setup
var player = new Player();
game.addChild(player);
// Start in lower center
player.x = 2048 / 2;
player.y = 2732 - 350;
// Arrays for obstacles and points
var obstacles = [];
var points = [];
// Game state
var score = 0;
var ticks = 0;
var gameSpeed = 12; // Initial speed
var spawnInterval = 60; // Ticks between spawns (1s at 60fps)
var lastObstacleSpawn = 0;
var lastPointSpawn = 0;
var isGameOver = false;
// Helper: clamp
function clamp(val, min, max) {
return val < min ? min : val > max ? max : val;
}
// Face control logic
function updatePlayerPosition() {
// Use facekit.mouthCenter if available, else fallback to noseTip or center
var fx = facekit.mouthCenter && facekit.mouthCenter.x ? facekit.mouthCenter.x : facekit.noseTip && facekit.noseTip.x ? facekit.noseTip.x : 1024;
var fy = facekit.mouthCenter && facekit.mouthCenter.y ? facekit.mouthCenter.y : facekit.noseTip && facekit.noseTip.y ? facekit.noseTip.y : 2048;
// Clamp to game area, keep player fully visible
var px = clamp(fx, player.width / 2, 2048 - player.width / 2);
var py = clamp(fy, 2732 - 600, 2732 - player.height / 2); // Only allow movement in lower 600px
// Smooth movement
tween.stop(player, {
x: true,
y: true
});
tween(player, {
x: px,
y: py
}, {
duration: 80,
easing: tween.easeOut
});
}
// Spawn obstacle at random x
function spawnObstacle() {
var obs = new Obstacle();
obs.speed = gameSpeed;
obs.x = clamp(200 + Math.floor(Math.random() * (2048 - 400)), obs.width / 2, 2048 - obs.width / 2);
obs.y = -obs.height / 2;
obstacles.push(obs);
game.addChild(obs);
}
// Spawn point at random x
function spawnPoint() {
var pt = new PointItem();
pt.speed = gameSpeed;
pt.x = clamp(200 + Math.floor(Math.random() * (2048 - 400)), pt.width / 2, 2048 - pt.width / 2);
pt.y = -pt.height / 2;
points.push(pt);
game.addChild(pt);
}
// Game update loop
game.update = function () {
if (isGameOver) return;
ticks++;
// Increase speed every 10 seconds
if (ticks % 600 === 0 && gameSpeed < 32) {
gameSpeed += 2;
if (spawnInterval > 30) spawnInterval -= 4;
}
// Face control: update player position
updatePlayerPosition();
// Microphone: if volume > 0.5, give a small speed boost (visual feedback)
if (facekit.volume > 0.5) {
player.scale.x = 1.18;
player.scale.y = 1.18;
} else {
player.scale.x = 1;
player.scale.y = 1;
}
// Spawn obstacles
if (ticks - lastObstacleSpawn > spawnInterval) {
spawnObstacle();
lastObstacleSpawn = ticks;
}
// Spawn points less frequently
if (ticks - lastPointSpawn > spawnInterval * 1.5) {
spawnPoint();
lastPointSpawn = ticks;
}
// Update obstacles
for (var i = obstacles.length - 1; i >= 0; i--) {
var obs = obstacles[i];
obs.speed = gameSpeed;
obs.update();
// Remove if off screen
if (obs.y - obs.height / 2 > 2732 + 100) {
obs.destroy();
obstacles.splice(i, 1);
continue;
}
// Collision with player
if (player.intersects(obs)) {
// Hit!
isGameOver = true;
LK.getSound('blast').play();
LK.getSound('hit').play();
player.flash();
// Explosion effect at collision point (simulate with flashObject)
LK.effects.flashObject(obs, 0xffa500, 700);
LK.effects.flashScreen(0xff0000, 800);
// Play hit sound when game ends
LK.getSound('hit').play();
LK.showGameOver();
return;
}
}
// Update points
for (var j = points.length - 1; j >= 0; j--) {
var pt = points[j];
pt.speed = gameSpeed;
pt.update();
// Remove if off screen
if (pt.y - pt.height / 2 > 2732 + 100) {
pt.destroy();
points.splice(j, 1);
continue;
}
// Collect
if (player.intersects(pt)) {
score += 1;
LK.setScore(score);
scoreTxt.setText(score);
// High score update
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
highScoreTxt.setText('Best Score: ' + highScore);
}
LK.getSound('collect').play();
// Visual feedback
tween(pt, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 180,
onFinish: function onFinish() {
pt.destroy();
}
});
points.splice(j, 1);
continue;
}
}
};
// Reset on game restart
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 = [];
score = 0;
LK.setScore(0);
scoreTxt.setText('0');
// Reset high score display (do not reset value)
highScore = storage.highScore || highScore;
highScoreTxt.setText('Best Score: ' + highScore);
player.x = 2048 / 2;
player.y = 2732 - 350;
gameSpeed = 12;
spawnInterval = 60;
lastObstacleSpawn = 0;
lastPointSpawn = 0;
ticks = 0;
isGameOver = false;
});
// No touch/mouse controls: face only, so no game.down/game.move/game.up
// Show instructions overlay for first 2 seconds
var instructTxt = new Text2('Move your face to dodge! Open mouth for boost.', {
size: 80,
fill: 0xFFFFFF
});
instructTxt.anchor.set(0.5, 0.5);
instructTxt.x = 2048 / 2;
instructTxt.y = 2732 / 2 - 200;
game.addChild(instructTxt);
tween(instructTxt, {
alpha: 0
}, {
duration: 2000,
onFinish: function onFinish() {
instructTxt.destroy();
}
}); /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var facekit = LK.import("@upit/facekit.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Obstacle class
var Obstacle = Container.expand(function () {
var self = Container.call(this);
var obsAsset = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 12; // Will be increased as game progresses
self.update = function () {
self.y += self.speed;
};
return self;
});
// Player class
var Player = Container.expand(function () {
var self = Container.call(this);
var playerAsset = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
// For hit feedback
self.flash = function () {
tween(self, {
alpha: 0.5
}, {
duration: 80,
onFinish: function onFinish() {
tween(self, {
alpha: 1
}, {
duration: 120
});
}
});
};
return self;
});
// Point collectible class
var PointItem = Container.expand(function () {
var self = Container.call(this);
var pointAsset = self.attachAsset('point', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 12;
self.update = function () {
self.y += self.speed;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181c2c
});
/****
* Game Code
****/
// Game area: 2048x2732
// Player character: a bright ellipse
// Obstacle: a red box
// Point collectible: a yellow ellipse
// Sound for collecting a point
// Sound for hitting an obstacle
// Music (background, optional)
game.setBackgroundColor(0x181c2c);
// Score display
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFF700
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// High score variable and display
var highScore = storage.highScore || 0;
var highScoreTxt = new Text2('Best Score: ' + highScore, {
size: 60,
fill: 0xFFFFFF
});
highScoreTxt.anchor.set(0.5, 0);
highScoreTxt.y = scoreTxt.height + 10;
LK.gui.top.addChild(highScoreTxt);
// Start music
LK.playMusic('bgmusic', {
fade: {
start: 0,
end: 1,
duration: 1200
}
});
// Player setup
var player = new Player();
game.addChild(player);
// Start in lower center
player.x = 2048 / 2;
player.y = 2732 - 350;
// Arrays for obstacles and points
var obstacles = [];
var points = [];
// Game state
var score = 0;
var ticks = 0;
var gameSpeed = 12; // Initial speed
var spawnInterval = 60; // Ticks between spawns (1s at 60fps)
var lastObstacleSpawn = 0;
var lastPointSpawn = 0;
var isGameOver = false;
// Helper: clamp
function clamp(val, min, max) {
return val < min ? min : val > max ? max : val;
}
// Face control logic
function updatePlayerPosition() {
// Use facekit.mouthCenter if available, else fallback to noseTip or center
var fx = facekit.mouthCenter && facekit.mouthCenter.x ? facekit.mouthCenter.x : facekit.noseTip && facekit.noseTip.x ? facekit.noseTip.x : 1024;
var fy = facekit.mouthCenter && facekit.mouthCenter.y ? facekit.mouthCenter.y : facekit.noseTip && facekit.noseTip.y ? facekit.noseTip.y : 2048;
// Clamp to game area, keep player fully visible
var px = clamp(fx, player.width / 2, 2048 - player.width / 2);
var py = clamp(fy, 2732 - 600, 2732 - player.height / 2); // Only allow movement in lower 600px
// Smooth movement
tween.stop(player, {
x: true,
y: true
});
tween(player, {
x: px,
y: py
}, {
duration: 80,
easing: tween.easeOut
});
}
// Spawn obstacle at random x
function spawnObstacle() {
var obs = new Obstacle();
obs.speed = gameSpeed;
obs.x = clamp(200 + Math.floor(Math.random() * (2048 - 400)), obs.width / 2, 2048 - obs.width / 2);
obs.y = -obs.height / 2;
obstacles.push(obs);
game.addChild(obs);
}
// Spawn point at random x
function spawnPoint() {
var pt = new PointItem();
pt.speed = gameSpeed;
pt.x = clamp(200 + Math.floor(Math.random() * (2048 - 400)), pt.width / 2, 2048 - pt.width / 2);
pt.y = -pt.height / 2;
points.push(pt);
game.addChild(pt);
}
// Game update loop
game.update = function () {
if (isGameOver) return;
ticks++;
// Increase speed every 10 seconds
if (ticks % 600 === 0 && gameSpeed < 32) {
gameSpeed += 2;
if (spawnInterval > 30) spawnInterval -= 4;
}
// Face control: update player position
updatePlayerPosition();
// Microphone: if volume > 0.5, give a small speed boost (visual feedback)
if (facekit.volume > 0.5) {
player.scale.x = 1.18;
player.scale.y = 1.18;
} else {
player.scale.x = 1;
player.scale.y = 1;
}
// Spawn obstacles
if (ticks - lastObstacleSpawn > spawnInterval) {
spawnObstacle();
lastObstacleSpawn = ticks;
}
// Spawn points less frequently
if (ticks - lastPointSpawn > spawnInterval * 1.5) {
spawnPoint();
lastPointSpawn = ticks;
}
// Update obstacles
for (var i = obstacles.length - 1; i >= 0; i--) {
var obs = obstacles[i];
obs.speed = gameSpeed;
obs.update();
// Remove if off screen
if (obs.y - obs.height / 2 > 2732 + 100) {
obs.destroy();
obstacles.splice(i, 1);
continue;
}
// Collision with player
if (player.intersects(obs)) {
// Hit!
isGameOver = true;
LK.getSound('blast').play();
LK.getSound('hit').play();
player.flash();
// Explosion effect at collision point (simulate with flashObject)
LK.effects.flashObject(obs, 0xffa500, 700);
LK.effects.flashScreen(0xff0000, 800);
// Play hit sound when game ends
LK.getSound('hit').play();
LK.showGameOver();
return;
}
}
// Update points
for (var j = points.length - 1; j >= 0; j--) {
var pt = points[j];
pt.speed = gameSpeed;
pt.update();
// Remove if off screen
if (pt.y - pt.height / 2 > 2732 + 100) {
pt.destroy();
points.splice(j, 1);
continue;
}
// Collect
if (player.intersects(pt)) {
score += 1;
LK.setScore(score);
scoreTxt.setText(score);
// High score update
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
highScoreTxt.setText('Best Score: ' + highScore);
}
LK.getSound('collect').play();
// Visual feedback
tween(pt, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 180,
onFinish: function onFinish() {
pt.destroy();
}
});
points.splice(j, 1);
continue;
}
}
};
// Reset on game restart
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 = [];
score = 0;
LK.setScore(0);
scoreTxt.setText('0');
// Reset high score display (do not reset value)
highScore = storage.highScore || highScore;
highScoreTxt.setText('Best Score: ' + highScore);
player.x = 2048 / 2;
player.y = 2732 - 350;
gameSpeed = 12;
spawnInterval = 60;
lastObstacleSpawn = 0;
lastPointSpawn = 0;
ticks = 0;
isGameOver = false;
});
// No touch/mouse controls: face only, so no game.down/game.move/game.up
// Show instructions overlay for first 2 seconds
var instructTxt = new Text2('Move your face to dodge! Open mouth for boost.', {
size: 80,
fill: 0xFFFFFF
});
instructTxt.anchor.set(0.5, 0.5);
instructTxt.x = 2048 / 2;
instructTxt.y = 2732 / 2 - 200;
game.addChild(instructTxt);
tween(instructTxt, {
alpha: 0
}, {
duration: 2000,
onFinish: function onFinish() {
instructTxt.destroy();
}
});