[[ Part 2: Analysis ]] I want the character to be able to engage in sprinting while on the ground or in the air. To me, it doesn't feel consistent to have control in the air like you do in the ground when there is an exception for sprinting, so perhaps my code should be in the Move state, as you had hinted. This was until I decided that I wanted to maximize performance by keeping this input check outside of the Idle state, where the input would do nothing, thus checking for it would be wasteful. The sprint input is only intended for some of the children state of Move, so I will write the function once in Move and check the input's status in every frame in those specific states.
[[ Part 3: Implementation ]] First, I added an exported variable ("max_speed_sprint" a Vector2(1000.0, 0.0) ) to the move state, as it is a value that should be modified with the others. I could define it in Run and Air so that I could control it independently, but I want to keep things consistent in both cases. I will paste the function in its own comment. It will likely not keep the indentation or the new lines.
# Handle the sprint input.
max_speed.x = max_speed_sprint.x
max_speed.x = max_speed_default.x
Then, within physics_process, I simply call the function in both of the states using move.get_sprint_input(). Finally, in the Idle state, I ensure that move.max_speed = move.max_speed_default.
[[ Part 4: Extra Challenge ]] Acceleration is the easy part, indeed, but deceleration is in fact more work. Not only will I need to add code for decelerating my speed, but I also need to ensure that my maximum speed doesn't immediately snap to the maximum speed, as well. Both should decrease according to a common deceleration rate until they are below the threshold. This should all take place in the Move state.
[[ Part 5: Extra Challenge Implementation ]] So, first, I wanted an exported deceleration_default and a deceleration variable, for extensibility purposes in case more states ought to be added. I decided to add a slowest_speed which functions as the trigger to snap the horizontal speed to 0, which I would ideally use to trigger moving to an idle state. Then, I made modifications to the Move state's calculate_velocity() function to take in a deceleration Vector2. The added code looks like this:
var new_velocity: = old_velocity
new_velocity += move_direction * acceleration * delta
if abs(move_direction.x) == 0:
new_velocity.x -= sign(new_velocity.x) * deceleration.x * delta
if abs( new_velocity.x ) < slowest_speed:
new_velocity.x = 0.0
This required adding the move.deceleration argument to the Air state as well as adding the deceleration variable to an earlier call to calculate_velocity() in Move's physics_process(). After this, get_sprint_input() needed to be modified similarly. Only one line was necessary: max_speed.x = max( max_speed_default.x, max_speed.x - ( deceleration.x * delta ) ) in that second else block. Then I added the delta argument to the function as well as the arguments in which it called.
The final piece of the puzzle was in the conditions in which we transitioned to and from these states, which was admittedly caught during testing. In the run and air states, the condition for transitioning to the idle state depended on the current movement direction, when it really should have depended on the active velocity, as well.
The end result was almost perfect, but I noticed that somehow jumping while moving horizontally was causing the horizontal vector to twitch a little. The fix was in modifying a line in the enter() function that made the jump velocity calculations only apply to the vertical aspect of the vector: `move.velocity.y += calculate_jump_velocity( msg.impulse ).y`. I believe it was because of the added code about deceleration and the passing of Vector2.UP that led to this issue. So, by only applying the y-axis calculations to the y-axis part of the vector, the horizontal movement remains smooth and unperturbed while jumping.
I'll happily accept feedback for this implementation if offered.
Thank you for the feedback. I'll stop sweating such small stuff, then. If you make a video on using a profiler with Godot, I'll happily watch it. Thanks for this course. It's great, so far.