In this lesson, we will create a rock the player can push and roll over platforms.
We’ll later use that to code doors that open when you push a rock over a remote pressure plate.
It’s a good opportunity to explore how to make the character interact physically with an object by pushing it and how to use the last type of physics body: rigid bodies.
Rigid bodies automatically move and interact with somewhat realistic physics. They roll, push one another, slide, and more based on their physics properties.
In Godot, rigid bodies come in the form of the
node.Unlike the move_and_slide()
function.
Instead, we tweak physical properties such as mass and friction. We can then apply impulses or forces to it.
Note: With
, we don’t need to manually apply gravity because it is built into the node and handled by the physics simulation.Let’s start by creating the rock scene.
Create a new scene with a
named Rock as the root.Save the scene as Rock.tscn
and add two child nodes:
Assign the image asteroid.png
to the ’s Texture property.
Add a
to the and make the shape match the inner mass of the rock.You can also click the 80
pixels.
Be sure to center the sprite and collision shape on the scene’s origin point, as shown below.
The physics engine treats that point as the rigid body’s center of mass. If you offset the collision shape, the rock will roll back and forth like a rocking chair and be much harder to move.
We can now reopen the Level scene and drag
Rock.tscn
in it. Place it where you like. We placed it on a
platform next to the character.
You can run the scene and run into the rock to see your character push it around like it’s a plastic ball.
By default, the physics engine treats kinematic bodies as having infinite inertia, so they can just push rigid bodies out of their way.
We have to update the robot’s call to move_and_slide()
to address this issue.
We’re now ready to code the interaction between the player character and the rock.
We want to push the rock when the robot touches it and moves toward it. Here, we could write the code that moves the rock either on the rock or on the character. Both options can work.
However, you will generally want to change the character’s animation when pushing an object, which you want to do from the character’s script. That’s why we’ll write the push code on the character.
After calling move_and_slide()
on the character, we can
detect if the character touches anything. Thanks to that, we can check
if the character has collided with the rock. If so, then we will apply
an impulse to the rock.
Over the next sections, we will:
move_and_slide()
call.To get started, reopen the Robot.gd
script.
So far, we only saw the first two arguments of the
move_and_slide()
function, but it has four more, all of
which are optional.
The third and fifth arguments control the body’s motion on slopes, and the fourth one helps to make the character’s movement feel smooth.
You can read more on each by pressing the F1 key and
searching for move_and_slide
in the built-in help.
We are only interested in the last argument:
infinite_inertia
. By default, if we omit the argument, it
will be true
and the kinematic body will automatically push
rigid bodies out of its way.
We need to explicitly set to false
. To do this in
GDScript, we must provide all the arguments that come before it.
Update the line that calls the move_and_slide
function
like so.
# The third, fourth, and fifth arguments control movement on slopes and
# curved terrain.
#
# The last argument prevents the body from pushing RigidBody2D nodes
# automatically.
= move_and_slide(velocity, Vector2.UP, false, 4, PI / 4, false) velocity
In a move_and_slide()
, we use
get_slide_count()
.
It is not the most explicit function name! It returns the number of times the body collided in a given frame.
The move_and_slide()
function can move the kinematic
body multiple times per frame. In games, physics bodies move in straight
lines, but when moving along a curved terrain, this can cause the body
to get a bit stuck multiple times per second. If you’ve ever played a
game where the characters seemed to bump into slopes, this might be
why.
There’s a trick game developers can use to make the motion smooth and avoid this problem: moving the body multiple times in a row, and adjusting its motion vector each time. Godot’s
does it for you so you don’t have to worry about coding it yourself.However, it’s important to know about these multiple collisions to process them and know what the robot collided with.
At the end of the _physics_process()
function,
write:
func _physics_process(delta: float) -> void:
# ...
for index in get_slide_count():
var collision := get_slide_collision(index)
As long as the character is on the ground,
get_slide_count()
will return at least 1
. If
we bumped into the wall while walking, it would return
2
.
The robot would first collide with the floor, then the wall.
We can retrieve information about each collision with
get_slide_collision(n)
, where n
is the
collision’s index, starting at 0
.
The get_slide_collision()
function returns an object of
type KinematicCollision2D
that contains information about
the collision.
If we only have one collision (the ground), then
get_slide_collision(0)
will return a
KinematicCollision2D
representing the collision with the
ground.
Note: The KinematicCollision2D
is
not the ground. It has information about the collision with the
ground. It’s an object that says, “we collided with that physics body,
at these coordinates, in this direction.” From that object, we can
retrieve the ground.
We will use two variables from it:
KinematicCollision2D.collider
, which we can use to
retrieve what we collided with.KinematicCollision2D.normal
, which is the direction of
the collision, which faces away from the surface you hit. More on that
below.Add the following condition to check if the collider
is
a .
func _physics_process(delta: float) -> void:
# ...
for index in get_slide_count():
# ...
if collision.collider is RigidBody2D:
pass
Note: We only have one type of
in this project: the rock instances. We don’t have any other node that is a . If we did, we would need to verify if the we’ve collided with is a rock. For example, we could use the node groups feature we saw in the obstacle course series.With those lines, we’ve detected if we collided with a rock. All that’s left is pushing!
To push the rock, we call the
RigidBody2D.apply_central_impulse()
function, which takes a
vector as its argument.
We will use the KinematicCollision2D.normal
property,
which gives us the collision direction.
We want to push in the opposite direction to the collision, so we will invert the normal. We use the minus sign to reverse the direction of a vector.
-collision.normal
Note: A normal vector is a vector perpendicular to the surface we collided with. It represents the direction of the collision. In this case, because we check for a collision with the rock, it will be a line perpendicular to the point of collision, pointing away from the rock.
We then multiply this direction by an arbitrary value representing the impulse’s strength.
func _physics_process(delta: float) -> void:
# ...
for index in get_slide_count():
# ...
if collision.collider is RigidBody2D:
# Replace "pass" with the following two lines.
var impulse := -collision.normal * 5000 * delta
apply_central_impulse(impulse) collision.collider.
Our final code looks like this:
func _physics_process(delta: float) -> void:
# ...
for index in get_slide_count():
var collision := get_slide_collision(index)
# Check if we collided with a RigidBody2D.
if collision.collider is RigidBody2D:
# Push the rock from the center.
var impulse := -collision.normal * 5000 * delta
apply_central_impulse(impulse) collision.collider.
If you try the game now, you’ll see you can push the rock!
But it feels very floaty. Let’s make it a bit better.
You can control how a
behaves by tweaking its properties. We will only use two here:Open the Rock.tscn
scene once again and select the
Rock node.
Let’s first give the rock some good mass. Set the Mass
property to 15
.
Notice how the Weight changes too. These two properties are linked so changing one causes the other to update.
Note: The mass is the amount of
matter in an object, while weight measures how the force of
gravity acts upon that object. The default gravity is 9.8
,
so if the mass is 15
, the weight will be
15 * 9.8 = 147
.
If you try the scene now, you’ll notice the rock is harder to move. It doesn’t bounce around as soon as you touch it. However, it still falls slowly.
Note: Intuitively, we think that heavy objects fall faster than light objects, but that’s not the case. In a vacuum, all objects fall with the same acceleration: the value of gravity. What affects the falling speed of bodies is air resistance. That’s why an empty plastic ball falls as fast as a ball made of heavy metal.
The simulation does not handle air resistance, so we have to change how the node reacts to gravity.
Let’s change the Gravity Scale, so gravity gets applied more
on each frame, and our object falls faster. Set it to
20
.
Note: We can also change the main gravity of the whole project in Project > Project Settings > Physics > 2D, but since we probably want different values for different objects, it’s often more convenient to change it in the Gravity Scale.
Play the scene now. Congrats! You can now push the rock.
There are no right or wrong values when working with rigid bodies. You must try different ones in your game and choose them based on what feels best when playing.
In this lesson, we used a value of 5000
for the impulse
applied to the rock, a Mass of 15
, and a
Gravity Scale of 20
.
We found them through trial and error. We kept changing the values until we got a result that felt good enough.
You can technically use either a rigid or a kinematic body for any object that moves in your game, character or otherwise.
Rigid bodies can seem simpler to start with because of the built-in gravity, but in practice, they are harder to control precisely than kinematic bodies.
Kinematic bodies give you precise control over how objects move but require a little more initial code. Now, in most 2D games, that precision matters a lot.
That’s why we recommend favoring
for the player character and enemies and for things that should roll and bounce around somewhat realistically, as the rock in this lesson.Open the practice Marbles.
In the next lesson, we will add doors that open when you place a rock on the corresponding pressure plate.
Here’s the complete Robot.gd
script.
extends KinematicBody2D
export var speed := 600.0
export var gravity := 4500.0
var jump_strengths := [1400.0, 1000.0]
var extra_jumps := jump_strengths.size()
var jump_number := 0
var velocity := Vector2.ZERO
func _physics_process(delta: float) -> void:
var horizontal_direction := Input.get_axis("move_left", "move_right")
= horizontal_direction * speed
velocity.x += gravity * delta
velocity.y
var is_jumping := Input.is_action_just_pressed("jump")
var is_jump_cancelled := Input.is_action_just_released("jump") and velocity.y < 0.0
if is_jumping and jump_number < extra_jumps:
= -jump_strengths[jump_number]
velocity.y += 1
jump_number elif is_jump_cancelled:
= 0.0
velocity.y elif is_on_floor():
= 0
jump_number
# The third, fourth, and fifth arguments control movement on slopes and
# curved terrain.
#
# The last argument prevents the body from pushing RigidBody2D nodes
# automatically.
= move_and_slide(velocity, Vector2.UP, false, 4, PI / 4, false)
velocity
# Loop over all collisions that occurred in this frame
for index in get_slide_count():
var collision := get_slide_collision(index)
# Check if we collided with a RigidBody2D.
if collision.collider is RigidBody2D:
# Push the rock from the center.
var impulse := -collision.normal * 5000 * delta
apply_central_impulse(impulse) collision.collider.