1

我在 Unity 中创建了一个简单的钟摆 - 带有刚体和铰链关节组件的游戏对象。我已将拖动角度拖动设置为 0。起始位置为 90 度,我希望钟摆从 90 度到 -90 度来回摆动。然而,情况并非如此——幅度衰减非常快,但对于小角度,钟摆看起来永远不会停止。

我的问题是:我应该如何配置铰链关节以实现对物理和抵抗运动的力​​的完全控制?我的目标是尽可能精确地进行物理模拟,即使是以性能为代价。

我已经尝试减少固定步骤的时间间隔并增加求解器迭代 - 这些都没有奏效。

为什么我需要它?我正计划为推车上的多个倒立摆设计一个控制系统。我有一个在 Matlab 中实现的摆锤的数学模型,我想在 Unity 中使用一个简单的模型来验证它(因为在这种情况下,我调整了所有参数、初始条件等,并且物理引擎正在为我计算一切)。如果事实证明支持 Unity 的物理引擎不够可靠,您还会推荐什么其他软件?

4

2 回答 2

2

我的理解是,由于 Unity 的物理运行方式,如果只使用铰链接头,这种钟摆运动可能会随着时间的推移而损失动能。基本上,如果你想要一个准确的钟摆模拟,你必须绕过物理引擎并直接实现它。

最初由MLM发布的 gamedev stackexchange 上有一篇非常好的帖子,内容是关于如何在 Unity 中实现更准确的钟摆模拟,我已将其粘贴在下面。


我认为这将是一个相对简单的问题,但我花了几天时间试图弄清楚如何模拟钟摆运动。我不想作弊,只是根据 sin(theta) 和 cos(theta) 曲线更改 x,y 位置。相反,我想处理现实生活中应用的两种力,重力张力。我缺少的主要部分是向心力。

Pendulum (mathematics)维基百科页面有一个很棒的动画(左下方)解释了摆的运动。你可以看到我的结果(右侧)与该图惊人地相似

“bob”是摆动的物体,“pivot”是原点/根。

钟摆运动:速度和加速度

我还发现这篇文章和图表(下)非常有用:


Theta等于绳索与重力方向的夹角。

当鲍勃在左侧或右侧时,张力等于:

m*g*cos(θ)

当摆锤接近平衡点(中间)时张力更大的原因是因为向心力

(m*v^2)/绳长

因此,鲍勃摆动时的整体张力公式如下:

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

钟摆系统中有两种力:

  • 重力
    • GravityForce = mass * gravity.magnitude
    • GravityDirection = gravity.normalized
  • 紧张
    • TensionForce = (mass * gravity * Cos(theta)) + ((mass * velocityTangent^2)/ropeLength)
    • TensionDirection = ropeDirection = bob to pivot

只需像对普通物体一样对物体施加重力,然后施加张力。施加力时,只需将力乘以方向和 deltaTime。

下面是Pendulum.cs脚本(也作为GitHub Gist)。它工作得很好,但如果你离开它一段时间(不会回到完全相同的位置),会有一些舍入误差漂移。

该脚本在 3D 中工作,但钟摆当然只能在 2D 平面中摆动。它也适用于任何方向的重力。例如,如果你颠倒重力,钟摆会倒置。Edit->Project Settings->Physics->Gravity

更新钟摆时保持一致的相对较小的 deltaTime 非常重要,这样您就不会在曲线周围反弹。我正在使用本文中的技术,修复您的时间步长!由 Glenn Fiedler完成。检查Update()下面的函数,看看我是如何实现它的。

也作为 GitHub Gist

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

更多魅力镜头:

于 2018-12-26T17:03:27.830 回答
0

Rigidbody上的maxAngularVelocity设置为Mathf.Infinity

我知道这个话题已经 9 个月大了,但是最近因为这个问题我一直在撞墙。出于某种原因,Unity 开发人员认为将刚体的最大旋转速度限制为每秒 7 弧度是个好主意!这只是每秒一转多一点,这对于任何需要物理精度的应用来说都太低了。最重要的是,该属性在检查器或物理设置中不可见!

我希望这会帮助你(如果你还没有自己弄清楚)和其他任何可能在未来与这个问题作斗争的人,干杯!

于 2019-10-01T18:11:05.047 回答