Chuyển động dường như phụ thuộc vào tốc độ khung hình, mặc dù sử dụng Time.deltaTime


13

Tôi có đoạn mã sau để tính toán bản dịch cần thiết để di chuyển một đối tượng trò chơi trong Unity, được gọi là LateUpdate. Theo những gì tôi hiểu, việc sử dụng của tôi Time.deltaTimesẽ làm cho tốc độ khung hình dịch cuối cùng trở nên độc lập (xin lưu ý CollisionDetection.Move()là chỉ thực hiện các chương trình phát sóng).

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

Nếu tôi khóa tốc độ khung hình của trò chơi lên 60FPS, các đối tượng của tôi sẽ di chuyển như mong đợi. Tuy nhiên, nếu tôi mở khóa nó ( Application.targetFrameRate = -1;), một số đối tượng sẽ di chuyển với tốc độ chậm hơn nhiều thì tôi sẽ mong đợi khi đạt được ~ 200FPS trên màn hình 144hz. Điều này dường như chỉ xảy ra trong một bản dựng độc lập, và không nằm trong trình chỉnh sửa Unity.

GIF chuyển động của đối tượng trong trình chỉnh sửa, FPS đã được mở khóa

http://gfycat.com/SmugAnnualFugu

GIF chuyển động của đối tượng trong bản dựng độc lập, FPS đã được mở khóa

http://gfycat.com/OldAmpleJuliabutoston


2
Bạn nên đọc cái này Thời gian xô là những gì bạn muốn, và các bước thời gian cố định! gafferongames.com/game-physics/fix-your-timestep
Alan Wolfe

Câu trả lời:


30

Mô phỏng dựa trên khung sẽ gặp lỗi khi cập nhật không thể bù cho tốc độ thay đổi phi tuyến tính.

Ví dụ, hãy xem xét một vật bắt đầu bằng các giá trị vị trí và vận tốc bằng 0 trải qua gia tốc không đổi của một.

Nếu chúng tôi áp dụng logic cập nhật này:

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

Chúng tôi có thể mong đợi những kết quả này theo tỷ lệ khung hình khác nhau: nhập mô tả hình ảnh ở đây

Lỗi được gây ra bằng cách xử lý vận tốc cuối cùng như thể nó được áp dụng cho toàn bộ khung. Điều này tương tự với Sum Riemann phải và lượng lỗi thay đổi theo tốc độ khung hình (được minh họa trên một chức năng khác):

Như MichaelS chỉ ra, lỗi này sẽ giảm một nửa khi thời lượng khung hình giảm một nửa và có thể trở nên không quan trọng ở tốc độ khung hình cao. Mặt khác, bất kỳ trò chơi nào có hiệu suất tăng đột biến hoặc khung chạy dài có thể thấy điều này tạo ra hành vi không thể đoán trước.


May mắn thay, động học cho phép chúng ta tính toán chính xác sự dịch chuyển gây ra bởi gia tốc tuyến tính:

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

Vì vậy, nếu chúng ta áp dụng logic cập nhật này:

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

Chúng tôi sẽ có kết quả như sau:

nhập mô tả hình ảnh ở đây


2
Đây là thông tin hữu ích, nhưng làm thế nào để nó thực sự giải quyết được mã đang đề cập? Đầu tiên, lỗi giảm đáng kể khi tốc độ khung hình tăng, do đó chênh lệch giữa 60 và 200 khung hình / giây là không đáng kể (8 khung hình / giây so với vô cực chỉ là 12,5% quá cao). Thứ hai, một khi sprite ở tốc độ tối đa, sự khác biệt lớn nhất là 0,5 đơn vị phía trước. Nó không ảnh hưởng đến tốc độ đi bộ thực tế như được hiển thị trong .gifs đính kèm. Khi họ quay lại, khả năng tăng tốc dường như tức thì (có thể là vài khung hình ở tốc độ 60+ khung hình / giây, nhưng không phải là toàn bộ giây).
MichaelS

2
Đó là vấn đề về Unity hoặc code, không phải là vấn đề toán học. Một bảng tính nhanh cho biết nếu chúng ta sử dụng a = 1, vi = 0, di = 0, vmax = 1, chúng ta nên nhấn vmax tại t = 1, với d = 0,5. Làm điều đó trên 5 khung hình (dt = 0,2), d (t = 1) = 0,6. Trên 50 khung (dt = 0,02), d (t = 1) = 0,51. Hơn 500 khung hình (dt = 0,002), d (t = 1) = 0,501. Vì vậy, 5 khung hình / giây là cao 20%, 50 khung hình / giây là cao 2% và 500 khung hình / giây là cao 0,2%. Nói chung, lỗi là 100 / khung hình / giây phần trăm quá cao. 50 khung hình / giây cao hơn khoảng 1,8% so với 500 khung hình / giây. Và đó chỉ là trong quá trình tăng tốc. Khi vận tốc đạt cực đại, sẽ không có chênh lệch. Với a = 100 và vmax = 5, sẽ có sự khác biệt thậm chí ít hơn.
MichaelS

2
Trên thực tế, tôi đã tiếp tục và sử dụng mã của bạn trong ứng dụng VB.net (mô phỏng dt 1/60 và 1/200) và nhận Bounce: 5 tại khung 626 (10,433) giây so với Bounce: 5 tại khung 2081 ( 10.405) giây . Thêm 0,27% thời gian ở 60 khung hình / giây.
MichaelS

2
Đó là cách tiếp cận "động học" của bạn mang lại sự khác biệt 10%. Cách tiếp cận truyền thống là chênh lệch 0,27%. Bạn chỉ dán nhãn chúng không chính xác. Tôi nghĩ đó là bởi vì bạn không chính xác bao gồm cả gia tốc khi vận tốc được tối đa. Tốc độ khung hình cao hơn thêm ít lỗi hơn trên mỗi khung hình, do đó cho kết quả chính xác hơn. Bạn cần if(velocity==vmax||velocity==-vmax){acceleration=0}. Sau đó, lỗi giảm đáng kể, mặc dù nó không hoàn hảo vì chúng ta không biết chính xác phần nào của quá trình tăng tốc khung hình đã kết thúc.
MichaelS

6

Nó phụ thuộc vào nơi bạn đang gọi bước của bạn từ. Nếu bạn gọi nó từ Cập nhật, chuyển động của bạn sẽ thực sự độc lập nếu bạn chia tỷ lệ với Time.deltaTime, nhưng nếu bạn gọi nó từ FixedUpdate, bạn cần mở rộng quy mô với Time.fixedDeltaTime. Tôi cho rằng bạn đang gọi bước của bạn từ FixedUpdate, nhưng mở rộng bằng Time.deltaTime, điều này sẽ làm giảm tốc độ rõ ràng khi bước cố định của Unity chậm hơn vòng lặp chính, đó là những gì xảy ra trong bản dựng độc lập của bạn. Khi bước cố định chậm, fixedDeltaTime lớn.


1
Nó đang được gọi từ lateUpdate. Tôi sẽ cập nhật câu hỏi của tôi để làm rõ điều đó. Mặc dù tôi tin rằng Time.deltaTimevẫn sẽ sử dụng giá trị chính xác bất kể nó được gọi ở đâu (nếu được sử dụng trong FixedUpdate, nó sẽ sử dụng fixedDeltaTime).
Cooper
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.