use bevy::{
input::gamepad::GamepadEvent, input::keyboard::KeyboardInput, prelude::*, window::PrimaryWindow,
};
use leafwing_input_manager::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(InputManagerPlugin::<PlayerAction>::default())
.add_plugins(InputModeManagerPlugin)
.init_resource::<ActionState<PlayerAction>>()
.insert_resource(PlayerAction::default_input_map())
.add_systems(Startup, setup_scene)
.add_systems(
Update,
player_mouse_look.run_if(in_state(ActiveInput::MouseKeyboard)),
)
.add_systems(Update, control_player.after(player_mouse_look))
.run();
}
#[derive(Actionlike, PartialEq, Eq, Clone, Copy, Hash, Debug, Reflect)]
#[actionlike(DualAxis)]
pub enum PlayerAction {
Move,
Look,
#[actionlike(Button)]
Shoot,
}
impl PlayerAction {
fn default_input_map() -> InputMap<Self> {
let mut input_map = InputMap::default();
input_map.insert_dual_axis(Self::Move, GamepadStick::LEFT);
input_map.insert_dual_axis(Self::Look, GamepadStick::RIGHT);
input_map.insert(Self::Shoot, GamepadButton::RightTrigger);
input_map.insert_dual_axis(Self::Move, VirtualDPad::wasd());
input_map.insert_dual_axis(Self::Look, VirtualDPad::arrow_keys());
input_map.insert(Self::Shoot, MouseButton::Left);
input_map
}
}
pub struct InputModeManagerPlugin;
impl Plugin for InputModeManagerPlugin {
fn build(&self, app: &mut App) {
app.init_state::<ActiveInput>()
.add_systems(
Update,
activate_gamepad.run_if(in_state(ActiveInput::MouseKeyboard)),
)
.add_systems(Update, activate_mkb.run_if(in_state(ActiveInput::Gamepad)));
}
}
#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Hash, States)]
enum ActiveInput {
#[default]
MouseKeyboard,
Gamepad,
}
fn activate_gamepad(
mut next_state: ResMut<NextState<ActiveInput>>,
mut gamepad_evr: MessageReader<GamepadEvent>,
) {
for ev in gamepad_evr.read() {
match ev {
GamepadEvent::Button(_) | GamepadEvent::Axis(_) => {
info!("Switching to gamepad input");
next_state.set(ActiveInput::Gamepad);
return;
}
_ => (),
}
}
}
fn activate_mkb(
mut next_state: ResMut<NextState<ActiveInput>>,
mut kb_evr: MessageReader<KeyboardInput>,
) {
for _ev in kb_evr.read() {
info!("Switching to mouse and keyboard input");
next_state.set(ActiveInput::MouseKeyboard);
}
}
fn player_mouse_look(
camera_query: Query<(&GlobalTransform, &Camera)>,
player_query: Query<&Transform, With<Player>>,
window_query: Query<&Window, With<PrimaryWindow>>,
mut action_state: ResMut<ActionState<PlayerAction>>,
) {
let (camera_transform, camera) = camera_query.single().expect("Need a single camera");
let player_transform = player_query.single().expect("Need a single player");
let window = window_query.single().expect("Need a single primary window");
let player_position = player_transform.translation;
if let Some(p) = window
.cursor_position()
.and_then(|cursor| camera.viewport_to_world(camera_transform, cursor).ok())
.and_then(|ray| {
Some(ray).zip(ray.intersect_plane(player_position, InfinitePlane3d::new(Vec3::Y)))
})
.map(|(ray, p)| ray.get_point(p))
{
let diff = (p - player_position).xz();
if diff.length_squared() > 1e-3f32 {
let action_data = action_state.dual_axis_data_mut_or_default(&PlayerAction::Look);
action_data.pair = Vec2::new(diff.x, -diff.y);
}
}
}
fn control_player(
time: Res<Time>,
action_state: Res<ActionState<PlayerAction>>,
mut player_transform: Single<&mut Transform, With<Player>>,
) {
if action_state.axis_pair(&PlayerAction::Move) != Vec2::ZERO {
let move_delta = time.delta_secs() * action_state.clamped_axis_pair(&PlayerAction::Move);
player_transform.translation += Vec3::new(move_delta.x, 0.0, move_delta.y);
println!("Player moved to: {}", player_transform.translation.xz());
}
if action_state.axis_pair(&PlayerAction::Look) != Vec2::ZERO {
let look = action_state.axis_pair(&PlayerAction::Look).normalize();
println!("Player looking in direction: {look}");
}
if action_state.pressed(&PlayerAction::Shoot) {
println!("Shoot!")
}
}
#[derive(Component)]
struct Player;
fn setup_scene(mut commands: Commands) {
commands
.spawn(Camera3d::default())
.insert(Transform::from_xyz(0.0, 10.0, 15.0).looking_at(Vec3::new(0.0, 0.0, 0.0), Vec3::Y));
commands.spawn(Player).insert(Transform::default());
}