Unity rơi cơ thể con lắc hành vi


7

Tôi tự hỏi nếu ai đó có thể cung cấp một số hướng dẫn.

Tôi đang cố gắng tạo ra một con lắc giống như hành vi trong không gian 2D trong Unity mà không sử dụng khớp bản lề.

Về cơ bản, tôi muốn tác động đến một cơ thể rơi xuống để hành động như thể nó bị hạn chế ở bán kính của một điểm, và phải chịu trọng lực và ma sát, v.v.

Tôi đã thử nhiều sửa đổi của mã này và đã đưa ra một số hành vi giống như 'người thu hút lạ' nhưng tôi không thể tạo ra một con lắc thực tế như hành động.

Đây là những gì tôi có cho đến nay:

    startingposition = transform.position;          //Get start position
    newposition = startingposition + velocity;      //add old velocity
    newposition.y -= gravity * Time.deltaTime;      //add gravity
    newposition = pivot + Vector2.ClampMagnitude(newposition-pivot,radius); //clamp body at radius???
    velocity = newposition-startingposition;        //Get new velocity
    transform.Translate (velocity * Time.deltaTime, Space.World);   //apply to transform

Vì vậy, tôi đang tìm ra vị trí mới dựa trên vận tốc + trọng lực cũ, sau đó hạn chế nó đến một khoảng cách từ một điểm, đó là yếu tố trong mã tôi không thể hiểu chính xác. Đây có phải là một cách hợp lý để đi về nó?


Tại sao bạn không muốn sử dụng khớp bản lề?
Hải lý

Quyết định sản xuất cho sự linh hoạt, và thật tuyệt khi biết cách thực hiện nó, tôi đang thử nghiệm các kỹ thuật khác nhau cho một hộp cát vật lý nhỏ cho giáo dục của riêng tôi. Tôi chắc chắn rằng nó là một hoạt động khá dễ dàng để thực hiện chỉ là sự hiểu biết của tôi về toán học / vật lý là thiếu.
dùng3447980

Ok tôi có thể thấy một lỗi bây giờ, tôi nghĩ rằng tôi nên làm việc với trọng lực và vận tốc độc lập với nhau và sau đó bình thường hóa kết quả. Không thể kiểm tra nếu đó là vấn đề hiện tại.
dùng3447980

Tôi sợ con lắc của bạn có thể mất năng lượng quá nhanh với phương pháp này. Hãy cho chúng tôi biết nếu bạn có vấn đề như vậy.
kolenda

Câu trả lời:


20

Tôi nghĩ rằng đây sẽ là một vấn đề tương đối đơn giản để giải quyết nhưng tôi đã dành một vài ngày để cố gắng tìm ra cách quái nào để mô phỏng chuyển động của con lắc. Tôi không muốn gian lận và chỉ thay đổi vị trí x, y dựa trên các đường cong sin (theta) và cos (theta). Thay vào đó tôi muốn đối phó với hai lực được áp dụng trong cuộc sống thực, Trọng lựcCăng thẳng . Phần chính tôi bị thiếu là lực hướng tâm.

Các Pendulum (toán học) trang wikipedia có một hình ảnh động tuyệt vời (bên dưới, bên trái) giải thích sự chuyển động con lắc. Bạn có thể thấy kết quả của tôi (bên phải) rất giống với sơ đồ đó

"Bob" là đối tượng xoay và "trục" là gốc / gốc.

Pendulum Motion: vận tốc và gia tốc

Tôi cũng thấy bài viết và sơ đồ này (bên dưới) khá hữu ích:


Theta bằng góc giữa sợi dây và hướng trọng lực.

Khi bob ở bên trái hoặc bên phải, độ căng bằng:

m * g * cos (theta)

Lý do lực căng lớn hơn khi bob tiến đến điểm cân bằng (giữa) là do lực hướng tâm :

(m * v ^ 2) / dây dài

Vì vậy, công thức căng thẳng quá mức trông giống như đu quay là:

m * g * cos (theta) + (m * v ^ 2) / RopeLpm

Có hai lực trong hệ thống con lắc:

  • Trọng lực
    • GravityForce = mass * gravity.magnitude
    • GravityDirection = gravity.normalized
  • Căng thẳng
    • TensionForce = (mass * gravity * Cos(theta)) + ((mass * velocityTangent^2)/ropeLength)
    • TensionDirection = ropeDirection = bob to pivot

Chỉ cần áp dụng trọng lực cho đối tượng của bạn như bạn sẽ làm cho một đối tượng bình thường và sau đó áp dụng sức căng. Khi bạn áp dụng các lực, chỉ cần nhân lực theo hướng và deltaTime.

Dưới đây là Pendulum.cskịch bản (cũng như một GitHub Gist ). Nó hoạt động khá tốt nhưng có một số lỗi làm tròn trôi nếu bạn để nó trong một thời gian (không trở lại chính xác cùng một vị trí).

Kịch bản hoạt động ở chế độ 3D nhưng tất nhiên một con lắc chỉ dao động trong mặt phẳng 2D. Nó cũng hoạt động với trọng lực theo bất kỳ hướng nào. Vì vậy, ví dụ, nếu bạn đảo ngược trọng lực, con lắc hoạt động lộn ngược.Edit->Project Settings->Physics->Gravity

Điều rất quan trọng là phải có deltaTime tương đối nhỏ phù hợp khi cập nhật con lắc để bạn không bị trả lại xung quanh đường cong. Tôi đang sử dụng kỹ thuật được tìm thấy trong bài viết này, CỐ ĐỊNH THỜI GIAN CỦA BẠN! bởi Glenn Fiedler để thực hiện điều này. Kiểm tra Update()chức năng dưới đây để xem cách tôi thực hiện nó.

Cũng như một ý chính GitHub

using UnityEngine;
using System.Collections;

// Author: Eric Eastwood (ericeastwood.com)
//
// Description:
//      Written for this gd.se question: http://gamedev.stackexchange.com/a/75748/16587
//      Simulates/Emulates pendulum motion in code
//      Works in any 3D direction and with any force/direciton of gravity
//
// Demonstration: https://i.imgur.com/vOQgFMe.gif
//
// Usage: https://i.imgur.com/BM52dbT.png
public class Pendulum : MonoBehaviour {

    public GameObject Pivot;
    public GameObject Bob;


    public float mass = 1f;

    float ropeLength = 2f;

    Vector3 bobStartingPosition;
    bool bobStartingPositionSet = false;

    // You could define these in the `PendulumUpdate()` loop 
    // But we want them in the class scope so we can draw gizmos `OnDrawGizmos()`
    private Vector3 gravityDirection;
    private Vector3 tensionDirection;

    private Vector3 tangentDirection;
    private Vector3 pendulumSideDirection;

    private float tensionForce = 0f;
    private float gravityForce = 0f;


    // Keep track of the current velocity
    Vector3 currentVelocity = new Vector3();

    // We use these to smooth between values in certain framerate situations in the `Update()` loop
    Vector3 currentStatePosition;
    Vector3 previousStatePosition;

    // Use this for initialization
    void Start () {
        // Set the starting position for later use in the context menu reset methods
        this.bobStartingPosition = this.Bob.transform.position;
        this.bobStartingPositionSet = true;

        this.PendulumInit();
    }


    float t = 0f;
    float dt = 0.01f;
    float currentTime = 0f;
    float accumulator = 0f;

    void Update()
    {
        /* */
        // Fixed deltaTime rendering at any speed with smoothing
        // Technique: http://gafferongames.com/game-physics/fix-your-timestep/
        float frameTime = Time.time - currentTime;
        this.currentTime = Time.time;

        this.accumulator += frameTime;

        while (this.accumulator >= this.dt)
        {
            this.previousStatePosition = this.currentStatePosition;
            this.currentStatePosition = this.PendulumUpdate(this.currentStatePosition, this.dt);
            //integrate(state, this.t, this.dt);
            accumulator -= this.dt;
            this.t += this.dt;
        }

        float alpha = this.accumulator/this.dt;

        Vector3 newPosition = this.currentStatePosition*alpha + this.previousStatePosition*(1f-alpha);

        this.Bob.transform.position = newPosition; //this.currentStatePosition;
        /* */

        //this.Bob.transform.position = this.PendulumUpdate(this.Bob.transform.position, Time.deltaTime);
    }


    // Use this to reset forces and go back to the starting position
    [ContextMenu("Reset Pendulum Position")]
    void ResetPendulumPosition()
    {
        if(this.bobStartingPositionSet)
            this.MoveBob(this.bobStartingPosition);
        else
            this.PendulumInit();
    }

    // Use this to reset any built up forces
    [ContextMenu("Reset Pendulum Forces")]
    void ResetPendulumForces()
    {
        this.currentVelocity = Vector3.zero;

        // Set the transition state
        this.currentStatePosition = this.Bob.transform.position;
    }

    void PendulumInit()
    {
        // Get the initial rope length from how far away the bob is now
        this.ropeLength = Vector3.Distance(Pivot.transform.position, Bob.transform.position);
        this.ResetPendulumForces();
    }

    void MoveBob(Vector3 resetBobPosition)
    {
        // Put the bob back in the place we first saw it at in `Start()`
        this.Bob.transform.position = resetBobPosition;

        // Set the transition state
        this.currentStatePosition = resetBobPosition;
    }


    Vector3 PendulumUpdate(Vector3 currentStatePosition, float deltaTime)
    {
        // Add gravity free fall
        this.gravityForce = this.mass * Physics.gravity.magnitude;
        this.gravityDirection = Physics.gravity.normalized;
        this.currentVelocity += this.gravityDirection * this.gravityForce * deltaTime;

        Vector3 pivot_p = this.Pivot.transform.position;
        Vector3 bob_p = this.currentStatePosition;


        Vector3 auxiliaryMovementDelta = this.currentVelocity * deltaTime;
        float distanceAfterGravity = Vector3.Distance(pivot_p, bob_p + auxiliaryMovementDelta);

        // If at the end of the rope
        if(distanceAfterGravity > this.ropeLength || Mathf.Approximately(distanceAfterGravity, this.ropeLength))
        {

            this.tensionDirection = (pivot_p - bob_p).normalized;

            this.pendulumSideDirection = (Quaternion.Euler(0f, 90f, 0f) * this.tensionDirection);
            this.pendulumSideDirection.Scale(new Vector3(1f, 0f, 1f));
            this.pendulumSideDirection.Normalize();

            this.tangentDirection = (-1f * Vector3.Cross(this.tensionDirection, this.pendulumSideDirection)).normalized;


            float inclinationAngle = Vector3.Angle(bob_p-pivot_p, this.gravityDirection);

            this.tensionForce = this.mass * Physics.gravity.magnitude * Mathf.Cos(Mathf.Deg2Rad * inclinationAngle);
            float centripetalForce = ((this.mass * Mathf.Pow(this.currentVelocity.magnitude, 2))/this.ropeLength);
            this.tensionForce += centripetalForce;

            this.currentVelocity += this.tensionDirection * this.tensionForce * deltaTime;
        }

        // Get the movement delta
        Vector3 movementDelta = Vector3.zero;
        movementDelta += this.currentVelocity * deltaTime;


        //return currentStatePosition + movementDelta;

        float distance = Vector3.Distance(pivot_p, currentStatePosition + movementDelta);
        return this.GetPointOnLine(pivot_p, currentStatePosition + movementDelta, distance <= this.ropeLength ? distance : this.ropeLength);
    }

    Vector3 GetPointOnLine(Vector3 start, Vector3 end, float distanceFromStart)
    {
        return start + (distanceFromStart * Vector3.Normalize(end - start));
    }

    void OnDrawGizmos()
    {
        // purple
        Gizmos.color = new Color(.5f, 0f, .5f);
        Gizmos.DrawWireSphere(this.Pivot.transform.position, this.ropeLength);

        Gizmos.DrawWireCube(this.bobStartingPosition, new Vector3(.5f, .5f, .5f));


        // Blue: Auxilary
        Gizmos.color = new Color(.3f, .3f, 1f); // blue
        Vector3 auxVel = .3f * this.currentVelocity;
        Gizmos.DrawRay(this.Bob.transform.position, auxVel);
        Gizmos.DrawSphere(this.Bob.transform.position + auxVel, .2f);

        // Yellow: Gravity
        Gizmos.color = new Color(1f, 1f, .2f);
        Vector3 gravity = .3f * this.gravityForce*this.gravityDirection;
        Gizmos.DrawRay(this.Bob.transform.position, gravity);
        Gizmos.DrawSphere(this.Bob.transform.position + gravity, .2f);

        // Orange: Tension
        Gizmos.color = new Color(1f, .5f, .2f); // Orange
        Vector3 tension = .3f * this.tensionForce*this.tensionDirection;
        Gizmos.DrawRay(this.Bob.transform.position, tension);
        Gizmos.DrawSphere(this.Bob.transform.position + tension, .2f);

        // Red: Resultant
        Gizmos.color = new Color(1f, .3f, .3f); // red
        Vector3 resultant = gravity + tension;
        Gizmos.DrawRay(this.Bob.transform.position, resultant);
        Gizmos.DrawSphere(this.Bob.transform.position + resultant, .2f);


        /* * /
        // Green: Pendulum side direction
        Gizmos.color = new Color(.3f, 1f, .3f);
        Gizmos.DrawRay(this.Bob.transform.position, 3f*this.pendulumSideDirection);
        Gizmos.DrawSphere(this.Bob.transform.position + 3f*this.pendulumSideDirection, .2f);
        /* */

        /* * /
        // Cyan: tangent direction
        Gizmos.color = new Color(.2f, 1f, 1f); // cyan
        Gizmos.DrawRay(this.Bob.transform.position, 3f*this.tangentDirection);
        Gizmos.DrawSphere(this.Bob.transform.position + 3f*this.tangentDirection, .2f);
        /* */
    }
}

Thêm những bức ảnh quyến rũ:


Đây là hoàn toàn phi thường. Thực sự đáng kinh ngạc. Đã chơi xung quanh với điều này và bạn thực sự có thể thấy điều này thực tế đến mức nào khi bạn đẩy nó vào trạng thái hỗn loạn và thấy nó tách ra. Một truy vấn của tôi sẽ là nó có vẻ hơi hỗn loạn trong việc tăng và giảm biên độ, có cách nào để giải thích những gì đang xảy ra ở đó không? Vẫn đang cố gắng tách nó ra và tiêu hóa tất cả ...
user3447980

@ user3447980 Tôi cũng nhận được hiệu ứng hơi "hỗn loạn" này. Một phần của nó phải làm với sự thiếu chính xác dấu phẩy động trong phạm vi thập phân. Việc tăng dtcó thể giúp ích vì deltaTime sẽ nhỏ hơn làm cho nó nội suy ít hơn nhưng cũng có nghĩa là sự thiếu chính xác hơn trong phạm vi thập phân. Tôi nghĩ vấn đề thực sự là vì dây không thể kéo dài, tôi giới hạn khoảng cách với trục (xem phần dưới cùng PendulumUpdate). Cũng nên nhớ rằng đây là một sợi dây và không phải là một thanh cứng nhắc. Vì vậy, nếu bạn đặt bob ở giữa, nó sẽ rơi tự do, trước khi chạm vào đầu dây và bắt đầu đung đưa.
MLM

@ user3447980 Tôi đã cập nhật mã với một bản sửa lỗi cho chuyển động từ bên này sang bên kia khi không nên có bất kỳ chuyển động nào. Tôi nhận thấy điều này một số thiết lập. Tôi cũng đã thử một phiên bản chính xác kép của kịch bản Pendulum nhưng không có sự khác biệt. Vẫn còn một vấn đề khi con lắc từ từ không làm cho nó cao như khi nó bắt đầu mà tôi sẽ cố gắng tìm ra.
MLM

@MLM xin lỗi vì đã phục hồi điều này, nhưng biết vấn đề này là một điểm chuẩn tốt để minh họa các thuộc tính của phương pháp tích hợp số, tôi nhận thấy nhận xét của bạn khi bạn nói rằng bạn không muốn gian lận bằng cách sử dụng giải pháp phân tích (dựa trên theta). Theo như tôi biết, tích hợp số (trong trường hợp của bạn là Symplectic Euler) không thể giải quyết hoàn toàn vấn đề không thể giữ bán kính xoay ổn định hợp lý . Sau đó, tôi thấy dòng này : return this.GetPointOnLine(pivot_p, currentStatePosition + movementDelta, distance <= this.ropeLength ? distance : this.ropeLength);.
teodron
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.