KinematicBody has inconsistent collisions

EthernautEthernaut Posts: 2Member
edited August 29 in 3D

Hi!
New user here. Before I report this as a bug, I wanted to double-check if I'm not doing anything wrong.

I am using a KinematicBody as a "Character controller", and need to check the collision with the floor to determine the floor "type" (water, wood, etc.) and adjust the character's animation/sounds accordingly.

I want to avoid additional ray casts, since I'm already colliding with the floor using the kinematic body anyway - all the information I need should already be in the collision info! Also, whichever method I decide on will be used on dozens of kinematic bodies at the same time, so it needs to be as fast as possible. Reusing existing collision data seems like a good plan.

The problem is that I can't get consistent collisions. Sometimes it gets reported, sometimes it doesn't. I also get very different results between using move_and_slide() and move_and_slide_with_snap() - which is odd to me, since the only difference I expected between those two methods would be in the movement.

I made a very minimal sample project that demonstrates the issue.

The ball should always match the floor color, unless it's in the air, when it should be blue.
I've attached the project (it's tiny).

Here is also the test code in the kinematic body, in case you don't want to download the project:

extends KinematicBody

export var speed = 10.0
export var jumpSpeed = 20.0
export var gravity = Vector3(0.0, -2.0, 0.0)
export var groundSnap = Vector3.DOWN * 0.1

var vel = Vector3()

onready var material = get_node("MeshInstance").get_surface_material(0)

func _process(delta):
    var dir = Vector3.ZERO

    if Input.is_action_pressed( "left" ):
        dir.x = -speed
    elif Input.is_action_pressed( "right" ):
        dir.x = speed

    if Input.is_action_pressed( "up" ):
        dir.z = -speed
    elif Input.is_action_pressed( "down" ):
        dir.z = speed

    if is_on_floor():
        vel= Vector3( dir.x, 0, dir.z )
        if Input.is_action_just_pressed( "jump" ):
            vel.y = jumpSpeed
    else:
        vel.y += gravity.y
        vel.x = dir.x
        vel.z = dir.z

    if is_on_ceiling():
        vel.y = 0


func _physics_process(delta):

    #This doesn't work - object color stays blur most of the time,
    #implying no collisions happened

    var col = move_and_slide_with_snap( vel, groundSnap, Vector3.UP )

    #This reports collisions better (ball is never blue when touching surfaces),
    #but doesn't jump (is_on_floor() doesn't work)

#   var col = move_and_slide( vel )

    if col:
        var count = get_slide_count()
        if count > 0:
            for n in range(count):
                var hit = get_slide_collision(n)
                var otherMesh = hit.collider.get_node("MeshInstance")
                var otherMaterial = otherMesh.get_surface_material(0)
                material.albedo_color = otherMaterial.albedo_color
        else:
            material.albedo_color = Color.blue

Thanks!


Tags :

Best Answer

  • EthernautEthernaut Posts: 2
    edited August 30 Accepted Answer

    I think I figured it out.
    I wasn't applying gravity when the body was on the floor, resulting in no more collisions detected.

    Still couldn't fix slippery collisions with moving platforms, are KinematicBodies going to receive the same "Sync to Physics" option that their 2D counterparts have?

    Here's the updated code, with some other improvements:

    extends KinematicBody
    
    export var speed = 15.0
    export var jumpSpeed = 35.0
    export var airControl = 0.667
    export var gravity = Vector3(0.0, -2.0, 0.0)
    export var groundSnap = 0.5
    export var ceilingPush = 1.0
    
    var vel = Vector3()
    var snap = Vector3()
    var groundVelocity = Vector3()
    var colliderVelocity = Vector3()
    
    onready var material = get_node("MeshInstance").get_surface_material(0)
    
    func _process(delta):
        var dir = Vector3.ZERO
    
        if Input.is_action_pressed( "left" ):
            dir.x = -speed
        elif Input.is_action_pressed( "right" ):
            dir.x = speed
    
        if Input.is_action_pressed( "up" ):
            dir.z = -speed
        elif Input.is_action_pressed( "down" ):
            dir.z = speed
    
        vel.y += gravity.y
    
        if is_on_floor():
            snap.y = -groundSnap
            vel= Vector3( dir.x, snap.y, dir.z )
    
            groundVelocity = vel + colliderVelocity
            if Input.is_action_just_pressed( "jump" ):
                vel.y = jumpSpeed
                snap = Vector3.ZERO
        else:
            #Balances inertia with air control
            vel.x = mix(groundVelocity.x, dir.x, airControl )
            vel.z = mix(groundVelocity.z, dir.z, airControl )
    
        if is_on_ceiling():
            vel.y = -ceilingPush
    
        #hack to ensure ground contact on vertical platforms
        if colliderVelocity.y > 0:
            vel.y -= colliderVelocity.y
    
    func _physics_process(delta):
        #Blue if no ground collisions
        material.albedo_color = Color.blue
        colliderVelocity = Vector3.ZERO
    
        var col = move_and_slide_with_snap( vel, snap, Vector3.UP )
    
        if col:
            var count = get_slide_count()
            if count > 0:
                for n in range(count):
                    var hit = get_slide_collision(n)
                    if abs(hit.collider_velocity.length()) > abs(colliderVelocity.length()):
                        colliderVelocity = hit.collider_velocity
                    if hit.normal.y>0.25:
                        #Ground collision, copy color from ground
                        var otherMesh = hit.collider.get_node("MeshInstance")
                        var otherMaterial = otherMesh.get_surface_material(0)
                        material.albedo_color = otherMaterial.albedo_color
    
    func mix(a:float, b:float, amount:float):
        return (b*amount)+(a*(1.0-amount))
    

Answers

  • EthernautEthernaut Posts: 2Member
    edited August 30 Accepted Answer

    I think I figured it out.
    I wasn't applying gravity when the body was on the floor, resulting in no more collisions detected.

    Still couldn't fix slippery collisions with moving platforms, are KinematicBodies going to receive the same "Sync to Physics" option that their 2D counterparts have?

    Here's the updated code, with some other improvements:

    extends KinematicBody
    
    export var speed = 15.0
    export var jumpSpeed = 35.0
    export var airControl = 0.667
    export var gravity = Vector3(0.0, -2.0, 0.0)
    export var groundSnap = 0.5
    export var ceilingPush = 1.0
    
    var vel = Vector3()
    var snap = Vector3()
    var groundVelocity = Vector3()
    var colliderVelocity = Vector3()
    
    onready var material = get_node("MeshInstance").get_surface_material(0)
    
    func _process(delta):
        var dir = Vector3.ZERO
    
        if Input.is_action_pressed( "left" ):
            dir.x = -speed
        elif Input.is_action_pressed( "right" ):
            dir.x = speed
    
        if Input.is_action_pressed( "up" ):
            dir.z = -speed
        elif Input.is_action_pressed( "down" ):
            dir.z = speed
    
        vel.y += gravity.y
    
        if is_on_floor():
            snap.y = -groundSnap
            vel= Vector3( dir.x, snap.y, dir.z )
    
            groundVelocity = vel + colliderVelocity
            if Input.is_action_just_pressed( "jump" ):
                vel.y = jumpSpeed
                snap = Vector3.ZERO
        else:
            #Balances inertia with air control
            vel.x = mix(groundVelocity.x, dir.x, airControl )
            vel.z = mix(groundVelocity.z, dir.z, airControl )
    
        if is_on_ceiling():
            vel.y = -ceilingPush
    
        #hack to ensure ground contact on vertical platforms
        if colliderVelocity.y > 0:
            vel.y -= colliderVelocity.y
    
    func _physics_process(delta):
        #Blue if no ground collisions
        material.albedo_color = Color.blue
        colliderVelocity = Vector3.ZERO
    
        var col = move_and_slide_with_snap( vel, snap, Vector3.UP )
    
        if col:
            var count = get_slide_count()
            if count > 0:
                for n in range(count):
                    var hit = get_slide_collision(n)
                    if abs(hit.collider_velocity.length()) > abs(colliderVelocity.length()):
                        colliderVelocity = hit.collider_velocity
                    if hit.normal.y>0.25:
                        #Ground collision, copy color from ground
                        var otherMesh = hit.collider.get_node("MeshInstance")
                        var otherMaterial = otherMesh.get_surface_material(0)
                        material.albedo_color = otherMaterial.albedo_color
    
    func mix(a:float, b:float, amount:float):
        return (b*amount)+(a*(1.0-amount))
    

Leave a Comment

BoldItalicStrikethroughOrdered listUnordered list
Emoji
Image
Align leftAlign centerAlign rightToggle HTML viewToggle full pageToggle lights
Drop image/file