Press Ctrl+S to save the scene
Create a new folder called Scenes
Create a new folder called Player
Save the Player scene as player.tscn
Rename the Coin Grabber folder to
Change the folder colours
We chose Area2D because it can detect when other objects overlap or collide with the player.
Now we will add the player visuals.
Add an AnimatedSprite2D as a child node
Ignore the warnings at this point.
Select the AnimatedSprite2D node
In the Inspector on the right
Now click on the word SpriteFrames
To open the SpriteFrames tab
At the bottom of the screen
Rename the default animation to run
Add two new animations and call them
In the Assets => player folder
Drag the corresponding images into their animations
Set each animations speed to 8.0 FPS
Select the idle animation
This makes idle autoplay by default
Because the Player image is a little small
We will scale it up by doubling its size
Select the AnimatedSprite2D node
In the Inspector on the right find Transform
Set scale to 2.0 on x and y axis
For the Area2D of our Player to detect anything
We must include a CollisionShape2D node
Add one as a child of Player
For the CollisionShape2D to function
Select the CollisionShape2D
Assign a New Rectangle2D shape
Use the orange handles and
fit the shape around the player sprite
or set the size in the Inspector
Notice how the warnings have been resolved!
Now we will program functionality
Into the player by attaching a script
Press the attach script button
You can leave all settings as they are
The first line of every script
describes what node it is attached to
Here the script is attached to an Area2D node
We will define some variables to control
The @export tag makes a variable adjustable in the Inspector
This is useful for tweaking values while testing the game
int is a data type that stores whole numbers (integers)
Vector2 is a data type that stores two values (x and y)
When the game runs we must repeatedly check for keyboard input, move the player in the given direction and play the correct animation.
Godot provides a function which runs 60 times per second.
This function is called _process
Update Player position on-screen
In game development, velocity is speed with a direction (pixels per second). We get the input direction, scale it by our speed to get our true velocity, and then multiply by delta so movement stays consistent regardless of frame rate.
You can run the scene (press F6) and test the movement now.
Notice that the fox can run out of the screen boundary? We can fix that by clamping his position to the screen extents.
To match animation to movement in the process function
If the player is moving we play the "run"
Depending on if they are moving left or right
Note that $ followed by NodeName
Gives us access to the internal properties of a node in our Scene
Eg: $AnimatedSprite2D.animation
Lines 19 & 20 can be confusing so I have shown how they can be re-written to make their purpose move obvious
Take a moment to test your game again to check that it is animating correctly.
Right now, our _process() function is doing two very different jobs:
Handling movement (calculating input and updating position)
Handling visuals (deciding which animation to play and which way the sprite faces)
While this code works fine for a simple player, _process() runs every single frame. As we add mechanics like jumping, taking damage, or sound effects, cramming everything into one function makes scripts difficult to read and debug.
A good rule of thumb in game programming is to give each function a single responsibility. Instead of doing all the heavy lifting itself, _process() can act like a manager, calling small, dedicated helper functions to handle specific tasks.
Let’s refactor our code by splitting movement and animations into their own functions:
The new _process function
Look how neat and tidy it is!
A new function for handling movement
A new function for handling animation
Now we can create a function for: starting the game
When a player loses a life or starts a brand-new match, we need a way to reset our character back to the centre of the screen, wake them up, and make sure they look ready to play.
Now we can create a function for: ending the game
set_process(true) or set_process(false) turns the process function on or off.
When the player dies we turn it off, since movement and animation code is in the process function - it all stops!
When the player hits a coin or an obstacle, they shouldn't have to worry about updating the score on the screen or triggering a game-over screen. The Player's only job is to announce what just happened to them: "I picked up an item!" or "I got hurt!"
In Godot, this is done using signals. Think of signals like a megaphone: the Player shouts an event out into the game world, and any node that cares—like the UI or the Game Manager—can listen in and react accordingly.
Let's create two signals at the top of our Player script:
Signals need to be connected to the nodes that listen to their broadcast.
Select the Signals tab to the right of Inspector
You can see the custom signals which we made
As well as the built-in signal for Area2D nodes
To connect the signal to our script
Double click the area_entered signal
Godot Connects the signal to a new function in our script
Whenever our player overlaps another area, Godot automatically calls _on_area_entered(area) and hands us the specific object we touched through the area parameter. We can check what that area is, whether it belongs to a coin or an obstacle, and react accordingly.
If it is a coin we call the pickup function
then we emit the pickup signal
If it is an obstacle we emit the hurt signal
and then call the die function
We use area.is_in_group() to check whether the touched object is tagged as a "coins" or "obstacles" group member.
This code references things we haven't built yet (like area.pickup()
We will create the Coin and Obstacle scenes in the very next step and tag them with these exact group names.