Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Question working around FixedUpdate-Physics Step execution order

Discussion in 'Physics' started by supernameworld, Apr 17, 2021.

  1. supernameworld

    supernameworld

    Joined:
    Sep 14, 2015
    Posts:
    16
    Hi all,

    I am trying to convert my 2d platformer controller from a raycast based system to a rigidbody based system, but I'm running into some trouble with the physics execution order that I don't know how to resolve. This seems like a general enough problem that I thought I might be able to get help from people here :)

    The problem essentially boils down to these two small snippets of code. Originally, my fixed update call looked like this:

    Code (CSharp):
    1. void FixedUpdate() //old version
    2. {
    3.      //update velocity from inputs, (prev frame) velocity and grounded flag
    4.      compute_movement();
    5.    
    6.      //get a tentative delta for this frame
    7.      Vector2 dxy = velocity * Time.deltaTime;
    8.    
    9.      //raycast in the directions of movement by length dxy to find hits
    10.      //truncate dxy to the shortest hits in each direction
    11.      CollisionInfo sides_touching = perform_raycasts(ref dxy);
    12.    
    13.      //zero this.velocity (*not* dxy) along the directions where there was a hit.
    14.      //set the grounded flag if there was a hit on the bottom
    15.      react_to_collisions(sides_touching);
    16.    
    17.      transform.translate(dxy)
    18. }
    After the port to rigidbodies, my code looks like this:

    Code (CSharp):
    1. void FixedUpdate() //new, version 1
    2. {
    3.      //update velocity from inputs, (prev frame) velocity and grounded flag
    4.      compute_movement();
    5.    
    6.      //get a tentative delta for this frame
    7.      Vector2 dxy = velocity * Time.deltaTime;
    8.    
    9.      //move first, and allow builtin physics to prevent passing through geometry
    10.      rigid_body.MovePosition(transform.position + (Vector3)dxy);
    11.  
    12.      //error: expecting physics step to update the contact points here!
    13.    
    14.      //check sides touching by reading the normals of each contact point
    15.      //and checking against cardinal directions (everything is an AABB)
    16.      CollisionInfo sides_touching = sides_from_contact_normals();
    17.    
    18.      react_to_collisions(sides_touching)
    19. }
    Obviously this doesn't work because the physics step won't update the contact points until after fixedupdate is done running. The result is that if the player jumps, there will be an extra frame where they are in the air but still register as grounded (this does cause different behavior for me in the game and is important).

    I tried fixing this by moving the last bit of code into a coroutine like so, but am still having the same problem. If the player jumps, the contact points still arent cleared between the call to moveposition and the call to check_sides_from_contact_normals

    Code (CSharp):
    1. void Start() //new, version 2
    2. {
    3.     //...
    4.     StartCoroutine("post_fixed_update");
    5.     //...
    6. }
    7.  
    8. void FixedUpdate()
    9. {
    10.      //update velocity from inputs, (prev frame) velocity and grounded flag
    11.      compute_movement();
    12.    
    13.      //get a tentative delta for this frame
    14.      Vector2 dxy = velocity * Time.deltaTime;
    15.    
    16.      //move first, and allow builtin physics to prevent passing through geometry
    17.      rigid_body.MovePosition(transform.position + (Vector3)dxy);
    18. }
    19.  
    20. void post_fixed_update()
    21. {
    22.     while (true)
    23.     {
    24.          yield return new WaitForFixedUpdate();
    25.          //check sides touching by reading the normals of each contact point
    26.          CollisionInfo sides_touching = sides_from_contact_normals();
    27.          react_to_collisions(sides_touching)
    28.     }
    29. }
    (note that in all 3 cases, I am using my own Vector2 velocity)

    Based on the execution order, it looks like this version should work, but when I step through with the debugger, I am still seeing sides_from_contact_normals() register a floor on the jump frame. Velocity is positive, the player's position has moved up, and still I am getting a contact point where there shouldn't be any.

    Non-solutions: I am using highly nonphysical equations of motion so forces are out of the question. I also require all the precision I can get, so approximate detections of floors, walls, etc using boxcasts or spherecasts are unusable.

    Has anyone else come across this problem? What is the way of getting around it?
     
  2. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,321
    Why would you expect that? As the docs state, "It is important to understand that the actual position change will only occur during the next physics update therefore calling this method repeatedly without waiting for the next physics update will result in the last call being used. For this reason, it is recommended that it is called during the FixedUpdate callback."

    If you're not interested in dynamic contacts in-between those position movements (sweeps) because everything is Kinematic then you can set the Rigidbody2D position/rotation directly prior the to simulation step and just teleport there. Doing this means any spatial queries you perform (overlaps, casts etc) are immediately available. If you're reading contacts though, they are only available from the last simulation step.

    You are also free to manually perform the physics simulation step using Physics2D.Simulate() too if that helps. You can arrange for this to be done in a script that executes before any other FixedUpdate callback on any other MonoBehaviour by controlling the script execution order of the script. Also, you are free to simulate per-frame if you want. You can even do so without code by setting the Phyiscs2D.SimulationMode to Update.
     
  3. supernameworld

    supernameworld

    Joined:
    Sep 14, 2015
    Posts:
    16
    Maybe expected is the wrong word to use. I only was trying to explain that that was the logical error in the code. I understand why the second example doesn't work. I don't understand why the third example doesn't fix it. WaitForFixedUpdate happens after the physics step, doesn't it?

    Even if I put sides_from_contact_normals() and react_to_collisions() at the *top* of fixed update, I am still getting the same issue of having 1 frame contact delay. That seems like it obviously puts the execution order the right way yet the issue is the same

    (E: I should add that Physics.SimulationMode isn't available in my unity version, and querying with casts and overlaps brings me back to where I was with the raycast controller, which was insufficient in the first place because of large number of moving platforms with composite shapes, where I need to know the side that was hit, not just that there was one. A moving platform and a stationary player, e.g, would present a problem in that setup. Casting from every moving platform would slow the game to a crawl from the number and shapes of the platforms)

    (E2: putting Physics2D.Simulate(Time.deltaTime) directly in between MovePosition and sides_from_contact_normals does not fix the one frame lag for me. In fact, when I step with the debugger, it doesn't even look like Physics2D.Simulate updates the position before the next line is reached.)
     
    Last edited: Apr 18, 2021
  4. MaartenB

    MaartenB

    Joined:
    Nov 6, 2014
    Posts:
    67
    I can confirm this issue and have helped @shelljump as a Unity Live Expert.

    Maybe it's something we're missing both, but a Bug report will be submitted by @shelljump .
     
  5. supernameworld

    supernameworld

    Joined:
    Sep 14, 2015
    Posts:
    16
    This indeed looks like some kind of editor bug.
    I sent in a bug report and will also paste an example project here.

    The following script illustrates the problem.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class UnityBug : MonoBehaviour
    6. {
    7.     ContactPoint2D[] contacts = new ContactPoint2D[20];
    8.     bool buffer_input = false;
    9.  
    10.     void Start()
    11.     {
    12.         StartCoroutine(after_physics_step());
    13.     }
    14.  
    15.     void Update()
    16.     {
    17.         if (Input.GetKeyDown(KeyCode.Space)) buffer_input = true;
    18.     }
    19.  
    20.     void FixedUpdate()
    21.     {
    22.         if (buffer_input)
    23.         {
    24.             GetComponent<Rigidbody2D>().MovePosition(transform.position + Vector3.up);
    25.         }
    26.     }
    27.     IEnumerator after_physics_step()
    28.     {
    29.         while (true)
    30.         {
    31.             yield return new WaitForFixedUpdate();
    32.             if (GetComponent<Rigidbody2D>().GetContacts(contacts) > 0)
    33.             {
    34.                 if (buffer_input) Debug.Log("contact points are lagging");
    35.                 else Debug.Log("touching");
    36.             }
    37.             else
    38.             {
    39.                 Debug.Log("airborne");
    40.             }
    41.  
    42.             buffer_input = false;
    43.         }
    44.     }
    45. }
    46.  
    In a scene like the one in the attached image, I place this script on the white square and align it flush with a box collider 2D. Pressing space moves the white square off the box collider.

    The frame that the white square moves, it runs fixedupdate, which calls MovePosition, then does a physics step, then does GetContacts. GetContacts returns contact points with the floor, even though there is a one unit gap between the white square and the floor after the physics step.

    If you don't like WaitForFixedUpdate you can manually step physics as suggested and then read contact points and you will get the same issue. This happens in the most recent version of unity but has existed as an error since at least the 2019 version.
     

    Attached Files:

  6. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,321
    If I follow what you're saying, this isn't a bug, it's just not what you want. This comes up time and time again for 2D and 3D physics.

    Contacts are not where it is in contact now, it's where there was a contact at the start of the simulation step. The rest of the simulation step involves resolving the contact. When everything is solved, it doesn't then recalculate all the contacts. Contacts are historic. This applies to both 2D (Box2D) and 3D (physx).

    https://forum.unity.com/threads/oncollisionstay-contacts-are-a-frame-behind.1092256/
     
  7. supernameworld

    supernameworld

    Joined:
    Sep 14, 2015
    Posts:
    16
    I guess I don't understand what the intended method of precise grounded/side detection is then.

    Every blogpost/tutorial I have come across has said to do one of two things:
    1. use forces and resign yourself to indirect control of player velocity
    2. use raycasts for hit detection

    1. doesn't work, and if you try 2, you run into a problem with corners, as wall hits come up as floors, etc

    the standard reply to this seems to be "only raycast in the direction of movement" which immediately breaks as soon as you add a moving platform.

    "Just add raycasts from moving platforms also". What if almost all platforms in your game are moving? with complex shapes?

    Is there not some function that I can call to just do GJK/EPA against my player object, and grab the contact points?
     
    Last edited: Apr 21, 2021