Добавить doom.html

This commit is contained in:
2026-06-15 18:16:38 +03:00
parent 0850b72e71
commit 0ab47fc64e
+301
View File
@@ -0,0 +1,301 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Web Doom Eternal Prototype</title>
<style>
body {
margin: 0;
padding: 0;
background-color: #000;
color: #fff;
font-family: 'Courier New', Courier, monospace;
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
}
#game-container {
position: relative;
border: 4px solid #4a4a4a;
box-shadow: 0 0 20px #ff0000;
}
canvas {
display: block;
background: #000;
image-rendering: pixelated;
}
/* Интерфейс в стиле Doom Eternal */
#ui-bar {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
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 {
display: flex;
flex-direction: column;
align-items: center;
}
.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>
</head>
<body>
<div id="game-container">
<canvas id="gameCanvas" width="640" height="400"></canvas>
<div id="message">WASD или Стрелки для движения | RIP AND TEAR</div>
<div id="weapon"></div>
<div id="ui-bar">
<div class="stat">
<span class="stat-label">Здоровье</span>
<span class="stat-value" id="health">100%</span>
</div>
<div class="stat">
<span class="stat-label">Броня</span>
<span class="stat-value armor" id="armor">50%</span>
</div>
<div class="stat">
<span class="stat-label">Патроны</span>
<span class="stat-value ammo" id="ammo">24</span>
</div>
</div>
</div>
<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
// Настройки рейкастинга
const SCREEN_WIDTH = 640;
const SCREEN_HEIGHT = 400;
const TICK = 30;
const CELL_SIZE = 64;
const FOV = Math.PI / 3;
const BLOCK_SIZE = 64;
const MAP_SIZE = 16;
// Карта (1 - стена, 0 - пусто)
const map = [
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1],
[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]
];
// Игрок
const player = {
x: 96,
y: 96,
dir: 0, // Угол в радианах
speed: 4,
rotSpeed: 0.08
};
// Управление
const keys = {
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; });
document.addEventListener('keyup', (e) => { if(keys.hasOwnProperty(e.key)) keys[e.key] = false; });
// Основной цикл игры
function gameLoop() {
update();
draw();
requestAnimationFrame(gameLoop);
}
function update() {
// Вращение
if (keys.a || keys.ArrowLeft) player.dir -= player.rotSpeed;
if (keys.d || keys.ArrowRight) player.dir += player.rotSpeed;
// Движение
let moveStep = player.speed;
let newX = player.x;
let newY = player.y;
if (keys.w || keys.ArrowUp) {
newX += Math.cos(player.dir) * moveStep;
newY += Math.sin(player.dir) * moveStep;
}
if (keys.s || keys.ArrowDown) {
newX -= Math.cos(player.dir) * moveStep;
newY -= Math.sin(player.dir) * moveStep;
}
// Простая коллизия со стенами
if (map[Math.floor(newY / CELL_SIZE)][Math.floor(player.x / CELL_SIZE)] === 0) {
player.y = newY;
}
if (map[Math.floor(player.y / CELL_SIZE)][Math.floor(newX / CELL_SIZE)] === 0) {
player.x = newX;
}
}
function draw() {
// Очистка экрана (потолок и пол)
// Потолок (темно-серый)
ctx.fillStyle = '#222';
ctx.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT / 2);
// Пол (адский красноватый)
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;
}
}
// Исправление эффекта "рыбьего глаза"
const correctedDist = distToWall * Math.cos(rayAngle - player.dir);
// Расчет высоты стены
const ceiling = SCREEN_HEIGHT / 2.0 - SCREEN_HEIGHT / correctedDist;
const floor = SCREEN_HEIGHT - ceiling;
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);
}
}
// Запуск
gameLoop();
</script>
</body>
</html>