34307-vm/assets/js/main.js
2025-10-01 05:28:11 +00:00

233 lines
8.2 KiB
JavaScript

import * as THREE from 'three';
import { Field } from './Field.js';
import { Ball } from './Ball.js';
import { Team } from './Team.js';
class Game {
constructor() {
this.scene = new THREE.Scene();
this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
this.renderer = new THREE.WebGLRenderer({ antialias: true });
this.keyboardState = {};
this.init();
}
init() {
this.scene.background = new THREE.Color(0x87CEEB); // Sky blue background
this.camera.position.set(45, 25, 0);
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.renderer.shadowMap.enabled = true;
document.body.appendChild(this.renderer.domElement);
this.initLighting();
this.field = new Field(this.scene);
this.ball = new Ball(this.scene);
this.playerTeam = new Team(this.scene, 0xff0000, 4, true);
this.botTeam = new Team(this.scene, 0x0000ff, 4, false);
this.allPlayers = [...this.playerTeam.players, ...this.botTeam.players];
this.player = this.playerTeam.players[0];
this.initControls();
this.animate();
}
initLighting() {
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
this.scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1.2);
directionalLight.position.set(20, 30, 20);
directionalLight.castShadow = true;
directionalLight.shadow.camera.top = 20;
directionalLight.shadow.camera.bottom = -20;
directionalLight.shadow.camera.left = -20;
directionalLight.shadow.camera.right = 20;
this.scene.add(directionalLight);
}
initControls() {
window.addEventListener('keydown', (e) => { this.keyboardState[e.code] = true; });
window.addEventListener('keyup', (e) => { this.keyboardState[e.code] = false; });
}
updatePlayerPosition() {
const playerSpeed = 0.2;
const playerRadius = 1; // Approximate radius for collision
const velocity = new THREE.Vector3();
if (this.keyboardState['ArrowUp']) {
velocity.x -= playerSpeed;
}
if (this.keyboardState['ArrowDown']) {
velocity.x += playerSpeed;
}
if (this.keyboardState['ArrowLeft']) {
velocity.z += playerSpeed;
}
if (this.keyboardState['ArrowRight']) {
velocity.z -= playerSpeed;
}
if (velocity.lengthSq() > 0) {
this.player.mesh.lastVelocity.copy(velocity);
}
const newPosition = this.player.mesh.position.clone().add(velocity);
// Collision detection with other players
let collision = false;
for (const otherPlayer of this.allPlayers) {
if (otherPlayer === this.player) continue;
const distance = newPosition.distanceTo(otherPlayer.mesh.position);
if (distance < playerRadius * 2) {
collision = true;
break;
}
}
if (!collision) {
this.player.mesh.position.copy(newPosition);
}
// Shooting
if (this.keyboardState['KeyD']) {
this.player.shoot(this.ball);
}
// Passing
if (this.keyboardState['KeyS']) {
this.player.pass(this.ball, this.playerTeam.players.slice(1));
}
}
updateAIPlayers() {
const aiSpeed = 0.1;
const playerRadius = 1;
for (const aiPlayer of this.allPlayers) {
if (aiPlayer === this.player) continue; // Skip user-controlled player
let targetPosition;
const ballOwner = this.ball.mesh.possessedBy;
if (ballOwner === aiPlayer.mesh) {
// This AI has the ball, move towards the opponent's goal
targetPosition = new THREE.Vector3(0, 0, aiPlayer.mesh.team === 'player' ? 50 : -50);
} else if (ballOwner && aiPlayer.mesh.team === ballOwner.team) {
// This AI is a teammate of the ball owner, get into formation
targetPosition = ballOwner.position.clone().add(new THREE.Vector3(10, 0, 10));
} else {
// This AI is an opponent, or no one has the ball. Chase the ball.
targetPosition = this.ball.mesh.position;
}
const direction = targetPosition.clone().sub(aiPlayer.mesh.position).normalize();
const velocity = direction.multiplyScalar(aiSpeed);
const newPosition = aiPlayer.mesh.position.clone().add(velocity);
// Basic collision avoidance with other players
let collision = false;
for (const otherPlayer of this.allPlayers) {
if (otherPlayer === aiPlayer) continue;
const distance = newPosition.distanceTo(otherPlayer.mesh.position);
if (distance < playerRadius * 2) {
collision = true;
break;
}
}
if (!collision) {
aiPlayer.mesh.position.copy(newPosition);
aiPlayer.mesh.lastVelocity.copy(velocity);
}
}
}
updateBall() {
this.ball.update();
const playerRadius = 1;
const possessionDistance = playerRadius + 0.8 + 0.5;
const STEAL_COOLDOWN = 1000;
// Ball collision with players
for (const p of this.allPlayers) {
const distance = this.ball.mesh.position.distanceTo(p.mesh.position);
if (distance < playerRadius + 0.8) {
const normal = this.ball.mesh.position.clone().sub(p.mesh.position).normalize();
this.ball.mesh.velocity.reflect(normal).multiplyScalar(0.8);
}
}
// Check for gaining possession
if (!this.ball.mesh.possessedBy) {
for (const p of this.allPlayers) {
const distanceToPlayer = this.ball.mesh.position.distanceTo(p.mesh.position);
if (distanceToPlayer < possessionDistance && this.ball.mesh.velocity.lengthSq() < 0.1) {
this.ball.mesh.possessedBy = p.mesh;
this.ball.mesh.velocity.set(0, 0, 0);
this.ball.mesh.lastStealTime = Date.now();
break; // Only one player can possess the ball at a time
}
}
}
// Check for steals
const stealRadius = playerRadius * 2.2;
const now = Date.now();
const ballOwner = this.ball.mesh.possessedBy;
if (ballOwner && now - this.ball.mesh.lastStealTime > STEAL_COOLDOWN) {
for (const p of this.allPlayers) {
if (p.mesh === ballOwner) continue;
const distanceToBallOwner = p.mesh.position.distanceTo(ballOwner.position);
if (distanceToBallOwner < stealRadius) {
const owner = ballOwner;
const stealer = p.mesh;
let canSteal = true; // Default to allow steal
if (owner.lastVelocity.lengthSq() > 0) {
// Check if the stealer is roughly in front of the owner
const ownerDirection = owner.lastVelocity.clone().normalize();
const toStealer = stealer.position.clone().sub(owner.position).normalize();
const angle = ownerDirection.angleTo(toStealer);
if (angle >= Math.PI / 2) { // If stealer is not in front (90 degree FOV)
canSteal = false;
}
}
if (canSteal) {
this.ball.mesh.possessedBy = stealer;
this.ball.mesh.lastStealTime = now;
break;
}
}
}
}
}
animate() {
requestAnimationFrame(() => this.animate());
this.updatePlayerPosition();
this.updateAIPlayers();
this.updateBall();
// Camera follows player from the sideline
this.camera.position.z = this.player.mesh.position.z;
this.camera.lookAt(this.player.mesh.position);
this.renderer.render(this.scene, this.camera);
}
}
new Game();