How to detect the player fallen from height?

Godot Version

4.7.2

Question

How is typically detected the player fall from height and should take a damage?

I thought about check velocity.y but what condition would looks like to say he actually reached the floor and fallen from 15m?

I’m not sure if this is typical, but I would check the player’s position once when they leave the ground, and again when they land. If the distance between the two positions’ Y values is great enough, damage the player. Something like that?

My current player script is

extends CharacterBody3D

const SPEED = 7.5
const JUMP_VELOCITY = 20
@onready var anim_tree: AnimationTree = $AnimationTree
@onready var anim_state: AnimationNodeStateMachinePlayback = anim_tree.get("parameters/playback" )

var state: String = "Idle"

func _ready() -> void:
	anim_state.travel(state)

func _physics_process(delta: float) -> void:
	# Add the gravity.
	if not is_on_floor():
		velocity.y += -90 * delta

	var input_dir := Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left")
	velocity.x = input_dir * SPEED
	velocity.z = 0
	position.z = 1.5
	print(velocity.y)
	move_and_slide()
	
	if input_dir != 0:
		rotation.y = PI/2 if input_dir > 0 else -PI/2 


	# State Machine Logic
	var new_state = state
	if Input.is_action_just_pressed("ui_up") and is_on_floor():
		new_state = "Jump"
		velocity.y = JUMP_VELOCITY
	elif not is_on_floor():
		new_state = "Jump"
	elif is_on_floor():
		if state == "Jump":
			new_state = "Land"
		if abs(input_dir) > 0.1:
			new_state = "Walk"
		else:
			new_state = "Idle"
		
	if new_state != state:
		state = new_state
		anim_state.travel(state)

Maybe in state Jump to add timer?

You need to register “take off” and “landing” moments. The former happens when the player is not on floor but was on floor on previous frame. The latter is reverse. Between those two events the player is in air. While in air, track their highest elevation. When the player lands, the total fall is the difference between the current and the highest recorded elevation.

So I made update of State Machine to include those two parameters, but I probably checking it wrongly.

	# State Machine Logic
	var new_state = state
	if Input.is_action_just_pressed("ui_up") and is_on_floor():
		new_state = "Jump"
		velocity.y = JUMP_VELOCITY
	elif not is_on_floor():
		take_off_height = global_position.y
		new_state = "Jump"
	elif is_on_floor():
		if state == "Jump":
			land_height = global_position.y
			recorded_elevation = land_height - take_off_height
			print(recorded_elevation)
			new_state = "Land"
			
		if abs(input_dir) > 0.1:
			new_state = "Walk"
		else:
			new_state = "Idle"

it’s showing -0.57 the recorded_elevation.

You need to track previous frame on-floor status. Without that you can’t determine if the player launched or landed at the current frame.

hmm, not sure how can I check it by frame is it specific part of code I should include a boolean to determine if player launched or landed ?

Store the status in a boolean property at the end of processing function, and use it in the processing function before you store it again.

var was_on_floor_previous_frame := false

func _physics_process(delta): 
	if was_on_floor_previous_frame and not is_on_floor():
		# take off
	if not was_on_floor_previous_frame and is_on_floor():
		# landing

	was_on_floor_previous_frame = is_on_floor()

Shall this be separate from state machine or is it ok to include it in statement branch?

It depends on what your state machine is doing. I’d start by keeping it separate. If your state machine switches on take off and landing, then you don’t need any of this as your state machine already tracks it and you only need to intervene when the relevant state switches happen.

This works thank you.

Is there improvements I shall make or is it ok?

extends CharacterBody3D

const SPEED = 7.5
const JUMP_VELOCITY = 20
@onready var anim_tree: AnimationTree = $AnimationTree
@onready var anim_state: AnimationNodeStateMachinePlayback = anim_tree.get("parameters/playback" )


var was_on_floor_previous_frame: bool = false

var take_off_y: float
var land_y

var state: String = "Idle"

func _ready() -> void:
	anim_state.travel(state)

func _physics_process(delta: float) -> void:
	# Add the gravity.
	if not is_on_floor():
		velocity.y += -90 * delta
	var input_dir := Input.get_action_strength("player_2_right") - Input.get_action_strength("player_2_left")
	velocity.x = input_dir * SPEED
	
	#Stop Moving and Slide on Z axis
	velocity.z = 0
	position.z = 1.5
	
	move_and_slide()
	
	# Check for height

	if was_on_floor_previous_frame and not is_on_floor():
		take_off_y = global_position.y
	if not was_on_floor_previous_frame and is_on_floor():
		land_y = global_position.y
		if (take_off_y - land_y) > 10:
			global_position = Vector3(0, 0, 1.5)
	was_on_floor_previous_frame = is_on_floor()
	
	# Rotation of Character to face direction of movement
	if input_dir != 0:
		rotation.y = PI/2 if input_dir > 0 else -PI/2

	# StateMachine Logic
	var new_state = state
	if Input.is_action_just_pressed("player_2_jump") and is_on_floor():
		new_state = "Jump"
		velocity.y = JUMP_VELOCITY
	elif not is_on_floor():
		new_state = "Jump"
	elif is_on_floor():
		if state == "Jump":
			new_state = "Land"
		if abs(input_dir) > 0.1:
			new_state = "Walk"
		else:
			new_state = "Idle"
	
	if new_state != state:
		state = new_state
		anim_state.travel(state)

Use consistent naming convention for the height variables, so something like take_off_y and land_y, or take_off_height and land_height.

Note that this will take the difference between on-ground positions. Depending on the mechanics, you may want the difference between the landing elevation and highest reached elevation (which may be higher than the take off elevation if the player jumps up). In that case, maintain a property that records that. Set it to current elevation on take off and on each frame overwrite it if the player is higher that what’s stored there. When the player lands - calculate the difference between that maximum and the landing elevation.

currently only checking if player fallen more than 10 meters, then I relocate him to start.

is this use of built in function max?

such as this?

	if was_on_floor_previous_frame and not is_on_floor():
		take_off_y = global_position.y
	if not was_on_floor_previous_frame and is_on_floor():
		land_y = global_position.y
		if (take_off_y - land_y) > 10:
			global_position = Vector3(0, 0, 1.5)
	if not is_on_floor():
		var new_take_off := global_position.y
		take_off_y = max(take_off_y, new_take_off)
	was_on_floor_previous_frame = is_on_floor()

Yeah, you can use max() here.
Note that in 2D it’d be min() because the higher elevation will have smaller y.