/**** * 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)