use bevy::{
input::{
keyboard::KeyboardInput,
mouse::{MouseButtonInput, MouseMotion},
ElementState,
},
prelude::*,
window::WindowFocused,
};
#[derive(Clone, Copy, Debug)]
pub struct KeyBindings {
pub forward: Option<KeyCode>,
pub back: Option<KeyCode>,
pub left: Option<KeyCode>,
pub right: Option<KeyCode>,
pub up: Option<KeyCode>,
pub down: Option<KeyCode>,
pub unlock: Option<KeyCode>,
}
impl Default for KeyBindings {
fn default() -> Self {
Self {
forward: Some(KeyCode::W),
back: Some(KeyCode::S),
left: Some(KeyCode::A),
right: Some(KeyCode::D),
up: Some(KeyCode::Space),
down: Some(KeyCode::LControl),
unlock: Some(KeyCode::Escape),
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct Config {
pub movespeed: f32,
pub sensitivity: f32,
pub key_bindings: KeyBindings,
}
impl Default for Config {
fn default() -> Self {
Self {
movespeed: 1.0,
sensitivity: 0.001,
key_bindings: Default::default(),
}
}
}
#[derive(Component, Default, Debug, Clone, Copy)]
pub struct FpsCam {
pub yaw: f32,
pub pitch: f32,
}
fn camera_move(
keys: Res<Input<KeyCode>>,
time: Res<Time>,
config: Res<Config>,
windows: Res<Windows>,
mut q: Query<&mut Transform, With<FpsCam>>,
) {
let window = windows.get_primary().unwrap();
for mut transform in q.iter_mut() {
let mut v = Vec3::ZERO;
let forward = transform.forward();
let right = transform.right();
if window.cursor_locked() {
for key in keys.get_pressed() {
match Some(*key) {
x if x == config.key_bindings.forward => v += forward,
x if x == config.key_bindings.back => v -= forward,
x if x == config.key_bindings.left => v -= right,
x if x == config.key_bindings.right => v += right,
x if x == config.key_bindings.up => v += Vec3::Y,
x if x == config.key_bindings.down => v -= Vec3::Y,
_ => (),
}
}
}
v = v.normalize_or_zero();
transform.translation += v * time.delta_seconds() * config.movespeed;
}
}
fn camera_look(
config: Res<Config>,
windows: Res<Windows>,
mut motion: EventReader<MouseMotion>,
mut q: Query<(&mut Transform, &mut FpsCam)>,
) {
let window = windows.get_primary().unwrap();
for (mut transform, mut fpscam) in q.iter_mut() {
for event in motion.iter() {
if window.cursor_locked() {
fpscam.yaw -= config.sensitivity * event.delta.x;
fpscam.pitch -= config.sensitivity * event.delta.y;
fpscam.pitch = fpscam
.pitch
.clamp(-std::f32::consts::PI / 2.0, std::f32::consts::PI / 2.0);
transform.rotation = Quat::from_axis_angle(Vec3::Y, fpscam.yaw)
* Quat::from_axis_angle(Vec3::X, fpscam.pitch);
}
}
}
}
fn lock_on_focus(mut windows: ResMut<Windows>, mut focus_events: EventReader<WindowFocused>) {
let window = windows.get_primary_mut().unwrap();
for ev in focus_events.iter() {
if ev.id == window.id() {
set_cursor_lock(window, ev.focused);
}
}
}
fn unlock_cursor(
config: Res<Config>,
mut windows: ResMut<Windows>,
mut key_events: EventReader<KeyboardInput>,
) {
let window = windows.get_primary_mut().unwrap();
for kev in key_events.iter() {
if let Some(code) = kev.key_code {
if Some(code) == config.key_bindings.unlock {
set_cursor_lock(window, false);
}
}
}
}
fn lock_cursor(mut windows: ResMut<Windows>, mut mouse_events: EventReader<MouseButtonInput>) {
let window = windows.get_primary_mut().unwrap();
for ev in mouse_events.iter() {
if ev.state == ElementState::Pressed {
set_cursor_lock(window, true);
}
}
}
fn spawn_camera(mut cmd: Commands) {
cmd.spawn_bundle(PerspectiveCameraBundle {
transform: Transform::from_xyz(1.0, 1.0, 1.0).looking_at(Vec3::ZERO, Vec3::Y),
..Default::default()
})
.insert(FpsCam::default());
}
fn set_cursor_lock(window: &mut Window, state: bool) {
window.set_cursor_lock_mode(state);
window.set_cursor_visibility(!state);
}
pub struct FpsCamPlugin;
impl Plugin for FpsCamPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<Config>()
.add_startup_system(spawn_camera)
.add_system(camera_move)
.add_system(camera_look)
.add_system(lock_on_focus)
.add_system(lock_cursor)
.add_system(unlock_cursor);
}
}
pub struct NoSpawnFpsCamPlugin;
impl Plugin for NoSpawnFpsCamPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<Config>()
.add_system(camera_move)
.add_system(camera_look)
.add_system(lock_on_focus)
.add_system(lock_cursor)
.add_system(unlock_cursor);
}
}