Raycast Ground Check Fails Intermittently (Only on Left Side of Nearby Object)

Hi everyone,

I’m working on a first-person controller and having a strange issue with my jump mechanic, which uses a raycast downwards to check if the player is grounded.

The Problem: Jumping (using the spacebar) works perfectly fine in most areas of my test scene. However, specifically when I stand near the left side of my main vehicle entity (“Aeroboticar”), the jump fails. If I move to the right side of the same vehicle, jumping works reliably.

Setup:

  • My Player entity has a Dynamic Rigidbody and a Collision component.
  • Movement and jumping are handled by a script called firstPersonMovement.js.
  • Inside this script, the update function calls a checkGround() function every frame.
  • checkGround() performs a raycastFirst downwards from near the player’s origin (getPosition().y + raycastStartOffset) for a short distance (groundCheckDistance + 0.1).
  • If the raycast hits a valid entity, a this.canJump flag is set to true (provided a jump cooldown isn’t active).
  • Pressing spacebar applies an upward impulse only if this.canJump is true.
  • My Ground entity has a Static Rigidbody and a Collision component, and its Rigidbody group is set to Default.

Debugging Done:

  • Console logs confirm that when standing on the right side of the Aeroboticar, the checkGround raycast correctly logs Hit: Ground | Setting canJump = true.
  • When standing on the left side of the Aeroboticar, the checkGround raycast logs Hit: None / Filtered | Setting canJump = false, even though the player visually appears to be on the same ground surface.
  • This happens consistently only on the left side relative to the vehicle.

My Hypothesis: It seems the raycast is somehow missing the ground collider, or perhaps hitting an invisible part of the Aeroboticar’s collider only when the player is on its left side. If it hits the Aeroboticar, the filter should ignore it, resulting in Hit: None. I’ve checked the Aeroboticar’s colliders and can’t see anything obvious that would only block the ray on one side.

Project Link: Here is a direct link to the editor: PlayCanvas | HTML5 Game Engine (You can test by launching, walking up to the Aeroboticar, and trying to jump on both its left and right sides).

Relevant Code (checkGround function from firstPersonMovement.js):

FirstPersonMovement.prototype.checkGround = function() {
    var groundCheckStart = this.entity.getPosition().clone();
    // OPTIONAL: Adjust start Y if player origin is centered: groundCheckStart.y -= 0.5;
    var checkDistance = this.groundCheckDistance + 0.1; // Ray distance including buffer
    var groundCheckEnd = groundCheckStart.clone().add(pc.Vec3.DOWN.clone().scale(checkDistance));
    var result = this.app.systems.rigidbody.raycastFirst(groundCheckStart, groundCheckEnd);

    // Set onGround based *only* on whether the ray hit anything
    var onGround = (result !== null);

    var previouslyCanJump = this.canJump;
    if (onGround && this.jumpTimer <= 0) {
        this.canJump = true;
        if (!previouslyCanJump) {
            console.log(`Ground Check - Hit: ${(result ? result.entity.name : 'ERROR')} | Timer <= 0 | Setting canJump = true`);
        }
    } else {
        this.canJump = false;
         if (previouslyCanJump && !onGround) {
             console.log(`Ground Check - Hit: None | Setting canJump = false`);
         }
    }
    // Optional detailed log:
    // console.log(`Ground Check Ray - Start:(${groundCheckStart.x.toFixed(1)},${groundCheckStart.y.toFixed(1)},${groundCheckStart.z.toFixed(1)}) End:(${groundCheckEnd.x.toFixed(1)},${groundCheckEnd.y.toFixed(1)},${groundCheckEnd.z.toFixed(1)}) | Hit: ${(result ? result.entity.name : 'None')} | Timer: ${this.jumpTimer.toFixed(2)} | Can Jump: ${this.canJump}`);
};


// Jump Logic
    if (app.keyboard.wasPressed(pc.KEY_SPACE)) {
        // console.log("Spacebar pressed. Current Can Jump flag:", this.canJump);
        if (this.canJump) {
            var jumpImpulse = pc.Vec3.UP.clone().scale(this.jumpPower);
            this.entity.rigidbody.applyImpulse(jumpImpulse);
            // console.log("Applying Jump Impulse:", jumpImpulse.toString());
            this.canJump = false; // Prevent immediate re-jump
            this.jumpTimer = this.jumpCooldown; // Start the cooldown timer
            console.log("Jumped! Starting cooldown.");
        } else {
            if(this.jumpTimer > 0) { console.log("Spacebar pressed but jump cooldown active."); }
            else { console.log("Spacebar pressed but not on ground (canJump=false)."); }
        }
    }

Could anyone take a look or suggest why the raycast might be failing specifically on the left side of the Aeroboticar? Any ideas would be greatly appreciated!

Thanks!

Hi @Peter_Roman and welcome!

I did a test, and I couldn’t jump from the start. Getting closer to the car made it work. Your onGround logic seems a bit overcomplicated to me in the actual project. I don’t see the same script as the one you shared here.

To debug, you should log all variables when the player presses the spacebar to see which one is failing. If onGround is failing, check your raycast and filters.

My advice is to start simple and add more features or filters later.

It started off simple but I had to implement the filters and raycast because I was getting bugs. Now it works but only on one side of the road. The ground entity is set up properly so I don’t get what’s going on lol

It is probably because of the precision issue. Your road collider is 2000 m, which is too large. Try a smaller collider, like 100m or less. If needed, use multiple smaller ones, instead of one large. Avoid too large colliders in general, Ammo can’t optimize those well.