All Questions

Community

B
Anthony Base

My Solution (other students, please attempt the challenge, first)

[[ Part 1: Input Map ]] First, I go to project settings to the Input Map to create a new input map named "move_sprint" for consistency. I will map the shift key as well as the X key (I have jump assigned to Up, Spacebar, and Z).
  • B
    Anthony Base replied

    [[ 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.

  • B
    Anthony Base replied

    [[ 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.

  • B
    Anthony Base replied

    ```

    func get_sprint_input():

       # Handle the sprint input.

       if Input.is_action_pressed("move_sprint"):

          max_speed.x = max_speed_sprint.x

       else:

          max_speed.x = max_speed_default.x

    ```

  • B
    Anthony Base replied

    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.

  • Nathan Lovato replied
    Thanks for the detailed explanation, it'll surely be useful to others. Seems good to me, I've only got one piece of feedback on the input part: > 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 You don't have to worry about a simple call like this performance-wise. Even for low end mobile phones, it's almost nothing. This line of thinking may lead you to write code that will cost a lot of development time in the long run. When it comes to performances, you always want to use a profiler instead, measure CPU/memory usage, and optimize the parts of your game that are actually slowing it down. Actually, calling an extra function 60 times per second in a specific state probably has a higher cost than checking for the input every frame in a function that will be called in any case. But again, the takeaway here is that these kinds of optimizations don't make a difference, so you shouldn't worry about them.
  • B
    Anthony Base replied

    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.