/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Obstacle (coral/rock) class var Obstacle = Container.expand(function () { var self = Container.call(this); // Top or bottom self.isTop = false; self.passed = false; // Attach asset var obs = self.attachAsset('obstacle', { anchorX: 0.5, anchorY: 0.0 }); // For top obstacles, flip vertically and anchor to bottom self.setTop = function () { self.isTop = true; obs.scaleY = -1; obs.anchorY = 1.0; }; // Move left every frame self.update = function () { self.x -= obstacleSpeed; }; return self; }); // Penguin class var Penguin = Container.expand(function () { var self = Container.call(this); var penguinSprite = self.attachAsset('penguin', { anchorX: 0.5, anchorY: 0.5 }); self.velocity = 0; self.gravity = 1.1; // Halved for 50% slower fall self.lift = -24; // Halved for 50% slower upward movement self.maxFall = 18; // Halved for 50% slower max fall speed self.alive = true; // Flap animation self.flap = function () { if (!self.alive) return; self.velocity = self.lift; // Animate penguin up a bit tween(self, { rotation: -0.35 }, { duration: 120, easing: tween.cubicOut }); }; // Call every frame self.update = function () { if (!self.alive) return; self.velocity += self.gravity; if (self.velocity > self.maxFall) self.velocity = self.maxFall; self.y += self.velocity; // Rotate penguin based on velocity var targetRot = Math.max(-0.4, Math.min(0.7, self.velocity / 50)); tween(self, { rotation: targetRot }, { duration: 120, easing: tween.linear }); }; // On death self.die = function () { self.alive = false; tween(self, { rotation: 1.2 }, { duration: 400, easing: tween.cubicIn }); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x6ec6f7 }); /**** * Game Code ****/ // Background (ocean blue, for parallax effect) // Sea floor/ceiling // Obstacle (coral/rock) - top and bottom // Penguin (player) // Game constants var GAP_SIZE = 420; // Gap between top and bottom obstacles var OBSTACLE_INTERVAL = 120; // Frames between obstacles (doubled for 50% slower spawn) var obstacleSpeed = 9; // Halved for 50% slower movement var penguinStartX = 600; var penguinStartY = 1366; var floorY = 2732 - 80; var ceilingY = 80; // Background var bg = LK.getAsset('bgwave', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); game.addChild(bg); // Sea floor var floor = LK.getAsset('seabound', { anchorX: 0, anchorY: 0, x: 0, y: floorY }); game.addChild(floor); // Sea ceiling var ceiling = LK.getAsset('seabound', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); game.addChild(ceiling); // Penguin var penguin = new Penguin(); penguin.x = penguinStartX; penguin.y = penguinStartY; game.addChild(penguin); // Obstacles array var obstacles = []; // Score var score = 0; var scoreTxt = new Text2('0', { size: 150, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Drag/tap handler var gameStarted = false; var gameOver = false; // Start the game on first tap function startGame() { if (!gameStarted) { gameStarted = true; } penguin.flap(); } // Touch/click events game.down = function (x, y, obj) { if (gameOver) return; startGame(); }; // No drag, so no move/up needed // Main update loop game.update = function () { if (gameOver) return; // Penguin physics penguin.update(); // Prevent penguin from going off top/ceiling if (penguin.y - 60 < ceilingY) { penguin.y = ceilingY + 60; penguin.velocity = 0; } // Prevent penguin from going below floor if (penguin.y + 60 > floorY) { penguin.y = floorY - 60; penguin.velocity = 0; penguin.die(); endGame(); return; } // Spawn obstacles as pairs: one at the very top, one at the very bottom, with a gap in between if (gameStarted && LK.ticks % OBSTACLE_INTERVAL === 0) { // Randomize the vertical position of the gap (gapY is the center of the gap) var minGapY = ceilingY + GAP_SIZE / 2 + 40; var maxGapY = floorY - GAP_SIZE / 2 - 40; var gapY = Math.floor(minGapY + Math.random() * (maxGapY - minGapY)); // Top obstacle: anchored at the very top, height is up to the start of the gap var topObs = new Obstacle(); topObs.setTop(); topObs.x = 2048 + 90; // Place at y=ceilingY, and scale the obstacle to reach the gap topObs.y = ceilingY; // Set the height of the top obstacle so its bottom is at gapY - GAP_SIZE/2 var topHeight = gapY - GAP_SIZE / 2 - ceilingY; if (topHeight < 40) topHeight = 40; // minimum height topObs.children[0].height = topHeight; obstacles.push(topObs); game.addChild(topObs); // Bottom obstacle: anchored at the very bottom, height is from gap to floor var botObs = new Obstacle(); botObs.x = 2048 + 90; // Place at y = gapY + GAP_SIZE/2 botObs.y = gapY + GAP_SIZE / 2; // Set the height of the bottom obstacle so its top is at gapY + GAP_SIZE/2, and bottom is at floorY var botHeight = floorY - (gapY + GAP_SIZE / 2); if (botHeight < 40) botHeight = 40; // minimum height botObs.children[0].height = botHeight; obstacles.push(botObs); game.addChild(botObs); } // Move and manage obstacles for (var i = obstacles.length - 1; i >= 0; i--) { var obs = obstacles[i]; obs.update(); // Remove if off screen if (obs.x < -200) { obs.destroy(); obstacles.splice(i, 1); continue; } // Collision with penguin if (penguin.alive && penguin.intersects(obs)) { penguin.die(); endGame(); return; } // Score: Only count bottom obstacles, and only once per pair if (!obs.isTop && !obs.passed && obs.x + 90 < penguin.x) { obs.passed = true; score += 1; scoreTxt.setText(score); } } }; // End game function endGame() { gameOver = true; // Flash red LK.effects.flashScreen(0xff0000, 800); // Show game over popup LK.setTimeout(function () { LK.showGameOver(); }, 800); }
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Obstacle (coral/rock) class
var Obstacle = Container.expand(function () {
var self = Container.call(this);
// Top or bottom
self.isTop = false;
self.passed = false;
// Attach asset
var obs = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.0
});
// For top obstacles, flip vertically and anchor to bottom
self.setTop = function () {
self.isTop = true;
obs.scaleY = -1;
obs.anchorY = 1.0;
};
// Move left every frame
self.update = function () {
self.x -= obstacleSpeed;
};
return self;
});
// Penguin class
var Penguin = Container.expand(function () {
var self = Container.call(this);
var penguinSprite = self.attachAsset('penguin', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocity = 0;
self.gravity = 1.1; // Halved for 50% slower fall
self.lift = -24; // Halved for 50% slower upward movement
self.maxFall = 18; // Halved for 50% slower max fall speed
self.alive = true;
// Flap animation
self.flap = function () {
if (!self.alive) return;
self.velocity = self.lift;
// Animate penguin up a bit
tween(self, {
rotation: -0.35
}, {
duration: 120,
easing: tween.cubicOut
});
};
// Call every frame
self.update = function () {
if (!self.alive) return;
self.velocity += self.gravity;
if (self.velocity > self.maxFall) self.velocity = self.maxFall;
self.y += self.velocity;
// Rotate penguin based on velocity
var targetRot = Math.max(-0.4, Math.min(0.7, self.velocity / 50));
tween(self, {
rotation: targetRot
}, {
duration: 120,
easing: tween.linear
});
};
// On death
self.die = function () {
self.alive = false;
tween(self, {
rotation: 1.2
}, {
duration: 400,
easing: tween.cubicIn
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x6ec6f7
});
/****
* Game Code
****/
// Background (ocean blue, for parallax effect)
// Sea floor/ceiling
// Obstacle (coral/rock) - top and bottom
// Penguin (player)
// Game constants
var GAP_SIZE = 420; // Gap between top and bottom obstacles
var OBSTACLE_INTERVAL = 120; // Frames between obstacles (doubled for 50% slower spawn)
var obstacleSpeed = 9; // Halved for 50% slower movement
var penguinStartX = 600;
var penguinStartY = 1366;
var floorY = 2732 - 80;
var ceilingY = 80;
// Background
var bg = LK.getAsset('bgwave', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.addChild(bg);
// Sea floor
var floor = LK.getAsset('seabound', {
anchorX: 0,
anchorY: 0,
x: 0,
y: floorY
});
game.addChild(floor);
// Sea ceiling
var ceiling = LK.getAsset('seabound', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
game.addChild(ceiling);
// Penguin
var penguin = new Penguin();
penguin.x = penguinStartX;
penguin.y = penguinStartY;
game.addChild(penguin);
// Obstacles array
var obstacles = [];
// Score
var score = 0;
var scoreTxt = new Text2('0', {
size: 150,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Drag/tap handler
var gameStarted = false;
var gameOver = false;
// Start the game on first tap
function startGame() {
if (!gameStarted) {
gameStarted = true;
}
penguin.flap();
}
// Touch/click events
game.down = function (x, y, obj) {
if (gameOver) return;
startGame();
};
// No drag, so no move/up needed
// Main update loop
game.update = function () {
if (gameOver) return;
// Penguin physics
penguin.update();
// Prevent penguin from going off top/ceiling
if (penguin.y - 60 < ceilingY) {
penguin.y = ceilingY + 60;
penguin.velocity = 0;
}
// Prevent penguin from going below floor
if (penguin.y + 60 > floorY) {
penguin.y = floorY - 60;
penguin.velocity = 0;
penguin.die();
endGame();
return;
}
// Spawn obstacles as pairs: one at the very top, one at the very bottom, with a gap in between
if (gameStarted && LK.ticks % OBSTACLE_INTERVAL === 0) {
// Randomize the vertical position of the gap (gapY is the center of the gap)
var minGapY = ceilingY + GAP_SIZE / 2 + 40;
var maxGapY = floorY - GAP_SIZE / 2 - 40;
var gapY = Math.floor(minGapY + Math.random() * (maxGapY - minGapY));
// Top obstacle: anchored at the very top, height is up to the start of the gap
var topObs = new Obstacle();
topObs.setTop();
topObs.x = 2048 + 90;
// Place at y=ceilingY, and scale the obstacle to reach the gap
topObs.y = ceilingY;
// Set the height of the top obstacle so its bottom is at gapY - GAP_SIZE/2
var topHeight = gapY - GAP_SIZE / 2 - ceilingY;
if (topHeight < 40) topHeight = 40; // minimum height
topObs.children[0].height = topHeight;
obstacles.push(topObs);
game.addChild(topObs);
// Bottom obstacle: anchored at the very bottom, height is from gap to floor
var botObs = new Obstacle();
botObs.x = 2048 + 90;
// Place at y = gapY + GAP_SIZE/2
botObs.y = gapY + GAP_SIZE / 2;
// Set the height of the bottom obstacle so its top is at gapY + GAP_SIZE/2, and bottom is at floorY
var botHeight = floorY - (gapY + GAP_SIZE / 2);
if (botHeight < 40) botHeight = 40; // minimum height
botObs.children[0].height = botHeight;
obstacles.push(botObs);
game.addChild(botObs);
}
// Move and manage obstacles
for (var i = obstacles.length - 1; i >= 0; i--) {
var obs = obstacles[i];
obs.update();
// Remove if off screen
if (obs.x < -200) {
obs.destroy();
obstacles.splice(i, 1);
continue;
}
// Collision with penguin
if (penguin.alive && penguin.intersects(obs)) {
penguin.die();
endGame();
return;
}
// Score: Only count bottom obstacles, and only once per pair
if (!obs.isTop && !obs.passed && obs.x + 90 < penguin.x) {
obs.passed = true;
score += 1;
scoreTxt.setText(score);
}
}
};
// End game
function endGame() {
gameOver = true;
// Flash red
LK.effects.flashScreen(0xff0000, 800);
// Show game over popup
LK.setTimeout(function () {
LK.showGameOver();
}, 800);
}