/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var facekit = LK.import("@upit/facekit.v1");
/****
* Classes
****/
// Animal class
var Animal = Container.expand(function (assetId) {
var self = Container.call(this);
// Attach animal asset
var animalGraphics = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Escape state
self.escaped = false;
// Movement velocity
self.vx = 0;
self.vy = 0;
// Used for mouth open "boost"
self.boostTimer = 0;
// Update method
self.update = function () {
if (self.escaped) return;
// Apply velocity
self.x += self.vx;
self.y += self.vy;
// Friction
self.vx *= 0.92;
self.vy *= 0.92;
// Clamp inside snake tummy
if (self.x < tummy.x - tummy.width / 2 + animalGraphics.width / 2) {
self.x = tummy.x - tummy.width / 2 + animalGraphics.width / 2;
self.vx *= -0.5;
}
if (self.x > tummy.x + tummy.width / 2 - animalGraphics.width / 2) {
self.x = tummy.x + tummy.width / 2 - animalGraphics.width / 2;
self.vx *= -0.5;
}
if (self.y < tummy.y - tummy.height / 2 + animalGraphics.height / 2) {
self.y = tummy.y - tummy.height / 2 + animalGraphics.height / 2;
self.vy *= -0.5;
}
if (self.y > tummy.y + tummy.height / 2 - animalGraphics.height / 2) {
self.y = tummy.y + tummy.height / 2 - animalGraphics.height / 2;
self.vy *= -0.5;
}
};
// Escape animation
self.escape = function () {
self.escaped = true;
tween(self, {
y: self.y - 600,
alpha: 0
}, {
duration: 900,
easing: tween.easeIn,
onFinish: function onFinish() {
self.visible = false;
}
});
};
return self;
});
// Obstacle class
var Obstacle = Container.expand(function () {
var self = Container.call(this);
var obsGraphics = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5
});
// Movement
self.vx = 0;
self.vy = 0;
self.update = function () {
self.x += self.vx;
self.y += self.vy;
// Bounce off tummy walls
if (self.x < tummy.x - tummy.width / 2 + obsGraphics.width / 2) {
self.x = tummy.x - tummy.width / 2 + obsGraphics.width / 2;
self.vx *= -1;
}
if (self.x > tummy.x + tummy.width / 2 - obsGraphics.width / 2) {
self.x = tummy.x + tummy.width / 2 - obsGraphics.width / 2;
self.vx *= -1;
}
if (self.y < tummy.y - tummy.height / 2 + obsGraphics.height / 2) {
self.y = tummy.y - tummy.height / 2 + obsGraphics.height / 2;
self.vy *= -1;
}
if (self.y > tummy.y + tummy.height / 2 - obsGraphics.height / 2) {
self.y = tummy.y + tummy.height / 2 - obsGraphics.height / 2;
self.vy *= -1;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Success flash
// Obstacle
// Animal shapes (different colors)
// Snake tummy (background, semi-transparent)
/*
We use simple shapes for animals and obstacles for now.
Each animal will be a different colored ellipse.
The snake's tummy is a large, semi-transparent ellipse in the center.
Obstacles are red ellipses.
*/
// Use facekit: show camera feed as background
facekit.enabled = true;
// Center of the game
var centerX = 2048 / 2;
var centerY = 2732 / 2;
// Snake tummy (background, not interactive)
var tummy = LK.getAsset('snakeTummy', {
anchorX: 0.5,
anchorY: 0.5,
x: centerX,
y: centerY,
alpha: 0.7
});
game.addChild(tummy);
// List of animal types and their asset ids
var animalTypes = [{
id: 'animal_pig',
name: 'Pig'
}, {
id: 'animal_cat',
name: 'Cat'
}, {
id: 'animal_bee',
name: 'Bee'
}, {
id: 'animal_frog',
name: 'Frog'
}, {
id: 'animal_polarbear',
name: 'Polar Bear'
}, {
id: 'animal_elephant',
name: 'Elephant'
}, {
id: 'animal_bird',
name: 'Bird'
}, {
id: 'animal_snail',
name: 'Snail'
}, {
id: 'animal_axolotl',
name: 'Axolotl'
}, {
id: 'animal_fox',
name: 'Pink Fox'
}, {
id: 'animal_boybat',
name: 'Boy Bat'
}, {
id: 'animal_wolf',
name: 'Wolf'
}, {
id: 'animal_chinchilla',
name: 'Chinchilla'
}, {
id: 'animal_moth',
name: 'Moth'
}];
// Shuffle animals for random placement
function shuffle(arr) {
for (var i = arr.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}
shuffle(animalTypes);
// Create animals
var animals = [];
for (var i = 0; i < animalTypes.length; i++) {
var a = new Animal(animalTypes[i].id);
// Place animals in a circle inside the tummy
var angle = i / animalTypes.length * Math.PI * 2;
var radius = 500;
a.x = centerX + Math.cos(angle) * radius;
a.y = centerY + Math.sin(angle) * radius;
a.name = animalTypes[i].name;
animals.push(a);
game.addChild(a);
}
// Obstacles
var obstacles = [];
for (var i = 0; i < 4; i++) {
var obs = new Obstacle();
// Place randomly inside tummy
obs.x = centerX + (Math.random() - 0.5) * 700;
obs.y = centerY + (Math.random() - 0.5) * 900;
// Random velocity
obs.vx = (Math.random() - 0.5) * 7;
obs.vy = (Math.random() - 0.5) * 7;
obstacles.push(obs);
game.addChild(obs);
}
// Score: animals escaped
var animalsEscaped = 0;
// Score text
var scoreTxt = new Text2('0 / ' + animals.length, {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Instructions text
var instructions = new Text2('Move your face to guide the animals!\nOpen your mouth to help them escape!', {
size: 70,
fill: "#fff"
});
instructions.anchor.set(0.5, 0);
LK.gui.bottom.addChild(instructions);
// Main animal to control (the first in the array)
var mainAnimal = animals[0];
// Helper: get facekit mouth center, fallback to center
function getFaceX() {
if (facekit.mouthCenter && typeof facekit.mouthCenter.x === 'number') {
return facekit.mouthCenter.x;
}
return centerX;
}
function getFaceY() {
if (facekit.mouthCenter && typeof facekit.mouthCenter.y === 'number') {
return facekit.mouthCenter.y;
}
return centerY;
}
// Helper: check if animal is at escape zone (top of tummy)
function isAtEscapeZone(animal) {
// Escape zone: top 120px of tummy
return animal.y < tummy.y - tummy.height / 2 + 120;
}
// Helper: check collision between two containers (circle approx)
function isColliding(a, b) {
var dx = a.x - b.x;
var dy = a.y - b.y;
var dist = Math.sqrt(dx * dx + dy * dy);
var r1 = a.width || 90;
var r2 = b.width || 60;
return dist < r1 / 2 + r2 / 2 - 10;
}
// Flash effect for escape
function flashEscape() {
var flash = LK.getAsset('escapeFlash', {
anchorX: 0.5,
anchorY: 0.5,
x: centerX,
y: centerY,
alpha: 0.7
});
game.addChild(flash);
tween(flash, {
alpha: 0
}, {
duration: 600,
onFinish: function onFinish() {
flash.destroy();
}
});
}
// Game update
game.update = function () {
// Update all animals
for (var i = 0; i < animals.length; i++) {
animals[i].update();
}
// Update obstacles
for (var i = 0; i < obstacles.length; i++) {
obstacles[i].update();
}
// Control main animal with face
if (!mainAnimal.escaped) {
// Move towards face position
var fx = getFaceX();
var fy = getFaceY();
// Clamp to tummy area
var tx = Math.max(tummy.x - tummy.width / 2 + 90, Math.min(tummy.x + tummy.width / 2 - 90, fx));
var ty = Math.max(tummy.y - tummy.height / 2 + 90, Math.min(tummy.y + tummy.height / 2 - 90, fy));
// Smooth follow
mainAnimal.vx += (tx - mainAnimal.x) * 0.08;
mainAnimal.vy += (ty - mainAnimal.y) * 0.08;
// If mouth open, give a boost upwards
if (facekit.mouthOpen) {
mainAnimal.vy -= 2.5;
mainAnimal.boostTimer = 8;
} else if (mainAnimal.boostTimer > 0) {
mainAnimal.vy -= 1.2;
mainAnimal.boostTimer--;
}
// If at escape zone, escape!
if (isAtEscapeZone(mainAnimal)) {
mainAnimal.escape();
animalsEscaped++;
scoreTxt.setText(animalsEscaped + ' / ' + animals.length);
flashEscape();
// Next animal becomes main
var found = false;
for (var i = 0; i < animals.length; i++) {
if (!animals[i].escaped) {
mainAnimal = animals[i];
found = true;
break;
}
}
if (!found) {
// All escaped!
LK.showYouWin();
}
}
}
// Collisions: main animal with obstacles
if (!mainAnimal.escaped) {
for (var i = 0; i < obstacles.length; i++) {
if (isColliding(mainAnimal, obstacles[i])) {
// Flash red, reset animal to center
LK.effects.flashScreen(0xff0000, 600);
mainAnimal.x = centerX;
mainAnimal.y = centerY + 400;
mainAnimal.vx = 0;
mainAnimal.vy = 0;
}
}
}
// Collisions: animals with each other (push apart)
for (var i = 0; i < animals.length; i++) {
for (var j = i + 1; j < animals.length; j++) {
if (animals[i].escaped || animals[j].escaped) continue;
var dx = animals[i].x - animals[j].x;
var dy = animals[i].y - animals[j].y;
var dist = Math.sqrt(dx * dx + dy * dy);
var minDist = 90;
if (dist < minDist && dist > 0) {
var overlap = (minDist - dist) / 2;
var ox = dx / dist * overlap;
var oy = dy / dist * overlap;
animals[i].x += ox;
animals[i].y += oy;
animals[j].x -= ox;
animals[j].y -= oy;
}
}
}
};
// No touch controls: all control is via facekit
// No music, no sound, no pause, no game over handling (handled by LK) /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var facekit = LK.import("@upit/facekit.v1");
/****
* Classes
****/
// Animal class
var Animal = Container.expand(function (assetId) {
var self = Container.call(this);
// Attach animal asset
var animalGraphics = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Escape state
self.escaped = false;
// Movement velocity
self.vx = 0;
self.vy = 0;
// Used for mouth open "boost"
self.boostTimer = 0;
// Update method
self.update = function () {
if (self.escaped) return;
// Apply velocity
self.x += self.vx;
self.y += self.vy;
// Friction
self.vx *= 0.92;
self.vy *= 0.92;
// Clamp inside snake tummy
if (self.x < tummy.x - tummy.width / 2 + animalGraphics.width / 2) {
self.x = tummy.x - tummy.width / 2 + animalGraphics.width / 2;
self.vx *= -0.5;
}
if (self.x > tummy.x + tummy.width / 2 - animalGraphics.width / 2) {
self.x = tummy.x + tummy.width / 2 - animalGraphics.width / 2;
self.vx *= -0.5;
}
if (self.y < tummy.y - tummy.height / 2 + animalGraphics.height / 2) {
self.y = tummy.y - tummy.height / 2 + animalGraphics.height / 2;
self.vy *= -0.5;
}
if (self.y > tummy.y + tummy.height / 2 - animalGraphics.height / 2) {
self.y = tummy.y + tummy.height / 2 - animalGraphics.height / 2;
self.vy *= -0.5;
}
};
// Escape animation
self.escape = function () {
self.escaped = true;
tween(self, {
y: self.y - 600,
alpha: 0
}, {
duration: 900,
easing: tween.easeIn,
onFinish: function onFinish() {
self.visible = false;
}
});
};
return self;
});
// Obstacle class
var Obstacle = Container.expand(function () {
var self = Container.call(this);
var obsGraphics = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5
});
// Movement
self.vx = 0;
self.vy = 0;
self.update = function () {
self.x += self.vx;
self.y += self.vy;
// Bounce off tummy walls
if (self.x < tummy.x - tummy.width / 2 + obsGraphics.width / 2) {
self.x = tummy.x - tummy.width / 2 + obsGraphics.width / 2;
self.vx *= -1;
}
if (self.x > tummy.x + tummy.width / 2 - obsGraphics.width / 2) {
self.x = tummy.x + tummy.width / 2 - obsGraphics.width / 2;
self.vx *= -1;
}
if (self.y < tummy.y - tummy.height / 2 + obsGraphics.height / 2) {
self.y = tummy.y - tummy.height / 2 + obsGraphics.height / 2;
self.vy *= -1;
}
if (self.y > tummy.y + tummy.height / 2 - obsGraphics.height / 2) {
self.y = tummy.y + tummy.height / 2 - obsGraphics.height / 2;
self.vy *= -1;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Success flash
// Obstacle
// Animal shapes (different colors)
// Snake tummy (background, semi-transparent)
/*
We use simple shapes for animals and obstacles for now.
Each animal will be a different colored ellipse.
The snake's tummy is a large, semi-transparent ellipse in the center.
Obstacles are red ellipses.
*/
// Use facekit: show camera feed as background
facekit.enabled = true;
// Center of the game
var centerX = 2048 / 2;
var centerY = 2732 / 2;
// Snake tummy (background, not interactive)
var tummy = LK.getAsset('snakeTummy', {
anchorX: 0.5,
anchorY: 0.5,
x: centerX,
y: centerY,
alpha: 0.7
});
game.addChild(tummy);
// List of animal types and their asset ids
var animalTypes = [{
id: 'animal_pig',
name: 'Pig'
}, {
id: 'animal_cat',
name: 'Cat'
}, {
id: 'animal_bee',
name: 'Bee'
}, {
id: 'animal_frog',
name: 'Frog'
}, {
id: 'animal_polarbear',
name: 'Polar Bear'
}, {
id: 'animal_elephant',
name: 'Elephant'
}, {
id: 'animal_bird',
name: 'Bird'
}, {
id: 'animal_snail',
name: 'Snail'
}, {
id: 'animal_axolotl',
name: 'Axolotl'
}, {
id: 'animal_fox',
name: 'Pink Fox'
}, {
id: 'animal_boybat',
name: 'Boy Bat'
}, {
id: 'animal_wolf',
name: 'Wolf'
}, {
id: 'animal_chinchilla',
name: 'Chinchilla'
}, {
id: 'animal_moth',
name: 'Moth'
}];
// Shuffle animals for random placement
function shuffle(arr) {
for (var i = arr.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
}
shuffle(animalTypes);
// Create animals
var animals = [];
for (var i = 0; i < animalTypes.length; i++) {
var a = new Animal(animalTypes[i].id);
// Place animals in a circle inside the tummy
var angle = i / animalTypes.length * Math.PI * 2;
var radius = 500;
a.x = centerX + Math.cos(angle) * radius;
a.y = centerY + Math.sin(angle) * radius;
a.name = animalTypes[i].name;
animals.push(a);
game.addChild(a);
}
// Obstacles
var obstacles = [];
for (var i = 0; i < 4; i++) {
var obs = new Obstacle();
// Place randomly inside tummy
obs.x = centerX + (Math.random() - 0.5) * 700;
obs.y = centerY + (Math.random() - 0.5) * 900;
// Random velocity
obs.vx = (Math.random() - 0.5) * 7;
obs.vy = (Math.random() - 0.5) * 7;
obstacles.push(obs);
game.addChild(obs);
}
// Score: animals escaped
var animalsEscaped = 0;
// Score text
var scoreTxt = new Text2('0 / ' + animals.length, {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Instructions text
var instructions = new Text2('Move your face to guide the animals!\nOpen your mouth to help them escape!', {
size: 70,
fill: "#fff"
});
instructions.anchor.set(0.5, 0);
LK.gui.bottom.addChild(instructions);
// Main animal to control (the first in the array)
var mainAnimal = animals[0];
// Helper: get facekit mouth center, fallback to center
function getFaceX() {
if (facekit.mouthCenter && typeof facekit.mouthCenter.x === 'number') {
return facekit.mouthCenter.x;
}
return centerX;
}
function getFaceY() {
if (facekit.mouthCenter && typeof facekit.mouthCenter.y === 'number') {
return facekit.mouthCenter.y;
}
return centerY;
}
// Helper: check if animal is at escape zone (top of tummy)
function isAtEscapeZone(animal) {
// Escape zone: top 120px of tummy
return animal.y < tummy.y - tummy.height / 2 + 120;
}
// Helper: check collision between two containers (circle approx)
function isColliding(a, b) {
var dx = a.x - b.x;
var dy = a.y - b.y;
var dist = Math.sqrt(dx * dx + dy * dy);
var r1 = a.width || 90;
var r2 = b.width || 60;
return dist < r1 / 2 + r2 / 2 - 10;
}
// Flash effect for escape
function flashEscape() {
var flash = LK.getAsset('escapeFlash', {
anchorX: 0.5,
anchorY: 0.5,
x: centerX,
y: centerY,
alpha: 0.7
});
game.addChild(flash);
tween(flash, {
alpha: 0
}, {
duration: 600,
onFinish: function onFinish() {
flash.destroy();
}
});
}
// Game update
game.update = function () {
// Update all animals
for (var i = 0; i < animals.length; i++) {
animals[i].update();
}
// Update obstacles
for (var i = 0; i < obstacles.length; i++) {
obstacles[i].update();
}
// Control main animal with face
if (!mainAnimal.escaped) {
// Move towards face position
var fx = getFaceX();
var fy = getFaceY();
// Clamp to tummy area
var tx = Math.max(tummy.x - tummy.width / 2 + 90, Math.min(tummy.x + tummy.width / 2 - 90, fx));
var ty = Math.max(tummy.y - tummy.height / 2 + 90, Math.min(tummy.y + tummy.height / 2 - 90, fy));
// Smooth follow
mainAnimal.vx += (tx - mainAnimal.x) * 0.08;
mainAnimal.vy += (ty - mainAnimal.y) * 0.08;
// If mouth open, give a boost upwards
if (facekit.mouthOpen) {
mainAnimal.vy -= 2.5;
mainAnimal.boostTimer = 8;
} else if (mainAnimal.boostTimer > 0) {
mainAnimal.vy -= 1.2;
mainAnimal.boostTimer--;
}
// If at escape zone, escape!
if (isAtEscapeZone(mainAnimal)) {
mainAnimal.escape();
animalsEscaped++;
scoreTxt.setText(animalsEscaped + ' / ' + animals.length);
flashEscape();
// Next animal becomes main
var found = false;
for (var i = 0; i < animals.length; i++) {
if (!animals[i].escaped) {
mainAnimal = animals[i];
found = true;
break;
}
}
if (!found) {
// All escaped!
LK.showYouWin();
}
}
}
// Collisions: main animal with obstacles
if (!mainAnimal.escaped) {
for (var i = 0; i < obstacles.length; i++) {
if (isColliding(mainAnimal, obstacles[i])) {
// Flash red, reset animal to center
LK.effects.flashScreen(0xff0000, 600);
mainAnimal.x = centerX;
mainAnimal.y = centerY + 400;
mainAnimal.vx = 0;
mainAnimal.vy = 0;
}
}
}
// Collisions: animals with each other (push apart)
for (var i = 0; i < animals.length; i++) {
for (var j = i + 1; j < animals.length; j++) {
if (animals[i].escaped || animals[j].escaped) continue;
var dx = animals[i].x - animals[j].x;
var dy = animals[i].y - animals[j].y;
var dist = Math.sqrt(dx * dx + dy * dy);
var minDist = 90;
if (dist < minDist && dist > 0) {
var overlap = (minDist - dist) / 2;
var ox = dx / dist * overlap;
var oy = dy / dist * overlap;
animals[i].x += ox;
animals[i].y += oy;
animals[j].x -= ox;
animals[j].y -= oy;
}
}
}
};
// No touch controls: all control is via facekit
// No music, no sound, no pause, no game over handling (handled by LK)