Обновить doom.html

This commit is contained in:
2026-06-15 18:20:46 +03:00
parent 0ab47fc64e
commit 26c2b65ad9
+386 -249
View File
@@ -3,299 +3,436 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Web Doom Eternal Prototype</title> <title>DOOM ETERNAL: Web 3D Prototype</title>
<style> <style>
body { body { margin: 0; overflow: hidden; background: #000; font-family: 'Impact', 'Arial Black', sans-serif; user-select: none; }
margin: 0; #game-container { position: relative; width: 100vw; height: 100vh; }
padding: 0;
background-color: #000; /* Прицел */
color: #fff; #crosshair {
font-family: 'Courier New', Courier, monospace; position: absolute; top: 50%; left: 50%; width: 20px; height: 20px;
overflow: hidden; transform: translate(-50%, -50%); pointer-events: none; z-index: 10;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
} }
#crosshair::before, #crosshair::after {
#game-container { content: ''; position: absolute; background: #00ff00; box-shadow: 0 0 5px #00ff00;
position: relative;
border: 4px solid #4a4a4a;
box-shadow: 0 0 20px #ff0000;
} }
#crosshair::before { top: 9px; left: 0; width: 20px; height: 2px; }
#crosshair::after { top: 0; left: 9px; width: 2px; height: 20px; }
canvas { /* Экран старта */
display: block; #blocker {
background: #000; position: absolute; width: 100%; height: 100%; background: rgba(0,0,0,0.85);
image-rendering: pixelated; display: flex; flex-direction: column; align-items: center; justify-content: center;
z-index: 20; color: #fff; cursor: pointer;
} }
#instructions { font-size: 36px; text-transform: uppercase; letter-spacing: 4px; color: #ff3333; text-shadow: 0 0 15px #ff0000; text-align: center; }
#sub-instructions { font-size: 18px; color: #aaa; margin-top: 20px; font-family: monospace; }
/* Интерфейс в стиле Doom Eternal */ /* HUD в стиле Doom Eternal */
#ui-bar { #hud {
position: absolute; position: absolute; bottom: 0; left: 0; width: 100%; height: 100px;
bottom: 0; background: linear-gradient(to top, #0a0a0a 80%, transparent);
left: 0; border-top: 3px solid #8b0000; display: flex; justify-content: space-between;
width: 100%; align-items: center; padding: 0 40px; box-sizing: border-box; pointer-events: none; z-index: 10;
height: 80px;
background: linear-gradient(to bottom, #1a1a1a, #000);
border-top: 3px solid #8b0000;
display: flex;
justify-content: space-around;
align-items: center;
font-weight: bold;
font-size: 24px;
text-transform: uppercase;
color: #fff;
text-shadow: 2px 2px 0 #000;
} }
.stat-box { text-align: center; color: #fff; }
.stat-label { font-size: 14px; color: #888; letter-spacing: 2px; text-transform: uppercase; }
.stat-value { font-size: 48px; line-height: 48px; text-shadow: 0 0 10px currentColor; }
.health { color: #00ff00; }
.armor { color: #00bfff; }
.ammo { color: #ffcc00; }
.stat { #damage-overlay {
display: flex; position: absolute; top: 0; left: 0; width: 100%; height: 100%;
flex-direction: column; background: radial-gradient(circle, transparent 50%, rgba(255,0,0,0.6) 100%);
align-items: center; opacity: 0; pointer-events: none; transition: opacity 0.2s; z-index: 5;
}
.stat-label {
font-size: 12px;
color: #aaa;
margin-bottom: 4px;
}
.stat-value {
font-size: 32px;
color: #00ff00; /* Классический зеленый цвет здоровья */
}
.stat-value.armor { color: #00bfff; }
.stat-value.ammo { color: #ffcc00; }
#weapon {
position: absolute;
bottom: 80px;
left: 50%;
transform: translateX(-50%);
width: 200px;
height: 200px;
background: linear-gradient(to top, #333, #111);
border: 2px solid #555;
border-bottom: none;
border-radius: 10px 10px 0 0;
display: flex;
align-items: flex-end;
justify-content: center;
padding-bottom: 10px;
color: #888;
font-size: 14px;
}
#weapon::after {
content: "SUPER SHOTGUN";
color: #ff4444;
font-weight: bold;
text-shadow: 0 0 5px #ff0000;
}
#message {
position: absolute;
top: 20px;
width: 100%;
text-align: center;
color: #ff3333;
font-size: 18px;
text-shadow: 0 0 5px #ff0000;
pointer-events: none;
} }
</style> </style>
<!-- Импорт Three.js через CDN -->
<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>
</head> </head>
<body> <body>
<div id="game-container"> <div id="game-container">
<canvas id="gameCanvas" width="640" height="400"></canvas> <div id="damage-overlay"></div>
<div id="message">WASD или Стрелки для движения | RIP AND TEAR</div> <div id="crosshair"></div>
<div id="weapon"></div> <div id="blocker">
<div id="instructions">КЛИКНИ, ЧТОБЫ ИГРАТЬ</div>
<div id="sub-instructions">WASD - Движение | Мышь - Обзор | ЛКМ - Огонь | RIP AND TEAR</div>
</div>
<div id="ui-bar"> <div id="hud">
<div class="stat"> <div class="stat-box">
<span class="stat-label">Здоровье</span> <div class="stat-label">Здоровье</div>
<span class="stat-value" id="health">100%</span> <div class="stat-value health" id="health-val">100</div>
</div> </div>
<div class="stat"> <div class="stat-box">
<span class="stat-label">Броня</span> <div class="stat-label">Броня</div>
<span class="stat-value armor" id="armor">50%</span> <div class="stat-value armor" id="armor-val">50</div>
</div> </div>
<div class="stat"> <div class="stat-box">
<span class="stat-label">Патроны</span> <div class="stat-label">Патроны</div>
<span class="stat-value ammo" id="ammo">24</span> <div class="stat-value ammo" id="ammo-val">24</div>
</div> </div>
</div> </div>
</div> </div>
<script> <script type="module">
const canvas = document.getElementById('gameCanvas'); import * as THREE from 'three';
const ctx = canvas.getContext('2d'); import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js';
// Настройки рейкастинга // --- Глобальные переменные ---
const SCREEN_WIDTH = 640; let camera, scene, renderer, controls;
const SCREEN_HEIGHT = 400; let moveForward = false, moveBackward = false, moveLeft = false, moveRight = false;
const TICK = 30; let prevTime = performance.now();
const CELL_SIZE = 64; const velocity = new THREE.Vector3();
const FOV = Math.PI / 3; const direction = new THREE.Vector3();
const BLOCK_SIZE = 64;
const MAP_SIZE = 16;
// Карта (1 - стена, 0 - пусто) // Игровые параметры
const map = [ let health = 100, armor = 50, ammo = 24;
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], let enemies = [];
[1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1], let particles = [];
[1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1], let weaponGroup;
[1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1], let isShooting = false;
[1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1], let muzzleLight;
[1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
];
// Игрок // Элементы UI
const player = { const blocker = document.getElementById('blocker');
x: 96, const instructions = document.getElementById('instructions');
y: 96, const damageOverlay = document.getElementById('damage-overlay');
dir: 0, // Угол в радианах const uiHealth = document.getElementById('health-val');
speed: 4, const uiArmor = document.getElementById('armor-val');
rotSpeed: 0.08 const uiAmmo = document.getElementById('ammo-val');
};
// Управление init();
const keys = { animate();
w: false, s: false, a: false, d: false,
ArrowUp: false, ArrowDown: false, ArrowLeft: false, ArrowRight: false
};
document.addEventListener('keydown', (e) => { if(keys.hasOwnProperty(e.key)) keys[e.key] = true; }); function init() {
document.addEventListener('keyup', (e) => { if(keys.hasOwnProperty(e.key)) keys[e.key] = false; }); // 1. Сцена и Камера
scene = new THREE.Scene();
scene.background = new THREE.Color(0x050000);
// Адский туман
scene.fog = new THREE.FogExp2(0x1a0505, 0.035);
// Основной цикл игры camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
function gameLoop() { camera.position.y = 1.6; // Рост игрока
update();
draw(); // 2. Освещение
requestAnimationFrame(gameLoop); const ambientLight = new THREE.AmbientLight(0x401010, 0.5); // Темно-красный ambient
scene.add(ambientLight);
const dirLight = new THREE.DirectionalLight(0xff4400, 0.8);
dirLight.position.set(10, 20, 10);
dirLight.castShadow = true;
scene.add(dirLight);
// Вспышка выстрела (PointLight)
muzzleLight = new THREE.PointLight(0xffaa00, 0, 10);
scene.add(muzzleLight);
// 3. Окружение (Пол и Стены)
const floorGeometry = new THREE.PlaneGeometry(100, 100);
const floorMaterial = new THREE.MeshStandardMaterial({
color: 0x221111, 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);
// Создаем несколько колонн/стен для укрытий
const boxGeo = new THREE.BoxGeometry(2, 6, 2);
const boxMat = new THREE.MeshStandardMaterial({ color: 0x331111, roughness: 0.9 });
for (let i = 0; i < 15; i++) {
const wall = new THREE.Mesh(boxGeo, boxMat);
wall.position.x = (Math.random() - 0.5) * 60;
wall.position.z = (Math.random() - 0.5) * 60;
wall.position.y = 3;
wall.castShadow = true;
wall.receiveShadow = true;
scene.add(wall);
} }
function update() { // 4. Оружие (Супердробовик - процедурная модель)
// Вращение weaponGroup = new THREE.Group();
if (keys.a || keys.ArrowLeft) player.dir -= player.rotSpeed;
if (keys.d || keys.ArrowRight) player.dir += player.rotSpeed;
// Движение const barrelGeo = new THREE.CylinderGeometry(0.04, 0.04, 0.8, 8);
let moveStep = player.speed; const barrelMat = new THREE.MeshStandardMaterial({ color: 0x111111, metalness: 0.8, roughness: 0.3 });
let newX = player.x; const barrel1 = new THREE.Mesh(barrelGeo, barrelMat);
let newY = player.y; barrel1.rotation.x = Math.PI / 2;
barrel1.position.set(-0.03, 0, -0.4);
if (keys.w || keys.ArrowUp) { const barrel2 = new THREE.Mesh(barrelGeo, barrelMat);
newX += Math.cos(player.dir) * moveStep; barrel2.rotation.x = Math.PI / 2;
newY += Math.sin(player.dir) * moveStep; barrel2.position.set(0.03, 0, -0.4);
}
if (keys.s || keys.ArrowDown) { const bodyGeo = new THREE.BoxGeometry(0.15, 0.2, 0.6);
newX -= Math.cos(player.dir) * moveStep; const bodyMat = new THREE.MeshStandardMaterial({ color: 0x4a3c31, metalness: 0.5 });
newY -= Math.sin(player.dir) * moveStep; const body = new THREE.Mesh(bodyGeo, bodyMat);
body.position.z = 0.1;
weaponGroup.add(barrel1, barrel2, body);
weaponGroup.position.set(0.3, -0.3, -0.5);
camera.add(weaponGroup); // Прикрепляем оружие к камере
scene.add(camera);
// 5. Управление
controls = new PointerLockControls(camera, document.body);
blocker.addEventListener('click', () => {
controls.lock();
});
controls.addEventListener('lock', () => {
instructions.style.display = 'none';
blocker.style.display = 'none';
});
controls.addEventListener('unlock', () => {
blocker.style.display = 'flex';
instructions.style.display = 'block';
instructions.innerText = "ПАУЗА. КЛИКНИ, ЧТОБЫ ПРОДОЛЖИТЬ";
});
document.addEventListener('keydown', onKeyDown);
document.addEventListener('keyup', onKeyUp);
document.addEventListener('mousedown', onMouseDown);
// 6. Рендерер
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
document.getElementById('game-container').appendChild(renderer.domElement);
window.addEventListener('resize', onWindowResize);
// Спавн первых врагов
for(let i=0; i<5; i++) spawnEnemy();
} }
// Простая коллизия со стенами function spawnEnemy() {
if (map[Math.floor(newY / CELL_SIZE)][Math.floor(player.x / CELL_SIZE)] === 0) { // Враг - демоническая светящаяся сфера (Икосаэдр)
player.y = newY; const geometry = new THREE.IcosahedronGeometry(0.8, 1);
const material = new THREE.MeshStandardMaterial({
color: 0xff0000,
emissive: 0x550000,
roughness: 0.4,
metalness: 0.6
});
const enemy = new THREE.Mesh(geometry, material);
// Случайная позиция вдали от игрока
const angle = Math.random() * Math.PI * 2;
const radius = 15 + Math.random() * 20;
enemy.position.x = camera.position.x + Math.cos(angle) * radius;
enemy.position.z = camera.position.z + Math.sin(angle) * radius;
enemy.position.y = 1;
enemy.castShadow = true;
enemy.userData = { health: 30, speed: 2 + Math.random() * 2, lastAttack: 0 };
scene.add(enemy);
enemies.push(enemy);
} }
if (map[Math.floor(player.y / CELL_SIZE)][Math.floor(newX / CELL_SIZE)] === 0) {
player.x = newX; function createParticles(position, color, count) {
const geo = new THREE.BoxGeometry(0.1, 0.1, 0.1);
const mat = new THREE.MeshBasicMaterial({ color: color });
for (let i = 0; i < count; i++) {
const mesh = new THREE.Mesh(geo, mat);
mesh.position.copy(position);
mesh.position.x += (Math.random() - 0.5) * 0.5;
mesh.position.y += (Math.random() - 0.5) * 0.5;
mesh.position.z += (Math.random() - 0.5) * 0.5;
const vel = new THREE.Vector3(
(Math.random() - 0.5) * 5,
(Math.random() - 0.5) * 5 + 2, // Вверх
(Math.random() - 0.5) * 5
);
scene.add(mesh);
particles.push({ mesh, vel, life: 1.0 });
} }
} }
function draw() { function onKeyDown(event) {
// Очистка экрана (потолок и пол) switch (event.code) {
// Потолок (темно-серый) case 'ArrowUp': case 'KeyW': moveForward = true; break;
ctx.fillStyle = '#222'; case 'ArrowLeft': case 'KeyA': moveLeft = true; break;
ctx.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2); case 'ArrowDown': case 'KeyS': moveBackward = true; break;
// Пол (адский красноватый) case 'ArrowRight': case 'KeyD': moveRight = true; break;
ctx.fillStyle = '#3a1515';
ctx.fillRect(0, SCREEN_HEIGHT / 2, SCREEN_WIDTH, SCREEN_HEIGHT / 2);
// Рейкастинг
for (let x = 0; x < SCREEN_WIDTH; x+=2) { // Оптимизация: рендерим каждый 2-й пиксель по ширине
const rayAngle = (player.dir - FOV / 2.0) + (x / SCREEN_WIDTH) * FOV;
const eyeX = Math.cos(rayAngle);
const eyeY = Math.sin(rayAngle);
let distToWall = 0;
let hitWall = false;
let texture = 0; // Для разнообразия стен
let testX = Math.floor(player.x / CELL_SIZE);
let testY = Math.floor(player.y / CELL_SIZE);
// Упрощенный DDA алгоритм
let stepX = eyeX < 0 ? -1 : 1;
let stepY = eyeY < 0 ? -1 : 1;
let sideDistX = (eyeX < 0 ? player.x / CELL_SIZE - testX : testX + 1 - player.x / CELL_SIZE) * Math.abs(1 / eyeX);
let sideDistY = (eyeY < 0 ? player.y / CELL_SIZE - testY : testY + 1 - player.y / CELL_SIZE) * Math.abs(1 / eyeY);
let deltaDistX = Math.abs(1 / eyeX);
let deltaDistY = Math.abs(1 / eyeY);
let side = 0; // 0 для NS, 1 для EW
while (!hitWall && distToWall < 20) {
if (sideDistX < sideDistY) {
sideDistX += deltaDistX;
testX += stepX;
side = 0;
} else {
sideDistY += deltaDistY;
testY += stepY;
side = 1;
}
if (testX < 0 || testX >= MAP_SIZE || testY < 0 || testY >= MAP_SIZE) {
hitWall = true;
distToWall = 20;
} else if (map[testY][testX] > 0) {
hitWall = true;
if (side === 0) distToWall = (testX - player.x / CELL_SIZE + (1 - stepX) / 2) / eyeX;
else distToWall = (testY - player.y / CELL_SIZE + (1 - stepY) / 2) / eyeY;
} }
} }
// Исправление эффекта "рыбьего глаза" function onKeyUp(event) {
const correctedDist = distToWall * Math.cos(rayAngle - player.dir); switch (event.code) {
case 'ArrowUp': case 'KeyW': moveForward = false; break;
// Расчет высоты стены case 'ArrowLeft': case 'KeyA': moveLeft = false; break;
const ceiling = SCREEN_HEIGHT / 2.0 - SCREEN_HEIGHT / correctedDist; case 'ArrowDown': case 'KeyS': moveBackward = false; break;
const floor = SCREEN_HEIGHT - ceiling; case 'ArrowRight': case 'KeyD': moveRight = false; break;
const wallHeight = floor - ceiling;
// Цвет стены (затенение по расстоянию + разные стороны для объема)
let colorVal = Math.max(50, 255 - correctedDist * 15);
if (side === 1) colorVal *= 0.7; // Одна сторона темнее
// Адская цветовая гамма
ctx.fillStyle = `rgb(${colorVal}, ${colorVal * 0.2}, ${colorVal * 0.2})`;
// Рисуем вертикальную полосу стены
ctx.fillRect(x, ceiling, 2, wallHeight);
} }
} }
// Запуск function onMouseDown(event) {
gameLoop(); if (!controls.isLocked || event.button !== 0 || ammo <= 0) return;
// Логика выстрела
ammo--;
uiAmmo.innerText = ammo;
isShooting = true;
// Анимация отдачи
weaponGroup.position.z += 0.2;
weaponGroup.rotation.x -= 0.1;
// Вспышка света
muzzleLight.position.copy(camera.position).add(camera.getWorldDirection(new THREE.Vector3()).multiplyScalar(1));
muzzleLight.intensity = 5;
setTimeout(() => { muzzleLight.intensity = 0; }, 50);
// Raycasting (стрельба)
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(new THREE.Vector2(0, 0), camera);
const intersects = raycaster.intersectObjects(enemies);
if (intersects.length > 0) {
const hit = intersects[0];
const enemy = hit.object;
// Эффект попадания
createParticles(hit.point, 0xff0000, 8); // Кровь
createParticles(hit.point, 0xffaa00, 4); // Искры
enemy.userData.health -= 15;
// Отталкивание врага
const pushDir = hit.point.clone().sub(camera.position).normalize();
enemy.position.add(pushDir.multiplyScalar(0.5));
if (enemy.userData.health <= 0) {
scene.remove(enemy);
enemies = enemies.filter(e => e !== enemy);
createParticles(enemy.position, 0xff0000, 20); // Взрыв крови
spawnEnemy(); // Спавним нового
spawnEnemy(); // Усложняем
}
}
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
const time = performance.now();
const delta = (time - prevTime) / 1000;
prevTime = time;
if (controls.isLocked) {
// --- Движение игрока ---
velocity.x -= velocity.x * 10.0 * delta;
velocity.z -= velocity.z * 10.0 * delta;
direction.z = Number(moveForward) - Number(moveBackward);
direction.x = Number(moveRight) - Number(moveLeft);
direction.normalize();
if (moveForward || moveBackward) velocity.z -= direction.z * 100.0 * delta;
if (moveLeft || moveRight) velocity.x -= direction.x * 100.0 * delta;
controls.moveRight(-velocity.x * delta);
controls.moveForward(-velocity.z * delta);
// Ограничение карты (простая коллизия с границами)
camera.position.x = Math.max(-45, Math.min(45, camera.position.x));
camera.position.z = Math.max(-45, Math.min(45, camera.position.z));
// --- Анимация оружия ---
// Возврат отдачи
weaponGroup.position.z = THREE.MathUtils.lerp(weaponGroup.position.z, -0.5, delta * 10);
weaponGroup.rotation.x = THREE.MathUtils.lerp(weaponGroup.rotation.x, 0, delta * 10);
// Покачивание оружия при ходьбе (Bobbing)
if (moveForward || moveBackward || moveLeft || moveRight) {
weaponGroup.position.y = -0.3 + Math.sin(time * 0.01) * 0.01;
weaponGroup.position.x = 0.3 + Math.cos(time * 0.01) * 0.01;
}
// --- Логика врагов ---
enemies.forEach(enemy => {
// Движение к игроку
const dirToPlayer = new THREE.Vector3().subVectors(camera.position, enemy.position).normalize();
dirToPlayer.y = 0; // Не летать вверх/вниз
// Простая проверка расстояния, чтобы не застревать друг в друге
let tooClose = false;
enemies.forEach(other => {
if (enemy !== other && enemy.position.distanceTo(other.position) < 1.5) {
tooClose = true;
}
});
if (!tooClose) {
enemy.position.add(dirToPlayer.multiplyScalar(enemy.userData.speed * delta));
enemy.lookAt(camera.position.x, enemy.position.y, camera.position.z);
}
// Атака игрока
const dist = enemy.position.distanceTo(camera.position);
if (dist < 2.0 && time - enemy.userData.lastAttack > 1000) {
enemy.userData.lastAttack = time;
health -= 10;
uiHealth.innerText = health;
// Эффект урона
damageOverlay.style.opacity = 0.8;
setTimeout(() => { damageOverlay.style.opacity = 0; }, 200);
// Тряска камеры
camera.position.y += 0.1;
setTimeout(() => { camera.position.y -= 0.1; }, 50);
if (health <= 0) {
alert("ВЫ ПОГИБЛИ. RIP AND TEAR В СЛЕДУЮЩИЙ РАЗ.");
location.reload();
}
}
});
// --- Частицы ---
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
p.life -= delta * 2;
p.vel.y -= 9.8 * delta; // Гравитация
p.mesh.position.add(p.vel.clone().multiplyScalar(delta));
p.mesh.rotation.x += delta * 5;
p.mesh.scale.setScalar(p.life);
if (p.life <= 0 || p.mesh.position.y < 0) {
scene.remove(p.mesh);
particles.splice(i, 1);
}
}
}
renderer.render(scene, camera);
}
</script> </script>
</body> </body>
</html> </html>