Tilting on Slopes
In the tutorial about handling slope movement, @intelligenced asked about tilting the character parallel to the slope. So I decided to make a quick experiment reusing the Slope Movement, which also just got updated with a small refactoring to encapsulate the moving logic into a _move
method.
Now in this experiment, I’ve approached tilting the character based on the slope’s angle. But how to get the slope angle, you might be thinking, right? We’ll see in this new tutorial.
What is a slope?
Well, the first thing to understand when we are dealing with a slope is…what a slope is?
The cool thing about programming and game development especially is that things are what you want them to be. That’s what a variable or a method declaration is, you name something and describe what this thing is.
In our context, the KinematicBody2D’s move_and_slide_with_snap
allows us to pass an argument (floor_max_angle
) to describe when the floor stops…being floor. In the documentation, it states that:
floor_max_angle
is the maximum angle (in radians) where a slope is still considered a floor (or a ceiling), rather than a wall. The default value equals 45 degrees.
It’s very interesting how programming is essentially a philosophical debate. Imagine you’re debating how an object should move on a slope and the person asks you “What is a slope?” you argue (give an argument) that a slope is a part of the floor or ceiling with an angle lower than 45
degrees. Now that this person knows the definition of a slope, you can keep your debate. The same thing goes here.
In our case, our definition of a slope uses the value from a const that I’ve declared right below the SNAP_LENGTH
. I’ve called it SLOPE_LIMIT
because beyond this angle the KinematicBody2D can consider that it is handling a wall. I’ve set the SLOPE_LIMIT
to be 46
degrees because I want to consider slopes up to 45
degrees.
I’ve used the deg2rad
built-in function to convert degrees to radians since the floor_max_angle
works in radians.
extends KinematicBody2D
const FLOOR_NORMAL = Vector2.UP
const SNAP_DIRECTION = Vector2.DOWN
const SNAP_LENGTH = 64.0
const SLOPE_LIMIT = deg2rad(46)
This is something that also got to the Slope Movement latest update.
Getting the Slope angle
Do you know how collisions work in Godot? Well…neither do I, but I do know that we can have access to a lot of information regarding them. But here comes the trick…we better not use the KinematicBody2D’s get_slide_collision
because it doesn’t always update us since the character doesn’t always slide. Here is how this implementation behaves:
We need a reliable way to always be updated about the floor’s angle. And for that, I present you [RayCast2D](https://docs.godotengine.org/en/stable/classes/class_racom aycast2d.html). A RayCast2D allows us to constantly check the floor. It does that by casting a Vector2
and checking for physical bodies, or even areas. If it intersects with one of those it immediately triggers a collision and stores data about this collision.
One advantage of RayCast2Ds is that they don’t accumulate collisions, so if there are 3 possible intersections between the casting point and the target point, it only triggers the first collision.
To tell the RayCast2D where it should cast to…we use the cast_to
property. I love how explicit Godot is. In this experiment, I’ve used a value of Vector2(0, 32)
.
But why all that? WHY?! Why are we still here, just to suffer?
Turns out that, as I’ve mentioned, the RayCast2D stores information about the current collision, including the most meaningful for us in this context: the collision normal.
Using the get_collision_normal
method we can…get…the collision’s normal…see? It’s not even funny. You try to explain the thing and it’s pointless because Godot’s engineers did it already. So here goes the code, guess by yourself.
func _tilt():
if is_on_floor() and raycast.is_colliding():
var normal = raycast.get_collision_normal()
Ohh, wait. I forgot to tell what a collision normal is right? So, briefly, a collision normal is a vector that only contains the collision’s direction data, its values can range from -1 to 1 and it always has a length of 1
, meaning it is a unitary vector. You can check the Godot documentation about working with vectors for more information.
Why having the collision normal is so important? READ THE HEADER!
Yes, using the collision normal is the secret to get the slope’s angle. But how?! How a vector will give me an angle? You might be asking yourself, right? Well, turns out that all Vector2
have a fun property of having an angle.
See, in Godot a Vector2
that points straight to the right, in other words Vector2.RIGHT
or rather Vector2(1, 0)
, independently from its length will always have an angle of…ZERO degrees! Yes, in Godot Engine we start measuring angles with Vector2.RIGHT
as base. From there, since vectors are essentially lines, you just need to know the angle between your Vector2
and Vector2.RIGHT
. For instance, Vector2.DOWN
, a.k.a Vector2(0, 1)
, gives us an angle of 90
degrees.
To know the angle of a Vector2
you can simply call angle()
on any Vector2
and it returns an angle in radians.
There we have it. Now that we know the slope’s angle, let’s tilt the character accordingly.
Tilting the character
Now that we know the slope’s angle we just need to rotate accordingly, right? Well…kinda.
See, this tilting, ideally, is just a visual cue for the player, in another post I’ll comment the design implications of that, but what you need to know is that you, ideally, shouldn’t rotate the whole KinematicBody2D
but instead just the visual components, in this case, the Sprite
.
BUT WAIT! You can’t simply apply the slope angle as the Sprite.rotation
. Had you forgotten how we count angles in Godot? Yes, with Vector2.RIGHT
being 0.0
degrees. And why is this a problem? Because unless your character is drawn like this:
It won’t rotate properly. So we need to add an offset angle…unless you really draw characters like that.
So, we are going to offset the slope’s angle by 90.0
degrees in order to properly tilt our character parallel to the slope. And this is how the tilt method looks like at the end.
func _tilt():
if is_on_floor() and raycast.is_colliding():
var normal = raycast.get_collision_normal()
sprite.rotation = normal.angle() + deg2rad(90)
For full context, you can download the Tilting on Slopes files. And we that we finished this tutorial.
You can discuss these topics and more at the project’s community. Also, you can make requests there for future experiments.
That’s it, thank you so much for reading. Keep developing and until the next time!
Files
Get Gamedev Experiments
Gamedev Experiments
A collection of gamedev experiments cut down to small digestible projects
Status | In development |
Category | Assets |
Author | Pigdev |
Genre | Platformer |
Tags | Creative, flat-shading, Game Design, godot-engine, Open Source, secrets, sourcecode |
More posts
- Selection BoxAug 06, 2020
- Selectable ObjectsJul 30, 2020
- Jump with SnapJul 24, 2020
- Jump and Fall Through PlatformsJul 23, 2020
- Path Follow PlatformsJul 15, 2020
- Moving PlatformsJul 10, 2020
- Slope MovementJun 27, 2020
Comments
Log in with itch.io to leave a comment.
I know it's been a while since you posted this, but would this translate to 3D?
Wow! that was fast. I was going to ask for it in the community. You have beaten me to it.
This is very very helpful. Exactly what I needed. Thanks a lot. I will be keeping a keen eye on your recipes.
Good stuff. Keep it going.
:D