From db2d07f244165afe7bda9fac666f1fca49195a9a Mon Sep 17 00:00:00 2001 From: Flatlogic Bot Date: Mon, 29 Sep 2025 17:44:01 +0000 Subject: [PATCH] Add the ball & implement pass/shoot functionality --- assets/js/game.js | 164 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 159 insertions(+), 5 deletions(-) diff --git a/assets/js/game.js b/assets/js/game.js index 50ab661..5917893 100644 --- a/assets/js/game.js +++ b/assets/js/game.js @@ -112,10 +112,31 @@ const playerMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 }); // R const player = new THREE.Mesh(playerGeometry, playerMaterial); player.position.y = 1.5; // Stand on the field player.castShadow = true; +player.lastVelocity = new THREE.Vector3(); +player.team = 'player'; scene.add(player); +// Teammates +const teammateMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 }); // Red teammates +const teammates = []; +for (let i = 0; i < 3; i++) { + const teammate = new THREE.Mesh(playerGeometry, teammateMaterial); + teammate.position.set( + (Math.random() - 0.5) * 50, + 1.5, + (Math.random() - 0.5) * 80 + ); + teammate.castShadow = true; + teammate.team = 'player'; + teammate.lastVelocity = new THREE.Vector3(); + scene.add(teammate); + teammates.push(teammate); +} + + // Simple Bot Players (Static) const botMaterial = new THREE.MeshStandardMaterial({ color: 0x0000ff }); // Blue bots +const bots = []; for (let i = 0; i < 4; i++) { const bot = new THREE.Mesh(playerGeometry, botMaterial); bot.position.set( @@ -124,9 +145,24 @@ for (let i = 0; i < 4; i++) { (Math.random() - 0.5) * 80 ); bot.castShadow = true; + bot.team = 'bot'; + bot.lastVelocity = new THREE.Vector3(); scene.add(bot); + bots.push(bot); } +const allPlayers = [player, ...teammates, ...bots]; + +// Ball +const ballGeometry = new THREE.SphereGeometry(0.8, 16, 16); +const ballMaterial = new THREE.MeshStandardMaterial({ color: 0xffffff }); +const ball = new THREE.Mesh(ballGeometry, ballMaterial); +ball.position.y = 0.8; +ball.castShadow = true; +ball.possessedBy = null; +ball.velocity = new THREE.Vector3(); +scene.add(ball); + // Player Controls const keyboardState = {}; @@ -134,19 +170,136 @@ window.addEventListener('keydown', (e) => { keyboardState[e.code] = true; }); window.addEventListener('keyup', (e) => { keyboardState[e.code] = false; }); const playerSpeed = 0.2; +const playerRadius = 1; // Approximate radius for collision function updatePlayerPosition() { + const velocity = new THREE.Vector3(); + if (keyboardState['ArrowUp']) { - player.position.x -= playerSpeed; + velocity.x -= playerSpeed; } if (keyboardState['ArrowDown']) { - player.position.x += playerSpeed; + velocity.x += playerSpeed; } if (keyboardState['ArrowLeft']) { - player.position.z += playerSpeed; + velocity.z += playerSpeed; } if (keyboardState['ArrowRight']) { - player.position.z -= playerSpeed; + velocity.z -= playerSpeed; + } + + if (velocity.lengthSq() > 0) { + player.lastVelocity.copy(velocity); + } + + const newPosition = player.position.clone().add(velocity); + + // Collision detection with other players + let collision = false; + for (const otherPlayer of allPlayers) { + if (otherPlayer === player) continue; + const distance = newPosition.distanceTo(otherPlayer.position); + if (distance < playerRadius * 2) { + collision = true; + break; + } + } + + if (!collision) { + player.position.copy(newPosition); + } + + // Shooting + if (keyboardState['KeyD']) { + shoot(); + } + + // Passing + if (keyboardState['KeyS']) { + pass(); + } +} + +function shoot() { + if (ball.possessedBy === player) { + ball.possessedBy = null; + const shootPower = 1.5; + ball.velocity.copy(player.lastVelocity).normalize().multiplyScalar(shootPower); + } +} + +function pass() { + if (ball.possessedBy === player) { + ball.possessedBy = null; + const passPower = 0.8; + const passAngle = Math.PI / 6; // 30 degrees + + let targetTeammate = null; + let minAngle = passAngle; + + const playerDirection = player.lastVelocity.clone().normalize(); + + for (const teammate of teammates) { + const toTeammate = teammate.position.clone().sub(player.position); + const distanceToTeammate = toTeammate.length(); + toTeammate.normalize(); + + const angle = playerDirection.angleTo(toTeammate); + + if (angle < minAngle && distanceToTeammate < 40) { // Max pass distance + minAngle = angle; + targetTeammate = teammate; + } + } + + if (targetTeammate) { + const direction = targetTeammate.position.clone().sub(player.position).normalize(); + ball.velocity.copy(direction).multiplyScalar(passPower); + } else { + ball.velocity.copy(playerDirection).multiplyScalar(passPower); + } + } +} + + +function updateBallPosition() { + const possessionDistance = playerRadius + 0.8 + 0.5; // player radius + ball radius + buffer + + if (ball.possessedBy) { + const ballOffset = 1.5; // Distance in front of player + let offset = new THREE.Vector3(ballOffset, 0, 0); // Default offset + + // Ball moves with the player who possesses it + if (ball.possessedBy.lastVelocity && ball.possessedBy.lastVelocity.lengthSq() > 0) { + const direction = ball.possessedBy.lastVelocity.clone().normalize(); + offset = direction.multiplyScalar(ballOffset); + } + ball.position.copy(ball.possessedBy.position).add(offset); + ball.position.y = 0.8; // Keep it on the ground + } else { + // Update ball position based on velocity (when not possessed) + ball.position.add(ball.velocity); + ball.velocity.multiplyScalar(0.97); // Friction + + // Ball collision with players + for (const p of allPlayers) { + const distance = ball.position.distanceTo(p.position); + if (distance < playerRadius + 0.8) { + const normal = ball.position.clone().sub(p.position).normalize(); + ball.velocity.reflect(normal).multiplyScalar(0.8); + } + } + + + // Check for gaining possession + for (const p of allPlayers) { + const distanceToPlayer = ball.position.distanceTo(p.position); + if (distanceToPlayer < possessionDistance && ball.velocity.lengthSq() < 0.1) { + ball.possessedBy = p; + ball.velocity.set(0, 0, 0); + break; // Only one player can possess the ball at a time + } + } } } @@ -154,6 +307,7 @@ function updatePlayerPosition() { function animate() { requestAnimationFrame(animate); updatePlayerPosition(); + updateBallPosition(); // Camera follows player from the sideline camera.position.z = player.position.z; @@ -169,4 +323,4 @@ window.addEventListener('resize', () => { renderer.setSize(window.innerWidth, window.innerHeight); }); -animate(); +animate(); \ No newline at end of file