การเคลื่อนไหวนั้นขึ้นอยู่กับอัตราเฟรมแม้จะใช้ Time.deltaTime


13

ฉันมีรหัสต่อไปนี้เพื่อคำนวณการแปลที่จำเป็นในการย้ายวัตถุเกมใน Unity ซึ่งเรียกว่าLateUpdateมา จากสิ่งที่ฉันเข้าใจการใช้งานของฉันTime.deltaTimeควรจะทำให้เป็นอิสระแปลสุดท้ายอัตราเฟรม (โปรดทราบCollisionDetection.Move()เป็นเพียงการแสดง raycasts)

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

ถ้าฉันล็อคอัตราเฟรมของเกมเป็น 60FPS วัตถุของฉันจะเคลื่อนไหวตามที่คาดไว้ อย่างไรก็ตามถ้าฉันปลดล็อคมัน ( Application.targetFrameRate = -1;) วัตถุบางอย่างจะเคลื่อนที่ในอัตราที่ช้ากว่ามากฉันคาดหวังว่าเมื่อบรรลุ ~ 200FPS บนหน้าจอขนาด 144hz สิ่งนี้ดูเหมือนว่าจะเกิดขึ้นในบิลด์สแตนด์อโลนเท่านั้นและไม่อยู่ในตัวแก้ไข Unity

GIF ของการเคลื่อนย้ายวัตถุภายในตัวแก้ไขปลดล็อก FPS

http://gfycat.com/SmugAnnualFugu

GIF ของการเคลื่อนย้ายวัตถุภายในบิลด์แบบสแตนด์อโลนปลดล็อค FPS

http://gfycat.com/OldAmpleJuliabutterfly


2
คุณควรอ่านสิ่งนี้ การเลือกเวลาเป็นสิ่งที่คุณต้องการและขั้นตอนเวลาที่แน่นอน! gafferongames.com/game-physics/fix-your-timestep
Alan Wolfe

คำตอบ:


30

การจำลองตามเฟรมจะพบข้อผิดพลาดเมื่อการอัปเดตล้มเหลวในการชดเชยอัตราการเปลี่ยนแปลงที่ไม่ใช่เชิงเส้น

ยกตัวอย่างเช่นลองพิจารณาวัตถุที่ขึ้นต้นด้วยค่าตำแหน่งและความเร็วที่ศูนย์เป็นศูนย์ซึ่งมีอัตราเร่งหนึ่ง

หากเราใช้ตรรกะการอัพเดทนี้:

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

เราสามารถคาดหวังผลลัพธ์เหล่านี้ภายใต้อัตราเฟรมที่แตกต่างกัน: ป้อนคำอธิบายรูปภาพที่นี่

ข้อผิดพลาดเกิดจากการรักษาความเร็วสุดท้ายราวกับว่ามันใช้กับทั้งเฟรม สิ่งนี้คล้ายกับRight Riemann Sumและปริมาณของข้อผิดพลาดแตกต่างกันไปตามอัตราเฟรม (แสดงในฟังก์ชันอื่น):

ขณะที่ไมเคิลชี้ให้เห็นข้อผิดพลาดนี้จะถูกลดลงครึ่งหนึ่งเมื่อระยะเวลาเฟรมจะลดลงครึ่งหนึ่งและอาจจะกลายเป็นไม่สำคัญที่อัตราเฟรมที่สูง ในทางกลับกันเกมใดก็ตามที่ประสบกับ spikes ประสิทธิภาพหรือเฟรมยาวอาจพบว่าสิ่งนี้ก่อให้เกิดพฤติกรรมที่คาดเดาไม่ได้


โชคดีที่จลนศาสตร์ช่วยให้เราสามารถคำนวณการกระจัดที่เกิดจากการเร่งความเร็วเชิงเส้นอย่างแม่นยำ:

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

ดังนั้นหากเราใช้ตรรกะการอัพเดทนี้:

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

เราจะได้ผลลัพธ์ดังต่อไปนี้:

ป้อนคำอธิบายรูปภาพที่นี่


2
นี่เป็นข้อมูลที่มีประโยชน์ แต่จริง ๆ แล้วมันแก้ปัญหาเรื่องรหัสได้อย่างไร? ข้อแรกข้อผิดพลาดจะลดลงอย่างมากเมื่ออัตราเฟรมเพิ่มขึ้นดังนั้นความแตกต่างระหว่าง 60 และ 200 เฟรมต่อวินาทีจึงเล็กน้อย ประการที่สองเมื่อสไปรต์ความเร็วเต็มที่ความแตกต่างที่ยิ่งใหญ่ที่สุดคือ 0.5 หน่วยข้างหน้า ไม่ควรส่งผลกระทบต่อความเร็วการเดินจริงตามที่แสดงใน. gif ที่แนบมา เมื่อพวกเขาหันไปรอบ ๆ การเร่งความเร็วจะปรากฏขึ้นทันที (อาจเป็นหลายเฟรมที่ 60+ fps แต่ไม่เต็มวินาที)
MichaelS

2
นั่นเป็นปัญหาความสามัคคีหรือรหัสไม่ใช่ปัญหาคณิตศาสตร์ กระดาษคำนวณอย่างรวดเร็วบอกว่าถ้าเราใช้ = 1, vi = 0, di = 0, vmax = 1 เราควรกด vmax ที่ t = 1 โดยมี d = 0.5 การทำมากกว่า 5 เฟรม (dt = 0.2), d (t = 1) = 0.6 มากกว่า 50 เฟรม (dt = 0.02), d (t = 1) = 0.51 มากกว่า 500 เฟรม (dt = 0.002), d (t = 1) = 0.501 ดังนั้น 5 fps สูง 20%, 50 fps สูง 2% และ 500 fps สูง 0.2% โดยทั่วไปแล้วข้อผิดพลาดคือ 100 / fps สูงเกินไป 50 fps นั้นสูงกว่า 500 fps ประมาณ 1.8% และนั่นเป็นเพียงช่วงเร่งความเร็ว เมื่อความเร็วถึงสูงสุดแล้วควรมีความแตกต่างเป็นศูนย์ ด้วย = 100 และ vmax = 5 ควรมีความแตกต่างน้อยลง
MichaelS

2
ในความเป็นจริงฉันไปข้างหน้าและใช้รหัสของคุณในแอป VB.net (จำลอง dt ที่ 1/60 และ 1/200) และได้รับการตีกลับ: 5 ที่เฟรม 626 (10.433) วินาทีกับการตีกลับ: 5 ที่เฟรม 2081 ( 10.405) วินาที เพิ่มเวลาอีก 0.27% ที่ 60 fps
MichaelS

2
เป็นแนวทาง "Kinematic" ของคุณที่ให้ความแตกต่าง 10% วิธีการดั้งเดิมคือความแตกต่าง 0.27% คุณระบุว่าพวกเขาไม่ถูกต้อง ฉันคิดว่าเป็นเพราะคุณไม่ถูกต้องรวมถึงการเร่งความเร็วเมื่อความเร็วสูงสุด อัตราเฟรมที่สูงกว่าเพิ่มข้อผิดพลาดน้อยลงต่อเฟรมดังนั้นให้ผลลัพธ์ที่แม่นยำยิ่งขึ้น if(velocity==vmax||velocity==-vmax){acceleration=0}คุณจำเป็นต้อง จากนั้นข้อผิดพลาดจะลดลงอย่างมีนัยสำคัญถึงแม้ว่ามันจะไม่สมบูรณ์แบบเนื่องจากเราไม่ทราบว่าส่วนใดของการเร่งเฟรมสิ้นสุด
MichaelS

6

ขึ้นอยู่กับว่าคุณโทรหาคุณในขั้นตอนไหน หากคุณกำลังเรียกมันจาก Update การเคลื่อนไหวของคุณจะมีความเป็นอิสระอย่างแน่นอนถ้าคุณปรับขนาดด้วย Time.deltaTime แต่ถ้าคุณเรียกมันจาก FixedUpdate คุณจะต้องปรับมาตราส่วนด้วย Time.fixedDeltaTime ฉันคิดว่าคุณกำลังเรียกขั้นตอนของคุณจาก FixedUpdate แต่ปรับขนาดด้วย Time.deltaTime ซึ่งจะทำให้ความเร็วลดลงอย่างเห็นได้ชัดเมื่อขั้นตอนคงที่ของ Unity ช้ากว่าลูปหลักซึ่งเป็นสิ่งที่เกิดขึ้นในบิลด์แบบสแตนด์อโลนของคุณ เมื่อขั้นตอนการแก้ไขช้า, fixedDeltaTime มีขนาดใหญ่


1
มันกำลังถูกเรียกจาก LateUpdate ฉันจะอัปเดตคำถามของฉันเพื่อให้ชัดเจน แม้ว่าฉันเชื่อว่าTime.deltaTimeจะยังคงใช้ค่าที่ถูกต้องไม่ว่าจะเรียกใช้ที่ใด (ถ้าใช้ใน FixedUpdate แต่จะใช้ fixedDeltaTime)
Cooper
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.