/**** * Plugins ****/ var tween = LK.import("@upit/tween.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 }); self.radius = ballAsset.width / 2; // Ball velocity self.vx = 0; self.vy = 0; self.speed = 18; // Initial speed self.setRandomDirection = function () { // Angle between 45 and 135 degrees, randomize left/right var angle = (Math.random() * 0.5 + 0.25) * Math.PI; // 45-135 deg if (Math.random() < 0.5) angle = Math.PI - angle; // flip left/right self.vx = Math.cos(angle) * self.speed; self.vy = Math.sin(angle) * self.speed; }; self.setRandomDirection(); return self; }); // Enemy Paddle class (AI) var EnemyPaddle = Container.expand(function () { var self = Container.call(this); var paddleAsset = self.attachAsset('paddle', { anchorX: 0.5, anchorY: 0.5 }); self.width = paddleAsset.width; self.height = paddleAsset.height; self.speed = 32; // AI paddle speed (pixels per frame) self.update = function () { // Track the ball's x position, but clamp movement speed var dx = ball.x - self.x; if (Math.abs(dx) > self.speed) { self.x += self.speed * (dx > 0 ? 1 : -1); } else { self.x = ball.x; } // Clamp within game area var halfW = self.width / 2; if (self.x < halfW) self.x = halfW; if (self.x > GAME_WIDTH - halfW) self.x = GAME_WIDTH - halfW; }; return self; }); // Hit effect class var HitEffect = Container.expand(function () { var self = Container.call(this); var effectAsset = self.attachAsset('hitEffect', { anchorX: 0.5, anchorY: 0.5, alpha: 0.8 }); // Play animation self.play = function (x, y, color) { self.x = x; self.y = y; self.alpha = 1; self.scale.x = 0.2; self.scale.y = 0.2; // Set color if provided if (color) { effectAsset.tint = color; } else { effectAsset.tint = 0xffffff; } // Scale up and fade out tween(self, { alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 400, easing: tween.easeOut, onFinish: function onFinish() { self.parent.removeChild(self); hitEffects.splice(hitEffects.indexOf(self), 1); } }); }; return self; }); // No plugins needed for MVP // Paddle class var Paddle = Container.expand(function () { var self = Container.call(this); var paddleAsset = self.attachAsset('paddle', { anchorX: 0.5, anchorY: 0.5 }); self.width = paddleAsset.width; self.height = paddleAsset.height; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x181818 }); /**** * Game Code ****/ // Import tween plugin // Ball: circle // Paddle: wide, short rectangle // Game area // Sound effects // Music var GAME_WIDTH = 2048; var GAME_HEIGHT = 2732; var hitEffects = []; // Function to create hit effect function createHitEffect(x, y, color) { var effect = new HitEffect(); game.addChild(effect); hitEffects.push(effect); effect.play(x, y, color); } // Play background music (looping) LK.playMusic('bgmusic'); // Paddle setup var paddle = new Paddle(); game.addChild(paddle); paddle.x = GAME_WIDTH / 2; paddle.y = GAME_HEIGHT - 180; // 180px from bottom // Enemy paddle setup (AI) var enemyPaddle = new EnemyPaddle(); game.addChild(enemyPaddle); enemyPaddle.x = GAME_WIDTH / 2; enemyPaddle.y = 180; // 180px from top // Ball setup var ball = new Ball(); game.addChild(ball); ball.x = GAME_WIDTH / 2; ball.y = GAME_HEIGHT / 2; // Score var score = 0; var scoreTxt = new Text2('0', { size: 120, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); // Dragging var dragging = false; var dragOffsetX = 0; // Touch/mouse controls game.down = function (x, y, obj) { // Only start drag if touch is on/near paddle var dx = x - paddle.x; var dy = y - paddle.y; if (Math.abs(dx) < paddle.width / 2 + 60 && Math.abs(dy) < paddle.height / 2 + 80) { dragging = true; dragOffsetX = paddle.x - x; } }; game.move = function (x, y, obj) { if (dragging) { var newX = x + dragOffsetX; // Clamp paddle within game area var halfW = paddle.width / 2; if (newX < halfW) newX = halfW; if (newX > GAME_WIDTH - halfW) newX = GAME_WIDTH - halfW; paddle.x = newX; } }; game.up = function (x, y, obj) { dragging = false; }; // Ball-paddle collision helper function ballHitsPaddle() { var paddleTop = paddle.y - paddle.height / 2; var paddleLeft = paddle.x - paddle.width / 2; var paddleRight = paddle.x + paddle.width / 2; var ballBottom = ball.y + ball.radius; // Simple collision check for low speeds if (ballBottom >= paddleTop && ball.y < paddle.y) { if (ball.x >= paddleLeft - ball.radius && ball.x <= paddleRight + ball.radius) { return true; } } // Advanced collision detection for high speeds // Check if ball will pass through paddle in this frame if (ball.vy > 0) { // Only check when ball is moving down var nextY = ball.y + ball.vy; var nextBottom = nextY + ball.radius; // If ball will move from above paddle to below paddle in one frame if (ball.y < paddleTop && nextBottom > paddle.y) { // Calculate x position at the moment of intersection with paddle top var t = (paddleTop - ball.y + ball.radius) / ball.vy; // Time of intersection var intersectX = ball.x + ball.vx * t; // Check if this x position is within paddle width if (intersectX >= paddleLeft - ball.radius && intersectX <= paddleRight + ball.radius) { return true; } } } return false; } // Ball-wall collision helper function ballHitsWall() { if (ball.x - ball.radius <= 0) return 'left'; if (ball.x + ball.radius >= GAME_WIDTH) return 'right'; if (ball.y - ball.radius <= 0) return 'top'; if (ball.y + ball.radius >= GAME_HEIGHT) return 'bottom'; return null; } // Game update game.update = function () { // Move ball ball.x += ball.vx; ball.y += ball.vy; // Update enemy paddle AI enemyPaddle.update(); // Wall collisions var wall = ballHitsWall(); if (wall === 'left' || wall === 'right') { ball.vx *= -1; // Play wall bounce sound LK.getSound('wall').play(); // Create hit effect at collision point createHitEffect(ball.x, ball.y, 0x33ccff); // Clamp inside if (wall === 'left') ball.x = ball.radius;else ball.x = GAME_WIDTH - ball.radius; } // Enemy paddle collision var enemyPaddleBottom = enemyPaddle.y + enemyPaddle.height / 2; var enemyPaddleTop = enemyPaddle.y - enemyPaddle.height / 2; var enemyPaddleLeft = enemyPaddle.x - enemyPaddle.width / 2; var enemyPaddleRight = enemyPaddle.x + enemyPaddle.width / 2; var ballTop = ball.y - ball.radius; if (ball.vy < 0 && ballTop <= enemyPaddleBottom && ball.y > enemyPaddle.y) { // Check if horizontally within enemy paddle if (ball.x >= enemyPaddleLeft - ball.radius && ball.x <= enemyPaddleRight + ball.radius) { // Bounce down ball.y = enemyPaddle.y + enemyPaddle.height / 2 + ball.radius; ball.vy *= -1; // Play hit sound LK.getSound('hit').play(); // Add some "english" based on where it hit the enemy paddle var hitPos = (ball.x - enemyPaddle.x) / (enemyPaddle.width / 2); // -1 (left) to 1 (right) ball.vx += hitPos * 6; // Clamp vx to avoid too much horizontal speed var maxVX = ball.speed * 0.85; if (ball.vx > maxVX) ball.vx = maxVX; if (ball.vx < -maxVX) ball.vx = -maxVX; // Increase speed ball.speed += 1.2; var angle = Math.atan2(ball.vy, ball.vx); ball.vx = Math.cos(angle) * ball.speed; ball.vy = Math.sin(angle) * ball.speed; // Flash enemy paddle for feedback LK.effects.flashObject(enemyPaddle, 0xff00ff, 180); // Create hit effect at collision point createHitEffect(ball.x, ball.y, 0xff00ff); } } if (wall === 'top') { ball.vy *= -1; ball.y = ball.radius; // Play wall bounce sound LK.getSound('wall').play(); // Create hit effect at top wall collision createHitEffect(ball.x, ball.y, 0x33ccff); } if (wall === 'bottom') { // Missed paddle: Game Over LK.getSound('miss').play(); LK.effects.flashScreen(0xff0000, 800); LK.showGameOver(); return; } // Paddle collision if (ball.vy > 0 && ballHitsPaddle()) { // Bounce up ball.y = paddle.y - paddle.height / 2 - ball.radius; ball.vy *= -1; // Play hit sound LK.getSound('hit').play(); // Add some "english" based on where it hit the paddle var hitPos = (ball.x - paddle.x) / (paddle.width / 2); // -1 (left) to 1 (right) ball.vx += hitPos * 6; // Clamp vx to avoid too much horizontal speed var maxVX = ball.speed * 0.85; if (ball.vx > maxVX) ball.vx = maxVX; if (ball.vx < -maxVX) ball.vx = -maxVX; // Score score += 1; scoreTxt.setText(score); // Increase speed every 10 points if (score % 10 === 0) { ball.speed += 3; // Bigger increase since it's less frequent var angle = Math.atan2(ball.vy, ball.vx); ball.vx = Math.cos(angle) * ball.speed; ball.vy = Math.sin(angle) * ball.speed; } // Flash paddle for feedback LK.effects.flashObject(paddle, 0x00ff00, 180); // Create hit effect at collision point createHitEffect(ball.x, ball.y, 0x00ff00); } }; // Center score text scoreTxt.setText(score); scoreTxt.anchor.set(0.5, 0); // Reset game state on restart game.on('reset', function () { score = 0; scoreTxt.setText(score); // Reset paddle paddle.x = GAME_WIDTH / 2; paddle.y = GAME_HEIGHT - 180; // Reset ball ball.x = GAME_WIDTH / 2; ball.y = GAME_HEIGHT / 2; ball.speed = 18; ball.setRandomDirection(); dragging = false; });
/****
* Plugins
****/
var tween = LK.import("@upit/tween.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
});
self.radius = ballAsset.width / 2;
// Ball velocity
self.vx = 0;
self.vy = 0;
self.speed = 18; // Initial speed
self.setRandomDirection = function () {
// Angle between 45 and 135 degrees, randomize left/right
var angle = (Math.random() * 0.5 + 0.25) * Math.PI; // 45-135 deg
if (Math.random() < 0.5) angle = Math.PI - angle; // flip left/right
self.vx = Math.cos(angle) * self.speed;
self.vy = Math.sin(angle) * self.speed;
};
self.setRandomDirection();
return self;
});
// Enemy Paddle class (AI)
var EnemyPaddle = Container.expand(function () {
var self = Container.call(this);
var paddleAsset = self.attachAsset('paddle', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = paddleAsset.width;
self.height = paddleAsset.height;
self.speed = 32; // AI paddle speed (pixels per frame)
self.update = function () {
// Track the ball's x position, but clamp movement speed
var dx = ball.x - self.x;
if (Math.abs(dx) > self.speed) {
self.x += self.speed * (dx > 0 ? 1 : -1);
} else {
self.x = ball.x;
}
// Clamp within game area
var halfW = self.width / 2;
if (self.x < halfW) self.x = halfW;
if (self.x > GAME_WIDTH - halfW) self.x = GAME_WIDTH - halfW;
};
return self;
});
// Hit effect class
var HitEffect = Container.expand(function () {
var self = Container.call(this);
var effectAsset = self.attachAsset('hitEffect', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8
});
// Play animation
self.play = function (x, y, color) {
self.x = x;
self.y = y;
self.alpha = 1;
self.scale.x = 0.2;
self.scale.y = 0.2;
// Set color if provided
if (color) {
effectAsset.tint = color;
} else {
effectAsset.tint = 0xffffff;
}
// Scale up and fade out
tween(self, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
self.parent.removeChild(self);
hitEffects.splice(hitEffects.indexOf(self), 1);
}
});
};
return self;
});
// No plugins needed for MVP
// Paddle class
var Paddle = Container.expand(function () {
var self = Container.call(this);
var paddleAsset = self.attachAsset('paddle', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = paddleAsset.width;
self.height = paddleAsset.height;
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x181818
});
/****
* Game Code
****/
// Import tween plugin
// Ball: circle
// Paddle: wide, short rectangle
// Game area
// Sound effects
// Music
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var hitEffects = [];
// Function to create hit effect
function createHitEffect(x, y, color) {
var effect = new HitEffect();
game.addChild(effect);
hitEffects.push(effect);
effect.play(x, y, color);
}
// Play background music (looping)
LK.playMusic('bgmusic');
// Paddle setup
var paddle = new Paddle();
game.addChild(paddle);
paddle.x = GAME_WIDTH / 2;
paddle.y = GAME_HEIGHT - 180; // 180px from bottom
// Enemy paddle setup (AI)
var enemyPaddle = new EnemyPaddle();
game.addChild(enemyPaddle);
enemyPaddle.x = GAME_WIDTH / 2;
enemyPaddle.y = 180; // 180px from top
// Ball setup
var ball = new Ball();
game.addChild(ball);
ball.x = GAME_WIDTH / 2;
ball.y = GAME_HEIGHT / 2;
// Score
var score = 0;
var scoreTxt = new Text2('0', {
size: 120,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
// Dragging
var dragging = false;
var dragOffsetX = 0;
// Touch/mouse controls
game.down = function (x, y, obj) {
// Only start drag if touch is on/near paddle
var dx = x - paddle.x;
var dy = y - paddle.y;
if (Math.abs(dx) < paddle.width / 2 + 60 && Math.abs(dy) < paddle.height / 2 + 80) {
dragging = true;
dragOffsetX = paddle.x - x;
}
};
game.move = function (x, y, obj) {
if (dragging) {
var newX = x + dragOffsetX;
// Clamp paddle within game area
var halfW = paddle.width / 2;
if (newX < halfW) newX = halfW;
if (newX > GAME_WIDTH - halfW) newX = GAME_WIDTH - halfW;
paddle.x = newX;
}
};
game.up = function (x, y, obj) {
dragging = false;
};
// Ball-paddle collision helper
function ballHitsPaddle() {
var paddleTop = paddle.y - paddle.height / 2;
var paddleLeft = paddle.x - paddle.width / 2;
var paddleRight = paddle.x + paddle.width / 2;
var ballBottom = ball.y + ball.radius;
// Simple collision check for low speeds
if (ballBottom >= paddleTop && ball.y < paddle.y) {
if (ball.x >= paddleLeft - ball.radius && ball.x <= paddleRight + ball.radius) {
return true;
}
}
// Advanced collision detection for high speeds
// Check if ball will pass through paddle in this frame
if (ball.vy > 0) {
// Only check when ball is moving down
var nextY = ball.y + ball.vy;
var nextBottom = nextY + ball.radius;
// If ball will move from above paddle to below paddle in one frame
if (ball.y < paddleTop && nextBottom > paddle.y) {
// Calculate x position at the moment of intersection with paddle top
var t = (paddleTop - ball.y + ball.radius) / ball.vy; // Time of intersection
var intersectX = ball.x + ball.vx * t;
// Check if this x position is within paddle width
if (intersectX >= paddleLeft - ball.radius && intersectX <= paddleRight + ball.radius) {
return true;
}
}
}
return false;
}
// Ball-wall collision helper
function ballHitsWall() {
if (ball.x - ball.radius <= 0) return 'left';
if (ball.x + ball.radius >= GAME_WIDTH) return 'right';
if (ball.y - ball.radius <= 0) return 'top';
if (ball.y + ball.radius >= GAME_HEIGHT) return 'bottom';
return null;
}
// Game update
game.update = function () {
// Move ball
ball.x += ball.vx;
ball.y += ball.vy;
// Update enemy paddle AI
enemyPaddle.update();
// Wall collisions
var wall = ballHitsWall();
if (wall === 'left' || wall === 'right') {
ball.vx *= -1;
// Play wall bounce sound
LK.getSound('wall').play();
// Create hit effect at collision point
createHitEffect(ball.x, ball.y, 0x33ccff);
// Clamp inside
if (wall === 'left') ball.x = ball.radius;else ball.x = GAME_WIDTH - ball.radius;
}
// Enemy paddle collision
var enemyPaddleBottom = enemyPaddle.y + enemyPaddle.height / 2;
var enemyPaddleTop = enemyPaddle.y - enemyPaddle.height / 2;
var enemyPaddleLeft = enemyPaddle.x - enemyPaddle.width / 2;
var enemyPaddleRight = enemyPaddle.x + enemyPaddle.width / 2;
var ballTop = ball.y - ball.radius;
if (ball.vy < 0 && ballTop <= enemyPaddleBottom && ball.y > enemyPaddle.y) {
// Check if horizontally within enemy paddle
if (ball.x >= enemyPaddleLeft - ball.radius && ball.x <= enemyPaddleRight + ball.radius) {
// Bounce down
ball.y = enemyPaddle.y + enemyPaddle.height / 2 + ball.radius;
ball.vy *= -1;
// Play hit sound
LK.getSound('hit').play();
// Add some "english" based on where it hit the enemy paddle
var hitPos = (ball.x - enemyPaddle.x) / (enemyPaddle.width / 2); // -1 (left) to 1 (right)
ball.vx += hitPos * 6;
// Clamp vx to avoid too much horizontal speed
var maxVX = ball.speed * 0.85;
if (ball.vx > maxVX) ball.vx = maxVX;
if (ball.vx < -maxVX) ball.vx = -maxVX;
// Increase speed
ball.speed += 1.2;
var angle = Math.atan2(ball.vy, ball.vx);
ball.vx = Math.cos(angle) * ball.speed;
ball.vy = Math.sin(angle) * ball.speed;
// Flash enemy paddle for feedback
LK.effects.flashObject(enemyPaddle, 0xff00ff, 180);
// Create hit effect at collision point
createHitEffect(ball.x, ball.y, 0xff00ff);
}
}
if (wall === 'top') {
ball.vy *= -1;
ball.y = ball.radius;
// Play wall bounce sound
LK.getSound('wall').play();
// Create hit effect at top wall collision
createHitEffect(ball.x, ball.y, 0x33ccff);
}
if (wall === 'bottom') {
// Missed paddle: Game Over
LK.getSound('miss').play();
LK.effects.flashScreen(0xff0000, 800);
LK.showGameOver();
return;
}
// Paddle collision
if (ball.vy > 0 && ballHitsPaddle()) {
// Bounce up
ball.y = paddle.y - paddle.height / 2 - ball.radius;
ball.vy *= -1;
// Play hit sound
LK.getSound('hit').play();
// Add some "english" based on where it hit the paddle
var hitPos = (ball.x - paddle.x) / (paddle.width / 2); // -1 (left) to 1 (right)
ball.vx += hitPos * 6;
// Clamp vx to avoid too much horizontal speed
var maxVX = ball.speed * 0.85;
if (ball.vx > maxVX) ball.vx = maxVX;
if (ball.vx < -maxVX) ball.vx = -maxVX;
// Score
score += 1;
scoreTxt.setText(score);
// Increase speed every 10 points
if (score % 10 === 0) {
ball.speed += 3; // Bigger increase since it's less frequent
var angle = Math.atan2(ball.vy, ball.vx);
ball.vx = Math.cos(angle) * ball.speed;
ball.vy = Math.sin(angle) * ball.speed;
}
// Flash paddle for feedback
LK.effects.flashObject(paddle, 0x00ff00, 180);
// Create hit effect at collision point
createHitEffect(ball.x, ball.y, 0x00ff00);
}
};
// Center score text
scoreTxt.setText(score);
scoreTxt.anchor.set(0.5, 0);
// Reset game state on restart
game.on('reset', function () {
score = 0;
scoreTxt.setText(score);
// Reset paddle
paddle.x = GAME_WIDTH / 2;
paddle.y = GAME_HEIGHT - 180;
// Reset ball
ball.x = GAME_WIDTH / 2;
ball.y = GAME_HEIGHT / 2;
ball.speed = 18;
ball.setRandomDirection();
dragging = false;
});