I'm building a simple "push the boxes" system, but something in my code isn't working as expected.

When include at least one box over another and try to push them all, sometimes it just don't move until I move that top box a little bit.

I tried to use RigidBody2d boxes, but it spins when touched... I need the boxes to be aligned to the ground. So this is all Kinematic2dBody.

Any help will be very appreciated!

---

The Player is basically the one from the Kinematic 2d platformer demo. Here's my GDS for the box:

extends KinematicBody2D

const PLAYER_SCALE = 2
const FLOOR_NORMAL = Vector2(0, -2)
const SLOPE_SLIDE_STOP = 25.0
const WALK_SPEED = 150 # pixels/sec

const TOP = 0
const BOTTOM = 1
const LEFT = 2
const RIGHT = 3

var linear_vel = Vector2()
var direction = 0
var on_floor = false
var can_play_sound = true

var top_area_bodies = []
var bottom_area_bodies = []
var left_area_bodies = []
var right_area_bodies = []

onready var box_sm = $box_sm
onready var anim = $anim
onready var player = global.get_player()

func _ready():
	reset()

func reset():
	global.boxes.append(self)

func _apply_gravity(delta):
	linear_vel.y += delta * global.GRAVITY

func _apply_movement(delta):
	var previous_direction = direction
	var desaccel = 0
	
	direction = 0
	
	for surface in [TOP, BOTTOM, LEFT, RIGHT]:
		check_surface(surface)
	
	if player.player_sm.is_on(player.player_sm.states.push): 
		if player.siding_left:
			for body in right_area_bodies:
				if global.is_player(body) and box_sm.is_on(box_sm.states.idle):
					if box_sm.is_on(box_sm.states.idle):
						direction = -1
					else:
						direction = -1.5
		elif !player.siding_left:
			for body in left_area_bodies:
				if global.is_player(body):
					if box_sm.is_on(box_sm.states.idle):
						direction = 1
					else:
						direction = 1.5
	
	if player.player_sm.is_on(player.player_sm.states.push) and direction == 0 and previous_direction != 0:
		direction = previous_direction
		if box_sm.is_on(box_sm.states.floating):
			play_sound(global.sound_splash)
		else:
			play_sound(global.sound_push)
	
	if linear_vel.x != 0:
		if linear_vel.x < 0:
			for body in left_area_bodies:
				if global.is_box(body):
					body.linear_vel.x = linear_vel.x
		elif linear_vel.x > 0:
			for body in right_area_bodies:
				if global.is_box(body):
					body.linear_vel.x = linear_vel.x
		
		for body in top_area_bodies:
			if linear_vel.x > 0 and linear_vel.x < body.linear_vel.x: return
			elif linear_vel.x < 0 and linear_vel.x > body.linear_vel.x: return
			body.linear_vel.x = linear_vel.x
	
	if box_sm.is_on(box_sm.states.idle):
		desaccel = 0.5
	else:
		desaccel = 0.05
	
	linear_vel = move_and_slide(linear_vel, FLOOR_NORMAL, SLOPE_SLIDE_STOP)
	linear_vel.x = lerp(linear_vel.x, direction * WALK_SPEED, desaccel)
	on_floor = is_on_floor()
	
func check_surface(area):
	var in_sewer = false
	var body = null
	
	match area:
		LEFT:
			left_area_bodies.clear()
			for body in $Area2D_left.get_overlapping_bodies():
				if body == self: continue
				if global.is_box(body) or global.is_player(body):
					left_area_bodies.append(body)
		RIGHT:
			right_area_bodies.clear()
			for body in $Area2D_right.get_overlapping_bodies():
				if body == self: continue
				if global.is_box(body) or global.is_player(body):
					right_area_bodies.append(body)
		TOP:
			top_area_bodies.clear()
			for body in $Area2D_top.get_overlapping_bodies():
				if body == self: continue
				if global.is_box(body):
					top_area_bodies.append(body)
		BOTTOM:
			bottom_area_bodies.clear()
			for body in $Area2D_bottom.get_overlapping_bodies():
				if body == self: continue
				if global.is_player(body):
					linear_vel.y = -WALK_SPEED
				if global.is_sewer(body):
					in_sewer = true
				if global.is_box(body):
					bottom_area_bodies.append(body)
			
			if box_sm.is_on(box_sm.states.idle):
				if in_sewer:
					box_sm.set_state(box_sm.states.floating)
			elif box_sm.is_on(box_sm.states.floating):
				if !in_sewer:
					box_sm.set_state(box_sm.states.idle)

func play_sound(stream, force=false):
	if not force and not can_play_sound: return
	
	can_play_sound = false
	$sound.stream = stream
	$sound.play()

func _on_Area2D_top_body_entered(body):
	if global.is_box(body):
		global.set_all_zindex()

func _on_sound_finished():
	can_play_sound = true

Is that code for the player or the boxes? I'm a little confused. I think only the player should be a kinematic body. The boxes should be rigid bodies, I think the physics engine should handle that (though I haven't tried yet).

@cybereality said: Is that code for the player or the boxes? I'm a little confused. I think only the player should be a kinematic body. The boxes should be rigid bodies, I think the physics engine should handle that (though I haven't tried yet).

Sorry, forgot to explain my approach. I'm using Kinematic for both player and box and this is the box script.

Player is basically the one from Kinematic 2d platformer demo. I'd love to use RigidBody2d but I don't want it to spin, my boxes need to stay sligned with ground, always. If there is a solution using RigidBody2d that don't spin when touched, it would be awesome!

Hmm, it seems there is no option to lock rotation of a RigidBody2D like there is for RigidBody3D nodes. You might be able to work around this though by adding something like the following to your RigidBody node:

func _physics_process(_delta):
	angular_velocity = Vector2.ZERO

I have not tried it myself, but I think that would cancel out any rotational velocity the box could have, which should make it no longer rotate.

@TwistedTwigleg said: Hmm, it seems there is no option to lock rotation of a RigidBody2D like there is for RigidBody3D nodes. You might be able to work around this though by adding something like the following to your RigidBody node:

func _physics_process(_delta):
	angular_velocity = Vector2.ZERO

I have not tried it myself, but I think that would cancel out any rotational velocity the box could have, which should make it no longer rotate.

I searched about that yesterday before posting and I found this nice tutorial and demo: http://kidscancode.org/godot_recipes/physics/kinematic_to_rigidbody/

But sadly your hint didn't work with "angular_velocity"... But I kept looking on this node properties and saw the "rotation degrees". Thought: "why not?" And worked!

Here's the solution. Player as KinematicBody2d, using the demo from the link above. Added a script to the RigidBody2d (Ball.tscn) and did:

extends RigidBody2D

func _physics_process(delta):
	self.rotation_degrees = 0

To avoid boxes to going up I added the code below that removes "negative gravity", but it didn't avoid some bouncing, because the CollisionShape2d still rotating some way... But at least it isn't show the sprite "angled".

if linear_velocity.y < 0: linear_velocity.y = 0

Some tips to get it even better?

That is great that you were able to find a solution. It is a little strange that RigidBody2D does not provide a way to lock rotation by default, especially since the RigidBody nodes does. I cannot think of any additional tips or ideas that might help, but if I think of any, I'll let you know!

3 years later