Unity Physics - 计算停在指定距离所需的减速度
Unity Physics - Calculate the deceleration needed to stop at a specified distance
一段时间以来,我一直在努力寻找这个问题的解决方案,但我觉得我对物理学的工作原理缺乏了解,无法找到正确的解决方案。希望这里有人能帮助我!
问题
如标题所述 - 我想要完成的是将移动的 object 减速到完全停止并到达特定距离。
上下文
我专门尝试将其用于播放器控制器,一旦不再提供输入,我就会采用移动 object 的当前速度并将其减慢到停止 x 个单位释放点的位置。
当前方法
目前我知道我知道 (a) 初始速度 (b) 目标速度 (c) 速度变化所经过的距离和 (d) 到达的目标距离。
我已经能够使用这个脚本以 5 单位/秒的特定速度运行它:
public class DecelToStop : MonoBehaviour
{
Rigidbody2D rb;
public float speed = 5f; // velocity of object while input is pressed
public float stoppingDistance = 3f; // distance object should stop at on input released
public float stopTimeMultiplier = 3.5f; // multiplier applied to time step to reach desired stopping distance
bool inputIsReleased = false;
float decelerationNeeded = 0;
public float GetDeceleration(float initalVelocity, float targetVelocity)
{
float travelTime = stoppingDistance / initalVelocity; // total time needed to reach a stop
float velocityChange = targetVelocity - initalVelocity;// total change in velocity
float decelTimeMultiplier = Mathf.Sqrt(stoppingDistance * stopTimeMultiplier); // how much to multiply the travel time by
float deceleration = initalVelocity / (travelTime * decelTimeMultiplier); //amount of deceleration to apply each fixed update
return deceleration;
}
private void FixedUpdate()
{
// get deceleration needed on input release
if (!inputIsReleased)
{
decelerationNeeded = GetDeceleration(speed, 0);
inputIsReleased = true;
}
// apply total force needed by applying the inital speed and the deceleration neeed
if (rb.velocity.x != 0)
{
rb.AddForce(new Vector2(speed, 0));
rb.AddForce(new Vector2(decelerationNeeded, 0));
}
}
}
我当前方法的问题是,一旦我更改了速度变量,stopTimeMultipler 就变成了我试图避免的东西——这是一堆猜测工作,以找到一切正常工作所需的确切值。
我确定这种方法存在多个缺陷 - 就像我说的我对物理计算没有很好的理解 - 所以如果你有解决方案,如果你能像你正在谈论的那样解释它一个 5 岁的孩子会很棒!该解决方案不需要达到精确的停止距离 - 只要它相对接近(在 0.2 个单位内)并且可以随着不同的速度和停止距离缩放,就会有一些变化。
好的,在这上面花了更多时间后,我找到了一个可扩展的解决方案。
我已经完全修改了我的方法 - 改为使用这两个视频中显示的方法来加速和减速我的刚体:
https://www.youtube.com/watch?v=uVKHllD-JZk
https://www.youtube.com/watch?v=YskC8h3xFVQ&ab_channel=BoardToBitsGames
(我建议观看这些以充分了解加速是如何实现的)
这些视频让我能够在指定的时间内将物体加速和减速到目标速度。
从这里我能够编写一个方法,将提供的距离值转换为找到应用每次更新的正确加速度量所需的时间变量。
我在这里找到了执行此操作的公式:https://physics.stackexchange.com/questions/18974/what-do-i-need-to-do-to-find-the-stopping-time-of-a-decelerating-car
但对于 c# 实现,请查看下面代码中的 ConvertDistanceToVelocityStep() 方法。
到目前为止,我的所有测试表明,一旦不再提供输入,此方法仅允许提供最大速度和所需的停止距离,以将移动物体减速到指定距离处完全停止。
这是带注释的完整脚本 - 如果您有任何优化或建议的改进,请随时将它们留在下面。
public class Accelerator : MonoBehaviour
{
Rigidbody2D m_Body2D;
// - Speed
public float maxSpeed = 6f;
// - Distance
public float stoppingDistance = 1f;
public float accelDistance = 1f;
// - Time
float timeZeroToMax = 2.5f;
float timeMaxToZero = 6f;
float accelRatePerSec;
float decelRatePerSec;
float xVel;
public bool inputPressed = false;
public bool allowInputs = true;
Vector2 lastHeldDirection;
// - get any needed references
private void Awake()
{
m_Body2D = GetComponent<Rigidbody2D>();
}
// - convert distance values into an acceleration to apply each update
void ConvertDistanceToVelocityStep()
{
//acceleration
timeZeroToMax = (2 * accelDistance) / (maxSpeed - 0);
accelRatePerSec = maxSpeed / timeZeroToMax;
//deceleration
timeMaxToZero = (2 * stoppingDistance) / (0 + maxSpeed);
decelRatePerSec = -maxSpeed / timeMaxToZero;
}
private void Start()
{
ConvertDistanceToVelocityStep();
xVel = 0;
}
private void Update()
{
// if inputs are allowed - check when horizontal buttons are pressed
if (allowInputs)
{
if (Input.GetButtonDown("Horizontal"))
inputPressed = true;
else if (Input.GetButtonUp("Horizontal"))
inputPressed = false;
}
else inputPressed = false;
}
private void FixedUpdate()
{
// if a valid input is provided
if (inputPressed && allowInputs)
{
// get direction
InputDirection();
// get acceleration
Accelerate(accelRatePerSec);
// apply acceleration in desired direction
m_Body2D.velocity = new Vector2(lastHeldDirection.x * xVel, m_Body2D.velocity.y);
}
// if input no longer pressed
else
{
// while still moving
if (Mathf.Abs(m_Body2D.velocity.x) > 0.01f)
{
// get deceleration
Accelerate(decelRatePerSec);
// apply deceleration in last held direction
m_Body2D.velocity = new Vector2(lastHeldDirection.x * xVel, m_Body2D.velocity.y);
}
else
// bring x velocity to zero
m_Body2D.velocity = new Vector2(0, m_Body2D.velocity.y);
}
}
// calculate x velocity to move rigidbody
void Accelerate(float accelRate)
{
xVel += accelRate * Time.deltaTime;
xVel = Mathf.Clamp(xVel, 0, maxSpeed);
}
Vector2 InputDirection()
{
// get both axis of input
float hor = Input.GetAxis("Horizontal");
float vert = Input.GetAxis("Vertical");
// save to vector2
Vector2 inputDir = new Vector2(hor, vert);
// round last held direction to whole number
if (Mathf.Abs(inputDir.x) > 0.25f)
{
if (inputDir.x > 0)
lastHeldDirection.x = 1;
else lastHeldDirection.x = -1;
}
//normalize diagonal inputs
if (inputDir.magnitude > 1)
inputDir.Normalize();
// return input direction
return inputDir;
}
}
一段时间以来,我一直在努力寻找这个问题的解决方案,但我觉得我对物理学的工作原理缺乏了解,无法找到正确的解决方案。希望这里有人能帮助我!
问题
如标题所述 - 我想要完成的是将移动的 object 减速到完全停止并到达特定距离。
上下文
我专门尝试将其用于播放器控制器,一旦不再提供输入,我就会采用移动 object 的当前速度并将其减慢到停止 x 个单位释放点的位置。
当前方法
目前我知道我知道 (a) 初始速度 (b) 目标速度 (c) 速度变化所经过的距离和 (d) 到达的目标距离。
我已经能够使用这个脚本以 5 单位/秒的特定速度运行它:
public class DecelToStop : MonoBehaviour
{ Rigidbody2D rb;
public float speed = 5f; // velocity of object while input is pressed
public float stoppingDistance = 3f; // distance object should stop at on input released
public float stopTimeMultiplier = 3.5f; // multiplier applied to time step to reach desired stopping distance
bool inputIsReleased = false;
float decelerationNeeded = 0;
public float GetDeceleration(float initalVelocity, float targetVelocity)
{
float travelTime = stoppingDistance / initalVelocity; // total time needed to reach a stop
float velocityChange = targetVelocity - initalVelocity;// total change in velocity
float decelTimeMultiplier = Mathf.Sqrt(stoppingDistance * stopTimeMultiplier); // how much to multiply the travel time by
float deceleration = initalVelocity / (travelTime * decelTimeMultiplier); //amount of deceleration to apply each fixed update
return deceleration;
}
private void FixedUpdate()
{
// get deceleration needed on input release
if (!inputIsReleased)
{
decelerationNeeded = GetDeceleration(speed, 0);
inputIsReleased = true;
}
// apply total force needed by applying the inital speed and the deceleration neeed
if (rb.velocity.x != 0)
{
rb.AddForce(new Vector2(speed, 0));
rb.AddForce(new Vector2(decelerationNeeded, 0));
}
}
}
我当前方法的问题是,一旦我更改了速度变量,stopTimeMultipler 就变成了我试图避免的东西——这是一堆猜测工作,以找到一切正常工作所需的确切值。
我确定这种方法存在多个缺陷 - 就像我说的我对物理计算没有很好的理解 - 所以如果你有解决方案,如果你能像你正在谈论的那样解释它一个 5 岁的孩子会很棒!该解决方案不需要达到精确的停止距离 - 只要它相对接近(在 0.2 个单位内)并且可以随着不同的速度和停止距离缩放,就会有一些变化。
好的,在这上面花了更多时间后,我找到了一个可扩展的解决方案。
我已经完全修改了我的方法 - 改为使用这两个视频中显示的方法来加速和减速我的刚体: https://www.youtube.com/watch?v=uVKHllD-JZk https://www.youtube.com/watch?v=YskC8h3xFVQ&ab_channel=BoardToBitsGames (我建议观看这些以充分了解加速是如何实现的)
这些视频让我能够在指定的时间内将物体加速和减速到目标速度。
从这里我能够编写一个方法,将提供的距离值转换为找到应用每次更新的正确加速度量所需的时间变量。
我在这里找到了执行此操作的公式:https://physics.stackexchange.com/questions/18974/what-do-i-need-to-do-to-find-the-stopping-time-of-a-decelerating-car
但对于 c# 实现,请查看下面代码中的 ConvertDistanceToVelocityStep() 方法。
到目前为止,我的所有测试表明,一旦不再提供输入,此方法仅允许提供最大速度和所需的停止距离,以将移动物体减速到指定距离处完全停止。
这是带注释的完整脚本 - 如果您有任何优化或建议的改进,请随时将它们留在下面。
public class Accelerator : MonoBehaviour
{ Rigidbody2D m_Body2D;
// - Speed
public float maxSpeed = 6f;
// - Distance
public float stoppingDistance = 1f;
public float accelDistance = 1f;
// - Time
float timeZeroToMax = 2.5f;
float timeMaxToZero = 6f;
float accelRatePerSec;
float decelRatePerSec;
float xVel;
public bool inputPressed = false;
public bool allowInputs = true;
Vector2 lastHeldDirection;
// - get any needed references
private void Awake()
{
m_Body2D = GetComponent<Rigidbody2D>();
}
// - convert distance values into an acceleration to apply each update
void ConvertDistanceToVelocityStep()
{
//acceleration
timeZeroToMax = (2 * accelDistance) / (maxSpeed - 0);
accelRatePerSec = maxSpeed / timeZeroToMax;
//deceleration
timeMaxToZero = (2 * stoppingDistance) / (0 + maxSpeed);
decelRatePerSec = -maxSpeed / timeMaxToZero;
}
private void Start()
{
ConvertDistanceToVelocityStep();
xVel = 0;
}
private void Update()
{
// if inputs are allowed - check when horizontal buttons are pressed
if (allowInputs)
{
if (Input.GetButtonDown("Horizontal"))
inputPressed = true;
else if (Input.GetButtonUp("Horizontal"))
inputPressed = false;
}
else inputPressed = false;
}
private void FixedUpdate()
{
// if a valid input is provided
if (inputPressed && allowInputs)
{
// get direction
InputDirection();
// get acceleration
Accelerate(accelRatePerSec);
// apply acceleration in desired direction
m_Body2D.velocity = new Vector2(lastHeldDirection.x * xVel, m_Body2D.velocity.y);
}
// if input no longer pressed
else
{
// while still moving
if (Mathf.Abs(m_Body2D.velocity.x) > 0.01f)
{
// get deceleration
Accelerate(decelRatePerSec);
// apply deceleration in last held direction
m_Body2D.velocity = new Vector2(lastHeldDirection.x * xVel, m_Body2D.velocity.y);
}
else
// bring x velocity to zero
m_Body2D.velocity = new Vector2(0, m_Body2D.velocity.y);
}
}
// calculate x velocity to move rigidbody
void Accelerate(float accelRate)
{
xVel += accelRate * Time.deltaTime;
xVel = Mathf.Clamp(xVel, 0, maxSpeed);
}
Vector2 InputDirection()
{
// get both axis of input
float hor = Input.GetAxis("Horizontal");
float vert = Input.GetAxis("Vertical");
// save to vector2
Vector2 inputDir = new Vector2(hor, vert);
// round last held direction to whole number
if (Mathf.Abs(inputDir.x) > 0.25f)
{
if (inputDir.x > 0)
lastHeldDirection.x = 1;
else lastHeldDirection.x = -1;
}
//normalize diagonal inputs
if (inputDir.magnitude > 1)
inputDir.Normalize();
// return input direction
return inputDir;
}
}