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:
void FixedUpdate() //old version
{
//update velocity from inputs, (prev frame) velocity and grounded flag
compute_movement();
//get a tentative delta for this frame
Vector2 dxy = velocity * Time.deltaTime;
//raycast in the directions of movement by length dxy to find hits
//truncate dxy to the shortest hits in each direction
CollisionInfo sides_touching = perform_raycasts(ref dxy);
//zero this.velocity (*not* dxy) along the directions where there was a hit.
//set the grounded flag if there was a hit on the bottom
react_to_collisions(sides_touching);
transform.translate(dxy)
}
After the port to rigidbodies, my code looks like this:
void FixedUpdate() //new, version 1
{
//update velocity from inputs, (prev frame) velocity and grounded flag
compute_movement();
//get a tentative delta for this frame
Vector2 dxy = velocity * Time.deltaTime;
//move first, and allow builtin physics to prevent passing through geometry
rigid_body.MovePosition(transform.position + (Vector3)dxy);
//error: expecting physics step to update the contact points here!
//check sides touching by reading the normals of each contact point
//and checking against cardinal directions (everything is an AABB)
CollisionInfo sides_touching = sides_from_contact_normals();
react_to_collisions(sides_touching)
}
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
void Start() //new, version 2
{
//...
StartCoroutine("post_fixed_update");
//...
}
void FixedUpdate()
{
//update velocity from inputs, (prev frame) velocity and grounded flag
compute_movement();
//get a tentative delta for this frame
Vector2 dxy = velocity * Time.deltaTime;
//move first, and allow builtin physics to prevent passing through geometry
rigid_body.MovePosition(transform.position + (Vector3)dxy);
}
void post_fixed_update()
{
while (true)
{
yield return new WaitForFixedUpdate();
//check sides touching by reading the normals of each contact point
CollisionInfo sides_touching = sides_from_contact_normals();
react_to_collisions(sides_touching)
}
}
(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?