Skip to content

Advanced Usage

Learn advanced patterns and techniques for using Phaser Virtual Joystick in your games.

You can create multiple joysticks for different purposes:

export class GameScene extends Phaser.Scene {
private movementJoystick!: VirtualJoystick;
private aimJoystick!: VirtualJoystick;
create() {
// Movement joystick (left side)
this.movementJoystick = new VirtualJoystick({
scene: this,
bounds: {
topLeft: { x: 0, y: 0 },
bottomRight: { x: this.cameras.main.width / 2, y: this.cameras.main.height }
},
stick: {
fillColor: 0x4ECDC4, // Teal
radius: 35
}
});
// Aim joystick (right side)
this.aimJoystick = new VirtualJoystick({
scene: this,
bounds: {
topLeft: { x: this.cameras.main.width / 2, y: 0 },
bottomRight: { x: this.cameras.main.width, y: this.cameras.main.height }
},
stick: {
fillColor: 0xFF6B6B, // Red for aiming
radius: 30
}
});
this.add.existing(this.movementJoystick);
this.add.existing(this.aimJoystick);
// Handle movement
this.movementJoystick.on('move', (data) => {
this.player.setVelocity(data.x * 200, data.y * 200);
});
// Handle aiming
this.aimJoystick.on('move', (data) => {
this.aimWeapon(data.x, data.y);
});
}
private aimWeapon(x: number, y: number) {
const angle = Math.atan2(y, x);
this.weapon.setRotation(angle);
}
}

Create different visual themes for your joysticks:

const neonJoystick = new VirtualJoystick({
scene: this,
deadZone: {
fillColor: 0x00FF00,
strokeColor: 0x00CC00,
alpha: 0.3
},
baseArea: {
fillColor: 0x00FF00,
alpha: 0.1,
strokeColor: 0x00CC00
},
stick: {
fillColor: 0x00FF00,
strokeColor: 0x00CC00,
alpha: 0.9
}
});
const darkJoystick = new VirtualJoystick({
scene: this,
deadZone: {
fillColor: 0x333333,
strokeColor: 0x666666,
alpha: 0.2
},
baseArea: {
fillColor: 0x333333,
alpha: 0.2,
strokeColor: 0x666666
},
stick: {
fillColor: 0x666666,
strokeColor: 0x999999,
alpha: 0.8
}
});
const minimalJoystick = new VirtualJoystick({
scene: this,
deadZone: { alpha: 0 },
baseArea: {
alpha: 0.05,
strokeWidth: 1,
strokeColor: 0xFFFFFF
},
stick: {
fillColor: 0xFFFFFF,
strokeWidth: 1,
alpha: 0.8
}
});

Add custom icons to your joystick:

export class GameScene extends Phaser.Scene {
create() {
// Create custom icon
const stickIcon = this.add.text(0, 0, '🎮', {
fontSize: '24px',
color: '#ffffff'
});
const joystick = new VirtualJoystick({
scene: this,
stickIcon: stickIcon,
stick: {
fillColor: 0x4ECDC4,
radius: 35
}
});
this.add.existing(joystick);
}
}

Position the joystick where the user first touches:

export class GameScene extends Phaser.Scene {
private joystick!: VirtualJoystick;
private isJoystickActive = false;
create() {
// Create joystick with custom bounds
this.joystick = new VirtualJoystick({
scene: this,
bounds: {
topLeft: { x: 0, y: 0 },
bottomRight: { x: this.cameras.main.width, y: this.cameras.main.height }
}
});
this.add.existing(this.joystick);
// Handle touch events
this.input.on('pointerdown', (pointer) => {
if (!this.isJoystickActive) {
this.isJoystickActive = true;
// Joystick will appear at touch point
}
});
this.joystick.on('release', () => {
this.isJoystickActive = false;
});
}
}

Add acceleration and deceleration for smoother movement:

export class GameScene extends Phaser.Scene {
private joystick!: VirtualJoystick;
private player!: Phaser.Physics.Arcade.Sprite;
private targetVelocity = { x: 0, y: 0 };
private acceleration = 0.1;
private maxSpeed = 200;
create() {
this.player = this.physics.add.sprite(400, 300, 'player');
this.joystick = new VirtualJoystick({
scene: this
});
this.add.existing(this.joystick);
this.joystick.on('move', (data) => {
this.targetVelocity.x = data.x * this.maxSpeed;
this.targetVelocity.y = data.y * this.maxSpeed;
});
this.joystick.on('release', () => {
this.targetVelocity.x = 0;
this.targetVelocity.y = 0;
});
}
update() {
// Update joystick (required for smooth following behavior)
this.joystick?.update();
// Smooth acceleration towards target velocity
const currentVel = this.player.body.velocity;
this.player.setVelocity(
currentVel.x + (this.targetVelocity.x - currentVel.x) * this.acceleration,
currentVel.y + (this.targetVelocity.y - currentVel.y) * this.acceleration
);
}
}

Add visual feedback based on joystick input:

export class GameScene extends Phaser.Scene {
private joystick!: VirtualJoystick;
private player!: Phaser.Physics.Arcade.Sprite;
private trail!: Phaser.GameObjects.Graphics;
create() {
this.player = this.physics.add.sprite(400, 300, 'player');
// Create trail effect
this.trail = this.add.graphics();
this.joystick = new VirtualJoystick({
scene: this
});
this.add.existing(this.joystick);
this.joystick.on('move', (data) => {
// Move player
this.player.setVelocity(data.x * 200, data.y * 200);
// Update trail
this.updateTrail(data);
});
this.joystick.on('release', () => {
this.player.setVelocity(0, 0);
this.trail.clear();
});
}
private updateTrail(data: { x: number, y: number }) {
this.trail.clear();
// Draw trail based on joystick intensity
const intensity = Math.sqrt(data.x * data.x + data.y * data.y);
const alpha = intensity * 0.5;
this.trail.fillStyle(0x00ff00, alpha);
this.trail.fillCircle(
this.player.x + data.x * 20,
this.player.y + data.y * 20,
5
);
}
}

Add haptic feedback for better mobile experience:

export class GameScene extends Phaser.Scene {
private joystick!: VirtualJoystick;
private lastHapticTime = 0;
create() {
this.joystick = new VirtualJoystick({
scene: this
});
this.add.existing(this.joystick);
this.joystick.on('press', () => {
this.triggerHaptic('light');
});
this.joystick.on('move', (data) => {
const magnitude = Math.sqrt(data.x * data.x + data.y * data.y);
// Trigger haptic feedback based on movement intensity
if (magnitude > 0.8 && Date.now() - this.lastHapticTime > 100) {
this.triggerHaptic('medium');
this.lastHapticTime = Date.now();
}
});
this.joystick.on('release', () => {
this.triggerHaptic('light');
});
}
private triggerHaptic(type: 'light' | 'medium' | 'heavy') {
if ('vibrate' in navigator) {
const patterns = {
light: [10],
medium: [20],
heavy: [30]
};
navigator.vibrate(patterns[type]);
}
}
}

Enable joystick on desktop for testing:

const joystick = new VirtualJoystick({
scene: this,
enableWithoutTouch: true // Allows mouse interaction on desktop
});

Optimize joystick performance for better frame rates:

export class GameScene extends Phaser.Scene {
private joystick!: VirtualJoystick;
private updateThrottle = 0;
private throttleRate = 16; // ~60fps
create() {
this.joystick = new VirtualJoystick({
scene: this
});
this.add.existing(this.joystick);
// Throttle updates for better performance
this.joystick.on('move', (data) => {
this.updateThrottle++;
if (this.updateThrottle % 2 === 0) { // Update every other frame
this.handleMovement(data);
}
});
}
update() {
// Always call update for smooth following behavior
this.joystick?.update();
}
private handleMovement(data: { x: number, y: number }) {
// Your movement logic here
this.player.setVelocity(data.x * 200, data.y * 200);
}
}
  1. Always call update(): Call joystick.update() in your scene’s update method
  2. Add to scene: Always call this.add.existing(joystick) after creating
  3. Use bounds: Define custom activation areas for better control
  4. Test on devices: Always test on actual mobile devices
  5. Consider haptics: Add haptic feedback for better UX
  6. Visual feedback: Use custom styling to show joystick state
  7. Multiple joysticks: Use separate joysticks for different actions
  8. Clean up: Destroy joysticks when changing scenes
  9. Custom icons: Add visual elements to make joysticks more intuitive
  10. Performance: Throttle updates if needed for better performance