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