游戏手柄

可参考的官方例子: gamepad_input, gamepad_input_events.


Bevy 支持游戏手柄输入硬件:游戏机控制器、游戏手柄等。许多不同种类的硬件应该都能工作,但如果你的设备不被支持,你应该向 gilrs 项目提交一个 issue。

手柄 ID

Bevy 为每个连接的游戏手柄分配一个唯一的 ID(Gamepad)。这可以让你把设备与特定的玩家联系起来,并区分你的输入是来自哪一个玩家。

你可以使用 Gamepads 资源 来列出所有当前连接的手柄设备的 ID,或者检查某个特定设备的状态。

为了检测游戏手柄何时连接或断开,你可以使用 GamepadEvent 事件

展示如何记住第一个连接成功的手柄 ID 的例子:

/// Simple resource to store the ID of the connected gamepad.
/// We need to know which gamepad to use for player input.
struct MyGamepad(Gamepad);

fn gamepad_connections(
    mut commands: Commands,
    my_gamepad: Option<Res<MyGamepad>>,
    mut gamepad_evr: EventReader<GamepadEvent>,
) {
    for GamepadEvent(id, kind) in gamepad_evr.iter() {
        match kind {
            GamepadEventType::Connected => {
                println!("New gamepad connected with ID: {:?}", id);

                // if we don't have any gamepad yet, use this one
                if my_gamepad.is_none() {
                    commands.insert_resource(MyGamepad(*id));
                }
            }
            GamepadEventType::Disconnected => {
                println!("Lost gamepad connection with ID: {:?}", id);

                // if it's the one we previously associated with the player,
                // disassociate it:
                if let Some(MyGamepad(old_id)) = my_gamepad.as_deref() {
                    if old_id == id {
                        commands.remove_resource::<MyGamepad>();
                    }
                }
            }
            // other events are irrelevant
            _ => {}
        }
    }
}

处理手柄输入

你可以用 Axis<GamepadAxis> (Axis, GamepadAxis)来处理模拟摇杆和触发开关。按键可以用 Input<GamepadButton> (Input, GamepadButton) 来处理,类似于鼠标按键键盘按键

注意,GamepadButton 中的按键名称是厂商中立的(比如使用 SouthEast,而非 X/OA/B)。

fn gamepad_input(
    axes: Res<Axis<GamepadAxis>>,
    buttons: Res<Input<GamepadButton>>,
    my_gamepad: Option<Res<MyGamepad>>,
) {
    let gamepad = if let Some(gp) = my_gamepad {
        // a gamepad is connected, we have the id
        gp.0
    } else {
        // no gamepad is connected
        return;
    };

    // The joysticks are represented using a separate axis for X and Y

    let axis_lx = GamepadAxis(gamepad, GamepadAxisType::LeftStickX);
    let axis_ly = GamepadAxis(gamepad, GamepadAxisType::LeftStickY);

    if let (Some(x), Some(y)) = (axes.get(axis_lx), axes.get(axis_ly)) {
        // combine X and Y into one vector
        let left_stick_pos = Vec2::new(x, y);

        // Example: check if the stick is pushed up
        if left_stick_pos.length() > 0.9 && left_stick_pos.y > 0.5 {
            // do something
        }
    }

    // In a real game, the buttons would be configurable, but here we hardcode them
    let jump_button = GamepadButton(gamepad, GamepadButtonType::South);
    let heal_button = GamepadButton(gamepad, GamepadButtonType::East);

    if buttons.just_pressed(jump_button) {
        // button just pressed: make the player jump
    }

    if buttons.pressed(heal_button) {
        // button being held down: heal the player
    }
}

你也可以使用 GamepadEvent 事件 来处理游戏手柄的输入:

fn gamepad_input_events(
    my_gamepad: Option<Res<MyGamepad>>,
    mut gamepad_evr: EventReader<GamepadEvent>,
) {
    let gamepad = if let Some(gp) = my_gamepad {
        // a gamepad is connected, we have the id
        gp.0
    } else {
        // no gamepad is connected
        return;
    };

    for GamepadEvent(id, kind) in gamepad_evr.iter() {
        if id.0 != gamepad.0 {
            // event not from our gamepad
            continue;
        }

        use GamepadEventType::{AxisChanged, ButtonChanged};

        match kind {
            AxisChanged(GamepadAxisType::RightStickX, x) => {
                // Right Stick moved (X)
            }
            AxisChanged(GamepadAxisType::RightStickY, y) => {
                // Right Stick moved (Y)
            }
            ButtonChanged(GamepadButtonType::DPadDown, val) => {
                // buttons are also reported as analog, so use a threshold
                if *val > 0.5 {
                    // button pressed
                }
            }
            _ => {} // don't care about other inputs
        }
    }
}

手柄设置

你可以使用 GamepadSettings 资源 来配置各个轴和按键的死区和其他参数。你可以设置全局默认值,也可以单独设置每个轴和按键。

下面是一个例子,展示了如何用自定义设置来配置游戏手柄(不一定是好的设置,请不要盲目地复制这些设置):

// this should be run once, when the game is starting
// (transition entering your in-game state might be a good place to put it)
fn configure_gamepads(
    my_gamepad: Option<Res<MyGamepad>>,
    mut settings: ResMut<GamepadSettings>,
) {
    let gamepad = if let Some(gp) = my_gamepad {
        // a gamepad is connected, we have the id
        gp.0
    } else {
        // no gamepad is connected
        return;
    };

    // add a larger default dead-zone to all axes (ignore small inputs, round to zero)
    settings.default_axis_settings.negative_low = -0.1;
    settings.default_axis_settings.positive_low = 0.1;

    // make the right stick "binary", squash higher values to 1.0 and lower values to 0.0
    let right_stick_settings = AxisSettings {
        positive_high:  0.5, // values  0.5 to  1.0, become  1.0
        positive_low:   0.5, // values  0.0 to  0.5, become  0.0
        negative_low:  -0.5, // values -0.5 to  0.0, become  0.0
        negative_high: -0.5, // values -1.0 to -0.5, become -1.0
        // the raw value should change by at least this much,
        // for Bevy to register an input event:
        threshold: 0.01,
    };

    // make the triggers work in big/coarse steps, to get fewer events
    // reduces noise and precision
    let trigger_settings = AxisSettings {
        threshold: 0.2,
        // also set some conservative deadzones
        positive_high: 0.8,
        positive_low: 0.2,
        negative_high: -0.8,
        negative_low: -0.2,
    };

    // set these settings for the gamepad we use for our player
    settings.axis_settings.insert(
        GamepadAxis(gamepad, GamepadAxisType::RightStickX),
        right_stick_settings.clone()
    );
    settings.axis_settings.insert(
        GamepadAxis(gamepad, GamepadAxisType::RightStickY),
        right_stick_settings.clone()
    );
    settings.axis_settings.insert(
        GamepadAxis(gamepad, GamepadAxisType::LeftZ),
        trigger_settings.clone()
    );
    settings.axis_settings.insert(
        GamepadAxis(gamepad, GamepadAxisType::RightZ),
        trigger_settings.clone()
    );

    // for buttons (or axes treated as buttons), make them less sensitive
    let button_settings = ButtonSettings {
        // require them to be pressed almost all the way, to count
        press: 0.9,
        // require them to be released almost all the way, to count
        release: 0.1,
    };

    settings.default_button_settings = button_settings;
}