PC gets stuck (vertically) on moving platform (Godot, 2D, collision)

I’ve been bashing my head against this issue for hours, time to ask for help…
I’m making a derivative of Hoppy Days, with moving platforms, some of which move up and down vertically.

The problem: when player character (PC) lands on a vertical motion platform while the platform is moving up, it gets vertically stuck and can’t jump off it. PC can, however, walk off it. When trying to jump, the audioplayer (implemented as in Hoppy Days) makes a buzzing sound, as if trying to play, then terminating.

Some constraints, and other potentially relevant info:

  • Player should move along with platform while idle on it. As of now, I’ve tried implementing this through platform collision detection, with a callback to the player, passing platform node as argument, and player updating its motion vector as platform velocity in every physics_process() call. I’ve commented this functionality out, and bug still there.

  • Player should be unaffected by player movement.

  • Platform needs collision detection. Platform locations and movement directions are procedurally (semi-randomly) generated, and reverse direction when colliding with anything except PC.

  • Level is implemented as an “arena”, totally enclosed by floors, walls, and ceiling. Game is intended for very young children - no losing lives or falling off the world. I have implemented arena walls as StaticBody2D.

What I’ve tried so far:

  • Implemented MovingPlatform as:
    – StaticBody2D, decorated with Area2D for collision detection. Exhibits jump bug as described.
    – Area2D. PC simply falls through - I can’t seem to make the PC move along with the platform.
    – KinematicBody2D, decorated with Area2D. Platforms stick to floor of arena. Can’t seem to get it to reverse direction.
    – RigidBody2D. Many undesired behaviors. Pretty sure this is not the way to go, as I don’t want the platform itself affected by the physics engine.

  • Some tweaks to player mechanics:
    – When trying to jump, move player slightly up first, then add velocity.
    – Setting PC collision safety margin low (0.01)

I saw a potentially related thread here on the forum, but I didn’t see a conclusive solution. Anybody have a clear solution to setting this up properly?

I will post a reply to this with code.

Player.gd code:

extends KinematicBody2D


const BASE_SPEED = 1000
const BOUNCE_SPEED = 3000
const DECEL = 0.75
const GRAVITY = 50
const UP = Vector2(0,-1)
const FRICTION = 0.5
const null_vector = Vector2(0,0)

var motion = Vector2(0,0)
var move_speed = Vector2(BASE_SPEED, BASE_SPEED)
var jump_speed = 3000
var coins = 0
var bounced = false
var accel_vector = Vector2(0,0)
var platform = null
var jumping = false # state variable used for PlayerAnimation

signal animate

func _ready():
	get_tree().call_group("HUD", "update_coins", coins)

func _physics_process(delta):
	apply_gravity()
	move()
	accel(delta)
	jump()
	if motion.y < -5000:
		motion.y = -5000
	move_and_slide(motion, UP)
	animate()


func apply_gravity():
	if not is_on_floor():
		motion.y += GRAVITY
	elif platform:  # player should move with platform
		motion.y = platform.velocity.y
		motion.x = platform.velocity.x
		accel_vector = null_vector
	elif motion.y > 0:
		motion.y = 0
		

func move():
	if Input.is_action_pressed("move_left") and not Input.is_action_pressed("move_right"):
		motion.x = -move_speed.x
	elif not Input.is_action_pressed("move_left") and Input.is_action_pressed("move_right"):
		motion.x = move_speed.x
	else:
		motion.x = 0
		

# Applies velocity impulse from bumper/spring hazards, and decays the impulse
func accel(delta):
	if bounced:
		bounced = false
		$PlayerSFX.stream = load("res://Player/jump1.ogg")
		$PlayerSFX.play()
		motion.x += accel_vector.x*BOUNCE_SPEED
		motion.y += accel_vector.y*BOUNCE_SPEED
	#decay the impulse
	if abs(accel_vector.x) < 0.05:
		accel_vector.x = 0
	else:
		accel_vector.x *= 1-(DECEL*delta)
	if abs(accel_vector.y) < 0.05:
		accel_vector.y = 0
	else:
		accel_vector.y *= 1-(DECEL*delta)


func jump():
	if (Input.is_action_pressed("jump") and (is_on_floor() or platform)):
		motion.y -= jump_speed
		jumping = true
		$PlayerSFX.stream = load("res://Player/jump1.ogg")
		$PlayerSFX.play()
	if is_on_ceiling():
		position.y += 1


func animate():
	emit_signal("animate", motion, jumping)


func collect():
	coins += 1
	get_tree().call_group("HUD", "update_HUD", coins)
	$PlayerSFX.stream = load("res://Player/coin_SFX.wav")
	$PlayerSFX.play()

# Callback from bumper/spring hazards, which can be oriented off-vertical.
func rebound(rebound_vector):
	bounced = true
	jumping = true
	accel_vector = rebound_vector
	

# Callback from MovingPlatform collision detection.
func collide_with_platform(plat):
	if plat.position.y > position.y:
		platform = plat
		

func leave_platform():
	platform = null

MovingPlatform.gd (in last-tried implementation as Area2D; code essentially the same with the other variants, with signal-processing function names changed appropriately)

extends Area2D


export var min_timer = 3
export var max_timer = 6
var velocity = Vector2(0,0)
var reverse = false
var animal
var track_timer = 0
var track_timeout = 0

func _ready():
	track_timeout = randi() % (max_timer-min_timer) + min_timer
	spawn_animal()

func init_velocity(new_v):
	velocity = new_v


func spawn_animal():	
	animal = load("res://Platforms/Animals/Animal.tscn").instance()
	add_child(animal)


func _physics_process(delta):
	check_tracking(delta)
	apply_velocity(delta)


func check_tracking(delta):
	track_timer += delta
	if track_timer == track_timeout:
		reverse_direction()
	if reverse:
		velocity.x *= -1
		velocity.y *= -1
		reverse = false


func apply_velocity(delta):
	position.x += velocity.x*delta
	position.y += velocity.y*delta


func reverse_direction():
	track_timer = 0
	reverse = true


func _on_MovingPlatform_body_entered(body):
	if body.name != "Player":
		reverse_direction()
	else:
		body.collide_with_platform(self)


func _on_MovingPlatform_body_exited(body):
	if body.name == "Player":
		body.leave_platform()


func _on_MovingPlatform_area_entered(area):
	reverse_direction()

Solved. Did two things:

  1. Made the MovingPlatform a StaticBody2D. Used code to move it around. Used StaticBody2D method “set_constant_linear_velocity(velocity)” to set its affecting velocity to whatever direction and speed the platform is actually moving in.

  2. When PC is on a platform moving up and is attempting to jump, first set its position.y a few pixels (I used 10) higher and then apply the jump velocity vector. The original 1 pixel was not cutting it. I think the issue is that the player’s collision shape was getting “stuck” in the platform’s collision shape (because of the latter moving up) - not sure why that is; Godot’s collision detection and move_and_slide() and is_on_floor() may be slightly bugged somehow. Anyways, with fix #1 above, I’m not sure this one was necessary, but I applied this one first and it seemed to work so I didn’t futz with it anymore. I still needed fix #1 to get the PC moving with the platform (at least horizontally).

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms