leafwing-input-manager 0.20.0

A powerful, flexible and ergonomic way to manage action-input keybindings for the Bevy game engine.
Documentation
use bevy::prelude::*;
use leafwing_input_manager::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        // This plugin maps inputs to an input-type agnostic action-state
        // We need to provide it with an enum which stores the possible actions a player could take
        .add_plugins(InputManagerPlugin::<ArpgAction>::default())
        // The InputMap and ActionState components will be added to any entity with the Player component
        .add_systems(Startup, spawn_player)
        // The ActionState can be used directly
        .add_systems(Update, cast_fireball)
        // Or multiple parts of it can be inspected
        .add_systems(Update, player_dash)
        // Or it can be used to emit messages for later processing
        .add_message::<PlayerWalk>()
        .add_systems(Update, player_walks)
        .run();
}

#[derive(Actionlike, PartialEq, Eq, Clone, Copy, Hash, Debug, Reflect)]
enum ArpgAction {
    // Movement
    Up,
    Down,
    Left,
    Right,
    // Abilities
    Ability1,
    Ability2,
    Ability3,
    Ability4,
    Ultimate,
}

impl ArpgAction {
    // Lists like this can be very useful for quickly matching subsets of actions
    const DIRECTIONS: [Self; 4] = [
        ArpgAction::Up,
        ArpgAction::Down,
        ArpgAction::Left,
        ArpgAction::Right,
    ];

    fn direction(self) -> Option<Dir2> {
        match self {
            ArpgAction::Up => Some(Dir2::Y),
            ArpgAction::Down => Some(Dir2::NEG_Y),
            ArpgAction::Left => Some(Dir2::NEG_X),
            ArpgAction::Right => Some(Dir2::X),
            _ => None,
        }
    }
}

#[derive(Component)]
pub struct Player;

impl Player {
    fn default_input_map() -> InputMap<ArpgAction> {
        // This allows us to replace `ArpgAction::Up` with `Up`,
        // significantly reducing boilerplate
        use ArpgAction::*;
        let mut input_map = InputMap::default();

        // Movement
        input_map.insert(Up, KeyCode::ArrowUp);
        input_map.insert(Up, GamepadButton::DPadUp);

        input_map.insert(Down, KeyCode::ArrowDown);
        input_map.insert(Down, GamepadButton::DPadDown);

        input_map.insert(Left, KeyCode::ArrowLeft);
        input_map.insert(Left, GamepadButton::DPadLeft);

        input_map.insert(Right, KeyCode::ArrowRight);
        input_map.insert(Right, GamepadButton::DPadRight);

        // Abilities
        input_map.insert(Ability1, KeyCode::KeyQ);
        input_map.insert(Ability1, GamepadButton::West);
        input_map.insert(Ability1, MouseButton::Left);

        input_map.insert(Ability2, KeyCode::KeyW);
        input_map.insert(Ability2, GamepadButton::North);
        input_map.insert(Ability2, MouseButton::Right);

        input_map.insert(Ability3, KeyCode::KeyE);
        input_map.insert(Ability3, GamepadButton::East);

        input_map.insert(Ability4, KeyCode::Space);
        input_map.insert(Ability4, GamepadButton::South);

        input_map.insert(Ultimate, KeyCode::KeyR);
        input_map.insert(Ultimate, GamepadButton::LeftTrigger2);

        input_map
    }
}

fn spawn_player(mut commands: Commands) {
    commands.spawn((Player, Player::default_input_map()));
}

fn cast_fireball(action_state: Single<&ActionState<ArpgAction>, With<Player>>) {
    if action_state.just_pressed(&ArpgAction::Ability1) {
        println!("Fwoosh!");
    }
}

fn player_dash(action_state: Single<&ActionState<ArpgAction>, With<Player>>) {
    if action_state.just_pressed(&ArpgAction::Ability4) {
        let mut direction_vector = Vec2::ZERO;

        for input_direction in ArpgAction::DIRECTIONS {
            if action_state.pressed(&input_direction) {
                if let Some(direction) = input_direction.direction() {
                    // Sum the directions as 2D vectors
                    direction_vector += *direction;
                }
            }
        }

        // Then reconvert at the end, normalizing the magnitude
        let net_direction = Dir2::new(direction_vector);

        if let Ok(direction) = net_direction {
            println!("Dashing in {direction:?}");
        }
    }
}

#[derive(Message)]
pub struct PlayerWalk {
    pub direction: Dir2,
}

fn player_walks(
    action_state: Single<&ActionState<ArpgAction>, With<Player>>,
    mut message_writer: MessageWriter<PlayerWalk>,
) {
    let mut direction_vector = Vec2::ZERO;

    for input_direction in ArpgAction::DIRECTIONS {
        if action_state.pressed(&input_direction) {
            if let Some(direction) = input_direction.direction() {
                // Sum the directions as 2D vectors
                direction_vector += *direction;
            }
        }
    }

    // Then reconvert at the end, normalizing the magnitude
    let net_direction = Dir2::new(direction_vector);

    if let Ok(direction) = net_direction {
        message_writer.write(PlayerWalk { direction });
    }
}