- Home /
Misunderstanding of OnCollisionStay2D
TL;DR I'm having a problem with OnCollisionStay2D activating when it seems like it shouldn't be. When the player is on the ground and jumps, they get a burst of speed, and immediately stop just above the ground, and onGround = true. The bolded section below is where I think my misunderstanding is.
public class PlayerAction : MonoBehaviour
{
public Vector2 jForce = new Vector2(0, 15);
public Vector2 gForce = new Vector2(0, -1);
public Vector2 pnp;
public Vector2 exForce;
public bool airborne = true;
public bool onGround = false;
private bool groundSwitch = false;
private Rigidbody2D rb2d;
private PlayerInputs pInputs;
private ContactPoint2D[] cpoint;
void Start()
{
rb2d = GetComponent<Rigidbody2D>();
pInputs = GetComponent<PlayerInputs>();
cpoint = new ContactPoint2D[10];
}
private void OnCollisionStay2D(Collision2D col)
{
col.GetContacts(cpoint);
Vector2 normal = cpoint[0].normal;
if (col.gameObject.layer == 10)
{
if (normal == Vector2.up)
{
onGround = true;
}
}
}
void FixedUpdate()
{
//Collision Switches
if (onGround == true)
{
if (groundSwitch != onGround)
{
if (groundSwitch == false)
{
Debug.Log("FirstGroundFrame");
exForce = new Vector2(exForce.x, 0);
}
}
}
//Set airborne state
if (onGround == true)
{
airborne = false;
}
else
{
airborne = true;
}
//Jump
if (pInputs.Jump == true)
{
if (onGround == true)
{
onGround = false;
exForce += jForce;
}
pInputs.Jump = false;
}
//Gravity
if (airborne == true)
{
exForce += gForce;
}
pnp += exForce;
rb2d.velocity = pnp;
pnp = new Vector2(0, 0);
groundSwitch = onGround;
}
}
My understanding of that code is as follows:
Player starts on the ground. OnCollisionStay2D has been running, and the player recognizes they're on the ground because of it.
Player hits the spacebar. The input is processed and sent to this script to queue up a jump action for the first cycle (pInputs.Jump).
FixedUpdate runs. It finds onGround = true, so it sets airborne = true.
pInputs.Jump and onGround = true, so the jumping force is applied to the upcoming motion, and onGround and pInput.Jump are set to false.
Gravity checks whether the player is currently airborne, finds that they aren't yet (no airborne check has occurred), and so it doesn't apply any force.
The game applies the queued forces (just the jump) to the pnp, and sets that as the Rigidbody2D's velocity for the frame.
pnp is wiped, and the groundSwitch is set to false.
The internal physics update for this cycle occurs, acting on the player and moving them according to Rigidbody2D's set velocity.
OnCollisionStay2D runs, sees that the player's current position means they aren't touching the ground, and does not set them as grounded.
As the second cycle begins, FixedUpdate checks onGround, finds that it is false, and does not initiate the chain of events that would cause the Player's upward motion to immediately stop.
Obviously I'm missing something, as this is not what's occurring. Instead of continuing to move upwards (only to be caught by gravity), the Player is immediately stopped, onGround is set to true/airborne is set to false, and the console shows "FirstGroundFrame", even though the player is floating in the air.
It seems that this is due to the OnCollisionStay2D not factoring in the physics update? From looking at this chart (https://docs.unity3d.com/Manual/ExecutionOrder.html), it looks to me like the game moves the player before the collision checks go off, or is that not the case? Any help understanding this, or any possible solutions to my problem would be much appreciated.
Answer by VincentLagerros · Jan 22, 2019 at 04:51 PM
If you are having problems with OnCollisionStay2D, you could use: void OnCollisionEnter2D(Collision2D collision) { onGround = true; } void OnCollisionExit2D(Collision2D collision) { onGround = false; }
I strongly recommend that you use the built-in gravity, and use .AddForce for the jump, something like this
if (pInputs.Jump) { if (onGround) { rb2d.addForce(jumpVel); } pInputs.Jump = false; }
And you can just type (onGround) instead of if (onGround == true), and airborne = !onGround, just easier to read code :)
I realize there are a few other ways to do this, but my question is really why is this happening? Is the information about the execution order wrong, or am I misunderstanding it, or what? I know I can do something like what you've said, or use raycasts to check for ground, but even if I can I'd rather know first why a method I've tried won't work so that I have the information for any future issues.
I think it is`//Jump if (pInputs.Jump == true) { if (onGround == true) { onGround = false; exForce += jForce;
} pInputs.Jump = false; }`
that is wrong, try if(Input.Get$$anonymous$$eyDown($$anonymous$$eyCode.Space)) ins$$anonymous$$d of if (pInputs.Jump == true), and see if that works
That works, but that brings the problem of having your Inputs dropped occasionally when registered during FIxUpdate.
Though it does bring some light as to why what's happening is happening. This means it doesn't seem to be an issue with the relationship between FixedUpdate and OnCollisionStay2D, but it's a problem that includes the Update sequencing, and passing inputs back and forth between FixedUpdate and Update.
Your answer
Follow this Question
Related Questions
Check if player is on track 1 Answer
How to use OverlapCollider? 1 Answer
Making an object collide but transparent 0 Answers
OnCollisionStay going one more time after i jump 1 Answer
Spherecast not always returning hits when it should? 1 Answer