[C#] Move through rigidbody?

I have this character that, when the player presses an arrow key, I want to move in a singular direction until it hits a wall, after which the player can move again. It also leaves a path behind it. This is part of my script.

    //Move Character and Stop at Blocks
        //Up
    IEnumerator MoveUp()
    {
        Debug.Log("Moved Upwards");
        stop = false;
        start = true;
        collider.size = new Vector2(0.10f, 0.46f);
        Instantiate(downPath, new Vector2(gameObject.transform.position.x, gameObject.transform.position.y), Quaternion.identity, storage.transform);
        while (stop == false)
        {
            transform.position = new Vector2(transform.position.x, transform.position.y + 0.32f);
            if (start == true)
            {
                Instantiate(upPath, new Vector2(gameObject.transform.position.x, gameObject.transform.position.y), Quaternion.identity, storage.transform);            
            }
            if (start == false)
            {
                Instantiate(verticalPath, new Vector2(gameObject.transform.position.x, gameObject.transform.position.y - 0.32f), Quaternion.identity, storage.transform);
            }
            start = false;
            yield return null;
        }
        moving = false;
        collider.size = new Vector2(0.32f, 0.32f);
        Instantiate(upPath, new Vector2(gameObject.transform.position.x, gameObject.transform.position.y), Quaternion.identity, storage.transform);
        StartCoroutine(checkBlocks());
    }
        //Down
    IEnumerator MoveDown()
    {
        Debug.Log("Moved Downwards");
        stop = false;
        start = true;
        collider.size = new Vector2(0.10f, 0.46f);
        Instantiate(upPath, new Vector2(gameObject.transform.position.x, gameObject.transform.position.y), Quaternion.identity, storage.transform);
        while (stop == false)
        {
            transform.position = new Vector2(transform.position.x, transform.position.y - 0.32f);
            if (start == true)
            {
                Instantiate(downPath, new Vector2(gameObject.transform.position.x, gameObject.transform.position.y), Quaternion.identity, storage.transform);
            }
            if (start == false)
            {
                Instantiate(verticalPath, new Vector2(gameObject.transform.position.x, gameObject.transform.position.y + 0.32f), Quaternion.identity, storage.transform);
            }
            start = false;
            yield return null;
        }
        moving = false;
        collider.size = new Vector2(0.32f, 0.32f);
        Instantiate(downPath, new Vector2(gameObject.transform.position.x, gameObject.transform.position.y), Quaternion.identity, storage.transform);
        StartCoroutine(checkBlocks());
    }
        //Left
    IEnumerator MoveLeft()
    {
        Debug.Log("Moved Left");
        stop = false;
        start = true;
        collider.size = new Vector2(0.46f, 0.10f);
        Instantiate(rightPath, new Vector2(gameObject.transform.position.x, gameObject.transform.position.y), Quaternion.identity, storage.transform);
        while (stop == false)
        {
            transform.position = new Vector2(transform.position.x - 0.32f, transform.position.y);
            if (start == true)
            {
                Instantiate(leftPath, new Vector2(gameObject.transform.position.x, gameObject.transform.position.y), Quaternion.identity, storage.transform);
            }
            if (start == false)
            {
                Instantiate(horizontalPath, new Vector2(gameObject.transform.position.x + 0.32f, gameObject.transform.position.y), Quaternion.identity, storage.transform);
            }
            start = false;
            yield return null;
        }
        moving = false;
        collider.size = new Vector2(0.32f, 0.32f);
        Instantiate(leftPath, new Vector2(gameObject.transform.position.x, gameObject.transform.position.y), Quaternion.identity, storage.transform);
        StartCoroutine(checkBlocks());
    }
        //Right
    IEnumerator MoveRight()
    {
        Debug.Log("Moved Right");
        stop = false;
        start = true;
        collider.size = new Vector2(0.46f, 0.10f);
        Instantiate(leftPath, new Vector2(gameObject.transform.position.x, gameObject.transform.position.y), Quaternion.identity, storage.transform);
        while (stop == false)
        {
            transform.position = new Vector2(transform.position.x + 0.32f, transform.position.y);
            if (start == true)
            {
                Instantiate(rightPath, new Vector2(gameObject.transform.position.x, gameObject.transform.position.y), Quaternion.identity, storage.transform);
            }
            if (start == false)
            {
                Instantiate(horizontalPath, new Vector2(gameObject.transform.position.x - 0.32f, gameObject.transform.position.y), Quaternion.identity, storage.transform);
            }
            start = false;
            yield return null;
        }
        moving = false;
        collider.size = new Vector2(0.32f, 0.32f);
        Instantiate(rightPath, new Vector2(gameObject.transform.position.x, gameObject.transform.position.y), Quaternion.identity, storage.transform);
        StartCoroutine(checkBlocks());
    }

    //If Enter Block, Stop
    private void OnTriggerEnter2D (Collider2D other)
    {
        stop = true;
    }

The problem with this that I noticed is: usually my character moves normally and everything worked as intended. They moved in units of 0.32 and stopped at walls. The problem was that about every 1/10 times the character would phase into a block, and if the move button was pressed again pass through it. Now I am pretty sure this is because I am moving definitively instead of moving with Unity’s physics.

I can’t find any examples of moving in unity without the Input.GetAxis method, and that won’t work in my code because the player will only press the button once. Also, I need my character to always occupy spaces that are multiples of 0.32, and I detect this through colliders. So either I need code that will stop exactly at a block, or that can move my character to a multiple of 0.32 after the movement; all of this with physics based movement.

Can anyone help me? Feel free to ask any questions, and I will provide the full script if needed. I don’t need a fully coded answer (though that would be helpful), just someone to point me in the right direction.

TLDR: Need character to continuously move in one direction through physics, and then occupy a position that is a multiple of 0.32.

When you set the transform.position of an object, to the Physics system, that is a “teleport.” No actual checking is done. Collisions might happen, or not.

If you want to “move with physics” a specific amount, then use a reference to your RigidBody (or Rigidbody2D) and call the .MovePosition() method. That will cause the physics system to move it for reals, triggering all the right collisions, bounds checking, bouncing, etc.

As for the snapping on 0.32 boundaries, probably the easiest way is at the end of the movement, re-quantize the individual coordinates (X,Y) to be on 0.32 boundaries. You can re-quantize by first adding a small fudge amount (to make sure you aren’t at 0.3199 for instance), then dividing by 0.32, taking the integer value of that, and then multiplying by 0.32 again.

float originalUnquantizedCoordinate = 0.123456f; // however you get this, from transform.position.x maybe?

const float Quantization = 0.32f;
const float Fudge = 0.05f;

int wholeSteps = (int)(originalUnquantizedCoordinate + Fudge) / Quantization);

float quantizedCoodinate = wholeSteps * Quantization;

Do that for X and for Y obviously, depending on your motion.

I thank you for taking the time, and I have some more questions for you.

I don’t really understand how MovePosition() works. Specifically, how can I specify that the character should move left or right, and up or down? I don’t understand the example given in the documentation.

I also don’t understand how the math in your example works. At all, honestly. I know it is asking for a bit much, but if you could walk me through in further detail it would be appreciated.

MovePosition() basically teleport you to to Vector3 position with PhysX.
I think the above example could use rounding instead.

    public float speed = 10f;
    private new Rigidbody rigidbody;

    public float Quantization = 0.32f;

    // Use this for initialization
    void Start()
    {
        rigidbody = GetComponent<Rigidbody>();
    }

    // Update is called once per frame
    void FixedUpdate()
    {
        var hax = Input.GetAxis("Horizontal");

        if (hax > 0f) // Move Right along X
        {
            rigidbody.MovePosition(transform.position + new Vector3(Time.deltaTime * speed * hax, 0, 0));
        }
        else if (hax < 0f) // Move Left along X
        {
            rigidbody.MovePosition(transform.position + new Vector3(Time.deltaTime * speed * hax, 0, 0));
        }
        else // Readjust when input ceased
        {
            var pos = transform.position;
            int roundedSteps = Mathf.RoundToInt(pos.x / Quantization);

            pos.x = roundedSteps * Quantization;
            //transform.position = pos; OR
            rigidbody.MovePosition(pos); // Preferably with Interpolate mode in Inspector set to Interpolate
        }
    }

Can you try that?

Thank you for your time, and I have a few questions for you if your are willing to stay and answer them.

For your movement example wouldn’t I have to hold down the left or right arrow to move? My issue is I need it to move continuously with one press of a button, and I don’t understand how to do that without Input.getAxis.

I would also like to know what Interpolate mode is.

Thank you for your math example, I understand it and it seems to work.

It’s easy, you can use an enum and GetKeyDown.

....
    enum Dir { Idle, Left, Right }
    Dir dir = Dir.Idle;
....

    // Update is called once per frame
    void FixedUpdate()
    {

        if (Input.GetKeyDown(KeyCode.LeftArrow)) dir = Dir.Left;
        if (Input.GetKeyDown(KeyCode.RightArrow)) dir = Dir.Right;

        // Whatever condition you set for stop, for example:
        if (Input.GetKeyDown(KeyCode.Space)) dir = Dir.Idle;

        if (dir == Dir.Left)
        {
            rigidbody.MovePosition(transform.position + new Vector3(-Time.deltaTime * speed, 0, 0));
        }
        else if (dir == Dir.Right)
        {
            rigidbody.MovePosition(transform.position + new Vector3(Time.deltaTime * speed, 0, 0));
        }

        else if (dir == Dir.Idle) // Readjust when idle condition
        {
            var pos = transform.position;
            int wholeSteps = Mathf.RoundToInt(pos.x / Quantization);

            pos.x = wholeSteps * Quantization;
            //transform.position = pos; OR
            rigidbody.MovePosition(pos); // Preferably with Interpolate mode in Inspector set to Interpolate
        }
    }

Maybe you want to optimize the idle clause to run only once.
Here is the doc page on rigidbody interpolation: Unity - Scripting API: Rigidbody.interpolation
Hope that helps.

1 Like

Sorry that it has taken me until now to respond to you.

Your code worked perfectly, and I know understand how MovePosition works, thank you. I also managed to use your rounding code to get my trails to work with the new movement system. You’ve truly given me a new foundation to stand on, and I thank you for that.

2 Likes

You’re welcome, glad I could be of help :wink:

1 Like