Player not colliding with enemy.

Godot Version

4.6.2 stable

Question

Hello all,

Non-programmer here attempting to make my first game. This is going to be a simplified Brotato clone while I get accustomed to the application.

I am trying to use the docs primarily but I am stuck on something. I have my player controller and I am attempting to implement a iFrame system. Here is the scene tree and script that I have thus far:

Node2D
|_Hurtbox (Area2D)
| |_CollisionShape2D
|_AnimatedSprite2D
|_PlayerCenter (Marker2D)
| |_Crosshair (other TSCN)
|_iFrames (Timer)

extends Node2D

@onready var idle_anim = $AnimatedSprite2D
@onready var run_anim = $AnimatedSprite2D
@onready var i_frames_timer = $iFrames
@onready var hurtbox = $Hurtbox
var health: float
var speed: float = 100.0
var invulnerable :bool


# Called when the node enters the scene tree for the first time.
func _ready():
	idle_anim.play("Idle")
	health = 10
	invulnerable = true

# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
	_movement(delta)
	if invulnerable == false:
		_player_health()
	
	print(health)
	print (invulnerable)
#	print ("iFrames ", i_frames_timer.time_left)

func _movement(delta):
	var direction = Vector2.ZERO	
	if Input.is_action_pressed("down"):
		direction.y += 1
	if Input.is_action_pressed("up"):
		direction.y -= 1
	if Input.is_action_pressed("right"):
		direction.x += 1	
	if Input.is_action_pressed("left"):
		direction.x -= 1
		
	position += direction.normalized() * speed * delta
	
	# Animation
	if direction != Vector2.ZERO:
		run_anim.play("Run")
		if direction.x < 0:
			run_anim.flip_h = true
		if direction.x > 0:
			run_anim.flip_h = false
	else:
		idle_anim.play("Idle")


func _on_i_frames_timeout():
	invulnerable = false


func _player_health():
	for area in hurtbox.get_overlapping_areas():
		if area.is_in_group("Enemy"):
			health -= 1
			invulnerable = true
			i_frames_timer.start()
			if health <= 0:
				queue_free()


I am able to see invulnerable change from true to false:

The enemy that I have has the following structure

Area2D
|_AnimatedSprite2D
|_CollisionShape2d

The collision shapes do overlap each other when the enemy hits the player.

I am lost on what is happening here. In my mind the logic works. This is the main bug that is keeping me from moving on.

I would add print statements to see what happens. Examples below.

You could also remove the “group” check, if you set only nodes belonging to a certain group to every layer. E.g. player is layer 1, enemies 2. If player is only looking for 2, you don’t need an extra check in the code. The function would not be called if the area was not an enemy.

You have a few problems here. First, you are trying to re-invent the wheel. Second, you are not doing collision checking correctly with an Area2D node.

It might help to take a step back and describe what you’re trying to accomplish. For example:

You said you are a non-programmer. So where did you come up with the idea to adopt a web programming paradigm to making a game? Can you define what an iFrame system is to you? What you’re trying to do with it? How and why did you decide it was the appropriate choice for whatever you’re trying to do? Your initial post reads like sci-fi gibberish, like when Doctor Who talks about “reversing the polarity” of something.

Please use a screen shot of your scene tree. There is a lot of hidden information in a picture of a scene tree that we can use to figure out what’s going wrong. This gets lost when you give us a text version.

Don’t Reinvent the Wheel

Your player is a Node2D instead of a CharacterBody2D. This not only complicates the code you need to create to move the player around, it also then requires you to add another CollisionObject2D to it to enable collisions. I recommend you do this:

  1. Change your Node2D to a CharacterBody2D.
  2. Delete your _player_health() function.
  3. Add a damage() function.
@onready var i_frames_timer = $iFrames


func damage(amount: float) -> void:
	if not i_frames_timer.is_stopped()
		return
	health -= amount
	i_frames_timer.start()
	if health <= 0:
		queue_free()
  1. Add a script to your enemy:
class_name Enemy extends Area2D

@export var damage: float = 1.0


func _ready() -> void:
	body_entered.connect(_on_body_entered) -> void:
		body.damage(damage)
  1. Remove Physics Mask 3 from the Player.
  2. Add Physics Layer 2 to the Player. (Assuming that’s the player layer, if not use Layer 4.)
  3. Add Physics Layer 1 to the Enemy so it can run into walls and stuff. (If you want that to happen.)

Now the Enemy can detect only the Player, and doesn’t need all those extra checks.

iFrames

EDIT: I understand what you mean by IFrames now. Invulnerability frame. I added it to step 4.

“Iframe” in game context tends to mean “invincibility frame”, aka a short period of invulnerability after taking damage. I’m honestly surprised I have to tell you of all people.

I can’t know everything. :slight_smile: I’ve never heard the term. But that’s how I learn new things. I even googled it to see if it meant something else and couldn’t find it. I looked it up, and this is how I would implement that:

@onready var i_frames_timer = $iFrames


func damage(amount: float) -> void:
	if not i_frames_timer.is_stopped()
		return
	health -= amount
	i_frames_timer.start()
	if health <= 0:
		queue_free()

No need for a bool named invulnerable. I will update my previous post.

Thanks @EriTheSwitch