Testreact / index.html
Aleksmorshen's picture
Update index.html
e0245e2 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Doom-Style 3D Shooter</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/PointerLockControls.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Courier New', monospace;
touch-action: none;
}
body {
overflow: hidden;
background: #000;
color: #ff5555;
height: 100vh;
perspective: 1px;
}
#gameContainer {
position: relative;
width: 100%;
height: 100vh;
overflow: hidden;
}
#gameCanvas {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
}
#ui {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 2;
pointer-events: none;
}
#crosshair {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 30px;
height: 30px;
border: 2px solid #ff5555;
border-radius: 50%;
pointer-events: none;
}
#crosshair::before, #crosshair::after {
content: '';
position: absolute;
background: #ff5555;
}
#crosshair::before {
width: 2px;
height: 10px;
top: -12px;
left: 50%;
transform: translateX(-50%);
}
#crosshair::after {
width: 10px;
height: 2px;
top: 50%;
right: -12px;
transform: translateY(-50%);
}
#healthBar {
position: absolute;
bottom: 30px;
left: 30px;
width: 200px;
height: 30px;
background: rgba(0, 0, 0, 0.7);
border: 2px solid #ff5555;
border-radius: 5px;
overflow: hidden;
}
#healthFill {
height: 100%;
width: 100%;
background: linear-gradient(90deg, #ff0000, #ff5555);
transition: width 0.3s;
}
#ammoCounter {
position: absolute;
bottom: 30px;
right: 30px;
font-size: 24px;
color: #ff5555;
text-shadow: 0 0 5px #ff0000;
background: rgba(0, 0, 0, 0.7);
padding: 10px 20px;
border: 2px solid #ff5555;
border-radius: 5px;
}
#score {
position: absolute;
top: 30px;
left: 30px;
font-size: 24px;
color: #ff5555;
text-shadow: 0 0 5px #ff0000;
}
#startScreen {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(rgba(0, 0, 0, 0.8), rgba(20, 0, 0, 0.9)),
url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><rect width="100" height="100" fill="%23110000"/><path d="M0 0L100 100M100 0L0 100" stroke="%23300" stroke-width="1"/></svg>');
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 10;
text-align: center;
}
#title {
font-size: 72px;
margin-bottom: 30px;
text-shadow: 0 0 10px #ff0000, 0 0 20px #ff0000;
letter-spacing: 5px;
}
#subtitle {
font-size: 24px;
margin-bottom: 50px;
color: #ff9999;
}
#startButton {
background: #ff0000;
color: #fff;
border: none;
padding: 15px 50px;
font-size: 24px;
cursor: pointer;
border-radius: 5px;
text-transform: uppercase;
letter-spacing: 3px;
font-weight: bold;
box-shadow: 0 0 15px #ff0000;
transition: all 0.3s;
pointer-events: auto;
}
#startButton:hover {
background: #ff5555;
transform: scale(1.05);
box-shadow: 0 0 25px #ff0000;
}
#gameOverScreen {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.85);
display: none;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 10;
}
#gameOverTitle {
font-size: 60px;
margin-bottom: 20px;
color: #ff0000;
text-shadow: 0 0 10px #ff0000;
}
#finalScore {
font-size: 36px;
margin-bottom: 40px;
color: #ff9999;
}
#restartButton {
background: #ff0000;
color: #fff;
border: none;
padding: 15px 50px;
font-size: 24px;
cursor: pointer;
border-radius: 5px;
text-transform: uppercase;
letter-spacing: 3px;
font-weight: bold;
box-shadow: 0 0 15px #ff0000;
transition: all 0.3s;
}
#restartButton:hover {
background: #ff5555;
transform: scale(1.05);
box-shadow: 0 0 25px #ff0000;
}
#weapon {
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 300px;
height: 200px;
background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 200"><rect x="50" y="100" width="200" height="30" fill="%23333"/><rect x="100" y="50" width="100" height="50" fill="%23444"/><rect x="130" y="30" width="40" height="20" fill="%23555"/><circle cx="150" cy="115" r="15" fill="%23222"/></svg>') no-repeat center bottom;
background-size: contain;
z-index: 3;
transition: transform 0.1s;
}
.hitEffect {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(255, 0, 0, 0.3);
pointer-events: none;
opacity: 0;
z-index: 4;
}
#bloodEffect {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: radial-gradient(circle, rgba(255,0,0,0.7) 0%, rgba(255,0,0,0) 70%);
pointer-events: none;
opacity: 0;
z-index: 5;
}
/* Mobile Controls */
#mobileControls {
position: absolute;
bottom: 20px;
width: 100%;
display: none;
justify-content: space-between;
padding: 0 20px;
z-index: 10;
pointer-events: none;
}
.controlPad {
width: 120px;
height: 120px;
background: rgba(255, 0, 0, 0.3);
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
pointer-events: auto;
}
.controlStick {
width: 50px;
height: 50px;
background: rgba(255, 85, 85, 0.7);
border-radius: 50%;
position: relative;
}
#shootButton {
width: 80px;
height: 80px;
background: rgba(255, 0, 0, 0.5);
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
font-size: 24px;
color: white;
pointer-events: auto;
border: 2px solid #ff5555;
}
#instructions {
position: absolute;
bottom: 160px;
width: 100%;
text-align: center;
color: #ff9999;
font-size: 18px;
padding: 0 20px;
}
@media (max-width: 768px) {
#mobileControls {
display: flex;
}
#instructions {
display: block;
}
#title {
font-size: 48px;
}
#subtitle {
font-size: 18px;
}
}
</style>
</head>
<body>
<div id="gameContainer">
<div id="gameCanvas"></div>
<div id="ui">
<div id="crosshair"></div>
<div id="healthBar">
<div id="healthFill"></div>
</div>
<div id="ammoCounter">AMMO: 30</div>
<div id="score">SCORE: 0</div>
<div id="weapon"></div>
<div class="hitEffect" id="hitEffect"></div>
<div id="bloodEffect"></div>
</div>
<div id="mobileControls">
<div class="controlPad" id="movementPad">
<div class="controlStick" id="movementStick"></div>
</div>
<div id="shootButton">FIRE</div>
</div>
<div id="instructions">Use joystick to move, tap FIRE to shoot</div>
<div id="startScreen">
<h1 id="title">DEMON SLAYER</h1>
<p id="subtitle">Eliminate all demons to survive</p>
<button id="startButton">START MISSION</button>
</div>
<div id="gameOverScreen">
<h1 id="gameOverTitle">MISSION FAILED</h1>
<p id="finalScore">SCORE: 0</p>
<button id="restartButton">TRY AGAIN</button>
</div>
</div>
<script>
// Game variables
let scene, camera, renderer, controls;
let player, enemies = [], bullets = [];
let playerHealth = 100;
let ammo = 30;
let score = 0;
let gameActive = false;
let moveForward = false;
let moveBackward = false;
let moveLeft = false;
let moveRight = false;
let canShoot = true;
let lastShotTime = 0;
let shotCooldown = 300; // ms
let enemySpawnTimer = 0;
let clock = new THREE.Clock();
let isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
// Mobile controls
let movementStickActive = false;
let movementStickPosition = { x: 0, y: 0 };
let movementStickRadius = 35;
// Initialize the game
function init() {
// Create scene
scene = new THREE.Scene();
scene.background = new THREE.Color(0x110000);
scene.fog = new THREE.Fog(0x110000, 10, 50);
// Create camera
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.y = 1.6;
// Create renderer
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
document.getElementById('gameCanvas').appendChild(renderer.domElement);
// Add lighting
const ambientLight = new THREE.AmbientLight(0x404040, 0.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xff5555, 0.8);
directionalLight.position.set(10, 20, 10);
directionalLight.castShadow = true;
scene.add(directionalLight);
// Create floor
const floorGeometry = new THREE.PlaneGeometry(100, 100);
const floorMaterial = new THREE.MeshStandardMaterial({
color: 0x222222,
roughness: 0.8,
metalness: 0.2
});
const floor = new THREE.Mesh(floorGeometry, floorMaterial);
floor.rotation.x = -Math.PI / 2;
floor.receiveShadow = true;
scene.add(floor);
// Create walls
createWalls();
// Create pillars
createPillars();
// Create player
player = new THREE.Object3D();
player.position.set(0, 1.6, 0);
scene.add(player);
player.add(camera);
// Add pointer lock controls for desktop
if (!isMobile) {
controls = new THREE.PointerLockControls(camera, document.body);
}
// Event listeners
if (!isMobile) {
document.addEventListener('keydown', onKeyDown, false);
document.addEventListener('keyup', onKeyUp, false);
document.addEventListener('mousedown', onMouseDown, false);
document.addEventListener('mousemove', onMouseMove, false);
} else {
setupMobileControls();
}
window.addEventListener('resize', onWindowResize, false);
// Start button event
document.getElementById('startButton').addEventListener('click', startGame);
document.getElementById('restartButton').addEventListener('click', restartGame);
// Start animation loop
animate();
}
function createWalls() {
const wallMaterial = new THREE.MeshStandardMaterial({
color: 0x330000,
roughness: 0.9,
metalness: 0.1
});
// Create a simple maze-like structure
const walls = [
// Outer walls
{ x: 0, y: 2, z: -25, width: 50, height: 4, depth: 1 },
{ x: 0, y: 2, z: 25, width: 50, height: 4, depth: 1 },
{ x: -25, y: 2, z: 0, width: 1, height: 4, depth: 50 },
{ x: 25, y: 2, z: 0, width: 1, height: 4, depth: 50 },
// Inner walls
{ x: -10, y: 2, z: -10, width: 1, height: 4, depth: 20 },
{ x: 10, y: 2, z: 10, width: 1, height: 4, depth: 20 },
{ x: 0, y: 2, z: 0, width: 20, height: 4, depth: 1 },
{ x: 0, y: 2, z: -20, width: 20, height: 4, depth: 1 }
];
walls.forEach(wall => {
const wallGeometry = new THREE.BoxGeometry(wall.width, wall.height, wall.depth);
const wallMesh = new THREE.Mesh(wallGeometry, wallMaterial);
wallMesh.position.set(wall.x, wall.y, wall.z);
wallMesh.castShadow = true;
wallMesh.receiveShadow = true;
scene.add(wallMesh);
});
}
function createPillars() {
const pillarMaterial = new THREE.MeshStandardMaterial({
color: 0x440000,
roughness: 0.7,
metalness: 0.3
});
const pillarGeometry = new THREE.CylinderGeometry(1, 1, 4, 16);
const positions = [
{ x: -20, z: -20 },
{ x: 20, z: -20 },
{ x: -20, z: 20 },
{ x: 20, z: 20 },
{ x: 0, z: -15 },
{ x: 0, z: 15 },
{ x: -15, z: 0 },
{ x: 15, z: 0 }
];
positions.forEach(pos => {
const pillar = new THREE.Mesh(pillarGeometry, pillarMaterial);
pillar.position.set(pos.x, 2, pos.z);
pillar.castShadow = true;
pillar.receiveShadow = true;
scene.add(pillar);
});
}
// Create a detailed demon model
function createDemon() {
const demonGroup = new THREE.Group();
// Body
const bodyGeometry = new THREE.SphereGeometry(0.8, 16, 16);
const bodyMaterial = new THREE.MeshStandardMaterial({
color: 0x880000,
roughness: 0.7,
metalness: 0.3
});
const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
body.position.y = 1;
body.castShadow = true;
demonGroup.add(body);
// Head
const headGeometry = new THREE.SphereGeometry(0.6, 16, 16);
const headMaterial = new THREE.MeshStandardMaterial({
color: 0xaa0000,
roughness: 0.7,
metalness: 0.3
});
const head = new THREE.Mesh(headGeometry, headMaterial);
head.position.y = 1.8;
head.castShadow = true;
demonGroup.add(head);
// Horns
const hornGeometry = new THREE.ConeGeometry(0.1, 0.8, 8);
const hornMaterial = new THREE.MeshStandardMaterial({ color: 0x333333 });
const horn1 = new THREE.Mesh(hornGeometry, hornMaterial);
horn1.position.set(-0.2, 2.3, 0);
horn1.rotation.z = Math.PI / 6;
horn1.castShadow = true;
demonGroup.add(horn1);
const horn2 = new THREE.Mesh(hornGeometry, hornMaterial);
horn2.position.set(0.2, 2.3, 0);
horn2.rotation.z = -Math.PI / 6;
horn2.castShadow = true;
demonGroup.add(horn2);
// Eyes
const eyeGeometry = new THREE.SphereGeometry(0.15, 8, 8);
const eyeMaterial = new THREE.MeshStandardMaterial({ color: 0xffff00 });
const eye1 = new THREE.Mesh(eyeGeometry, eyeMaterial);
eye1.position.set(-0.25, 1.9, 0.4);
demonGroup.add(eye1);
const eye2 = new THREE.Mesh(eyeGeometry, eyeMaterial);
eye2.position.set(0.25, 1.9, 0.4);
demonGroup.add(eye2);
// Arms
const armGeometry = new THREE.CylinderGeometry(0.15, 0.15, 1.2, 8);
const armMaterial = new THREE.MeshStandardMaterial({ color: 0x770000 });
const arm1 = new THREE.Mesh(armGeometry, armMaterial);
arm1.position.set(-0.9, 1, 0);
arm1.rotation.z = Math.PI / 4;
arm1.castShadow = true;
demonGroup.add(arm1);
const arm2 = new THREE.Mesh(armGeometry, armMaterial);
arm2.position.set(0.9, 1, 0);
arm2.rotation.z = -Math.PI / 4;
arm2.castShadow = true;
demonGroup.add(arm2);
// Legs
const legGeometry = new THREE.CylinderGeometry(0.2, 0.2, 1, 8);
const legMaterial = new THREE.MeshStandardMaterial({ color: 0x660000 });
const leg1 = new THREE.Mesh(legGeometry, legMaterial);
leg1.position.set(-0.4, 0.3, 0);
leg1.castShadow = true;
demonGroup.add(leg1);
const leg2 = new THREE.Mesh(legGeometry, legMaterial);
leg2.position.set(0.4, 0.3, 0);
leg2.castShadow = true;
demonGroup.add(leg2);
return demonGroup;
}
function spawnEnemy() {
const demon = createDemon();
demon.position.set(
(Math.random() - 0.5) * 40,
0,
(Math.random() - 0.5) * 40
);
demon.rotation.y = Math.random() * Math.PI * 2;
scene.add(demon);
enemies.push({
mesh: demon,
health: 100,
speed: 0.02 + Math.random() * 0.03,
lastAttack: 0,
attackCooldown: 1000 + Math.random() * 2000
});
}
function startGame() {
document.getElementById('startScreen').style.display = 'none';
if (!isMobile) {
controls.lock();
}
gameActive = true;
// Spawn initial enemies
for (let i = 0; i < 5; i++) {
spawnEnemy();
}
}
function restartGame() {
document.getElementById('gameOverScreen').style.display = 'none';
playerHealth = 100;
ammo = 30;
score = 0;
updateUI();
// Remove all enemies
enemies.forEach(enemy => {
scene.remove(enemy.mesh);
});
enemies = [];
// Remove all bullets
bullets.forEach(bullet => {
scene.remove(bullet.mesh);
});
bullets = [];
// Reset player position
player.position.set(0, 1.6, 0);
// Spawn initial enemies
for (let i = 0; i < 5; i++) {
spawnEnemy();
}
if (!isMobile) {
controls.lock();
}
gameActive = true;
}
function gameOver() {
gameActive = false;
if (!isMobile) {
controls.unlock();
}
document.getElementById('finalScore').textContent = `SCORE: ${score}`;
document.getElementById('gameOverScreen').style.display = 'flex';
}
function updateUI() {
document.getElementById('healthFill').style.width = `${playerHealth}%`;
document.getElementById('ammoCounter').textContent = `AMMO: ${ammo}`;
document.getElementById('score').textContent = `SCORE: ${score}`;
}
function onKeyDown(event) {
switch (event.code) {
case 'KeyW': moveForward = true; break;
case 'KeyS': moveBackward = true; break;
case 'KeyA': moveLeft = true; break;
case 'KeyD': moveRight = true; break;
}
}
function onKeyUp(event) {
switch (event.code) {
case 'KeyW': moveForward = false; break;
case 'KeyS': moveBackward = false; break;
case 'KeyA': moveLeft = false; break;
case 'KeyD': moveRight = false; break;
}
}
function onMouseDown() {
if (!gameActive) return;
if (ammo > 0 && canShoot) {
shoot();
canShoot = false;
setTimeout(() => { canShoot = true; }, shotCooldown);
}
}
function onMouseMove(event) {
if (!gameActive || isMobile) return;
// Add weapon sway effect
const weapon = document.getElementById('weapon');
const moveX = (event.movementX || 0) * 0.05;
const moveY = (event.movementY || 0) * 0.05;
weapon.style.transform = `translateX(calc(-50% + ${moveX}px)) translateY(${moveY}px)`;
}
function shoot() {
ammo--;
updateUI();
// Create bullet
const bulletGeometry = new THREE.SphereGeometry(0.1, 8, 8);
const bulletMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 });
const bullet = new THREE.Mesh(bulletGeometry, bulletMaterial);
// Position bullet at camera
bullet.position.set(0, 0, 0);
camera.getWorldPosition(bullet.position);
// Direction vector
const direction = new THREE.Vector3(0, 0, -1);
direction.applyQuaternion(camera.quaternion);
scene.add(bullet);
bullets.push({
mesh: bullet,
direction: direction,
speed: 1.0,
distance: 0
});
// Weapon recoil effect
const weapon = document.getElementById('weapon');
weapon.style.transform = 'translateX(-50%) translateY(20px)';
setTimeout(() => {
weapon.style.transform = 'translateX(-50%) translateY(0)';
}, 100);
// Muzzle flash effect
const flash = document.createElement('div');
flash.style.position = 'absolute';
flash.style.width = '50px';
flash.style.height = '50px';
flash.style.background = 'radial-gradient(circle, #ffff00, #ff5500, transparent)';
flash.style.borderRadius = '50%';
flash.style.top = '50%';
flash.style.left = '50%';
flash.style.transform = 'translate(-50%, -50%)';
flash.style.pointerEvents = 'none';
flash.style.zIndex = '10';
document.getElementById('ui').appendChild(flash);
setTimeout(() => {
document.getElementById('ui').removeChild(flash);
}, 50);
}
function setupMobileControls() {
const movementPad = document.getElementById('movementPad');
const movementStick = document.getElementById('movementStick');
const shootButton = document.getElementById('shootButton');
// Movement pad touch events
movementPad.addEventListener('touchstart', (e) => {
movementStickActive = true;
updateMovementStick(e.touches[0]);
});
movementPad.addEventListener('touchmove', (e) => {
if (movementStickActive) {
e.preventDefault();
updateMovementStick(e.touches[0]);
}
});
movementPad.addEventListener('touchend', () => {
movementStickActive = false;
movementStickPosition = { x: 0, y: 0 };
movementStick.style.transform = 'translate(0, 0)';
moveForward = false;
moveBackward = false;
moveLeft = false;
moveRight = false;
});
// Shoot button touch events
shootButton.addEventListener('touchstart', (e) => {
e.preventDefault();
if (gameActive && ammo > 0 && canShoot) {
shoot();
canShoot = false;
setTimeout(() => { canShoot = true; }, shotCooldown);
}
});
function updateMovementStick(touch) {
const padRect = movementPad.getBoundingClientRect();
const centerX = padRect.left + padRect.width / 2;
const centerY = padRect.top + padRect.height / 2;
let deltaX = touch.clientX - centerX;
let deltaY = touch.clientY - centerY;
// Limit to pad radius
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
if (distance > movementStickRadius) {
deltaX = deltaX * movementStickRadius / distance;
deltaY = deltaY * movementStickRadius / distance;
}
movementStickPosition = { x: deltaX, y: deltaY };
movementStick.style.transform = `translate(${deltaX}px, ${deltaY}px)`;
// Update movement flags
moveForward = deltaY < -10;
moveBackward = deltaY > 10;
moveLeft = deltaX < -10;
moveRight = deltaX > 10;
}
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
const delta = clock.getDelta();
if (gameActive) {
// Player movement
const speed = 0.1;
if (isMobile) {
// Mobile movement based on joystick
if (moveForward) player.translateZ(-speed);
if (moveBackward) player.translateZ(speed);
if (moveLeft) player.translateX(-speed);
if (moveRight) player.translateX(speed);
} else {
// Desktop movement
if (moveForward) player.translateZ(-speed);
if (moveBackward) player.translateZ(speed);
if (moveLeft) player.translateX(-speed);
if (moveRight) player.translateX(speed);
}
// Keep player within bounds
player.position.x = Math.max(-24, Math.min(24, player.position.x));
player.position.z = Math.max(-24, Math.min(24, player.position.z));
// Enemy AI
enemies.forEach((enemy, index) => {
// Move towards player
const direction = new THREE.Vector3();
direction.subVectors(player.position, enemy.mesh.position).normalize();
enemy.mesh.position.add(direction.multiplyScalar(enemy.speed));
// Rotate to face player
enemy.mesh.lookAt(player.position);
enemy.mesh.rotation.x = 0; // Keep upright
enemy.mesh.rotation.z = 0;
// Attack player if close
const distance = player.position.distanceTo(enemy.mesh.position);
if (distance < 3 && Date.now() - enemy.lastAttack > enemy.attackCooldown) {
playerHealth -= 10;
updateUI();
enemy.lastAttack = Date.now();
// Show hit effect
const hitEffect = document.getElementById('hitEffect');
hitEffect.style.opacity = '1';
setTimeout(() => {
hitEffect.style.opacity = '0';
}, 100);
// Show blood effect
const bloodEffect = document.getElementById('bloodEffect');
bloodEffect.style.opacity = '0.7';
setTimeout(() => {
bloodEffect.style.opacity = '0';
}, 200);
if (playerHealth <= 0) {
gameOver();
}
}
// Remove dead enemies
if (enemy.health <= 0) {
scene.remove(enemy.mesh);
enemies.splice(index, 1);
score += 100;
updateUI();
// Spawn new enemy
if (Math.random() > 0.7) {
spawnEnemy();
}
}
});
// Update bullets
for (let i = bullets.length - 1; i >= 0; i--) {
const bullet = bullets[i];
bullet.mesh.position.add(bullet.direction.clone().multiplyScalar(bullet.speed));
bullet.distance += bullet.speed;
// Remove bullets that travel too far
if (bullet.distance > 50) {
scene.remove(bullet.mesh);
bullets.splice(i, 1);
continue;
}
// Check for collisions with enemies
for (let j = enemies.length - 1; j >= 0; j--) {
const enemy = enemies[j];
const distance = bullet.mesh.position.distanceTo(enemy.mesh.position);
if (distance < 1.5) {
// Hit enemy
enemy.health -= 25;
scene.remove(bullet.mesh);
bullets.splice(i, 1);
// Enemy hit effect
enemy.mesh.children[0].material.color.set(0xff0000);
setTimeout(() => {
if (enemy.mesh && enemy.mesh.children[0]) {
enemy.mesh.children[0].material.color.set(0x880000);
}
}, 100);
break;
}
}
}
// Spawn enemies periodically
enemySpawnTimer += delta;
if (enemySpawnTimer > 5) {
spawnEnemy();
enemySpawnTimer = 0;
}
}
renderer.render(scene, camera);
}
// Initialize the game when the page loads
window.onload = init;
</script>
</body>
</html>