Using classes to implement a flexible damage and health system

Godot Version

v4.2.1.stable.official [b09f793f5]

Question

Hi! I’ve been following some guides (this, and it’s sequel mainly) in an attempt to use classes to implement a flexible damage/health system, and I’m having some trouble. Specifically, the enemy doesn’t seem to be hurting the player whatsoever (tried changing the damage to 50, to see if it was just hurting it once, but nothing). For context, this is my first ever project (So be patient with any idiocy, please) and I’m making a top-down 2d twin stick shooter. In this specific example, I’m trying to make my enemy hurt my player, for 5 damage, when they’re colliding. I had this working already, using the method shown here, but that was far too rigid and restrictive. Below is the code:

In a file named Attack.gd:

extends Node

class_name Attack

var attack_damage: float
var knockback_force: float
var attack_position: Vector2

In the enemy script (I’ve tried to cut out things that aren’t relevant):

extends CharacterBody2D

class_name Enemy

var attack_damage = 50
var knockback_force = 0.0
var health = 10

#Attack code
func _on_body_entered(body):
	if body.has_method("take_player_damage"):
		var attack = Attack.new()
		attack.attack_damage = attack_damage
		attack.knockback_force = knockback_force
		attack.attack_position = global_position
		body.take_player_damage(attack)

In the player script (same):

extends CharacterBody2D

var health = 100.0

func take_player_damage(attack: Attack, delta):
	health -= attack.attack_damage * delta

I have got this working for hurting the enemy with bullets. Here is the script for that:
(The same attack.gd)
The bullet script:

extends Area2D
#starts "empty" and "fills up" as the bullet travels
var travelled_distance = 0
var attack_damage = 1
var knockback_force = 100

func _physics_process(delta):
	const SPEED = 250
	const RANGE = 6000
	#defining a variable name direction. Direction is equal to: the right direction (for the bullet. ei. where the "right hand" of the bullet would be) rotated by the rotation predefined rotation of the bullet.
	var direction = Vector2.RIGHT.rotated(rotation)
	position += direction * SPEED * delta
	#calculate the traveled distance based on the "SPEED" constant, and if the travelled distance exceeds the "RANGE" constant, delete bullet
	travelled_distance += SPEED
	if travelled_distance > RANGE:
		queue_free()

#if the bullet enters a body with the "take_damage" method (ei. function) run "take_damage" and remove bullet.
func _on_body_entered(body):
	if body.has_method("take_damage"):
		var attack = Attack.new()
		attack.attack_damage = attack_damage
		attack.knockback_force = knockback_force
		attack.attack_position = global_position
		body.take_damage(attack)
	queue_free()

The enemy script (Yet again, tried to only include was what relevant for the sake of conciseness)

var health = 10
func take_damage(attack: Attack):
	health -= attack.attack_damage
	#Turn sprite red with .5 alpha (pure red was decided to be too contrasty)
	$AnimatedSprite2D.modulate = Color(1,0,0,.5)
	#starts the timer for the color, so that it flashes for .5 seconds
	hurt_timer.start()
	
	velocity = (global_position - attack.attack_position).normalized()*attack.knockback_force
	
	if health == 0:
		#stops the Adventurer
		AdventurerSpeed = 0
		#Plays animation of Adventurer sprite fading away
		anim.play("Dying")
		#Wait 1 second
		await get_tree().create_timer(1).timeout
		#delete the Adventurer
		queue_free()

Any help would be much appreciated. I’m still trying to wrap my head around GDScript (This is also the first time I’ve really tried to learn and use a programming language) and I’m finding myself frequently lost. If you could please be as detailed in your explanations of how I’m stupid, I’d much appreciate it :slight_smile:

There are 2 parameters in this function, attack and delta.
However you call it with only 1:

body.take_player_damage(attack)     

This should in fact be giving you an error before you even run it.
Are you seeing any errors?

Ah, I actually meant to remove that, I just added the “delta” as an experiment while trying to get some things to work. Interestingly enough, though, it doesn’t give me an error regardless.

Yes, it won’t give an error, but it also won’t run the connected function.
Its an unfortunate GODOT quirk. Unfortunate because it makes it harder to debug problems.

Thank you for pointing it out. I have removed the “delta”, but the player is still not taking damage when the mob overlaps. Is “_on_body_entered” dependent on collision, perhaps? I had been using a Hurt Box Area2D node with the previous method I mentioned, would there be a good way to implement that here, if I need to? Sorry if this seems nonsensical, I’m still in that stage of mostly following tutorials, and not quite getting how to do it myself :sweat_smile:

I’ve gotten it to work! (somewhat, I still have to modify it so that it does damage over time, instead of just once, but it’s working) The solution was to create an Area2D on my enemy and then use the Area2Ds “body_entered” signal. I was just really ignorant to how the “on_body_entered” function worked, I thought it was something you could use on all body2Ds to tell if it was overlapping with another.

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