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