Updating the character

There are two improvements we can make to the character.

1. Animating the player character to make them look cool.
2. Improving the standard character controller.

Lets start with the second option, and improve the feel of the character.

## Jump Buffer

A jump buffer is a way to allow the player to jump even if their feet are not firmly planted on the ground. Call it a "forgiveness" for trying to jump when they are close to landing from a previous jump, but not quite there.

One way to achieve this is with a timer. When the player presses the jump button, the timer begins and continues to tick along for the set amount of time.

Every time the game loop runs, we can then check for if the player is on the ground, and if the timer still has time left in it we know they previously hit the jump button, and we can trigger the jump.

```
if Input.is_action_just_pressed("ui_accept"):
        ##start the timer, but don't jump yet...
		jump_buffer_timer.start()

if is_on_floor() && jump_buffer_timer.time_left > 0:
    velocity.y = JUMP_VELOCITY
    jump_buffer_timer.stop()
```

This can provide a very nice way of improving the feel of the character, improving the perception of responsiveness.

## Acceleration or Deceleration of Running

Our second way of improving responsiveness is to introduce acceleration and decelleration into the character controller.

First, we need a variable we can use for control of acceleration:

`@export var acceleration:int = 5 `

Now we can apply this factor to movement.

```
var direction := Input.get_axis("ui_left", "ui_right")
## Apply acceleration and deceleration to movement
if direction == 0:
    velocity.x = move_toward(velocity.x, 0, acceleration)
else:
    velocity.x = move_toward(velocity.x, SPEED * direction, acceleration)
```

The move_toward function accepts three values: from, to, and a "delta", in other words how much it should move by.
Since this function will be called repeatedly by the game loop, a higher delta will cause the time to accelerate or decelerate to be shorter.

## Hard Gravity

Sometimes in games it produces a better result for game mechanics to change the effect of gravity depending on the state of the player. As an example, we can have gravity affect the player more during the downward part of a jump (in other words while they are falling).

This is possible because during the game loop gravity is always being applied to the player character, pushing them downward at a certain force. When they jump, an opposing force (velocity) is applied that allows them to overcome the downward push of gravity. But the gravity eats away at that jump force until it is overcome, and the player begins to fall. This is the apex of their jump, after which they are no longer moving upwards but rather moving downwards.

That "falling" state can be detected by a simple check:

`if not is_on_floor() && velocity.y > 0:`

In this scenario, the player is not on the floor, but their velocity is greater than zero - in other words gravity has kicked in stopping their jump upwards movement and is now pulling them downwards. If that is the case, they are now falling and we can take the opportunity to apply a harder gravity.

Here is how we normally apply gravity:

`velocity += get_gravity() * delta`

All we have to do is use a different gravity when the player is in the falling state, which we detected as above:

`velocity += get_gravity() * hard_gravity * delta`

In our physics process loop, we are constantly applying the gravity. If the player is in the "jump" state we should apply normal gravity, and if they are in the "falling" state we should apply the hard gravity.

```
func _physics_process(delta: float) -> void:
	# Add the gravity.
	if not is_on_floor():
		velocity += get_gravity() * delta
```

To complete the function, we need to start keeping track of what state the player is in. This calls for a code pattern known as a **finite state machine**

Let's begin our state machine by using it to keep track of whether the player is in the "jump" state, or the "falling" state.

## State Machine

To begin, we can use an Enum to list out the possible states of the player. (You recall we used an Enum to list out the directions the player could be facing in the last lesson.)

```
enum State{IDLE, WALK, JUMP, FALLING}
var current_state: State = State.IDLE
```

Lets take another look at our Jump Buffer, implemented above. We have the perfect place to identify the JUMP state there.

```
if is_on_floor() && jump_buffer_timer.time_left > 0:
		velocity.y = JUMP_VELOCITY
		current_state = State.JUMP
		jump_buffer_timer.stop()
```

Note the addition of the state change to `State.JUMP`. That is the main thing we need!

```
if current_state == State.JUMP:
		#apply normal velocity
		velocity += get_gravity() * delta
		if velocity.y>0:
			current_state = State.FALLING
else:
	#apply hard gravity, character falling
	velocity += get_gravity() * hard_gravity * delta
	
	
```

Now if the player is in the "JUMP" state they get normal gravity, otherwise they get the hard gravity. 

This solution works well, but the issue right now is that our state machine is only tracking the JUMP state. We need to flesh it out with rules for changing state to our other possible states such as IDLE, and WALK.

For that reason, we should put the state handling into it's own function. A nice way to do this is using a `match` conditional, also known as a switch-case in other languages.

```
func update_states()->void:
	match current_state:
		State.IDLE when velocity.x !=0:
			current_state = State.WALK
		
		##player stops walking, or starts falling
		State.WALK:
			if velocity.x == 0:
				current_state = State.IDLE
			if not is_on_floor() && velocity.y > 0:
				current_state = State.FALLING
				
		##when jump peaks, we start to fall	
		State.JUMP when velocity.y > 0:
			current_state = State.FALLING
		
		##player lands, either still or moving
		State.FALLING when is_on_floor():
			if velocity.x == 0:
				current_state = State.IDLE
			else:
				current_state = State.WALK
```

Now that we are tracking all the states, a function to update the animation based on the state should be straightforward.

```
func update_animation()-> void:
	match current_state:
		State.IDLE:
			playerGraphic.play("idle")
		State.RUN:
			playerGraphic.play("run")
		State.JUMP:
			playerGraphic.play("jump")
		State.FALLING:
			playerGraphic.play("fall")
```