/****
* 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