Prototyping Character Movement with Godot

Recently I made some mockups for a potential video game where the player manages some servers in the early days of the internet.  The mockups were done by combing assets found on itch.io with Aseprite and exporting animated gifs.  The result of this effort is shown in figure 1.

Bob installs 5 servers and the computer is happy

Figure 1 – Mockup

I shared this mockup on reddit and twitter and got some positive feedback.  Rather than continuing to test this idea by making mockups with Aseprite, I decided to move from mockup to prototype by creating some scenes with Godot.  My main motive for using Godot for future prototypes is that it is much easier to animate complicated scenes with Godot because you can script the behavior of the scene.  In Aseprite you are limited to carefully organizing each layer and frame of the scene one after another.  This is too tedious for anything more than a few frames.

In this article I will discuss my technique for prototyping the character movement in Godot.  As a disclaimer, I am not a Godot expert and this is not intended to be a tutorial.  As you read, be aware that there may be a better way to do things.

Screenshot of Godot IDE showing nodes for scene

Figure 2 – Scene Nodes

To get started, I created a new scene with an Area2D node, a Sprite for idle animations, another Sprite for walking animations, and a couple of other nodes that are not used yet. In Godot, Sprite nodes can be hidden.  The character’s idle sprite starts out hidden and the walking sprite is shown.

I also updated the projects settings to map the w, a, s, and d keys to player_up, player_left, player_right, and player_down.  Creating a named keymap rather than binding to specific keys in a script will allow me to support other input devices in the future.

Sprite sheet of idle animations

Figure 3 – Idle

As you might have guessed based on the fact that there are two sprite nodes, there are two sprite sheets for this character (figure 3 and figure 4).  Both sprite sheets cover four directions, but the idle sprite sheet has more columns per row.  In Godot the rows and columns are called hframes and vframes.  The frames are counted by starting with the top left frame and going right, starting with zero.  In figure 3, the top left frame is 0 and the bottom right frame is 19.

Sprite sheet for characters walking animation

Figure 4 – Walking

The character in the idle sprite blinks and bobs when animated and the character in the walking sprite moves its arms and legs.  Both animations go from left to right.

To get things started, I attached as script to the character’s Area2D node and added some variables that will be used to control the animation.  I declared a setter for each of these variables.  Setters via setget can be very useful for encapsulating logic specific to that variable.  They might not be needed at first, but I find it is useful to start that way rather than add them later and potentially detangle a mess where I have littered setter logic all over my script.  I also declared a couple of signals to communicate when the character is walking or idle.

In order to use the signals I declared, I connected them to the current node in the _ready() function.  Later I declare a function called _on_walking() and _on_idle() to handle these signals when they are fired.

The next step is to handle the values that need to be updated during the processing step.  _process() will be used to manage a couple of timers and emit signals when the player provides input or becomes idle.

Initially I tried to animate the character using a loop to iterate through each frame, calling yield with a timer at the end of each iteration to make the animation look natural.  Initially this seemed to work fine, especially when I was only working on the idle animation.  However, I quickly discovered that this technique does not work for a character that is moving across the screen because running the complete animation to it’s finish will make the character appear to be walking in the wrong direction until the animation loop has finished.  Instead, I advance the animation on each signal and use some logic to figure out which frame the animation should be on based on the current frame and the direction that the character is facing.

In _on_walking() you can see that I am calling get_next_position() to determine what position on the screen the character should move based on the new_direction from the player’s input.  I am also using get_next_frame() to set the frame of the walking sprite to the correct position.  This function relies on the result of get_start_frame() which figures out the starting frame for a given sprite sheet based on the direction of the character and the number of hframes and vframes in the sprite sheet.  I’ll discuss these functions in detail later.  Each time the "walking" signal is emitted I am checking to see if the walking_timeout is greater than 0.  The purpose of this is to control how quickly the character is animated.  The greater the value of walking_timeout the slower the character will be animated.  I check this timeout after moving the character so that the movement of the character across the screen still looks smooth.  In order to ensure that the transition from walking to idle is also smooth, I reduce the idle_timeout if the current frame is not the starting frame.  that way the character doesn’t stop with one foot forward like it it is mid-step when there is no additional player input.

_on_idle() is similar to _on_walking() and also uses get_next_frame() to animate the character.  The idle animation also uses a timer to control the speed of the animation.  In addition, the number of idle_cycles that occurs is tracked and a longer timer is set when enough cycles have elapsed.  This allows the animation to have a longer pause between animation loops so that it looks more natural.

get_next_frame() calculates what the next frame should be based on the current frame, the starting frame of the animation, and the boundary.  If the value of the current frame is less than the boundary, the frame is incremented.  Otherwise, the frame is set back to the starting frame.

get_next_position() returns a new position based on the current direction, walking speed, and the current position of the character (as a Vector2D).

The last function for animating the character is get_start_frame().  The function calculates what the starting direction should be for a given 4 direction sprite sheet with any number of horizontal frames.  I initially used a dict containing the hardcoded values for the idle and walking start frames.  However, using a dict in this way means that I will need to update the values for each start frame every time I wanted to add something new.  On the other hand processing get_start_frame() on every input signal isn’t a very efficient use of cpu.  I opted for something in the middle that allows the start frames to be pre-computed into a dict on _ready().

The init_start_frame() function initializes a dict of start frames for a 4 direction animation with any number of hframes.  If this scene is used as the base for an inherited scene in the future, the inherited scene can init_start_frame() for any additional sprite sheets and assign them to start_frames.

The snippets of code above are all that is necessary to move and animate the character on the screen.  The complete code can be viewed in this gist.  Keep in mind that the other files for the scene are not included so you will need to adapt this code to your own scene.

 

The end result can be seen in the video above.  The character moves around the screen depending on which key is pressed.  When the character is not moving the idle animation plays intermittently.

The original artwork used to create this prototype is linked below.  Thank you to each of the artists for sharing their work on itch.io.

  • Evil PC – The sprite used for the computer
  • 16×16 – The tile set used to create the background
  • Bob – The sprite sheet used to for the character animations