Spaces:
Running
Running
File size: 12,080 Bytes
9b68ef7 d20b9cd 9b68ef7 d20b9cd 9b68ef7 d20b9cd 9b68ef7 d20b9cd 9b68ef7 d20b9cd 9b68ef7 d20b9cd 9b68ef7 d20b9cd 9b68ef7 d20b9cd 9b68ef7 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>Fruit Fall 3D</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<style>
body { margin: 0; overflow: hidden; font-family: Arial, sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; background-color: #f0f0f0; }
#gameCanvas { display: block; }
#ui-container {
position: absolute;
top: 10px;
left: 10px;
padding: 10px;
background-color: rgba(0,0,0,0.5);
color: white;
border-radius: 5px;
z-index: 100;
}
#score { font-size: 1.5em; margin-bottom: 5px; }
#start-button, #reset-button {
padding: 8px 15px;
font-size: 1em;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 3px;
cursor: pointer;
margin-top: 5px;
}
#reset-button { background-color: #f44336; display: none;}
#game-over-message {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
background-color: rgba(0,0,0,0.7);
padding: 20px;
border-radius: 10px;
font-size: 2em;
text-align: center;
z-index: 101;
display: none; /* Hidden by default */
}
</style>
</head>
<body>
<div id="ui-container">
<div id="score">Score: 0</div>
<button id="start-button">Start Game</button>
<button id="reset-button">Play Again</button>
</div>
<div id="game-over-message">
Game Over!<br>
<span id="final-score-message"></span>
</div>
<canvas id="gameCanvas"></canvas>
<script>
let scene, camera, renderer;
let fruits = [];
let score = 0;
let gameActive = false;
let raycaster, mouse; // For click detection
let audioContext;
let gameTimer, timeLeft;
const GAME_DURATION = 30; // seconds
const scoreElement = document.getElementById('score');
const startButton = document.getElementById('start-button');
const resetButton = document.getElementById('reset-button');
const gameOverMessageDiv = document.getElementById('game-over-message');
const finalScoreMessageSpan = document.getElementById('final-score-message');
const fruitTypes = [
{ name: 'Mango', color: 0xFFBF00, size: 0.5, points: 10 },
{ name: 'Apple', color: 0xFF0000, size: 0.4, points: 10 },
{ name: 'Banana', color: 0xFFFF00, size: 0.35, points: 15 }, // Represented as sphere
{ name: 'Grapes', color: 0x800080, size: 0.3, points: 20 },
{ name: 'Watermelon', color: 0x00FF00, size: 0.7, points: 5 }
];
function initAudio() {
if (!audioContext) {
audioContext = new (window.AudioContext || window.webkitAudioContext)();
}
}
function playSound(type = 'click', freq = 440, duration = 0.05) {
if (!audioContext) return;
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
oscillator.type = (type === 'miss') ? 'sawtooth' : 'triangle';
oscillator.frequency.setValueAtTime(freq, audioContext.currentTime);
gainNode.gain.setValueAtTime(0.15, audioContext.currentTime); // Lowered volume
gainNode.gain.exponentialRampToValueAtTime(0.001, audioContext.currentTime + duration);
oscillator.start();
oscillator.stop(audioContext.currentTime + duration);
}
function init() {
// Scene
scene = new THREE.Scene();
scene.background = new THREE.Color(0x87CEEB); // Sky blue
// Camera
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 2, 7); // Positioned to see the falling fruits well
camera.lookAt(0, 0, 0);
// Renderer
renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('gameCanvas'), antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
// Lighting
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(5, 10, 7);
directionalLight.castShadow = true;
scene.add(directionalLight);
// Ground (optional, for visual reference)
const groundGeometry = new THREE.PlaneGeometry(20, 20);
const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x228B22, side: THREE.DoubleSide }); // Forest green
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2;
ground.position.y = -5; // Fruits fall past this
ground.receiveShadow = true;
scene.add(ground);
// Raycaster for mouse clicks
raycaster = new THREE.Raycaster();
mouse = new THREE.Vector2();
// Event Listeners
window.addEventListener('resize', onWindowResize, false);
renderer.domElement.addEventListener('click', onClick, false); // For desktop
renderer.domElement.addEventListener('touchstart', onTouch, false); // For mobile
startButton.addEventListener('click', startGame);
resetButton.addEventListener('click', startGame); // Reset button also starts the game
}
function startGame() {
initAudio();
score = 0;
timeLeft = GAME_DURATION;
updateScoreDisplay();
gameActive = true;
fruits.forEach(fruit => scene.remove(fruit.mesh)); // Clear existing fruits
fruits = [];
startButton.style.display = 'none';
resetButton.style.display = 'none';
gameOverMessageDiv.style.display = 'none';
if (gameTimer) clearInterval(gameTimer);
gameTimer = setInterval(() => {
timeLeft--;
// Optionally display timer: scoreElement.textContent = `Score: ${score} | Time: ${timeLeft}`;
if (timeLeft <= 0) {
endGame();
}
}, 1000);
spawnFruitLoop(); // Start spawning fruits
if (!renderer.xr.isPresenting) animate(); // Ensure animate isn't called twice if in XR
}
function endGame() {
gameActive = false;
clearInterval(gameTimer);
clearTimeout(fruitSpawnTimeout); // Stop spawning new fruits
finalScoreMessageSpan.textContent = `Your Score: ${score}`;
gameOverMessageDiv.style.display = 'flex';
resetButton.style.display = 'inline-block';
}
let fruitSpawnTimeout;
function spawnFruitLoop() {
if (!gameActive) return;
createFruit();
const spawnInterval = Math.random() * 1500 + 500; // 0.5 to 2 seconds
fruitSpawnTimeout = setTimeout(spawnFruitLoop, spawnInterval);
}
function createFruit() {
if (!gameActive) return;
const type = fruitTypes[Math.floor(Math.random() * fruitTypes.length)];
const geometry = new THREE.SphereGeometry(type.size, 16, 12);
const material = new THREE.MeshStandardMaterial({ color: type.color, roughness: 0.5, metalness: 0.1 });
const fruitMesh = new THREE.Mesh(geometry, material);
fruitMesh.castShadow = true;
// Random spawn position at the top
fruitMesh.position.x = (Math.random() - 0.5) * 10; // Range: -5 to 5
fruitMesh.position.y = 7 + Math.random() * 3; // Start above camera view
fruitMesh.position.z = (Math.random() - 0.5) * 4; // Some depth variation
fruitMesh.userData = { type: type.name, points: type.points, fallSpeed: 0.02 + Math.random() * 0.03 };
scene.add(fruitMesh);
fruits.push({ mesh: fruitMesh, data: fruitMesh.userData });
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function handleInteraction(clientX, clientY) {
if (!gameActive) return;
mouse.x = (clientX / window.innerWidth) * 2 - 1;
mouse.y = -(clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const fruitMeshes = fruits.map(f => f.mesh);
const intersects = raycaster.intersectObjects(fruitMeshes);
if (intersects.length > 0) {
const clickedFruitMesh = intersects[0].object;
const fruitIndex = fruits.findIndex(f => f.mesh === clickedFruitMesh);
if (fruitIndex !== -1) {
const fruitData = fruits[fruitIndex].data;
score += fruitData.points;
updateScoreDisplay();
playSound('click', 300 + Math.random()*200);
// Simple "pop" effect: shrink and remove
// More complex particle effects are possible but add overhead
clickedFruitMesh.scale.set(0.1,0.1,0.1); // Visually shrink
setTimeout(() => { // Remove after slight delay
scene.remove(clickedFruitMesh);
}, 50);
fruits.splice(fruitIndex, 1);
}
}
}
function onClick(event) {
handleInteraction(event.clientX, event.clientY);
}
function onTouch(event) {
if (event.touches.length > 0) {
handleInteraction(event.touches[0].clientX, event.touches[0].clientY);
}
}
function updateScoreDisplay() {
scoreElement.textContent = `Score: ${score} | Time: ${timeLeft}`;
}
function animate() {
if (!gameActive && timeLeft <= 0) { // If game ended, stop animation loop
return;
}
requestAnimationFrame(animate);
if (gameActive) {
// Fruit falling logic
for (let i = fruits.length - 1; i >= 0; i--) {
const fruitObj = fruits[i];
fruitObj.mesh.position.y -= fruitObj.data.fallSpeed;
fruitObj.mesh.rotation.x += 0.01;
fruitObj.mesh.rotation.y += 0.01;
// Remove fruit if it falls below a certain point (miss)
if (fruitObj.mesh.position.y < -6) { // Below the ground plane
scene.remove(fruitObj.mesh);
fruits.splice(i, 1);
// playSound('miss', 150); // Optional miss sound
// score -= 5; // Optional penalty
updateScoreDisplay();
}
}
}
renderer.render(scene, camera);
}
// --- Start ---
init();
// startGame(); // Initial call to animate if not waiting for button
</script>
</body>
</html> |