How to push another KinematicBody2D?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Zero

I’m trying to make a KinematicBody2D push another one, but move_and_slide doesn’t seem to work, the other kinematicbody doesn’t move at all.

What could I be doing wrong? Can anyone help? Thank you for reading.

:bust_in_silhouette: Reply From: SIsilicon

That is most likely because the second kinematic body does not have any collision response code. Depending on your application, their are two ways to handle the collision response.

  1. By changing the position directly.

  2. Have some sort of linear velocity variable and change that.

For both cases the second body will need to use either move_and_collide, or move_and_slide. Again depending on your application.

Thanks, I managed to make it push the other kinematic but it looks kinda jittery? Like they’re shaking a bit when they’re colliding/pushing. I created a scene with just two KinematicBody2Ds, one with the code, and a collisionshape2d for the both of them.
Is there any way to make it look less jittery?

Here’s my code for the KinematicBody2D:

extends KinematicBody2D

var velocity = Vector2()
var walk_speed = 200

func _ready():
	set_process(true)

func _process(delta):
	if Input.is_action_pressed("ui_right"):
		velocity.x = walk_speed
	elif Input.is_action_pressed("ui_left"):
		velocity.x = -walk_speed
	else:
		velocity.x = 0
	
	var collision = move_and_collide(velocity * delta)
	if collision:
        # To make the other kinematicbody2d move as well
		collision.collider.move_and_collide(velocity * delta)

Zero | 2018-05-04 22:19

Having the same exact problem. I’ve implemented a pushable KinematicBody2D object in a similar fashion, but the collision looks jittery. Have you found a better solution?

Gadzhi Kharkharov | 2018-07-30 22:36

In Godot 2.1.5, I “resolved” this problem by saving the colliding kinematicbody2d that was being pushed and then applying the same move parameter value to both the pushing and pushed bodies. There is still an initial jolt on the first collision but it then runs smoothly. See (untested) code below.

extends KinematicBody2D

var velocity = Vector2()
var walk_speed = 200
var pushed_body = null
var pushed_vel = null

func _ready():
    set_process(true)

func _process(delta):
    if Input.is_action_pressed("ui_right"):
        velocity.x = walk_speed
    elif Input.is_action_pressed("ui_left"):
        velocity.x = -walk_speed
    else:
        velocity.x = 0

    if pushed_body != null and pushed_vel == velocity:
        pushed_body.move(velocity*delta)
    elif pushed_body != null and pushed_vel != velocity:
        pushed_body = null
        pushed_vel = null

    var collision = move_and_collide(velocity * delta)
    if collision:
        # To make the other kinematicbody2d move as well
        collision.collider.move_and_collide(velocity * delta)
        pushed_body = collision.collider
        pushed_vel = velocity

joshpk105 | 2018-08-05 01:47

:bust_in_silhouette: Reply From: Anaxagor

Disclaimer, still a bit new to Godot, but this works for our game.

Something simple like this could do, if you have a velocity var implemented; in this case half the “energy” (speed) is “transferred” from the instigator to the collider.

To try it out, have two KinematicBody2D with collision shape and this script. Have one with an InitialSpeed so it moves towards the other.

extends KinematicBody2D

export (Vector2) var InitialSpeed = Vector2(0, 0)

onready var _speed = InitialSpeed

func _physics_process(delta):
    var collision = move_and_collide(velocity * delta)
    if collision:
        collision.collider.velocity = velocity.length() * 0.5 * -collision.normal
        velocity = velocity.bounce(collision.normal) * 0.5

Next, something more elaborate, if you want an actual push where the instigator follows through; in this case the instigator pushes the object at half its speed.

I fake the continuous collision (sticking) between instigator and collider since the actual collision will not be detected continuously even if we give the collider the same speed; FP error I suppose. Also note the unresolved issue of colliding with > 1 body that will move the instigator > 1 time.

To try it out, have two KinematicBody2D with collision shape and this script. Have one with an InitialSpeed so it moves towards the other with initial speed left to 0.

extends KinematicBody2D

const FLOOR_DIR = Vector2(0, 1)

export (Vector2) var InitialSpeed = Vector2(0, 0)

onready var _speed = InitialSpeed
var _appliedSpeed
var _pushedTimer = 0.0
var _pushedSpeed

func _physics_process(delta):
    if _pushedTimer > 0.0:
        if _pushedTimer < 0.1:
            move_and_slide(_pushedSpeed, FLOOR_DIR)
        _pushedTimer -= delta

    _appliedSpeed = _speed
    _speed = move_and_slide(_speed, FLOOR_DIR)

    var slideCount = get_slide_count()
    if slideCount > 0:
        var pushCollisions = []
        for i in range(slideCount):
            pushCollisions.append(get_slide_collision(i))

        # Process pushed colliders after looping through slide collisions
        # since OnPushed can make additional calls to move_and_slide
        for collision in pushCollisions:
            collision.collider.OnPushed(self, collision.remainder)

func OnPushed(instigator, remainder):
    _pushedTimer = 0.1
    _pushedSpeed = instigator._appliedSpeed * 0.5
    
    # Move out of the way of the player; the player did not move the full
    # value of its speed because of the collision handled here
    move_and_slide(_pushedSpeed, FLOOR_DIR)
    instigator.ReperformMoveAfterPush(remainder)
    #NOTE: should do only once per frame, in case there is more than one
    # object pushed

func ReperformMoveAfterPush(remainder):
    #NOTE: Not exactly what should be done, this will move the player
    # more than required but will *probably* re-collide with the pushable
    # anyway.
    # We say *probably* since there is the *slide* part of move_and_slide
    # that might have change the position from where the same speed will
    # not result in another/the same collision.
    move_and_slide(remainder, FLOOR_DIR)
    _speed = _appliedSpeed  # Keep the old speed, as if no collision occurred