#joystick #bevy #game-ui #game-input #bevy-input

virtual_joystick

Bevy virtual Joystick for mobile/web/touch games with Mouse Emulation

20 stable releases

Uses new Rust 2024

2.7.2 Apr 6, 2026
2.7.1 Feb 20, 2026
2.5.3 Oct 18, 2025
2.4.0 Jan 16, 2025
0.1.1 Apr 7, 2023

#58 in Game dev

Download history 23/week @ 2026-01-04 5/week @ 2026-02-08 17/week @ 2026-02-15 66/week @ 2026-02-22 27/week @ 2026-03-01 58/week @ 2026-03-08 18/week @ 2026-03-15 55/week @ 2026-03-22 15/week @ 2026-04-05 56/week @ 2026-04-12

133 downloads per month

MIT/Apache

130KB
901 lines

Bevy Virtual Joystick

VJoystick_Fixed_Preview


GitHub Workflow Status GitHub release (latest by date)

Create and use a Virtual Joystick in a UI for bevy Game Engine.

Versions

Aviable and compatible versions

bevy VirtualJoystick
0.18 2.7.2
0.17 2.6.0
0.16 2.5.3
0.15 2.4.0
0.14 2.3.0
0.13 2.2.0
0.12 2.1.0
0.11 2.0.1
0.10.1 1.1.2

Features

  • Support Mouse and Touch
  • Easy usage
  • Multiple Joysticks on screen
  • Multiple types of joystick behaviour
  • Track Messages on Joystick (Press, Drag and Up)
  • Support Axis block (Horizontal, Vertical or Both)

NOTE: To compile android projects you can use cargo-ndk. See the Android Section of the bevy example README.md for details.

Axis

Both (Default) Horizontal Vertical
VJoystick_Fixed_Both VJoystick_Fixed_Horizontal VJoystick_Fixed_Vertical

Joystick Types

Fixed Floating (Default) Dynamic (TODO: Fix movement feel)
VJoystick_Fixed_Both VJoystick_Floating_Both VJoystick_Dynamic_Both

Examples

Features

  • inspect: for world inspect with egui inspector
  • serde (default): for serialization support for all types (usable for save and load settings)
virtual_joystick = {
    version = "*",
    default-features = false,
    features = [ "inspect", "serde" ]
}

Usage

Check out the examples for details.

# to run example
cargo run --example simple -F=inspect

Add to Cargo.toml

[dependencies]
bevy = "*"
virtual_joystick = "*" # Add your version

The minimal requirement:

use bevy::prelude::*;
// import crate
use virtual_joystick::*;

/// ID for joysticks
#[derive(Default, Debug, Reflect, Hash, Clone, PartialEq, Eq)]
enum JoystickControllerID {
    #[default]
    Joystick1,
    Joystick2,
}

#[bevy_main]
fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        // Add plugin to application
        .add_plugins(VirtualJoystickPlugin::<JoystickControllerID>::default())
        .run()
}

Create Joystick

#[bevy_main]
fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        // Add plugin to application
        .add_plugins(VirtualJoystickPlugin::<String>::default())
        // Create system
        .add_startup_system(create_scene)
        // update System
        .add_system(update_player)
        .run()
}


fn create_scene(mut cmd: Commands, asset_server: Res<AssetServer>) {
    cmd.spawn(Camera2dBundle::default());
    cmd.spawn_empty().insert(Player(30.));

    // Spawn Virtual Joystick at horizontal center
    create_joystick(
        &mut cmd,
        "UniqueJoystick".to_string(),
        asset_server.load("Knob.png"),
        asset_server.load("Outline.png"),
        None,
        None,
        Some(Color::srgba(1.0, 0.27, 0.0, 0.3)),
        Vec2::new(75., 75.),
        Vec2::new(150., 150.),
        Node {
            width: Val::Percent(100.),
            height: Val::Percent(100.),
            position_type: PositionType::Absolute,
            left: Val::Percent(0.),
            bottom: Val::Percent(0.),
            ..default()
        },
        JoystickFloating,
        NoAction,
    );
}

Use variable generated by Joystick

fn update_joystick(
    mut reader: MessageReader<VirtualJoystickMessage<JoystickControllerID>>,
    player: Single<(&mut Transform, &Player)>,
    time_step: Res<Time>,
) {
    // Get player
    let (mut player, player_data) = player.into_inner();

    // Iter each `VirtualJoystickMessage`
    for joystick in reader.read() {
        let Vec2 { x, y } = joystick.axis();
        // Verify ID of joystick for movement
        match joystick.id() {
            JoystickControllerID::Joystick1 => {
                // Move player using joystick axis value
                player.translation.x += x * player_data.0 * time_step.delta_secs();
                player.translation.y += y * player_data.0 * time_step.delta_secs();
            }
        }
    }
}

TODOs

  • WIP: Add more better documentation

Dependencies

~71–115MB
~2M SLoC