404 lines
No EOL
14 KiB
HTML
404 lines
No EOL
14 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>3D Coin Collector Game</title>
|
|
<style>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
body {
|
|
overflow: hidden;
|
|
font-family: 'Arial', sans-serif;
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
}
|
|
#gameCanvas {
|
|
display: block;
|
|
}
|
|
#ui {
|
|
position: absolute;
|
|
top: 20px;
|
|
left: 20px;
|
|
color: white;
|
|
font-size: 24px;
|
|
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
|
|
z-index: 100;
|
|
}
|
|
#gameOver {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
background: rgba(0,0,0,0.8);
|
|
padding: 40px;
|
|
border-radius: 20px;
|
|
text-align: center;
|
|
color: white;
|
|
display: none;
|
|
z-index: 200;
|
|
}
|
|
#gameOver h1 {
|
|
font-size: 48px;
|
|
margin-bottom: 20px;
|
|
color: #ff6b6b;
|
|
}
|
|
#gameOver p {
|
|
font-size: 24px;
|
|
margin-bottom: 30px;
|
|
}
|
|
#restartBtn {
|
|
background: #4CAF50;
|
|
border: none;
|
|
color: white;
|
|
padding: 15px 40px;
|
|
font-size: 20px;
|
|
border-radius: 10px;
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
}
|
|
#restartBtn:hover {
|
|
background: #45a049;
|
|
transform: scale(1.1);
|
|
}
|
|
#instructions {
|
|
position: absolute;
|
|
bottom: 20px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
color: white;
|
|
text-align: center;
|
|
text-shadow: 2px 2px 4px rgba(0,0,0,0.5);
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="ui">
|
|
<div>Score: <span id="score">0</span></div>
|
|
<div>Health: <span id="health">100</span></div>
|
|
</div>
|
|
|
|
<div id="gameOver">
|
|
<h1>Game Over!</h1>
|
|
<p>Final Score: <span id="finalScore">0</span></p>
|
|
<button id="restartBtn">Play Again</button>
|
|
</div>
|
|
|
|
<div id="instructions">
|
|
<p><strong>Controls:</strong> WASD or Arrow Keys to move | Collect yellow coins | Avoid red obstacles!</p>
|
|
</div>
|
|
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
|
|
<script>
|
|
// Game variables
|
|
let scene, camera, renderer;
|
|
let player;
|
|
let coins = [];
|
|
let obstacles = [];
|
|
let score = 0;
|
|
let health = 100;
|
|
let gameActive = true;
|
|
|
|
// Movement
|
|
const keys = {};
|
|
const playerSpeed = 0.15;
|
|
const groundSize = 30;
|
|
|
|
// Initialize the game
|
|
function init() {
|
|
// Scene setup
|
|
scene = new THREE.Scene();
|
|
scene.background = new THREE.Color(0x87CEEB);
|
|
scene.fog = new THREE.Fog(0x87CEEB, 0, 50);
|
|
|
|
// Camera setup
|
|
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
|
|
camera.position.set(0, 10, 15);
|
|
camera.lookAt(0, 0, 0);
|
|
|
|
// Renderer setup
|
|
renderer = new THREE.WebGLRenderer({ antialias: true });
|
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
renderer.shadowMap.enabled = true;
|
|
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
|
document.body.appendChild(renderer.domElement);
|
|
|
|
// Lights
|
|
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
|
|
scene.add(ambientLight);
|
|
|
|
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
|
|
directionalLight.position.set(10, 20, 10);
|
|
directionalLight.castShadow = true;
|
|
directionalLight.shadow.mapSize.width = 2048;
|
|
directionalLight.shadow.mapSize.height = 2048;
|
|
directionalLight.shadow.camera.left = -20;
|
|
directionalLight.shadow.camera.right = 20;
|
|
directionalLight.shadow.camera.top = 20;
|
|
directionalLight.shadow.camera.bottom = -20;
|
|
directionalLight.shadow.camera.near = 0.5;
|
|
directionalLight.shadow.camera.far = 50;
|
|
scene.add(directionalLight);
|
|
|
|
// Ground
|
|
const groundGeometry = new THREE.PlaneGeometry(groundSize, groundSize);
|
|
const groundMaterial = new THREE.MeshStandardMaterial({
|
|
color: 0x90EE90,
|
|
roughness: 0.8
|
|
});
|
|
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
|
|
ground.rotation.x = -Math.PI / 2;
|
|
ground.receiveShadow = true;
|
|
scene.add(ground);
|
|
|
|
// Grid helper
|
|
const gridHelper = new THREE.GridHelper(groundSize, 20, 0x000000, 0x000000);
|
|
gridHelper.material.opacity = 0.2;
|
|
gridHelper.material.transparent = true;
|
|
scene.add(gridHelper);
|
|
|
|
// Player
|
|
const playerGeometry = new THREE.BoxGeometry(1, 1, 1);
|
|
const playerMaterial = new THREE.MeshStandardMaterial({
|
|
color: 0x4169E1,
|
|
metalness: 0.3,
|
|
roughness: 0.4
|
|
});
|
|
player = new THREE.Mesh(playerGeometry, playerMaterial);
|
|
player.position.y = 0.5;
|
|
player.castShadow = true;
|
|
player.receiveShadow = true;
|
|
scene.add(player);
|
|
|
|
// Create initial game objects
|
|
createCoins(10);
|
|
createObstacles(8);
|
|
|
|
// Event listeners
|
|
window.addEventListener('keydown', (e) => keys[e.key.toLowerCase()] = true);
|
|
window.addEventListener('keyup', (e) => keys[e.key.toLowerCase()] = false);
|
|
window.addEventListener('resize', onWindowResize);
|
|
document.getElementById('restartBtn').addEventListener('click', restartGame);
|
|
|
|
// Start animation loop
|
|
animate();
|
|
}
|
|
|
|
// Create coins
|
|
function createCoins(count) {
|
|
for (let i = 0; i < count; i++) {
|
|
const coinGeometry = new THREE.SphereGeometry(0.3, 16, 16);
|
|
const coinMaterial = new THREE.MeshStandardMaterial({
|
|
color: 0xFFD700,
|
|
metalness: 0.8,
|
|
roughness: 0.2,
|
|
emissive: 0xFFD700,
|
|
emissiveIntensity: 0.3
|
|
});
|
|
const coin = new THREE.Mesh(coinGeometry, coinMaterial);
|
|
|
|
// Random position
|
|
coin.position.x = (Math.random() - 0.5) * (groundSize - 4);
|
|
coin.position.z = (Math.random() - 0.5) * (groundSize - 4);
|
|
coin.position.y = 0.5;
|
|
|
|
coin.castShadow = true;
|
|
coin.receiveShadow = true;
|
|
coin.userData.rotationSpeed = 0.02;
|
|
|
|
scene.add(coin);
|
|
coins.push(coin);
|
|
}
|
|
}
|
|
|
|
// Create obstacles
|
|
function createObstacles(count) {
|
|
for (let i = 0; i < count; i++) {
|
|
const obstacleGeometry = new THREE.BoxGeometry(1.5, 2, 1.5);
|
|
const obstacleMaterial = new THREE.MeshStandardMaterial({
|
|
color: 0xFF4500,
|
|
metalness: 0.3,
|
|
roughness: 0.7
|
|
});
|
|
const obstacle = new THREE.Mesh(obstacleGeometry, obstacleMaterial);
|
|
|
|
// Random position (ensure not too close to player start)
|
|
let posX, posZ;
|
|
do {
|
|
posX = (Math.random() - 0.5) * (groundSize - 4);
|
|
posZ = (Math.random() - 0.5) * (groundSize - 4);
|
|
} while (Math.abs(posX) < 3 && Math.abs(posZ) < 3);
|
|
|
|
obstacle.position.x = posX;
|
|
obstacle.position.z = posZ;
|
|
obstacle.position.y = 1;
|
|
|
|
obstacle.castShadow = true;
|
|
obstacle.receiveShadow = true;
|
|
|
|
// Random movement pattern
|
|
obstacle.userData.direction = new THREE.Vector3(
|
|
(Math.random() - 0.5) * 0.05,
|
|
0,
|
|
(Math.random() - 0.5) * 0.05
|
|
);
|
|
|
|
scene.add(obstacle);
|
|
obstacles.push(obstacle);
|
|
}
|
|
}
|
|
|
|
// Update player movement
|
|
function updatePlayer() {
|
|
if (!gameActive) return;
|
|
|
|
const moveVector = new THREE.Vector3();
|
|
|
|
if (keys['w'] || keys['arrowup']) moveVector.z -= playerSpeed;
|
|
if (keys['s'] || keys['arrowdown']) moveVector.z += playerSpeed;
|
|
if (keys['a'] || keys['arrowleft']) moveVector.x -= playerSpeed;
|
|
if (keys['d'] || keys['arrowright']) moveVector.x += playerSpeed;
|
|
|
|
// Apply movement
|
|
player.position.add(moveVector);
|
|
|
|
// Boundary checks
|
|
const boundary = groundSize / 2 - 0.5;
|
|
player.position.x = Math.max(-boundary, Math.min(boundary, player.position.x));
|
|
player.position.z = Math.max(-boundary, Math.min(boundary, player.position.z));
|
|
|
|
// Rotate player based on movement
|
|
if (moveVector.length() > 0) {
|
|
player.rotation.y += 0.1;
|
|
}
|
|
}
|
|
|
|
// Update coins
|
|
function updateCoins() {
|
|
coins.forEach((coin, index) => {
|
|
// Rotate coins
|
|
coin.rotation.y += coin.userData.rotationSpeed;
|
|
coin.position.y = 0.5 + Math.sin(Date.now() * 0.003 + index) * 0.2;
|
|
|
|
// Check collision with player
|
|
const distance = player.position.distanceTo(coin.position);
|
|
if (distance < 0.8) {
|
|
// Collect coin
|
|
scene.remove(coin);
|
|
coins.splice(index, 1);
|
|
score += 10;
|
|
updateUI();
|
|
|
|
// Create new coin
|
|
createCoins(1);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Update obstacles
|
|
function updateObstacles() {
|
|
if (!gameActive) return;
|
|
|
|
obstacles.forEach(obstacle => {
|
|
// Move obstacle
|
|
obstacle.position.add(obstacle.userData.direction);
|
|
|
|
// Bounce off boundaries
|
|
const boundary = groundSize / 2 - 1;
|
|
if (Math.abs(obstacle.position.x) > boundary) {
|
|
obstacle.userData.direction.x *= -1;
|
|
}
|
|
if (Math.abs(obstacle.position.z) > boundary) {
|
|
obstacle.userData.direction.z *= -1;
|
|
}
|
|
|
|
// Check collision with player
|
|
const distance = player.position.distanceTo(obstacle.position);
|
|
if (distance < 1.5) {
|
|
health -= 1;
|
|
updateUI();
|
|
|
|
// Push player away (only on X and Z axis, keep Y constant)
|
|
const pushDirection = player.position.clone().sub(obstacle.position);
|
|
pushDirection.y = 0; // Keep player on ground level
|
|
pushDirection.normalize();
|
|
player.position.add(pushDirection.multiplyScalar(0.3));
|
|
player.position.y = 0.5; // Ensure player stays at ground level
|
|
|
|
// Check game over
|
|
if (health <= 0) {
|
|
gameOver();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Update UI
|
|
function updateUI() {
|
|
document.getElementById('score').textContent = score;
|
|
document.getElementById('health').textContent = Math.max(0, health);
|
|
}
|
|
|
|
// Game over
|
|
function gameOver() {
|
|
gameActive = false;
|
|
document.getElementById('finalScore').textContent = score;
|
|
document.getElementById('gameOver').style.display = 'block';
|
|
}
|
|
|
|
// Restart game
|
|
function restartGame() {
|
|
// Remove all coins and obstacles
|
|
coins.forEach(coin => scene.remove(coin));
|
|
obstacles.forEach(obstacle => scene.remove(obstacle));
|
|
coins = [];
|
|
obstacles = [];
|
|
|
|
// Reset player
|
|
player.position.set(0, 0.5, 0);
|
|
player.rotation.y = 0;
|
|
|
|
// Reset game state
|
|
score = 0;
|
|
health = 100;
|
|
gameActive = true;
|
|
|
|
// Recreate game objects
|
|
createCoins(10);
|
|
createObstacles(8);
|
|
|
|
// Hide game over screen
|
|
document.getElementById('gameOver').style.display = 'none';
|
|
updateUI();
|
|
}
|
|
|
|
// Handle window resize
|
|
function onWindowResize() {
|
|
camera.aspect = window.innerWidth / window.innerHeight;
|
|
camera.updateProjectionMatrix();
|
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
}
|
|
|
|
// Animation loop
|
|
function animate() {
|
|
requestAnimationFrame(animate);
|
|
|
|
updatePlayer();
|
|
updateCoins();
|
|
updateObstacles();
|
|
|
|
// Camera follows player
|
|
camera.position.x = player.position.x;
|
|
camera.position.z = player.position.z + 15;
|
|
camera.lookAt(player.position);
|
|
|
|
renderer.render(scene, camera);
|
|
}
|
|
|
|
// Start the game
|
|
init();
|
|
</script>
|
|
</body>
|
|
</html> |