How can I create a wall for the player to jump against from an Area2D node or something else that does not block it?

Godot Version:
4.2.2 stable

Question:

Hello! It might take me a while to understand if someone tries to help me but please bear with me. I will try to explain exactly what I want and how I think it should be implemented (even though I am not managing to do it myself).

Here is a video of the current setup I have in Godot (left) and what I would like it to become (right):

Basically, in Ninja Gaiden, the player can walk past walls that they can otherwise climb against if they are in mid-air. Also, they only climb to a wall if they jump toward it and not from behind. Right now, because I am using simple CollisionShape2D as walls, my player cannot go through them and climb to it on only the side I want (which would be the right side on the video for example). I hope it makes sense so far? The video should help.

I think I need to replace my CollisionShape2D by first using an Area2D so that the player does not collide with it. Then if my raycast attached to the player enters in the Area2D: they climb to it? Something like that… I tried for hours but couldn’t even get the player to climb to an area 2D (which has a CollisionShape2D attached to it). I put the signals but I am not even sure it did anything. I watched multiple videos about Area2D but it didn’t help.
I know it’s not clear at all, sorry. I hope the video helps to understand what I would like.

My current player script:

extends CharacterBody2D

@onready var sprite = $AnimatedSprite2D
@onready var wall_checker = $WallChecker

const SPEED = 100.0
const JUMP_VELOCITY = -310.0
var last_direction = 1
var direction = 1
var prev_velocity = Vector2.ZERO
var gravity: int = 900
var is_sticking = false
var is_crouching: bool = false
var is_attacking: bool
var is_wall_jumping: bool = false

func _ready():
	is_attacking = false

func _physics_process(delta):
	print(is_near_wall())
	
	if direction != 0:
		last_direction = direction
	
	# ADD THE GRAVITY.
	if not is_on_floor():
		velocity.y += gravity * delta
		prev_velocity = velocity

	# HANDLE JUMP.
	if Input.is_action_just_pressed("jump") and is_on_floor() and not is_attacking:
		velocity.y = JUMP_VELOCITY
		is_wall_jumping = false

	# HANDLE CROUCH.
	if not is_attacking:
		if Input.is_action_pressed("crouch") and is_on_floor():
			is_crouching = true
		else:
			is_crouching = false

	# Get the input direction and handle the movement/deceleration.
	var direction = Input.get_axis("move_left", "move_right")
	
	if is_attacking and is_on_floor():
		velocity.x = 0  # Stop movement during attack
	else:
		if direction:
			velocity.x = direction * SPEED
		else:
			velocity.x = move_toward(velocity.x, 0, SPEED)

	if not is_on_floor() and not is_wall_jumping:
		velocity.x = lerp(prev_velocity.x, velocity.x, 0.090 / 2)

	if not is_attacking and !is_sticking:  # Condition to check if not sticking to a wall
		if Input.is_action_just_pressed("attack"):  # Check attack input only when not sticking to a wall
			is_attacking = true
			handle_attack_animation(delta)

	set_direction()
	handle_sticking()
	move_and_slide()

	# Update animations
	handle_movement_animation(direction)
		
	toggle_flip_sprite(direction)

func set_direction():
	direction = 1 if not sprite.flip_h else -1
	wall_checker.rotation_degrees = 90 * -direction

func is_near_wall():
	return wall_checker.is_colliding()

func handle_sticking():
	var direction = Input.get_axis("move_left", "move_right")
	if !is_on_floor() and is_on_wall() and wall_checker.is_colliding():
		is_sticking = true
		is_attacking = false
		velocity.x = 0
		velocity.y = 0
		sprite.play("climb")
		
	elif !is_on_floor() and is_on_wall() and !wall_checker.is_colliding():
		sprite.flip_h = direction < 0
		is_sticking = true
		is_attacking = false
		sprite.play("jump")

	if Input.is_action_just_pressed("jump") and is_on_wall() and wall_checker.is_colliding():
		if direction != 0 and direction != last_direction:
			sprite.flip_h = direction < 0
			is_sticking = false
			velocity.y = JUMP_VELOCITY / 1.5
			velocity.x = direction * SPEED / 1
			is_wall_jumping = true

func handle_movement_animation(dir):
	if is_on_floor() and not is_attacking and !is_on_wall():
		if velocity.length() == 0:
			if is_crouching:
				sprite.play("crouch")
			else:
				sprite.play("stand")
		else:
			sprite.play("run")
	elif not is_on_floor() and not is_attacking and !is_on_wall():
		sprite.play("jump")
		
func toggle_flip_sprite(direction):
	if direction != 0 and is_on_floor() and !is_on_wall():
		sprite.flip_h = direction < 0

func handle_attack_animation(delta):
	if is_attacking and !is_on_wall():
		if is_crouching:
			sprite.play("crouch_attack")
		else:
			sprite.play("stand_attack")
	if is_attacking and !is_on_floor() and !is_on_wall():
		velocity.y += gravity * delta + 11 # Counteract gravity to prevent gaining height when attacking while jumping

func _on_animated_sprite_2d_animation_finished():
	is_attacking = false

Good luck to anyone brave enough to try and help me, thank you. I will just try to move on to another part of the game if it’s too complicated for now.

Hi!

You should use the One Way Collision of that vertical wall (don’t use Area2D)…

Something like this (look at the arrow, it appears with this option activated and indicates from which side you collide):
image

Try it, it works!

1 Like

Thank you for your answer! I didn’t think someone would answer after I saw the post was already viewed so many times haha…

I remember messing with One Way Collision but I didn’t have the result I wanted. How do you move that arrow that determines the One Way Collision? That may have been my issue all along… I will try again when I can.

I don’t think it’s gonna work exactly as I want but at least it would be something (because I still want the player to be able to go through the collision if they are on the floor and walking toward it). I will report when I can.

You have to set the One Way Collision to true and then rotate the figure until you get it the way you want it.

The other thing you mention also has a solution. Simply disable those collisions you want when the character is_on_floor() == true (Godot Character function)

I hove this is usefull for you

1 Like

I managed to do it!

I am going to explain it in case it helps someone else later… or even my forgetful future self.

I made a StaticBody2D to which I attached a CollisionShape2D. I set the ColisionShape2D to true and gave it a rectangle shape that I turned into a line. I put it on top of where I wanted in the game and turn the CollisionShape2D around so that it blocks the side I want.

I put the StaticBody’s Collision Layer to 9 (which is going to be for stuff I want the player to be able to wall jump from).

In the player’s script, under the _physics_process(delta) function, I added:

if is_on_floor():
		# Disable collision with objects on layer 9
		set_collision_mask_value(9, false)
	else:
		# Enable collision with objects on layer 9
		set_collision_mask_value(9, true)

To check if the StaticBody2D triggers “is_on_wall”, I added “if is_on_wall(): print(“wall”)” in my script. It was printing “wall” when I was jumping against it so it seems like it works.

I will try adding the wall jumps once I finally understand how to do state machines… Wish me luck.

Thanks a lot Ironsx!

1 Like