avatarMina Pêcheux

Summary

The provided content is a comprehensive tutorial on creating a basic 2D character controller in Godot 4 using C#, covering setup, movement, jumping, and animation.

Abstract

The tutorial guides readers through the process of implementing a simple 2D character controller in the Godot 4 game engine using C# scripting. It begins with setting up the player object, including the use of sprites, physics bodies, and collision shapes. The author then delves into scripting player movements such as walking, jumping, and falling, with attention to gravity and input handling. The tutorial also touches on the importance of regular updates in physics-based movement and the use of Godot's built-in code editor. Finally, it explains how to add animations to reflect the character's state, including walking and idle sprites, and how to toggle their visibility based on the character's movement. The article concludes with an invitation for readers to engage with the content and explore further Godot features.

Opinions

  • The author emphasizes the benefits of using Godot's built-in nodes like CharacterBody2D and CollisionShape2D for creating a character controller.
  • There is a preference for using C# over GDScript for this tutorial, highlighting the need for more C# resources in Godot development.
  • The author suggests that using the built-in code editor in Godot is convenient but may lack some features compared to external editors.
  • The tutorial promotes the use of project settings to keep gravity values consistent across the game.
  • The author encourages the use of input actions for game controls in a real game project, as opposed to direct key names used in the tutorial for simplicity.
  • There is an opinion that animating the character's visuals is crucial for player feedback and overall game feel.
  • The author values community feedback and invites readers to comment with suggestions for future tutorial topics.

Implementing a simple 2D character controller (Godot 4/C#)

Let’s create our own 2D avatar using physics & scripting!

You know, all those little 2D games where you control a little avatar that’s jumping around and running on platforms? Well, today, we’re going to see how to create such a basic 2D character controller in Godot 4 and C# :)

By the end of this tutorial, you’ll know how to setup a basic physics-based 2D player movement manager with some collisions with the ground, and even a few walk animations :)

Also, note that for this tutorial, I’ll be using this little 2D level based on tilemaps, and if you’re curious about how to create such a scene, you can check out this previous tutorial to learn the basics of 2D tilemaps in Godot 4:

As usual, don’t forget that you can get the demo scene and all the assets from this demo on my Github 🚀 with all my other Godot tutorials.

The tutorial is also available as a video — text version is below:

And with that said, let’s dive in and discover the fundamentals of making a 2D character controller in Godot!

Setting up the player object

To begin with, let’s discuss the different elements we need in our player object. For this tutorial, I’ll be using the sprites of the Platformer Art pack from Kenney’s amazing library and, in particular, I’m gonna use one of its alien characters for the player visuals:

So, here, our player object will need to do three things:

  • First, it will need to actually appear on screen — so we’ll need to show some image, some sprite, of our alien character, to visualise the object.
  • Second, it will need to collide with the ground, be subjected to gravity and overall use forces to move around — so we’ll need to use a physics body and a collision shape, just like we saw earlier in the series, in the “getting started” tutorial, except this time we’ll be using the 2D version of those Godot nodes.
  • And finally, it will need to react to our inputs to move horizontally or jump — so we’ll need to code a bit of logic to tie these key presses in the controller.

Note that here, we’re gonna discover how to code this logic in C# because there are already other tutorials on the net on how to do it in GDScript, and coding C# in Godot is not as widely discussed :)

So you should make sure that you have a version of Godot with the .NET support enabled.

Download a version of Godot with .NET support, to be able to program in C#! :)

But anyway — now that we know what structure we want, let’s build our player hierarchy.

You see that I’ve already imported the character sprites from Kenney’s pack, and I have one sprite for the idle standing visual, plus a series of sprites for the walk animation.

But for now, let’s just ignore the animation and use the basic standing sprite to visualise our player.

Since we’re going to work in 2D, let’s start by creating a 2D root node in our scene. Then, we’ll create a child node of type Sprite2D. This is a simple 2D image renderer; and to set its image, we simply need to go to the Texture field in the Inspector, use the dropdown and select a load option to reference our image file asset.

Also, let’s rename the node to IdleSprite so that we remember this just handles the single standing visual of the alien :)

Ok now — to have our object collide with the ground, we said that we need to do two things:

  1. we have to transform our root node into a 2D body so it computes forces and moves the overall player object
  2. and we have to give it a collider so it’s blocked by the terrain

For the body node, Godot actually has a nice built-in in our case called CharacterBody2D. As you can see in the little description at the bottom, it’s specifically to create characters moved by script — so exactly what we need here!

For the collider, we’ll add a new CollisionShape2D node in our hierarchy and set its shape to a 2D capsule. Be sure to also adjust it on the model so that it fits the visual, otherwise the gamers will feel like it’s inconsistent!

And as a bonus, let’s give our player a Camera2D node. Basically, by integrating the camera into our player’s hierarchy, it will follow it perfectly at all times and keep our alien centred in the screen :)

Now that our player object is ready, let’s just save it as a new Godot scene, so that we can reuse it in other bigger scenes. Then, let’s open the main scene I prepared beforehand that, for now, simply contains a 2D tilemap for the level.

To get this base template scene, have a look at the Github and look for the scene marked as template 🚀

This tilemap is already set for collisions, and as you can see it’s got jump or fall spots to test our gravity and moves later on.

To wrap up this first section, let’s just drag our player object in the scene, somewhere in the middle, slightly above the ground.

At this point, if we try to run our small game with this main scene, we see that… the player is not moving! And that’s because, contrary to a RigidBody, a CharacterBody isn’t subjected to gravity or physics forces by default.

So to fix this, let’s see how to code a bit of C# to have our character move!

Implementing the player movements

Alright — our player hierarchy is ready to bust a move, and it’s time to use some C# scripting to handle the basic gravity-based fall, the horizontal walking and the jumping.

The first step is to create our script and add it to our object. The best way to do this is actually to:

  • make sure we are in our player scene
  • right-click on the CharacterBody2D node at the root of this scene
  • and click on the Attach script option

This shows us a popup to create a new script resource in the project with some options already pre-set for us — but we need to do a few changes to it.

At the very top, we need to switch from GDScript to C#. Then, beneath, we see that the popup auto-filled the Inherits field with our node’s type. That’s nice because it means that the script will properly retrieve all the properties and methods of the node, and understand that it’s actually a CharacterBody2D node and not just a Node or a Node2D.

However, this also autosets the Template for the script — which is great when you want to develop your game quickly but here, for a tutorial, it’s better to start from a simpler script skeleton. So let’s use the dropdown to pick the basic Node template.

Then, just choose a path for the script file, and click the Create button.

As soon as we save it, Godot switches us to the built-in code editor. This is a simple IDE that is directly integrated in the engine and allows us to easily edit our game scripts, be them GDScript or C#. You see that we can switch between the 2D/3D scene editors and the scripting context with the buttons at the very top of the screen:

Of course, having the code editor built-in like this is interesting because it means that we don’t need to change window or soft to go between our scene editors and our code — everything is right next to each other. However, not many people are used to this specific editor, and you might not have all your plugins or shortcuts.

Overall, I think Godot’s built-in code editor is pretty good — though it still lacks a few features, especially for C# where we don’t yet have autocomplete.

But anyway :)

You see that Godot filled our script with some lib imports at the top and a C# class that inherits from CharacterBody2D, as expected, and that contains a few base lifecycle functions, according to the Node template we asked for:

using Godot;
using System;

public partial class PlayerController : CharacterBody2D
{
 
 public override void _Ready() {}
 public override void _Process(double delta) {}
 
}

These _Ready() and _Process() functions are basic hooks that you can find in any game engine, and that describe some of the important moments of the lifecycle of your objects:

  • The _Ready() function is the start point of the scene: it’s executed when the scene is first loaded, and thus the node with this script attached on it is first loaded. Typically, it’s great for initialising stuff that you only need to do once at the beginning.
  • The _Process() function is used for the update loop: it’s executed every frame, and it’s usually how you run continuous logics that execute again and again while the scene is running. You see that it receives a double delta parameter, which corresponds to the amount of time elapsed since the last frame.

However, depending on the machine, this delta time amount might not be regular and universal — lower-tier machines will run slower, and there could be some moments where a computer needs to compute more, or is busy doing something else in another soft, and then you get slightly irregulars intervals between your _Process() calls.

That’s often no big deal on common update operations — but there are a few cases, such as physics-based movement, where this can be annoying.

That’s why there’s another processing function for the update called _PhysicsProcess(), that also receives a delta time parameter but, this time, you’re are absolutely sure it will run regularly, no matter the frame rate of the game.

Note: By default, this regular time delta for _PhysicsProcess() is 60 times by second, but you can change it in your project settings in the Physics section :)

So, here, typically, for a 2D character controller, it’s better to use this!

using Godot;
using System;

public partial class PlayerController : CharacterBody2D
{
 
 public override void _Ready() {}
 public override void _PhysicsProcess(double delta) {}
 
}

Now, to actually have our character fall because of gravity, and move, we’re going to need a few variables so that we know how much to move the character each frame to accomplish those movements.

So let’s go at the top of our class, before the _Ready() function, and define three variables:

  • First, the moveSpeed, which is a float that determines the speed of the character across the screen. Note that whenever you’re working in 2D in Godot, all distances are expressed in pixels, so that’s why we need to set a pretty huge value.
  • Second, the jumpVelocity which is also a float and gives the upward velocity to apply to the controller when we press the jump key.
  • And finally, the gravity value — now, we could go and write it down manually but, in order to keep all the objects in the scene consistent and in sync, it’s better to directly access our project settings and get the default 2D gravity value from there. We can get it easily using the ProjectSettings.GetSetting() built-in.
using Godot;
using System;

public partial class PlayerController : CharacterBody2D
{

 public float moveSpeed = 150.0f;
 public float jumpVelocity = 400.0f;
 
 // get gravity from project settings (keep everything synced)
 public float gravity = ProjectSettings.GetSetting("physics/2d/default_gravity").AsSingle();

 public override void _Ready() {}
 public override void _PhysicsProcess(double delta) {}
 
}

Alright, now, the idea is to use those variables in our _PhysicsProcess() function to have the character move as time goes by — either automatically because of gravity, or to react to one of our key inputs. We’ll compute all of this in a new velocity 2D vector, and finally reassign it at the end of the function once it’s updated:

public override void _PhysicsProcess(double delta) {
  Vector2 velocity = Velocity;
  
  // update the velocity to move the player

  Velocity = velocity;
}

For the gravity, of course, we want to compute and apply it only if the character is in the air — otherwise, no need to move it! ;)

Luckily, that’s actually pretty straight-forward to do: because we’re using the CharacterBody2D node type and our class knows that, with the inheritance, we can directly use a nice built-in function of this type called IsOnFloor(). This function tells us if the physics collider of our object is grounded on another 2D physics collider.

So basically, if we are not on the floor, then we’ll want to apply gravity: this is a downward force, along the vertical Y axis, with the magnitude defined by our gravity parameter from before, that we need to multiply by our delta time amount to accumulate it over time:

public override void _PhysicsProcess(double delta) {
  Vector2 velocity = Velocity;
  
  // apply gravity (if in the air)
  if (!IsOnFloor()) {
    velocity.Y += gravity * (float)delta;
  }

  Velocity = velocity;
}

Here you see something essential, which is that in Godot, in 2D, the Y axis points down, so a downward vector is actually positive on the Y axis.

Ok so — time to try this out. Let’s try to play our game… and you see that still, nothing happens!

That’s because, for now, our _PhysicsProcess() function has cooked up a new velocity value for our controller… but it’s never actually used! To really apply this new velocity value, we need to call one other built-in at the end of the function: MoveAndSlide().

public override void _PhysicsProcess(double delta) {
  Vector2 velocity = Velocity;
  
  // apply gravity (if in the air)
  if (!IsOnFloor()) {
    velocity.Y += gravity * (float)delta;
  }

  Velocity = velocity;
  MoveAndSlide();
}

And there we go: the character now properly falls on the ground when we start the game due to gravity :)

The next step is to use our keyboard to have the player move horizontally left and right, and jump.

In this tutorial, we’ll keep things simple and use direct key names to do this — however, remember that in a real game, you’d probably rather use input actions to define multiple bindings, handle cross-platform and make the whole thing easier to re-edit in the future.

Note: By the way, if you’re curious about that and you want me to talk about setting up inputs in Godot, leave me a comment down below ;)

But anyway, here, let’s take care of our horizontal movement first.

We’ll start by setting our new horizontal velocity to 0, and then we’ll check keyboard keys directly. Basically, if we’re pressing the left arrow key, then our velocity along the X axis will be -moveSpeed, and if we’re pressing the right arrow key it will be +moveSpeed:

public override void _PhysicsProcess(double delta) {
  Vector2 velocity = Velocity;
  
  // apply gravity (if in the air)
  if (!IsOnFloor()) {
    velocity.Y += gravity * (float)delta;
  }

  // handle horizontal movement
  velocity.X = 0;
  if (Input.IsKeyPressed(Key.Left))
    velocity.X = -moveSpeed;
  else if (Input.IsKeyPressed(Key.Right))
    velocity.X =  moveSpeed;

  Velocity = velocity;
  MoveAndSlide();
}

If we rerun the game, you see that we can now move left and right — pretty neat!

To finish up our movement system, we can add a few lines to handle jump — so if we’re pressing the spacebar, we’ll say that our vertical velocity along the Y axis is -jumpVelocity (cause remember the Y axis points downwards, so an upward velocity is negative):

public override void _PhysicsProcess(double delta) {
  Vector2 velocity = Velocity;
  
  // apply gravity (if in the air)
  if (!IsOnFloor()) {
    velocity.Y += gravity * (float)delta;
  }

  // handle horizontal movement
  velocity.X = 0;
  if (Input.IsKeyPressed(Key.Left))
    velocity.X = -moveSpeed;
  else if (Input.IsKeyPressed(Key.Right))
    velocity.X =  moveSpeed;

  // handle jump (if grounded)
  if (Input.IsKeyPressed(Key.Space) && IsOnFloor())
    velocity.Y = -jumpVelocity;

  Velocity = velocity;
  MoveAndSlide();
}

And that’s it! Our character can now move, jump… and fall when there’s no terrain ;)

A last improvement we can do is make sure that our character’s visuals update as we’re moving — so time to use our walk sprites to create a little animation, and make sure that the player sees the difference between idle and walking…

Adding animations

Ok so — for now, our player always shows its simple idle sprite. That’s great when we’re not moving, or even when we’re falling, but it feels really weird when we’re walking because at the moment it’s more like we’re sliding.

To fix this, we’re going to setup a very basic system of two sprite nodes:

  • the one we already have for the idle state
  • and a new one for the walk state

Except that this second node will be of AnimatedSprite2D node type — this will make it super easy to use all of the alien sprites from Kenney’s package and make a sprite animation from them!

Let’s rename the node WalkSprite, and go at the bottom of the screen, in the SpriteFrames editor that Godot opened up, to set up our animation frames.

We simply need to select all of our frames in the FileSystem dock, and drag them to this zone to add them to the animation.

To try it out, we can hide our IdleSprite from before, and then click on the play icon in the SpriteFrames editor, over here. You see that our sprites are indeed played one after the other, but the rate is a bit too slow.

To fix this, we can change the FPS value in the input at the top of the SpriteFrames editor, and then replay the animation to check it looks better :)

The final thing is to make sure that the IdleSprite and the WalkSprite nodes are properly toggled on and off when we start or stop moving. Let’s go back to our C# script to take care of this.

First of all, at the top, we’re going to add two new variables of type Sprite2D and AnimatedSprite2D, and we’ll assign them in the _Ready() function.

using Godot;
using System;

public partial class PlayerController : CharacterBody2D
{

 public float moveSpeed = 150.0f;
 public float jumpVelocity = 400.0f;
 
 // get gravity from project settings (keep everything synced)
 public float gravity = ProjectSettings.GetSetting("physics/2d/default_gravity").AsSingle();

 private Sprite2D _idleSprite;
 private AnimatedSprite2D _walkSprite;

 public override void _Ready() {
    // get sprite references
    _idleSprite = GetNode<Sprite2D>("IdleSprite");
    _walkSprite = GetNode<AnimatedSprite2D>("WalkSprite");
 }

 public override void _PhysicsProcess(double delta) {
   ...
 }
 
}

To get those references, you see that we just have to use the built-in GetNode() function, pass it the type of node to get as a generic type, and then give it the path as a string. This path is relative to the node that our script is attached to, and it contains the exact list of nodes to go through to get the one we need, separated by slashes.

With those new variables ready, let’s now go to the end of our _PhysicsProcess() function and call a new private method, _UpdateSpriteRenderer():

public override void _PhysicsProcess(double delta) {
  // ...

  _UpdateSpriteRenderer();

  Velocity = velocity;
  MoveAndSlide();
}

We’ll define it as a void beneath:

using Godot;
using System;

public partial class PlayerController : CharacterBody2D
{

 // ...

 private void _UpdateSpriteRenderer() {
 }
 
}

And this is the function that will handle:

  • showing or hiding our sprite nodes
  • playing the walking animation when need be
  • and even flipping the sprite to properly match the current move direction

It will take the velocity along the X axis as input parameter, so that we know how to re-update the sprites:

using Godot;
using System;

public partial class PlayerController : CharacterBody2D
{

 // ...

  public override void _PhysicsProcess(double delta) {
    // ...

    _UpdateSpriteRenderer(velocity.X);

    Velocity = velocity;
    MoveAndSlide();
  }

 private void _UpdateSpriteRenderer(float velX) {
 }
 
}

So let’s begin by checking whether we’re currently walking or not, depending on our horizontal velocity. Then, we’ll use this boolean flag to make the IdleSprite and the WalkSprite either visible or hidden.

private void _UpdateSpriteRenderer(float velX) {
  bool walking = velX != 0;
  _idleSprite.Visible = !walking;
  _walkSprite.Visible = walking;
}

And finally, if we’re walking, we’ll do two things: first, we’ll play our AnimatedSprite2D’ animation, and second we’ll flip the sprite according to whether the horizontal velocity is positive or negative.

private void _UpdateSpriteRenderer(float velX) {
  bool walking = velX != 0;
  _idleSprite.Visible = !walking;
  _walkSprite.Visible = walking;
  
  if (walking) {
   _walkSprite.Play();
   _walkSprite.FlipH = velX < 0;
  }
}

And here we are! If we restart our game, you see that our little 2D alien character now walks around with its cool animation, it can jump when we press the spacebar and it falls if we get out of the terrain!

Conclusion

So there you go: you know how to setup a simple 2D character controller in Godot, and even how to play a simple animation to match the movement!

I hope you liked this tutorial and that it helped you learn the basics of 2D body controls.If you did, feel free to clap for the article and follow me to not miss the next ones — and of course, don’t hesitate to drop a comment with ideas of Godot tricks that you’d like to learn!

As always, thanks a lot for reading, and take care :)

To read more of my content, and articles from many other great writers from Medium, consider becoming a member! Your membership fee directly supports the writers you read.

Godot
Game Development
Programming
Tutorial
Csharp
Recommended from ReadMedium