Gerakan tampaknya bergantung pada kecepatan bingkai, meskipun menggunakan Time.deltaTime

13

Saya memiliki kode berikut untuk menghitung terjemahan yang diperlukan untuk memindahkan objek game di Unity, yang dipanggil masuk LateUpdate. Dari apa yang saya mengerti, penggunaan saya Time.deltaTimeharus membuat frame rate terjemahan akhir independen (harap dicatat CollisionDetection.Move()hanya melakukan raycast).

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);
}

Jika saya mengunci framerate game ke 60FPS, objek saya bergerak seperti yang diharapkan. Namun, jika saya membukanya ( Application.targetFrameRate = -1;), beberapa objek akan bergerak pada tingkat yang jauh lebih lambat maka saya harapkan ketika mencapai ~ 200FPS pada monitor 144hz. Ini sepertinya hanya terjadi pada bangunan yang berdiri sendiri, dan bukan di dalam editor Unity.

GIF gerakan objek dalam editor, membuka kunci FPS

http://gfycat.com/SmugAnnualFugu

GIF pergerakan objek dalam build mandiri, FPS terbuka

http://gfycat.com/OldAmpleJuliabutterfly

Cooper
sumber
2
Anda harus membaca ini. Ember waktu adalah yang Anda inginkan, dan memperbaiki langkah waktu! gafferongames.com/game-physics/fix-your-timestep
Alan Wolfe

Jawaban:

30

Simulasi berbasis bingkai akan mengalami kesalahan ketika pembaruan gagal mengkompensasi laju perubahan non-linear.

Sebagai contoh, pertimbangkan sebuah objek yang dimulai dengan nilai posisi dan kecepatan nol yang mengalami percepatan konstan satu.

Jika kami menerapkan logika pembaruan ini:

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

Kami dapat mengharapkan hasil ini di bawah frame rate yang berbeda: masukkan deskripsi gambar di sini

Kesalahan ini disebabkan oleh memperlakukan kecepatan akhir seolah-olah itu diterapkan untuk seluruh frame. Ini mirip dengan Jumlah Riemann Kanan dan jumlah kesalahan bervariasi dengan frame rate (diilustrasikan pada fungsi yang berbeda):

Seperti yang ditunjukkan oleh MichaelS , kesalahan ini akan berkurang separuhnya saat durasi bingkai dibelah dua, dan mungkin menjadi tidak penting pada tingkat bingkai yang tinggi. Di sisi lain, game apa pun yang mengalami lonjakan kinerja atau frame yang berjalan lama mungkin menemukan ini menghasilkan perilaku yang tidak terduga.


Untungnya kinematika memungkinkan kita untuk menghitung perpindahan yang akurat akibat percepatan linier:

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

Jadi jika kita menerapkan logika pembaruan ini:

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

Kami akan memiliki hasil sebagai berikut:

masukkan deskripsi gambar di sini

Kelly Thomas
sumber
2
Ini adalah informasi yang bermanfaat, tetapi bagaimana sebenarnya kode tersebut membahas kode yang dimaksud? Pertama, kesalahan turun secara dramatis ketika framerate meningkat, sehingga perbedaan antara 60 dan 200 fps dapat diabaikan (8 fps vs infinity sudah hanya 12,5% terlalu tinggi). Kedua, begitu sprite berada pada kecepatan penuh, perbedaan terbesar adalah 0,5 unit di depan. Seharusnya tidak mempengaruhi kecepatan berjalan yang sebenarnya seperti yang ditunjukkan pada .gif yang terpasang. Ketika mereka berbalik, akselerasi tampak instan (mungkin beberapa frame pada 60+ fps, tetapi tidak dalam hitungan detik penuh).
MichaelS
2
Itu masalah Unity atau kode, bukan masalah matematika. Spreadsheet cepat mengatakan jika kita menggunakan a = 1, vi = 0, di = 0, vmax = 1, kita harus menekan vmax pada t = 1, dengan d = 0,5. Melakukan lebih dari 5 frame (dt = 0,2), d (t = 1) = 0,6. Lebih dari 50 frame (dt = 0,02), d (t = 1) = 0,51. Lebih dari 500 frame (dt = 0,002), d (t = 1) = 0,501. Jadi 5 fps adalah 20% tinggi, 50 fps tinggi 2%, dan 500 fps tinggi 0,2%. Secara umum, kesalahannya 100 / fps persen terlalu tinggi. 50 fps sekitar 1,8% lebih tinggi dari 500 fps. Dan itu hanya saat akselerasi. Setelah kecepatan mencapai max, seharusnya tidak ada perbedaan. Dengan a = 100 dan vmax = 5, seharusnya ada lebih sedikit perbedaan.
MichaelS
2
Bahkan, saya melanjutkan dan menggunakan kode Anda di aplikasi VB.net (mensimulasikan dt 1/60 dan 1/200), dan mendapat Bounce: 5 pada frame 626 (10.433) detik vs. Bounce: 5 pada frame 2081 ( 10.405) detik . 0.27% lebih banyak waktu pada 60 fps.
MichaelS
2
Ini pendekatan "kinematik" Anda yang memberikan perbedaan 10%. Pendekatan tradisional adalah perbedaan 0,27%. Anda baru saja memberi label salah. Saya pikir itu karena Anda salah memasukkan akselerasi ketika kecepatan maksimum. Framerate yang lebih tinggi menambah lebih sedikit kesalahan per frame, jadi berikan hasil yang lebih akurat. Kamu butuh if(velocity==vmax||velocity==-vmax){acceleration=0}. Kemudian kesalahan menurun secara substansial, meskipun itu tidak sempurna karena kami tidak mengetahui dengan pasti bagian mana dari akselerasi frame yang berakhir.
MichaelS
6

Itu tergantung di mana Anda memanggil langkah Anda. Jika Anda memanggilnya dari Pembaruan, gerakan Anda akan framerate independen jika Anda skala dengan Time.deltaTime, tetapi jika Anda memanggilnya dari FixedUpdate, Anda perlu skala dengan Time.fixedDeltaTime. Saya pikir Anda sedang memanggil langkah Anda dari FixedUpdate, tetapi melakukan penskalaan dengan Time.deltaTime, yang akan menghasilkan penurunan kecepatan nyata ketika langkah tetap Unity lebih lambat daripada loop utama, yang merupakan apa yang terjadi di build mandiri Anda. Ketika langkah tetap lambat, fixDeltaTime besar.

Nox
sumber
1
Ini dipanggil dari LateUpdate. Saya akan memperbarui pertanyaan saya untuk memperjelasnya. Meskipun saya percaya Time.deltaTimemasih akan menggunakan nilai yang benar terlepas dari mana namanya (jika digunakan dalam FixedUpdate, itu akan menggunakan fixedDeltaTime).
Cooper