How to: Create a virtual joystick for a mobile UI

ui tutorials

7/19/2024

Mihail Todorov

In the ever-evolving world of mobile gaming, providing intuitive and responsive controls is crucial for an engaging user experience. One popular solution is the virtual joystick, a touch-based control system that mimics the physical joystick. In this blog post, we’ll explore how to create a virtual joystick for your mobile games, enhancing gameplay and user interaction.

Previewing the sample

To see the sample live you can find the whole project in the ${Gameface package}/Samples/uiresources/UITutorials/MobileControls directory.

Creating the joystick itself

To get started we first need to create the joystick itself.

To do that we’ll add the following HTML code:

1
<div class="joystick">
2
<svg class="joystick-outline" viewbox="-7 -7 310 310">
3
<path
4
d="M150,0 A150,150 0 1,1 150,300 A150,150 0 1,1 150,0"
5
fill="none"
6
stroke="rgba(255, 255, 255, 0.4)"
7
stroke-width="5"
8
stroke-dasharray="205 30"
9
stroke-dashoffset="221"
10
/>
11
</svg>
12
<div class="joystick-handle-container">
13
<div class="joystick-handle"></div>
14
</div>
15
</div>

Here we have the joystick outline which will be a big circle with a dashed border, hence why we are using an SVG to be able to control the dashes and the spacing using the stroke-dashoffset and stroke-dasharray properties._createMdxContent

Finally to complete the look of our joystick we’ll style it using the following CSS:

1
body {
2
width: 100vw;
3
height: 100vh;
4
overflow: hidden;
5
margin: 0;
6
padding: 0;
7
}
8
9
.joystick {
10
width: 20vmax;
11
height: 20vmax;
12
position: absolute;
13
bottom: 1vmax;
14
left: 1vmax;
15
background-color: rgba(0, 0, 0, 0.2);
16
border-radius: 50%;
17
}
18
19
.joystick-outline {
20
width: 100%;
21
height: 100%;
22
}
23
24
.joystick-handle-container {
25
position: absolute;
26
top: 0;
27
left: 0;
28
right: 0;
29
bottom: 0;
30
}
31
32
.joystick-handle {
33
position: absolute;
34
width: 8vmax;
35
height: 8vmax;
36
border-radius: 50%;
37
background-color: rgba(255, 255, 255, 0.6);
38
top: 50%;
39
left: 50%;
40
margin-left: -4vmax;
41
margin-top: -4vmax;
42
}

With this, we’ll get the following look to our joystick virtual joystick preview

Interacting with the joystick

Testing our solution

Since the virtual joystick is intended to be used in a mobile game with touches we first need to simulate an enviroment to test our UI. To do this, we can set the following in the Player.bat file:

Terminal window
1
--input touch

And so our .bat file the command will look like this:

Terminal window
1
start "Player" /d "%wd%" "..\Player\Player.exe" --player "--url=coui://uiresources/Internal/DragAndDrop/drag-n-drop.html" --input touch

Adding the touch logic

The first thing we need to do is attach a listener for the touchStart event.

1
const joyStickHandle = document.querySelector(".joystick-handle");
2
const joyStickContainer = document.querySelector(".joystick");
3
4
joyStickContainer.addEventListener("touchstart", handleJoystickStart);
5
6
function handleJoystickStart(event) {}

Then since we’ll be dragging our finger to move the joystick we’ll need to add event listeners for the touchmove and touchend events.

As we want to keep the touchmove event going even after leaving the joystick container we’ll add these events to the document and we’ll attach them on touch start so that they don’t intefere with other touch events.

1
function handleJoystickStart(event) {
2
document.addEventListener("touchmove", handleJoystickMove);
3
document.addEventListener("touchend", handleJoystickEnd);
4
}
5
6
function handleJoystickMove(event) {}
7
8
function handleJoystickEnd() {
9
document.removeEventListener("touchmove", handleJoystickMove);
10
document.removeEventListener("touchend", handleJoystickEnd);
11
}

Calculating the correct position of the joystick handle

Next we need to move the handle itself.

For our current case there are two approaches we can take:

  1. Move the handle on both X and Y axis and restrict it to the circle
  2. Rotate the handle then move it only on the Y axis

In this tutorial we’ll do approach 2, because it’s much easier to actually restrict the handle going only on a single axis, we can later map the directions much easier because of the angle of rotation and finally we have the added benefit of being able to easily provide the distance between the center and the handle.

To do this, we first need to calculate the angle between the center of the joystick and the center of the handle.

What we want to achieve is to have a polar coordinate system with the center of the joystick to be the center of the FileSystem.

We can do that by utilizing the built-in JavaScript Math.atan2 function. This function calculates the angle between the 0,0 coordinate and the passed y and x GeolocationCoordinates.

In our case the 0,0 point should be the center of the joystick instead of the top left of our viewport. This is why we’ll make a util function called calculateAngle

1
function calculateAngle(centerX, centerY, pointX, pointY) {
2
const deltaX = pointX - centerX;
3
const deltaY = pointY - centerY;
4
const angleInRadians = Math.atan2(deltaY, deltaX);
5
let angleInDegrees = (angleInRadians * 180) / Math.PI + 90;
6
if (angleInDegrees < 0) angleInDegrees += 360;
7
return angleInDegrees;
8
}

Here we create a deltaX and deltaY constants so that we can simulate the 0,0 point as the center of our joystick.

The atan2 function will return our angle in radians so we need to convert it to degrees. Here we also add additional 90 degrees to our angle as the polar coordinate system 0 degree angle is to the left of the center, however in CSS the rotation 0 degrees angle starts from the top of the center._createMdxContent

Finally we check if the angle in degrees is less than 0 and we add 360 to it, so it will be positive.

What we are now left with is a coordinate system like this:

virtual joystick preview

Moving the handle

To move the handle we’ll first need to calculate the angle using the util function we’ve previously made. This is why we’ll make a new function that we can pass the touch coordinates to and calculate based on the dimensions and position of the joystick._createMdxContent

1
function calculateCircleAngleAndDistance(clientX, clientY) {
2
const { x, y, width, height } = joyStickContainer.getBoundingClientRect();
3
4
let distance = Math.sqrt(
5
Math.pow(clientX - (x + width / 2), 2) +
6
Math.pow(clientY - (y + height / 2), 2)
7
);
8
9
distance = clamp(distance, 0, height / 2);
10
11
return {
12
angle: calculateAngle(x + width / 2, y + height / 2, clientX, clientY),
13
distance,
14
};
15
}

We get the x and y position of the joystick along with it’s width and height. Then we calculate the distance between the touch point and the center.

We’ve also restricted the distance between the handle and the center so that it doesn’t go past the bounds of the joystick using another util function:

1
function clamp(value, min, max) {
2
return Math.min(Math.max(value, min), max);
3
}

Finally we use the calculateAngle function to get the angle in degrees and we return it along with the distance

Then we use this function to get the distance and angle so that when we touch the joystick the handle will move

1
function handleJoystickMove(event) {
2
const { angle, distance } = calculateCircleAngleAndDistance(
3
event.touches[0].clientX,
4
event.touches[0].clientY
5
);
6
7
joyStickHandle.style.transform = `translateY(${-distance}px)`;
8
joyStickHandle.parentElement.style.transform = `rotate(${angle}deg)`;
9
}

We use event.touches[0] to get the first touch as there may be multiple touches to the joystick.

We also need to do call the handleJoystickMove function in the touchstart event so that when we touch the joystick the handle will move immediately.

1
function handleJoystickStart(event) {
2
handleJoystickMove(event);
3
4
document.addEventListener("touchmove", handleJoystickMove);
5
document.addEventListener("touchend", handleJoystickEnd);
6
}

Returning the joystick to the initial position

Physical joysticks have springs that allow the handle to return to the center when let go, we can do the same by removing the style in touchend event:

1
function handleJoystickEnd() {
2
joyStickHandle.style.transform = "";
3
joyStickHandle.parentElement.style.transform = "";
4
document.removeEventListener("touchmove", handleJoystickMove);
5
document.removeEventListener("touchend", handleJoystickEnd);
6
}

In conclusion

Creating a virtual joystick for mobile games can significantly enhance the user experience by providing intuitive and responsive controls. By following the steps outlined in this tutorial, you should now have a solid understanding of how to implement a virtual joystick in your game. Whether you’re a seasoned developer or just starting, mastering this technique will add a valuable skill to your game development toolkit. Happy coding, and may your games be ever more engaging!

On this page