Hareket, Time.deltaTime kullanılmasına rağmen kare hızına bağlı gibi görünüyor


13

Ben denir Unity, bir oyun nesnesini taşımak için gerekli çeviri hesaplamak için aşağıdaki kodu var LateUpdate. Anladığım kadarıyla, benim kullanımım Time.deltaTime, son çeviri kare hızını bağımsız hale getirmelidir (lütfen dikkat edin CollisionDetection.Move(), sadece rekolte yapmak).

public IMovementModel Move(IMovementModel model) {    
    this.model = model;

    targetSpeed = (model.HorizontalInput + model.VerticalInput) * model.Speed;

    model.CurrentSpeed = accelerateSpeed(model.CurrentSpeed, targetSpeed,
        model.Accel);

    if (model.IsJumping) {
        model.AmountToMove = new Vector3(model.AmountToMove.x,
            model.AmountToMove.y);
    } else if (CollisionDetection.OnGround) {
        model.AmountToMove = new Vector3(model.AmountToMove.x, 0);
    }

    model.FlipAnim = flipAnimation(targetSpeed);
    // If we're ignoring gravity, then just use the vertical input.
    // if it's 0, then we'll just float.
    gravity = model.IgnoreGravity ? model.VerticalInput : 40f;

    model.AmountToMove = new Vector3(model.CurrentSpeed, model.AmountToMove.y - gravity * Time.deltaTime);

    model.FinalTransform =
        CollisionDetection.Move(model.AmountToMove * Time.deltaTime,
            model.BoxCollider.gameObject, model.IgnorePlayerLayer);
    // Prevent the entity from moving too fast on the y-axis.
    model.FinalTransform = new Vector3(model.FinalTransform.x,
        Mathf.Clamp(model.FinalTransform.y, -1.0f, 1.0f),
        model.FinalTransform.z);

    return model;
}

private float accelerateSpeed(float currSpeed, float target, float accel) {
    if (currSpeed == target) {
        return currSpeed;
    }
    // Must currSpeed be increased or decreased to get closer to target
    float dir = Mathf.Sign(target - currSpeed);
    currSpeed += accel * Time.deltaTime * dir;
    // If currSpeed has now passed Target then return Target, otherwise return currSpeed
    return (dir == Mathf.Sign(target - currSpeed)) ? currSpeed : target;
}

private void OnMovementCalculated(IMovementModel model) {
    transform.Translate(model.FinalTransform);
}

Oyunun karesini 60FPS'ye kilitlersem, nesnelerim beklendiği gibi hareket eder. Ancak, kilidini Application.targetFrameRate = -1;açarsam ( ), bazı nesneler çok daha yavaş bir hızda hareket edecek, o zaman 144hz monitörde ~ 200FPS elde ederken beklenir. Bu, yalnızca bağımsız bir yapıda gerçekleşiyor gibi görünüyor, Unity editöründe değil.

Editör içindeki nesne hareketinin GIF'i, kilidi açılmış FPS

http://gfycat.com/SmugAnnualFugu

Bağımsız yapı içindeki nesne hareketinin GIF'i, kilidi açılmış FPS

http://gfycat.com/OldAmpleJuliabutterfly


2
Bunu okumalısın. Zaman kovalama istediğiniz ve sabit zaman adımları! gafferongames.com/game-physics/fix-your-timestep
Alan Wolfe

Yanıtlar:


30

Kare tabanlı simülasyonlar, güncellemeler doğrusal olmayan değişim oranlarını telafi edemediğinde hatalarla karşılaşır.

Örneğin sıfırın konum ve hız değerleriyle başlayan bir nesneyi, sabit bir ivmelenmesi ile düşünün.

Bu güncelleme mantığını uygularsak:

velocity += acceleration * elapsedTime
position += velocity * elapsedTime

Bu sonuçları farklı kare hızlarında bekleyebiliriz: resim açıklamasını buraya girin

Hata, nihai hızın tüm çerçeve için uygulanmış gibi işlenmesinden kaynaklanır. Bu, Sağ Riemann Toplamına benzer ve hata miktarı kare hızına göre değişir (farklı bir işlevde gösterilmiştir):

MichaelS'ın işaret ettiği gibi , kare süresi yarıya düştüğünde bu hata yarıya iner ve yüksek kare hızlarında önemsiz hale gelebilir. Öte yandan, performans artışı veya uzun süren kareler yaşayan oyunlar bunun öngörülemeyen davranışlar ürettiğini görebilir.


Neyse ki kinematik , doğrusal hızlanmanın neden olduğu yer değiştirmeyi doğru bir şekilde hesaplamamızı sağlar:

d =  vᵢ*t + (a*t²)/2

where:
  d  = displacement
  v = initial velocity
  a  = acceleration
  t  = elapsed time

breakdown:
  vᵢ*t     = movement due to the initial velocity
  (a*t²)/2 = change in movement due to acceleration throughout the frame

Bu güncelleme mantığını uygularsak:

position += (velocity * elapsedTime) + (acceleration * elapsedTime * elapsedTime / 2)
velocity += acceleration * elapsedTime

Aşağıdaki sonuçları elde edeceğiz:

resim açıklamasını buraya girin


2
Bu yararlı bilgilerdir, ancak söz konusu kodu gerçekte nasıl ele alır? İlk olarak, kare hızı arttıkça hata önemli ölçüde azalır, bu nedenle 60 ve 200 fps arasındaki fark göz ardı edilebilir (8 fps'e karşı sonsuzluk zaten sadece% 12.5 çok yüksektir). İkincisi, hareketli grafik tam hıza ulaştığında, en büyük fark 0,5 birim ileridedir. Ekli .gif'lerde gösterildiği gibi gerçek yürüme hızını etkilememelidir. Geri döndüklerinde, ivme görünüşte anında olur (muhtemelen 60+ fps'de birkaç kare, ancak tam saniye değil).
MichaelS

2
Bu bir birlik ya da kod sorunu, matematik sorunu değil. Hızlı bir e-tablo, a = 1, vi = 0, di = 0, vmax = 1 kullanırsak, d = 0,5 ile v = 'e vurmamız gerektiğini söyler. Bunu 5 kareden fazla yapmak (dt = 0.2), d (t = 1) = 0.6. 50'den fazla çerçeve (dt = 0.02), d (t = 1) = 0.51. 500'den fazla çerçeve (dt = 0.002), d (t = 1) = 0.501. Yani 5 fps% 20 yüksek, 50 fps% 2 yüksek ve 500 fps% 0.2 yüksek. Genel olarak, hata yüzde 100 / fps çok yüksektir. 50 fps, 500 fps'den yaklaşık% 1.8 daha yüksektir. Ve bu sadece hızlanma sırasında. Hız maksimuma ulaştığında, sıfır fark olmalıdır. A = 100 ve vmax = 5 ile daha da az fark olmalıdır.
MichaelS

2
Aslında, devam ettim ve bir VB.net uygulaması (1/60 ve 1/200 simüle dt) kodunuzu kullanılan ve var Bounce: çerçeve 626, 5 (10,433) saniye vs Bounce: çerçeve 2081, 5 ( 10.405) saniye . 60 fps'de% 0,27 daha fazla zaman.
MichaelS

2
% 10 fark veren "kinematik" yaklaşımınız. Geleneksel yaklaşım% 0.27'lik farktır. Sadece yanlış etiketlediniz. Sanırım bunun nedeni, hızın maksimize edilmesi durumunda yanlış hızlanmayı dahil ettiğinizdir. Daha yüksek kare hızları kare başına daha az hata eklediğinden daha doğru bir sonuç verir. Gerek if(velocity==vmax||velocity==-vmax){acceleration=0}. O zaman hata önemli ölçüde düşer, ancak çerçeve hızlandırmanın tam olarak hangi bölümünün sona erdiğini anlamadığımız için mükemmel değildir.
MichaelS

6

Adımınızı nereden aradığınıza bağlıdır. Güncelleme'den çağırıyorsanız, Time.deltaTime ile ölçeklendirirseniz, hareketiniz gerçekten kare hızından bağımsız olacaktır, ancak FixedUpdate'den çağırıyorsanız, Time.fixedDeltaTime ile ölçeklendirmeniz gerekir. Adımınızı FixedUpdate'den çağırdığınızı, ancak Time.deltaTime ile ölçeklendirdiğinizi düşünüyorum, bu da Unity'nin sabit adımı ana döngüden daha yavaş olduğunda görünen hızın düşmesine neden olacak, bu da bağımsız derlemenizde gerçekleşiyor. Sabit adım yavaş olduğunda fixedDeltaTime büyüktür.


1
LateUpdate'den çağrılıyor. Bunu açıklığa kavuşturmak için sorumu güncelleyeceğim. Her Time.deltaTimene kadar çağrıldığından bağımsız olarak doğru değeri kullanacağına inansam da (FixedUpdate'de kullanılıyorsa, fixedDeltaTime kullanacak).
Cooper
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.