Add teammates and some basic chasing mechanics
This commit is contained in:
parent
db2d07f244
commit
364f853d0e
@ -1,12 +1,22 @@
|
|||||||
# Pocket 5 Soccer Implementation Plan
|
# Pocket 5 Soccer Game Plan
|
||||||
## Phase 1: MVP Development
|
|
||||||
- **Task 1**: Setup Laravel backend and establish database connections.
|
## Phase 1: Core Game Mechanics (Complete)
|
||||||
- **Task 2**: Develop basic React components for game UI.
|
- [x] Set up basic Three.js scene with a soccer field.
|
||||||
- **Task 3**: Integrate a 3D game engine, ensuring minimal re-rendering issues.
|
- [x] Create player, AI teammates, and AI opponents.
|
||||||
- **Task 4**: Implement single-player mode with AI opponents.
|
- [x] Implement player movement controls.
|
||||||
- **Task 5**: Setup landing page and registration/login system.
|
- [x] Implement ball physics and possession logic.
|
||||||
## Phase 2: Enhanced Multiplayer
|
- [x] Basic AI: All AI players chase the ball.
|
||||||
- **Task 6**: Expand multiplayer capabilities to support 1v1 matches.
|
|
||||||
- **Task 7**: Implement flexible team assignment with a mix of AI and human players.
|
## Phase 2: Improved AI Behavior
|
||||||
## Phase 3: Advanced Features
|
- [x] When a player has the ball, AI teammates spread out in a diamond formation.
|
||||||
- **Task 8**:
|
- [ ] **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.
|
||||||
@ -276,6 +276,18 @@ function updateBallPosition() {
|
|||||||
}
|
}
|
||||||
ball.position.copy(ball.possessedBy.position).add(offset);
|
ball.position.copy(ball.possessedBy.position).add(offset);
|
||||||
ball.position.y = 0.8; // Keep it on the ground
|
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 {
|
} else {
|
||||||
// Update ball position based on velocity (when not possessed)
|
// Update ball position based on velocity (when not possessed)
|
||||||
ball.position.add(ball.velocity);
|
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
|
// Animation Loop
|
||||||
function animate() {
|
function animate() {
|
||||||
requestAnimationFrame(animate);
|
requestAnimationFrame(animate);
|
||||||
updatePlayerPosition();
|
updatePlayerPosition();
|
||||||
|
updateAIPlayers();
|
||||||
updateBallPosition();
|
updateBallPosition();
|
||||||
|
|
||||||
// Camera follows player from the sideline
|
// Camera follows player from the sideline
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user