In this video, you’ll learn how to implement
a swimming mechanic. This particular style of swimming will cause
the player to float up towards the surface without being pushed out of the water. You’ll also learn how to alter the character’s
movement underwater to better convey the feeling that they’re swimming. Howdy, and welcome to Game Endeavor. Learn practical dev skills and improve the
quality of your games by subscribing and ding-a-linging that bell. There are many different ways to handle swimming
in a platforming game. With the style that I’ll be showing you
in this video, the player will passively float upwards, but will be able to directly control
their movement using the x and y axis controls, or even the jump button if you prefer. To keep the player controller from becoming
a confusing mess of spaghetti code, we’ll be using a state machine to manage the flow
of the logic. I have a short video on creating a simple
state machine if you haven’t brought your own, iCard in the corner. Once you’re ready, let’s go ahead and
get started. How you choose to implement water in your
game can be as varied as any other choice you will make as a developer. Each with their own advantages and disadvantages. For this tutorial, I will be using a TileMap
to layout collisions and sprites for the water. Alternatively you can use Area2Ds if you prefer. In my TileMap, I have created my water tiles,
and they have collisions. The player will not be colliding with the
collisions, but we will be needing them to detect the water. The TileMap will be on its own collision layer
that I have designated as water. To determine whether or not our character
should be swimming, we need something to detect the TileMap. We’ll be checking a specific point for water
collision, and we’ll need a way to designate the point that we’re going to check. For this I’m going to use a Position2D node
named SwimLevel. The y location of this node will determine
where the water comes up to the player. Consider this while making your swimming animations. Inside of our player script, we can go ahead
and cache the node into an onready variable named `swim_level`. Probably the most important part of adding
a swim mechanic, is being able to determine whether or not the character is in water. We’re going to create a simple little function
that will check for this, which will be called `is_in_water()`. In this method, we’re going to do something
similar to a raycast, except we’re only going to check a specific point. So to do this we’re going to need the world’s
direct space state, which we’ll get by saying `var space_state = get_world_2d().direct_space_state`
So far on the channel, I’ve shown you how to use the space state to perform raycasts
and shape intersections, but there’s another useful option that the space state provides
us with. That being the intersect_point which will
return an array of collisions at a given point. We’ll call it by saying `var results = space_state.intersect_point(`
The first parameter is going to be the point where the space state checks for collisions. We’re checking if we’re in water, so we’re
going to use `swim_level.global_position` as the point. The next parameter is the maximum number of
results you want this to return. We only need one, because there’s either
water, or there isn’t. The next parameter is an array of what you
want to exclude from the cast, which we don’t need, so we’ll pass in an empty array. And then we have to pass in what collision
layer we want to mask for when performing the point intersection. This will be what collision layer you’ve
set for your water. Personally I don’t want to use a magic number
here. I would like to be able to see what layer
I’m referencing. What I generally do with collisions is to
make a script that stores them for me. So I’m going to make a script called `CollisionLayers`
giving it a class name so that we can reference it anywhere in our project. We don’t need to autoload it, since we’re
only going to be storing constants here. And the way we’re going to set this up is
by creating some constants for whatever collision layers we’ve defined in our game. We can use binary shifters to keep our values
neat and clean and easy to maintain. When using binary shifters, the value on the
right is what’s used to determine the layer of the collision. You can get this value by going to your inspector,
finding the collision_layer property, hovering over the layer you want, and using the `Bit`
value. You’ll notice that this increments by 1
from 0 to 31. Which fun fact, using this method you can
get an extra 12 layers for a total of 32 as opposed to the 20 that you’re limited to
by the inspector. A little trick you can use if you ever run
out of layers. With that out of the way, we can return to
the method in our player script and plug it in by passing `CollisionLayers.WATER` as a
parameter. If you’re using an Area2D for your water
instead of a TileMap like I am, then this method has two more additional parameters
for whether or not you want to detect bodies and areas. These two lines of code will be used twice
more in this video, so I’m going to go ahead and copy them just to make this whole tutorial
making process easier for myself. And then to finish off this function, we want
to `return results.size() != 0` which will return true if we detected water, false if
not. This is what we’ll use to enter and exit
the swim state. Now we need to handle the logic for swimming. We’ll start by moving our character along
the y axis. To do this I’m going to be using three different
speeds. One for passively moving upwards, one for
actively moving upwards, and another for actively moving downwards. We’re going to store these as constants. One being `const SWIM_UP_SPEED` which I will
set to -3 * 96. 96 being the size of my tileset. This means that at max speed, the character
will move 3 tiles upward per second. Next we’ll define `const PASSIVE_SWIM_UP_SPEED`
which I will set to -1 * 96. And then we’ll define `const SWIM_DOWN_SPEED`
and I will set mine to 3 * 96. 3 being positive, which will cause the character
to move downwards. Once we start plugging logic into our state
machine, we’re also going to modify our horizontal speed for swimming, so while we’re
here, we’re going to create another const named `SWIM_SPEED_HORIZONTAL` which I will
set to 3 * 96. Rather than applying gravity to our player
while swimming, we’re going to apply a more linear velocity to them, but based on their
input. We’re going to create a function for handling
this called `_apply_vertical_swim_velocity():`. Which will have an argument named delta. We’ll start off by getting the player’s
vertical input. If you’ve followed along with the platformer
series, we’re going to do this much like we got our horizontal input by saying `var
input = -int(Input.is_action_pressed(“move_up”) || Input.is_action_pressed(“jump”)) +
int(Input.is_action_pressed(“move_down”))`. This will set input to a value of -1 if the
player is pressing up or jump, 0 if there is no input or they’re pressing both up
or jump and down, and 1 if they’re pressing down. Next we’ll define a variable for how fast
the player should move along the y axis, which we’ll name `y_speed`. We’re going to initialize this to `PASSIVE_SWIM_UP_SPEED`,
which is what it will be if there’s no input. Then we can check `if input < 0:` meaning
that the player is pressing up, then we’ll set y_speed to `SWIM_SPEED_UP`. `elif input > 0:` meaning the player is pressing
down. Then we’ll set y_speed to `SWIM_SPEED_DOWN`. This is the maximum speed to move the player
along the y axis. If we were to set this value immediately then
it wouldn’t have much of an underwater feel. So instead we’re going to lerp towards this
value so that it gradually gets set. We’ll say `velocity.y = lerp(velocity.y,
y_speed`. We’re going to use a somewhat low weight
here, being 0.75. Since we’re using lerp again, allow me to
address something that was pointed out to me some time ago. When using lerp like this, it doesn’t take
delta into consideration. Meaning that if the game were to lag, then
this will quickly become inaccurate. However, it turns out that if we apply delta
to the weight, we can fix this. But as you can imagine, this will drastically
alter how the weight behaves, because delta is generally a small fraction, typically 1/60th. We can fix this by dividing by 1/60th, ensuring
to make the 60 a float. I’m using 1/60th here because ideally my
game will run at 60 frames per second. If you’re running your game at a different
ideal frame rate, then you will want to use a different value. With this knowledge at your disposal, you
should apply it to your horizontal lerping as well. I am using `move_and_slide_with_snap()` for
my movement. To handle the snap vector, I am setting it
to some downward Vector2 if the character is not jumping, otherwise I set it to Vector2.ZERO. I have a variable for this called `is_jumping`
which I set to true when the character jumps, and false otherwise. This is what keeps Paw Bearer stuck to the
floor when moving along slopes and moving platforms, and it also turns the ground into
a death trap underwater. We need to adjust is_jumping for our underwater
needs. I’m going to assume that we don’t need
this while underwater, and just set is_jumping to true. This will get our character moving how we
want, but there’s an issue we need to address first. This will push our character up towards the
surface, and eventually out of the water to leave the swim state, only to fall back into
the water, and repeat. We need a way to prevent the player from getting
pushed completely out of the water. We’re going to create a function for handling
this named `_handle_surfacing()` and it will need delta as an argument as well. We’ll say `if velocity.y < 0` because we
only want to perform this logic whenever we’re moving upwards, since there’s no point in
doing it downwards. We need to detect the surface, but we can’t
just cast a ray upwards because you may be using a TileMap to lay out your water. In which case, casting a ray upwards would
detect each water tile, despite being deep underwater. Instead we’re going to look to where the
player is going to be when they move next. If there isn’t water there, then we’ll
cast an arrow down towards the player to find the surface. Remember the code snippet that we copied earlier? We’re going to paste it here to do another
`intersect_point()`. We need to change where we cast the point
though, because we want to cast where the player will be once they move. So we’ll create a variable named `surface_level`. We’re going to set this to the swim_level’s
global_position like before, but we’re going to add `Vector2.DOWN * velocity.y * delta`
to it, which is the distance the player will move upward if unaltered. Then we’ll pass our new `surface_level`
variable into the `intersect_point()` method. We want to know if there isn’t water here,
so we’ll say `if !results:`. After which we’ll want to cast our raycast,
so we’ll say `var ray_result = space_state.intersect_ray(`. We want to cast from above the surface of
the water down to our player, so we’ll pass in `surface_level` as the from vector. Then `swim_level.global_position` as our to
vector. We don’t need any exceptions so we can pass
in an empty array. Then finally we can use `CollisionLayers.WATER`
as our collision mask. If you’re using areas instead of bodies
for the water, then be sure to set it up here as well. This raycast will hit the surface of the water,
and if we’re even casting it then it’s practically guaranteed to hit, but sometimes
I can be a little too cautious, so I’m going to say `if ray_result:` juuust to make sure
we don’t get any errors. The reason we’re casting this ray is to
get the surface position of the water so that we can modify our character’s velocity so
that they move directly to it and not past it. So we’ll go ahead and set our character’s
y velocity. We’re going to get the displacement of the
two positions by saying `(ray_result.position.y - swim_level.global_position.y) which is the
distance along the y axis that the character needs to move in order to get there. However, when we call `move_and_slide()` later,
it’s going to modify velocity by delta so that the movement is consistent over time. Thus we need to divide this displacement by
delta, then sit back and giggle when Godot multiplies it by delta to get the number we
originally wanted. This handles our movement along the vertical
axis, and we’re almost ready to head into the state machine to connect up our logic. Right quick though let us deal with horizontal
acceleration and deceleration as well, it won’t take but a second. If you follow along with the platformer series,
then we use lerping for our horizontal movement. Specifically we have a `get_h_weight()` method
to determine how we should be applying the weight to our lerping. Inside of this method, all we need to do is
add a condition (which we will define in our state machine in a moment), saying `if state_machine.is_swimming()`. state_machine just being where we cached our
state machine node. If this is true then we want to return a low
value, something that feels somewhat sluggish but not unresponsive. I will be using 0.05. This should be before your `is_on_floor()`
check so that it doesn’t get cancelled out by swimming along the bottom of the floor,
and change that one to an elif if you’re particular. We can finally head on over to our state machine
and start plugging in the logic. We’re going to be adding a new state, so
`add_state(“swim”)`. We’re only adding one state because swimming
up and down is easy to keep in one state at the moment, and I don’t want to over complicate
it. Before I forget, let’s add in a method at
the bottom of the script saying `func is_swimming():`. It just needs to `return state == states.swim`. We’ll be adding our logic to our `_state_logic()`
method, so create a condition for it saying `elif state == states.swim:`. Inside of this if condition we want to say
`parent._handle_movement(parent.SWIM_SPEED_HORIZONTAL)`. `_handle_movement()` is the method in my player
script that I use for setting the movement along the y axis. It takes in a speed value which is what allows
us to slow the player down while swimming. We’ll then say `parent._apply_vertical_swim_velocity(`. Pass in the delta parameter. And then `parent._handle_surfacing(delta)`. With the logic implemented, we then want to
transition to and from the state. We’ll go into our `states.idle` statement
and add a condition checking if `parent.is_in_water()`. If this is true, then we want to `return states.swim`
so that the state machine will transition to our state. Copy this condition, because we also want
to use it inside of our run and fall states. If you ever think the player can jump into
water (such as if the water can rise up while they’re jumping) then you may want to add
it to the jump state as well, go nuts. Transitioning out of the water is a little
different however. First we’ll handle the simple condition,
saying `if !parent.is_in_water():`. Then return states.idle. This happens if you have slopes or moving
platforms in your water and they push your player out of the water. But perhaps you want to allow your player
to jump out of the water when near the surface. Well we need a way to determine if they’re
close enough to the surface. So quickly, run back to your player script,
and we’ll make a function named `can_jump_out_of_water():`. Paste in the code from earlier, but to it
we’re going to add a distance upward. This distance will be how close to the surface
you want the player to be able to jump out of the water. I will use 32 pixels by saying `+ Vector2.UP
* 32`. When returning whether or not we can jump,
first we want to make sure that `velocity.y <= 0` meaning that the player isn’t swimming
downwards. Feel free to remove this if your character
is a skipping stone. Then we’ll add `&& results.size() == 0`,
meaning that there is no water detected from the `intersect_point()`. We also need to set a velocity to our player
so that they jump out of the water. So we’ll go to the top of our script and
create a constant named `JUMP_OUT_VELOCITY` which I will set mine to -1200. If you want to learn how to set a specific
height to this jump, then watch my video on how you can set a jump height and duration
using equations of motion. We can go back to our state machine now and
setup the transition that will allow us to jump out of the water, by saying: `elif (Input.is_action_pressed(“move_up”)
|| Input.is_action_pressed(“jump”)) && parent.can_jump_out_of_water():` This allows the player to press either of
these keys to jump out of the water. You can use whichever you want or both. Since we’re only using one state for swimming
and not several different states, we need to update our animations for swimming the
old way. So at the bottom of my `_state_logic()` method,
I’m going to say `if state == states.swim:` because we don’t want to call swim animations
during the jump state. `parent._update_swim_animations()`. Which we’re going to create right now as
we moonmosey back on over to our player script. We’ll create the method, saying `func _update_swim_animations():`. We’re going to be checking some conditions
here to see what animation we should be using, which we’ll store in a variable named animation. I will default to “swim” so that it if
none of these conditions are triggered, then the character will do normal swimming animation. We want to get the input that the player is
pressing so that we can tell if the player is actively swimming upward. I’m just going to steal this from our `_apply_vertical_swim_velocity()`
method and plop it down here. `if input < 0:` then the player is pressing
up, so we should set animation to “swim_up” which is an animation I have defined for swimming
upward. If you want to use a downward swimming animation
then you would add another if condition checking if input > 0. I also have an animation for treading water
that I would like to use, which is where the character is not moving and is floating near
the surface. However due to issues with using lerping for
movement in 3.1, checking if our velocity is zero will not return true. Instead we need to check `if abs(velocity.x)
< 64.0` which will check if the character is moving slow enough for our liking. 64.0 is a variable of my choosing, you’ll
need to play around with it for yourself and find what is best for your game. Then we’ll add to this `&& can_jump_out_of_water():`
which will ensure that our character is close enough to the surface to jump. If this is true, then we can set `animation
= “tread_water”` which is what I’ve named my animation. And then, once we’ve picked our chosen state,
we can set it to the animation player, but first we’ll say `if animation_player.assigned_animation
!= animation` which will check that it’s not the current animation already, otherwise
it will keep restarting the animation. Then `animation_player.play(animation)`. If you want to learn more practical dev skills,
then watch this video on implementing wall jumping in your game, and if you’re new,
then join the sub-club to get notified for future tutorials.