The development of mobile games requires a special approach to control, because users do not have physical buttons under their fingers. The virtual joystick is becoming the de facto standard for action, RPG and platform games on the Android platform. Implementing such a control in the engine Unity may seem like a difficult task for a beginner, but in reality the process comes down to competently setting up the interface and writing a few lines of code.
In this article we will look at creating a joystick from scratch, without relying on third-party assets, which will give you full control over the logic of the work. You will learn to separate input logic and character reaction, which is the best practice in game development. Understanding these principles will allow you to easily adapt controls to any task, from simple walking to complex aiming.
Preparing the Stage and Canvas System
The first step is to create the right container for the controls. In Unity, all UI elements must be inside an object Canvas. For mobile games, it is critical to set the rendering mode in Screen Space - Overlayso that the joystick is always displayed on top of the game scene, regardless of the camera position.
Create a new Canvas through the menu GameObject โ UI โ Canvas. Immediately add the component Canvas Scaler and set the mode Scale With Screen Size. This ensures that your joystick will have the same relative size on different smartphone screens, from compact models to large tablets. Without this setting, elements may appear too small or cover half the screen.
Inside the Canvas, create an empty object and name it JoystickContainer. This is where we will place the graphical controls. It is recommended to immediately anchor this container in the lower left corner of the screen using the anchor tools (Anchors) in the inspector. This arrangement is most ergonomic for the right hand, if the joystick is responsible for movement.
โ ๏ธ Attention: Make sure that in the project settings (
Edit โ Project Settings โ Player) in the Other Settings tab there is a checkmarkForce talkedor the appropriate orientation settings so that the interface does not flip unexpectedly while playing on the device.
Creating graphic controls
The visual part of the joystick consists of two components: the base (background) and the handle (lever), which the user will drag. Inside JoystickContainer create an object Image and name it Background. Give it a color or load a circle sprite. The size of the base is usually made about 100-150 pixels in diameter, so that it is convenient to hit with a finger.
Next, create a child object for Background and name it Handle. This is the stick itself. Its size should be smaller than the base, for example, 50-70 pixels. It is important that the initial coordinates Handle are strictly (0, 0) relative to the parent, that is, it must be in the center of the base when the game starts.
To improve the responsiveness of the interface, add a component Raycast Target to both images. However, for optimization, you can leave this flag active only on Background, since the base is the main touch zone. If you want the joystick to respond to touches anywhere on the screen (floating joystick), the event processing logic will be different, but for a static version this structure is ideal.
Use translucent sprites for the joystick background so that they do not block the view of the gameplay, but remain visible when touched.
Writing a joystick logic script
Now let's move on to programming. Create a new C# script named Joystick. The main task of this script is to track screen touches, calculate the displacement vector of the finger from the center of the joystick, and limit the movement of the handle within the base. We will use the namespace UnityEngine.EventSystems to work with the interface.
In the code we will need references to the RectTransform of the background and handles, as well as a variable to store the current input direction. We implement interface methods IDragHandler, IPointerDownHandler and IPointerUpHandler. The OnDrag method will be called every frame as the finger moves across the screen, allowing the pen position to be updated in real time.
using UnityEngine;using UnityEngine.EventSystems;
public class Joystick: MonoBehaviour, IDragHandler, IPointerDownHandler, IPointerUpHandler
{
public RectTransform background;
public RectTransform handle;
private Vector2 inputVector = Vector2.zero;
public Vector2 Direction
{
get { return inputVector; }
}
public void OnPointerDown(PointerEventData eventData)
{
OnDrag(eventData);
}
public void OnDrag(PointerEventData eventData)
{
Vector2 pos;
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(background, eventData.position, eventData.pressEventCamera, out pos))
{
pos.x = (pos.x / background.sizeDelta.x);
pos.y = (pos.y / background.sizeDelta.y);
inputVector = new Vector2(pos.x 2 - 1, pos.y 2 - 1);
inputVector = (inputVector.magnitude > 1.0f)? inputVector.normalized: inputVector;
handle.anchoredPosition = new Vector2(
inputVector.x * (background.sizeDelta.x / 2),
inputVector.y * (background.sizeDelta.y / 2)
);
}
}
public void OnPointerUp(PointerEventData eventData)
{
inputVector = Vector2.zero;
handle.anchoredPosition = Vector2.zero;
}
}
Note the usage RectTransformUtility. This class allows you to correctly translate the coordinates of a touch on the screen into the local coordinates of the interface rectangle. Without this conversion, the joystick will not work correctly when the screen resolution or device orientation changes. Vector normalization ensures that the input value never exceeds one, even if the finger goes far beyond the base.
Why use RectTransform instead of Transform?
In Unity UI, all positioning is based on anchors and relative sizes. Using a regular Transform would break the layout when resizing the window or running on different devices, since it operates with world coordinates, and not coordinates within the Canvas.
Character integration and control
Once the joystick is ready to output data, you need to make the character react to it. Create a script PlayerController and attach it to your hero. In this script, we need to get a reference to the component Joystickthat we just created and read the property Direction in the FixedUpdate.
Use FixedUpdate required for physics movement logic, since the Unity physics engine updates at a fixed frequency. If you move an object through Transform.position in normal Updatethe movement may appear jerky or go through walls. To move, use the component CharacterController or Rigidbody.
An example of implementing movement via CharacterController:
void FixedUpdate{
Vector2 input = joystick.Direction;
Vector3 moveDirection = new Vector3(input.x, 0, input.y);
if (moveDirection.magnitude >= 0.1f)
{
float targetAngle = Mathf.Atan2(moveDirection.x, moveDirection.z) * Mathf.Rad2Deg;
transform.rotation = Quaternion.Euler(0f, targetAngle, 0f);
controller.Move(moveDirection.normalized speed Time.fixedDeltaTime);
}
}
This approach allows you to share responsibility: the joystick is responsible only for input, and the character controller is responsible for physics and animation. This makes the code modular and easy to maintain. You can replace the joystick with control from a gyroscope or keyboard by simply changing the data source in the controller, without rewriting the movement logic.
Event setup and optimization
For a full game, one movement is not enough. Often you need to combine the joystick with action buttons, such as jump or attack. Unity Event System allows you to handle multi-touch, that is, pressing a joystick and a button at the same time. Make sure there is an object in your scene EventSystemthat is created automatically with Canvas.
Optimization is important for mobile devices where battery and processor resources are limited. Avoid creating new Vector2 objects inside a loop OnDragif possible, although in this case the allocations are minimal. It is more important to configure the layer (Layer) for the UI so that the raycast rays do not check unnecessary objects in the scene, which can be done in the project physics settings.
It is also worth adding visual feedback. For example, change the transparency of the joystick when it is not in use, or add a slight offset in the direction of pressing when touching starts. This improves the tactile feel of the game, making controls more predictable for the user.
| Parameter | Recommended value | Impact on the game |
|---|---|---|
| Base size | 150-200 px | Easy to hit with a finger |
| Dead zone | 0.1 - 0.2 | Drift protection character |
| Refresh rate | FixedUpdate | Smoothness of the physical model |
| Transparency | 50% (rest), 100% (active) | Overview of the game world |
โ๏ธ Check before assembly
Testing on real devices
The emulator in Unity Editor is useful for quickly testing logic, but it cannot fully simulate multi-touch and the specifics of Android touch screens. Be sure to collect APK the file and test the game on a real smartphone. It often happens that on a PC the controls work perfectly, but on the phone the joystick โgoes awayโ or does not respond to quick swipes.
When testing, pay attention to the safe display zone (Safe Area). On modern smartphones with camera cutouts or curved screen edges, UI elements may overlap with system elements. Use the SafeArea script or built-in Unity tools to correctly position the joystick in the visible area.
Test the joystick in different screen orientations, if your game supports them. In portrait mode, the joystick is usually positioned lower to avoid obstructing your view, while in landscape mode it is moved towards the edge. Adaptive layout via Anchors will help solve this problem without creating separate scenes.
โ ๏ธ Attention: On some Android devices with high DPI, UI scaling may not work correctly. If the joystick looks too small, check the settings
Canvas Scalerand make sure the value Reference Resolution corresponds to your design (for example, 1920x1080).
The main secret of smooth control is the separation of input logic (Joystick) and movement logic (PlayerController), connected through public properties.
Why does the joystick not move with your finger?
Most likely, the problem is in the coordinates. Make sure you use RectTransformUtility.ScreenPointToLocalPointInRectangle to convert screen coordinates to local ones. Also check that the joystick object has a component Image with a checkmark Raycast Target, otherwise touch events will not be transmitted to the script.
How to make a joystick that appears at the point of contact?
To do this, you need to change the logic OnPointerDown. Instead of using a fixed position, move the entire object JoystickContainer to the touch point (eventData.position). At the same time, make sure that the joystick does not go beyond the boundaries of the screen, limiting its coordinates to the width and height of the display.
Can this joystick be used to control the camera?
Yes, the logic is the same. Instead of moving the character along the X and Z axes, you will rotate the camera along the X and Y axes. Typically, the right stick controls the view. Just pass the values inputVector.x and inputVector.y to the camera rotation method, inverting the Y axis if necessary.
How to add a Dead Zone for a joystick?
A dead zone is needed to prevent the character from moving on its own due to shaking finger In the joystick script, add a check: if inputVector.magnitude is less than a threshold (for example, 0.1f), force the vector to zero before returning it to the outside world.