diff --git a/.gitignore b/.gitignore index d4be7c0..839beb4 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,8 @@ .LSOverride # Icon must end with two \r -Icon +Icon + # Thumbnails ._* diff --git a/README.md b/README.md index 1f59925..df4e8cb 100644 --- a/README.md +++ b/README.md @@ -24,11 +24,13 @@ ### 🏝️ Fase 1: La Raccolta (Timer: 1m) - [⚠️] **Ambiente di gioco:** Modellazione dell'isola 3D con Three.js e gestione dei confini della mappa. -- [⚠️] **Sistema di Spawn:** Posizionamento casuale dei rifiuti su 20 coordinate casuali. -- [⚠️] **Sistema Ostacoli:** Inserimento di modelli 3D di alberi e oggetti ambientali decorativi. -- [⚠️] **Meccaniche di Raccolta:** Gestione delle collisioni e incremento del punteggio ecologico. +- [✅] **Sistema di Spawn:** Posizionamento casuale dei rifiuti su 20 coordinate casuali. +- [✅] **Sistema Ostacoli:** Inserimento di modelli 3D di alberi e oggetti ambientali decorativi. +- [✅] **Meccaniche di Raccolta:** Gestione delle collisioni e incremento del punteggio ecologico. - [✅] **Interfaccia (HUD):** Overlay con Timer (60s), contatore rifiuti e disattivazione della pausa. - [❌] **Rifinitura (Polish):** Animazioni di raccolta e ottimizzazione delle mesh 3D. +- [❌] **Timer:** Meccanica del timer per il tempo di raccolta dei rifiuti +- [❌] **Personaggio:** Cilindro che rappresenta il personaggio (fatto per collisioni piu' precise) ### ♻️ Fase 2: Lo Smistamento (Timer: 5s * Punteggio) - [❌] **Timer Dinamico:** Calcolo del tempo a disposizione basato sul successo della Fase 1. diff --git a/index-test.html b/index-test.html new file mode 100644 index 0000000..bade6b2 --- /dev/null +++ b/index-test.html @@ -0,0 +1,46 @@ + + + + + Semplice Island FPS + + + +
+ +
+
+
+ titolo +
+
+
+
+

Controlli

+

WASD Movimento

+

Mouse Camera

+
+
+
+ Clicca per giocare +
+
+
+
+
+
Rifiuti: 0
+
Tempo: XX:XX
+
+
+ + + + \ No newline at end of file diff --git a/script-test.js b/script-test.js new file mode 100644 index 0000000..9865080 --- /dev/null +++ b/script-test.js @@ -0,0 +1,242 @@ +import * as THREE from 'three'; +import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js'; +import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; + +// 1. SCENA E CAMERA +const scene = new THREE.Scene(); +scene.background = new THREE.Color(0x33ccff); // Cielo più azzurro +const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); +const renderer = new THREE.WebGLRenderer(); +renderer.setSize(window.innerWidth, window.innerHeight); +document.body.appendChild(renderer.domElement); +window.addEventListener('resize', function () { + renderer.setSize(window.innerWidth, window.innerHeight) +}) +// AGGIUNTA MARE +const oceanGeometry = new THREE.PlaneGeometry(2000, 2000); +const oceanMaterial = new THREE.MeshStandardMaterial({ + color: 0x0077be, + transparent: true, + opacity: 0.8 +}); +const ocean = new THREE.Mesh(oceanGeometry, oceanMaterial); +ocean.rotation.x = -Math.PI / 2; +ocean.position.y = -0.2; // Leggermente sotto l'isola +scene.add(ocean); + +// 2. LUCI E AMBIENTE +scene.add(new THREE.AmbientLight(0xffffff, 0.8)); +const sunLight = new THREE.DirectionalLight(0xffffff, 1); +sunLight.position.set(10, 20, 10); +scene.add(sunLight); + +const islandGroup = new THREE.Group(); + +const altezzaIsola = 0.5; // Quanto sta il prato sopra il livello del mare (0.0) +const altezzaOcchi = 1.6; // L'altezza standard della telecamera + +// --- IL FONDALE (Sabbia scura sul fondo) --- +const floorGeo = new THREE.PlaneGeometry(1000, 1000); +const floorMat = new THREE.MeshStandardMaterial({ color: 0x1a1a1a }); // Grigio scuro/Fondale +const floor = new THREE.Mesh(floorGeo, floorMat); +floor.rotation.x = -Math.PI / 2; +floor.position.y = -3; // 5 metri sotto il livello del mare +scene.add(floor); + +// --- IL PRATO --- +const raggioPrato = 19; +const grassGeo = new THREE.CircleGeometry(raggioPrato, 64); +const grassMat = new THREE.MeshStandardMaterial({ color: 0x4df556 }); +const grass = new THREE.Mesh(grassGeo, grassMat); +grass.rotation.x = -Math.PI / 2; + +// Posizioniamo il prato all'altezza desiderata +grass.position.y = altezzaIsola; +islandGroup.add(grass); + +// --- LA SABBIA (Cilindro Svasato) --- +const altezzaSabbia = 1.5; // Altezza totale del blocco sabbia +const sandGeo = new THREE.CylinderGeometry(raggioPrato, 22 + 10, altezzaSabbia + 3, 64, 1, true); +const sandMat = new THREE.MeshStandardMaterial({ color: 0xedc9af, side: THREE.DoubleSide }); +const sand = new THREE.Mesh(sandGeo, sandMat); + +// Per far combaciare la cima del cilindro con il prato: +// L'altezza è 1.5, il centro del cilindro deve stare a (AltezzaIsola - metà altezza cilindro) +sand.position.y = altezzaIsola - (altezzaSabbia / 2) - 1.5; +islandGroup.add(sand); + +scene.add(islandGroup); +scene.fog = new THREE.Fog(0x33ccff, 20, 100); + +// 3. CONTROLLI +function lock() { + document.getElementById('start').style.display = 'none'; + document.getElementById('punti').style.display = 'block'; + document.getElementById('tempo').style.display = 'block'; + document.getElementById('crosshair').style.display = 'block'; +} +function unlock() { + const startDiv = document.getElementById('start'); + startDiv.style.display = 'flex'; // Forza il layout Flexbox + // Nascondi l'HUD + document.getElementById('punti').style.display = 'none'; + document.getElementById('tempo').style.display = 'none'; + document.getElementById('crosshair').style.display = 'none'; +} +const controls = new PointerLockControls(camera, document.body); +document.getElementById('start').addEventListener('click', () => controls.lock()); +controls.addEventListener('lock', lock); +controls.addEventListener('unlock', unlock); + +// 4. GESTIONE OGGETTI DI SCENA (20 rifiuti + 6 alberi) +const trashArray = []; +let score = 0; +const loader = new GLTFLoader(); + +loader.load('models/rifiuto.glb', (gltf) => { + const mesh = gltf.scene.getObjectByName("Trash_Pile_03_GEO"); + mesh.geometry.center(); // Centra l'oggetto per collisioni precise + for (let i = 0; i < 20; i++) { + const clone = mesh.clone(); + scene.add(clone); + trashArray.push(clone); + spawn(clone, 0.2); + } +}); + +loader.load('models/tree.glb', (gltf) => { + const mesh = gltf.scene; + mesh.scale.set(4, 4, 4); + console.log(mesh); + + function spawn(obj, radius, minDistance) { + const points = []; + const attempts = 1000; // Numero massimo di tentativi per trovare un punto valido + for (let i = 0; i < attempts; i++) { + let angle = Math.random() * 2 * Math.PI; + let dist = Math.random() * radius; + let x = dist * Math.cos(angle); + let z = dist * Math.sin(angle); + let valid = true; + for (let j = 0; j < points.length; j++) { + const dx = x - points[j].x; + const dz = z - points[j].z; + const distance = Math.sqrt(dx * dx + dz * dz); + if (distance < minDistance) { + valid = false; + break; + } + } + if (valid) { + points.push({ x: x, z: z }); + } + } + + // Spawna gli alberi nei punti generati + for (let i = 0; i < points.length; i++) { + const clone = obj.clone(); + const a = Math.random() * Math.PI * 2; + const r = Math.random() * 18; + const x = Math.cos(a) * r; + const z = Math.sin(a) * r; + // Y deve essere altezzaIsola + un piccolo offset + clone.position.set(points[i].x, altezzaIsola - 0.1, points[i].z); + clone.rotation.y = Math.random() * Math.PI * 2; // Rotazione tra 0 e 360 gradi + scene.add(clone); + } + } + spawn(mesh, 18, 8); // Raggio 18, distanza minima 5 +}); +console.log(scene) + +function spawn(obj) { + const a = Math.random() * Math.PI * 2; + const r = Math.random() * 18; + const x = Math.cos(a) * r; + const z = Math.sin(a) * r; + // Y deve essere altezzaIsola + un piccolo offset + obj.position.set(x, altezzaIsola + 0.1, z); +} + +// 5. MOVIMENTO E COLLISIONI +const keys = {}; +document.onkeydown = (e) => keys[e.code] = true; +document.onkeyup = (e) => keys[e.code] = false; + +// Setup Raycaster (fuori dal loop animate) +const raycaster = new THREE.Raycaster(); +const downVector = new THREE.Vector3(0, -1, 0); + +// --- CONFIGURAZIONE MOVIMENTO --- +let canLeaveIsland = true; // Se false, l'acqua blocca il movimento. +let lastSafePosition = camera.position.clone(); + +function animate() { + requestAnimationFrame(animate); + + if (controls.isLocked) { + // 1. Salva la posizione attuale prima del movimento + const oldPos = camera.position.clone(); + + // 2. Esegui il movimento WASD + if (keys['KeyW']) controls.moveForward(0.15); + if (keys['KeyS']) controls.moveForward(-0.15); + if (keys['KeyA']) controls.moveRight(-0.15); + if (keys['KeyD']) controls.moveRight(0.15); + + // 3. --- GESTIONE ALTEZZA DINAMICA --- + const rayOrigin = camera.position.clone(); + rayOrigin.y = 10; // Spara dall'alto verso il basso + raycaster.set(rayOrigin, downVector); + + // Controlliamo Prato e Sabbia + const intersects = raycaster.intersectObjects([grass, sand]); + + if (intersects.length > 0) { + // Logica del "Punto più alto": + // Se il raggio colpisce sia prato che sabbia, prendiamo il prato. + let highestPoint = -Infinity; + for (let i = 0; i < intersects.length; i++) { + if (intersects[i].point.y > highestPoint) { + highestPoint = intersects[i].point.y; + } + } + + // Applichiamo l'altezza alla camera + camera.position.y = highestPoint + altezzaOcchi; + + // --- CONTROLLO BARRIERA ACQUA --- + // Se il punto più alto è sotto il livello del mare (0) + // e non possiamo uscire, blocchiamo il movimento + if (!canLeaveIsland && highestPoint < 0) { + camera.position.x = oldPos.x; + camera.position.z = oldPos.z; + camera.position.y = oldPos.y; + } else { + // Posizione valida, la salviamo + lastSafePosition.copy(camera.position); + camera.position.y = camera.position.y > (-2.25 + altezzaOcchi) ? camera.position.y : (-2.25 + altezzaOcchi); + } + } else { + // --- FUORI DALL'ISOLA (VUOTO) --- + if (!canLeaveIsland) { + camera.position.copy(oldPos); + } else { + camera.position.y = -2.25 + altezzaOcchi; // Volo sull'acqua + } + } + + // --- COLLISIONI RIFIUTI --- + trashArray.forEach(t => { + if (camera.position.distanceTo(t.position) < 1.8) { + score++; + document.getElementById('score').innerText = score; + spawn(t); + } + }); + } + + renderer.render(scene, camera); +} + +animate(); \ No newline at end of file