铰链关节的无损运动 - Unity
Lossless movement in hinge joints - Unity
我在 Unity 中创建了一个简单的钟摆 - 带有刚体和铰链组件的游戏对象。我已经将 drag 和 angular drag 都设置为 0。起始位置为 90 度,我希望钟摆可以在 90 到 -90 度之间来回摆动。然而,事实并非如此——振幅衰减得非常快,但对于小角度,钟摆看起来永远不会停止。
我的问题是:我应该如何配置铰链关节以实现对抵抗运动的物理和力的完全控制?我的目标是让物理模拟尽可能精确,即使以牺牲性能为代价。
我已经尝试减少固定步长的时间间隔并增加求解器迭代次数 - none 已经成功。
为什么我需要它?我打算为推车上的多个倒立摆设计一个控制系统。我有一个在 Matlab 中实现的钟摆的数学模型,我想在 Unity 中使用一个简单的模型来验证它(因为在那种情况下我调整所有参数、初始条件等,物理引擎正在为我计算一切)。如果事实证明支持Unity的物理引擎不够可靠,你会推荐我什么其他软件?
我的理解是,由于 Unity 的物理运行方式,如果您仅使用铰链关节,在这种钟摆运动中可能会随着时间的推移而损失动能。基本上,如果你想要一个精确的钟摆模拟,你必须绕过物理引擎,直接实现它。
有a very good post on the gamedev stackexchange originally posted by MLM关于如何在Unity中实现更精确的钟摆模拟,我贴在下面。
我认为这将是一个相对简单的问题,但我花了几天时间试图弄清楚如何模拟钟摆运动。我不想作弊,只是根据 sin(theta) 和 cos(theta) 曲线更改 x,y 位置。相反,我想处理现实生活中应用的两种力,Gravity 和 Tension。我缺少的主要部分是向心力。
Pendulum (mathematics) wikipedia page 有一个很棒的动画(左下方)解释了钟摆运动。你可以看到我的结果(在右边)与那个图表惊人地相似
"bob"是摆动物体"pivot"是origin/root.
我还发现 this article 和图表(下图)非常有用:
Theta
等于绳子与重力方向的夹角
当摆锤在左边或右边时,张力等于:
当摆锤接近平衡点(中间)时张力变大的原因是 centripetal force:
因此摆动时的整体张力公式如下:
钟摆系统中有两种力:
- 重力
GravityForce = mass * gravity.magnitude
GravityDirection = gravity.normalized
- 紧张
TensionForce = (mass * gravity * Cos(theta)) + ((mass * velocityTangent^2)/ropeLength)
TensionDirection = ropeDirection = bob to pivot
就像对普通物体一样对物体施加重力,然后施加张力。施加力时,只需将力乘以方向和 deltaTime。
下面是Pendulum.cs
script(also as a GitHub Gist)。它工作得很好但是如果你离开它一段时间会有一些舍入误差漂移(不会 return 到完全相同的位置)。
该脚本在 3D 中运行,但当然钟摆只能在 2D 平面中摆动。它也适用于任何方向的重力。因此,例如,如果您反转重力,钟摆就会颠倒。 Edit->Project Settings->Physics->Gravity
在更新钟摆时有一个一致的相对较小的 deltaTime 是非常重要的,这样你就不会在曲线上反弹。我正在使用本文 FIX YOUR TIMESTEP! by Glenn Fiedler 中的技术来完成此操作。检查下面的 Update()
函数,看看我是如何实现它的。
Also as a 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);
/* */
}
}
更多魅力镜头:
将 Rigidbody 上的 maxAngularVelocity 设置为 Mathf.Infinity。
我知道这个话题已经有 9 个月了,但最近因为这个问题我一直在碰壁。出于某种原因,Unity 开发人员认为将刚体的最大旋转速度限制为每秒 7 弧度是个好主意! 这只是每秒转一圈多一点,这太过分了对于需要物理精度的任何应用程序来说都是低的。最重要的是,属性 在检查器或物理设置中不可见!
我希望这对您(如果您还没有自己弄明白的话)以及将来可能会遇到这个问题的其他人有所帮助,干杯!
我在 Unity 中创建了一个简单的钟摆 - 带有刚体和铰链组件的游戏对象。我已经将 drag 和 angular drag 都设置为 0。起始位置为 90 度,我希望钟摆可以在 90 到 -90 度之间来回摆动。然而,事实并非如此——振幅衰减得非常快,但对于小角度,钟摆看起来永远不会停止。
我的问题是:我应该如何配置铰链关节以实现对抵抗运动的物理和力的完全控制?我的目标是让物理模拟尽可能精确,即使以牺牲性能为代价。
我已经尝试减少固定步长的时间间隔并增加求解器迭代次数 - none 已经成功。
为什么我需要它?我打算为推车上的多个倒立摆设计一个控制系统。我有一个在 Matlab 中实现的钟摆的数学模型,我想在 Unity 中使用一个简单的模型来验证它(因为在那种情况下我调整所有参数、初始条件等,物理引擎正在为我计算一切)。如果事实证明支持Unity的物理引擎不够可靠,你会推荐我什么其他软件?
我的理解是,由于 Unity 的物理运行方式,如果您仅使用铰链关节,在这种钟摆运动中可能会随着时间的推移而损失动能。基本上,如果你想要一个精确的钟摆模拟,你必须绕过物理引擎,直接实现它。
有a very good post on the gamedev stackexchange originally posted by MLM关于如何在Unity中实现更精确的钟摆模拟,我贴在下面。
我认为这将是一个相对简单的问题,但我花了几天时间试图弄清楚如何模拟钟摆运动。我不想作弊,只是根据 sin(theta) 和 cos(theta) 曲线更改 x,y 位置。相反,我想处理现实生活中应用的两种力,Gravity 和 Tension。我缺少的主要部分是向心力。
Pendulum (mathematics) wikipedia page 有一个很棒的动画(左下方)解释了钟摆运动。你可以看到我的结果(在右边)与那个图表惊人地相似
"bob"是摆动物体"pivot"是origin/root.
我还发现 this article 和图表(下图)非常有用:
Theta
等于绳子与重力方向的夹角
当摆锤在左边或右边时,张力等于:
当摆锤接近平衡点(中间)时张力变大的原因是 centripetal force:
因此摆动时的整体张力公式如下:
钟摆系统中有两种力:
- 重力
GravityForce = mass * gravity.magnitude
GravityDirection = gravity.normalized
- 紧张
TensionForce = (mass * gravity * Cos(theta)) + ((mass * velocityTangent^2)/ropeLength)
TensionDirection = ropeDirection = bob to pivot
就像对普通物体一样对物体施加重力,然后施加张力。施加力时,只需将力乘以方向和 deltaTime。
下面是Pendulum.cs
script(also as a GitHub Gist)。它工作得很好但是如果你离开它一段时间会有一些舍入误差漂移(不会 return 到完全相同的位置)。
该脚本在 3D 中运行,但当然钟摆只能在 2D 平面中摆动。它也适用于任何方向的重力。因此,例如,如果您反转重力,钟摆就会颠倒。 Edit->Project Settings->Physics->Gravity
在更新钟摆时有一个一致的相对较小的 deltaTime 是非常重要的,这样你就不会在曲线上反弹。我正在使用本文 FIX YOUR TIMESTEP! by Glenn Fiedler 中的技术来完成此操作。检查下面的 Update()
函数,看看我是如何实现它的。
Also as a 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);
/* */
}
}
更多魅力镜头:
将 Rigidbody 上的 maxAngularVelocity 设置为 Mathf.Infinity。
我知道这个话题已经有 9 个月了,但最近因为这个问题我一直在碰壁。出于某种原因,Unity 开发人员认为将刚体的最大旋转速度限制为每秒 7 弧度是个好主意! 这只是每秒转一圈多一点,这太过分了对于需要物理精度的任何应用程序来说都是低的。最重要的是,属性 在检查器或物理设置中不可见!
我希望这对您(如果您还没有自己弄明白的话)以及将来可能会遇到这个问题的其他人有所帮助,干杯!