What is causing transform.position to change (besides my script)?

I’m having a problem trying to stop the player after they run into an obstacle.

The general program flow is this:

  1. I have a Vector3 called moveTarget. This value is set when the player clicks the mouse somewhere on the screen.
  2. During FixedUpdate, if the player’s transform.position != moveTarget, I move the player towards the move target.
  3. If the player collides with anything, I set moveTarget = transform.position. This has the effect of stopping the player.

Most of the time this works. However, occasionally the player doesn’t stop moving.

I’ve included my script below. Here are the run time results from the Debug.Log() statements in the script:

=== OnCollisionEnter2D ===

19.62: transform.position: (0.3688746, 1.15707, 0), moveTarget: (1.511121, 1.197506, 0)
playerController:OnCollisionEnter2D(Collision2D) (at Assets/Scripts/playerController.cs:121)

19.62: transform.position: (0.3688746, 1.15707, 0), moveTarget: (0.3688746, 1.15707, 0)
playerController:OnCollisionEnter2D(Collision2D) (at Assets/Scripts/playerController.cs:123)

19.64: transform.position: (0.3688746, 1.15707, 0), moveTarget: (0.3688746, 1.15707, 0), AreEqual: True
playerController:FixedUpdate() (at Assets/Scripts/playerController.cs:38)

Not moving
playerController:FixedUpdate() (at Assets/Scripts/playerController.cs:51)

19.66: transform.position: (0.3559954, 1.156929, 0), moveTarget: (0.3688746, 1.15707, 0), AreEqual: False
playerController:FixedUpdate() (at Assets/Scripts/playerController.cs:38)

The first 19.62 shows that transform.position != moveTarget. The second 19.62 shows that they are equal after I explicitly set them equal.

The 19.64 entry is in FixedUpdate. Notice that the values are still equal to each other.

As expected, the next entry is “Not Moving”.

However, at 19.66 the values are different again and I don’t know why.

I don’t see anything in my code that would start the movement again.

using UnityEngine;
using System.Collections;

[System.Serializable]
public class Boundary {
    public float xMin, xMax, yMin, yMax;
}

public class playerController : MonoBehaviour {

    private enum Facing {
        Left,
        Right
    }

    private Animator animator;
    private Vector3 moveTarget; // Point the character moves towards (at moveSpeed).
    private Facing facing = Facing.Left; // Whether the character is facing left or right.

    public float moveSpeed = 1; // How fast the character will move.
    public Boundary boundary; // The scene bounds.

    // Use this for initialization
    void Start() {
        animator = this.GetComponent<Animator>();
        moveTarget = transform.position;
    }

    void Update() {
    }

    // Update is called once per frame
    void FixedUpdate() {
        if (Input.GetMouseButtonDown(0)) {
            SetMoveTarget();
        }

        Debug.Log(Time.time + ": transform.position: " + AccurateVector3ValuesToString(transform.position) + ", moveTarget: " + AccurateVector3ValuesToString(moveTarget) + ", AreEqual: " + (transform.position == moveTarget));
    
        if (transform.position != moveTarget) {
            animator.SetBool("IsMoving", true);

            SetPlayerFacing();
            MoveTowardsMoveTarget();
        }
        else {
            Debug.Log("Not moving");
            animator.SetBool("IsMoving", false);
        }
    }

    private string AccurateVector3ValuesToString(Vector3 vector3) {
        return "(" + vector3.x + ", " + vector3.y + ", " + vector3.z + ")"; 
    }

    // Sets the target the character will move towards. 
    private void SetMoveTarget() {
        var mousePosition = Camera.main.ScreenToWorldPoint(Input.mousePosition);
        mousePosition.z = 0; // We don't care about the z axis.
        var direction = mousePosition - transform.position;
        var distance = (transform.position - mousePosition).magnitude;

        // 1 << 0 is the bit-masked value for which layers to check on.
        RaycastHit2D raycastHit2D = Physics2D.Raycast(transform.position, direction, distance, 1 << 0);

        if (raycastHit2D.collider != null) {
            // If there was a collision, it means the mouse was clicked outside of the play area.
            moveTarget = raycastHit2D.point;
        
            // If the user clicked outside of the boundaries, we want to move the character to the most extreme value for 
            // the appropriate axis. 
            if (raycastHit2D.collider.name.Equals("LeftBoundary") || raycastHit2D.collider.name.Equals("RightBoundary")) {
                moveTarget.y = mousePosition.y;
            }
            else if (raycastHit2D.collider.name.Equals("TopBoundary") || raycastHit2D.collider.name.Equals("BottomBoundary")) {
                moveTarget.x = mousePosition.x;
            }
        }
        else {
            moveTarget = mousePosition;
        }
    }

    // Face the character to the left or right depending on which way they are heading.
    private void SetPlayerFacing() {
        if ((facing == Facing.Left && moveTarget.x > transform.position.x) || (facing == Facing.Right && moveTarget.x < transform.position.x)) {
            Vector3 scale = transform.localScale;
            scale.x *= -1;
            transform.localScale = scale;

            if (facing == Facing.Left) {
                facing = Facing.Right;
            }
            else {
                facing = Facing.Left;
            }
        }
    }

    // Moves the character towards the moveTowards target.
    private void MoveTowardsMoveTarget() {
        Debug.Log(Time.time + ": MoveTowardsMoveTarget");
        var newPosition = Vector3.MoveTowards(transform.position, moveTarget, moveSpeed * Time.deltaTime);

        newPosition.x = Mathf.Clamp(newPosition.x, boundary.xMin, boundary.xMax);
        newPosition.y = Mathf.Clamp(newPosition.y, boundary.yMin, boundary.yMax);

        if (transform.position != moveTarget) {
            transform.position = newPosition;
        }
    }

    void OnCollisionEnter2D(Collision2D collision2D) {
        if (collision2D.gameObject.layer == LayerMask.NameToLayer("Obstacle")) {
            Debug.Log("=== OnCollisionEnter2D ===");
            Debug.Log(Time.time + ": transform.position: " + AccurateVector3ValuesToString(transform.position) + ", moveTarget: " + AccurateVector3ValuesToString(moveTarget));
            moveTarget = transform.position;
            Debug.Log(Time.time + ": transform.position: " + AccurateVector3ValuesToString(transform.position) + ", moveTarget: " + AccurateVector3ValuesToString(moveTarget));
        }
    }
}

1 Answer

1

Maybe that is a result of the collision and/or other physics since you are using rigidbodies? Anyway, it is always better to accept a small radius (eg 0.1 or 0.2) of distance between the target and current position instead of directly comparing them. So in your code if (transform.position != moveTarget) can be replaced with if (Vector3.SqrDistance(transform.position, moveTarget) > slop * slop). slop is a public variable which you can set to a small value like 0.2 or 0.1 so that problems such as what you are getting can be avoided. These problems may be caused by floating point errors also.

Thank you Evil Tak. I couldn't figure out why it the position was still moving but comparing the distance to the small value rather than using = worked well.