152 lines
4.9 KiB
Rust
152 lines
4.9 KiB
Rust
use bevy::app::{Events, ManualEventReader};
|
|
use bevy::input::mouse::MouseMotion;
|
|
use bevy::prelude::*;
|
|
|
|
// stolen with neither shame nor pity from git@github.com:sburris0/bevy_flycam.git, b90f6fc, which is copyright 2020 Spencer Burris
|
|
|
|
/// Keeps track of mouse motion events, pitch, and yaw
|
|
#[derive(Default)]
|
|
struct InputState {
|
|
reader_motion: ManualEventReader<MouseMotion>,
|
|
pitch: f32,
|
|
yaw: f32,
|
|
}
|
|
|
|
/// Mouse sensitivity and movement speed
|
|
pub struct MovementSettings {
|
|
pub sensitivity: f32,
|
|
pub speed: f32,
|
|
}
|
|
|
|
impl Default for MovementSettings {
|
|
fn default() -> Self {
|
|
Self {
|
|
sensitivity: 0.00012,
|
|
speed: 12.,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Used in queries when you want flycams and not other cameras
|
|
#[derive(Debug, Component)]
|
|
pub struct FlyCam;
|
|
|
|
/// Grabs/ungrabs mouse cursor
|
|
fn toggle_grab_cursor(window: &mut Window) {
|
|
window.set_cursor_lock_mode(!window.cursor_locked());
|
|
window.set_cursor_visibility(!window.cursor_visible());
|
|
}
|
|
|
|
/// Grabs the cursor when game first starts
|
|
fn initial_grab_cursor(mut windows: ResMut<Windows>) {
|
|
toggle_grab_cursor(windows.get_primary_mut().unwrap());
|
|
}
|
|
|
|
/// Spawns the `Camera3dBundle` to be controlled
|
|
fn setup_player(mut commands: Commands) {
|
|
commands
|
|
.spawn_bundle(PerspectiveCameraBundle {
|
|
transform: Transform::from_xyz(-2.0, 5.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
|
|
..Default::default()
|
|
})
|
|
.insert(FlyCam);
|
|
}
|
|
|
|
/// Handles keyboard input and movement
|
|
fn player_move(
|
|
keys: Res<Input<KeyCode>>,
|
|
time: Res<Time>,
|
|
windows: Res<Windows>,
|
|
settings: Res<MovementSettings>,
|
|
mut query: Query<(&FlyCam, &mut Transform)>,
|
|
) {
|
|
let window = windows.get_primary().unwrap();
|
|
for (_camera, mut transform) in query.iter_mut() {
|
|
let mut velocity = Vec3::ZERO;
|
|
let local_z = transform.local_z();
|
|
let forward = -Vec3::new(local_z.x, 0., local_z.z);
|
|
let right = Vec3::new(local_z.z, 0., -local_z.x);
|
|
|
|
for key in keys.get_pressed() {
|
|
if window.cursor_locked() {
|
|
match key {
|
|
KeyCode::W => velocity += forward,
|
|
KeyCode::S => velocity -= forward,
|
|
KeyCode::A => velocity -= right,
|
|
KeyCode::D => velocity += right,
|
|
KeyCode::Space => velocity += Vec3::Y,
|
|
KeyCode::Delete => velocity -= Vec3::Y,
|
|
_ => (),
|
|
}
|
|
}
|
|
}
|
|
|
|
velocity = velocity.normalize();
|
|
|
|
if !velocity.is_nan() {
|
|
transform.translation += velocity * time.delta_seconds() * settings.speed
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Handles looking around if cursor is locked
|
|
fn player_look(
|
|
settings: Res<MovementSettings>,
|
|
windows: Res<Windows>,
|
|
mut state: ResMut<InputState>,
|
|
motion: Res<Events<MouseMotion>>,
|
|
mut query: Query<(&FlyCam, &mut Transform)>,
|
|
) {
|
|
let window = windows.get_primary().unwrap();
|
|
for (_camera, mut transform) in query.iter_mut() {
|
|
for ev in state.reader_motion.iter(&motion) {
|
|
if window.cursor_locked() {
|
|
// Using smallest of height or width ensures equal vertical and horizontal sensitivity
|
|
let window_scale = window.height().min(window.width());
|
|
|
|
state.pitch -= (settings.sensitivity * ev.delta.y * window_scale).to_radians();
|
|
state.yaw -= (settings.sensitivity * ev.delta.x * window_scale).to_radians();
|
|
}
|
|
|
|
state.pitch = state.pitch.clamp(-1.54, 1.54);
|
|
|
|
// Order is important to prevent unintended roll
|
|
transform.rotation = Quat::from_axis_angle(Vec3::Y, state.yaw)
|
|
* Quat::from_axis_angle(Vec3::X, state.pitch);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn cursor_grab(keys: Res<Input<KeyCode>>, mut windows: ResMut<Windows>) {
|
|
let window = windows.get_primary_mut().unwrap();
|
|
if keys.just_pressed(KeyCode::Escape) {
|
|
toggle_grab_cursor(window);
|
|
}
|
|
}
|
|
|
|
/// Contains everything needed to add first-person fly camera behavior to your game
|
|
pub struct PlayerPlugin;
|
|
impl Plugin for PlayerPlugin {
|
|
fn build(&self, app: &mut App) {
|
|
app.init_resource::<InputState>()
|
|
.init_resource::<MovementSettings>()
|
|
.add_startup_system(setup_player.system())
|
|
.add_startup_system(initial_grab_cursor.system())
|
|
.add_system(player_move.system())
|
|
.add_system(player_look.system())
|
|
.add_system(cursor_grab.system());
|
|
}
|
|
}
|
|
|
|
/// Same as `PlayerPlugin` but does not spawn a camera
|
|
pub struct NoCameraPlayerPlugin;
|
|
impl Plugin for NoCameraPlayerPlugin {
|
|
fn build(&self, app: &mut App) {
|
|
app.init_resource::<InputState>()
|
|
.init_resource::<MovementSettings>()
|
|
.add_startup_system(initial_grab_cursor.system())
|
|
.add_system(player_move.system())
|
|
.add_system(player_look.system())
|
|
.add_system(cursor_grab.system());
|
|
}
|
|
}
|