Trying to make a player bounce of an enemy like Mario

Godot Version

Replace this line with your Godot version

Question

How would you go about doing that? Would it be with vectors or raycasting?

What are your own thoughts on how to solve this problem? You seem to have narrowed down the solution to one that includes either “vectors” or “raycasting”. What makes you think you need to use these?

On a sidenote, a statement saying that a solution needs to use “vectors” in gamedev is kinda as abstract as it gets. You can’t get around using vectors. So, could you elaborate a bit?

Well I was thinking of making a collision box on the top of the enemy and using Vectors for the player to collide with that box, but I’m struggling how to do that. I could be wrong, I am new to this engine.

I see. Let’s wait for @yesko’s response since he seems to be conjuring up a solution for you.

1 Like

As far as I know, Super Mario basically does it like this:

  • Use a shapecast at the player’s feet as well as a collision shape covering the entire enemy to check if there is an enemy that Mario could be jumping on right now
  • If there is, and the y-velocity of Mario is greater than zero (i.e. he is moving downward), “kill” trigger the function to “bounce on an enemy” which kills the enemy, gives Mario upward velocity to make him bounce back up, stuff like that

Of course, that’s not the only way to do it.

As @Sweatix said, using vectors is kind of essential in gamedev so it’s hard to tell what you mean with the phrase…

But I assume you mean the solution I described but “in reverse”, so the player has a collision shape that covers their entire body and the enemy has a collision shape at their head checking for collision. For this to work you would also need to check if the player has a downward velocity (so they can’t just walk into an enemy to kill them) but other than that it’s a valid solution.
Here is what I would do to implement it:

  • Add an Area2D as a child of the player that has a collision shape fully covering the player sprite
  • Add an Area2D as a child of the enemy with a collision shape only at its head
  • Put the enemy’s area on a seperate collision layer and set the player’s area’s mask to only include that layer. This is so the player doesn’t try to bounce on other things like the ground or collectibles
  • In the player’s _process() function, check if there are any enemies’ areas colliding with the Area2D. If that’s the case, and the player is moving downwards, get the enemy and destroy them, as well as doing anything else that should happen when the player bounces on an enemy
1 Like

Thanks, can it by physics process or just regular process?

And, how do I check under the function if the enemies area is colliding with the players areas?

Yeah sorry, physics process is actually better in this case because you’re interacting with the physics engine. Both theoretically work though

Area2D has a function has_overlapping_areas with which you can detect if there are any intersecting areas and with get_overlapping_areas you can get those areas

Can the script be in the main node or should i make a script for the area2d child node?

Basically, was doing Brackeys tutorial and maybe the enemies just being generic 2D nodes was not a good idea?

I use Area Collision shapes for this above the player.

extends CharacterBody2D


const SPEED = 100.0
const JUMP_VELOCITY = -300.0

# Get the gravity from the project settings to be synced with RigidBody nodes.
var gravity = ProjectSettings.get_setting("physics/2d/default_gravity")
var collision = get_slide_collision

@onready var collision_shape_bottom = $Area2D/CollisionShapeBottom
@onready var animated_sprite = $AnimatedSprite2D
@onready var grunt = $"../Grunt"
@onready var collision_shape = $Area2D/CollisionShape2D


func _physics_process(delta):
	# Add the gravity.
	if not is_on_floor():
		velocity.y += gravity * delta
	if collision_shape_bottom = direction
	
	# Handle Jump.
	if Input.is_action_just_pressed("jump") and is_on_floor():
		$Jump.play()
		velocity.y = JUMP_VELOCITY

	# Get the input direction and handle the movement/deceleration.
	# As good practice, you should replace UI actions with custom gameplay actions.
	var direction = Input.get_axis("move_left", "move_right")
	
	if direction > 0:
		animated_sprite.flip_h = false
	elif direction < 0:
		animated_sprite.flip_h = true
	# Animates the Player
	if is_on_floor():
		if direction == 0:
			animated_sprite.play("Idle")
		else:
			animated_sprite.play("Walk")
	else:
		animated_sprite.play("Jump")
		
		
	if direction:
		velocity.x = direction * SPEED
	else:
		velocity.x = move_toward(velocity.x, 0, SPEED)

	move_and_slide()

Here’s my code for the player character, honestly, still getting stumped, maybe I should just do more tutorials, was doing Brackeys and the 2D Dodge the creeps and learned some stuff, but some of this connecting signals and learning what each function does and reading the documentation just frustrates me, maybe I should just go back to Game Maker lol.

It usually works okay to have them just be Node2Ds, but if you feel like it’s achievable for you it’s better to give them their own class that has methods like kill or damage or something. This just makes them more independent from the rest of the game and prevents the main script from getting bloated with many different systems.

In my opinion, you should have the the Area2D’s be handled by their respective scene roots, so the player handles their own areas.
You can do this easily by getting a reference to the area in the script (e.g. @onready var area: Area2D = $Area2D) and then managing the reference.

I don’t think tutorials are the best way to learn, they are good for learning the absolute basics, but from there, (when possible) you should try to solve every “large” problem yourself and ask, research, or watch tutorials about the smaller things that are holding you back. This will inevitably lead to “imperfect” code or architecture but no one is ever perfect at game development and you will learn a lot more trying to find a solution than watching someone find the best solution and copying them.

I’ll include some code snippets that should hopefully help you put the feature in place

# in the player script

@onready var area: Area2D = $Area2D # replace this with the actual node path to the Area2D
# in the player script

# function to handle the player bouncing on enemies
func _handle_enemy_bouncing() -> void:
	# player needs to be falling to bounce on enemies, so if they're not we just cancel the function
	if velocity.y <= 0:
		return

	# if there are no enemies' areas overlapping with "area", there are no enemies that the player can bounce on
	if not area.has_overlapping_areas():
		return

	# get one of the enemy areas that are overlapping with "area"
	var enemy_area: Area2D = area.get_overlapping_areas()[0]

	# to get the scene root call get_owner() on the area. This assumes that you have an enemy scene
	var enemy: Node2D = enemy_area.get_owner()

	# whatever happens when the player bounces on an enemy goes in the following lines!
	enemy.queue_free() # e.g. destroy the enemy
	velocity.y = JUMP_VELOCITY # make the player bounce upward
# at the top of _physics_process in the player script

_handle_enemy_bouncing()

By the way, you might be wondering what’s up with the colon and type I put after var variable_name (e.g. var enemy: Node2D = enemy_area.get_owner()). It’s called “static typing” and it means telling the engine what a variable is supposed to store. Without the static types, something like this is possible:

var test = 1
test = "Hello, world!"

test is first assigned an integer and then a string without any errors.
This seems pretty handy at first, but it’s a lot more error-prone than its alternative and you don’t get much code completion with this.

var test: int = 1
test = "Hello, world!" # the engine will complain about this line because "Hello, world" isn't an int

With static typing every variable can only have one type, which prevent a lot of very annoying bugs because you can’t accidentally assign a variable the wrong value anymore. It also makes it clearer what a variable does. A variable just called var position could be an int, a float, a Vector2, or a Vector3, depending on context. But if you use var position: Vector2 instead, everyone immediately knows what position is

1 Like

Looks good, just put it in, getting an error with the area part of the line, going to try and figure that out.

Line 59:Identifier "area" not declared in the current scope.
Line 63:Identifier "area" not declared in the current scope.

EDIT: NVM, fixed it, it works! Thanks so much! Going to look more into this code to understand it more.

1 Like

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