RandomMomentania

Godot Touch Screen Joystick

Tutorial introduction

Making games for mobile platforms can be difficult. Unlike on PC or console, we cannot assume there will be a keyboard, mouse, or any other input device.

Most of the time, all we have to work with is just a single touchscreen. This forces us, the game developers, to find ways to make the games we make completely playable only with touchscreen controls.

One popular touchscreen control is the virtual joystick. These joysticks act similarly to the joysticks you find on game console controllers.

In this tutorial series, we will be making a touch screen joystick with the following features:

  • The joystick will act as close as possible to a normal, real world joystick.
  • The joystick can either be static, or can appear when there is a touch/click on a section of the screen.
  • The joystick will emit signals when joystick motion starts, updates, and ends.
  • The joystick will work with both touch screens and the mouse, and works fine with multi-touch displays.
  • The joystick works great with mobile devices and is performance friendly.
  • More than one joystick can be active and working at the same time.

Because these joysticks are so popular and have a variety of uses, let’s make one in Godot!

Virtual Joystick Theory

Let’s quickly go over how a joystick works from a game developer’s point of view, and the differences a virtual joystick has over a real joystick.

Note

I have little experience with computer hardware. There may be some mistakes, and I’m probably over simplifying things!

If you are experienced with computer hardware and notice I’ve made a mistake, let me know in the comments below or send me a email by following the Contact section above! Thanks!

First, let’s go over how a real life joystick works, or rather what we need to know to use a real life joystick in our games.

This will tell us what our virtual joystick will need to do, and roughly how it will work.

Please keep in mind this is a simplification!

The first thing to know is there are many different types of joysticks. The joysticks we are going to be talking about in this tutorial, and making in Godot, are analog joysticks.

Analog joysticks are a variation of joystick that is used for two dimensional input. Analog joysticks get the distance from the center of the joystick, and generally return it in a -1 to 1 range for each dimension, with 0 being the center position, or rest position, of the joystick.

Almost all modern analog joysticks return the joystick’s motion within a circle with a radius of 1 unit. This allows for smooth and precise input, where we can track anything from how far the joystick is from the center, to whether or not it’s in a corner of the circle.

Here is a image of some typical input values return from a analog joystick at various positions:

These values are estimated, but more or less are what you could expect from a joystick at these positions

One other thing we need to know is a key problem we have to account for when using joystick input in games is the dead zone.

The dead zone is a small circle around the center of the joystick where the joystick returns an ever so slightly shifting value. We call this ever so slight shifting jittering, because it jitters around a point.

When using joysticks we need to work around the dead zone, so that whatever is attached to the joystick does not jitter.

The easiest way to do this is simply to ignore input near the center. We can do something like this (pseudo C++/C# code):

While this works, there are certainly better ways to get around this. I would highly suggest reading this article from Third-Helix, as it explains dead zones and the various ways to counter dead zones clearly with pictures to help show each counter method.

Later in this tutorial, we will be using the Scaled Radial Dead Zone method from that article because it is a great method that works great for this project.

Note

Third-Helix (now KickBomb Entertainment) is not in any way involved with this tutorial, or RandomMomentania. I just learned a lot reading the article, and have used the example code in the article several times for various projects.

As I said before, I would highly suggest reading the article. It is a great read and the example code works great in C#, and can easily be translated to other programming languages like GDScript, C++, Python, etc.

Now that we know the basics of real life joysticks, let’s talk about what is different with virtual joysticks.


Most virtual joysticks keep giving input if the user has started to move the joystick, which is like normal joystick, but it continues to give input even when the joystick is moved beyond the joystick’s boundaries.

This is different from real joysticks because they are generally designed never to go beyond a certain range of motion. If you could get the joystick beyond it’s normal contained motion, you’d likely break the joystick and it would not continue to give input.

The red circle is the touch/click, while the white circle is the joystick.

The reason behind this functionality is because it is easy to move the touch/mouse too slightly far and have it fall off the virtual joystick.

Instead of no longer sending input, most virtual joysticks just continue to send input like if your finger/touch is still on the joystick.

From a implementation point of view, all this is doing is clamping the input from the virtual joystick so that it stays withing a certain radius from the center of the joystick.


Sometimes game developers make their virtual joysticks invisible until the player touches a certain portion of the screen.

When a touch is detected within the portion of the screen, a joystick suddenly appears under their finger and they can then move it like a normal joystick.

Obviously we cannot do this with real joysticks, as the constraints of the physical world simply do not allow for it.

Implementation wise, this is pretty easy! We just teleport the joystick on the first touch/click so that it is under the touch/click, and then process the joystick like normal.


Other than those differences, game developers generally try to keep the virtual joystick as close as possible to the real life joystick.

This includes things from dead zones, to mapping movement to a two dimensional input.

But enough jabbering! Let’s make some virtual joysticks!

Project Setup

Go ahead and download the starter assets for this tutorial and extract them somewhere on your computer.

Tip

You can download the starter assets here if you are supporting a patron.

If you are not a patron, you can download the starter assets here.

Open up the project in Godot and open TestScene.tscn

Notice how there is already a few things set up. This is just a simple test scene we will be using to make sure the virtual joystick is working correctly.

Expand the UI CanvasLayer node. You’ll find two TextureRect nodes: Joystick and Joystick2. Expand Joystick and you’ll find another TextureRect node, Joystick_Ring.

Joystick is the background of the virtual joystick and will be where all of the code for our virtual analog joysticks live. Joystick_Ring is the inner ring that will move with the player’s touch/finger/mouse.

Here is a GIF of the working virtual joystick to give an idea of what each TextureRect node will do:

Coding the joysticks

Let’s write the code for the analog joysticks. Select the Joystick node, create a new script and call it Joystick.gd.

Add the following code to Joystick.gd:

This is a lot to go through, so let’s break it down into smaller, more manageable chunks.

First, notice how we have the tool keyword at the top of the file. This makes it where Godot will run this script in the editor! We do this so we can visually see what happens when we change properties of the joystick in the editor.

Because we are using the tool keyword, we will need to do some additional checks, since we do not want the joystick to look for input while in the editor (as we have no use for it).

Next let’s look at the class variables, which are variables outside of any/all functions.

  • Joystick_Start (signal) : This signal will emit when the joystick starts updating.
  • Joystick_End (signal): This signal will emit when the joystick stops updating.
  • Joystick_Updated (signal): This signal will emit when the joystick updates.
  • radius : The radius of the circle that the inner joystick ring can stay within. We export this variable using the export keyword so we can change this value in the editor.
  • use_screen_rectangle : A boolean for deciding whether or not the joystick will use the screen rectangle.
  • screen_rectangle : A portion of the screen where the player can press/touch, and the joystick will appear under said press/touch. screen_rectangle is only used if use_screen_rectangle is true.
  • editor_color : The color of the screen rectangle in the editor.
  • joystick_vector : The results/axes-values for the joystick. We will change this vector just like a real joystick, so we can use it exactly like how we’d use a real joystick.
  • joystick_touch_id : The touch ID for the finger/mouse being used for this joystick. (We will use a null ID to represent the mouse)
  • joystick_active : A boolean for tracking whether this joystick is active or not, or to put it another way: We will use this boolean to track whether the joystick is being used or not.
  • joystick_ring : The inner ring/part/circle of the joystick.

Now that we have looked at all of the class variables, let’s start looking at the functions.

Looking through _ready

The very first thing we do is check to see whether this code is running in the editor or not. Because we added the tool keyword at the top of the script, this code will run in both the editor and in game.

We can check the code is running in the editor by using Engine.editor_hint, which should only return true while in the Godot editor, and false otherwise.

Because we only want the rest of the code in _ready to run if we are not in the editor, we check to see if editor_hint is false.

Next, we get the inner joystick ring node and assign it to joystick_ring.

Then we move the inner ring to the center of the joystick. We do this by calling a function called get_center_of_joystick, which will return the center position of the joystick. Because TextureRect nodes are positioned using the top left corner, we need to subtract half of the size of the inner ring so it is perfectly centered within the joystick.

Finally, we check to see whether this joystick is going to use a portion of the screen or not. If the joystick is going to use a portion of the screen, we make the joystick invisible, while if it’s not we make the joystick visible.

Looking through _draw

Unlike in _ready where we are checking to see if we are in the editor and not running the code if we are, in _draw we first check to see if we are in the editor and we do run the code if we are.

This is because the code in _draw will be only for visually showing how the exported class variables effect the joystick.

Alright, so if we are in the editor, the first thing we do is get the screen_rectangle class variable and assign it to a new local variable called draw_screen_rect.

We do this because the _draw function draws relative to the position of the node. We are wanting to draw screen_rectangle relative to the screen, not the node, so we can show which portion of the screen the joystick will respond to.

To make draw_screen_rect use local coordinates instead of global ones, all we do is subtract the global position of the joystick from draw_screen_rect, and that will put the Rect2 in local coordinates. This is what we want because this places draw_screen_rect in the same position relative to the screen.

Next, we draw four lines, one for each side of the draw_screen_rect rectangle.

The reason we are using four draw_line calls instead of one draw_rect to draw the rectangle is because we cannot set the width of the drawn rectangle using draw_rect. draw_rect always draws rectangle with a 1px line, which is too small to see with most monitors.

Finally, the last thing we do is draw a circle for the radius the inner joystick ring is clamped to be within.

Looking through get_center_of_joystick

What this function does is it returns the center position of the TextureRect node the joystick script is attached to.

Remember, we are assuming the joystick script is attached to the outer circle of the joystick, so by getting the center position of the TextureRect, we are also getting the center position of the joystick.

To get the center position, we take the local position of the joystick TextureRect and add half of the joystick TextureRect’s size to it.

This gives us the center of the joystick TextureRect in global space (technically in the parent node’s space).

To convert the center from global space to local space, we subtract the global position of the joystick TextureRect.

Finally, we return the calculated center position.

Final notes

As much as I hate to cut the explanation of the code short, we’ll have to leave going through _input for part 2.

If we went through _input in this tutorial part, this tutorial part would be twice as long as it is now!

I don’t know about you, but reading a giant tutorial post can be overwhelming. (Not to mention it’s much easier to edit two medium sized tutorial parts, then it is to edit one giant tutorial part.)

_input is a fairly large function whose code controls the entire joystick. This makes performance pretty good, since the joystick is only updating when the joystick moves, but the downside is that the function is large and a tad complex.

In part 2 we will go over _input, and will make a simple player who will utilize the newly created joysticks.

Warning

If you ever get lost, be sure to read over the code again!

There is no project to download for part 1. There is a download for the finished project in Part 2!

The reason there is no project to download is because we have not fully gone through Joystick.gd, we have not explained how to setup the joysticks, and other than adding Joystick.gd, there is nothing different than the starter assets!

Leave a Reply

Your email address will not be published. Required fields are marked *