AsterixKnut
AsterixKnut

Reputation: 77

Unity renders object before updating Rigidbody2D's properties

I am trying to implement a way for the player to switch between a square and a circle. For this to work my player object has two colliders, one circle and one box. When switching between them I simply disable one collider and enable the other, and switch the current sprite. The issue arises when I switch from a circle to a square.

I want the square to be able to glide across the floor, whereas the circle is supposed to roll. In order to make the switch seamless I have to reorient the square to be aligned with the current velocity, and remove the angular velocity. This does seem to work, however there is a slight period of frames (or frame) where the square has the same rotation the circle had before switching. This seems odd to me since the new rotation and sprite is changed in the same part of the code. This is a video showcasing the issue.

If this is an issue resulting from the way the objects are rendered I can solve this another way. I would just like to understand why it happens.

Code snippet of the part that changes the properties from circle to square when switching:

else if (Input.GetKeyDown("2"))
        {
            // Update rotation of box to velocity:
            float newAngleRadians = Mathf.Atan2(rb.velocity.y, rb.velocity.x);
            float newAngleDegrees = newAngleRadians * 180 / Mathf.PI;
            rb.rotation = newAngleDegrees;
            rb.angularVelocity = 0;
            Debug.Log(rb.rotation);

            playerShape = Shape.SQUARE;
            spriteRenderer.sprite = spriteArray[1];
            circleCollider.enabled = false;
            boxCollider.enabled = true;

            updateShape = true;
        }

Logging the angle of the rigidbody directly after setting it to newAngleDegrees shows that the rotation has been set correct, yet the issue persists.

And just in case it is needed, full code of the script:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class scr_StateMachine : MonoBehaviour
{
    // Types of shapes:
    public enum Shape { SQUARE, CIRCLE, TRIANGLE }

    // Variables:
    public Rigidbody2D rb;
    public SpriteRenderer spriteRenderer;
    public Sprite[] spriteArray;
    public Shape playerShape;
    public CircleCollider2D circleCollider;
    public BoxCollider2D boxCollider;

    private bool updateShape;


    void Start()
    {
        playerShape = Shape.CIRCLE;
        updateShape = true;
    }

    void Update()
    {
        // Get input for shape change:
        if(Input.GetKeyDown("1"))
        {
            playerShape = Shape.CIRCLE;
            spriteRenderer.sprite = spriteArray[0];
            circleCollider.enabled = true;
            boxCollider.enabled = false;

            updateShape = true;
        }
        else if (Input.GetKeyDown("2"))
        {
            // Update rotation of box to velocity:
            float newAngleRadians = Mathf.Atan2(rb.velocity.y, rb.velocity.x);
            float newAngleDegrees = newAngleRadians * 180 / Mathf.PI;
            rb.rotation = newAngleDegrees;
            rb.angularVelocity = 0;
            Debug.Log(rb.rotation);

            playerShape = Shape.SQUARE;
            spriteRenderer.sprite = spriteArray[1];
            circleCollider.enabled = false;
            boxCollider.enabled = true;

            updateShape = true;
        }

        // Update movement script's shape:
        if (updateShape)
        {
            GetComponent<scr_Movement>().shape = playerShape;
            updateShape = false;
        }
    }
}

Upvotes: 0

Views: 65

Answers (1)

derHugo
derHugo

Reputation: 90813

I think the issue is Rigidbody/Rigidbody2D physics are applied in fixed time steps (see FixedUpdate). Therefore yes, on a strong device it can definitely happen that you render some frames before the next FixedUpdate call kicks in and changes the Rigidbody/Rigidbody2D behavior.

I think what you could do is actually wait with the change for the next FixedUpdate call e.g. using a Coroutine and WaitForFixedUpdate like e.g.

public class scr_StateMachine : MonoBehaviour
{
    // Types of shapes:
    public enum Shape { SQUARE, CIRCLE, TRIANGLE }

    // Variables:
    public Rigidbody2D rb;
    public SpriteRenderer spriteRenderer;
    public Sprite[] spriteArray;
    public Shape playerShape;
    public CircleCollider2D circleCollider;
    public BoxCollider2D boxCollider;

    [SerializeField] private scr_Movement _scrMovement;

    void Start()
    {
        if(!_scrMovement) _scrMovement = GetComponent<scr_Movement>();

        ChangeShape(Shape.CIRCLE);
    }

    // Still get User Input in Update
    void Update()
    {
        // Get input for shape change:
        if(Input.GetKeyDown("1"))
        {
            ChangeShape(Shape.CIRCLE);
        }
        else if (Input.GetKeyDown("2"))
        {
            ChangeShape(Shape.SQUARE);
        }
    }

    private void ChangeShape(Shape newShape)
    {
        // if the same shape comes in we already have -> nothing to do
        if(newShape == playerShape) return;

        // Stop any already running coroutine since we only want to handle the last input before the FixUpdate call
        // https://docs.unity3d.com/ScriptReference/MonoBehaviour.StopAllCoroutines.html
        StopAllCoroutines();

        // Start a new coroutine for the new shape
        // see https://docs.unity3d.com/ScriptReference/MonoBehaviour.StartCoroutine.html
        StartCoroutine(ChangeShapeInNextFixedUpdate(newShape));
    }

    private IEnumerator ChangeShapeInNextFixedUpdate(Shape newShape)
    {
        // just in case again, if the same shape comes in we already have -> nothing to do
        if(newShape == playerShape) yield break;

        // Wait until the next FixedUpdate call
        // see https://docs.unity3d.com/ScriptReference/WaitForFixedUpdate.html
        yield return new WaitForFixedUpdate();

        // Now do your required changes depending on the new shape

        circleCollider.enabled = newShape == Shape.CIRCLE;
        boxCollider.enabled = newShape == Shape.SQUARE;

        switch(newShape)
        {
            case Shape.CIRCLE:
                spriteRenderer.sprite = spriteArray[0];

                break;

            case Shape.SQUARE:
                // Update rotation of box to velocity:
                var newAngleRadians = Mathf.Atan2(rb.velocity.y, rb.velocity.x);
                // see https://docs.unity3d.com/ScriptReference/Mathf.Rad2Deg.html
                var newAngleDegrees = newAngleRadians * Mathf.Rad2Deg;
                rb.rotation = newAngleDegrees;
                rb.angularVelocity = 0;
                Debug.Log(rb.rotation);

                spriteRenderer.sprite = spriteArray[1];

                break;
        }

        playerShape = newShape;
        _scrMovement.shape = playerShape;
    }
}

Upvotes: 2

Related Questions