Rausda6's picture
create a fully playable PONG game but in 3D, computer vs. player, where the play area is a sphere, and the bars from 2D game are here a spherical quadrilateral so square like surface . use W & S and Q&E to control the panel , the mouse to rotate the sphere
5c6379d verified
// Game variables
let scene, camera, renderer;
let sphere, playerPaddle, computerPaddle, ball;
let playerScore = 0;
let computerScore = 0;
let gameActive = false;
let clock = new THREE.Clock();
// Paddle properties
const PADDLE_WIDTH = 0.5;
const PADDLE_HEIGHT = 0.3;
const PADDLE_DEPTH = 0.1;
const PADDLE_SPEED = 0.08;
// Ball properties
const BALL_RADIUS = 0.05;
let ballVelocity = new THREE.Vector3(0.03, 0.03, 0.03);
// Sphere arena properties
const SPHERE_RADIUS = 3;
const SPHERE_SEGMENTS = 32;
const SPHERE_RINGS = 32;
// Rotation controls
let sphereRotationX = 0;
let sphereRotationY = 0;
let sphereRotationSpeed = 0.02;
let autoRotate = true;
// DOM Elements
const playerScoreElement = document.getElementById('playerScore');
const computerScoreElement = document.getElementById('computerScore');
const startBtn = document.getElementById('startBtn');
const resetBtn = document.getElementById('resetBtn');
const playAgainBtn = document.getElementById('playAgainBtn');
const gameOverScreen = document.getElementById('gameOverScreen');
const winnerText = document.getElementById('winnerText');
// Keyboard state tracking
const keys = {
w: false,
s: false,
q: false,
e: false
};
// Initialize Three.js scene
function init() {
// Create scene
scene = new THREE.Scene();
scene.background = new THREE.Color(0x0c0c2d);
// Create camera
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 0, 7);
// Create renderer
renderer = new THREE.WebGLRenderer({
canvas: document.getElementById('gameCanvas'),
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
// Create spherical arena
createSphereArena();
// Create paddles
createPaddles();
// Create ball
createBall();
// Add lighting
addLighting();
// Event listeners
setupEventListeners();
// Start animation loop
animate();
}
// Create the spherical playing arena
function createSphereArena() {
const geometry = new THREE.SphereGeometry(SPHERE_RADIUS, SPHERE_SEGMENTS, SPHERE_RINGS);
const material = new THREE.MeshBasicMaterial({
color: 0x1a1a40,
wireframe: true,
transparent: true,
opacity: 0.5
});
sphere = new THREE.Mesh(geometry, material);
scene.add(sphere);
// Add center dividing planes
const planeGeometry = new THREE.PlaneGeometry(SPHERE_RADIUS * 1.8, SPHERE_RADIUS * 1.8);
const planeMaterial = new THREE.MeshBasicMaterial({
color: 0x00ffff,
wireframe: true,
transparent: true,
opacity: 0.2,
side: THREE.DoubleSide
});
// Vertical center plane
const verticalPlane = new THREE.Mesh(planeGeometry, planeMaterial);
verticalPlane.rotation.x = Math.PI / 2;
scene.add(verticalPlane);
// Horizontal center plane
const horizontalPlane = new THREE.Mesh(planeGeometry, planeMaterial);
scene.add(horizontalPlane);
}
// Create player and computer paddles as curved surfaces on the sphere
function createPaddles() {
// Player paddle (right side)
const playerGeometry = new THREE.BoxGeometry(PADDLE_WIDTH, PADDLE_HEIGHT, PADDLE_DEPTH);
const playerMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
playerPaddle = new THREE.Mesh(playerGeometry, playerMaterial);
playerPaddle.position.x = SPHERE_RADIUS - 0.1;
scene.add(playerPaddle);
// Computer paddle (left side)
const computerGeometry = new THREE.BoxGeometry(PADDLE_WIDTH, PADDLE_HEIGHT, PADDLE_DEPTH);
const computerMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
computerPaddle = new THREE.Mesh(computerGeometry, computerMaterial);
computerPaddle.position.x = -(SPHERE_RADIUS - 0.1);
scene.add(computerPaddle);
}
// Create the ball
function createBall() {
const geometry = new THREE.SphereGeometry(BALL_RADIUS, 16, 16);
const material = new THREE.MeshBasicMaterial({ color: 0xffff00 });
ball = new THREE.Mesh(geometry, material);
resetBall();
scene.add(ball);
}
// Add lighting to the scene
function addLighting() {
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(1, 1, 1);
scene.add(directionalLight);
const pointLight = new THREE.PointLight(0x4d79ff, 1, 20);
pointLight.position.set(5, 5, 5);
scene.add(pointLight);
}
// Reset ball to center
function resetBall() {
ball.position.set(0, 0, 0);
// Random direction
const angleXY = Math.random() * Math.PI * 2;
const angleZ = (Math.random() - 0.5) * Math.PI;
const speed = 0.03;
ballVelocity.set(
Math.cos(angleXY) * Math.cos(angleZ) * speed,
Math.sin(angleXY) * Math.cos(angleZ) * speed,
Math.sin(angleZ) * speed
);
}
// Update player paddle based on keyboard input
function updatePlayerPaddle() {
if (!gameActive) return;
// Move paddle vertically (W/S keys)
if (keys.w) {
playerPaddle.position.y += PADDLE_SPEED;
}
if (keys.s) {
playerPaddle.position.y -= PADDLE_SPEED;
}
// Constrain paddle to sphere surface
constrainPaddleToSphere(playerPaddle);
}
// Update computer paddle AI
function updateComputerPaddle() {
if (!gameActive) return;
// Simple AI - follow the ball with some delay
const targetY = ball.position.y;
const currentY = computerPaddle.position.y;
if (currentY < targetY - 0.1) {
computerPaddle.position.y += PADDLE_SPEED * 0.8; // Slightly slower than player
} else if (currentY > targetY + 0.1) {
computerPaddle.position.y -= PADDLE_SPEED * 0.8;
}
// Constrain paddle to sphere surface
constrainPaddleToSphere(computerPaddle);
}
// Constrain paddle to sphere surface
function constrainPaddleToSphere(paddle) {
// Keep paddle on sphere surface
const distanceFromCenter = Math.sqrt(
paddle.position.x * paddle.position.x +
paddle.position.y * paddle.position.y +
paddle.position.z * paddle.position.z
);
if (Math.abs(distanceFromCenter - SPHERE_RADIUS) > 0.1) {
const scale = SPHERE_RADIUS / distanceFromCenter;
paddle.position.x *= scale;
paddle.position.y *= scale;
paddle.position.z *= scale;
}
// Limit paddle movement to prevent it from going through the sphere
const maxDistance = SPHERE_RADIUS - Math.max(PADDLE_WIDTH, PADDLE_HEIGHT) / 2;
const currentDistance = Math.sqrt(
paddle.position.x * paddle.position.x +
paddle.position.y * paddle.position.y +
paddle.position.z * paddle.position.z
);
if (currentDistance > maxDistance) {
const scale = maxDistance / currentDistance;
paddle.position.x *= scale;
paddle.position.y *= scale;
paddle.position.z *= scale;
}
}
// Update ball position and handle collisions
function updateBall() {
if (!gameActive) return;
// Move ball
ball.position.add(ballVelocity);
// Check for collisions with paddles
checkPaddleCollisions();
// Check for scoring
checkScoring();
// Keep ball on sphere surface
constrainBallToSphere();
}
// Check for collisions with paddles
function checkPaddleCollisions() {
// Player paddle collision
if (
Math.abs(ball.position.x - playerPaddle.position.x) < (PADDLE_WIDTH/2 + BALL_RADIUS) &&
Math.abs(ball.position.y - playerPaddle.position.y) < (PADDLE_HEIGHT/2 + BALL_RADIUS) &&
Math.abs(ball.position.z - playerPaddle.position.z) < (PADDLE_DEPTH/2 + BALL_RADIUS)
) {
// Reflect ball and add some randomness
ballVelocity.x = -Math.abs(ballVelocity.x);
ballVelocity.y += (ball.position.y - playerPaddle.position.y) * 0.02;
ballVelocity.z += (ball.position.z - playerPaddle.position.z) * 0.02;
// Increase speed slightly
ballVelocity.multiplyScalar(1.05);
}
// Computer paddle collision
if (
Math.abs(ball.position.x - computerPaddle.position.x) < (PADDLE_WIDTH/2 + BALL_RADIUS) &&
Math.abs(ball.position.y - computerPaddle.position.y) < (PADDLE_HEIGHT/2 + BALL_RADIUS) &&
Math.abs(ball.position.z - computerPaddle.position.z) < (PADDLE_DEPTH/2 + BALL_RADIUS)
) {
// Reflect ball and add some randomness
ballVelocity.x = Math.abs(ballVelocity.x);
ballVelocity.y += (ball.position.y - computerPaddle.position.y) * 0.02;
ballVelocity.z += (ball.position.z - computerPaddle.position.z) * 0.02;
// Increase speed slightly
ballVelocity.multiplyScalar(1.05);
}
}
// Check for scoring
function checkScoring() {
// Check if ball has passed the paddles (scoring)
if (ball.position.x > SPHERE_RADIUS) {
// Computer scores
computerScore++;
computerScoreElement.textContent = computerScore;
resetBall();
checkGameOver();
} else if (ball.position.x < -SPHERE_RADIUS) {
// Player scores
playerScore++;
playerScoreElement.textContent = playerScore;
resetBall();
checkGameOver();
}
}
// Check if game is over
function checkGameOver() {
if (playerScore >= 5 || computerScore >= 5) {
gameActive = false;
winnerText.textContent = playerScore >= 5 ? "You Win!" : "Computer Wins!";
gameOverScreen.classList.remove('hidden');
}
}
// Keep ball constrained to sphere surface
function constrainBallToSphere() {
const distance = ball.position.length();
if (Math.abs(distance - SPHERE_RADIUS) > BALL_RADIUS) {
// Normalize position vector and scale to sphere radius
ball.position.normalize().multiplyScalar(SPHERE_RADIUS);
// Reflect velocity vector
const normal = ball.position.clone().normalize();
ballVelocity.reflect(normal);
}
}
// Rotate sphere based on keyboard input
function rotateSphere() {
if (keys.q) {
sphereRotationY += sphereRotationSpeed;
}
if (keys.e) {
sphereRotationY -= sphereRotationSpeed;
}
sphere.rotation.y = sphereRotationY;
sphere.rotation.x = sphereRotationX;
}
// Set up event listeners
function setupEventListeners() {
// Keyboard controls
document.addEventListener('keydown', (event) => {
if (event.key === 'w' || event.key === 'W') keys.w = true;
if (event.key === 's' || event.key === 'S') keys.s = true;
if (event.key === 'q' || event.key === 'Q') keys.q = true;
if (event.key === 'e' || event.key === 'E') keys.e = true;
});
document.addEventListener('keyup', (event) => {
if (event.key === 'w' || event.key === 'W') keys.w = false;
if (event.key === 's' || event.key === 'S') keys.s = false;
if (event.key === 'q' || event.key === 'Q') keys.q = false;
if (event.key === 'e' || event.key === 'E') keys.e = false;
});
// Mouse controls for camera rotation
let isDragging = false;
let previousMousePosition = {
x: 0,
y: 0
};
document.addEventListener('mousedown', () => {
isDragging = true;
});
document.addEventListener('mouseup', () => {
isDragging = false;
});
document.addEventListener('mousemove', (event) => {
if (!isDragging) return;
const deltaMove = {
x: event.offsetX - previousMousePosition.x,
y: event.offsetY - previousMousePosition.y
};
// Rotate camera based on mouse movement
camera.rotation.y += deltaMove.x * 0.01;
camera.rotation.x += deltaMove.y * 0.01;
previousMousePosition = {
x: event.offsetX,
y: event.offsetY
};
});
// Start button
startBtn.addEventListener('click', () => {
gameActive = !gameActive;
startBtn.textContent = gameActive ? "Pause" : "Start Game";
if (playerScore >= 5 || computerScore >= 5) {
resetGame();
}
});
// Reset button
resetBtn.addEventListener('click', resetGame);
// Play again button
playAgainBtn.addEventListener('click', () => {
gameOverScreen.classList.add('hidden');
resetGame();
gameActive = true;
startBtn.textContent = "Pause";
});
// Window resize
window.addEventListener('resize', onWindowResize);
}
// Reset game state
function resetGame() {
playerScore = 0;
computerScore = 0;
playerScoreElement.textContent = "0";
computerScoreElement.textContent = "0";
resetBall();
gameActive = false;
startBtn.textContent = "Start Game";
sphereRotationX = 0;
sphereRotationY = 0;
sphere.rotation.set(0, 0, 0);
camera.rotation.set(0, 0, 0);
}
// Handle window resize
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
// Animation loop
function animate() {
requestAnimationFrame(animate);
const delta = clock.getDelta();
// Update game objects
updatePlayerPaddle();
updateComputerPaddle();
updateBall();
rotateSphere();
// Auto-rotate sphere slowly when not manually rotated
if (autoRotate && !keys.q && !keys.e) {
sphere.rotation.y += 0.002;
}
renderer.render(scene, camera);
}
// Initialize the game when page loads
window.addEventListener('load', init);