Does anyone have a better way to make an object with a rigidbody check for ground and then jump, right now I wrote a script where a raycast is cast downwards and it checks for distance but then I discovered that a player can still jump when close to the ground and when I decrease the distance amount on raycast then the jumping works a few times (5 or 6) and then stops working, so I had to increase the distance a bit and add a delay so the player can't jump for some time after a jump. But I think there might be a better way of doing it. So does anyone have suggestions on how can I improve my script or what other method I can use to make a character with a rigidbody to check if grounded and jump?
-
\$\begingroup\$ What does "stops working" mean exactly? \$\endgroup\$– VaTTeRGeRCommented Aug 10, 2015 at 9:27
-
\$\begingroup\$ It means that when I press space the player isn't jumping \$\endgroup\$– John SmithCommented Aug 10, 2015 at 9:31
-
1\$\begingroup\$ ok, seems like your ray loses contact, you could check for velocity. player has a low z-velocity -> enable jump. This works if your player is jumping from horizontal surfaces only. You could check with the longer ray in addition to checking the velocity to be more certain. \$\endgroup\$– VaTTeRGeRCommented Aug 10, 2015 at 9:40
-
\$\begingroup\$ @VaTTeRGeR - That idea actually works a lot better than I thought it would. One small problem - at the apex of your jump, your velocity will be near zero. I handled this by requiring two frames in a row of y-velocity being under 0.01f. Can probably trip this up by running up or down hill or something, but eh, works well enough for now. Maybe my game just won't have hills. Who knows. \$\endgroup\$– ArtOfWarfareCommented Feb 17, 2018 at 4:04
3 Answers
Here is a simplified example of how you could implement a check to see if your gameObject is grounded or not. It is not perfect, but should probably work for your situation:
bool Grounded;
void OnCollisionStay2D(Collision2D collider)
{
CheckIfGrounded ();
}
void OnCollisionExit2D(Collision2D collider)
{
Grounded = false;
}
private void CheckIfGrounded()
{
RaycastHit2D[] hits;
//We raycast down 1 pixel from this position to check for a collider
Vector2 positionToCheck = transform.position;
hits = Physics2D.RaycastAll (positionToCheck, new Vector2 (0, -1), 0.01f);
//if a collider was hit, we are grounded
if (hits.Length > 0) {
Grounded = true;
}
}
-
\$\begingroup\$ Pretty good solution. Probably a good thing to suggest that you might want to check the normal of that surface which the ray intersects, and then can be useful to detect, for example, a slanted wall that should not be treated as a ground. \$\endgroup\$ Commented Mar 16, 2021 at 3:58
What worked for me is the following:
private int countColliders = 0;
private void OnCollisionEnter(Collision collision) {
countColliders++;
}
private void OnCollisionExit(Collision collision) {
countColliders--;
}
private void IsGrounded() {
return countColliders>0;
}
(after setting the player layer to Player and the ground layer to Ground and setting the collision table so that only Player and Ground layers collide).
-
2\$\begingroup\$ This solution can land you in trouble if you're moving over a floor made of multiple colliders. In one frame, you might leave the collider on the left, while still standing on the collider on the right. If the OnCollisionExit for the left collider fires after the OnCollisionStay for the right collider, your grounded is false for a frame when it should have been true, possibly eating a valid jump input. A better solution is to keep a count of colliders - increment on enter, decrement on exit, then isGrounded becomes groundedColliders > 0 \$\endgroup\$– DMGregory ♦Commented May 13, 2020 at 4:53
-
1\$\begingroup\$ @DMGregory Indeed, when I did not use OnCollisionStay, my player did not jump, and I did not understand why - now thanks to your explanation I understand! I replaced the boolean with an int as you suggested, and now it works even without OnCollisionStay. \$\endgroup\$ Commented May 14, 2020 at 5:12
Use the following script it will work well...
Animator anim;
Rigidbody2D rigid;
bool facingRight = true;
bool canJump = true;
public Transform startpos, endpos;
public LayerMask groundLayer;
void Start()
{
rigid = gameObject.GetComponent<Rigidbody2D>();
anim = gameObject.GetComponent<Animator>();
}
void Update()
{
float move = Input.GetAxisRaw("Horizontal");
anim.SetFloat("speed", Mathf.Abs(move));
rigid.velocity = new Vector2(move * 5f, rigid.velocity.y);
RaycastHit2D hit = Physics2D.Linecast(startpos.position, endpos.position, groundLayer.value);
if (hit.collider != null)
{
canJump = true;
anim.SetBool("jump", false);
}
if (Input.GetKeyDown(KeyCode.Space) && canJump == true)
{
anim.SetBool("jump", true);
rigid.velocity = Vector2.up * 10f;
canJump = false;
}
if (facingRight && move < 0)
{
facingRight = false;
transform.rotation = Quaternion.Euler(transform.rotation.x, 180, transform.rotation.z);
}
else if (!facingRight && move > 0)
{
facingRight = true;
transform.rotation = Quaternion.Euler(transform.rotation.x, 0, transform.rotation.z);
}
}
Place 'startpos' empty object on the center of the character and 'endpos' empty object on the bottom of the character.