How to process fall damage and other types of damage at the same time?

Godot Version

v4.2.2.stable.official [15073afe3]

Question

Hey all,

I’m currently developing a 2D platformer game. I have implemented fall damage, which utilizes the difference in player velocity right before hitting the ground and while on the ground.

The process works as intended when the player lands on the ground. The fall damage is calculated and deducted from the player’s health, and invincibility is applied for 3 seconds.

And if the player is invincible when hitting the ground from a height, the fall damage is ignored, which is also as intended.

The issue is when the player lands on a hazard (traps, damaging areas, etc.) before touching the ground.
It applies the damage from the hazard first. Then the player invincibility status is applied, ignoring the fall damage.

Is there any way to apply both fall damage and hazard damage at the same time, when the player lands on a hazard and becomes invincible afterward?

If the player is invincible when they land on a hazard from a height, both the fall damage and hazard damage should be ignored.

Scripts:

Player script:

var old_vel: float = 0
var velocity_difference: float
var fall_dmg: int

func apply_damage(damage_amount: int):
	if is_shielded == false:
		is_hurt = true
		current_health -= damage_amount
		go_invincible()
		
	else:
		SoundManager.play_clip(sound_player_hurt,SoundManager.SOUND_PLAYER_SHIELD_HIT)
		if current_shield_amount >= damage_amount:
			current_shield_amount -= damage_amount
		else:
			var remaining_dmg = damage_amount - current_shield_amount
			current_shield_amount = 0
			current_health -= remaining_dmg
		go_invincible()
		
		
	Signalmanager.on_player_hit.emit(current_shield_amount, current_health)

func _on_player_hitbox_area_entered(area):
	if is_dead == true:
		return
		
	is_in_damaging_area = true
	if invincible == true:
		return
	else:
		if velocity_difference < -750:
			return
		else:
			periodic_damage_check_timer.start()
			if area.is_in_group("enemy_bullet"):
				apply_damage(1)
			if area.is_in_group("enemy_bomb_explosion"):
				apply_damage(3)
			if area.is_in_group("enemy_hitbox"):
				apply_damage(1)
			if area.is_in_group("hazard_normal_dmg"):
				apply_damage(1)
func apply_fall_damage():
	if invincible:
		return
	
	is_hurt = true
	if velocity_difference < -750 and velocity_difference >= -850:
		fall_dmg = roundi(max_health * 0.1)
	elif velocity_difference < -850 and velocity_difference >= -950:
		fall_dmg = roundi(max_health * 0.3)
	elif velocity_difference < -950 and velocity_difference >= -1050:
		fall_dmg = roundi(max_health * 0.5)
	elif velocity_difference < -1050 and velocity_difference >= -1150:
		fall_dmg = roundi(max_health * 0.7)
	elif velocity_difference < -1150 and velocity_difference >= -1250:
		fall_dmg = roundi(max_health * 0.9)
	elif velocity_difference < -1250:
		fall_dmg = roundi(max_health * 1)
	
	current_health = current_health - fall_dmg
	Signalmanager.on_player_hit.emit(current_shield_amount, current_health)
	go_invincible()

Player State Machine > Fall state script
(“Player” is a reference to the Player node)

extends "State.gd"

func update(delta):
	Player.apply_gravity(delta)
	Player.animation_player.play("fall")
	

	Player.velocity_difference = Player.velocity.y - Player.old_vel

	if Player.velocity_difference < -750:
		if Player.invincible == true:
			pass
		else:
			Player.apply_fall_damage()
	Player.old_vel = Player.velocity.y
	
	player_movement()
	if Player.is_on_floor() and Player.jump_input_actuation == false:
		return STATES.IDLE
	if Player.jump_input_actuation == true and can_coyote_jump == true:
		return STATES.JUMP
	if Player.shoot_input:
		return STATES.AIR_SHOOT
	if Player.throw_grenade_input:
		return STATES.AIR_THROW
	if Player.is_hurt:
		return STATES.HURT
	if Player.climbing:
		return STATES.CLIMB_IDLE
	if Player.is_dead == true:
			return STATES.DEATH
	return null

func enter_state():
	//other code. not relevant to the question
	
func exit_state():
	Player.velocity = Vector2.ZERO
       //other code. not relevant to the question

Any help on this would be much appreciated.
Thank you in advance!

Maybe you could make it so falling doesnt care about invincibility state or separate them.

Make a new method, get_fall_damage(). Move your if/else block to that method. Call it from both damage methods.

func get_fall_damage() -> int:
	if velocity_difference < -750 and velocity_difference >= -850:
		fall_dmg = roundi(max_health * 0.1)
	elif velocity_difference < -850 and velocity_difference >= -950:
		fall_dmg = roundi(max_health * 0.3)
	elif velocity_difference < -950 and velocity_difference >= -1050:
		fall_dmg = roundi(max_health * 0.5)
	elif velocity_difference < -1050 and velocity_difference >= -1150:
		fall_dmg = roundi(max_health * 0.7)
	elif velocity_difference < -1150 and velocity_difference >= -1250:
		fall_dmg = roundi(max_health * 0.9)
	elif velocity_difference < -1250:
		fall_dmg = roundi(max_health * 1)
	return fall_dmg

Here how you fix this issue depends on how you want your game to work. The simplest solution is to defer activating invincibility until after the current frame is processed. This will make sure that all damage that would be taken in this frame still gets taken and the order of processing damage will not matter. If you want to try this look into call_deferred Object — Godot Engine (stable) documentation in English it should be as simple as

call_deferred("go_invincible")

There are also other ways to achieve this if this one will not work. I do not know when Godot will actually process your damage functions.

The possible problem with this approach is that it will only account damage taken in this exact frame which is very little time, and it might sometimes look like the player didn’t take damage it should take but there was actually a one frame difference in the background.

A more complex way is to add a small timer before activating invincibility, this will ensure that all damage taken in this small window gets applied but it might feel a bit clunky depending on how you set this timer up. If you do this do not forget to ignore other calls to go_invincible while the timer is running and cause invincibility to last longer then it should.

If you want the player to be invincible just from fall damage for a short while after falling, then you have to implement different damage types and check what was actually the cause of taking damage.

Thank you!
Calling the invincibility function via call_deferred resolved my issue.

I’ve tweaked my player code a bit after posting the question.
I will put the updated code in this response with the resolution provided, in case if another person needs it.

NOTE: The code piece from the “State Machine > Fall state”, where it checks the velocity difference of the player, has been removed and moved to the player physics_process function.

Player code:

var old_vel: float = 0
var velocity_difference: float
var fall_dmg: int

func _physics_process(delta):

//some code

	move_and_slide()

//some code

	if velocity_difference < -750:
		apply_fall_damage()
	old_vel = velocity.y

func go_invincible():
	invincible = true
	animation_player_invincible.play("invincible")
	invincible_timer.start()


func apply_damage(damage_amount: int):
	if is_shielded == false:
		is_hurt = true
		current_health -= damage_amount
	else:
		SoundManager.play_clip(sound_player_hurt,SoundManager.SOUND_PLAYER_SHIELD_HIT)
		if current_shield_amount >= damage_amount:
			current_shield_amount -= damage_amount
		else:
			var remaining_dmg = damage_amount - current_shield_amount
			current_shield_amount = 0
			current_health -= remaining_dmg
	call_deferred("go_invincible")  //resolution to my question
	Signalmanager.on_player_hit.emit(current_shield_amount, current_health)

func _on_player_hitbox_area_entered(area): // signal
	if is_dead == true:
		return
		
	is_in_damaging_area = true
	if invincible == true:
		return
	else:
		periodic_damage_check_timer.start()
		if area.is_in_group("enemy_bullet"):
			apply_damage(1)
		if area.is_in_group("enemy_bomb_explosion"):
			apply_damage(3)
		if area.is_in_group("enemy_hitbox"):
			apply_damage(1)
		if area.is_in_group("hazard_normal_dmg"):
			apply_damage(1)


func apply_fall_damage():
	if invincible:
		return
	if velocity_difference < -750 and velocity_difference >= -850:
		fall_dmg = roundi(max_health * 0.1)
	elif velocity_difference < -850 and velocity_difference >= -950:
		fall_dmg = roundi(max_health * 0.3)
	elif velocity_difference < -950 and velocity_difference >= -1050:
		fall_dmg = roundi(max_health * 0.5)
	elif velocity_difference < -1050 and velocity_difference >= -1150:
		fall_dmg = roundi(max_health * 0.7)
	elif velocity_difference < -1150 and velocity_difference >= -1250:
		fall_dmg = roundi(max_health * 0.9)
	elif velocity_difference < -1250:
		fall_dmg = roundi(max_health * 1)
	apply_damage(fall_dmg)
1 Like

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