/* ===================================================================== * 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);