Reputation: 1
In Unity, I need to move a dynamic rigidbody from point A to point B. I know how to do so with a kinematic rigidbody, below the code:
[SerializeField] Transform destination;
[SerializeField] float moveTime = 0.1f;
private Rigidbody2D rb;
private float inverseMoveTime;
private void Start()
{
rb = GetComponent<Rigidbody2D>();
inverseMoveTime = 1f / moveTime;
Move(destination.position.x, destination.position.y);
}
public void Move(float xDir, float yDir)
{
Vector2 start = rb.velocity;
Vector2 end = start + new Vector2(xDir, yDir);
StartCoroutine(SmoothMovement(end));
}
public IEnumerator SmoothMovement(Vector3 end)
{
float sqrRemainingDistance = (transform.position - end).sqrMagnitude;
while (sqrRemainingDistance > float.Epsilon) // While that distance is greater than a very small amount (Epsilon, almost zero):
{
Vector3 newPosition = Vector3.MoveTowards(rb.position, end, inverseMoveTime * Time.deltaTime); // Find a new position proportionally closer to the end, based on the moveTime
rb.MovePosition(new Vector3(newPosition.x, newPosition.y));
sqrRemainingDistance = (transform.position - end).sqrMagnitude; // Recalculate the remaining distance after moving.
yield return null; // Return and loop until sqrRemainingDistance is close enough to zero to end the function
}
rb.MovePosition(end);
}
The problem is that using the function rigidbody.MovePosition() doesn't work well with dynamic rigidbodies, as Unity's docs and my experiments prove.
I've then tried with the following code, but it leads to weird results: the toon moves in the opposite direction of its destination! And the acceleration keeps increasing, the latter seeming the only point I think I can solve pretty easily.
[SerializeField] Transform destination;
[SerializeField] float moveTime;
private Rigidbody2D rb;
private float inverseMoveTime;
private void Start()
{
rb = GetComponent<Rigidbody2D>();
inverseMoveTime = 1f / moveTime;
Move(destination.position.x, destination.position.y);
}
public void Move(float xDir, float yDir)
{
Vector2 startVelocity = rb.velocity;
Vector2 end = startVelocity + new Vector2(xDir, yDir);
StartCoroutine(SmoothMovement(end));
}
public IEnumerator SmoothMovement(Vector3 end)
{
float sqrRemainingDistance = (transform.position - end).sqrMagnitude;
while (sqrRemainingDistance > float.Epsilon) // While that distance is greater than a very small amount (Epsilon, almost zero):
{
Vector2 newPosition = Vector2.MoveTowards(rb.position, end, inverseMoveTime * Time.deltaTime); // Find a new position proportionally closer to the end, based on the moveTime
rb.AddForce(new Vector3(newPosition.x, newPosition.y), ForceMode2D.Force);
sqrRemainingDistance = (transform.position - end).sqrMagnitude; // Recalculate the remaining distance after moving.
yield return null; // Return and loop until sqrRemainingDistance is close enough to zero to end the function
}
rb.MovePosition(end);
}
It's very similar to the first one, changing the way the character moves within the coroutine.
Basically my problem now is: how can I calculate the Vector2 to use withing the function AddForce(Vector2, ForceMode2D.Force) in the coroutine, so that it simply leads my character towards the destination and then the character stops when the destination is reached? Moreover, it should reach the destination at a constant speed, instead of keeping accelerating (but as I was saying above, I think I can solve this easily).
If you know any other way to move a 2D object with a dynamic rigidbody from point A to point B which is effective, of course, it's fine anyways: the coroutine seemed to me a good way but not mandatory. The only requirement is that it doesn't have to be a "tricky" way like transform.position or translate or any other function which ignores the phyisics.
Thanks again for the patience and for the next answer :slight_smile:
Upvotes: 0
Views: 2072
Reputation: 90872
I think in your case you could simply make your Rigidbody a kinematic
for the time the routine is running. But in general you should make sure that your code is executed in FixedUpdate
not every frame
public IEnumerator SmoothMovement(Vector2 end)
{
yield return new WaitForFixedUpdate();
rb.isKinematic = true;
// If you really need a precision down to epsilon
//while (!Mathf.Approximately(Vector2.Distance(rb.position, end), 0f))
// otherwise for most use cases in physics the default precision of
// 0.00001f should actually be enough
while(rb.position != end)
{
rb.MovePosition(rb.position, end, inverseMoveTime * Time.deltaTime);
yield return new WaitForFixedUpdate();
}
rb.position = end;
rb.isKinematic = false;
}
What I don't understand though is why you pass in a position and treat it as it would be a direction added to the current velocity ...
If your goal is moving towards a certain position then rather simply do
StartCoroutine (SmoothMovement(destination.position));
Upvotes: 0