Using obj.rayCast() to see if player is on ground

Alright, so the title mostly explains the problem. But let me explain what I’ve done to try and solve this, and what more I want.

Using Python I want to cast a ray along the Z-axis to detect a property named ‘ground’ below the player. If it’s above the player, they can pass right through it. I know I can set the player to rest upon a plane with the ‘ground’ property without falling by using worldLinearVelocity.z = 0 and then set the max velocity when they’re falling. So that’s not a problem.

The problem comes when I’m trying to get the game to recognize the rayCast in the first place. After reading through all the documentation for every version of 2.7, using Wayback Machine to access TutoritalsForBlender3D.com, and searching through both here and StackExchange, I still can’t figure out how to make rayCast cast along that single axis in a single direction. The code I tried detects the ground object regardless of orientation, and doesn’t deactivate when it’s no longer near the planes with the ‘ground’ property. It’s as follows:

blah blah blah import BGE and list the scene objects whatever, we all know this

rayToGround = playerA.rayCast((0.0, 0.0, 0.0), None, -0.01, 'ground', 0, 0, 0)

if rayToGround is not None:
    own['isOnGroundYesEdBoy'] = True
    print("The player's head ain't in the clouds for once")

else:
    own['isOnGroundYesEdBoy'] = False
    print("Hope that fall doesn't break their legs...")

And even if the player character is in the middle of the air it still prints only that first statement and keeps the boolean property true. All because it’s detecting across all axes and then not deactivating when it’s nowhere near what it’s supposed to detect. Driving me nuts.

This means that you fire a ray from playerA.worldPosition to (0, 0, 0), always.
You are not casting a ray a few units down, you always draw a ray to the scene center.

rayTarget = playerA.worldPosition.copy()
rayTarget.z = 0 # if you want the ray to go all the way to the ground, below playerA
rayToGround = playerA.rayCast(rayTarget, None, 0, 'ground', 0, 0, 0)

Regarding the Python API, you can use the following link:
https://shuvit.org/python_api/index.html#game-engine-modules
Hosted by a member of the community :slight_smile:

end = own.worldTransform*Vector([x,y,z])

is handy for casting to any point, relative to a actor

end = own.worldPosition + (own.worldOrientation.col[0] *distance)
#raycast out forward relative to player X+

#if your doing something like a sphere planet
end = own.worldPosition +(own.worldOrientation.col[2] * -distance)
#end point is below player, no matter what angle they face

big thing you missed:

rayCast() returns a triple; object, point, normal, so itll never be None. but the object value is commonly checked.

  • obj, pnt, nrm = owner.rayCast()

rayCastTo() is a simpler version that only returns one object.

  • obj = owner.rayCastTo()

easy coords

rayfrom = owner.worldPosition # actually, this can be None and will assume origin
rayto = owner.worldPosition+owner.getAxisVect([0,0,-1])

or you can always cast in direction of gravity
rayto = owner.worldPosition+scene.gravity

yeah if raycast misses, it returns None

Ah, I see. Seems odd that you would never get a None state if it wasn’t active. I also tried

rayToGround = playerA.rayCastTo((0.0, 0.0, -1.0), 0.01, 'ground')

if rayToGround == ACTIVE:
    own['isOnGroundYesEdBoy'] = True
    print("The player's head ain't in the clouds for once")

else:
    own['isOnGroundEdBoy'] = False
    print("Hope that fall doesn't break their legs...")

But now the state just stays False and won’t change.

I did it like so for the longest time,

from mathutils import Vector
def lyebRay(origin, target, x_off=0.0, y_off=0.0, z_off=0.0,
            propName="",bit_mask=0xFFFF, offset_origin=False, maxdist=0):

    pos = target
    _pos = Vector(origin.worldPosition)
    if isinstance(target, bge.types.KX_GameObject):
        pos = Vector(target.worldPosition)
    
    #add offset to ray target coordinates
    ray_target = [pos[0]+x_off, pos[1]+y_off, pos[2]+z_off]
    ray_origin = _pos
    
    #add offset to ray origin coordinates when flag == True
    if offset_origin:
        ray_origin = [_pos[0]+x_off, _pos[1]+y_off, _pos[2]+z_off]

    #return tuple (hitObject, hitPosition, hitNormal) if hit, else False
    ray = origin.rayCast(ray_target, ray_origin, dist=maxdist, prop=propName, mask=bit_mask)
    return False if ray[0] == None else ray

def other_func():
    isOnGround = lyebRay(player, player, z_off= -0.5, propName="ground")
    if isOnGround:
        print("You're grounded!")

In case your problem still exists: I remember comparing with “is” is something like identity check in Python, similar to comparing if two objects have the same address in c/c++ Terms. You are doing this in the suspiciously always entered if branch as far as I understood. Maybe you can investigate in that direction. See this.

as @Daedalus_MDW said it returns more then just active state, infact it does not even send the active state, so this (should) works:

rayToGround = playerA.rayCastTo((0.0, 0.0, -1.0), 0.6, 'ground')

if rayToGround[0]:
    own['isOnGroundYesEdBoy'] = True
    print("The player's head ain't in the clouds for once")

else:
    own['isOnGroundEdBoy'] = False
    print("Hope that fall doesn't break their legs...")

[0] to check if you hit an object with the ray
changed distance to 0.6 you need to calculate distance from the origin, so cube at 1M height origin at center = 0.5 meter/BU + a small offset 0.1 makes 0.6

Ah, I see. It’s checking for property, face, and X-ray and without explicitly stating it face and X-ray will confuse it.Thank you.

just cast a ray, don’t look for property just add a check for it, so you can use that same ray for a lot more functions then only a ground check (saves performance)

rayToGround = playerA.rayCastTo((0.0, 0.0, -1.0), 0.01)

if rayToGround[0]:# hit an object
    
    hit_obj = rayToGround[0]
    
    if 'ground' in hit_obj: # if property ground is in it
        own['isOnGroundYesEdBoy'] = True
        print("The player's head ain't in the clouds for once")

else:
    own['isOnGroundEdBoy'] = False
    print("Hope that fall doesn't break their legs...")

you can even leave ground out of it, because you need to add a property to any object you are jumping onto.

Right, so trying

if rayToGround[0]:

Is just giving me the “TypeError: ‘NoneType’ object is not subscriptable” message. Even though it should work.

Because according to the documentation, rayCastTo returns a GameObject or None.
https://shuvit.org/python_api/bge.types.KX_GameObject.html#KX_GameObject.rayCastTo

Did my first answer not work?

It works in the same way my original code works. In that it won’t turn off, and the on ground property stays true regardless of whether playerA has jumped and/or fallen or not. Daedalus’ answer produces the same result.

here this works:

    vec = playerA.worldPosition - playerA.worldOrientation.col[2]
    ray = playerA.rayCast(vec, playerA, 5.0) 

    if ray[0]:
        
        hit_obj = ray[0]
        
        print('hit object:', hit_obj)
        
        if 'ground' in hit_obj:
            print('ray hitted the floor')

Ok, so to be fair I did not really understand how you wanted to fire your ray.

But if you just want it to go down:

import time

ray = playerA.getAxisVect((0, 0, -1))
rayTarget = playerA.worldPosition + ray
ground, *rest = playerA.rayCast(rayTarget, None, ray.magnitude, 'ground', 0, 0, 0)

if ground:
    print('hit ground at', time.time())

Now I don’t understand what you meany by “turn off”, but here depending on the size of the ray, it should stop triggering once far enough from the ground.