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:
- rAF / Clock / delta 101 → Three · Animasyon döngüsü
- Ms / taban FPS / spike → Platform · FPS
- Tam motor döngüsü, ECS, netcode → Hexovian
- glTF şema, asset pipeline, fizik sabit adım derinliği
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.
| 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.
- Clock ve delta cap kur.
- Input → logic → scene sync → render sırasını kilitle.
- Dilimleri performance mark ile etiketle.
- Tab pause politikasını yaz.
- 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.
setIntervalile 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
requestAnimationFramebağ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
- Delta cap var mı?
- Pause politikası yazıldı mı?
- Input → logic → render sırası korunuyor mu?
- Tek rAF mi, çoğul mu?
- Dilim ölçümü alındı mı?
- Stop/dispose döngüyü kesiyor mu?
- 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.