Bir oyun sunucusunun kalbinde tek bir şey atar: döngü. Game loop tick rate, sunucunun saniyede kaç kez dünyayı güncellediğini, yani fiziği işlediğini, hareketleri uyguladığını ve istemcilere durum gönderdiğini belirler. Bu sayıyı ve döngünün nasıl kurulduğunu doğru tasarlamazsanız; oyuncular ışınlanan karakterler, tutarsız çarpışmalar ve hızlandıkça bozulan oynanış ile karşılaşır. Bu yazıda tick'in ne olduğunu, neden sabit zaman adımı kullanmanız gerektiğini ve kararlı bir sunucu döngüsünü C++ ile nasıl kuracağınızı anlatıyorum.
Tick nedir, tick rate ne anlama gelir?
Bir tick, simülasyonun ilerlediği tek bir adımdır. Her tick'te sunucu oyun durumunu sabit bir miktar zaman ileri taşır: girdileri okur, karakterleri hareket ettirir, çarpışmaları çözer ve sonucu hesaplar. Tick rate ise bunun saniyedeki sayısıdır ve genellikle Hz ile ifade edilir. 20 tick/s, dünyanın saniyede 20 kez (her 50 ms'de bir) güncellendiği anlamına gelir.
Yaygın değerler oyun türüne göre değişir:
- 10–20 Hz — MMO ve nişancı olmayan rol yapma oyunları; tepki süresi kritik değildir.
- 30 Hz — birçok aksiyon ve hayatta kalma oyunu için makul bir denge.
- 60–128 Hz — rekabetçi FPS oyunları; nişan alma ve isabet kaydı hassasiyeti yüksek olmalıdır.
Tick rate'i artırmak oyunu daha duyarlı yapar ama CPU yükünü ve bant genişliğini doğru orantılı büyütür. 64 Hz, 32 Hz'in iki katı işlem ve genellikle iki katı paket demektir. Doğru sayı, oyununuzun gerektirdiği hassasiyet ile sunucunuzun kaldırabileceği yük arasındaki dengedir.
Neden sabit zaman adımı (fixed timestep)?
Acemi bir döngü genellikle şöyle yazılır: "geçen süreyi ölç, her şeyi o süre kadar ilerlet." Buna variable timestep denir ve sorunludur. delta her karede değiştiğinde fizik deterministik olmaz: aynı girdiyle iki farklı sonuç çıkabilir, hızlı nesneler çarpışmaları atlayabilir (tunneling), ve sunucu ile istemci aynı sayıyı bulamaz.
Sabit zaman adımı bu sorunu çözer. Simülasyonu her zaman aynı dt ile (örneğin 1/30 saniye) ilerletirsiniz. Gerçek zaman daha hızlı veya yavaş aksa bile, mantık her adımda aynı miktarda ilerler. Bu determinizm; tekrar oynatma (replay), hile tespiti ve sunucu yetkili (server-authoritative) mimari için şarttır.
Mantık burada bir biriktirme (accumulator) ile çalışır: geçen gerçek süreyi bir kovaya eklersiniz, kova bir dt'yi aştıkça sabit adımlar atarsınız. CPU bir kare geç kalırsa, döngü birikeni boşaltmak için art arda birkaç tick işler ("catch-up"). Böylece simülasyon zamanı gerçek zamanın gerisinde kalmaz.
Sabit adımlı bir sunucu döngüsü
Aşağıdaki C++ örneği biriktirme tabanlı klasik döngüyü gösterir. std::chrono ile yüksek çözünürlüklü saat kullanıyoruz:
#include <chrono>
#include <thread>
using clock_t = std::chrono::steady_clock;
using namespace std::chrono;
const int TICK_RATE = 30;
const double DT = 1.0 / TICK_RATE; // saniye cinsinden sabit adım
void run_server() {
auto previous = clock_t::now();
double accumulator = 0.0;
while (server_running) {
auto now = clock_t::now();
double frame = duration<double>(now - previous).count();
previous = now;
// Tek bir geç kareyle spiral of death'e girmeyi önle
if (frame > 0.25) frame = 0.25;
accumulator += frame;
while (accumulator >= DT) {
process_input(); // istemci paketlerini uygula
update_world(DT); // fizik + oyun mantığı, hep aynı DT
accumulator -= DT;
}
broadcast_state(); // güncel durumu istemcilere gönder
// CPU'yu boşa döndürmemek için bir sonraki tick'e kadar uyu
auto next = previous + duration_cast<clock_t::duration>(duration<double>(DT));
std::this_thread::sleep_until(next);
}
}
Buradaki üç kritik nokta: DT her zaman sabittir (saatten gelen değişken süre değil), frame süresine bir tavan koyulur (aksi halde sunucu takılırsa accumulator şişer ve döngü hiç bitmeyen tick'lerle kilitlenir — buna "spiral of death" denir) ve sleep_until ile bir sonraki tick'e kadar beklenir ki CPU %100'de boşa dönmesin.
Uyku doğruluğu ve zamanlama kayması
sleep_for yerine sleep_until kullanmak önemlidir. sleep_for(33ms) her seferinde "33 ms uyu" der ama uyandırma gecikmesi her turda birikerek tick'leri yavaşça kaydırır. sleep_until(next) ise mutlak bir hedef zamanına nişan alır; bir tick geç uyandıysanız bir sonraki daha erken uyanarak ortalamayı korur.
İşletim sistemi uyku çözünürlüğü mükemmel değildir: Linux'ta birkaç yüz mikrosaniye, Windows'ta varsayılan zamanlayıcıyla milisaniyeler oynayabilir. Yüksek tick rate (64+ Hz) hedefliyorsanız, hedef zamana çok yakınken kısa bir busy-wait ile saati keskinleştirmek yaygın bir tekniktir; ancak bu CPU çekirdeğini meşgul eder, gerektiği yerde ölçülü kullanın.
Tick rate, ağ gönderim hızı ve istemci tarafı
Sunucunun simülasyon hızı ile ağa durum gönderme hızı aynı olmak zorunda değildir. Örneğin sunucu 60 Hz simüle edip durumu yalnızca 20 Hz yayınlayabilir; bu bant genişliğini düşürürken simülasyon kalitesini korur. İstemci ise aradaki boşluğu interpolation (geçmiş iki durum arasında yumuşatma) ve kendi girdisi için prediction ile doldurur.
Pratik bir kontrol listesi:
- Simülasyon hızını oyunun gerektirdiği hassasiyete göre seçin, ağ hızını ayrı düşünün.
- Sunucuyu yetkili tutun: istemci girdi yollar, sonucu sunucu hesaplar.
- Her durum paketine bir tick numarası koyun ki istemci hangi adımı gördüğünü bilsin.
- Tick süresini ölçün; bir tick işlemi
DT'yi aşmaya başlıyorsa ya tick rate'i düşürün ya da işi optimize edin.
Sık karşılaşılan hatalar
- Değişken delta ile fizik: deterministliği bozar; isabet kaydı ve replay güvenilmez olur.
- Tavansız accumulator: sunucu bir kez takılınca "spiral of death" ile tamamen kilitlenir.
- Tick içinde bloklayan G/Ç: disk veya senkron veritabanı çağrısı tüm simülasyonu bekletir; bunları ayrı bir thread veya kuyruğa taşıyın.
- Tick rate'i kör artırmak: CPU ve bant genişliği lineer büyür; önce darboğazı ölçün.
Sık Sorulan Sorular
Tick rate ne kadar olmalı?
Oyun türüne bağlı. Yavaş tempolu MMO için 10–20 Hz yeterlidir; aksiyon oyunları için 30 Hz dengeli bir başlangıçtır; rekabetçi nişancılar 60–128 Hz hedefler. Önce oyunun gerektirdiği tepki süresini belirleyin, sonra sunucunun bunu kaldırıp kaldırmadığını ölçün.
Sabit zaman adımı ile değişken zaman adımı arasındaki fark nedir?
Sabit adımda simülasyon hep aynı dt ile ilerler ve deterministiktir; değişken adımda her kare geçen gerçek süre kullanılır, bu da fiziği tutarsız ve tekrarlanamaz yapar. Sunucu yetkili oyunlarda neredeyse her zaman sabit adım tercih edilir.
Tick rate'i artırırsam gecikme azalır mı?
Bir miktar evet: daha yüksek tick rate, sunucunun girdiyi işlemesi arasındaki bekleme süresini kısaltır. Ama ağ gidiş-dönüş süresini (ping) değiştirmez. Gerçek gecikme hissi büyük ölçüde ağ ve istemci tarafı interpolation/prediction tasarımına bağlıdır.
Sunucu döngünüz kararsız mı, yoksa doğru tick rate'i mi arıyorsunuz? Oyun sunucunuzun mimarisini birlikte gözden geçirip kararlı, deterministik bir game loop kuralım — benimle iletişime geçin.