Skip to content

Getting Started

This guide will help you add a virtual joystick to your Phaser 3 game in just a few minutes.

Here are some examples of the virtual joystick in action across different game genres:

The virtual joystick provides smooth character movement with precise control, perfect for action games where quick and accurate movement is essential.

Brawl Stars style game with virtual joystick

In puzzle games like this Tetris Attack style game, the joystick offers precise control for moving pieces and navigating the game board.

Miner Rush puzzle game with virtual joystick

For games that require rotation mechanics, the virtual joystick provides intuitive control for rotating characters or objects in any direction.

Robot Vacuum game with rotation controls

Install the library using npm:

Terminal window
npm install phaser-virtual-joystick
# or
yarn add phaser-virtual-joystick
# or
pnpm add phaser-virtual-joystick
import { VirtualJoystick } from 'phaser-virtual-joystick';

Add the joystick to your scene in the create() method:

export class GameScene extends Phaser.Scene {
private joystick!: VirtualJoystick;
create() {
// Create the virtual joystick with default settings
this.joystick = new VirtualJoystick({
scene: this
});
// ⚠️ IMPORTANT: Don't forget to add the joystick to the scene!
this.add.existing(this.joystick);
}
}

Listen to joystick events to control your game:

create() {
// ... joystick creation code ...
// Listen to movement
this.joystick.on('move', (data) => {
console.log('Joystick moved:', data.x, data.y);
// data.x and data.y are between -1 and 1
});
// Listen to press
this.joystick.on('press', () => {
console.log('Joystick pressed');
});
// Listen to release
this.joystick.on('release', () => {
console.log('Joystick released');
});
}

Here’s a complete example that moves a player sprite:

import { VirtualJoystick } from 'phaser-virtual-joystick';
export class GameScene extends Phaser.Scene {
private joystick!: VirtualJoystick;
private player!: Phaser.Physics.Arcade.Sprite;
preload() {
// Load your player sprite
this.load.image('player', 'assets/player.png');
}
create() {
// Create player sprite
this.player = this.physics.add.sprite(400, 300, 'player');
this.player.setCollideWorldBounds(true);
// Create virtual joystick
this.joystick = new VirtualJoystick({
scene: this
});
// ⚠️ IMPORTANT: Add joystick to scene
this.add.existing(this.joystick);
// Handle joystick movement
this.joystick.on('move', (data) => {
// Move player based on joystick input
this.player.setVelocity(
data.x * 200, // Horizontal speed
data.y * 200 // Vertical speed
);
});
// Stop player when joystick is released
this.joystick.on('release', () => {
this.player.setVelocity(0, 0);
});
}
update() {
// Update joystick (required for smooth following behavior)
this.joystick?.update();
}
}

The joystick accepts a configuration object with the following options:

interface VirtualJoystickParams {
scene: Phaser.Scene; // Required: The Phaser scene
deadZone?: Partial<StyleConfig>; // Optional: Dead zone styling
baseArea?: Partial<StyleConfig>; // Optional: Base area styling
stick?: Partial<StyleConfig>; // Optional: Stick styling
stickIcon?: Phaser.GameObjects.Text; // Optional: Icon on the stick
bounds?: { // Optional: Activation area
topLeft: { x: number; y: number };
bottomRight: { x: number; y: number };
};
enableWithoutTouch?: boolean; // Optional: Enable on non-touch devices
}

Each visual component (deadZone, baseArea, stick) can be customized:

interface StyleConfig {
alpha: number; // Transparency (0-1)
strokeColor: number; // Stroke color (hex)
strokeAlpha: number; // Stroke transparency (0-1)
strokeWidth: number; // Stroke width in pixels
radius: number; // Circle radius in pixels
fillColor: number; // Fill color (hex)
}

The joystick provides normalized values:

  • X axis: -1 (left) to 1 (right)
  • Y axis: -1 (up) to 1 (down)
  • Center: (0, 0) when not touched
this.joystick.on('move', (data) => {
if (data.x > 0.5) {
// Moving right
} else if (data.x < -0.5) {
// Moving left
}
if (data.y > 0.5) {
// Moving down
} else if (data.y < -0.5) {
// Moving up
}
});
  • Activation Area: Left half of the screen (with 20% top padding)
  • Visual Style: Blue-themed with subtle transparency
  • Dead Zone: 16px radius - no input registered in this area
  • Base Area: 64px radius - maximum joystick movement range
  • Stick: 40px radius - follows your finger with smooth following behavior
  • Touch Only: Only works on touch devices by default

The joystick automatically detects touch capability and only appears on devices that support it. On desktop browsers, the joystick will be hidden unless you set enableWithoutTouch: true.

  • Make sure you called this.add.existing(joystick)
  • Check if you’re on a touch device (or set enableWithoutTouch: true)
  • Verify the touch is within the bounds area
  • Check if there are interactive objects blocking the touch
  • Make sure to call joystick.update() in your scene’s update method
  • Consider reducing the update frequency if needed