RandomMomentania

Godot Touch Screen Joystick Part 2

In the first part of this two part tutorial series, we started coding the joysticks and were going through the code.

In this part, we will finish going through the code, and will make a simple player that shows how we can use the joystick in our games.

Let’s jump right in!

Continuing from where we left off

In the last part we were looking through Joystick.gd, and we stopped at the _input function.

Let’s continue from where we left off. I would suggest either having part one open in another browser tab or have the Godot editor showing Joystick.gd so it’s easier to follow along.

Going through _input

This is where all of the actual joystick related code happens, and because of that the function is quite large.

To break it down into more manageable chunks, let’s go through each input event check separately.

Tip

Because of the sheer volume of code in _input, this section may be a little harder to follow.

I highly suggest downloading the finished project, which has comments explaining each part of the code, if you have a hard time following this section!

The comments explain what the code is doing right there by the code, so you know exactly which part of the code is doing what!

So, working our way down, we’ll start with if event is InputEventScreenTouch or event is InputEventMouseButton:

Press/Release events

First, we check to see if the input event is a InputEventScreenTouch or InputEventMouseButton, meaning the event is a press or a release.

The next thing we do is make a local variable called event_is_pressed so we can use different code depending on whether the touch is a press or a release.

We then have to figure out which event we are being given. Both events have a pressed variable, which will only be true when the event is a press event and not a release event.

Note

You may be wondering why we are checking to see which type the input event is, since they both have a pressed variable. After all, why do we not just use event_is_pressed = event.pressed?

They happen to have the same property in this case, but it is good habit to not assume they will, and therefore treat them separately.

This also gives some additional flexibility and future proofing. While we can hope that the InputEvent’s we are using now always have the same variable (pressed), it may not always be this way in the future!

If the event is a press and not a release, then we need to check whether the joystick is already active or not.

We do this because we only really care about press events if the joystick is not already tracking a touch/mouse already, since we do not want other touches/mice to effect a already in use joystick.

If the joystick is not already in use/active, then we check to see whether the joystick is using a portion of the screen, or whether it’s just a static joystick.

Note

Some of the code is the same regardless of whether you are using a portion of the screen, or using a static joystick.

If the joystick is using a portion of the screen, we first need to get the position and ID associated with the input event.

Because InputEventScreenTouch provides both a position (in global space) and a ID, if the event is a screen touch event, we set the variables accordingly.

If the event is a InputEventMouseButton event, we have to get the position and ID manually. We can get the mouse position using get_global_mouse_position, which is provided as part of the Control node the TextureRect extends. For the ID we set it to null, since there is no way to get an ID from a mouse.

Once we have the position and ID for the event, we can check whether the input event happened within screen_rectangle. We do this by using the has_point function, which is provided by Rect2.

If the event position is inside screen_rectangle, we then need to do some additional processing and make the joystick active.

First, we move the joystick to the event position, so it appears under the touch/mouse. All we do is set the global position of the TexturedRect to the event position, offsetting the position slightly to account for the size of the TexturedRect.

Then we set joystick_touch_id to event_ID, so when the event with the same ID moves, we can update the joystick.

We then set joystick_active to true, and make the joystick visible.

Because the joystick is teleported to the touch position, we need to reset the position of the inner ring so that it will be in the center of the newly placed joystick. The code for doing this is exactly the same as the code we used in _ready to center the inner ring.

We set joystick_vector to a empty Vector2 since the joystick inner ring has been reset to the center of the joystick joystick.

Finally, we emitting the Joystick_Start signal since the joystick is now active.


So, all of that above was for if the joystick is using a portion of the screen.

If the joystick is not using a portion of the screen, is static, then we need to do thing a little differently.

First, we get the position and ID of the input event. The code for getting the position and ID is exactly the same as when the joystick is using a portion of the screen.

Then we need to check if the event is within the radius of the joystick.

First we get the center of the joystick in global coordinates. We do this by adding rect_global_position to the result from get_center_of_joystick. This will give us the position of the joystick in global coordinates.

Then we subtract the event position from the global coordinates of the joystick. This gives us a vector pointing from the center of the joystick towards the event position. We get the length of that vector using the length function, and then we check to see if it is equal to, or lower than, radius.

If the event position is within the radius of the joystick, we then need to set some variables.

First, we set the joystick_id to event_ID, so when the event with the same ID moves, we can update the joystick.

We then set joystick_active to true, since the joystick is now active and has a event over it.

We then calculate the new joystick_vector value. Because joystick_vector is the ‘input’ returned from the joystick, and the joystick is static, we need to do some math to figure out where the event is relative to the center of the joystick.

To get the relative position of the event in relation to the center of the joystick, we first need to get the center of the joystick in global space. We do this by adding rect_global_position to get_center_of_joystick.

Next we subtract event_position from the center of the joystick so we get a vector pointing at the event from the center of the joystick.

Now, we need to convert the vectoring pointing towards the event so that it’s within a one to negative one radius circle.

Because we know the event position has to be within the radius of the joystick, we can convert the vector pointing towards the event position to a normalized vector by dividing it by the radius of the joystick.

This will give us a vector that only has number within a one to negative one range, which is what we expect a joystick to return.

Now that we have joystick_vector updated, we need to move the joystick inner ring so that it is at the event position.

Since the event position is within the radius of the joystick, all we need to do is set the position of the joystick inner ring to the position of the event, offset it by the size of the inner ring so it’s centered on top of the input event.

Finally, we emit the Joystick_Start signal since the joystick is now active.

Note

At this point the joystick can now react correctly when there is a touch/click, but that’s it!

The explanation of the code so far has almost all been just for handling presses!

We still have to handle releases too!

Now that we’ve handled all of the code for presses, we need to go through the code for releases too!

Thankfully the code shorter!

If event_is_press is not true, then the event must be a release.

If the event is a release, we first check to see if the joystick is active. We only care about release events if the joystick is active, since an inactive joystick is not tracking any event IDs and so has no reason to care about releases.

If the joystick is active, we then need to get the ID for the event.

If the event is a InputEventScreenTouch event, we get the event provided from the event, while if the event is a InputEventMouseButton, we use null.

Then we check to see if the event that was just released has the same ID as joystick_touch_id, which is the variable we are using to track the event that controls the joystick.

If the joystick has the same ID, we need to reset some variables and make the joystick inactive.

First, we move the joystick inner ring back to the center of the joystick, and we reset joystick_vector to a empty Vector2 since the joystick is no longer active.

We then set joystick_touch_id to null, and set joystick_active to false, so we no longer are tracking any events and the joystick is no longer active.

We then check to see if the joystick is using a portion of the screen. If it is, we make the joystick invisible.

Finally, we emit the Joystick_End signal since the joystick is no longer active.

Tip

Now the joystick can respond to both presses and releases!

Movement events

Now let’s look at the code under if event is InputEventScreenDrag or event is InputEventMouseMotion:.

First we check to see if the event is a InputEventScreenDrag event, or a InputEventMouseMotion event. Either event means a input event is moving across the screen!

Since a inactive joystick has no reason to track moving events, we check to see if the joystick is active or not.

If the joystick is active, we then get the ID and position of the movement event.

Because InputEventScreenDrag provides both a position (in global space) and a ID, if the event is a screen touch event, we set the variables accordingly.

If the event is a InputEventMouseMotion event, we have to get the position and ID manually. We can get the mouse position using get_global_mouse_position, which is provided as part of the Control node the TextureRect extends. For the ID we set it to null, since there is no way to get an ID from a mouse.

We then check to see if the input event is equal to joystick_touch_id. If it is, then this is the event we are tracking, and we need to update the joystick.


First we need to know whether the input event is within the joystick’s radius or not. We can do this by getting the position of the input event relative to the center of the joystick.

To get the relative position of the event in relation to the center of the joystick, we first need to get the center of the joystick in global space. We do this by adding rect_global_position to get_center_of_joystick.

Next we subtract event_position from the center of the joystick so we get a vector pointing at the event from the center of the joystick.

To check whether the input position is within the radius of the joystick, we see if the length of the vector pointing towards the event is less than or equal to radius.


If the event is within the radius of the joystick, we can use code similar to the static joystick for updating the joystick’s position and vector.

Because we know the event is within the radius of the joystick, we can set the position of the inner ring of the joystick to the position of the event, offset so the inner ring is centered on top of the joystick.

We know the event position has to be within the radius of the joystick, so we can convert the vector pointing towards the event position to a normalized vector by dividing it by the radius of the joystick.

This will give us a vector that only has number within a one to negative one range, which is what we expect a joystick to return. This is exactly the same process we used for a press event on a static joystick.

Finally, we emit the Joystick_Updated signal since the joystick has been moved.


The last thing we need to do is update the joystick when the motion event is outside of the radius of the joystick.

First, we need to figure out the new joystick_vector value.

We do this by creating a vector at the center of the joystick that points towards the input event. Instead of dividing by radius to get a normalized vector, instead we use the normalized function.

The normalized function will return a vector with values on a 1 unit radius circle, which is what we expect a joystick to return.

Tip

You may be wondering why we are not just using normalized even when the event is within the radius of the joystick.

The reason we are not using normalized is because when the joystick is closer to the center of the joystick, we want the value in joystick_vector to be smaller.

If we used normalized, we’d always get a vector on the edge of a circle with a radius of one. This means the joystick would always act as if the event is at the edge of the joystick’s radius, even when it’s close to the middle.

Next we set the position of the joystick inner ring to the center of the joystick, add a offset the so the center of the inner ring is at the center of the joystick.

To place the inner ring of the joystick at the current position around the radius of the joystick, we subtract the joystick vector minus the radius of the joystick.

This will place the joystick inner ring on the edge of the joystick, at the current normalized position.

Finally, we emit the Joystick_Updated signal.

Testing the joysticks

With all of that done, technically the joysticks are ready for use, but we still need to set the editor values up, and we still need to make something that uses them!

Let’s make a simple player that moves with a static joystick on the left, and rotates with a joystick that uses a portion of the screen on the right.

Select the Player KinematicBody node and make a new script called Player.gd.

Add the following code to Player.gd:

Let’s go over what this script does, starting with the class variables:

  • joystick_one_path : A exported NodePath, which will allow us to select a joystick to use for the player in the editor.
  • joystick_two_path : Another exported NodePath, which will allow us to select a different joystick from the editor.
  • joystick_one : A variable to hold the first touch screen joystick. We’ll use this joystick to move the player around.
  • joystick_two : A variable to hold the second touch screen joystick. We’ll use this joystick to rotate the player along with the joystick.
  • MOVE_SPEED : The speed the player moves through the world at.
  • JOYSTICK_DEADZONE : The dead zone for the touch joysticks. We are using a higher value than we would normally so when the game is on a touch screen, tiny movements do not cause the player to jitter around.

Going through _ready

First we get the joysticks and assign them to the proper variables.

Note

We are not checking to see if the nodes are joysticks, nor are we checking to see if the NodePaths even point to a node or not.

Ideally you’d want to make sure there is a node at the NodePath, and that it’s a joystick. For this example player, it’s not a big deal so we’re not going to check, but if you are using this joystick in bigger projects, I would highly suggest doing some checks.

Then we connect the “Joystick_Updated” signal from the second joystick, which we will be using for rotation.

We could do this for both joysticks, only updating when the joysticks send their various signals, but to better show how the joysticks work, we’ll not be using any signals for the first joystick.

Going through _physics_process

Next let’s go through the code for movement using the first joystick.

First we check to see if the length of the first joystick’s joystick_vector is farther than half of the JOYSTICK_DEADZONE constant.


Remember how I said JOYSTICK_DEADZONE is really large, and that was because of the touchscreen. This mostly applies for the joysticks that use a portion of the screen.

The reason behind this is because when you have a joystick that uses a portion of the screen, the joystick teleports under the touch/click. This makes it much harder to rest your finger/mouse on a portion of the screen and not accidentally move/rotate/other the player.

The red circles are the dead zone area

As you can see in the picture above, a dead zone of 0.2 is rather tiny, just a mere 20% of the joystick’s area.

This is fine for static joysticks, since when you want to stop using them you just stop pressing. Since the player stops pressing when they no longer want to use it, we can get away with a much smaller dead zone value and gain a lot of precision by using a smaller value.

For joysticks that use a portion of the screen however, many users like to rest their finger/mouse near the center of the joystick when they are not using it.

Using a higher dead zone value for the joystick that uses a portion of the screen and a lower value for static joysticks gives the best of both worlds: Precision for the static joystick, and comfort for the screen portion joystick.

Tip

Everyone has their own opinions and preferences. This is by no means true for every player/user.

You will likely need to tailor your joystick’s dead zones with the preferences your players have, and/or make configurable dead zones.


If the joystick is farther than the dead zone, we then move the player relative to the joystick’s joystick_vector variable so that it moves where the joystick is pointing.

Notice how we are using the negative joystick_vector. This is because our touch screen joystick is inverted in comparison to Godot’s coordinate system. By making joystick_vector negative, we make it follow the same directions Godot uses for it’s scenes.

Tip

If you do not want to worry about remembering to invert joystick_vector, you can invert joystick_vector in Joystick.gd.

Whenever you update joystick_vector in Joystick.gd, just add joystick_vector = -joystick_vector after updating joystick_vector and then it will be inverted automatically.

Going through rotation_updated

First, we check to see if the second joystick’s length is longer than JOYSTICK_DEADZONE.

If it is, we then change the player’s rotation using the angle_to_point function. The position we input into that function is the player’s global position, plus the joystick_vector from the second joystick.

This makes the player rotate to look in the same direction as the joystick is pointing.

Setting up the scene

The last thing we need to do is get everything setup.

First, select Player. You’ll find there are a couple script variables you can assign. Go ahead and assign the node paths to the proper joysticks. It should look something like this once you have it set up:

This will set the joysticks to the proper variables when the project is run.

Now we need to set up the joysticks themselves. First, let’s setup Joystick. which is the joystick on the left.

Select Joystick and change the script variables to the following:

Note

Notice how we are setting Screen Rectangle, even though the joystick is a static joystick. There is no real reason for this, other than it’s leftovers from when I was testing two screen portion using joystick (which works by the way!)

Also, you may need to move the node slightly to get the editor to redraw the joystick(s) using the newly inputted values.

For the right joystick, Joystick2, we need use the following script variables:

And then the joysticks are setup and everything is ready do go! Go ahead and give the game a try!

You can play the project by hitting the play button in the top right corner, or by pressing F5.

Final Notes

Now you have a script you can use to make touchscreen joysticks!

Because of how we wrote the script, the joysticks are not tied to any game play. This means it should be fairly easy to drop into a project, and start using it without having to change the code that makes the joysticks work!

This allows you to easy add touchscreen joysticks to your mobile games, add touchscreen controls for your games that are already using joysticks, or just another tool in your game development toolbox.

Warning

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

You can download the finished project here.

I hope to see you in the next tutorial series!

Feel free to leave any questions and feedback in the comments section below or through the Contract section!

If you have any ideas on future tutorials please let me know by following the instructions in the Contract section above!

Tip

Like the tutorial? Please consider supporting RandomMomentania by buying one of our paid games!

By supporting RandomMomentania you allow us to continue to make great games and tutorials that we can share with the world!

11 thoughts on “Godot Touch Screen Joystick Part 2

    1. Thanks Alex!

      Implementing the joystick into a 3D game should be very similar to implementing a traditional game controller joystick into a 3D game, albeit with some minor adjustments. Currently I have no plans to make a tutorial showing how to implement the touchscreen joystick into a 3D game, but perhaps in the future I will a part 3 showing how to use it in some sample project(s), though no promises.

      Thanks again! 🙂

  1. HI, thank you very much for this tutorial, it’s very helpful 🙂 Can I use it for my next game ? 🙂 Thanks again 🙂

  2. Invalid get index ‘joystick_vector’ (on base: ‘Control’). Could you tall, how i can fix this proble?

    1. Hey,
      Hard to say what could be causing the problem right off. The most likely problem is that the joystick_vector variable is not defined in the Control you are using, but that is just a guess really. Without seeing the code files, it is hard to say.

      Please feel free to make a post on the Godot Community forums with your problem, and I can try to help there! That way, not only can others help, but it will be easier to communicate back and forth.

      Godot Community Forums URL: https://godotforums.org/

      Thanks!

    1. Hey,
      Right now I do not have he time to make more tutorials, as I am busy with other projects. However, I believe HeartBeast has an excellent platformer tutorial series. HeartBeast makes Godot YouTube tutorials, so you should be able to find it by searching “HeartBeast platformer godot tutorial” on YouTube.

Comments are closed.