/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var facekit = LK.import("@upit/facekit.v1"); /**** * Classes ****/ // Ball class var Ball = Container.expand(function () { var self = Container.call(this); var ballAsset = self.attachAsset('ball', { anchorX: 0.5, anchorY: 0.5 }); // Ball velocity self.vx = 0; self.vy = 0; // Ball acceleration (for realism, e.g. gravity or air resistance) self.ax = 0; self.ay = 0; // Ball friction (air resistance) self.friction = 0.998; // Ball max speed self.maxSpeed = 24; // Ball radius for collision self.getRadius = function () { return ballAsset.width / 2; }; // Ball update: move and handle wall bounces self.update = function () { // Apply acceleration (gravity only on Y) self.vx += self.ax; self.vy += self.ay; // If the ball is moving upwards, keep speed consistent (no friction) // If the ball is moving downwards, apply gravity and friction (energy loss) if (self.vy < 0) { // Upwards: keep speed consistent, no friction // Clamp speed to maxSpeed var speed = Math.sqrt(self.vx * self.vx + self.vy * self.vy); if (speed > self.maxSpeed) { var scale = self.maxSpeed / speed; self.vx *= scale; self.vy *= scale; } } else { // Downwards: apply friction and gravity (energy loss) self.vx *= self.friction; self.vy *= self.friction; // Add gravity to make it fall faster as it loses energy self.vy += 0.45; // gravity strength, tweak as needed } // Move ball self.x += self.vx; self.y += self.vy; }; return self; }); // Paddle class var Paddle = Container.expand(function () { var self = Container.call(this); var paddleAsset = self.attachAsset('paddle', { anchorX: 0.5, anchorY: 0.5 }); // Paddle width/height for collision self.getWidth = function () { return paddleAsset.width; }; self.getHeight = function () { return paddleAsset.height; }; return self; }); // Wall class (for top wall) var Wall = Container.expand(function () { var self = Container.call(this); var wallAsset = self.attachAsset('wall', { anchorX: 0.5, anchorY: 0.5 }); self.getWidth = function () { return wallAsset.width; }; self.getHeight = function () { return wallAsset.height; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // Center positions // Paddle: wide, short rectangle // Ball: circle // Wall: thin, wide rectangle (for top wall) // Sound for bounce (optional, but not used as per instructions) // LK.init.sound('bounce', {volume: 0.5}); var GAME_WIDTH = 2048; var GAME_HEIGHT = 2732; // Score var score = 0; // Create paddle var paddle = new Paddle(); game.addChild(paddle); // Paddle position: bottom center, above bottom edge paddle.x = GAME_WIDTH / 2; paddle.y = GAME_HEIGHT - 180; // Create ball var ball = new Ball(); game.addChild(ball); // Ball start position: above paddle ball.x = GAME_WIDTH / 2; ball.y = paddle.y - 120; // Ball initial velocity (randomized angle upwards) function resetBallVelocity() { // Angle: between -60 and +60 degrees from vertical var angle = Math.random() * Math.PI / 3 - Math.PI / 6; var speed = 18; ball.vx = Math.sin(angle) * speed; ball.vy = -Math.cos(angle) * speed; ball.ax = 0; ball.ay = 0; // gravity is handled in Ball.update } resetBallVelocity(); // Create top wall var topWall = new Wall(); game.addChild(topWall); topWall.x = GAME_WIDTH / 2; topWall.y = 40 / 2 + 10; // 10px margin from top // Score text var scoreTxt = new Text2('0', { size: 120, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Helper: clamp paddle within game bounds function clampPaddleX(x) { var halfWidth = paddle.getWidth() / 2; if (x < halfWidth) return halfWidth; if (x > GAME_WIDTH - halfWidth) return GAME_WIDTH - halfWidth; return x; } // Face tracking: update paddle position and rotation game.update = function () { var mouthX = GAME_WIDTH / 2; var mouthY = GAME_HEIGHT - 180; var mouthAngle = 0; // Use facekit for paddle position if available (ignore angle/rotation) if (facekit.mouthCenter && typeof facekit.mouthCenter.x === 'number' && typeof facekit.mouthCenter.y === 'number') { mouthX = facekit.mouthCenter.x; mouthY = facekit.mouthCenter.y; } // Clamp paddle inside game area var halfWidth = paddle.getWidth() / 2; var halfHeight = paddle.getHeight() / 2; if (typeof paddle.lastX !== "number") paddle.lastX = paddle.x; if (typeof paddle.lastY !== "number") paddle.lastY = paddle.y; var prevX = paddle.x; var prevY = paddle.y; paddle.x = Math.max(halfWidth, Math.min(GAME_WIDTH - halfWidth, mouthX)); paddle.y = Math.max(halfHeight + 40, Math.min(GAME_HEIGHT - halfHeight, mouthY)); paddle.rotation = 0; paddle.lastX = prevX; paddle.lastY = prevY; // Ball movement ball.update(); // Ball collision with left/right walls var ballRadius = ball.getRadius(); if (ball.x - ballRadius < 0) { ball.x = ballRadius; // Add a little randomness to bounce angle for realism var angleJitter = (Math.random() - 0.5) * 0.12; var speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy) * 0.98; // lose a bit of energy var angle = Math.atan2(ball.vy, -ball.vx) + angleJitter; ball.vx = Math.abs(Math.cos(angle) * speed); ball.vy = Math.sin(angle) * speed; } if (ball.x + ballRadius > GAME_WIDTH) { ball.x = GAME_WIDTH - ballRadius; var angleJitter = (Math.random() - 0.5) * 0.12; var speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy) * 0.98; var angle = Math.atan2(ball.vy, -ball.vx) + angleJitter; ball.vx = -Math.abs(Math.cos(angle) * speed); ball.vy = Math.sin(angle) * speed; } // Ball collision with top wall if (ball.y - ballRadius < topWall.y + topWall.getHeight() / 2) { ball.y = topWall.y + topWall.getHeight() / 2 + ballRadius; var angleJitter = (Math.random() - 0.5) * 0.12; var speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy) * 0.98; var angle = Math.atan2(-ball.vy, ball.vx) + angleJitter; ball.vy = Math.abs(Math.cos(angle) * speed); ball.vx = Math.sin(angle) * speed; } // Ball collision with paddle (axis-aligned rectangle vs circle) var hw = paddle.getWidth() / 2; var hh = paddle.getHeight() / 2; var closestX = Math.max(paddle.x - hw, Math.min(ball.x, paddle.x + hw)); var closestY = Math.max(paddle.y - hh, Math.min(ball.y, paddle.y + hh)); var dx = ball.x - closestX; var dy = ball.y - closestY; var distSq = dx * dx + dy * dy; // Only allow bounce if ball is moving towards the paddle (ball.vy > 0) if (distSq < ballRadius * ballRadius && ball.vy > 0) { // Calculate bounce direction based on where it hit the paddle var hitPos = (ball.x - paddle.x) / hw; // -1 (left) to 1 (right) var angle = hitPos * (Math.PI / 2.2); // up to +/-~82deg for more control // Add a little randomness to bounce angle for realism var angleJitter = (Math.random() - 0.5) * 0.10; angle += angleJitter; // Add paddle movement to ball (if paddle is moving) var paddleSpeedX = 0; if (typeof paddle.lastX === "number") { paddleSpeedX = paddle.x - paddle.lastX; } // Ball speed increases slightly on each bounce var speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy) * 1.04 + Math.abs(paddleSpeedX) * 0.5; ball.vx = Math.sin(angle) * speed + paddleSpeedX * 0.4; ball.vy = -Math.abs(Math.cos(angle) * speed); // Move ball just outside paddle to prevent sticking var sep = ballRadius + 2; if (dy > 0) { ball.y = paddle.y + hh + sep; } else { ball.y = paddle.y - hh - sep; } // Flash paddle LK.effects.flashObject(paddle, 0xffff00, 200); // Increase score score += 1; LK.setScore(score); scoreTxt.setText(score); // Optionally: play sound // LK.getSound('bounce').play(); } // Ball falls below paddle: game over if (ball.y - ballRadius > GAME_HEIGHT) { // Flash screen red LK.effects.flashScreen(0xff0000, 800); // Show game over (auto resets game) LK.showGameOver(); } }; // No dragging or touch controls needed; paddle is controlled by facekit only // Reset score on game start LK.setScore(0); score = 0; scoreTxt.setText('0');
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var facekit = LK.import("@upit/facekit.v1");
/****
* Classes
****/
// Ball class
var Ball = Container.expand(function () {
var self = Container.call(this);
var ballAsset = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
// Ball velocity
self.vx = 0;
self.vy = 0;
// Ball acceleration (for realism, e.g. gravity or air resistance)
self.ax = 0;
self.ay = 0;
// Ball friction (air resistance)
self.friction = 0.998;
// Ball max speed
self.maxSpeed = 24;
// Ball radius for collision
self.getRadius = function () {
return ballAsset.width / 2;
};
// Ball update: move and handle wall bounces
self.update = function () {
// Apply acceleration (gravity only on Y)
self.vx += self.ax;
self.vy += self.ay;
// If the ball is moving upwards, keep speed consistent (no friction)
// If the ball is moving downwards, apply gravity and friction (energy loss)
if (self.vy < 0) {
// Upwards: keep speed consistent, no friction
// Clamp speed to maxSpeed
var speed = Math.sqrt(self.vx * self.vx + self.vy * self.vy);
if (speed > self.maxSpeed) {
var scale = self.maxSpeed / speed;
self.vx *= scale;
self.vy *= scale;
}
} else {
// Downwards: apply friction and gravity (energy loss)
self.vx *= self.friction;
self.vy *= self.friction;
// Add gravity to make it fall faster as it loses energy
self.vy += 0.45; // gravity strength, tweak as needed
}
// Move ball
self.x += self.vx;
self.y += self.vy;
};
return self;
});
// Paddle class
var Paddle = Container.expand(function () {
var self = Container.call(this);
var paddleAsset = self.attachAsset('paddle', {
anchorX: 0.5,
anchorY: 0.5
});
// Paddle width/height for collision
self.getWidth = function () {
return paddleAsset.width;
};
self.getHeight = function () {
return paddleAsset.height;
};
return self;
});
// Wall class (for top wall)
var Wall = Container.expand(function () {
var self = Container.call(this);
var wallAsset = self.attachAsset('wall', {
anchorX: 0.5,
anchorY: 0.5
});
self.getWidth = function () {
return wallAsset.width;
};
self.getHeight = function () {
return wallAsset.height;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
// Center positions
// Paddle: wide, short rectangle
// Ball: circle
// Wall: thin, wide rectangle (for top wall)
// Sound for bounce (optional, but not used as per instructions)
// LK.init.sound('bounce', {volume: 0.5});
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
// Score
var score = 0;
// Create paddle
var paddle = new Paddle();
game.addChild(paddle);
// Paddle position: bottom center, above bottom edge
paddle.x = GAME_WIDTH / 2;
paddle.y = GAME_HEIGHT - 180;
// Create ball
var ball = new Ball();
game.addChild(ball);
// Ball start position: above paddle
ball.x = GAME_WIDTH / 2;
ball.y = paddle.y - 120;
// Ball initial velocity (randomized angle upwards)
function resetBallVelocity() {
// Angle: between -60 and +60 degrees from vertical
var angle = Math.random() * Math.PI / 3 - Math.PI / 6;
var speed = 18;
ball.vx = Math.sin(angle) * speed;
ball.vy = -Math.cos(angle) * speed;
ball.ax = 0;
ball.ay = 0; // gravity is handled in Ball.update
}
resetBallVelocity();
// Create top wall
var topWall = new Wall();
game.addChild(topWall);
topWall.x = GAME_WIDTH / 2;
topWall.y = 40 / 2 + 10; // 10px margin from top
// Score text
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Helper: clamp paddle within game bounds
function clampPaddleX(x) {
var halfWidth = paddle.getWidth() / 2;
if (x < halfWidth) return halfWidth;
if (x > GAME_WIDTH - halfWidth) return GAME_WIDTH - halfWidth;
return x;
}
// Face tracking: update paddle position and rotation
game.update = function () {
var mouthX = GAME_WIDTH / 2;
var mouthY = GAME_HEIGHT - 180;
var mouthAngle = 0;
// Use facekit for paddle position if available (ignore angle/rotation)
if (facekit.mouthCenter && typeof facekit.mouthCenter.x === 'number' && typeof facekit.mouthCenter.y === 'number') {
mouthX = facekit.mouthCenter.x;
mouthY = facekit.mouthCenter.y;
}
// Clamp paddle inside game area
var halfWidth = paddle.getWidth() / 2;
var halfHeight = paddle.getHeight() / 2;
if (typeof paddle.lastX !== "number") paddle.lastX = paddle.x;
if (typeof paddle.lastY !== "number") paddle.lastY = paddle.y;
var prevX = paddle.x;
var prevY = paddle.y;
paddle.x = Math.max(halfWidth, Math.min(GAME_WIDTH - halfWidth, mouthX));
paddle.y = Math.max(halfHeight + 40, Math.min(GAME_HEIGHT - halfHeight, mouthY));
paddle.rotation = 0;
paddle.lastX = prevX;
paddle.lastY = prevY;
// Ball movement
ball.update();
// Ball collision with left/right walls
var ballRadius = ball.getRadius();
if (ball.x - ballRadius < 0) {
ball.x = ballRadius;
// Add a little randomness to bounce angle for realism
var angleJitter = (Math.random() - 0.5) * 0.12;
var speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy) * 0.98; // lose a bit of energy
var angle = Math.atan2(ball.vy, -ball.vx) + angleJitter;
ball.vx = Math.abs(Math.cos(angle) * speed);
ball.vy = Math.sin(angle) * speed;
}
if (ball.x + ballRadius > GAME_WIDTH) {
ball.x = GAME_WIDTH - ballRadius;
var angleJitter = (Math.random() - 0.5) * 0.12;
var speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy) * 0.98;
var angle = Math.atan2(ball.vy, -ball.vx) + angleJitter;
ball.vx = -Math.abs(Math.cos(angle) * speed);
ball.vy = Math.sin(angle) * speed;
}
// Ball collision with top wall
if (ball.y - ballRadius < topWall.y + topWall.getHeight() / 2) {
ball.y = topWall.y + topWall.getHeight() / 2 + ballRadius;
var angleJitter = (Math.random() - 0.5) * 0.12;
var speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy) * 0.98;
var angle = Math.atan2(-ball.vy, ball.vx) + angleJitter;
ball.vy = Math.abs(Math.cos(angle) * speed);
ball.vx = Math.sin(angle) * speed;
}
// Ball collision with paddle (axis-aligned rectangle vs circle)
var hw = paddle.getWidth() / 2;
var hh = paddle.getHeight() / 2;
var closestX = Math.max(paddle.x - hw, Math.min(ball.x, paddle.x + hw));
var closestY = Math.max(paddle.y - hh, Math.min(ball.y, paddle.y + hh));
var dx = ball.x - closestX;
var dy = ball.y - closestY;
var distSq = dx * dx + dy * dy;
// Only allow bounce if ball is moving towards the paddle (ball.vy > 0)
if (distSq < ballRadius * ballRadius && ball.vy > 0) {
// Calculate bounce direction based on where it hit the paddle
var hitPos = (ball.x - paddle.x) / hw; // -1 (left) to 1 (right)
var angle = hitPos * (Math.PI / 2.2); // up to +/-~82deg for more control
// Add a little randomness to bounce angle for realism
var angleJitter = (Math.random() - 0.5) * 0.10;
angle += angleJitter;
// Add paddle movement to ball (if paddle is moving)
var paddleSpeedX = 0;
if (typeof paddle.lastX === "number") {
paddleSpeedX = paddle.x - paddle.lastX;
}
// Ball speed increases slightly on each bounce
var speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy) * 1.04 + Math.abs(paddleSpeedX) * 0.5;
ball.vx = Math.sin(angle) * speed + paddleSpeedX * 0.4;
ball.vy = -Math.abs(Math.cos(angle) * speed);
// Move ball just outside paddle to prevent sticking
var sep = ballRadius + 2;
if (dy > 0) {
ball.y = paddle.y + hh + sep;
} else {
ball.y = paddle.y - hh - sep;
}
// Flash paddle
LK.effects.flashObject(paddle, 0xffff00, 200);
// Increase score
score += 1;
LK.setScore(score);
scoreTxt.setText(score);
// Optionally: play sound
// LK.getSound('bounce').play();
}
// Ball falls below paddle: game over
if (ball.y - ballRadius > GAME_HEIGHT) {
// Flash screen red
LK.effects.flashScreen(0xff0000, 800);
// Show game over (auto resets game)
LK.showGameOver();
}
};
// No dragging or touch controls needed; paddle is controlled by facekit only
// Reset score on game start
LK.setScore(0);
score = 0;
scoreTxt.setText('0');