/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Acid Bubble (obstacle) var AcidBubble = Container.expand(function () { var self = Container.call(this); var bubble = self.attachAsset('acidBubble', { anchorX: 0.5, anchorY: 0.5 }); self.radius = bubble.width / 2; self.vx = (Math.random() - 0.5) * 6; self.vy = (Math.random() - 0.5) * 6; self.update = function () { self.x += self.vx; self.y += self.vy; // Bounce off belly walls if (self.x < snakeBelly.x - snakeBelly.width / 2 + self.radius) { self.x = snakeBelly.x - snakeBelly.width / 2 + self.radius; self.vx *= -1; } if (self.x > snakeBelly.x + snakeBelly.width / 2 - self.radius) { self.x = snakeBelly.x + snakeBelly.width / 2 - self.radius; self.vx *= -1; } if (self.y < snakeBelly.y - snakeBelly.height / 2 + self.radius) { self.y = snakeBelly.y - snakeBelly.height / 2 + self.radius; self.vy *= -1; } if (self.y > snakeBelly.y + snakeBelly.height / 2 - self.radius) { self.y = snakeBelly.y + snakeBelly.height / 2 - self.radius; self.vy *= -1; } }; return self; }); // Animal class var Animal = Container.expand(function () { var self = Container.call(this); // Defensive: set a default assetId if not set if (!self.assetId) self.assetId = 'pig'; // Asset id is passed in as self.assetId var animalSprite = self.attachAsset(self.assetId, { anchorX: 0.5, anchorY: 0.5 }); self.radius = animalSprite.width / 2; // Used for movement self.vx = 0; self.vy = 0; self.isSelected = false; // Called every tick self.update = function () { // Gravity inside snake self.vy += 0.5; self.x += self.vx; self.y += self.vy; // Friction self.vx *= 0.95; self.vy *= 0.95; // Keep inside belly bounds if (self.x < snakeBelly.x - snakeBelly.width / 2 + self.radius) { self.x = snakeBelly.x - snakeBelly.width / 2 + self.radius; self.vx *= -0.5; } if (self.x > snakeBelly.x + snakeBelly.width / 2 - self.radius) { self.x = snakeBelly.x + snakeBelly.width / 2 - self.radius; self.vx *= -0.5; } if (self.y < snakeBelly.y - snakeBelly.height / 2 + self.radius) { self.y = snakeBelly.y - snakeBelly.height / 2 + self.radius; self.vy *= -0.5; } if (self.y > snakeBelly.y + snakeBelly.height / 2 - self.radius) { self.y = snakeBelly.y + snakeBelly.height / 2 - self.radius; self.vy *= -0.5; } }; // Touch down on animal self.down = function (x, y, obj) { self.isSelected = true; selectedAnimal = self; dragOffsetX = self.x - x; dragOffsetY = self.y - y; }; // Touch up self.up = function (x, y, obj) { self.isSelected = false; selectedAnimal = null; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x222222 }); /**** * Game Code ****/ // Sound for animal jump // Exit (esophagus opening) // Obstacle (stomach acid bubble) // Snake exterior (for outside interaction) // Snake belly (background) // Animal shapes (simple colored ellipses for each animal) // --- Global variables --- var animalIds = ['pig', 'cat', 'bee', 'frog', 'polarBear', 'elephant', 'bird', 'snail', 'axolotl', 'pinkFox', 'boyBat', 'wolf', 'chinchilla', 'moth', 'goat', 'bunny', 'bear', 'deer']; var animals = []; var acidBubbles = []; var selectedAnimal = null; var dragOffsetX = 0; var dragOffsetY = 0; var insideMode = true; // true: inside snake, false: outside var snakeBelly, snakeSkin, exitNode; var scoreTxt; var rescuedCount = 0; var totalToRescue = 5; // MVP: rescue 5 animals to win // --- Setup snake belly (inside) --- snakeBelly = LK.getAsset('snakeBelly', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 1200 }); game.addChild(snakeBelly); // --- Setup exit (esophagus opening) --- exitNode = LK.getAsset('exit', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 800 }); game.addChild(exitNode); // --- Setup animals --- function spawnAnimals() { animals = []; var startX = 2048 / 2 - 400; var startY = 1200; for (var i = 0; i < totalToRescue; i++) { var id = animalIds[i]; var animal = new Animal(); animal.assetId = id; animal.x = startX + i * 200; animal.y = startY + Math.random() * 100 - 50; animal.vx = (Math.random() - 0.5) * 6; animal.vy = (Math.random() - 0.5) * 6; animals.push(animal); game.addChild(animal); } } spawnAnimals(); // --- Setup acid bubbles (obstacles) --- function spawnAcidBubbles() { acidBubbles = []; for (var i = 0; i < 3; i++) { var bubble = new AcidBubble(); bubble.x = 2048 / 2 + (Math.random() - 0.5) * 600; bubble.y = 1200 + (Math.random() - 0.5) * 300; acidBubbles.push(bubble); game.addChild(bubble); } } spawnAcidBubbles(); // --- Setup score text --- scoreTxt = new Text2('0', { size: 120, fill: "#fff" }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // --- Setup snake skin (outside) --- snakeSkin = LK.getAsset('snakeSkin', { anchorX: 0.5, anchorY: 0, x: 2048 / 2, y: 0 }); // Not added yet; only shown in outside mode // --- Helper: reset game state --- function resetGame() { // Remove all animals and bubbles for (var i = 0; i < animals.length; i++) animals[i].destroy(); for (var i = 0; i < acidBubbles.length; i++) acidBubbles[i].destroy(); animals = []; acidBubbles = []; rescuedCount = 0; scoreTxt.setText('0'); insideMode = true; if (snakeSkin.parent) snakeSkin.parent.removeChild(snakeSkin); if (!snakeBelly.parent) game.addChild(snakeBelly); if (!exitNode.parent) game.addChild(exitNode); spawnAnimals(); spawnAcidBubbles(); } // --- Touch/drag logic --- game.move = function (x, y, obj) { if (!insideMode) return; if (selectedAnimal) { // Move animal with finger selectedAnimal.x = x + dragOffsetX; selectedAnimal.y = y + dragOffsetY; } }; game.down = function (x, y, obj) { if (!insideMode) return; // Check if any animal is touched for (var i = 0; i < animals.length; i++) { var a = animals[i]; var dx = x - a.x; var dy = y - a.y; if (dx * dx + dy * dy < a.radius * a.radius) { a.down(x, y, obj); return; } } }; game.up = function (x, y, obj) { if (!insideMode) return; if (selectedAnimal) { // Give a jump impulse upwards selectedAnimal.vy = -18; selectedAnimal.vx = (Math.random() - 0.5) * 8; LK.getSound('jump').play(); selectedAnimal.up(x, y, obj); } }; // --- Outside mode: tap snake skin to help animals escape --- snakeSkin.down = function (x, y, obj) { if (!insideMode) { // "Massage" the snake: all animals get a little upward boost for (var i = 0; i < animals.length; i++) { animals[i].vy -= 10 + Math.random() * 6; } // Animate snake skin tween(snakeSkin, { y: 20 }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { tween(snakeSkin, { y: 0 }, { duration: 180, easing: tween.easeIn }); } }); } }; // --- Game update loop --- game.update = function () { // Switch between inside/outside every 10 seconds if (LK.ticks % 600 === 0) { insideMode = !insideMode; if (insideMode) { // Show inside if (!snakeBelly.parent) game.addChild(snakeBelly); if (!exitNode.parent) game.addChild(exitNode); if (snakeSkin.parent) snakeSkin.parent.removeChild(snakeSkin); } else { // Show outside if (!snakeSkin.parent) game.addChild(snakeSkin); if (snakeBelly.parent) snakeBelly.parent.removeChild(snakeBelly); if (exitNode.parent) exitNode.parent.removeChild(exitNode); } } // Update animals for (var i = animals.length - 1; i >= 0; i--) { var a = animals[i]; a.update(); // If animal touches exit, rescue it! if (insideMode && a.intersects(exitNode)) { // Animate out tween(a, { y: exitNode.y - 200 }, { duration: 400, easing: tween.easeIn, onFinish: function onFinish() { a.destroy(); } }); animals.splice(i, 1); rescuedCount++; scoreTxt.setText(rescuedCount); // Win condition if (rescuedCount >= totalToRescue) { LK.showYouWin(); return; } } } // Update acid bubbles for (var i = acidBubbles.length - 1; i >= 0; i--) { var b = acidBubbles[i]; b.update(); // If any animal hits a bubble, game over for (var j = 0; j < animals.length; j++) { var a = animals[j]; var dx = a.x - b.x; var dy = a.y - b.y; var dist = a.radius + b.radius - 10; if (dx * dx + dy * dy < dist * dist) { LK.effects.flashScreen(0xff0000, 800); LK.showGameOver(); return; } } } }; // --- Reset game on game over or win --- LK.on('gameover', function () { resetGame(); }); LK.on('youwin', function () { resetGame(); }); // --- Instructions text --- var instructions = new Text2("Drag animals to help them jump to the exit!\nAvoid acid bubbles.\nSometimes tap the snake's back to help!", { size: 60, fill: "#fff" }); instructions.anchor.set(0.5, 0); LK.gui.top.addChild(instructions); instructions.y = 180; instructions.x = 2048 / 2;
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Acid Bubble (obstacle)
var AcidBubble = Container.expand(function () {
var self = Container.call(this);
var bubble = self.attachAsset('acidBubble', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = bubble.width / 2;
self.vx = (Math.random() - 0.5) * 6;
self.vy = (Math.random() - 0.5) * 6;
self.update = function () {
self.x += self.vx;
self.y += self.vy;
// Bounce off belly walls
if (self.x < snakeBelly.x - snakeBelly.width / 2 + self.radius) {
self.x = snakeBelly.x - snakeBelly.width / 2 + self.radius;
self.vx *= -1;
}
if (self.x > snakeBelly.x + snakeBelly.width / 2 - self.radius) {
self.x = snakeBelly.x + snakeBelly.width / 2 - self.radius;
self.vx *= -1;
}
if (self.y < snakeBelly.y - snakeBelly.height / 2 + self.radius) {
self.y = snakeBelly.y - snakeBelly.height / 2 + self.radius;
self.vy *= -1;
}
if (self.y > snakeBelly.y + snakeBelly.height / 2 - self.radius) {
self.y = snakeBelly.y + snakeBelly.height / 2 - self.radius;
self.vy *= -1;
}
};
return self;
});
// Animal class
var Animal = Container.expand(function () {
var self = Container.call(this);
// Defensive: set a default assetId if not set
if (!self.assetId) self.assetId = 'pig';
// Asset id is passed in as self.assetId
var animalSprite = self.attachAsset(self.assetId, {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = animalSprite.width / 2;
// Used for movement
self.vx = 0;
self.vy = 0;
self.isSelected = false;
// Called every tick
self.update = function () {
// Gravity inside snake
self.vy += 0.5;
self.x += self.vx;
self.y += self.vy;
// Friction
self.vx *= 0.95;
self.vy *= 0.95;
// Keep inside belly bounds
if (self.x < snakeBelly.x - snakeBelly.width / 2 + self.radius) {
self.x = snakeBelly.x - snakeBelly.width / 2 + self.radius;
self.vx *= -0.5;
}
if (self.x > snakeBelly.x + snakeBelly.width / 2 - self.radius) {
self.x = snakeBelly.x + snakeBelly.width / 2 - self.radius;
self.vx *= -0.5;
}
if (self.y < snakeBelly.y - snakeBelly.height / 2 + self.radius) {
self.y = snakeBelly.y - snakeBelly.height / 2 + self.radius;
self.vy *= -0.5;
}
if (self.y > snakeBelly.y + snakeBelly.height / 2 - self.radius) {
self.y = snakeBelly.y + snakeBelly.height / 2 - self.radius;
self.vy *= -0.5;
}
};
// Touch down on animal
self.down = function (x, y, obj) {
self.isSelected = true;
selectedAnimal = self;
dragOffsetX = self.x - x;
dragOffsetY = self.y - y;
};
// Touch up
self.up = function (x, y, obj) {
self.isSelected = false;
selectedAnimal = null;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
// Sound for animal jump
// Exit (esophagus opening)
// Obstacle (stomach acid bubble)
// Snake exterior (for outside interaction)
// Snake belly (background)
// Animal shapes (simple colored ellipses for each animal)
// --- Global variables ---
var animalIds = ['pig', 'cat', 'bee', 'frog', 'polarBear', 'elephant', 'bird', 'snail', 'axolotl', 'pinkFox', 'boyBat', 'wolf', 'chinchilla', 'moth', 'goat', 'bunny', 'bear', 'deer'];
var animals = [];
var acidBubbles = [];
var selectedAnimal = null;
var dragOffsetX = 0;
var dragOffsetY = 0;
var insideMode = true; // true: inside snake, false: outside
var snakeBelly, snakeSkin, exitNode;
var scoreTxt;
var rescuedCount = 0;
var totalToRescue = 5; // MVP: rescue 5 animals to win
// --- Setup snake belly (inside) ---
snakeBelly = LK.getAsset('snakeBelly', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 1200
});
game.addChild(snakeBelly);
// --- Setup exit (esophagus opening) ---
exitNode = LK.getAsset('exit', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 800
});
game.addChild(exitNode);
// --- Setup animals ---
function spawnAnimals() {
animals = [];
var startX = 2048 / 2 - 400;
var startY = 1200;
for (var i = 0; i < totalToRescue; i++) {
var id = animalIds[i];
var animal = new Animal();
animal.assetId = id;
animal.x = startX + i * 200;
animal.y = startY + Math.random() * 100 - 50;
animal.vx = (Math.random() - 0.5) * 6;
animal.vy = (Math.random() - 0.5) * 6;
animals.push(animal);
game.addChild(animal);
}
}
spawnAnimals();
// --- Setup acid bubbles (obstacles) ---
function spawnAcidBubbles() {
acidBubbles = [];
for (var i = 0; i < 3; i++) {
var bubble = new AcidBubble();
bubble.x = 2048 / 2 + (Math.random() - 0.5) * 600;
bubble.y = 1200 + (Math.random() - 0.5) * 300;
acidBubbles.push(bubble);
game.addChild(bubble);
}
}
spawnAcidBubbles();
// --- Setup score text ---
scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// --- Setup snake skin (outside) ---
snakeSkin = LK.getAsset('snakeSkin', {
anchorX: 0.5,
anchorY: 0,
x: 2048 / 2,
y: 0
});
// Not added yet; only shown in outside mode
// --- Helper: reset game state ---
function resetGame() {
// Remove all animals and bubbles
for (var i = 0; i < animals.length; i++) animals[i].destroy();
for (var i = 0; i < acidBubbles.length; i++) acidBubbles[i].destroy();
animals = [];
acidBubbles = [];
rescuedCount = 0;
scoreTxt.setText('0');
insideMode = true;
if (snakeSkin.parent) snakeSkin.parent.removeChild(snakeSkin);
if (!snakeBelly.parent) game.addChild(snakeBelly);
if (!exitNode.parent) game.addChild(exitNode);
spawnAnimals();
spawnAcidBubbles();
}
// --- Touch/drag logic ---
game.move = function (x, y, obj) {
if (!insideMode) return;
if (selectedAnimal) {
// Move animal with finger
selectedAnimal.x = x + dragOffsetX;
selectedAnimal.y = y + dragOffsetY;
}
};
game.down = function (x, y, obj) {
if (!insideMode) return;
// Check if any animal is touched
for (var i = 0; i < animals.length; i++) {
var a = animals[i];
var dx = x - a.x;
var dy = y - a.y;
if (dx * dx + dy * dy < a.radius * a.radius) {
a.down(x, y, obj);
return;
}
}
};
game.up = function (x, y, obj) {
if (!insideMode) return;
if (selectedAnimal) {
// Give a jump impulse upwards
selectedAnimal.vy = -18;
selectedAnimal.vx = (Math.random() - 0.5) * 8;
LK.getSound('jump').play();
selectedAnimal.up(x, y, obj);
}
};
// --- Outside mode: tap snake skin to help animals escape ---
snakeSkin.down = function (x, y, obj) {
if (!insideMode) {
// "Massage" the snake: all animals get a little upward boost
for (var i = 0; i < animals.length; i++) {
animals[i].vy -= 10 + Math.random() * 6;
}
// Animate snake skin
tween(snakeSkin, {
y: 20
}, {
duration: 120,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(snakeSkin, {
y: 0
}, {
duration: 180,
easing: tween.easeIn
});
}
});
}
};
// --- Game update loop ---
game.update = function () {
// Switch between inside/outside every 10 seconds
if (LK.ticks % 600 === 0) {
insideMode = !insideMode;
if (insideMode) {
// Show inside
if (!snakeBelly.parent) game.addChild(snakeBelly);
if (!exitNode.parent) game.addChild(exitNode);
if (snakeSkin.parent) snakeSkin.parent.removeChild(snakeSkin);
} else {
// Show outside
if (!snakeSkin.parent) game.addChild(snakeSkin);
if (snakeBelly.parent) snakeBelly.parent.removeChild(snakeBelly);
if (exitNode.parent) exitNode.parent.removeChild(exitNode);
}
}
// Update animals
for (var i = animals.length - 1; i >= 0; i--) {
var a = animals[i];
a.update();
// If animal touches exit, rescue it!
if (insideMode && a.intersects(exitNode)) {
// Animate out
tween(a, {
y: exitNode.y - 200
}, {
duration: 400,
easing: tween.easeIn,
onFinish: function onFinish() {
a.destroy();
}
});
animals.splice(i, 1);
rescuedCount++;
scoreTxt.setText(rescuedCount);
// Win condition
if (rescuedCount >= totalToRescue) {
LK.showYouWin();
return;
}
}
}
// Update acid bubbles
for (var i = acidBubbles.length - 1; i >= 0; i--) {
var b = acidBubbles[i];
b.update();
// If any animal hits a bubble, game over
for (var j = 0; j < animals.length; j++) {
var a = animals[j];
var dx = a.x - b.x;
var dy = a.y - b.y;
var dist = a.radius + b.radius - 10;
if (dx * dx + dy * dy < dist * dist) {
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
return;
}
}
}
};
// --- Reset game on game over or win ---
LK.on('gameover', function () {
resetGame();
});
LK.on('youwin', function () {
resetGame();
});
// --- Instructions text ---
var instructions = new Text2("Drag animals to help them jump to the exit!\nAvoid acid bubbles.\nSometimes tap the snake's back to help!", {
size: 60,
fill: "#fff"
});
instructions.anchor.set(0.5, 0);
LK.gui.top.addChild(instructions);
instructions.y = 180;
instructions.x = 2048 / 2;