Making the Player Climb Ropes and Walls

This game development tutorial follows my Mario-like platformer tutorials. This recipe covers adding ladder climbing to a platformer, scaling walls and making a climbable rope in a side scroller.

This is relatively a simple mechanic to add if you have done making a platformer player.

Assets & Project Link

All assets are given credit in a.txt files in every folder. This is the project link.

Climbing System Breakdown

Our rope is just an Area2D. We will use its signals body_entered and body_exited to detect if player has entered the vicinity of the rope. Whenever a player is nearby, the rope body_entered signal will be triggered and rope is now in active state and ready to be grabbed. And when body_exited signal is triggered, it means player is no longer touching the rope, so make the rope inactive again.

If player presses the climb action, the script will loop over all the ropes and see which one is active. If it found an active rope (meaning the rope to which player is colliding), it will trigger the grab function on player.

The grab function will play the ‘scaling/climbing-animation’ in AnimationPlayer2D node, it will also restrict all other movements of player (such as walk, run, jump, etc). And will only allow player to move up or down (and never left or right).

When player presses the un-climb action (toggles the climb button again), player will go back to normal state and can now normally walk, run, jump and so on.

platformer game tutorial, climbing the ladder

Make a Climbable Object (Rope)

We start by making a scene for rope, ladder, chain, vines (or basically anything that is climbable). Create a scene and call it Rope or Climbable or whatever you wish. Make sure that the root node is Area2D and it has a collision shape node as a child of root. This is how the scene looks like:

making climbable rope or ladder in godot

Now attach a script and connect the signals body_entered and body_exited to the script functions. We will use these signals to make the rope active or inactive based on player’s vicinity.

Rope.gd:

extends Area2D


var is_player_touching_the_rope: bool = false


func _ready():
	add_to_group("ropes")



func _on_body_entered(body):
	if body in get_tree().get_nodes_in_group("players"):
		is_player_touching_the_rope = true


func _on_body_exited(body):
	if body in get_tree().get_nodes_in_group("players"):
		is_player_touching_the_rope = false

The rope being active means that is_player_touching_the_rope is true. Rope being inactive means that is_player_touching_the_rope is false. So when player is touching the rope, only then player can actually call grab() call on it, else it will ignore the rope.

Use the Rope System to Climb

Adding rope to the level

Before using it, make sure to instance the rope scene in the level. I instanced it and placed it on tree trunk, so the trunk becomes climbable. Like this:

climbing rope in platformer game

I also have the same character that we made in the earlier tutorial and added climbing on top of its existing mechanics. You can also make your own player, it is totally your choice.

Making the player use the rope

In Player’s script, when player presses the climbing action, trigger climbing mechanics:

Player.gd:

func _process(delta):
  
  # ... REST OF THE CODE ...
  
	# Trigger climbing mechanics
	if Input.is_action_just_pressed("ui_end"): # I used END key here
		# If already climbing, make it leave climbing (unclimb)
		if is_climbing:
			ungrab_rope()
			return
		# Else check for nearby rope and grab it (start climbing)
		if not is_climbing:
			grab_rope()
  
  # ... REST OF THE CODE ...
  
  

# Grabs the rope object whose area are we overlapping with
func grab_rope():
	for rope in get_tree().get_nodes_in_group("ropes"):
		if rope.is_player_touching_the_rope:
			is_climbing = true
			position.x = rope.position.x


# Player no longer climbing
func ungrab_rope():
	is_climbing = false

In above code, we have successfully toggled climb/unclimb. But it has no effect. This is because we did not use is_climbing after making it true.

So in _process(delta) function, we will check if player is climbing, then do all sort of climbing things such as playing the scaling/climb animation and moving up and down based on input. In Player.gd _process(delta) function:

	
	# !!! NOTE: ANIMATION IS TOTALLY OPTIONAL, AND YOU CAN REMOVE
	# !!! ANIMATION PART IF YOU DONT HAVE AnimatedSprite2D NODE OR
	# !!! CLIMB ANIMATION
	
	
	func _process(delta):
	
	  
  # ... REST OF THE CODE ...
  
	
	# If climbing, move up/down and play animation
	if is_climbing:
		velocity = Vector2(0, 0) # Reset velocity
		$AnimatedSprite2D.animation = "climb"
		if Input.is_action_pressed("ui_up"):
			$AnimatedSprite2D.play("climb")
			velocity.y -= 8000 * delta # Main part
		elif Input.is_action_pressed("ui_down"):
			$AnimatedSprite2D.play("climb")
			velocity.y += 8000 * delta # Main part
		else:
			$AnimatedSprite2D.stop()

  
  # ... REST OF THE CODE ...
  

Problems with this Ladder Scaling System

While scaling the ladder/rope, make sure that jumping, walking, running and other normal things are disabled. I used a hacky way of adding an if-statement to check if we are is_climbing: then don’t jump. But a better designed game must have a system of state-machines that defines the constraints of when certain actions can or cannot be done.

But my hacky solution looks like this though (not recommended):


func _physics_process(delta):
	var dir = Input.get_axis("ui_left", "ui_right")
	
	if not is_climbing: # ***SEE THIS***
		velocity.y += gravity * delta # Apply gravity
		
		if dir != 0: # Movement
			if is_on_floor():
				velocity.x = lerp(velocity.x, dir * speed, acceleration)
			else:
				velocity.x = lerp(velocity.x, dir * speed * 1.4, acceleration)
			if dir > 0:
				$AnimatedSprite2D.flip_h = false
			else:
				$AnimatedSprite2D.flip_h = true
			if not is_attacking:
				$AnimatedSprite2D.play("run")
		else:
			velocity.x = lerp(velocity.x, 0.0, friction)
			if not is_attacking:
				$AnimatedSprite2D.play("idle")

	# Double Jump
	if not is_climbing: # ***SEE THIS***
		if Input.is_action_just_pressed("ui_up") and (is_on_floor() or jumps_used < jumps_count - 1):
			velocity.y = jump_speed
			jumps_used += 1
			is_jumping = true

	# Animation and Jumping State
	if not is_climbing: # ***SEE THIS***
		if is_jumping:
			if is_on_floor():
				jumps_used = 0
				is_jumping = false
			if not is_attacking:
				$AnimatedSprite2D.play("fall")

More Things

These related 2D platformer game tutorials discuss other common things needed for 2D platformer games.

Thank you for reading it <3

Leave a Reply

Your email address will not be published. Required fields are marked *