Search code examples
unity-game-enginephysicsrigid-bodies

RigidBody.MovePosition won't stop even after reaching the destination


I am creating an elevator of which constantly moves from Point A to Point B. I cannot just use transform.position ( location here * speed here etc) since I have a player that has a Rigidbody, and if using that it would flicker my player out whenever I am on the elevator. I also tried parenting the Player whenever its on the elevator (and de-parents it when it jumps etc), that fixes the flickering however it somewhat bugs the player's jumping mechanism.

Last resort is using a Rigidbody to my elevator and moves it with this code:

private void FixedUpdate() 
{
    Vector2 l_mypos = new Vector2(transform.position.x, transform.position.y);
    Vector2 l_target = new Vector2(_targetPoint.position.x, _targetPoint.position.y);
    if (l_mypos != l_target)
    {
        MoveElevator(l_target);
        Debug.Log(l_mypos + " - " + l_target);
    }
    else
        Debug.Log("reached");
}

private void MoveElevator(Vector2 toTarget)
{
    Vector2 direction = (toTarget - (Vector2)transform.position).normalized;
    _elevatorRB.MovePosition((Vector2)transform.position + direction * _speed * Time.deltaTime);
}

This moves the elevator towards the direction given, however it doesn't reach the "reached" condition. I placed a debug.log there to see both mpos and target to see the differences. It ends with 0, 10, 0 - 0, 10, 0 meaning both my target and the elevator's position is already the same. However it doesn't reach the else condition, and the elevator keeps flickering at Point B.


Solution

  • The == operator for Vector2 uses an estimation of 0.00001 for equality.

    However it is very possible that you overshoot the target in

    (Vector2)transform.position + direction * _speed * Time.deltaTime
    

    since your final velocity speed * Time.deltaTime is certainly greater than 0.00001. (Except your speed is smaller than 0.0006 which I doubt.)

    The value you see in the Debug.Log is the result of a Vector3.ToString which uses human readable rounded values and does not show the actually float values! From the source code

    public override string ToString()
    {
        return ToString(null, CultureInfo.InvariantCulture.NumberFormat);
    }
    
    public string ToString(string format)
    {
        return ToString(format, CultureInfo.InvariantCulture.NumberFormat);
    }
    
    public string ToString(string format, IFormatProvider formatProvider)
    {
        if (string.IsNullOrEmpty(format)) format = "F1"; // <- !! HERE !!
    
        return UnityString.Format("({0}, {1}, {2})", x.ToString(format, formatProvider), y.ToString(format, formatProvider), z.ToString(format, formatProvider));
    }
    

    You should rather use Vector2.MoveTowards which avoids this overshooting.

    The API of Vector3.MoveTowards actually explains it better then the one of Vector2.MoveTowards

    Calculate a position between the points specified by current and target, moving no farther than the distance specified by maxDistanceDelta.

    Use the MoveTowards member to move an object at the current position toward the target position. By updating an object’s position each frame using the position calculated by this function, you can move it towards the target smoothly. Control the speed of movement with the maxDistanceDelta parameter. If the current position is already closer to the target than maxDistanceDelta, the value returned is equal to target; the new position does not overshoot target. To make sure that object speed is independent of frame rate, multiply the maxDistanceDelta value by Time.deltaTime

    private void FixedUpdate() 
    {
         // Vector3 and Vector2 have implicit operators allowing to use
         // both types exchangeably. 
         // In order actively to convert them you can simply typecast between them
         var l_mypos = (Vector2) transform.position;
         var l_target = (Vector2) _targetPoint.position;
    
         if (l_mypos != l_target)
         {
             MoveElevator(l_target);
             Debug.Log(l_mypos + " - " + l_target);
         }
         else
         {
             Debug.Log("reached");
         }
    }
    
    private void MoveElevator(Vector2 toTarget)
    {
        var pos = Vector2.MoveTowards(transform.position, toTarget, _speed * Time.deltaTime);
        _elevatorRB.MovePosition(pos);
    }