Leafwing-Studios / leafwing-input-manager

A straightforward stateful input manager for the Bevy game engine.
Apache License 2.0
664 stars 100 forks source link

Multiple gamepad issue #557

Closed safstromo closed 1 month ago

safstromo commented 1 month ago

Version

bevy = {version = "0.14" ,features = ["dynamic_linking","bevy_state"]} leafwing-input-manager = "0.14"

Operating system & version

popos 22.04

What you did

This is my function for join:

fn join(
    mut commands: Commands,
    mut joined_players: ResMut<JoinedPlayers>,
    gamepads: Res<Gamepads>,
    button_inputs: Res<ButtonInput<GamepadButton>>,
    mut texture_atlases: ResMut<Assets<TextureAtlasLayout>>,
    asset_server: Res<AssetServer>,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<ColorMaterial>>,
) {
    for gamepad in gamepads.iter() {
        // Join the game when both bumpers (L+R) on the controller are pressed
        // We drop down the Bevy's input to get the input from each gamepad
        if button_inputs.pressed(GamepadButton::new(gamepad, GamepadButtonType::LeftTrigger))
            && button_inputs.pressed(GamepadButton::new(gamepad, GamepadButtonType::RightTrigger))
        {
            // Make sure a player can not join twice
            if !joined_players.0.contains_key(&gamepad) {
                println!("Player {} has joined the game!", gamepad.id);

                let input_map = InputMap::default()
                    .insert_multiple([
                        (PlayerAction::Left, GamepadButtonType::DPadLeft),
                        (PlayerAction::Right, GamepadButtonType::DPadRight),
                        (PlayerAction::Up, GamepadButtonType::DPadUp),
                        (PlayerAction::Down, GamepadButtonType::DPadDown),
                        (PlayerAction::Throw, GamepadButtonType::RightTrigger2),
                        (PlayerAction::Dash, GamepadButtonType::South),
                        (PlayerAction::Start, GamepadButtonType::Start),
                        (PlayerAction::Disconnect, GamepadButtonType::Select),

                    ])
                    .insert(PlayerAction::Move, DualAxis::left_stick())
                    .insert(PlayerAction::Aim, DualAxis::right_stick())
                    // Make sure to set the gamepad or all gamepads will be used!
                    .set_gamepad(gamepad)
                    .build();

                let player = spawn_player(
                    &mut commands,
                    &asset_server,
                    &mut texture_atlases,
                    input_map,
                    gamepad,
                );

                // Insert the created player and its gamepad to the hashmap of joined players
                // Since uniqueness was already checked above, we can insert here unchecked
                joined_players.0.insert_unique_unchecked(gamepad, player);
            }
        }
    }
}

This is my spawn_player:

pub fn spawn_player(
    commands: &mut Commands,
    asset_server: &Res<AssetServer>,
    texture_atlases_layouts: &mut ResMut<Assets<TextureAtlasLayout>>,
    input_map: InputMap<PlayerAction>,
    gamepad: Gamepad,
) -> Entity {
    let texture = asset_server.load("duckyatlas.png");

    let layout = TextureAtlasLayout::from_grid(UVec2::new(64, 64), 5, 3, None, None);
    let texture_atlas_layout = texture_atlases_layouts.add(layout);

    // Use only the subset of sprites in the sheet that make up the run animation
    let animation_indices = AnimationIndices {
        first: 10,
        last: 13,
    };

    let player = commands
        .spawn(PlayerBundle {
            player: Player {
                player_id: gamepad.id, //make this dynamic
                lives: 3,
                gamepad,
                have_ball: false,
            },
            input_manager: InputManagerBundle {
                input_map,
                ..Default::default()
            },
            sprite: SpriteSheetBundle {
                texture,
                atlas: TextureAtlas {
                    layout: texture_atlas_layout,
                    index: animation_indices.first,
                },
                transform: Transform::from_xyz(50.0, -250., 2.0),
                ..default()
            },
            ..default()
        })
        .insert(RigidBody::KinematicPositionBased)
        .insert(Ccd::enabled())
        .insert(KinematicCharacterController::default())
        .insert(Collider::ball(10.0))
        .insert(ActiveEvents::COLLISION_EVENTS)
        .insert(GravityScale(0.0))
        .id();

    return player;
}

Test function with bug?

fn test_bug(action_query: Query<(&ActionState<PlayerAction>, &Player)>) {
    for (action_state, player) in action_query.iter() {
        if action_state.just_pressed(&PlayerAction::Throw) {
            println!("Player {} dash!", player.gamepad.id);
        }
    }
}

What you expected to happen

When RightTrigger is pressed on a gamepad, that players gamepad id is printed.

What actually happened

When pressing a RightTrigger, all connected gamepads id is printed: image

Additional information

I this is my move function. The dualaxis stick movement works as expected(Only one player moving.) The dpad movement is the same as the bug. (all players move).

fn move_player(
    mut query: Query<
        (
            &ActionState<PlayerAction>,
            &mut Transform,
            &mut Velocity,
            &mut PlayerDirection,
            &mut Sprite,
            &mut TextureAtlas,
        ),
        With<Player>,
    >,
    time_step: Res<Time<Fixed>>,
) {
    for (
        action_state,
        mut player_transform,
        mut velocity,
        mut direction,
        mut sprite,
        mut texture_atlas,
    ) in query.iter_mut()
    {
        let mut horizontal = 0.0;
        let mut vertical = 0.0;
        if action_state.pressed(&PlayerAction::Up) {
            vertical += 1.0;
        }
        if action_state.pressed(&PlayerAction::Down) {
            vertical -= 1.0;
        }
        if action_state.pressed(&PlayerAction::Left) {
            horizontal -= 1.0;
            sprite.flip_x = true;
        }
        if action_state.pressed(&PlayerAction::Right) {
            horizontal += 1.0;
            sprite.flip_x = false;
        }

        let mut new_player_position_horizontal =
            player_transform.translation.x + horizontal * PLAYER_SPEED * time_step.delta_seconds();

        let mut new_player_position_vertical =
            player_transform.translation.y + vertical * PLAYER_SPEED * time_step.delta_seconds();

        if action_state.pressed(&PlayerAction::Move) {
            // We're working with gamepads, so we want to defensively ensure that we're using the clamped values
            let axis_pair = action_state.clamped_axis_pair(&PlayerAction::Move).unwrap();

            velocity.0.x = axis_pair.x();
            velocity.0.y = axis_pair.y();
            direction.direction.x = axis_pair.x();
            direction.direction.y = axis_pair.y();

            new_player_position_horizontal = player_transform.translation.x
                + velocity.0.x * PLAYER_SPEED * time_step.delta_seconds();
            new_player_position_vertical = player_transform.translation.y
                + velocity.0.y * PLAYER_SPEED * time_step.delta_seconds();

            // if moved left or right flip sprite
            if velocity.0.x != 0.0 {
                sprite.flip_x = velocity.0.x < 0.0;
            }

            // idle animation or run animation
            if velocity.0.x != 0.0 || velocity.0.y != 0.0 {
                if texture_atlas.index < 4 || texture_atlas.index > 7 {
                    texture_atlas.index = 4;
                }
            } else {
                if texture_atlas.index < 10 || texture_atlas.index > 13 {
                    texture_atlas.index = 10;
                }
            }
        }

        // Update the player position,
        // making sure it doesn't cause the player to leave the arena
        let left_bound = LEFT_WALL + WALL_THICKNESS / 2.0 + PLAYER_SIZE.x / 2.0 + PLAYER_PADDING;
        let right_bound = RIGHT_WALL - WALL_THICKNESS / 2.0 - PLAYER_SIZE.x / 2.0 - PLAYER_PADDING;
        let top_bound = TOP_WALL - WALL_THICKNESS / 2.0 - PLAYER_SIZE.y / 2.0 - PLAYER_PADDING;
        let bottom_bound =
            BOTTOM_WALL + WALL_THICKNESS / 2.0 + PLAYER_SIZE.y / 2.0 + PLAYER_PADDING;

        player_transform.translation.x =
            new_player_position_horizontal.clamp(left_bound, right_bound);
        player_transform.translation.y =
            new_player_position_vertical.clamp(bottom_bound, top_bound);
    }
}

Its acting like the .set_gamepad() for the input map is not working? I'd appreciate some help :heart:

Thanks!

Shute052 commented 1 month ago