Impulse only applied after state is switched

Godot Version

I’m currently trying to make really rough prototype for a game in Godot 4.5.

Question

I’m currently trying to make a missile(rigidbody2d) that has different stages during it’s launch. For these different stages I’m using a finite state machine that uses child nodes for states(that I took from a YouTube video I can’t remember the link to). I only have two stages right now, a boost stage where the rocket is supposed to move slowly and follow the cursor and a cruising state where the missile zips off on it’s own. My issue is, anything impulses that I’m applying to the missile in the boost state doesn’t affect it, until the state switches to cruising where it seems all the impulses are applied at once.

The missile still follows the mouse in the boost state so I’m pretty sure it’s all running as intended. And even if there’s no impulses applied in the cruising state, the missile still zips off really quick once it switches states, leading me to assume that it’s the impulses I tried applying in the boost state hitting it all at once. Removing any impulses from the boost state also make the missile move as intended once the cruising state is activated.

State Machine code:

extends Node
class_name StateMachine

@export var inital_state : State

var current_state: State
var states := {}

# Called when the node enters the scene tree for the first time.
func _ready() -> void:
	# Look through all the states attatched to this state machine, checking if they're states
	for child in get_children():
		if child is State:
			# Recall: making the index to the state the name of the state
			states[child.name.to_lower()] = child
			child.Transitioned.connect(on_child_transmition)
			
	if inital_state:
		inital_state.Enter()
		current_state = inital_state
	print("states: ", states)
	print("initial state: ", inital_state)

# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta: float) -> void:
	# Check if there's a current state and run it's version of _process
	if current_state:
		current_state.Update(delta)

func _physics_process(delta: float) -> void:
	# Same as _process
	if current_state:
		current_state.Physics_Update(delta)

func on_child_transmition(state, new_state_name):
	# Checks if the state is correct
	if state != current_state:
		print("Error: given state isn't current state")
		return
	
	# Searches for and checks if the new state even exists
	var new_state = states.get(new_state_name.to_lower())
	if !new_state:
		print("Error: new state doesn't exist")
		return
	
	if current_state:
		#print("Leaving: ", current_state) # Debug line
		current_state.Exit()
	
	print("Entering: ", new_state) # Debug Line
	new_state.Enter()
	
	current_state = new_state

Boost state code:

extends State
class_name BoostState

@export var boost_force := 100
@export var move_speed := 10
@export var launch_delay := 1.0
@export var rotation_speed := 1.0

@onready var rocket: RigidBody2D = $"../.."

func Enter():
	var launch_vector = Vector2(cos(rocket.global_rotation), sin(rocket.global_rotation))
	rocket.apply_central_impulse(launch_vector*boost_force)
	
func Exit():
	pass
	
# Stand in for _process
func Update(delta: float):
	if(launch_delay <= 0):
		Transitioned.emit(self, "Cruising")
	launch_delay -= delta
	
	#follow/move towards mouse
	var mouse_pos := rocket.get_global_mouse_position()
	var rocket_pos := rocket.global_position
	
	var dx := mouse_pos.x - rocket_pos.x
	var dy := mouse_pos.y - rocket_pos.y
	
	var target_angle := atan2(dy,dx)
	
	rocket.global_rotation = lerp_angle(rocket.global_rotation, target_angle, rotation_speed)


# Stand in for _physics_process
func Physics_Update(_delta: float):
	var move_vector = Vector2(cos(rocket.global_rotation), sin(rocket.global_rotation))
	rocket.apply_central_impulse(move_vector*move_speed)

Cruising state code:

extends State
class_name CruisingState

@export var cruise_speed := 100.0
@export var detection_range := 500.0
@export var detection_angle := 30.0

@onready var rocket: RigidBody2D = $"../.."

func Enter():
	pass
	
func Exit():
	pass
	
# Stand in for _process
func Update(_delta: float):
	pass
	
# Stand in for _physics_process
func Physics_Update(_delta: float):
	var thrust_vector := Vector2(cos(rocket.global_rotation), sin(rocket.global_rotation))
	rocket.apply_central_impulse(thrust_vector*cruise_speed)

I feel a state machine like this is a bit much and if I can’t find a solution I may just scrap it for something a lot simpler. But I am very curious what’s going on here and any help would be very much appreciated.

I would guess the problem is caused by directly changing the RigidBody’s rotation? If you disable the line that rotates the rocket in the boost state, does that fix the issue for its acceleration?

Using a state machine here might be premature.
Before creating multiple scripts, you could work on a single one named “Missile” directly on the rocket. You can use a boolean or an enum to switch between the states you need. This will prevent you from being unsure if you are facing a behavior script issue or an issue with the state machine.

apply_central_impulse is designed for one-time impact, which is good when entering the BoostState; but on physic update it can cause issues. You might want to use add to linear_velocity or use apply_central_force instead.

I disabled the line that was controlling the rigidbody’s rotation and it seems to be working fine now. Not sure why controlling the rotation would cause this but good catch! Thank you so much