Oyun Döngüsünde Sabit Zaman Adımı: Fizik Neden Kayıyor?

Oyun Döngüsünde Sabit Zaman Adımı: Fizik Neden Kayıyor?

Aynı zıplama, 144 Hz ekranda farklı yüksekliğe çıkıyorsa sorun fizikte değil, zaman adımınızda.

Sabit zaman adımı, oyun geliştirmede öğrenilmesi en geç kalınan ama en çok baş ağrısı çözen konulardan biridir. Belirti genellikle şudur: oyununuz geliştirme makinenizde mükemmel çalışır, ama 144 Hz ekranı olan bir oyuncuda karakter daha yükseğe zıplar, 30 FPS'e düşen bir makinede ise çarpışmalar duvarın içinden geçer. Kod değişmemiştir; değişen tek şey karelerin arasındaki süredir.

Sabit zaman adımlı oyun döngüsü akışı
Fizik sabit adımlarla, çizim ise ekranın hızıyla ilerler.

Sorunun kaynağı: değişken zaman adımı

En yaygın oyun döngüsü şöyle yazılır: her karede geçen süreyi (deltaTime) ölç, fiziği bu süre kadar ilerlet, çiz. Basit hareket için bu yeterlidir; sabit hızla giden bir nesne, konum += hız * deltaTime ile her kare hızında aynı mesafeyi kat eder.

Sorun, ivme devreye girdiğinde başlar. Yerçekimi altında düşen bir cismin konumu zamanın karesiyle değişir ve basit (Euler) tümleştirme yöntemi bu eğriyi adım büyüklüğüne bağlı bir hatayla yaklaşıklar. Adım ne kadar büyükse hata o kadar büyür. Sonuç: 16 ms'lik adımlarla hesaplanan zıplama ile 33 ms'lik adımlarla hesaplanan zıplama farklı yüksekliğe ulaşır. Oyuncu bunu "oyun tutarsız" diye tarif eder.

İkinci ve daha tehlikeli sorun, çarpışma kaçırmadır (tunneling). Kare süresi uzadığında hızlı bir mermi tek adımda duvarın öteki tarafına geçer; arada hiçbir karede duvarla kesişmediği için çarpışma hiç algılanmaz.

Çözüm: fiziği çizimden ayırmak

Doğru yapı, iki farklı saati birbirinden ayırır. Fizik ve oyun mantığı sabit adımlarla (örneğin saniyede 60 kez, yani 16,67 ms) ilerler; çizim ise ekranın verebildiği hızda yapılır. Aradaki uyumsuzluğu bir biriktirici (accumulator) yönetir:

Kod
const SABIT_ADIM = 1.0 / 60.0;
let biriktirici = 0.0;

function dongu(gecenSure) {
    // Uzun donmalardan sonra sonsuz döngüye girmemek için sınır
    biriktirici += Math.min(gecenSure, 0.25);

    while (biriktirici >= SABIT_ADIM) {
        oncekiDurum = mevcutDurum;
        fizigiIlerlet(SABIT_ADIM);
        biriktirici -= SABIT_ADIM;
    }

    const oran = biriktirici / SABIT_ADIM;
    ciz(araDegerle(oncekiDurum, mevcutDurum, oran));
}

Bu döngüde fizik her zaman aynı büyüklükte adımlarla ilerler; kare hızı ne olursa olsun simülasyon aynı sonucu üretir. Kare süresi uzunsa döngü birden fazla fizik adımı atar, kısaysa hiç atmayabilir.

Ara değerleme neden gerekli?

Fizik 60 Hz, ekran 144 Hz ise, bazı karelerde fizik hiç ilerlemez ve nesneler aynı yerde çizilir. Bu, gözle görülür bir takılma (juddering) yaratır. Çözüm, biriktiricide kalan artık süreyi kullanarak bir önceki ve şu anki fizik durumu arasında çizim yapmaktır. Nesne, iki fizik durumunun arasında bir noktada çizilir; hareket akıcı görünür. Bu, fiziği bozmaz çünkü yalnızca görüntüyü etkiler.

Ölüm sarmalından kaçınmak

Biriktiricili döngünün klasik tuzağı şudur: bir kare çok uzun sürerse (örneğin oyun bir yükleme yüzünden yarım saniye donarsa), biriktirici 30 fizik adımı biriktirir. Bu 30 adımı hesaplamak da uzun sürer, bu sırada daha fazla süre birikir ve oyun bir daha asla toparlanamaz. Bu yüzden yukarıdaki kodda Math.min(gecenSure, 0.25) ile geçen süreye üst sınır konur. Bu, simülasyonun bir miktar "zaman kaybetmesi" anlamına gelir — ama oyunun kilitlenmesinden iyidir.

Çok oyunculu oyunlarda ekstra önem

Sabit zaman adımının ikinci büyük faydası determinizmdir: aynı girdi dizisi, aynı adım büyüklüğüyle her makinede aynı sonucu üretir. Bu, ağ üzerinden yalnızca girdileri gönderip simülasyonu her istemcide yeniden çalıştıran mimarilerin (lockstep) temelidir ve bant genişliğini dramatik biçimde azaltır. Aynı şekilde, sunucunun oyuncu girdisini geçmişe dönük yeniden oynatması gereken gecikme telafisi teknikleri de sabit adım olmadan güvenilir çalışmaz; bu konudaki ayrıntılar için gecikme telafisi yazımıza bakabilirsiniz.

Not: kayan noktalı sayılar farklı donanımlarda ufak farklar üretebildiğinden, tam determinizm gereken durumlarda sabit noktalı aritmetik tercih edilir.

Hazır motorlarda durum

Unity'de FixedUpdate tam olarak bu sabit adımlı döngüdür ve fizik kodunuz oraya yazılmalıdır; Update ise kare başına çalışır ve girdi okuma, görsel güncellemeler içindir. Godot'da _physics_process ve _process aynı ayrımı yapar. Motor bu ayrımı sizin için kurar, ancak fizik kodunu yanlış geri çağırıma yazmak aynı hatayı geri getirir — sahada gördüğümüz sorunların çoğu tam olarak budur. Bu tür davranış farklarını yakalamak için otomatik testlerden yararlanmak da mümkündür; hangi testin ne zaman yazılacağı için test rehberimize bakabilirsiniz.

Konunun klasik referansı, sabit zaman adımını popülerleştiren Gaffer On Games — Fix Your Timestep! yazısıdır. Oyun geliştirmeye yeni başlıyorsanız ve önce bitirilebilir bir kapsam belirlemek istiyorsanız ilk oyununuzu bitirme rehberimiz iyi bir başlangıçtır.

Sonuç

Kare hızına bağlı davranan bir oyun, farklı donanımlarda farklı bir oyundur. Sabit zaman adımı bu bağımlılığı ortadan kaldırır: fizik her makinede aynı adımlarla ilerler, çizim ise ekranın hızından faydalanır ve ara değerlemeyle akıcı kalır. Uygulaması yaklaşık yirmi satırlık bir döngü değişikliğidir, ancak çözdüğü sorunlar — tutarsız zıplama, duvardan geçen mermiler, ağda tutmayan senkronizasyon — teker teker ayıklanmaya çalışıldığında haftalar alır. Oyununuzda kare hızını yapay olarak 20 FPS'e düşürüp aynı zıplamayı tekrarlayın; sonuç değişiyorsa, döngünüzü ayırma zamanı gelmiş demektir.

Beğeniniz benzer içerikleri öne çıkarmamıza yardımcı olur.

Sık Sorulan Sorular

Çoğu oyun için saniyede 50-60 adım yeterlidir. Hızlı nesneler ve hassas çarpışma gerektiren oyunlarda 120 adıma çıkmak yaygındır, ancak her artış işlemci maliyetini doğrusal büyütür.

Girdiyi kare başına toplamak, ancak sabit adımda tüketmek en yaygın yaklaşımdır. Böylece hızlı bir tuş basışı, fizik adımları arasında kaybolmaz.

Fizik doğru kalır ama ekranda mikro takılmalar görünür; özellikle fizik frekansı ekran yenileme hızının tam katı değilse fark belirgindir.

Kısmen. Çok hızlı nesneler için adım küçültmek pahalıya mal olur; bu durumda sürekli çarpışma tespiti (CCD) yani hareket yolunun taranması daha doğru çözümdür.

S
superadmin

Bu yazıyı hazırladı. Sorularınız için iletişim sayfasından ulaşabilirsiniz.

Yorumlar (0)

Bu yazıya henüz yorum yapılmamış. İlk yorumu siz yazın!

Yorum Yaz

Yorumunuz onaylandıktan sonra yayınlanır. Ekibimiz gerekirse konuyla ilgili bir yanıt da paylaşır.

En az 10 karakter.