2
votes

Overview

Using Unity2D 2019.3.5, I am making a platformer game using C#. I implemented raycast to detect when my player is touching the ground and attempted to make it only so the player can jump only once.

Problem

Although I thought I programmed my character to jump once, after the first jump, the Unity engine still shows a checkmark to my "isGrounded" variable and only turns to false (unchecked) after a second jump before hitting the ground.

My Code

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

public class Player_Controller : MonoBehaviour
{

    public int playerSpeed = 10;
    public int playerJumpPower = 1250;
    private float moveX;
    public bool isGrounded;
    public float distanceToBottomOfPlayer = .7f;

    // Update is called once per frame
    void Update()
    {
        PlayerMove();
        PlayerRaycast();
    }

    void PlayerMove()
    {
        // CONTROLS
        moveX = Input.GetAxis("Horizontal");
        if (Input.GetButtonDown("Jump") && isGrounded == true)
        {
            Jump();
        }

        // ANIMATIONS

        // PLAYER DIRECTION
        if (moveX < 0.0f)
        {
            GetComponent<SpriteRenderer>().flipX = true;
        }
        else if (moveX > 0.0f)
        {
            GetComponent<SpriteRenderer>().flipX = false;
        }

        // PHYSICS
        gameObject.GetComponent<Rigidbody2D>().velocity = new Vector2(moveX * playerSpeed, 
gameObject.GetComponent<Rigidbody2D>().velocity.y);
    }

    void Jump()
    {
        GetComponent<Rigidbody2D>().AddForce(Vector2.up * playerJumpPower);
        isGrounded = false;
    }

    void PlayerRaycast()
    {
        // Ray Down
        RaycastHit2D rayDown = Physics2D.Raycast(transform.position, Vector2.down);

        if (rayDown.collider != null && rayDown.distance < distanceToBottomOfPlayer && 
rayDown.collider.tag == "ground")
        {
            isGrounded = true;
        }
    }
}

Extra Info

I did have to change a Unity setting in Edit > Project Settings > Physics 2D > Queries Start In Colliders. I had to turn this setting off (uncheck) in order to get my player to jump using the code I wrote above. I know there are other ways of making my player jump, however, this seemed to be the most efficient while maintaining the readability of the code.

Solutions Tried

What I believe the problem is that I have a raycast issue that I don't know how to fix. I looked at other Stack Overflow posts including the ones recommended after writing this post, but none of them applied to my problem.

Final Notes

As I said before, I know there are other ways to make my player jump only once using different code, however, I would like to stick with this code for my own learning purposes and for future reference.

2
I enjoy looking at questions like this, seldom am I qualified to answer them. I would like to say that looking at this as an outsider, the expression rayDown.distance < distanceToBottomOfPlayer seems prone to issues, especially since we're dealing with floats. Also, it seems to me that the state of isGrounded should always be based on a "physical" condition. Upvote for being interesting and well constructed. - rfmodulator

2 Answers

1
votes

Because you can't be sure that isGrounded is false when you call Jump().
I think issue lies there.
Try not setting isGrounded flag when calling Jump(). Setting isGrounded is purely PlayerRaycast()'s job.

    void Update()
    {
        // Raycast before moving
        PlayerRaycast();
        PlayerMove();
    }

     void PlayerRaycast()
    {
        // Ray Down
        RaycastHit2D rayDown = Physics2D.Raycast(transform.position, Vector2.down);

        if (rayDown.collider != null && rayDown.collider.tag == "ground")
        {
            if( rayDown.distance < distanceToBottomOfPlayer )
            {
                isGrounded = true;
            }
            else
            {
                isGrounded = false;
            }
        }
    }

    void Jump()
    {
        GetComponent<Rigidbody2D>().AddForce(Vector2.up * playerJumpPower);
        //isGrounded = false;
    }
1
votes

The first problem is that you add the force and check for the ground at the same frame.

Applied Force is calculated in FixedUpdate or by explicitly calling the Physics.Simulate method.

So after you press the "Jump" button, the object is still on the ground until the next frame comes.

To fix this, you can simply exchange the order of "move" and "raycast"

void Update()
{
    PlayerRaycast();
    PlayerMove();
}

The second problem is if the jump power is not large enough, the object can still be close to the ground in the next frame, you should avoid checking the landing when the jump is in ascending state.

void PlayerRaycast()
{
    if(GetComponent<Rigidbody2D>().velocity.y > 0)
        return;
    ...
}