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; }
}
}
}
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);
}