Add teammates and some basic chasing mechanics

This commit is contained in:
Flatlogic Bot 2025-09-29 23:02:26 +00:00
parent db2d07f244
commit 364f853d0e
2 changed files with 137 additions and 12 deletions

View File

@ -1,12 +1,22 @@
# Pocket 5 Soccer Implementation Plan
## Phase 1: MVP Development
- **Task 1**: Setup Laravel backend and establish database connections.
- **Task 2**: Develop basic React components for game UI.
- **Task 3**: Integrate a 3D game engine, ensuring minimal re-rendering issues.
- **Task 4**: Implement single-player mode with AI opponents.
- **Task 5**: Setup landing page and registration/login system.
## Phase 2: Enhanced Multiplayer
- **Task 6**: Expand multiplayer capabilities to support 1v1 matches.
- **Task 7**: Implement flexible team assignment with a mix of AI and human players.
## Phase 3: Advanced Features
- **Task 8**:
# Pocket 5 Soccer Game Plan
## Phase 1: Core Game Mechanics (Complete)
- [x] Set up basic Three.js scene with a soccer field.
- [x] Create player, AI teammates, and AI opponents.
- [x] Implement player movement controls.
- [x] Implement ball physics and possession logic.
- [x] Basic AI: All AI players chase the ball.
## Phase 2: Improved AI Behavior
- [x] When a player has the ball, AI teammates spread out in a diamond formation.
- [ ] **Next Steps:**
- [ ] Improve defensive AI: Instead of all chasing the ball, have defenders mark opponent players or cover zones.
- [ ] Goalkeeper AI: Implement a goalkeeper that stays near the goal and tries to block shots.
- [ ] AI Shooting/Passing: Make AI players with the ball decide whether to shoot or pass to a teammate.
## Phase 3: UI and UX
- [ ] **Next Steps:**
- [ ] Display a scoreboard.
- [ ] Add a game timer.
- [ ] Create a start/reset menu.
- [ ] Add visual indicators for the selected player.

View File

@ -276,6 +276,18 @@ function updateBallPosition() {
}
ball.position.copy(ball.possessedBy.position).add(offset);
ball.position.y = 0.8; // Keep it on the ground
// Check for steals
const stealRadius = playerRadius * 2.2;
for (const p of allPlayers) {
if (p === ball.possessedBy) continue;
const distanceToBallOwner = p.position.distanceTo(ball.possessedBy.position);
if (distanceToBallOwner < stealRadius) {
ball.possessedBy = p;
break;
}
}
} else {
// Update ball position based on velocity (when not possessed)
ball.position.add(ball.velocity);
@ -303,10 +315,113 @@ function updateBallPosition() {
}
}
const aiSpeed = 0.1;
function getDiamondFormation(ballOwner, allTeammates) {
const formation = [];
const spacing = 15;
const ownerPos = ballOwner.position;
let playerDirection = ballOwner.lastVelocity.clone();
if (playerDirection.lengthSq() === 0) { // If player is not moving
// Default direction towards opponent's goal
playerDirection = (ballOwner.team === 'player') ? new THREE.Vector3(0, 0, 1) : new THREE.Vector3(0, 0, -1);
}
playerDirection.normalize();
// Vector perpendicular to player direction on the XZ plane
const perpendicularDirection = new THREE.Vector3(-playerDirection.z, 0, playerDirection.x);
const basePositions = [
playerDirection.clone().multiplyScalar(spacing), // Ahead
playerDirection.clone().multiplyScalar(-spacing * 0.8), // Behind (closer)
perpendicularDirection.clone().multiplyScalar(spacing), // Right
perpendicularDirection.clone().multiplyScalar(-spacing) // Left
];
// Assign available positions to teammates
for (let i = 0; i < allTeammates.length; i++) {
if(basePositions[i]) {
formation.push(ownerPos.clone().add(basePositions[i]));
}
}
return formation;
}
function updateAIPlayers() {
const ballOwner = ball.possessedBy;
// Get all AI players for team 'player' and team 'bot'
const playerTeamAIs = teammates;
const botTeamAIs = bots;
// Determine which AI players are teammates of the ball owner
let formationAIs = [];
if (ballOwner) {
if (ballOwner.team === 'player') {
formationAIs = playerTeamAIs.filter(p => p !== ballOwner);
} else if (ballOwner.team === 'bot') {
formationAIs = botTeamAIs.filter(p => p !== ballOwner);
}
}
// To have a consistent assignment, sort them by id
formationAIs.sort((a,b) => a.id - b.id);
let formationPositions = [];
if (ballOwner && formationAIs.length > 0) {
formationPositions = getDiamondFormation(ballOwner, formationAIs);
}
for (const aiPlayer of allPlayers) {
if (aiPlayer === player) continue; // Skip user-controlled player
let targetPosition;
const formationIndex = formationAIs.indexOf(aiPlayer);
if (ballOwner === aiPlayer) {
// This AI has the ball, move towards the opponent's goal
targetPosition = new THREE.Vector3(0, 0, aiPlayer.team === 'player' ? 50 : -50);
} else if (ballOwner && aiPlayer.team === ballOwner.team && formationIndex !== -1) {
// This AI is a teammate of the ball owner, get into formation
if(formationPositions[formationIndex]){
targetPosition = formationPositions[formationIndex];
} else {
targetPosition = ball.position; // Fallback
}
} else {
// This AI is an opponent, or no one has the ball. Chase the ball.
targetPosition = ball.position;
}
const direction = targetPosition.clone().sub(aiPlayer.position).normalize();
const velocity = direction.multiplyScalar(aiSpeed);
const newPosition = aiPlayer.position.clone().add(velocity);
// Basic collision avoidance with other players
let collision = false;
for (const otherPlayer of allPlayers) {
if (otherPlayer === aiPlayer) continue;
const distance = newPosition.distanceTo(otherPlayer.position);
if (distance < playerRadius * 2) {
collision = true;
break;
}
}
if (!collision) {
aiPlayer.position.copy(newPosition);
aiPlayer.lastVelocity.copy(velocity);
}
}
}
// Animation Loop
function animate() {
requestAnimationFrame(animate);
updatePlayerPosition();
updateAIPlayers();
updateBallPosition();
// Camera follows player from the sideline