Tunneling Problemi

SONICO v8'i geliştirirken hız yüksek olduğunda karakter duvardan geçiyordu. Bu klasik bir "tunneling" problemi: delta time büyük olduğunda nesne bir frame'de çok uzak gidiyor ve çarpışma tespiti atlıyor.

// PROBLEM: Tek adımda büyük hareket function update(dt) { player.x += player.vx * dt; // dt büyükse duvardan geçer player.y += player.vy * dt; checkCollision(); // İş işten geçmiş olabilir }

Sub-stepping Çözümü

Sub-stepping, büyük bir zaman adımını birden fazla küçük adıma bölerek her adımda çarpışma tespiti yapar. Bu sayede hız ne kadar yüksek olursa olsun çarpışma atlama yaşanmaz.

const MAX_STEP = 4; // maksimum piksel/adım function update(dt) { const steps = Math.ceil( Math.max(Math.abs(player.vx), Math.abs(player.vy)) * dt / MAX_STEP ); const subDt = dt / steps; for (let i = 0; i < steps; i++) { player.x += player.vx * subDt; player.y += player.vy * subDt; resolveCollisions(); } } function resolveCollisions() { for (const tile of getNearbyTiles(player)) { if (overlaps(player, tile)) { // Minimum translation vector ile çöz const mtv = getMTV(player, tile); player.x += mtv.x; player.y += mtv.y; if (mtv.y < 0) { player.vy = 0; player.grounded = true; } if (mtv.y > 0) { player.vy = 0; } if (mtv.x !== 0) { player.vx = 0; } } } }
💡 PERFORMANS

Her tile ile çarpışma kontrolü yapmak O(n) maliyet. getNearbyTiles() ile sadece yakın tile'ları kontrol ederek büyük haritalar için O(1)'e yakın performans elde edilir.

One-way Platformlar

Klasik platformerlarda aşağıdan geçip üstüne çıkabileceğin platformlar. Çözüm: sadece yukarıdan gelen çarpışmayı kabul et.

function resolveOneWay(player, platform) { const prevBottom = player.y + player.h - player.vy * subDt; const isFromAbove = prevBottom <= platform.y + 2; // 2px tolerans if (isFromAbove && player.vy >= 0) { player.y = platform.y - player.h; player.vy = 0; player.grounded = true; } // Aşağıdan veya yandan gelen çarpışmayı yoksay }

Parry ve Dash Mekanikleri

Parry: Tam doğru anda düşman saldırısını karşılamak. 8 frame'lik bir parry penceresi var. Başarılı parry düşmanı sersemleter.

Dash: Kısa süreli dokunulmazlık ile hızlı hareket. Cooldown sistemi ve görsel trail efektiyle. Her ikisi de frame-perfect timing gerektirdiğinden sub-stepping kritik.

Sinematik Boss Intro Sahnesi

Klasik boss ekranda belirince letterbox (üst/alt siyah bantlar) açılıyor ve kamera boss'a kayıyor. Bu efekt için canvas'ta overlay çizildi.

function drawLetterbox(progress) { // progress: 0→1 const barH = canvas.height * 0.12 * progress; ctx.fillStyle = "#000"; ctx.fillRect(0, 0, canvas.width, barH); ctx.fillRect(0, canvas.height - barH, canvas.width, barH); } async function playBossIntro(boss) { // 1. Letterbox aç await tween(0, 1, 800, p => { letterboxProgress = p; }); // 2. Kamera boss'a kay await tweenCamera(boss.x, boss.y, 1200); // 3. Boss isim kartı göster showBossCard(boss.name); await wait(2000); // 4. Oyun başlasın gameState = "boss_fight"; }

Lightning Strike AoE Yeteneği

Q tuşuyla aktive edilen Lightning Strike, geniş bir alana yıldırım düşürüyor. Görsel efekt için canvas'ta rastgele zikzak çizgiler çizildi, ses efekti Web Audio API ile üretildi.

function drawLightning(x1, y1, x2, y2, branches = 3) { if (branches <= 0) return; const mx = (x1 + x2) / 2 + (Math.random() - 0.5) * 40; const my = (y1 + y2) / 2 + (Math.random() - 0.5) * 40; ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(mx, my); ctx.lineTo(x2, y2); ctx.strokeStyle = `rgba(180,120,255,${0.3 + branches * 0.2})`; ctx.lineWidth = branches; ctx.stroke(); // Alt dallar if (Math.random() > 0.5) { drawLightning(mx, my, mx + 30, my + 40, branches - 1); } drawLightning(x1, y1, mx, my, branches - 1); drawLightning(mx, my, x2, y2, branches - 1); }