Forewords
Before beginning, I'd like to announce there's a new top-down action RPG tutorial here by HeartBeast. It's the most up-to-date tutorial for Godot in Youtube right now, which is version 3.2. As of I'm writing this, he's already 11 episodes in. Go and show your love.
By the way, I'm trying out C# in Godot, but the tutorial series above uses GDScript, so mine is going to be a bit different. I also need to state that I've never coded in C# before, what I had in mind before I started C# was that C# was kinda like Java, which helped me understand it a lot but, if you see any quirky code, like violation of style guides or common practices in C#, let me know or simply ignore them.
This post also follows the logic of HeartBeast's tutorial, so I strongly recommend you check it out, at least 4 episodes in.
Movement 101
Firstly, we need to understand that there are three parts for a smooth movement:
- Acceleration is how fast a player starts moving.
- Friction is how fast a player stops moving.
- Of course when a player accelerates, you need to define a peek value, which is the limit of how fast it can accelerate. This is called maximum speed.
When you imagine it in a graph, it starts increasing as soon as the player receives an input and it starts decreasing back to zero when it stops receiving the input. See below:
(I know this is not the most beautiful graph you've ever seen but bear with me please.)
Knowing this, we can assume a player's code (assuming the player is a KinematicBody2D
) is going to be as such:
using Godot;
using System;
public class Player : KinematicBody2D
{
private const int ACCELERATION = 500;
private const int MAX_SPEED = 60;
private const int FRICTION = 500;
private Vector2 velocity;
public override void _PhysicsProcess(float delta) // compute next frame
{
// let's create a vector for user input, defining which coordinates the vector is targeting to
var input_vector = Vector2.Zero; // equivalent to `new Vector2()`, I use it for shorthand sometimes
input_vector.x = Input.GetActionStrength("ui_right") - Input.GetActionStrength("ui_left"); // assign *if it is left or right* to input vector
input_vector.y = Input.GetActionStrength("ui_down") - Input.GetActionStrength("ui_up"); // assign *if it is up or down* to input vector
input_vector = input_vector.Normalized(); // see heartbeast for this, he explains better
if (input_vector == Vector2.Zero) // if we do not receive any input
{
velocity = velocity.MoveToward(Vector2.Zero, FRICTION * delta);
}
else // if we receive any input
{
velocity = velocity.MoveToward(input_vector * MAX_SPEED, ACCELERATION * delta);
}
velocity = MoveAndSlide(velocity); // move to target and new assign target coordinates to velocity
}
}
This is a basic movement which has a constant speed. You can play with ACCELERATION
, MAX_SPEED
and FRICTION
constants to change the feel of your movement.
How to Run
Let's assume we'd like to bump our speed when we, say, press SHIFT
key. In order to do that, we first need to define an input. You can do that by clicking "Project > Project Settings" menu...
...and going into "Input Map" tab.
As you can see, the ones such as ui_up
and ui_down
in the code are defined here along with the keys they target. So, as in the image, we need to add a new action and mapping it to a key by first defining a name for our new action and clicking the "Add" button.
Then we need to add a key for our new accelerate
action...
...and define a key for it.
When you do that, you can now check if accelerate
action is received by writing a code like below in C#:
bool is_accelerating = Input.IsActionPressed("accelerate");
Now we need to refactor our code a little bit. I wanted to do this with as little change to the code as possible. Our target is ACCELERATION
, MAX_SPEED
and FRICTION
constants.
The problem is these are constants, which are not meant to be mutated on the runtime. However, we'd like to change the values of these depending on if we receive accelerate
action or not. So, we change these to static.
private static int ACCELERATION = 500;
private static int MAX_SPEED = 60;
private static int FRICTION = 500;
There is also a very nice feature in C#. Just like Kotlin (for the sake of giving an example from JVM-based languages), we can write getters (and also setters) in property line. So, my code changes to:
private static int ACCELERATION
{
get
{
var value = 500;
if (Input.IsActionPressed("accelerate"))
{
value = 700;
}
return value;
}
}
private static int MAX_SPEED
{
get
{
var value = 60;
if (Input.IsActionPressed("accelerate"))
{
value = 90;
}
return value;
}
}
private static int FRICTION
{
get
{
var value = 500;
if (Input.IsActionPressed("accelerate"))
{
value = 700;
}
return value;
}
}
We define default values with value
variable and change it if we receive accelerate
action. The code above results in the table below:
Normal | While accelerate | |
ACCELERATION | 500 | 700 |
MAX_SPEED | 60 | 90 |
FRICTION | 500 | 700 |
You can, again, change the values as your needs. Some values feel like you are controlling a rock while others feel like the ground is ice.
Final Words
As I've said in the beginning, be sure to check HeartBeast's Godot RPG series. It's the hottest thing in Godot tutorials right now. See you in the next post.