Composition is a powerful design tool for game developers. In this article I’ll show you how to create smooth top-down 2d movement with 3 simple custom components. Keep in mind I’m using the term “component” rather loosely here.
Godot 4 comes with a lot of improvements and quality of life features not found in Godot 3. Two of these features in particular are useful for our components:
The ability to export nodes directly (previously it was limited to node paths)
The ability to export custom resources
These features make the code much cleaner and easier to read.
Let’s get started.
The Plan
First, let’s look at the 3 custom components we’ll be creating:
1. A top-down movement stats component
This component will store the max speed, acceleration, and friction. It will be a resource so we can share it with any components that need access to that data.
2. A top-down movement input component
This component will handle getting the input and setting the velocity on our moving character.
3. A top-down movement component
This component will handle movement and collisions.
The Stats Component
Let’s start by creating the stats component. Here is what the code looks like:
We’ll save this script as “top_down_movement_stats.gd”
Once we’ve saved the script. We can right click in the file system of the editor and create a new resource:
And then save a new TopDownMovementStats resource:
Save this resource as “player_movement_stats.tres”
The Input Component
Now we can create the input component. The code for it has a bit more to it:
Save this script as “top_down_movement_velocity_input.gd”
Here we export an actor as a CharacterBody2D. This is the body we will be manipulating. We also need to export our movement stats so we can use them when updating the velocity. Once we have access to the actor and stats, we can get input and then accelerate or decelerate the actor’s velocity.
The Movement Component
Now we can create the movement component.
Save this script as “top_down_movement.gd”
Here we export the actor as well and also a min_slide_angle. By default Godot’s CharacterBody2D node is set to a grounded motion mode and a 20 degree min_slide_angle. The grounded motion mode is for platformers. You should always switch to floating for top-down games. I personally prefer a min_slide_angle of 0 for top-down games but I’ve exported it to make adjustments easy. The last part of the script is just calling move_and_slide() to update the characters position based on their velocity.
Now you might be asking, “Ben why didn’t you just move the body in the other component?” The reason has to do with making this component more reuseable. By splitting the input component and the movement component, we can easily create an enemy that replaced the input component with an AI component but still used the same stats and movement components.
Bringing Them All Together
Now that we have all our components. We can construct our player scene using the components.
Here is the player scene:
The root node is a CharacterBody2D. It has a sprite, a collision shape, our input component, and movement component.
Once the scene is set up, don’t forget to set the export variables on the components:
Once they’re set, you’re ready to try out your character!
You can find an example version of this project here on github.
Conclusion
Components like this have become easier to create and use in Godot 4. While this example is simple, it gives a good starting point to any developers wanting to get started with composition.
I hope you enjoyed this article and learned from it. Don’t hesitate to comment below if you have any questions.
Ben
I am also making a top down shooter (very early prototype still) and knew that I needed to get my movement out of the individual player/enemy scripts, just a little work and I was able to get my movement system in components using this method. Thank you for the quick guide it was extremely helpful and straightforward!
Selfishly, I am gonna start working on enemy AI and context based steering for my game soon, just in case you needed ideas for the next one 😅
thank you. While not a complete newb, I was just a tad bit coRnfused.
thanks for taking the time to answer.