Oyun geliştirme · Three + fizik köprüsü
Sabit Timestep & Performans: Accumulator Köprüsü
requestAnimationFrame değişken aralık getirir; fizik motoru
sabit adım ister. Bu sayfa accumulator desenini, görsel
enterpolasyonu ve kare bütçesi bağlantısını anlatır. Platform FPS dilimi
tekrarı değil; oyun fizik köprüsünün zaman sözleşmesidir.
Holodepth bu yaprağı motor içi integrator teorisi veya netcode rollback dersi olarak yazmaz. Web oyun döngüsü Loop & zaman bütçesi yaprağındaki rAF omurgasını fizik adımına bağlar. Çarpışma hook’ları Çarpışma tepkisi köprüsü yaprağında mühürlendi. Ms / jank SLA ve FPS mantığı Platform · FPS yaprağındadır. Deterministik çok oyunculu fizik ve motor içi Hexovian izleğindedir.
Sayfayı bitirdiğinizde şu cümleyi kurabilmelisiniz: «rAF değişken dt verir; fizik sabit dt ile accumulator’da adımlanır. Görsel ara kare enterpole edilir. Spiral of death clamp edilir. Platform FPS ile ölçülür. Oyun geliştirme izleği burada kapanır.»
Accumulator deseni ilk bakışta fazladan karmaşıklık gibi görünür; ama
tek bir hatalı world.step(frameDt) çağrısının cihazlar
arası tutarsızlık üretmesi, bu karmaşıklığın maliyetini genelde birkaç
saat içinde geri öder.
Bu sayfanın sınırı · komşu konularla ayrım
Bu sayfa Three + fizikte sabit timestep ve performans köprüsünü anlatır:
- Accumulator deseni
- Fixed
dtvs variable render - Görsel enterpolasyon / extrapolation notu
- Spiral of death ve clamp
Bilinçli olarak dışarıda bırakılan konular:
- Genel rAF kurulumu → Loop & zaman bütçesi
- World + body kurulumu → Rapier / Cannon yüzeyi
- Collision hook → Çarpışma tepkisi köprüsü
- FPS ms tabloları, profil araçları → Platform · FPS
- Netcode fizik, rollback, sunucu sim → Hexovian
- glTF ve asset pipeline
Kısa ayrım: Bu sayfa «fizik adımı zamanı nasıl sabitlenir?» · Loop bütçesi «rAF ve oyun fazları» · Platform FPS «ms dilimi ve ölçüm» · Hexovian «replike deterministik sim».
Neden sabit fizik adımı?
Variable dt ile fizik adımı: sekme yavaş cihazda artar, hızlı
cihazda azalır; aynı girdi farklı sonuç üretir. Sabit adım (ör. 1/60 s)
simülasyonu tekrarlanabilir kılar; render ise monitör hızına göre akar.
Holodepth: «her rAF’te bir world.step(frameDt)» varsayılanı web oyunu için risklidir; accumulator köprüsü üretim standardıdır.
Tekrarlanabilirlik sadece bug ayıklamayı kolaylaştırmaz; oynanış dengesini de korur. 144 Hz bir monitörde oynayan bir oyuncu, 60 Hz'de oynayan bir oyuncudan farklı bir zıplama yüksekliği veya çarpışma hassasiyeti yaşamamalıdır — sabit adım bu garantiyi verir, variable adım vermez.
Render akışkandır; fizik adımı sabittir.
Accumulator deseni
Her karede gerçek süre frameDt accumulator’a eklenir. Accumulator
fixed dt’den büyük olduğu sürece fizik adımı çalışır ve
accumulator’dan dt düşülür. Render, adımlar bittikten sonra
(enterpolasyon ile) yapılır.
Holodepth pseudo-akış: ölç → biriktir → while (acc ≥ dt) step → sync / interpolate → render. Bu sıra mühürlenmezse debug zordur.
Bir karede sıfır fizik adımı çalışması normaldir (yüksek FPS'te
accumulator henüz dt'yi doldurmamış olabilir); bunu hata
sanıp «her karede en az bir adım» zorlamak, tam da kaçınılmak istenen
variable step davranışına geri döner.
rAF köprüsü ve oyun fazları
Loop bütçesi yaprağındaki faz sırası korunur: input → simülasyon (fizik adımları) → senkron → render. Fizik adımı sayısı kare başına değişebilir; input okuma kare başına bir kez tipik kalır.
Çarpışma hook’ları fizik adımı içinde veya hemen sonrasında çalışır; render öncesi state tutarlı olmalıdır.
Girdi bir karede birden fazla fizik adımına yayılıyorsa (ör. 3 adım çalıştı), her adımda aynı input snapshot'ı kullanılmalıdır — adımlar arasında input'u yeniden okumak, tek bir tuş basışının üç kez sayılmasına yol açabilir.
Görsel enterpolasyon
Son fizik durumu ile bir önceki durum arasında
alpha = accumulator / dt ile mesh pozisyonu enterpole edilir;
hareket pürüzsüz görünür. Holodepth: enterpolasyon görsel içindir; oyun
mantığı son tamamlanan fizik adımına bağlanır.
Extrapolation (tahmin) düşük gecikmede kullanılabilir; yanlış pozisyon riski vardır. Web vitrinlerinde enterpolasyon yeterlidir.
Enterpolasyon için önceki ve şimdiki fizik pozisyonunu ayrı alanlarda
tutmak gerekir (prevPosition, currentPosition);
mesh'e doğrudan motor pozisyonunu yazmak bu iki değeri birbirine
karıştırır ve alpha hesaplaması anlamsızlaşır.
Spiral of death ve clamp
Kare süresi uzayınca accumulator çok adım ister; fizik CPU’yu yiyerek bir
sonraki kareyi daha da geciktirir. Holodepth: frameDt üst
sınırı (ör. 0.25 s) ve kare başına maksimum adım sayısı clamp edilir.
Geri kalan accumulator bilinçli drop veya «yavaş çekim» politikası ile
yönetilir.
Clamp olmadan sabit adım üretimde çöküş üretir; ölçüm Platform · FPS ile doğrulanır.
maxSteps sınırına ulaşıldığında oyunun «yavaşladığını»
hissettirmek, aniden donmasından daha iyi bir kullanıcı deneyimidir —
Holodepth önerisi, drop edilen zamanı sessizce yok saymak yerine bir
«yavaş çekim» (slow-mo) sinyaline çevirmektir.
Platform FPS ile ölçüm
Fizik adımı ms dilimini ayrı sayın: «render hafif, fizik ağır» teşhisi
sık görülür. Platform FPS yaprağındaki kare bütçesi dilimini fizik
step çağrılarına etiketleyin. Hedef: sabit adım sayısı +
kabul edilebilir p99 gecikme.
Holodepth: accumulator doğruyken bile çok gövde veya geniş collision hook’ları bütçeyi aşar; profil önce fizik, sonra render ayırır.
Fizik dilimini ölçerken adım sayısını da loglayın: aynı ms değeri, bir karede 1 adım ile 4 adım çalıştığında çok farklı anlam taşır — ikincisi accumulator'ın geride kaldığının işaretidir.
Güvenli döngü: simüle accumulator
Örnekler öğretim amaçlıdır; motor step(dt) imzasını sürümünüzde
doğrulayın. Sonsuz while döngüsüne karşı maxSteps zorunludur.
Simüle · fixed step game loop (yüzey şekli).
const FIXED_DT = 1 / 60
const MAX_FRAME_DT = 0.25
const MAX_STEPS_PER_FRAME = 5
let accumulator = 0
let lastTime = performance.now()
let previousStates = new Map()
let currentStates = new Map()
export function createFixedStepLoop({ world, syncBodies, render }) {
function tick(now) {
let frameDt = (now - lastTime) / 1000
lastTime = now
frameDt = Math.min(frameDt, MAX_FRAME_DT)
accumulator += frameDt
let steps = 0
while (accumulator >= FIXED_DT && steps < MAX_STEPS_PER_FRAME) {
snapshotPrevious(previousStates, currentStates)
world.step(FIXED_DT)
syncBodies(currentStates)
accumulator -= FIXED_DT
steps += 1
}
const alpha = accumulator / FIXED_DT
interpolateMeshes(previousStates, currentStates, alpha)
render()
requestAnimationFrame(tick)
}
requestAnimationFrame(tick)
}
function snapshotPrevious(prev, curr) {
for (const [id, state] of curr) prev.set(id, { ...state })
}
function interpolateMeshes(prev, curr, alpha) {
for (const [id, now] of curr) {
const was = prev.get(id)
if (!was || !now.mesh) continue
now.mesh.position.lerpVectors(was.position, now.position, alpha)
now.mesh.quaternion.slerpQuaternions(was.quaternion, now.quaternion, alpha)
}
}
Simüle · adım sayısı telemetrisi (ölçüm kancası).
// Platform FPS yaprağındaki ms dilimi ile birlikte okuyun.
export function createStepMetrics() {
const samples = []
return {
record(steps, physicsMs) {
samples.push({ steps, physicsMs, t: performance.now() })
if (samples.length > 120) samples.shift()
},
p99PhysicsMs() {
if (!samples.length) return 0
const sorted = samples.map((s) => s.physicsMs).sort((a, b) => a - b)
const idx = Math.floor(sorted.length * 0.99)
return sorted[Math.min(idx, sorted.length - 1)]
},
avgSteps() {
if (!samples.length) return 0
const sum = samples.reduce((n, s) => n + s.steps, 0)
return sum / samples.length
},
}
}
// Holodepth: avgSteps sürekli MAX'e yakınsa clamp veya gövde sayısı gözden geçirilir.
Performans notları
Sabit adım gövde sayısıyla doğrusal ölçeklenir; sleeping, broadphase ve basit collider seçimi motor yüzeyi yaprağıyla birlikte düşünülür. Hook maliyeti adım başına eklenir; stay spam fizikten bağımsız CPU yer.
Worker’da fizik adımı ileri seviye konudur; Holodepth web oyunu köprüsü ana iş parçacığında accumulator ile başlar. Derin paralel sim Hexovian’dadır.
Sabit dt'yi 1/60'tan 1/120'ye düşürmek «daha kararlı»
hissettirebilir, ama adım sayısını ikiye katlayarak CPU maliyetini de
ikiye katlar — bu değişimi yalnızca ölçümle doğrulanmış bir kararlılık
sorunu varsa yapın, varsayılan olarak değil.
Zaman tuzakları
- Variable step: her rAF’te frameDt vermek.
- Clamp yok: spiral of death.
- maxSteps yok: donma riski.
- Enterpolasyon yok: titreyen görsel.
- Oyun mantığı alpha’da: tutarsız skor.
- Ölçüm yok: «hissettim» optimizasyonu.
- Netcode determinizm: Hexovian konusu.
Üç zaman profili
Üç profil de aynı accumulator iskeletini paylaşır; ayrışan şey
dt büyüklüğü, clamp politikası ve enterpolasyon
kalitesidir.
-
01 · Zaman
Vitrin fizik
- dt
- 1/60 · az gövde
- Interp
- Basit lerp
- Risk
- Variable step tembelliği
-
02 · Zaman
Web oyunu
- Clamp
- maxSteps + frameDt cap
- Ölçüm
- Platform FPS
- Risk
- Spiral
-
03 · Zaman
Yoğun sahne
- dt
- 1/120 veya alt gövde
- Politika
- Drop accumulator
- Risk
- Ölçeksiz adım
Holodepth perspektifleri
Önce ölç, sonra dt oyna
1/120’ye geçmeden önce adım ms ve gövde sayısını yazın; Platform FPS ile doğrulayın.
İzlek kapanışı
Döngü, girdi, entity, fizik yüzeyi, çarpışma tepkisi ve şimdi sabit adım — altı yaprak birlikte web/Three için minimal ama üretime hazır bir oyun köprüsü oluşturur. Daha derin motor ihtiyacı Hexovian sınırında karşılanır.
Teşhis maddeleri
- Accumulator var mı?
- frameDt clamp ediliyor mu?
- maxSteps sınırı var mı?
- Görsel enterpolasyon uygulanıyor mu?
- Oyun state son adıma mı bağlı?
- Fizik ms Platform FPS ile etiketli mi?
- Netcode determinizm Hexovian’a kaymış mı?
Holodepth içgörüsü
«Fizik titriyor» çoğu zaman collider değil; variable step veya enterpolasyon eksikliğidir. Önce accumulator okuyun.
İzlek kapanışı: oyun geliştirme 6/6
Three + fizik köprüsü izleği burada kapanır. Döngü, girdi, entity, motor yüzeyi, çarpışma tepkisi ve sabit adım birlikte web Three oyununun zaman ve fizik omurgasını verir. Geniş döngü ve bütçe için Loop & zaman bütçesi yaprağına dönün; motor içi, ECS ve netcode için Hexovian izleğine geçin. Holodepth oyun geliştirme yolu 6/6 tamamlanmıştır.