Unable to kill my player character

Godot Version

4.2.1 Stable

Question

Hi All. I am having difficulty destroying the player scene when I have my projectile hit the player. Currently I have it set up where the missile scene will shoot from the top down towards the player and change to the explosion scene if it comes into contact with either the player or the tilemap. And once it comes into contact it will destroy itself, which I want.

I’ve followed video tutorials on how to destroy the player CharacterNody2D node, usually with a Area2D child and CollisionShape2D grandchild. I’ve connected the Area2D via body_entered signal:

func _body_entered(body):
	if body.is_in_group("Enemy"):
		queue_free()

But the player stands strong.

For reference I have three layers (1: World, 2: Player, 3:Enemy).
The player is just on layer 2, mask 1 and 3.
The missile and missile explosion on layer 3, mask 1 and 2.

I also have a RayCast2D turret scene which shoots the missiles.

Not sure if this is due to some layer/mask issue, Area2D monitoring/monitorable issue, or if its some issue related to the specific codes for the player, missile, or explosion scenes.

Please let me know if you need more detail or the code itself for the scenes.

You need to remove the player node from the tree.

Not sure if you can:
parent_node.remove_child(self)

If not, then send a signal to some parent manager to do the deed.

After that, you use queue_free

What do you get if you print(body.is_in_group(“Enemy”)) instead of the if-check?
Groups are not the same thing as layers. You can use layers to make sure that only enemies can collide with the player, which would make that is_in_group check unnecessary.
If you do need to be able to collide with multiple types of bodies on the same hitbox and you want to check if the colliding body is in a certain group, you have to make sure that every enemy is added to the “Enemey” group. You can find a guide on how to do that in the official Godot documentation

Could you elaborate on that? I am very new to Godot, still trying to understand everything. Would the

parent_node.remove_child(self)

be inside the func _body_entered?

Regardless, I did try to create a

signal hit

based on Godot’s Killing the player and the following kill code. The logic makes sense. But even after resetting the collision layers for the Area2D/Hitbox/MissileDetector, the player didn’t die. I’m thinking it might have something to do with the missile and/or missile explosion scene codes. I also tried to manually add the player, and missile scenes to groups under the Node tab, but that also failed.

Just in case, heres the code for the missile

extends Node2D

var direction := Vector2.DOWN
var speed = randf_range(350,525)
@onready var area_2d = $Area2D

func _physics_process(delta):
	position += direction * speed * delta

func _ready():
	add_to_group("Enemy")
	area_2d.connect("body_entered",Callable(self, "_on_impact"))
	look_at(position+direction)

func _on_impact(_body: Node) -> void:
	queue_free()

func _on_screen_exited():
	queue_free()

Missile Explosion

extends Node2D
@onready var audio = $AudioStreamPlayer2D

func _ready():
	add_to_group("Enemy")

func _process(_delta):
	if audio.playing == false:
		audio.play()

func _animation_finished():
	queue_free()

The Missile’s Replace Node Script (child node Missile Node2D)

class_name ReplaceOnAreaEntered
extends ReplaceScene

@export var enter_area : Area2D

func _ready():
	enter_area.connect("body_entered", _on_body_entered)

func _on_body_entered(_p_body : Node2D):
	replace()

And the ReplaceScene script

class_name ReplaceScene
extends Node

@export var replace_target : Node2D
@export_file() var replacement_scene_path : String 
#Set a file path [@export_file], then load the variable string at path
#Then instantiate copy to replace target

func replace():
	var instance : Node2D = load(replacement_scene_path).instantiate()
	call_deferred("_repo_and_free", instance)

func _repo_and_free(p_instance : Node2D):
	replace_target.get_parent().add_child(p_instance)
	p_instance.global_position = replace_target.global_position
	replace_target.queue_free()

The missile is a Node2D, not a body, so it won’t be detected by the body_entered signal.

Also, calling parent_node.remove_child(self) is a bad idea, since that doesn’t free the node, it will just exist outside the scene tree, taking up memory. Just use queue_free().

Thank you! You pointed out that since its Node2D, and wont be detected by body_entered. I changed the signal to be

func _on_hitbox_area_area_entered(area):
	hit.emit()
	queue_free()

And the player is gone. Thank you so much, you just helped me deal with a massive problem. I realize now that the tutorials I was following had the enemy scene also set as CharacterBody2D, not as a Node2D.

1 Like

Does queue_free also remove_child? If so, cool! I type too much code :smiley: