holodepth

Oyun geliştirme · Web oyun döngüsü

Loop & Zaman Bütçesi: Tarayıcıda Three Oyun Döngüsü

Vitrin döngüsü ile oyun döngüsü aynı rAF çizgisinde görünür; fark, her tikte neyin güncellendiği ve ne kadar ms harcandığıdır. Bu sayfa tarayıcı + Three bağlamında kare bütçesini, delta zamanı ve güncelle-çiz sırasını anlatır: Hexovian motor teorisi veya glTF yükleme değildir.

Holodepth bu yaprağı «rAF nedir?» 101 dersi olarak yazmaz. rAF, Clock ve delta iskeleti Three.js · Animasyon döngüsü & zaman yaprağındadır. Ms dilimi, taban FPS ve spike politikası Platform · FPS & kare bütçesi yaprağındadır. Tam ECS, netcode ve motor çekirdeği Hexovian sınırındadır; burada web/Three oyun köprüsü kurulur.

Sayfayı bitirdiğinizde şu cümleyi kurabilmelisiniz: «Oyun döngüsü rAF üzerinde yaşar; delta ile hız sabitlenir; bütçe dilimleri ayrılır; update → simulate → render sırası yazılır; tab arka planı zamanı bozar; ölçüm olmadan sabit adım ezberi borçtur. Sonraki kapı girdi ve durum makinesidir.»

Pratikte bu ayrım ilk performans şikâyetinde ortaya çıkar: «oyun yavaş» raporu genelde tek bir rAF ölçümüyle değil, dilim bazlı bir profil ile çözülür. Holodepth bu yaprağı üretim öncesi bir sözleşme olarak okumanızı önerir; döngü sırası ve delta politikası kod incelemesinde tek satırlık bir kural haline gelmelidir.

Bu sayfanın sınırı · komşu konularla ayrım

Bu sayfa web/Three oyununda döngü ve zaman bütçesi köprüsünü anlatır:

  • rAF oyun döngüsü vs vitrin döngüsü
  • Delta time ve sabit adım önizleme
  • Kare dilimleri ve disiplin
  • Güncelle → çiz sırası · tab görünürlüğü

Bilinçli olarak dışarıda bırakılan konular:

Kısa ayrım: Bu sayfa «oyun tikinde ne, ne kadar?» · Three animasyon «saat nasıl ölçülür?» · Platform FPS «ms SLA'sı» · Hexovian «motor çekirdeği».

rAF: vitrin döngüsü değil oyun tikidir

requestAnimationFrame ekranın dikey tazeleme ritmine yaslanır; tarayıcı «bir kare çizmeye hazırsın» dediğinde çalışır. Vitrin sahnesinde bu çağrı genelde kameranın hafifçe dönmesi veya bir materyalin parlaması kadar ucuzdur. Oyunda aynı çağrı; girdi tamponunu okumak, FSM geçişini kontrol etmek, entity update hook'larını çalıştırmak ve sahneyi senkronlamak gibi birbirine bağlı beş altı işi sırayla taşır. rAF hattı aynı kalır, yük tamamen değişir.

Döngüyü ilk günlerde tek bir function tick() içinde toplamak makul bir başlangıçtır; oyun büyüdükçe «tick» (mantık) ile «render submission» (çizim) ayrılmalıdır. Aksi halde profil aracı tek bir kalın çubuk gösterir; hangi alt işin şiştiğini görmek için kodu elle bölmeniz gerekir. Holodepth: fonksiyon sınırlarını dilim sınırlarıyla hizalayın, ikisi ayrı yaşarsa ölçüm de ayrı yaşar.

Tek sahnede birden fazla bağımsız requestAnimationFrame döngüsü açmak yaygın bir tuzaktır: her entity kendi rAF'ini planlarsa tarayıcı aynı karede birden çok geri çağırma sıraya alır, sıralama garantisi kaybolur ve dilim ölçümü anlamsızlaşır. Tek döngü, tek çağrı — entity'ler bu döngünün içinde gezilir.

rAF metronomdur; oyun notası, tik içinde çalınan işlerdir.

Delta time: hızı FPS'ten ayırın

clock.getDelta()'nun döndürdüğü saniye değeri, hareket denklemlerinde çarpan olarak kullanılmalıdır: position += speed * delta. 60 FPS'te delta ~0,0167 s, 30 FPS'te ~0,033 s'dir — aynı speed sabiti her ikisinde de saniyede aynı mesafeyi kat eder. Sabit «her kare 0,1 birim ilerle» yazmak bu ilişkiyi koparır; 30 FPS'te oyun yarı hızda, 120 FPS'te iki kat hızda oynanır ve zorluk dengesi cihaza göre değişir.

Delta üst sınırı (cap) olmadan sekme arka plana gidip döndüğünde getDelta() saniyeler mertebesinde bir değer döndürebilir; bu tek kare içinde entity'leri duvarların içine ışınlar, fizik gövdelerini patlatır. Holodepth varsayılanı: delta'yı ör. 0,1 s ile clamp edin, kalan zamanı yok sayın — «kayıp» bir animasyon karesi, jank'tan daha ucuzdur.

Sabit adım simülasyonu fizik izleğinde derinleşir; burada yalnızca «mantık güncellemesi delta ile, fizik ayrı saat ile yaşayabilir» önizlemesi yeter. İki saat kavramını erken yazın — mantık saatinin delta ile, fizik saatinin sabit dt ile ilerlediğini net biçimde ayırmak, sonraki Sabit timestep & performans köprüsüne zemin hazırlar.

Kare bütçesi dilimleri

16,7 ms tek satır değildir. Holodepth oyun döngüsü için kaba dilimler: input toplama, mantık/state, entity güncelleme, render hazırlığı, renderer.render. Hangi dilim Platform FPS yaprağındaki SLA ile çelişiyorsa oraya müdahale edilir; «genel yavaş» teşhisi yetersizdir.

Kaba bütçe paylaşımı (60 Hz hedef)
Dilim Örnek iş Risk
Input Keyboard, pointer, gamepad DOM listener birikimi
Sim / logic FSM, skor, AI lite GC, geniş dizi map
Scene sync Mesh pozisyon yazma Matrix her nesnede
Render Draw, state Overdraw, shader

Dilimler arasında pay sabit değildir; vitrin sahnesinde render ağır basar, yoğun bir arcade'de sim/logic dilimi büyür. Holodepth önerisi: her dilime performance.mark ile isim verin, ölçümü ürün kararına göre okuyun — «input dilimi 2 ms mi 8 ms mi» sorusu, hangi entity sayısında test edildiğine bağlıdır.

Sıra: input → update → render

Holodepth varsayılan sıra: önce girdi tamponunu oku, sonra oyun mantığını ve entity hook'larını çalıştır, en sonda tek render. Render öncesinde mantık bitmeli; aksi halde bir kare gecikmeli input hissi oluşur. Çift render veya ara render nadiren gerekir; çoğu web oyununda borçtur.

Three sahne grafı ile oyun state'i aynı nesne değildir; döngü sırası bu ayrımı görünür kılar. Entity yaprağında mesh eşlemesi netleşir: update() mantığı yazar, syncToView() mesh'e taşır, render() yalnızca okur.

Sıra karışırsa hata izi karmaşıklaşır: bir entity'nin bir kare önceki pozisyonunu mu yoksa bu karedeki pozisyonunu mu gösterdiği belirsizleşir. Holodepth: bu üç adımı tek fonksiyonda numaralandırın, sıralamayı yorum satırına değil kod yapısına yazın.

Sekme görünürlüğü ve zaman sıçraması

Arka planda rAF seyrekleşir veya durur (bazı tarayıcılarda saniyede bir kareye düşer); dönüşte büyük delta gelir. Holodepth: document.visibilityState ile döngüyü duraklatın veya mantığı «menü duraklatıldı» moduna alın. Çok oyunculu netcode bu sayfada yok; tek oyunculu web oyununda bile zaman sıçraması oynanışı bozar — düşman aniden ekranın öbür ucunda belirir.

  • Pause: delta sıfırla, input buffer temizle
  • Cap: max delta ile fizik patlamasını kes
  • Resume: bir kare «ısınma» atlama

Mobil tarayıcılarda sekme değişimi dışında ekran kilidi de aynı sıçramayı üretir; visibilitychange dinleyicisi tek noktadan hem tab hem kilit senaryosunu kapsar. Holodepth: pause anındaki input buffer'ı atmak, resume sonrası «birikmiş komutları» aniden boşaltmaktan daha güvenlidir.

Döngü disiplini

Önceki beş dilim ayrı ayrı doğru olsa da, disiplin onları tek bir tekrarlanabilir prosedüre bağlar. Holodepth bu maddeleri bir PR şablonu gibi okur: her yeni oyun özelliği bu listeyi bozmadan eklenmelidir.

  1. Clock ve delta cap kur.
  2. Input → logic → scene sync → render sırasını kilitle.
  3. Dilimleri performance mark ile etiketle.
  4. Tab pause politikasını yaz.
  5. Dispose ve döngü durdurma bağla.

«Daha akıcı hissettim» kapı değildir; Platform FPS ile taban ms karşılaştır. Öznel izlenim, ölçüm olmadan hangi değişikliğin işe yaradığını söyleyemez — bazen «iyileştirme» yalnızca test cihazının o an daha az arka plan işi yapmasından kaynaklanır.

Güvenli oyun döngüsü: simüle iskelet

Örnekler öğretim amaçlıdır; gerçek projede modül sınırları ve hata yönetimi eklenir. Aşağıdaki iskelet delta cap, pause politikası ve tek render çağrısını aynı dosyada gösterir — üç maddeyi ayrı dosyalara dağıtmak erken optimizasyon değildir, okunabilirlik kararıdır.

Simüle · delta cap + pause + tek render.

import * as THREE from 'three'

const MAX_DELTA = 1 / 30 // Cap · arka plan dönüşü patlamasını kes
let rafId = 0
let paused = false

export function createGameLoop({ renderer, scene, camera, onTick }) {
  const clock = new THREE.Clock(false)

  function handleVisibility() {
    paused = document.visibilityState !== 'visible'
    if (paused) clock.getDelta() // biriken süreyi at
  }

  document.addEventListener('visibilitychange', handleVisibility)

  function frame() {
    rafId = requestAnimationFrame(frame)
    if (paused) return

    const raw = clock.getDelta()
    const dt = Math.min(raw, MAX_DELTA)

    // 1 input · 2 logic · 3 scene — onTick sözleşmesi
    if (typeof onTick === 'function') onTick(dt)

    renderer.render(scene, camera)
  }

  return {
    start() {
      clock.start()
      rafId = requestAnimationFrame(frame)
    },
    stop() {
      cancelAnimationFrame(rafId)
      document.removeEventListener('visibilitychange', handleVisibility)
    },
  }
}

Simüle · dilim işaretleme (ölçüm köprüsü).

/** Geliştirme modunda dilim süresi · üretimde kapat */
export function markSlice(name, fn) {
  if (!import.meta.env?.DEV) return fn()
  const t0 = performance.now()
  const result = fn()
  const ms = performance.now() - t0
  if (ms > 2) console.info(`[slice] ${name}: ${ms.toFixed(2)} ms`)
  return result
}

// Kullanım: markSlice('logic', () => updateEntities(dt))

Yanlış döngü kalıpları

Aşağıdaki kalıplar tek seferlik kod incelemesinde her zaman yakalanmaz; genelde ürün büyüdükçe veya düşük güçlü bir cihazda test edildiğinde ortaya çıkar.

  • setInterval ile render (tearing, pil)
  • Delta capsiz arka plan dönüşü
  • Her entity'de ayrı rAF
  • Render öncesi await ile ağır IO
  • Ölçüm yokken «sabit 60 FPS varsaydım»
  • Aynı döngüye ikinci kez requestAnimationFrame bağlamak (hot reload artığı)
  • Hexovian motor döngüsünü birebir kopyalama beklentisi

Bu tuzakların çoğu ölçüm eklendiğinde kendini gösterir; performans şikâyeti geldiğinde ilk adım tahmin değil, dilim bazlı profil almaktır.

Üç döngü profili

Üç profil de aynı rAF omurgasını paylaşır; ayrışma saat politikasında ve ölçüm disiplininde yaşar. Ürün kararınızı bu üçünden birine yakınsatın, ikisini karıştırmayın.

  • 01 · Döngü

    Arcade lite

    Saat
    Değişken delta + cap
    Hedef
    60 Hz web
    Risk
    Fizik ile karıştırma
  • 02 · Döngü

    Sim önizleme

    Saat
    Logic delta · fizik ayrı
    Ölçüm
    Dilim mark zorunlu
    Risk
    İki saat senkronu
  • 03 · Döngü

    Mobil tasarruf

    Pause
    visibility zorunlu
    Bütçe
    30 FPS modu opsiyon
    Risk
    Input buffer taşması

Ölçüm köprüsü

Döngü refaktörü öncesi ve sonrası aynı kamera yolunda taban ms alın. Platform FPS yaprağındaki spike politikası ile hizalayın: logic dilimi mi render mı şişti? GPU bound sorunu döngü sırası değiştirerek çözülmez.

Ölçümü tek bir cihazda almak yeterli değildir; orta segment bir mobil ve masaüstü tarayıcı arasında en az bir karşılaştırma yapın. Holodepth: performance.mark / performance.measure çiftini üretim build'inde de bırakın — geliştirici konsolu kapalıyken de dilim verisi toplanabilsin.

Holodepth perspektifleri

Saat önce, estetik sonra

Lerp ve animasyon mixer güzel hareket ister; önce delta ve sıra doğru olmalı. Three animasyon yaprağı estetik katmandır — orada öğrenilen easing ve mixer teknikleri, burada kurulan saat üzerine oturur, onun yerine geçmez.

Köprü, motor değil

Bu izlek Unreal/Unity döngüsünü taşımaz; tarayıcı kısıtlarında yaşar. Sekme görünürlüğü, tek iş parçacığı ve rAF metronomu web'e özgü sınırlardır. Motor derinliği Hexovian'dadır; oradaki döngü web sayfası kısıtlarını bilmez.

Ölçüm, ezberden önce gelir

«Sabit 60 FPS varsayalım» rahat bir başlangıçtır; üretimde düşük güçlü bir cihaz bu varsayımı hemen bozar. Holodepth: delta cap ve pause politikasını yazdıktan sonra bile, Platform FPS ile bir kez taban ms alın.

Teşhis maddeleri

  1. Delta cap var mı?
  2. Pause politikası yazıldı mı?
  3. Input → logic → render sırası korunuyor mu?
  4. Tek rAF mi, çoğul mu?
  5. Dilim ölçümü alındı mı?
  6. Stop/dispose döngüyü kesiyor mu?
  7. glTF veya ECS bu sayfaya sızmış mı?

Holodepth içgörüsü

Web oyun döngüsü «sürekli çiz» değil «her tikte doğru işi sırayla yap» disiplinidir. İzlek: döngü → girdi → entity → fizik köprüsü.

Sonraki kapı: girdi ve durum

Döngü iskeleti hazırsa sıradaki yaprak girdi yüzeyi ve hafif durum makinesidir. Klavye, pointer ve gamepad aynı tikte toplanır; FSM oyun fazlarını ayırır. Entity eşlemesi üçüncü yaprakta mesh ile buluşur.