Добавить doom.html
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user