/**** * Classes ****/ // Initialize rotation speed //<Assets used in the game will automatically appear here> // Ball class var Ball = Container.expand(function () { var self = Container.call(this); var ballGraphics = self.attachAsset('ball', { anchorX: 0.5, anchorY: 0.5 }); self.rotationSpeed = 0; var half = self.width / 2; self.speedX = 3; self.speedY = 3; self.accelerationY = 0.5; // Gravity self.friction = 0.99; // Friction to slow down the ball over time self.lastCollisionTime = 0; // Initialize last collision time self.update = function (net, player1, player2) { if (!ballCanMove) { return; } // Apply gravity self.speedY += self.accelerationY; // Apply friction self.speedX *= self.friction; self.speedY *= self.friction; // Apply speed limit self.speedX = Math.sign(self.speedX) * Math.min(Math.abs(self.speedX), SPEED_LIMIT); self.speedY = Math.sign(self.speedY) * Math.min(Math.abs(self.speedY), SPEED_LIMIT); // Update ball position self.x += self.speedX; self.y += self.speedY; self.rotation += self.rotationSpeed * (self.speedX > 0 ? 1 : -1); // Update ball rotation based on direction self.rotationSpeed *= 0.98; // Gradually decrease rotation speed // Check for out of bounds if (self.y + half > 2000) { self.y = 2000 - half; self.speedY = -Math.abs(self.speedY) * 0.7; // Make the ball bounce up with more reduced speed self.speedX *= 0.9; // Apply slight energy loss on horizontal speed playSound('whistle', 1000); // Play whistle sound when ball touches the ground with a cooldown of 1 second if (self.x < 1024 && servingPlayer === 2) { updateScore(2); resetBall(); // Reset the ball after scoring } else if (self.x >= 1024 && servingPlayer === 1) { updateScore(1); resetBall(); // Reset the ball after scoring } else { servingPlayer = servingPlayer == 1 ? 2 : 1; resetBall(); // Reset the ball after scoring } player1Touches = 0; player2Touches = 0; } else if (self.y - half < 0) { self.y = half; self.speedY = Math.abs(self.speedY) * 0.8; // Make the ball bounce down with reduced speed } // Check for x screen limits if (self.x - half < 0) { self.x = half; self.speedX = Math.abs(self.speedX); // Make the ball bounce to the right } else if (self.x + half > 2048) { self.x = 2048 - half; self.speedX = -Math.abs(self.speedX); // Make the ball bounce to the left } // Collision detection logic moved to game update }; }); // Net class var Net = Container.expand(function () { var self = Container.call(this); var netGraphics = self.attachAsset('net', { anchorX: 0.5, anchorY: 1, alpha: isDebug ? 1 : 0 }); }); // Player class var Player = Container.expand(function (index) { var self = Container.call(this); var playerGraphics = self.attachAsset('player', { anchorX: 0.5, anchorY: 1, alpha: 0.98, scaleX: index === 2 ? -1 : 1, tint: index === 1 ? 0xADD8E6 : 0xFF6347 // Light blue for player 1, Tomato red for player 2 }); self.index = index; self.scale.set(1, 1); // Initialize player scale to normal self.jumping = false; self.falling = false; self.speedX = 0; // Initialize horizontal speed var collidSize = 245; self.collisionBody = LK.getAsset('collisionBody', { anchorX: 0.5, anchorY: 0.5, alpha: isDebug ? 0.6 : 0, width: collidSize, height: collidSize, x: 0, y: -200 }); self.addChild(self.collisionBody); self.update = function () { var prevX = self.x; // Store previous x position var prevY = self.y; // Store previous y position if (self.jumping && !self.falling) { self.y -= 20; // Increase jump speed self.scale.set(1, 1 + 0.25 * (PLAYER_INITIAL_Y - self.y) / 700); // Progressive scaling when jumping if (self.y <= 1300) { self.falling = true; } } if (self.falling && self.y < PLAYER_INITIAL_Y) { self.y += 20; // Increase fall speed for smoother jump if (self.y > PLAYER_INITIAL_Y - 256) { self.scale.set(1 + 0.15 * (PLAYER_INITIAL_Y - self.y) / 700, 1 - 0.15 * (PLAYER_INITIAL_Y - self.y) / 700); // Progressive scaling when falling } } if (self.y >= PLAYER_INITIAL_Y) { self.jumping = false; self.falling = false; self.y = PLAYER_INITIAL_Y; // Ensure player lands on the ground // Reset scale to normal when player stops moving self.scale.set(1, 1); } if (self.y < 0) { self.y = 0; // Prevent player1 from moving above the window } if (self.index === 2 && self.x > 1024 && self.x < 1024 + self.width / 2) { self.x = 1024 + self.width / 2; // Prevent player2 from moving past the net } self.speedX = self.x - prevX; // Calculate horizontal speed based on movement self.speedY = self.y - prevY; // Calculate vertical speed based on movement }; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x87CEEB // Sky blue background }); /**** * Game Code ****/ var PLAYER1_INITIAL_X = 256; var PLAYER_INITIAL_Y = 2000; var PLAYER2_INITIAL_X = 1792; var BALL1_INITIAL_X = 640; var BALL_INITIAL_Y = 1256; var BALL2_INITIAL_X = 1408; var gameStarted = false; var startButton = null; var joystick; var joystickBasePosition; var joystickDrag; var touchTime; // Global variable to store the touch time var prevMouseY; var lastPlayedTime; var ballCanMove; var SPEED_LIMIT; // Define a global speed limit var isDebug = false; var player2Debug; var player1Touches; var player2Touches; var resetTime; // Global variable to store the reset time var nextRoundPauseDelay; var score1; var score2; var finalScore = 15; // Define the final score for the game var servingPlayer = 1; // Initialize serving player to player 1 var ballShadow; var player1Shadow; var player2Shadow; var beachBall = null; function playSound(soundId, cooldown) { var currentTime = Date.now(); if (!lastPlayedTime[soundId] || currentTime - lastPlayedTime[soundId] > cooldown) { LK.getSound(soundId).play(); lastPlayedTime[soundId] = currentTime; } } function updateScore(index) { if (index === 1) { score1 += 1; scoreTxt1.setText(score1.toString().padStart(2, '0')); } else if (index === 2) { score2 += 1; scoreTxt2.setText(score2.toString().padStart(2, '0')); } // Check for game over condition if ((score1 >= finalScore || score2 >= finalScore) && Math.abs(score1 - score2) >= 2) { var resultText = new Text2(score1 > score2 ? 'VICTORY!' : 'DEFEAT!', { size: 300, fill: "#FFFFFF", fontWeight: "bold", dropShadow: true }); resultText.anchor.set(0.5, 2); //resultText.x = 0; //resultText.y = -512; LK.gui.center.addChild(resultText); LK.setTimeout(function () { LK.showGameOver(); }, 2000); } } function handleJoystickLimits(x, y) { if (joystickDrag) { var dx = x - joystickBasePosition.x; var dy = y - joystickBasePosition.y; var distance = Math.sqrt(dx * dx + dy * dy); var maxDistance = joystick.width / 3; // Max distance joystick can move from center if (distance > maxDistance) { var angle = Math.atan2(dy, dx); dx = Math.cos(angle) * maxDistance; dy = Math.sin(angle) * maxDistance; } joystick.x = joystickBasePosition.x + dx; joystick.y = joystickBasePosition.y + dy; } } function aiUpdate() { if (ball.x > 1024) { if (ball.x > player2.x + player2.width / 2) { player2.x += 5; // Move right } else if (ball.x < player2.x - player2.width / 2) { player2.x -= 5; // Move left } } if (!player2.jumping && player2.y >= PLAYER_INITIAL_Y && ball.y < player2.y && Math.abs(ball.x - player2.x) < player2.width) { player2.jumping = true; } } function resetBall() { ball.y = BALL_INITIAL_Y; // Set the ball's initial vertical position using BALL_INITIAL_Y ball.speedX = 3; ball.speedY = 3; // Reset speed ball.accelerationY = 0.5; // Reset gravity ball.friction = 0.99; // Reset friction ballCanMove = false; // Reset ball movement flag ball.rotation = 0; // Reset rotation angle resetTime = Date.now(); // Update reset time player1Touches = 0; player2Touches = 0; ball.alpha = 0; joystick.alpha = 0.; ball.x = servingPlayer === 1 ? BALL1_INITIAL_X : BALL2_INITIAL_X; // Move players progressively to their initial X positions var movePlayerToInitialX = function movePlayerToInitialX(player, initialX) { var moveInterval = LK.setInterval(function () { updateShadows(); if (Math.abs(player.x - initialX) < 5) { player.x = initialX; LK.clearInterval(moveInterval); } else { player.x += (initialX - player.x) * 0.1; } }, 16); // Move players every 16ms (approximately 60 FPS) }; movePlayerToInitialX(player1, PLAYER1_INITIAL_X); movePlayerToInitialX(player2, PLAYER2_INITIAL_X); } function customBoxCircleIntersect(box, circle) { var circleX = circle.x; var circleY = circle.y; if (circle.parent) { circleX += circle.parent.x; circleY += circle.parent.y; } var boxX = box.x; var boxY = box.y; var halfBoxWidth = box.width / 2; var halfCircleWidth = circle.width / 2; var netBuffer = 2; // Adjust netBuffer to control how far the ball bounces from the net var left = boxX - halfBoxWidth; var right = boxX + halfBoxWidth; var top = boxY - box.height; var bottom = boxY; // Check if the circle intersects with the box (considering entire ball) return circleX + halfCircleWidth > left && circleX - halfCircleWidth < right && circleY + halfCircleWidth > top + netBuffer && circleY - halfCircleWidth < bottom; } function customIntersect(circle1, circle2) { //console.log("customIntersect ", circle1, circle2.parent); var circle2X = circle2.x; var circle2Y = circle2.y; if (circle2.parent) { circle2X += circle2.parent.x; circle2Y += circle2.parent.y; } var dx = circle1.x - circle2X; var dy = circle1.y - circle2Y; var distance = Math.sqrt(dx * dx + dy * dy); var radiusSum = circle1.width / 2 + circle2.width / 2; //console.log("customIntersect ", distance.toFixed(0), radiusSum); return distance < radiusSum; } var background; gameInitialize(); game.update = function () { if (Date.now() - resetTime < nextRoundPauseDelay) { updateShadows(); return; } ball.alpha = 1; joystick.alpha = 1; if (!ballCanMove && (customIntersect(ball, player1.collisionBody) || customIntersect(ball, player2.collisionBody))) { ///|| player2.collisionBody && customIntersect(ball, player2.collisionBody) || customIntersect(ball, player1) || customIntersect(ball, player2))) { ballCanMove = true; //console.log("Player collision detected 1"); } // Check for collision with the net if (customBoxCircleIntersect(net, ball)) { // Check if enough time has passed since the last collision if (Date.now() - ball.lastCollisionTime > 0) { playSound('bump', 500); // Play bump sound on collision with a cooldown of 0.5 seconds // Reverse ball's horizontal direction and apply some energy loss // Handle y reaction when intersecting from the top var newSpeedX = ball.speedX * 1.15 + (Math.random() - 0.5) * 0.05; var newSpeedY = ball.speedY * 1.15 + (Math.random() - 0.5) * 0.05; // Check if the ball is intersecting the net from the top //console.log("TOP chceck", ball.y + ball.height / 2, net.y - net.height); if (ball.y + ball.height / 2 <= net.y - net.height + 100) { //console.log("TOP collision detected ", ball.y + ball.height / 2, net.y - net.height); // Top collision: Reverse the horizontal direction and limit the speed newSpeedX = Math.sign(ball.speedX) + ball.speedX * 1.25; // Top collision: Reverse the vertical direction and limit the speed newSpeedY = -Math.min(Math.abs(newSpeedY), SPEED_LIMIT); } else { // Side collision: Reverse the horizontal direction and limit the speed newSpeedX = -Math.sign(newSpeedX) * Math.min(Math.abs(newSpeedX), SPEED_LIMIT); newSpeedY = ball.speedY; } ball.speedX = newSpeedX; ball.speedY = newSpeedY; // Update the last collision time ball.lastCollisionTime = Date.now(); } } // Check for collisions with players var touchIndex = 0; if (ball.x + ball.width / 2 < 1024) { player2Touches = 0; } else if (ball.x - ball.width / 2 > 1024) { player1Touches = 0; } if (ball.x < 1024 && customIntersect(ball, player1.collisionBody)) { touchIndex = 1; } else if (ball.x > 1024 && customIntersect(ball, player2.collisionBody)) { touchIndex = 2; } if (touchIndex) { playSound('bump', 500); // Play bump sound on collision with a cooldown of 0.5 seconds // Increment touch counters if (touchIndex === 1) { if (Date.now() - touchTime >= 300) { player1Touches++; } player2Touches = 0; // Reset opponent's touch counter } else if (touchIndex === 2) { if (Date.now() - touchTime >= 300) { player2Touches++; } player1Touches = 0; // Reset opponent's touch counter } touchTime = Date.now(); // Update touchTime when a player touches the ball // Check for touch limit if (player1Touches >= 4) { if (servingPlayer == 1) { servingPlayer = 2; } else { updateScore(2); } } else if (player2Touches >= 4) { if (servingPlayer == 2) { servingPlayer = 1; } else { updateScore(1); } } if (player1Touches >= 4 || player2Touches >= 4) { playSound('whistle', 1000); // Play whistle sound resetBall(); } //console.log("Player collision detected 2"); var player = touchIndex === 1 ? player1 : player2; var collisionAngle = Math.atan2(ball.y - player.y, ball.x - player.x); var speed = Math.sqrt(ball.speedX * ball.speedX + ball.speedY * ball.speedY); var playerSpeed = Math.sqrt(player.speedX * player.speedX + player.speedY * player.speedY); var newSpeedX = Math.sign(speed * Math.cos(collisionAngle) + (playerSpeed + 0.1 * Math.sign(playerSpeed)) * Math.cos(collisionAngle)) * Math.min(Math.abs(speed * Math.cos(collisionAngle) + (playerSpeed + 0.1 * Math.sign(playerSpeed)) * Math.cos(collisionAngle)), SPEED_LIMIT); var newSpeedY = Math.sign(speed * Math.sin(collisionAngle) + (playerSpeed + 0.1 * Math.sign(playerSpeed)) * Math.sin(collisionAngle)) * Math.min(Math.abs(speed * Math.sin(collisionAngle) + (playerSpeed + 0.1 * Math.sign(playerSpeed)) * Math.sin(collisionAngle)), SPEED_LIMIT); // Add a bit of randomness to the ball's speed to avoid infinite stuck ball.speedX = newSpeedX * 1.20 + (Math.random() - 0.5) * 0.05; ball.speedY = newSpeedY * 1.20 + (Math.random() - 0.5) * 0.05; } ball.rotationSpeed = Math.sqrt(ball.speedX * ball.speedX + ball.speedY * ball.speedY) * 0.005; // Update rotation speed based on collision aiUpdate(); if (joystickDrag) { var dx = joystick.x - joystickBasePosition.x; var dy = joystick.y - joystickBasePosition.y; var movementRatio = 0.25; // Adjust this ratio to make the movement smoother player1.x = Math.max(player1.width / 2, Math.min(player1.x + dx * movementRatio, 1024 - player1.width / 2 - net.width / 2)); player1.speedX = dx * movementRatio; if (!player1.jumping && dy < -80 && Date.now() - resetTime >= nextRoundPauseDelay) { player1.jumping = true; } //console.log("MOVE joystickDrag dx: ".concat(dx.toFixed(0), ", player1.speedX: ").concat(player1.speedX.toFixed(0), ", joystickDrag dy: ").concat(dy.toFixed(0))); } updateShadows(); }; game.down = function (x, y, obj) { if (!gameStarted) { gameStarted = true; startButton.destroy(); joystick.visible = true; } else { joystickDrag = true; } }; game.move = function (x, y, obj) { if (Date.now() - resetTime < nextRoundPauseDelay) { return; } handleJoystickLimits(x, y); }; game.up = function (x, y, obj) { joystickDrag = false; //console.log("UP joystickDrag state:", joystickDrag); joystick.x = joystickBasePosition.x; joystick.y = joystickBasePosition.y; }; function gameInitialize() { background = LK.getAsset('background', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); game.addChild(background); initShadows(); var parasol = LK.getAsset('parasol', { anchorX: 0.5, anchorY: 1, x: 1848, y: 1450 }); game.addChild(parasol); var chair = LK.getAsset('chair', { anchorX: 0.5, anchorY: 1, x: 1948, y: 1500 }); game.addChild(chair); var towel = LK.getAsset('towel', { anchorX: 0.5, anchorY: 1, x: 10, y: 1540 }); game.addChild(towel); beachBall = LK.getAsset('beachBall', { anchorX: 0.5, anchorY: 1, x: 120, y: 1465 }); game.addChild(beachBall); player1 = new Player(1); player2 = new Player(2); game.addChild(player1); game.addChild(player2); player1.x = PLAYER1_INITIAL_X; player1.y = PLAYER_INITIAL_Y; player2.y = PLAYER_INITIAL_Y; player2.x = PLAYER2_INITIAL_X; ball = new Ball(); ball.x = servingPlayer === 1 ? BALL1_INITIAL_X : BALL2_INITIAL_X; ball.y = BALL_INITIAL_Y; // Set the ball's initial vertical position using BALL_INITIAL_Y ball.rotationSpeed = 0; game.addChild(ball); scoreTxt1 = new Text2('00', { size: 200, fill: "#0000d8", fontWeight: "bold", dropShadow: true }); scoreTxt1.anchor.set(-1, 0); LK.gui.topLeft.addChild(scoreTxt1); scoreTxt2 = new Text2('00', { size: 200, fill: "#FF6347", fontWeight: "bold", dropShadow: true }); scoreTxt2.anchor.set(2, 0); LK.gui.topRight.addChild(scoreTxt2); joystick = game.addChild(LK.getAsset('joystick', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 - 330 })); joystick.visible = false; joystickBasePosition = { x: joystick.x, y: joystick.y }; joystickBasePosition = { x: joystick.x, y: joystick.y }; joystickDrag = false; net = new Net(); net.x = 2048 / 2; net.y = 2000; game.addChild(net); touchTime = 0; // Global variable to store the touch time prevMouseY = null; lastPlayedTime = {}; ballCanMove = false; SPEED_LIMIT = 50; // Define a global speed limit isDebug = false; player2Debug = false; player1Touches = 0; player2Touches = 0; resetTime = 0; // Global variable to store the reset time nextRoundPauseDelay = 1600; score1 = 0; score2 = 0; scoreTxt1.setText(score1.toString().padStart(2, '0')); scoreTxt2.setText(score2.toString().padStart(2, '0')); startButton = LK.getAsset('startButton', { anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 2732 / 2 }); game.addChild(startButton); } function initShadows() { ballShadow = LK.getAsset('shadow', { anchorX: 0.5, anchorY: 0.5, alpha: 0.33, scaleX: 2.2, scaleY: 0.6 }); player1Shadow = LK.getAsset('shadow', { anchorX: 0.5, anchorY: 0.5, alpha: 0.33, scaleX: 2.7, scaleY: 0.7 }); player2Shadow = LK.getAsset('shadow', { anchorX: 0.5, anchorY: 0.5, alpha: 0.33, scaleX: 2.7, scaleY: 0.7 }); var parasolShadow = LK.getAsset('shadow', { anchorX: 0.5, anchorY: 0.5, alpha: 0.33, scaleX: 3.5, scaleY: 0.65, x: 1900 + 10, y: 1445 + 10 }); var beachBallShadow = LK.getAsset('shadow', { anchorX: 0.5, anchorY: 0.5, alpha: 0.33, scaleX: 0.75, scaleY: 0.35, x: 120 + 20, y: 1465 }); ballShadow.x = BALL1_INITIAL_X; ballShadow.y = PLAYER_INITIAL_Y; player1Shadow.x = PLAYER1_INITIAL_X; player1Shadow.y = PLAYER_INITIAL_Y; player2Shadow.x = PLAYER2_INITIAL_X; player2Shadow.y = PLAYER_INITIAL_Y; game.addChild(parasolShadow); game.addChild(beachBallShadow); game.addChild(ballShadow); game.addChild(player1Shadow); game.addChild(player2Shadow); } function updateShadows() { // Update ball shadow position and size ballShadow.visible = ball.alpha != 0; if (ballShadow.visible) { ballShadow.x = ball.x; ballShadow.scale.x = 2.2 * (1 + (ball.y - PLAYER_INITIAL_Y) / 3000); ballShadow.scale.y = 0.6 * (1 + (ball.y - PLAYER_INITIAL_Y) / 3000); } // Update player1 shadow position and size player1Shadow.x = player1.x; player1Shadow.scale.x = 2.7 * (1 + (player1.y - PLAYER_INITIAL_Y) / 1500); player1Shadow.scale.y = 0.7 * (1 + (player1.y - PLAYER_INITIAL_Y) / 1500); // Update player2 shadow position and size player2Shadow.x = player2.x; player2Shadow.scale.x = 2.7 * (1 + (player2.y - PLAYER_INITIAL_Y) / 1500); player2Shadow.scale.y = 0.7 * (1 + (player2.y - PLAYER_INITIAL_Y) / 1500); }
/****
* Classes
****/
// Initialize rotation speed
//<Assets used in the game will automatically appear here>
// Ball class
var Ball = Container.expand(function () {
var self = Container.call(this);
var ballGraphics = self.attachAsset('ball', {
anchorX: 0.5,
anchorY: 0.5
});
self.rotationSpeed = 0;
var half = self.width / 2;
self.speedX = 3;
self.speedY = 3;
self.accelerationY = 0.5; // Gravity
self.friction = 0.99; // Friction to slow down the ball over time
self.lastCollisionTime = 0; // Initialize last collision time
self.update = function (net, player1, player2) {
if (!ballCanMove) {
return;
}
// Apply gravity
self.speedY += self.accelerationY;
// Apply friction
self.speedX *= self.friction;
self.speedY *= self.friction;
// Apply speed limit
self.speedX = Math.sign(self.speedX) * Math.min(Math.abs(self.speedX), SPEED_LIMIT);
self.speedY = Math.sign(self.speedY) * Math.min(Math.abs(self.speedY), SPEED_LIMIT);
// Update ball position
self.x += self.speedX;
self.y += self.speedY;
self.rotation += self.rotationSpeed * (self.speedX > 0 ? 1 : -1); // Update ball rotation based on direction
self.rotationSpeed *= 0.98; // Gradually decrease rotation speed
// Check for out of bounds
if (self.y + half > 2000) {
self.y = 2000 - half;
self.speedY = -Math.abs(self.speedY) * 0.7; // Make the ball bounce up with more reduced speed
self.speedX *= 0.9; // Apply slight energy loss on horizontal speed
playSound('whistle', 1000); // Play whistle sound when ball touches the ground with a cooldown of 1 second
if (self.x < 1024 && servingPlayer === 2) {
updateScore(2);
resetBall(); // Reset the ball after scoring
} else if (self.x >= 1024 && servingPlayer === 1) {
updateScore(1);
resetBall(); // Reset the ball after scoring
} else {
servingPlayer = servingPlayer == 1 ? 2 : 1;
resetBall(); // Reset the ball after scoring
}
player1Touches = 0;
player2Touches = 0;
} else if (self.y - half < 0) {
self.y = half;
self.speedY = Math.abs(self.speedY) * 0.8; // Make the ball bounce down with reduced speed
}
// Check for x screen limits
if (self.x - half < 0) {
self.x = half;
self.speedX = Math.abs(self.speedX); // Make the ball bounce to the right
} else if (self.x + half > 2048) {
self.x = 2048 - half;
self.speedX = -Math.abs(self.speedX); // Make the ball bounce to the left
}
// Collision detection logic moved to game update
};
});
// Net class
var Net = Container.expand(function () {
var self = Container.call(this);
var netGraphics = self.attachAsset('net', {
anchorX: 0.5,
anchorY: 1,
alpha: isDebug ? 1 : 0
});
});
// Player class
var Player = Container.expand(function (index) {
var self = Container.call(this);
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 1,
alpha: 0.98,
scaleX: index === 2 ? -1 : 1,
tint: index === 1 ? 0xADD8E6 : 0xFF6347 // Light blue for player 1, Tomato red for player 2
});
self.index = index;
self.scale.set(1, 1); // Initialize player scale to normal
self.jumping = false;
self.falling = false;
self.speedX = 0; // Initialize horizontal speed
var collidSize = 245;
self.collisionBody = LK.getAsset('collisionBody', {
anchorX: 0.5,
anchorY: 0.5,
alpha: isDebug ? 0.6 : 0,
width: collidSize,
height: collidSize,
x: 0,
y: -200
});
self.addChild(self.collisionBody);
self.update = function () {
var prevX = self.x; // Store previous x position
var prevY = self.y; // Store previous y position
if (self.jumping && !self.falling) {
self.y -= 20; // Increase jump speed
self.scale.set(1, 1 + 0.25 * (PLAYER_INITIAL_Y - self.y) / 700); // Progressive scaling when jumping
if (self.y <= 1300) {
self.falling = true;
}
}
if (self.falling && self.y < PLAYER_INITIAL_Y) {
self.y += 20; // Increase fall speed for smoother jump
if (self.y > PLAYER_INITIAL_Y - 256) {
self.scale.set(1 + 0.15 * (PLAYER_INITIAL_Y - self.y) / 700, 1 - 0.15 * (PLAYER_INITIAL_Y - self.y) / 700); // Progressive scaling when falling
}
}
if (self.y >= PLAYER_INITIAL_Y) {
self.jumping = false;
self.falling = false;
self.y = PLAYER_INITIAL_Y; // Ensure player lands on the ground
// Reset scale to normal when player stops moving
self.scale.set(1, 1);
}
if (self.y < 0) {
self.y = 0; // Prevent player1 from moving above the window
}
if (self.index === 2 && self.x > 1024 && self.x < 1024 + self.width / 2) {
self.x = 1024 + self.width / 2; // Prevent player2 from moving past the net
}
self.speedX = self.x - prevX; // Calculate horizontal speed based on movement
self.speedY = self.y - prevY; // Calculate vertical speed based on movement
};
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87CEEB // Sky blue background
});
/****
* Game Code
****/
var PLAYER1_INITIAL_X = 256;
var PLAYER_INITIAL_Y = 2000;
var PLAYER2_INITIAL_X = 1792;
var BALL1_INITIAL_X = 640;
var BALL_INITIAL_Y = 1256;
var BALL2_INITIAL_X = 1408;
var gameStarted = false;
var startButton = null;
var joystick;
var joystickBasePosition;
var joystickDrag;
var touchTime; // Global variable to store the touch time
var prevMouseY;
var lastPlayedTime;
var ballCanMove;
var SPEED_LIMIT; // Define a global speed limit
var isDebug = false;
var player2Debug;
var player1Touches;
var player2Touches;
var resetTime; // Global variable to store the reset time
var nextRoundPauseDelay;
var score1;
var score2;
var finalScore = 15; // Define the final score for the game
var servingPlayer = 1; // Initialize serving player to player 1
var ballShadow;
var player1Shadow;
var player2Shadow;
var beachBall = null;
function playSound(soundId, cooldown) {
var currentTime = Date.now();
if (!lastPlayedTime[soundId] || currentTime - lastPlayedTime[soundId] > cooldown) {
LK.getSound(soundId).play();
lastPlayedTime[soundId] = currentTime;
}
}
function updateScore(index) {
if (index === 1) {
score1 += 1;
scoreTxt1.setText(score1.toString().padStart(2, '0'));
} else if (index === 2) {
score2 += 1;
scoreTxt2.setText(score2.toString().padStart(2, '0'));
}
// Check for game over condition
if ((score1 >= finalScore || score2 >= finalScore) && Math.abs(score1 - score2) >= 2) {
var resultText = new Text2(score1 > score2 ? 'VICTORY!' : 'DEFEAT!', {
size: 300,
fill: "#FFFFFF",
fontWeight: "bold",
dropShadow: true
});
resultText.anchor.set(0.5, 2);
//resultText.x = 0;
//resultText.y = -512;
LK.gui.center.addChild(resultText);
LK.setTimeout(function () {
LK.showGameOver();
}, 2000);
}
}
function handleJoystickLimits(x, y) {
if (joystickDrag) {
var dx = x - joystickBasePosition.x;
var dy = y - joystickBasePosition.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var maxDistance = joystick.width / 3; // Max distance joystick can move from center
if (distance > maxDistance) {
var angle = Math.atan2(dy, dx);
dx = Math.cos(angle) * maxDistance;
dy = Math.sin(angle) * maxDistance;
}
joystick.x = joystickBasePosition.x + dx;
joystick.y = joystickBasePosition.y + dy;
}
}
function aiUpdate() {
if (ball.x > 1024) {
if (ball.x > player2.x + player2.width / 2) {
player2.x += 5; // Move right
} else if (ball.x < player2.x - player2.width / 2) {
player2.x -= 5; // Move left
}
}
if (!player2.jumping && player2.y >= PLAYER_INITIAL_Y && ball.y < player2.y && Math.abs(ball.x - player2.x) < player2.width) {
player2.jumping = true;
}
}
function resetBall() {
ball.y = BALL_INITIAL_Y; // Set the ball's initial vertical position using BALL_INITIAL_Y
ball.speedX = 3;
ball.speedY = 3; // Reset speed
ball.accelerationY = 0.5; // Reset gravity
ball.friction = 0.99; // Reset friction
ballCanMove = false; // Reset ball movement flag
ball.rotation = 0; // Reset rotation angle
resetTime = Date.now(); // Update reset time
player1Touches = 0;
player2Touches = 0;
ball.alpha = 0;
joystick.alpha = 0.;
ball.x = servingPlayer === 1 ? BALL1_INITIAL_X : BALL2_INITIAL_X;
// Move players progressively to their initial X positions
var movePlayerToInitialX = function movePlayerToInitialX(player, initialX) {
var moveInterval = LK.setInterval(function () {
updateShadows();
if (Math.abs(player.x - initialX) < 5) {
player.x = initialX;
LK.clearInterval(moveInterval);
} else {
player.x += (initialX - player.x) * 0.1;
}
}, 16); // Move players every 16ms (approximately 60 FPS)
};
movePlayerToInitialX(player1, PLAYER1_INITIAL_X);
movePlayerToInitialX(player2, PLAYER2_INITIAL_X);
}
function customBoxCircleIntersect(box, circle) {
var circleX = circle.x;
var circleY = circle.y;
if (circle.parent) {
circleX += circle.parent.x;
circleY += circle.parent.y;
}
var boxX = box.x;
var boxY = box.y;
var halfBoxWidth = box.width / 2;
var halfCircleWidth = circle.width / 2;
var netBuffer = 2; // Adjust netBuffer to control how far the ball bounces from the net
var left = boxX - halfBoxWidth;
var right = boxX + halfBoxWidth;
var top = boxY - box.height;
var bottom = boxY;
// Check if the circle intersects with the box (considering entire ball)
return circleX + halfCircleWidth > left && circleX - halfCircleWidth < right && circleY + halfCircleWidth > top + netBuffer && circleY - halfCircleWidth < bottom;
}
function customIntersect(circle1, circle2) {
//console.log("customIntersect ", circle1, circle2.parent);
var circle2X = circle2.x;
var circle2Y = circle2.y;
if (circle2.parent) {
circle2X += circle2.parent.x;
circle2Y += circle2.parent.y;
}
var dx = circle1.x - circle2X;
var dy = circle1.y - circle2Y;
var distance = Math.sqrt(dx * dx + dy * dy);
var radiusSum = circle1.width / 2 + circle2.width / 2;
//console.log("customIntersect ", distance.toFixed(0), radiusSum);
return distance < radiusSum;
}
var background;
gameInitialize();
game.update = function () {
if (Date.now() - resetTime < nextRoundPauseDelay) {
updateShadows();
return;
}
ball.alpha = 1;
joystick.alpha = 1;
if (!ballCanMove && (customIntersect(ball, player1.collisionBody) || customIntersect(ball, player2.collisionBody))) {
///|| player2.collisionBody && customIntersect(ball, player2.collisionBody) || customIntersect(ball, player1) || customIntersect(ball, player2))) {
ballCanMove = true;
//console.log("Player collision detected 1");
}
// Check for collision with the net
if (customBoxCircleIntersect(net, ball)) {
// Check if enough time has passed since the last collision
if (Date.now() - ball.lastCollisionTime > 0) {
playSound('bump', 500); // Play bump sound on collision with a cooldown of 0.5 seconds
// Reverse ball's horizontal direction and apply some energy loss
// Handle y reaction when intersecting from the top
var newSpeedX = ball.speedX * 1.15 + (Math.random() - 0.5) * 0.05;
var newSpeedY = ball.speedY * 1.15 + (Math.random() - 0.5) * 0.05;
// Check if the ball is intersecting the net from the top
//console.log("TOP chceck", ball.y + ball.height / 2, net.y - net.height);
if (ball.y + ball.height / 2 <= net.y - net.height + 100) {
//console.log("TOP collision detected ", ball.y + ball.height / 2, net.y - net.height);
// Top collision: Reverse the horizontal direction and limit the speed
newSpeedX = Math.sign(ball.speedX) + ball.speedX * 1.25;
// Top collision: Reverse the vertical direction and limit the speed
newSpeedY = -Math.min(Math.abs(newSpeedY), SPEED_LIMIT);
} else {
// Side collision: Reverse the horizontal direction and limit the speed
newSpeedX = -Math.sign(newSpeedX) * Math.min(Math.abs(newSpeedX), SPEED_LIMIT);
newSpeedY = ball.speedY;
}
ball.speedX = newSpeedX;
ball.speedY = newSpeedY;
// Update the last collision time
ball.lastCollisionTime = Date.now();
}
}
// Check for collisions with players
var touchIndex = 0;
if (ball.x + ball.width / 2 < 1024) {
player2Touches = 0;
} else if (ball.x - ball.width / 2 > 1024) {
player1Touches = 0;
}
if (ball.x < 1024 && customIntersect(ball, player1.collisionBody)) {
touchIndex = 1;
} else if (ball.x > 1024 && customIntersect(ball, player2.collisionBody)) {
touchIndex = 2;
}
if (touchIndex) {
playSound('bump', 500); // Play bump sound on collision with a cooldown of 0.5 seconds
// Increment touch counters
if (touchIndex === 1) {
if (Date.now() - touchTime >= 300) {
player1Touches++;
}
player2Touches = 0; // Reset opponent's touch counter
} else if (touchIndex === 2) {
if (Date.now() - touchTime >= 300) {
player2Touches++;
}
player1Touches = 0; // Reset opponent's touch counter
}
touchTime = Date.now(); // Update touchTime when a player touches the ball
// Check for touch limit
if (player1Touches >= 4) {
if (servingPlayer == 1) {
servingPlayer = 2;
} else {
updateScore(2);
}
} else if (player2Touches >= 4) {
if (servingPlayer == 2) {
servingPlayer = 1;
} else {
updateScore(1);
}
}
if (player1Touches >= 4 || player2Touches >= 4) {
playSound('whistle', 1000); // Play whistle sound
resetBall();
}
//console.log("Player collision detected 2");
var player = touchIndex === 1 ? player1 : player2;
var collisionAngle = Math.atan2(ball.y - player.y, ball.x - player.x);
var speed = Math.sqrt(ball.speedX * ball.speedX + ball.speedY * ball.speedY);
var playerSpeed = Math.sqrt(player.speedX * player.speedX + player.speedY * player.speedY);
var newSpeedX = Math.sign(speed * Math.cos(collisionAngle) + (playerSpeed + 0.1 * Math.sign(playerSpeed)) * Math.cos(collisionAngle)) * Math.min(Math.abs(speed * Math.cos(collisionAngle) + (playerSpeed + 0.1 * Math.sign(playerSpeed)) * Math.cos(collisionAngle)), SPEED_LIMIT);
var newSpeedY = Math.sign(speed * Math.sin(collisionAngle) + (playerSpeed + 0.1 * Math.sign(playerSpeed)) * Math.sin(collisionAngle)) * Math.min(Math.abs(speed * Math.sin(collisionAngle) + (playerSpeed + 0.1 * Math.sign(playerSpeed)) * Math.sin(collisionAngle)), SPEED_LIMIT);
// Add a bit of randomness to the ball's speed to avoid infinite stuck
ball.speedX = newSpeedX * 1.20 + (Math.random() - 0.5) * 0.05;
ball.speedY = newSpeedY * 1.20 + (Math.random() - 0.5) * 0.05;
}
ball.rotationSpeed = Math.sqrt(ball.speedX * ball.speedX + ball.speedY * ball.speedY) * 0.005; // Update rotation speed based on collision
aiUpdate();
if (joystickDrag) {
var dx = joystick.x - joystickBasePosition.x;
var dy = joystick.y - joystickBasePosition.y;
var movementRatio = 0.25; // Adjust this ratio to make the movement smoother
player1.x = Math.max(player1.width / 2, Math.min(player1.x + dx * movementRatio, 1024 - player1.width / 2 - net.width / 2));
player1.speedX = dx * movementRatio;
if (!player1.jumping && dy < -80 && Date.now() - resetTime >= nextRoundPauseDelay) {
player1.jumping = true;
}
//console.log("MOVE joystickDrag dx: ".concat(dx.toFixed(0), ", player1.speedX: ").concat(player1.speedX.toFixed(0), ", joystickDrag dy: ").concat(dy.toFixed(0)));
}
updateShadows();
};
game.down = function (x, y, obj) {
if (!gameStarted) {
gameStarted = true;
startButton.destroy();
joystick.visible = true;
} else {
joystickDrag = true;
}
};
game.move = function (x, y, obj) {
if (Date.now() - resetTime < nextRoundPauseDelay) {
return;
}
handleJoystickLimits(x, y);
};
game.up = function (x, y, obj) {
joystickDrag = false;
//console.log("UP joystickDrag state:", joystickDrag);
joystick.x = joystickBasePosition.x;
joystick.y = joystickBasePosition.y;
};
function gameInitialize() {
background = LK.getAsset('background', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
game.addChild(background);
initShadows();
var parasol = LK.getAsset('parasol', {
anchorX: 0.5,
anchorY: 1,
x: 1848,
y: 1450
});
game.addChild(parasol);
var chair = LK.getAsset('chair', {
anchorX: 0.5,
anchorY: 1,
x: 1948,
y: 1500
});
game.addChild(chair);
var towel = LK.getAsset('towel', {
anchorX: 0.5,
anchorY: 1,
x: 10,
y: 1540
});
game.addChild(towel);
beachBall = LK.getAsset('beachBall', {
anchorX: 0.5,
anchorY: 1,
x: 120,
y: 1465
});
game.addChild(beachBall);
player1 = new Player(1);
player2 = new Player(2);
game.addChild(player1);
game.addChild(player2);
player1.x = PLAYER1_INITIAL_X;
player1.y = PLAYER_INITIAL_Y;
player2.y = PLAYER_INITIAL_Y;
player2.x = PLAYER2_INITIAL_X;
ball = new Ball();
ball.x = servingPlayer === 1 ? BALL1_INITIAL_X : BALL2_INITIAL_X;
ball.y = BALL_INITIAL_Y; // Set the ball's initial vertical position using BALL_INITIAL_Y
ball.rotationSpeed = 0;
game.addChild(ball);
scoreTxt1 = new Text2('00', {
size: 200,
fill: "#0000d8",
fontWeight: "bold",
dropShadow: true
});
scoreTxt1.anchor.set(-1, 0);
LK.gui.topLeft.addChild(scoreTxt1);
scoreTxt2 = new Text2('00', {
size: 200,
fill: "#FF6347",
fontWeight: "bold",
dropShadow: true
});
scoreTxt2.anchor.set(2, 0);
LK.gui.topRight.addChild(scoreTxt2);
joystick = game.addChild(LK.getAsset('joystick', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 - 330
}));
joystick.visible = false;
joystickBasePosition = {
x: joystick.x,
y: joystick.y
};
joystickBasePosition = {
x: joystick.x,
y: joystick.y
};
joystickDrag = false;
net = new Net();
net.x = 2048 / 2;
net.y = 2000;
game.addChild(net);
touchTime = 0; // Global variable to store the touch time
prevMouseY = null;
lastPlayedTime = {};
ballCanMove = false;
SPEED_LIMIT = 50; // Define a global speed limit
isDebug = false;
player2Debug = false;
player1Touches = 0;
player2Touches = 0;
resetTime = 0; // Global variable to store the reset time
nextRoundPauseDelay = 1600;
score1 = 0;
score2 = 0;
scoreTxt1.setText(score1.toString().padStart(2, '0'));
scoreTxt2.setText(score2.toString().padStart(2, '0'));
startButton = LK.getAsset('startButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2
});
game.addChild(startButton);
}
function initShadows() {
ballShadow = LK.getAsset('shadow', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.33,
scaleX: 2.2,
scaleY: 0.6
});
player1Shadow = LK.getAsset('shadow', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.33,
scaleX: 2.7,
scaleY: 0.7
});
player2Shadow = LK.getAsset('shadow', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.33,
scaleX: 2.7,
scaleY: 0.7
});
var parasolShadow = LK.getAsset('shadow', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.33,
scaleX: 3.5,
scaleY: 0.65,
x: 1900 + 10,
y: 1445 + 10
});
var beachBallShadow = LK.getAsset('shadow', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.33,
scaleX: 0.75,
scaleY: 0.35,
x: 120 + 20,
y: 1465
});
ballShadow.x = BALL1_INITIAL_X;
ballShadow.y = PLAYER_INITIAL_Y;
player1Shadow.x = PLAYER1_INITIAL_X;
player1Shadow.y = PLAYER_INITIAL_Y;
player2Shadow.x = PLAYER2_INITIAL_X;
player2Shadow.y = PLAYER_INITIAL_Y;
game.addChild(parasolShadow);
game.addChild(beachBallShadow);
game.addChild(ballShadow);
game.addChild(player1Shadow);
game.addChild(player2Shadow);
}
function updateShadows() {
// Update ball shadow position and size
ballShadow.visible = ball.alpha != 0;
if (ballShadow.visible) {
ballShadow.x = ball.x;
ballShadow.scale.x = 2.2 * (1 + (ball.y - PLAYER_INITIAL_Y) / 3000);
ballShadow.scale.y = 0.6 * (1 + (ball.y - PLAYER_INITIAL_Y) / 3000);
}
// Update player1 shadow position and size
player1Shadow.x = player1.x;
player1Shadow.scale.x = 2.7 * (1 + (player1.y - PLAYER_INITIAL_Y) / 1500);
player1Shadow.scale.y = 0.7 * (1 + (player1.y - PLAYER_INITIAL_Y) / 1500);
// Update player2 shadow position and size
player2Shadow.x = player2.x;
player2Shadow.scale.x = 2.7 * (1 + (player2.y - PLAYER_INITIAL_Y) / 1500);
player2Shadow.scale.y = 0.7 * (1 + (player2.y - PLAYER_INITIAL_Y) / 1500);
}
white volley ball.
top view of a concave blue (0xADD8E6) plastic button. 4 small black directionnal chevrons engraved : right, left, top , bottom.. Photorealistic
Beach ball. photo
full view of a Beach white towel with colored infinte logo. placed on the sand. photo
Start button in the shape of a white beach volleyball with « START » written on it in black. Photo