Unpausing with the jump button makes the player jump at the same time the game is unpaused

Godot Version

v4.7.2.stable.official [ed1daf0bf]

Question

In my game, currently, the jump button also serves as the confirm button, and I’m planning to add more advanced control mappings that would allow for primary and secondary bindings. There is however a problem with the pause screen, which is described in the title. I assume if I were to map the confirm button to a key that does something else, like attacking, the player character would do that other extra action when unpausing. Currently I have this problem sorta solved by having await get_tree().create_timer(0.01).timeout before actually unpausing the game, but recently I learned that awaiting isn’t the best way to do stuff and also timers like these can be unreliable. So like… is there a better way to solve this problem?

This is where _unhandled_input could help, if you can place jumping inside that function instead. Otherwise adding a short transition like a fade-out after unpausing or even waiting one frame await get_tree().process_frame isn’t so bad.

First one doesn’t work for me at all, second one kinda works arbitrarily, sometimes it does, other times it doesn’t. As for fadeouts, I don’t even know how it would work in my game because the paused game is literally the game screen but darkened, with the pause menu slapped on top. There kinda isn’t anywhere to put the fadeout, unless I’m misunderstanding something

Depends on how you are handling the pause menu too, if you are using a Button then _unhandled_input should work, otherwise you will have to consume the input manually with accept_event() on Control node or get_viewport().set_input_as_handled() on other nodes. Eitherway _unhandled_input should be the go-to input processing function because it allows these kinds of interrupts to prevent an input from walking all the way up the tree.

I presume your pause menu is a scene, you could add an AnimationPlayer to fade out the “darkened” game screen, then unpause the game once it’s finished animating.

Yes I am using a Button so why the hell doesn’t it work? There is a possibility that it could be because I have my own CustomButton class that inherits from BaseButton, but like, I didn’t override any press functions, so I don’t even know

Okay so a combination of accept_event() and putting my movement inputs into _unhandled_input seems to fix this problem but it creates a new one: for whatever reason, my character doesn’t jump as high anymore compared to when the inputs were in _physics_process??? Why is that?

Post some code! Maybe a sample of the previous jump too if the comparison is needed.

The Input helper functions like get_vector and get_axis will not take input handling into account.

Maintain a paused flag. Don’t run the jump code if the flag is set regardless of input. Clear the flag deferred in the button input handling code.

So here’s basically the entirety of the inputs func

func movement_inputs() -> void:
#region Jumping
	
	if Input.is_action_just_pressed("jump"):
		if !jumped:
			if is_on_floor() or coyote_timer > 0:
				if !Input.is_action_pressed("ui_down"):
					jump_buffer_timer = JUMP_BUFFER_TIME
				else:
					set_semisolid_fallthrough(true)
		
		elif PlayerStats.energy > MIN_GLITCH_ENERGY and jump_buffer_timer <= 0:
			glitch()
	
	if is_on_floor() and jump_buffer_timer > 0:
		velocity.y = -jump_force
		jump_buffer_timer = 0
	
	# glitch prep
	if Input.is_action_just_released("jump") and !jumped:
		jumped = true
		if velocity.y < -jump_height:
			velocity.y = -jump_height
#endregion
	
#region Interaction
	
	if Input.is_action_pressed("ui_up") or Input.is_action_pressed("ui_down"):
		var collider: Area2D = $NpcDetectionRay.get_collider()
	
		if Input.is_action_just_pressed("ui_up"):
			if is_on_floor():
				ladder_buffer_timer = LADDER_BUFFER_GROUND
			else:
				ladder_buffer_timer = LADDER_BUFFER_AIR
		
		if (Input.is_action_pressed("ui_up")
				and (collider and collider.get_collision_layer_value(8) == true
				and collider.overlaps_area($InteractBox))
				and (!is_on_floor() or (is_on_floor() and ladder_buffer_timer > 0))):
			get_on_ladder(collider)
		
		if Input.is_action_just_pressed("ui_down"):
			if !is_on_floor():
				set_semisolid_fallthrough(true)
			
			if collider and collider.get_collision_layer_value(9) == true:
				get_on_ladder(collider)
		
		if is_equal_approx(input_vector.x, 0) and !looking_around:
			_enable_looking_around()
		
	if Input.is_action_just_released("ui_up") or Input.is_action_just_released("ui_down"):
		_disable_looking_around()
	
	if Input.is_action_just_released("ui_down"):
		set_semisolid_fallthrough(false)
#endregion

Normally it sits at the very top of state_move(), which is in _physics_process and mostly takes care of the animations:

func _physics_process(delta: float) -> void:
	if allow_input:
		input_vector = get_input_vector()
	
	position.x = round(position.x)
	position.y = round(position.y)
	
	if is_affected_by_gravity:
		if velocity.y < 0:
			velocity.y += GRAVITY * delta
		else:
			velocity.y += GRAVITY * delta * fall_gravity_multiplier
	
	if input_vector.x == 0:
		if is_on_floor():
			velocity.x = lerp(velocity.x, 0.0, ground_friction)
		else:
			velocity.x = lerp(velocity.x, 0.0, air_friction)
	
	match movement_mode:
		MovementModes.REGULAR_MOVEMENT:
			state_move(delta)
		MovementModes.HURT:
			state_hurt()
		MovementModes.DEAD:
			state_dead()
		MovementModes.LADDER:
			state_ladder()
		MovementModes.GLITCH:
			state_glitch()
	
	move_and_slide()
	
	velocity.x = round(velocity.x)
	velocity.y = round(velocity.y)
	
	if is_on_floor():
		rotation = move_toward(rotation, get_floor_normal().angle() + PI / 2, 0.05)
		
		jumped = false
		coyote_timer = COYOTE_TIME
		
	else:
		rotation = move_toward(rotation, 0, 0.05)
		
		if coyote_timer > 0:
			coyote_timer -= delta
		
		if jump_buffer_timer > 0:
			jump_buffer_timer -= delta
	
	if ladder_buffer_timer > 0:
		ladder_buffer_timer -= delta

If moved to _unhandled_input, it reduces jump height very slightly, barely noticeable until you need to jump on a 4-tile-tall platform and suddenly you can’t anymore

My gods, why does it have to be so hard with so many redundant flags? How would this flag be any different from simply checking if the tree is paused or not?

It’s not different, although you typically don’t want to pause the tree when the game is paused. Some processing always needs to happen. Flags are bread and butter of programming. A typical game may have hundreds of them. Learn to apply them with confidence to solve simple problems like this.

You can likely solve this by setting things up to play nice with engine’s input handling order, but a mutual-exclusion flag is bulletproof and will work regardless of how the input is handled.

Wait, what do I want to do with it then? Doesn’t the paused property exist specifically to pause execution of the game?

The actual jump force happens on release, can you move only the pressed part to _unhandled_input? That should edit the velocity exactly the same, thus the same jump height.

I do strongly disagree with normalized on writing your own paused variable, the engine’s works fine and handles processing certain things and their children through their process_mode property.

It doesn’t have to be called paused. It can be called player_input_enabled or whatever it actually switches.

I’d personally handle this solely via engine mechanisms but when people have problems with understanding simple switching logic, implementing things like this is good exercise. So I’d recommend OP makes it work with a custom flag, and then we can discuss the finer points of how to handle it elegantly in accord with engine’s input hierarchy and node processing.

I actually do have one like that, it’s called allow_input and currently serves to prevent player from moving during screen transitions when the game technically isn’t paused (I mean it’s paused during most of them but not when transitioning to a stage, for the stage title card to be able to pause correctly)

I think they’d end up with their initial solution but with player_input_enabled instead of paused

I also don’t think it’s great practice to make a bunch of flags; They do already have quite a few, and superfluous flags are something state machines are supposed to reduce.

If a delay is desired then it should come from an animation, I don’t think it’s a particularly bad solution in this case, especially as I’d recommend the animation any ways. snowystoat’s on the right track by detecting random await create timers as a nasty code smell. There is probably a route re-working your inputs to feed through the input handling system, but with the state machines splitting it sounds messy and yet more annoying to build on.

The problem likely happens because the flag condition check happens on the same frame the flag is switched, only a bit later. Changing the value deferred would avoid that and need for any awaits/timers.

I don’t think a delay is really desired in my case

I tried doing this and while it does restore max jump height it also makes the jump floatier overall (as well as ruins my nicely structured code where the rest of the function takes care of the animation and then BAM there’s an input bit smack dab in the middle)

I hoped this would work but no, he still jumps every now and then even if it’s set deferred

What happens if you disable coyote/buffering?

Interesting, can you share how this code looks? I can’t imagine it changing jump height that much, I assumed the slight reduction in height would be from gravity being applied after setting velocity.y for one frame, just a slight operation re-ordering error. You’re not applying gravity as part of the input function right?

Same thing

Okay nvm I tried it again and it works just fine, maybe I accidentally pasted it in the wrong place the first time

Okay so currently I have both setting pause deferred and the inputs func moved to _unhandled_input. This seems to have fixed the problem. Thanks a ton everyone

You shouldn’t really need both. As an exercise, if you’re willing - try to make it work both ways separately.