is there a way to delay my character jumping to sync up with the animation?

Godot Version

godot 4.3 stable

Question

very new to all this just messing about with it seeing what sort of 3d 3rd person game i could do and got as far as getting a very basic world to test in my player model and animations in and a player controller script that all works but trying to tweak the animations somehow so the animation for jumping i have slightly crouches first before jumping up but obviously the model leaves the floor straight away is there anyway to delay leaving the floor for say half a second or any other way to sync them up? Cheers

extends CharacterBody3D

@onready var camera_mount = $"camera mount"
@onready var visuals = $visuals
@onready var animation_player = $visuals/test/AnimationPlayer

var SPEED = 2.0
const JUMP_VELOCITY = 6.0
 

var walking_speed = 2.0
var sprinting_speed = 5.0
var crouching_speed = 2.0

var running = false
var crouching = false
var in_air = false


@export var sens_horizontal = 0.1
@export var sens_vertical = 0.1

func _ready():
	Input.mouse_mode = Input.MOUSE_MODE_CAPTURED

func _input(event):
	if event is InputEventMouseMotion:
		rotate_y(deg_to_rad(-event.relative.x*sens_horizontal))
		visuals.rotate_y(deg_to_rad(event.relative.x*sens_horizontal))
		camera_mount.rotate_x(deg_to_rad(-event.relative.y*sens_vertical))
		camera_mount.rotation.x = clamp(camera_mount.rotation.x, deg_to_rad(-80), deg_to_rad(45))

func _physics_process(delta):
	
	if not is_on_floor():
		velocity += get_gravity() * delta
		in_air = true
	else:
		if in_air:
			in_air = false
			if velocity.length() > 0:
				if crouching:
					animation_player.play("crouch walk")
				elif running:
					animation_player.play("sprint")
				else:
					animation_player.play("walk")
			else:
					animation_player.play("idle")
		
	
	# Handle sprinting and crouching
	running = Input.is_action_pressed("sprint")
	crouching = Input.is_action_pressed("crouch")

	if crouching:
		SPEED = crouching_speed
	elif running:
		SPEED = sprinting_speed
	else:
		SPEED = walking_speed

	# Handle jump
	if Input.is_action_just_pressed("jump") and is_on_floor():
		if animation_player.current_animation != "jump":
			animation_player.play("jump")
		velocity.y = JUMP_VELOCITY
		in_air = true
		

	# Get the input direction
	var input_dir = Input.get_vector("left", "right", "forward", "backward")
	var direction = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()

	if direction.length() > 0 and not in_air:
		if crouching:
			if animation_player.current_animation != "crouch walk":
				animation_player.play("crouch walk")
		elif running:
			if animation_player.current_animation != "sprint":
				animation_player.play("sprint")
		else:
			if animation_player.current_animation != "walk":
				animation_player.play("walk")

		visuals.rotation.y = lerp_angle(visuals.rotation.y, atan2(-input_dir.x, -input_dir.y), 0.15)
		velocity.x = direction.x * SPEED
		velocity.z = direction.z * SPEED
	elif not in_air:
		if crouching:
			if animation_player.current_animation != "crouch":
				animation_player.play("crouch")
		else:
			if animation_player.current_animation != "idle":
				animation_player.play("idle")
				
				
		var DECELERATION = 12.0

		velocity.x = move_toward(velocity.x, 0, DECELERATION * delta)
		velocity.z = move_toward(velocity.z, 0, DECELERATION * delta)

	# Move the character and apply gravity using move_and_slide()
	move_and_slide()

Create a new function called apply_jump_force and move this code for this function:

After that save your script and restart the editor.

In your jump animation, create a call method track and select the node with the character script. In this track do a right click, select insert key and select the apply_jump_force function. Now you just need to drag the created key for the point you want the character start leaving the floor.

1 Like

Hi cheers for that! again quite new to all this so it maybe the way ive wrote the code or done something else but its not quite working properly yet but think thats down to how ive done it rather than the method, so done what you said and created a new func apply_jump_force(): and copied the velocity.y = JUMP_VELOCITY
in_air = true to under that func and that is doing exactly what i asked just after my initial jump now haha so character jumps how i first described but then after the roughly half a second delay on the key on the call method track for the jump animation it jumps again while already mid air but synced up if thats makes sense, so like he has a double jump now with one press of the button but the animation is in time for the “second” jump

so i added

func _apply_jump_force():
	velocity.y = JUMP_VELOCITY
	in_air = true

and also lefy my bit that handles the jump the same

# Handle jump
	if Input.is_action_just_pressed("jump") and is_on_floor():
		if animation_player.current_animation != "jump":
			animation_player.play("jump")
		velocity.y = JUMP_VELOCITY
		in_air = true	

so with it like that i get the weird double jump thing but if i remove “velocity.y = JUMP_VELOCITY” from either of them blocks i end up with only the first out of sync jump and lose the in sync one

Can you show the entire code from character?

yeah ill attach here, so ive only got the one script with everything at the moment and chances are its all wrote horribly aswell as im just trying random things and seeing what works, only thing that is different at this very second is the line " @onready var animation_player = $visuals/test/AnimationPlayer" is now “@onready var animation_player = $visuals/test2/AnimationPlayer” as ive just changed the jump animation for now to one that doesnt crouch down first aha and also removed the func _apply_jump_force bit you said before but everything else is the same as it was

extends CharacterBody3D

@onready var camera_mount = $"camera mount"
@onready var visuals = $visuals
@onready var animation_player = $visuals/test2/AnimationPlayer

var SPEED = 2.0
const JUMP_VELOCITY = 4.5
 

var walking_speed = 2.0
var sprinting_speed = 5.0
var crouching_speed = 2.0

var running = false
var crouching = false
var in_air = false


@export var sens_horizontal = 0.1
@export var sens_vertical = 0.1

func _ready():
	Input.mouse_mode = Input.MOUSE_MODE_CAPTURED

func _input(event):
	if event is InputEventMouseMotion:
		rotate_y(deg_to_rad(-event.relative.x*sens_horizontal))
		visuals.rotate_y(deg_to_rad(event.relative.x*sens_horizontal))
		camera_mount.rotate_x(deg_to_rad(-event.relative.y*sens_vertical))
		camera_mount.rotation.x = clamp(camera_mount.rotation.x, deg_to_rad(-80), deg_to_rad(45))
		

func _physics_process(delta):
	
	if not is_on_floor():
		velocity += get_gravity() * delta
		in_air = true
	else:
		if in_air:
			in_air = false
			if velocity.length() > 0:
				if crouching:
					animation_player.play("crouch walk")
				elif running:
					animation_player.play("sprint")
				else:
					animation_player.play("walk")
			else:
					animation_player.play("idle")
		
	
	# Handle sprinting and crouching
	running = Input.is_action_pressed("sprint")
	crouching = Input.is_action_pressed("crouch")

	if crouching:
		SPEED = crouching_speed
	elif running:
		SPEED = sprinting_speed
	else:
		SPEED = walking_speed

	# Handle jump
	if Input.is_action_just_pressed("jump") and is_on_floor():
		if animation_player.current_animation != "jump":
			animation_player.play("jump")
		velocity.y = JUMP_VELOCITY
		in_air = true	

	# Get the input direction
	var input_dir = Input.get_vector("left", "right", "forward", "backward")
	var direction = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()

	if direction.length() > 0 and not in_air:
		if crouching:
			if animation_player.current_animation != "crouch walk":
				animation_player.play("crouch walk")
		elif running:
			if animation_player.current_animation != "sprint":
				animation_player.play("sprint")
		else:
			if animation_player.current_animation != "walk":
				animation_player.play("walk")

		visuals.rotation.y = lerp_angle(visuals.rotation.y, atan2(-input_dir.x, -input_dir.y), 0.15)
		velocity.x = direction.x * SPEED
		velocity.z = direction.z * SPEED
	elif not in_air:
		if crouching:
			if animation_player.current_animation != "crouch":
				animation_player.play("crouch")
		else:
			if animation_player.current_animation != "idle":
				animation_player.play("idle")
				
				
		var DECELERATION = 12.0

		velocity.x = move_toward(velocity.x, 0, DECELERATION * delta)
		velocity.z = move_toward(velocity.z, 0, DECELERATION * delta)

	# Move the character and apply gravity using move_and_slide()
	move_and_slide()
	

Might not be an elegant solution but you could set up a timer as a child node and change the code to something like this. (don’t know if code is different for 3d - I’ve only tried 2d)

# Handle jump
	if Input.is_action_just_pressed("jump") and is_on_floor():
		timer.start()

#this is a signal
func _on_timer_timeout():
	if animation_player.current_animation != "jump":
				animation_player.play("jump")
			velocity.y = JUMP_VELOCITY
			in_air = true

then set the time to equal whatever amount of time between the crouch and the actual jump and make the timer a one shot

dont worry nothing ive done so far has been elegant haha and chances are ive used something wrong somewhere already, never even used timers or anything like that very very new aha but ive just tried it but not sure if i even set it up correctly, so i added a timer as a child node to my characterbody3d and just left on 1 sec for now and oneshot on then in my script added

@onready var timer = $Timer

func _on_timer_timeout():
	if animation_player.current_animation != "jump":
			animation_player.play("jump")
			velocity.y = JUMP_VELOCITY
			in_air = true

if Input.is_action_just_pressed("jump") and is_on_floor():
		timer.start()

so they are the 3 changes ive just made to the code but now i cant jump and no animation again, is it the way ive done my initial code for the jumping? i had to try a few different things to even get the animation to play at first but got it working with all the in_air = true/false stuff but most of the things ive tried for this issue have just stopped the jump and animation all togther so im wondering have i done something weird early on

did try something along these lines aswell

var jump_queued = false

func _apply_jump_force():
	if !in_air and !jump_queued:  # Only allow jump if not in air and jump hasn't been queued
		velocity.y = JUMP_VELOCITY
		in_air = true
		jump_queued = true  # Mark jump as queued to prevent double jumping


func _physics_process(delta):
	if not is_on_floor():
		velocity += get_gravity() * delta
		in_air = true
	else:
		if in_air:
			in_air = false
			jump_queued = false

func _animation_jump_trigger():
	# This method is called by the animation's call method track at the correct moment
	_apply_jump_force()

might have missed some there but tried along them lines aswell and couldnt get it im pretty sure when i tried that it just got rid of the second jump i had that was in sync but started mid air and kept the normal out of sync one

just to make sure with the timer code did you insert the func on_timer_timeout() as a signal?
if not, you can find the signal by pressing on the timer node then pressing node in the top right.

@onready var timer = $Timer

func _physics_process(delta):
	if Input.is_action_just_pressed("jump") and is_on_floor():
		timer.start()

func _on_timer_timeout():
	if animation_player.current_animation != "jump":
			animation_player.play("jump")
			velocity.y = JUMP_VELOCITY
			in_air = true


Also it looks a bit unclear but if Input.is_action_just_pressed("jump") and is_on_floor(): should still be in func _physics_process(delta): So that when you are on the floor and pressing jump, the timer will start - then when it ends the actual jump should happen.

ahhh cheers for that yeah completely missed that and yeah so my code is like this atm

func _physics_process(delta):
	
	if not is_on_floor():
		velocity += get_gravity() * delta
		in_air = true
	else:
		if in_air:
			in_air = false
			if velocity.length() > 0:
				if crouching:
					animation_player.play("crouch walk")
				elif running:
					animation_player.play("sprint")
				else:
					animation_player.play("walk")
			else:
					animation_player.play("idle")
		
	
	# Handle sprinting and crouching
	running = Input.is_action_pressed("sprint")
	crouching = Input.is_action_pressed("crouch")

	if crouching:
		SPEED = crouching_speed
	elif running:
		SPEED = sprinting_speed
	else:
		SPEED = walking_speed

	# Handle jump
	if Input.is_action_just_pressed("jump") and is_on_floor():
		timer.start()

and this bit

func _on_timer_timeout():
	if animation_player.current_animation != "jump":
			animation_player.play("jump")
			velocity.y = JUMP_VELOCITY
			in_air = true

is added at the very bottom

so with actually inserting that as a signal the delay works great now but no animation plays

func _on_timer_timeout():
	if animation_player.current_animation != "jump":
			animation_player.play("jump")
			velocity.y = JUMP_VELOCITY
			in_air = true

so is that bit saying if the jump animation isnt playing when the timer finishes start playing it? if so do i not want the animation to start straight away and the actual jump be delayed

I’m going to be honest and say i’m not too familiar with using animation player on it’s own (I’ve found that animation trees give a bit more overview).
If you could explain something for me it might help a bit but why do you need the line if animation_player.current_animation != "jump":

Or you could change the code to look something like this:

if velocity.y != 0:
    animation_player.play("jump")

(that’s the y axis should be handling up- or down force)

But I’m not really familiar with 3d programming so not sure if this is correct

ahh right see ive not even looked at that yet like im guessing theres just a lot of things i dont know about in godot yet that would make these things easier ill have a read into that now! and with regards to that line of code so i ended up with that through trial an error like i menioned before i just could not get the animation to play when jumping so i went through various different bits defining is on floor and is in air true/false

var in_air = false
func _physics_process(delta):
	
	if not is_on_floor():
		velocity += get_gravity() * delta
		in_air = true
	else:
		if in_air:
			in_air = false
			if velocity.length() > 0:
				if crouching:
					animation_player.play("crouch walk")
				elif running:
					animation_player.play("sprint")
				else:
					animation_player.play("walk")
			else:
					animation_player.play("idle")
# Handle jump
	if Input.is_action_just_pressed("jump") and is_on_floor():
		if animation_player.current_animation != "jump":
			animation_player.play("jump")
		velocity.y = JUMP_VELOCITY
		in_air = true	
if direction.length() > 0 and not in_air:
		if crouching:
			if animation_player.current_animation != "crouch walk":
				animation_player.play("crouch walk")
		elif running:
			if animation_player.current_animation != "sprint":
				animation_player.play("sprint")
		else:
			if animation_player.current_animation != "walk":
				animation_player.play("walk")

		visuals.rotation.y = lerp_angle(visuals.rotation.y, atan2(-input_dir.x, -input_dir.y), 0.15)
		velocity.x = direction.x * SPEED
		velocity.z = direction.z * SPEED
	elif not in_air:
		if crouching:
			if animation_player.current_animation != "crouch":
				animation_player.play("crouch")
		else:
			if animation_player.current_animation != "idle":
				animation_player.play("idle")

so between all them bits is how i ended up getting the jump and the animation to work ill try and remember how i originally had the script that was basically just for movement before trying to add the animations but yeah it was fine with walking,crouching,running etc but when i tried adding jump if just didnt want to do it and ended up with all that nonsense above haha but gurantee theres someone who knows loads more and will know a method to do it without being as much of a mess

did you get to work? lol

extends CharacterBody3D

@onready var camera_mount = $"camera mount"
@onready var animation_player = $visuals/test/AnimationPlayer
@onready var visuals = $visuals


var SPEED = 2.0
const JUMP_VELOCITY = 4.5

var walking_speed = 2.0
var sprinting_speed = 5.0

var running = false

@export var sens_horizontal = 0.1
@export var sens_vertical = 0.1

func _ready():
	Input.mouse_mode = Input.MOUSE_MODE_CAPTURED

func _input(event):
	if event is InputEventMouseMotion:
		rotate_y(deg_to_rad(-event.relative.x*sens_horizontal))
		visuals.rotate_y(deg_to_rad(event.relative.x*sens_horizontal))
		camera_mount.rotate_x(deg_to_rad(-event.relative.y*sens_vertical))


func _physics_process(delta):
	
	if Input.is_action_pressed("sprint"):
		SPEED = sprinting_speed
		running = true
	else:
		SPEED = walking_speed
		running = false
	
	# Add the gravity.
	if not is_on_floor():
		velocity += get_gravity() * delta

	# Handle jump.
	if Input.is_action_just_pressed("jump") and is_on_floor():
		velocity.y = JUMP_VELOCITY

	# Get the input direction and handle the movement/deceleration.
	# As good practice, you should replace UI actions with custom gameplay actions.
	var input_dir = Input.get_vector("left", "right", "forward", "backward")
	var direction = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
	if direction:
		if running:
			if animation_player.current_animation != "running":
				animation_player.play("running")
		else:
			if animation_player.current_animation != "walking":
				animation_player.play("walking")	
			
		visuals.look_at(position + direction)
			
			
		velocity.x = direction.x * SPEED
		velocity.z = direction.z * SPEED
	else:
		if animation_player.current_animation != "idle":
			animation_player.play("idle")
		
		velocity.x = move_toward(velocity.x, 0, SPEED)
		velocity.z = move_toward(velocity.z, 0, SPEED)

	move_and_slide()

thats roughly how it started out i think but just been trying different things and adding stuff just like adding little bits for the camera controls and stuff etc seeing what i can do but its a bit of a mess now haha but no unfortunately not yet haha probably be a lot easier now to start from scratch and maybe watch some tutorials see if other people have done it issue is most of the time i dont remember it the next day if im just watching/copying some do it haha

Thats how i learned, but i would suggest asking for help from someone more experienced. Good luck

yeah definitely think thats the way to go rather than spend 10x the time to figure out a way 10x worse to do it aha also other problems i havent figured out yet since getting the jump and animation to play like now i have no directional control when in the air where as i did before the jumping animation but cheers anyway still learnt some good things from your guys suggestions

Try replace this


For this:

	# Handle jump
	if Input.is_action_just_pressed("jump") and is_on_floor():
		animation_player.play("jump")

And do what i said here: