Code edit (6 edits merged)
Please save this source code
User prompt
now add a new function : updateShadows (to update position and size of ballShadow., player1Shadow and player2Shadow
User prompt
inside initShadows, use game.addChildAt to place shadows just before players and ball
Code edit (9 edits merged)
Please save this source code
User prompt
in initShadows, use PLAYER1_INITIAL_X, PLAYER2_INITIAL_X... to place ball and players shadows (shdows are always on the ground)
Code edit (1 edits merged)
Please save this source code
Code edit (18 edits merged)
Please save this source code
User prompt
place beachBallShadow z-index, just before beachBall
Code edit (1 edits merged)
Please save this source code
Code edit (1 edits merged)
Please save this source code
Code edit (11 edits merged)
Please save this source code
User prompt
call initShadows at the end of gameInitialize
User prompt
inside initShadows, add all shadows to the game
User prompt
in initShadows, add 2 locals parasolShadow and chairShadow
User prompt
create 3 globals : ballShadow, player1Shadow, player2Shadow and a new function initShadows
Code edit (4 edits merged)
Please save this source code
User prompt
add the parasol to the game (respect coding style)
Code edit (1 edits merged)
Please save this source code
Code edit (5 edits merged)
Please save this source code
User prompt
update the animation : they should get longer vertically when jumping and get smaller when landing
User prompt
think a lot on the way to do it properly, then animate the blobs (players) when they move (like jellys)
Code edit (1 edits merged)
Please save this source code
User prompt
ball shadow should not rotate (always on the ground)
Code edit (7 edits merged)
Please save this source code
User prompt
move ball update shadow in a dicated function in ball class
/**** 
* Classes
****/ 
//https://beta.frvr.ai/image/65ffe5a19e8f228944f00dd8?utm_medium=share&utm_source=link
// 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
		// 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 = 220;
	self.collisionBody = LK.getAsset('collisionBody', {
		anchorX: 0.5,
		anchorY: 0.5,
		alpha: isDebug ? 0.6 : 0,
		width: collidSize,
		height: collidSize,
		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.2); // Stretch vertically 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
			self.scale.set(1.2, 0.8); // Compress vertically 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
		// Apply jelly-like animation based on speed
		/*var scaleX = 1 + Math.abs(self.speedX) * 0.01;
		var scaleY = 1 - Math.abs(self.speedY) * 0.01;
		self.scale.set(scaleX, scaleY);*/
	};
});
/**** 
* 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 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;
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
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 () {
			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) {
		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 == 1) {
				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
	}
	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)));
		//prevMouseY = dy; // Store current mouse position as previous mouse position
	}
};
game.down = function (x, y, obj) {
	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);
	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,
		alpha: 0.9,
		x: 2048 / 2,
		y: 2732 - 250
	}));
	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 = 2000;
	score1 = 0;
	score2 = 0;
	scoreTxt1.setText(score1.toString().padStart(2, '0'));
	scoreTxt2.setText(score2.toString().padStart(2, '0'));
} ===================================================================
--- original.js
+++ change.js
@@ -133,11 +133,11 @@
 		}
 		self.speedX = self.x - prevX; // Calculate horizontal speed based on movement
 		self.speedY = self.y - prevY; // Calculate vertical speed based on movement
 		// Apply jelly-like animation based on speed
-		var scaleX = 1 + Math.abs(self.speedX) * 0.01;
+		/*var scaleX = 1 + Math.abs(self.speedX) * 0.01;
 		var scaleY = 1 - Math.abs(self.speedY) * 0.01;
-		self.scale.set(scaleX, scaleY);
+		self.scale.set(scaleX, scaleY);*/
 	};
 });
 
 /**** 
:quality(85)/https://cdn.frvr.ai/667b12f12fe7ff4a7658074d.png%3F3) 
 white volley ball.
:quality(85)/https://cdn.frvr.ai/667b52222fe7ff4a765808d8.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/667ee6c8dd6f02985af5bd2d.png%3F3) 
 top view of a concave blue (0xADD8E6) plastic button. 4 small black directionnal chevrons engraved : right, left, top , bottom.. Photorealistic
:quality(85)/https://cdn.frvr.ai/667f2e1add6f02985af5bf4f.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/667f2fe3dd6f02985af5bf7b.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/667ff0486b731bccb12bc88f.png%3F3) 
 Beach ball. photo
:quality(85)/https://cdn.frvr.ai/667ffe216b731bccb12bc8f7.png%3F3) 
 full view of a Beach white towel with colored infinte logo. placed on the sand. photo
:quality(85)/https://cdn.frvr.ai/66807f21e7b74e71994abcc0.png%3F3) 
 :quality(85)/https://cdn.frvr.ai/6682ec9a6b731bccb12bce97.png%3F3) 
 Start button in the shape of a white beach volleyball with « START » written on it in black. Photo