raycaster-3d/public/app.js

184 lines
6.1 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* =====================================================================
* app.js — OYUN DÖNGÜSÜ • KONTROLLER • FİZİK • HUD • MİNİMAP
* Saf 2D canvas arayüz katmanı (engine.js'i kullanır).
* ===================================================================== */
'use strict';
/* ---------- Canvas bağlantıları ---------- */
const screenCanvas = document.getElementById('game'); // 3D görünüm
const mapCanvas = document.getElementById('minimap'); // radar
const ctx = screenCanvas.getContext('2d');
const mctx = mapCanvas.getContext('2d');
/* ---------- HUD elemanları ---------- */
const hudFps = document.getElementById('hud-fps');
const hudX = document.getElementById('hud-x');
const hudY = document.getElementById('hud-y');
const hudAng = document.getElementById('hud-ang');
/* ---------- Çözünürlük (retro ~640x360) ---------- */
function resize() {
// CSS ile dolu kare; iç canvas piksel sayısını retro tutalım
if (screenCanvas.width !== 640) screenCanvas.width = 640;
if (screenCanvas.height !== 360) screenCanvas.height = 360;
// CSS ölçeği: genişlikte taşmasın
const cssW = Math.min(window.innerWidth - 24, 900);
const cssH = cssW * (360 / 640);
screenCanvas.style.width = cssW + 'px';
screenCanvas.style.height = cssH + 'px';
}
resize();
window.addEventListener('resize', resize);
/* ---------- Yer kaplama (collider) ---------- */
const WALL = {
isWall(x, y) {
const ix = Math.floor(x), iy = Math.floor(y);
if (ix < 0 || iy < 0 || ix >= GRID || iy >= GRID) return true; // kapalı alan
return MAP[iy][ix] !== 0;
},
/* Yarıçap radius'lı (oyuncu gövdesi) çarpışma testi */
canMove(nx, ny, radius) {
// Köşeleri test et
return !(this.isWall(nx - radius, ny - radius) ||
this.isWall(nx + radius, ny - radius) ||
this.isWall(nx - radius, ny + radius) ||
this.isWall(nx + radius, ny + radius));
}
};
const PLAYER_R = 0.22; // hücre birimi cinsinden oyuncu gövde yarıçapı
/* ---------- Tuş durumu ---------- */
const keys = {};
const MOVE_SPEED = 3.2; // saniyede hücre
const TURN_SPEED = 2.6; // radyan/saniye
document.addEventListener('keydown', (e) => { keys[e.code] = true; });
document.addEventListener('keyup', (e) => { keys[e.code] = false; });
/* ---------- Fizik + giriş (her kare) ---------- */
function update(player_, dt) {
// Dönüş: sola/sağa (A / D veya ok tuşları)
let turn = 0;
if (keys['KeyA'] || keys['ArrowLeft']) turn -= 1;
if (keys['KeyD'] || keys['ArrowRight']) turn += 1;
player_.angle += turn * TURN_SPEED * dt;
// İleri / geri
let move = 0;
if (keys['KeyW'] || keys['ArrowUp']) move += 1;
if (keys['KeyS'] || keys['ArrowDown']) move -= 1;
if (move !== 0) {
const step = move * MOVE_SPEED * dt;
const nx = player_.x + dirX(player_.angle) * step;
const ny = player_.y + dirY(player_.angle) * step;
// Eksen bağımsız çarpışma (duvarlara sürtünerek kayma)
if (WALL.canMove(nx, player_.y, PLAYER_R)) player_.x = nx;
if (WALL.canMove(player_.x, ny, PLAYER_R)) player_.y = ny;
}
// Açı periyodik tut (0..2π)
player_.angle = ((player_.angle % (Math.PI * 2)) + Math.PI * 2) % (Math.PI * 2);
}
/* ---------- Minimap / radar (sağ üst) ---------- */
const CELL_PX = 10; // harita hücre başına px
function renderMinimap() {
const mw = GRID * CELL_PX;
const mh = GRID * CELL_PX;
mapCanvas.width = mw;
mapCanvas.height = mh;
mctx.clearRect(0, 0, mw, mh);
// Duvarları çiz
for (let yy = 0; yy < GRID; yy++) {
for (let xx = 0; xx < GRID; xx++) {
const tile = MAP[yy][xx];
if (tile === 0) continue;
const pal = WALL_PALETTE[tile] || WALL_PALETTE[1];
mctx.fillStyle = pal.base;
mctx.fillRect(xx * CELL_PX + 1, yy * CELL_PX + 1, CELL_PX - 2, CELL_PX - 2);
}
}
// Sprite dekor objeleri (minimap: küçük beyaz nokta)
if (typeof SPRITES !== 'undefined') {
mctx.fillStyle = '#ffffff';
for (const sp of SPRITES) {
mctx.fillRect(sp.x * CELL_PX - 1, sp.y * CELL_PX - 1, 2, 2);
}
}
// Işın konisi (oyuncu görüşü): FOV kadar saçılan ışınlar
mctx.strokeStyle = 'rgba(255,220,80,0.75)';
mctx.lineWidth = 1;
const coneSteps = 24;
for (let i = 0; i <= coneSteps; i++) {
const a = player.angle - FOV / 2 + (i / coneSteps) * FOV;
// duvara kadar çek (basit + ince DDA render'ı tekrar dene, görsel için yeterli)
const hit = castRay(player.x, player.y, a, 14);
const ex = player.x + dirX(a) * hit.dist;
const ey = player.y + dirY(a) * hit.dist;
mctx.beginPath();
mctx.moveTo(player.x * CELL_PX, player.y * CELL_PX);
mctx.lineTo(ex * CELL_PX, ey * CELL_PX);
mctx.stroke();
}
// Oyuncu noktası
mctx.fillStyle = '#ffe14d';
mctx.beginPath();
mctx.arc(player.x * CELL_PX, player.y * CELL_PX, 3, 0, Math.PI * 2);
mctx.fill();
// Bakış yönü kısa çizgi
mctx.strokeStyle = '#ffffff';
mctx.lineWidth = 1.5;
mctx.beginPath();
mctx.moveTo(player.x * CELL_PX, player.y * CELL_PX);
mctx.lineTo((player.x + dirX(player.angle) * 0.45) * CELL_PX,
(player.y + dirY(player.angle) * 0.45) * CELL_PX);
mctx.stroke();
}
/* ---------- HUD ---------- */
function updateHUD(fps) {
hudFps.textContent = String(Math.round(fps));
hudX.textContent = player.x.toFixed(2);
hudY.textContent = player.y.toFixed(2);
hudAng.textContent = ((player.angle * 180) / Math.PI).toFixed(0) + '°';
}
/* ---------- Oyun döngüsü ---------- */
let lastT = performance.now();
let frameCount = 0;
let fpsTimer = lastT;
let fpsValue = 60;
function loop(now) {
let dt = (now - lastT) / 1000;
lastT = now;
if (dt > 0.05) dt = 0.05; // sekme arka plana geçtiyse sınırla
// FPS sayacı
frameCount++;
if (now - fpsTimer >= 500) {
fpsValue = (frameCount * 1000) / (now - fpsTimer);
frameCount = 0;
fpsTimer = now;
}
update(player, dt);
renderScene(ctx, screenCanvas.width, screenCanvas.height);
renderMinimap();
updateHUD(fpsValue);
requestAnimationFrame(loop);
}
/* ---------- Başlat ---------- */
requestAnimationFrame(loop);