inizio classifica
@@ -0,0 +1,113 @@
|
||||
# ---> VisualStudioCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/*.code-snippets
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
|
||||
# ---> macOS
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
# ---> Windows
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
Thumbs.db:encryptable
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
|
||||
# Dump file
|
||||
*.stackdump
|
||||
|
||||
# Folder config file
|
||||
[Dd]esktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
# ---> Dreamweaver
|
||||
# DW Dreamweaver added files
|
||||
_notes
|
||||
_compareTemp
|
||||
configs/
|
||||
dwsync.xml
|
||||
dw_php_codehinting.config
|
||||
*.mno
|
||||
|
||||
# ---> Android
|
||||
# Gradle files
|
||||
.gradle/
|
||||
build/
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
|
||||
# Log/OS Files
|
||||
*.log
|
||||
|
||||
# Android Studio generated files and folders
|
||||
captures/
|
||||
.externalNativeBuild/
|
||||
.cxx/
|
||||
*.apk
|
||||
output.json
|
||||
|
||||
# IntelliJ
|
||||
*.iml
|
||||
.idea/
|
||||
misc.xml
|
||||
deploymentTargetDropDown.xml
|
||||
render.experimental.xml
|
||||
|
||||
# Keystore files
|
||||
*.jks
|
||||
*.keystore
|
||||
|
||||
# Google Services (e.g. APIs or Firebase)
|
||||
google-services.json
|
||||
|
||||
# Android Profiling
|
||||
*.hprof
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
# 🌍 Save the Island - Progetto per la Giornata della Terra
|
||||
|
||||
**Save the Island** è un videogioco web in 3D nato per sensibilizzare gli utenti sulla gestione dei rifiuti e l'importanza del riciclo. Il progetto adotta un'architettura Fullstack basata su **XAMPP** per la gestione di una classifica globale e il salvataggio dei record.
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Architettura Tecnica
|
||||
* **Entrypoint:** `index.php` (Pagina di atterraggio con trailer e controlli).
|
||||
* **Backend:** PHP + MySQL (XAMPP) per la gestione del database e dei punteggi.
|
||||
* **Frontend:** **Three.js** (motore 3D), JavaScript (ES6+), CSS3.
|
||||
* **Trasferimento Dati:** Utilizzo di Cookie temporanei o `localStorage` per mantenere i dati tra le diverse fasi di gioco.
|
||||
|
||||
---
|
||||
|
||||
## 🗺️ Roadmap di Sviluppo
|
||||
|
||||
### 🚩 Milestone Generali (Nucleo del Progetto)
|
||||
- [⚠️] **Design UI & Mockup:** Finalizzazione degli asset grafici (basati sullo schema Draw.io).
|
||||
- [❌] **Configurazione Database:** Creazione delle tabelle `classifica` e `records` su MySQL.
|
||||
- [❌] **Logica Entrypoint:** Sviluppo di `index.php` con video di sfondo e overlay dei comandi.
|
||||
- [⚠️] **Sistema di Trasferimento:** Implementazione logica per il passaggio dei dati dalla Fase 1 alla Fase 2.
|
||||
- [❌] **Gestione Impostazioni:** Pannello per regolare il volume e inserire il nome della squadra (servira' nella classifica).
|
||||
- [❌] **Pagina Dinamica:** Creazione della classifica in PHP con recupero dati in tempo reale.
|
||||
|
||||
### 🏝️ 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.
|
||||
- [✅] **Interfaccia (HUD):** Overlay con Timer (60s), contatore rifiuti e disattivazione della pausa.
|
||||
- [⚠️] **Schermata di caricamento:** Schermata di caricamento iniziale
|
||||
- [⚠️] **Schermata iniziale:** Schermata iniziale
|
||||
- [❌] **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.
|
||||
- [❌] **Motore di Smistamento:** Logica di convalida (Rifiuto ↔️ Bidone corretto).
|
||||
- [❌] **Interfaccia Utente:** Layout con i 6 bidoni (Plastica, Umido, Indifferenziata, Vetro, Carta, Alluminio).
|
||||
- [❌] **Gestione Input:** Meccanica di interazione tramite Drag & Drop o selezione rapida.
|
||||
- [❌] **Feedback Visivo:** Effetti sonori e visivi per risposte corrette o errate.
|
||||
|
||||
### 🏆 Fase Finale: Classifica & Record
|
||||
- [❌] **Calcolo Punteggio:** Elaborazione dei risultati finali e calcolo dei bonus velocità.
|
||||
- [❌] **Verifica Record:** Confronto del punteggio con il record personale salvato.
|
||||
- [❌] **Classifica Globale:** Script PHP per l'invio e la visualizzazione della Top 10 dal database.
|
||||
- [❌] **Schermata Game Over:** Riepilogo statistiche e opzione per riavviare la partita.
|
||||
|
||||
---
|
||||
|
||||
## 📂 Struttura delle Cartelle
|
||||
* `/assets` - Modelli 3D, video loop, texture dei rifiuti e icone dei bidoni.
|
||||
* `/css` - Fogli di stile per i menu e l'interfaccia di gioco (HUD).
|
||||
* `/js` - Logica core e rendering (Three.js, stage1.js, stage2.js).
|
||||
* `/php` - Script per il backend (`db_connect.php`, `save_score.php`).
|
||||
* `/games` - Pagine HTML dedicate alle sessioni di gioco (`fase1.html`, `fase2.html`).
|
||||
* `index.php` - Homepage e controller principale del progetto.
|
||||
@@ -0,0 +1,57 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Semplice Island FPS</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- SCHERMATA DI CARICAMENTO -->
|
||||
<div id="loading-screen" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: #000; display: flex; flex-direction: column; align-items: center; justify-content: center; z-index: 9999;">
|
||||
<div style="width: 80%; max-width: 600px; background: #222; height: 20px; border-radius: 10px; overflow: hidden; margin-bottom: 10px;">
|
||||
<div id="loading-bar" style="width: 0%; height: 100%; background: #4CAF50; transition: width 0.3s;"></div>
|
||||
</div>
|
||||
<div id="loading-text" style="color: #fff; font-family: monospace; font-size: 14px;">Loading...</div>
|
||||
</div>
|
||||
|
||||
<!-- SCHERMATA INIZIALE -->
|
||||
<div id="start">
|
||||
<video autoplay muted loop id="bg-video">
|
||||
<source src="./video/background.mp4" type="video/mp4">
|
||||
</video>
|
||||
<div class="overlay"></div>
|
||||
<div class="content">
|
||||
<div class="top-section">
|
||||
<img src="./img/titolo.png" alt="titolo" class="main-title" />
|
||||
</div>
|
||||
<div class="container bottom-section">
|
||||
<div class="item left">
|
||||
<div class="controls-box">
|
||||
<h3>Controlli</h3>
|
||||
<p><kbd>W</kbd><kbd>A</kbd><kbd>S</kbd><kbd>D</kbd> Movimento</p>
|
||||
<p><kbd>Mouse</kbd> Camera</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item center">
|
||||
<span class="pulse-text">Clicca per giocare</span>
|
||||
</div>
|
||||
<div class="item right"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- INTERFACCIA GRAFICA DEL GIOCO -->
|
||||
<div id="punti">Rifiuti: <span id="score">0</span></div>
|
||||
<div id="tempo">Tempo: <span id="minuti">XX</span>:<span id="secondi">XX</span></div>
|
||||
<div id="crosshair">+</div>
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
"three": "https://unpkg.com/three@0.160.0/build/three.module.js",
|
||||
"three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script type="module" src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
TODO:
|
||||
@@ -0,0 +1,58 @@
|
||||
<mxfile host="simonez-cloud.ddns.net" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36" version="29.5.2">
|
||||
<diagram name="Page-1" id="i5RDAsyKeYJ5rP6QEPLP">
|
||||
<mxGraphModel dx="1026" dy="507" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="hKNONnDbWQ7ccftNSymI-2" parent="1" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#dae8fc;strokeColor=#6c8ebf;" value="" vertex="1">
|
||||
<mxGeometry height="440" width="680" x="80" y="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="hKNONnDbWQ7ccftNSymI-5" parent="1" style="ellipse;whiteSpace=wrap;html=1;fillColor=#fff2cc;strokeColor=#d6b656;" value="" vertex="1">
|
||||
<mxGeometry height="370" width="440" x="190" y="70" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="hKNONnDbWQ7ccftNSymI-3" parent="1" style="ellipse;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" value="" vertex="1">
|
||||
<mxGeometry height="340" width="400" x="220" y="80" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="hKNONnDbWQ7ccftNSymI-4" parent="1" style="ellipse;whiteSpace=wrap;html=1;fillColor=#AEBEAD;strokeColor=#567441;" value="" vertex="1">
|
||||
<mxGeometry height="90" width="134" x="400" y="190" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="hKNONnDbWQ7ccftNSymI-6" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="N° rifiuti" vertex="1">
|
||||
<mxGeometry height="50" width="90" x="80" y="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="hKNONnDbWQ7ccftNSymI-7" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="tempo" vertex="1">
|
||||
<mxGeometry height="50" width="70" x="690" y="40" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="hKNONnDbWQ7ccftNSymI-8" parent="1" style="rounded=0;whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;fontColor=#333333;" value="" vertex="1">
|
||||
<mxGeometry height="440" width="680" x="80" y="600" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="hKNONnDbWQ7ccftNSymI-9" parent="1" style="whiteSpace=wrap;html=1;aspect=fixed;" value="plastica" vertex="1">
|
||||
<mxGeometry height="110" width="110" x="100" y="810" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="hKNONnDbWQ7ccftNSymI-10" parent="1" style="whiteSpace=wrap;html=1;aspect=fixed;" value="umido" vertex="1">
|
||||
<mxGeometry height="110" width="110" x="230" y="810" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="hKNONnDbWQ7ccftNSymI-11" parent="1" style="whiteSpace=wrap;html=1;aspect=fixed;" value="indifferenziata" vertex="1">
|
||||
<mxGeometry height="110" width="110" x="365" y="810" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="hKNONnDbWQ7ccftNSymI-12" parent="1" style="whiteSpace=wrap;html=1;aspect=fixed;" value="vetro" vertex="1">
|
||||
<mxGeometry height="110" width="110" x="500" y="810" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="hKNONnDbWQ7ccftNSymI-13" parent="1" style="whiteSpace=wrap;html=1;aspect=fixed;" value="carta" vertex="1">
|
||||
<mxGeometry height="110" width="110" x="630" y="810" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="hKNONnDbWQ7ccftNSymI-14" parent="1" style="whiteSpace=wrap;html=1;aspect=fixed;" value="aulliminio" vertex="1">
|
||||
<mxGeometry height="80" width="80" x="115" y="940" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="hKNONnDbWQ7ccftNSymI-15" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="tempo" vertex="1">
|
||||
<mxGeometry height="30" width="60" x="390" y="610" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="hKNONnDbWQ7ccftNSymI-16" parent="1" style="whiteSpace=wrap;html=1;aspect=fixed;" value="immagine rifiuto" vertex="1">
|
||||
<mxGeometry height="120" width="120" x="360" y="650" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="hKNONnDbWQ7ccftNSymI-17" parent="1" style="text;html=1;whiteSpace=wrap;strokeColor=none;fillColor=none;align=center;verticalAlign=middle;rounded=0;" value="punteggio" vertex="1">
|
||||
<mxGeometry height="30" width="60" x="90" y="610" as="geometry" />
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
||||
|
After Width: | Height: | Size: 59 KiB |
@@ -0,0 +1,8 @@
|
||||
Nome gioco: Save the Island
|
||||
|
||||
posizioni: 20 posizioni predefinite scelte random
|
||||
tempo 1° fase: 1m
|
||||
tempo 2° fase: 5s * punteggio
|
||||
schermata iniziale: click + controlli + bckg trailer (loop video) + titolo
|
||||
schermata intermedia: istruzioni
|
||||
schermata finale: punteggio finale + record + classifica(?)
|
||||
|
After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Test Punti</title>
|
||||
</head>
|
||||
<body>
|
||||
<p>Punti: <span id="punti">0</span></p>
|
||||
<script>
|
||||
document.getElementById("punti").innerHTML = localStorage.getItem("punteggio");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,331 @@
|
||||
import * as THREE from 'three';
|
||||
import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js';
|
||||
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
||||
|
||||
// 1a. INIZIALIZZAZIONE CARICAMENTO ASSETS
|
||||
const manager = new THREE.LoadingManager();
|
||||
manager.onStart = function (url, itemsLoaded, itemsTotal) {
|
||||
document.getElementById("loading-text").innerHTML = `Inizio caricamento: ${url}`;
|
||||
console.log(`Inizio caricamento: ${url}`);
|
||||
};
|
||||
manager.onLoad = function () {
|
||||
document.getElementById('loading-screen').style.display = 'none';
|
||||
animate(); // avvia il rendering
|
||||
};
|
||||
manager.onProgress = function (url, itemsLoaded, itemsTotal) {
|
||||
// Calcola la percentuale
|
||||
const percent = (itemsLoaded / itemsTotal) * 100;
|
||||
document.getElementById('loading-bar').style.width = percent + '%';
|
||||
|
||||
// Aggiorna il testo
|
||||
document.getElementById("loading-text").innerHTML = `Caricamento: ${itemsLoaded} di ${itemsTotal} - ${url}`;
|
||||
console.log(`Caricamento: ${itemsLoaded} di ${itemsTotal} - ${url}`);
|
||||
};
|
||||
manager.onError = function (url) {
|
||||
document.getElementById("loading-text").innerHTML = `Errore nel caricamento: ${url}`;
|
||||
console.log(`Errore nel caricamento: ${url}`);
|
||||
};
|
||||
|
||||
// 1b. 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
|
||||
window.totalTime;
|
||||
class PausableTimer {
|
||||
constructor(callback, delay) {
|
||||
this.callback = callback;
|
||||
this.delay = delay; // salvi il delay originale
|
||||
this.remaining = delay;
|
||||
this.timerId = null;
|
||||
this.start = null;
|
||||
this.loop = null;
|
||||
this.cont = 0;
|
||||
window.totalTime = delay / 1000;
|
||||
}
|
||||
pause() {
|
||||
if (this.timerId) {
|
||||
clearTimeout(this.timerId);
|
||||
this.timerId = null;
|
||||
// Calcola il tempo passato
|
||||
this.remaining -= Date.now() - this.start;
|
||||
}
|
||||
if (this.loop) {
|
||||
clearInterval(this.loop);
|
||||
this.loop = null;
|
||||
}
|
||||
console.log("pausa");
|
||||
}
|
||||
resume() {
|
||||
console.log("resume");
|
||||
if (this.timerId) return; // già in esecuzione
|
||||
if (this.remaining > 0) {
|
||||
this.start = Date.now();
|
||||
this.timerId = setTimeout(() => {
|
||||
this.pause();
|
||||
this.callback();
|
||||
}, this.remaining);
|
||||
|
||||
// Se il ciclo di aggiornamento non è già attivo, avvialo
|
||||
if (!this.loop) {
|
||||
this.loop = setInterval(() => {
|
||||
this.cont++;
|
||||
// Aggiorna il timer visualizzato
|
||||
document.getElementById("minuti").innerHTML = String(Math.floor((window.totalTime - this.cont) / 60)).padStart(2, '0');
|
||||
document.getElementById("secondi").innerHTML = String((window.totalTime - this.cont) % 60).padStart(2, '0');
|
||||
console.log(`${document.getElementById("minuti").innerHTML}:${document.getElementById("secondi").innerHTML}`);
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const timer = new PausableTimer(() => {
|
||||
localStorage.setItem("punteggio", document.getElementById("score").innerHTML.trim());
|
||||
window.location = "punteggio.html";
|
||||
}, 60000)
|
||||
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() {
|
||||
timer.pause();
|
||||
document.getElementById('start').style.display = 'flex';
|
||||
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();
|
||||
lock();
|
||||
});
|
||||
controls.addEventListener('lock', () => {
|
||||
lock();
|
||||
timer.resume();
|
||||
});
|
||||
controls.addEventListener('unlock', () => { unlock(); });
|
||||
|
||||
// 4. GESTIONE OGGETTI DI SCENA (20 rifiuti + 6 alberi)
|
||||
const trashArray = [];
|
||||
let score = 0;
|
||||
const loader = new GLTFLoader(manager);
|
||||
|
||||
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 arrow = false;
|
||||
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 = false; // 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 (arrow) {
|
||||
if (keys['ArrowUp']) controls.moveForward(0.15);
|
||||
if (keys['ArrowDown']) controls.moveForward(-0.15);
|
||||
if (keys['ArrowLeft']) controls.moveRight(-0.15);
|
||||
if (keys['ArrowRight']) controls.moveRight(0.15);
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
document.getElementById("minuti").innerHTML = String(Math.floor(window.totalTime / 60)).padStart(2, '0');
|
||||
document.getElementById("secondi").innerHTML = String(window.totalTime % 60).padStart(2, '0');
|
||||
@@ -0,0 +1,178 @@
|
||||
:root {
|
||||
--top: 10px;
|
||||
--border: 20px;
|
||||
--text: 1.5rem;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
#punti {
|
||||
position: absolute;
|
||||
top: var(--top);
|
||||
left: var(--border);
|
||||
color: white;
|
||||
font-size: var(--text);
|
||||
pointer-events: none;
|
||||
display: none;
|
||||
}
|
||||
#tempo {
|
||||
position: absolute;
|
||||
top: var(--top);
|
||||
right: var(--border);
|
||||
color: white;
|
||||
font-size: var(--text);
|
||||
pointer-events: none;
|
||||
display: none;
|
||||
}
|
||||
#punti, #tempo {
|
||||
position: absolute;
|
||||
top: var(--top);
|
||||
color: white;
|
||||
font-size: var(--text);
|
||||
pointer-events: none;
|
||||
display: none;
|
||||
/* Aggiungi questa riga */
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.7);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Cursore */
|
||||
#crosshair {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: white;
|
||||
font-size: 24px;
|
||||
font-family: sans-serif;
|
||||
pointer-events: none;
|
||||
display: none; /* Sarà visualizzato solo durante il gioco */
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* Schermata iniziale del gioco */
|
||||
#start {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
color: white;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
/* Queste 4 righe garantiscono la centratura totale */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 100; /* Assicurati che sia sopra al renderer di Three.js */
|
||||
}
|
||||
|
||||
/* Video a tutto schermo */
|
||||
#bg-video {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
object-fit: cover; /* Riempie lo schermo senza deformare */
|
||||
}
|
||||
|
||||
.overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
z-index: 0;
|
||||
pointer-events: none; /* Il click attraversa l'overlay e arriva a #start */
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
height: 100%;
|
||||
width: 100%; /* Aggiunto */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Divisione 50% Sopra */
|
||||
.top-section {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.main-title {
|
||||
max-width: 80%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/* Divisione 50% Sotto */
|
||||
.bottom-section {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%; /* Forza l'espansione orizzontale */
|
||||
align-items: flex-start;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.item {
|
||||
/* flex: 1 0 33%; significa: cresci, non restringerti, base 33% */
|
||||
flex: 1 0 33.33%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Styling dei Controlli (KBD) */
|
||||
.controls-box {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.controls-box h3 {
|
||||
margin-bottom: 10px;
|
||||
font-size: 1.2rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.controls-box h3, .controls-box p, .pulse-text {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
kbd {
|
||||
background-color: #eee;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #b4b4b4;
|
||||
box-shadow: 0 1px 1px rgba(0,0,0,0.2), 0 2px 0 0 rgba(255,255,255,0.7) inset;
|
||||
color: #333;
|
||||
display: inline-block;
|
||||
font-size: 0.85em;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
padding: 2px 4px;
|
||||
white-space: nowrap;
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
/* Animazione per "Clicca per giocare" */
|
||||
.pulse-text {
|
||||
font-size: 1.5rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% { opacity: 1; }
|
||||
50% { opacity: 0.4; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
|
After Width: | Height: | Size: 16 MiB |
|
After Width: | Height: | Size: 16 MiB |
|
After Width: | Height: | Size: 4.7 MiB |
|
After Width: | Height: | Size: 14 MiB |
|
After Width: | Height: | Size: 13 MiB |