Compare commits
2 Commits
138a218fc5
...
1458958ba2
| Author | SHA1 | Date |
|---|---|---|
|
|
1458958ba2 | 3 weeks ago |
|
|
df8cc62ece | 4 weeks ago |
19 changed files with 422 additions and 303 deletions
@ -0,0 +1,15 @@ |
||||
//! Common module to handle asset loading and adding visual components
|
||||
//! (such as [`Mesh`]) to entities when they are spawned from the server.
|
||||
//!
|
||||
//! This doesn't mean this is the only place assets are loaded.
|
||||
//! For example UI related assets may be loaded in `ui` plugins.
|
||||
|
||||
use bevy::prelude::*; |
||||
|
||||
mod block; |
||||
mod player; |
||||
|
||||
pub fn plugin(app: &mut App) { |
||||
app.add_plugins(block::plugin); |
||||
app.add_plugins(player::plugin); |
||||
} |
||||
@ -0,0 +1,38 @@ |
||||
use bevy::prelude::*; |
||||
use common::prelude::*; |
||||
|
||||
pub fn plugin(app: &mut App) { |
||||
app.load_resource::<PlayerAssets>(); |
||||
app.add_observer(insert_player_visuals); |
||||
} |
||||
|
||||
#[derive(Resource, Asset, Reflect, Clone)] |
||||
#[reflect(Resource)] |
||||
pub struct PlayerAssets { |
||||
#[dependency] |
||||
mesh: Handle<Mesh>, |
||||
#[dependency] |
||||
material: Handle<StandardMaterial>, |
||||
} |
||||
|
||||
impl FromWorld for PlayerAssets { |
||||
fn from_world(world: &mut World) -> Self { |
||||
let assets = world.resource::<AssetServer>(); |
||||
Self { |
||||
mesh: assets.add(Cuboid::new(1.0, 1.0, 1.0).into()), |
||||
material: assets.add(Color::srgb_u8(124, 255, 144).into()), |
||||
} |
||||
} |
||||
} |
||||
|
||||
fn insert_player_visuals( |
||||
event: On<Add, Player>, |
||||
mut commands: Commands, |
||||
block_assets: Res<PlayerAssets>, |
||||
) { |
||||
let player = event.entity; |
||||
commands.entity(player).insert(( |
||||
Mesh3d(block_assets.mesh.clone()), |
||||
MeshMaterial3d(block_assets.material.clone()), |
||||
)); |
||||
} |
||||
@ -1,148 +0,0 @@ |
||||
use std::f32::consts::TAU; |
||||
|
||||
use bevy::prelude::*; |
||||
|
||||
use bevy::input::mouse::AccumulatedMouseMotion; |
||||
use bevy::window::{CursorGrabMode, CursorOptions}; |
||||
|
||||
pub fn cursor_grab( |
||||
mut mouse_button_input: ResMut<ButtonInput<MouseButton>>, |
||||
key_input: Res<ButtonInput<KeyCode>>, |
||||
window: Single<(&mut Window, &mut CursorOptions)>, |
||||
) { |
||||
let (mut window, mut cursor) = window.into_inner(); |
||||
|
||||
let is_grabbed = cursor.grab_mode != CursorGrabMode::None; |
||||
let request_grab = mouse_button_input.any_just_pressed([MouseButton::Left, MouseButton::Right]); |
||||
let request_ungrab = !window.focused || key_input.just_pressed(KeyCode::Escape); |
||||
|
||||
if !is_grabbed && request_grab && !request_ungrab { |
||||
cursor.grab_mode = CursorGrabMode::Locked; |
||||
|
||||
// To prevent other systems (such as `place_break_blocks`)
|
||||
// from seeing the mouse button inputs, clear the state here.
|
||||
mouse_button_input.clear(); |
||||
} |
||||
|
||||
if is_grabbed && request_ungrab && !request_grab { |
||||
cursor.grab_mode = CursorGrabMode::None; |
||||
} |
||||
|
||||
if is_grabbed && !request_ungrab { |
||||
// Set the cursor position to the center of the window, so that when
|
||||
// it is ungrabbed, it will reappear there. Because `is_grabbed` is
|
||||
// not updated on grab, this block is delayed by one frame.
|
||||
//
|
||||
// On Wayland, since the cursor is locked into place, this only needs
|
||||
// to be done once. Unfortunately, for some reason this doesn't work
|
||||
// in the same frame as setting `grab_mode`, and would log an error.
|
||||
//
|
||||
// On X11, the cursor can't be locked into place, only confined to the
|
||||
// window bounds, so we repeatedly move the cursor back to the center
|
||||
// while it's grabbed.
|
||||
//
|
||||
// On the web, the cursor can be locked, but setting its position is
|
||||
// not supported at all, so this would instead log a bunch of errors.
|
||||
let center = window.resolution.size() / 2.; |
||||
#[cfg(not(target_family = "wasm"))] // skip on web
|
||||
window.set_cursor_position(Some(center)); |
||||
} |
||||
|
||||
// Keep cursor visbility in sync with `grab_mode`.
|
||||
cursor.visible = cursor.grab_mode == CursorGrabMode::None; |
||||
} |
||||
|
||||
pub fn is_cursor_grabbed(cursor: Single<&CursorOptions>) -> bool { |
||||
cursor.grab_mode != CursorGrabMode::None |
||||
} |
||||
|
||||
#[derive(Component, Debug)] |
||||
pub struct CameraFreeLook { |
||||
/// The mouse sensitivity, in radians per pixel.
|
||||
pub sensitivity: Vec2, |
||||
/// How far the camera can be tilted up and down.
|
||||
pub pitch_limit: std::ops::RangeInclusive<f32>, |
||||
|
||||
/// Upon initialization, `pitch` and `yaw` will
|
||||
/// be set from the camera transform's rotation.
|
||||
initialized: bool, |
||||
/// The current yaw (right/left) of the camera, in radians.
|
||||
pub yaw: f32, |
||||
/// The current pitch (tilt) of the camera, in radians.
|
||||
pub pitch: f32, |
||||
} |
||||
|
||||
impl Default for CameraFreeLook { |
||||
fn default() -> Self { |
||||
Self { |
||||
sensitivity: Vec2::splat(0.2).map(f32::to_radians), |
||||
pitch_limit: -(TAU / 4.0)..=(TAU / 4.0), |
||||
initialized: false, |
||||
yaw: 0.0, |
||||
pitch: 0.0, |
||||
} |
||||
} |
||||
} |
||||
|
||||
pub fn camera_look( |
||||
accumulated_mouse_motion: Res<AccumulatedMouseMotion>, |
||||
camera: Single<(&mut Transform, &mut CameraFreeLook)>, |
||||
) { |
||||
let (mut transform, mut look) = camera.into_inner(); |
||||
|
||||
// Ensure the yaw and pitch are initialized once
|
||||
// from the camera transform's current rotation.
|
||||
if !look.initialized { |
||||
(look.yaw, look.pitch, _) = transform.rotation.to_euler(EulerRot::YXZ); |
||||
look.initialized = true; |
||||
} |
||||
|
||||
// Update the current camera state's internal yaw and pitch.
|
||||
let motion = accumulated_mouse_motion.delta * look.sensitivity; |
||||
let (min, max) = look.pitch_limit.clone().into_inner(); |
||||
look.yaw = (look.yaw - motion.x).rem_euclid(TAU); // keep within 0°..360°
|
||||
look.pitch = (look.pitch - motion.y).clamp(min, max); |
||||
|
||||
// Override the camera transform's rotation.
|
||||
transform.rotation = Quat::from_euler(EulerRot::ZYX, 0.0, look.yaw, look.pitch); |
||||
} |
||||
|
||||
#[derive(Component)] |
||||
pub struct Crosshair; |
||||
|
||||
pub fn setup_crosshair(mut commands: Commands, assets: Res<AssetServer>) { |
||||
commands.spawn(( |
||||
Node { |
||||
width: percent(100), |
||||
height: percent(100), |
||||
align_items: AlignItems::Center, |
||||
justify_content: JustifyContent::Center, |
||||
..default() |
||||
}, |
||||
children![( |
||||
Crosshair, |
||||
Node { |
||||
width: px(64), |
||||
height: px(64), |
||||
..default() |
||||
}, |
||||
ImageNode { |
||||
image: assets.load("crosshair.png"), |
||||
..default() |
||||
}, |
||||
// Hidden by default, because cursor shouldn't be grabbed at startup either.
|
||||
Visibility::Hidden, |
||||
)], |
||||
)); |
||||
} |
||||
|
||||
pub fn update_crosshair_visibility( |
||||
cursor: Single<&CursorOptions, Changed<CursorOptions>>, |
||||
crosshair: Single<&mut Visibility, With<Crosshair>>, |
||||
) { |
||||
let is_grabbed = cursor.grab_mode != CursorGrabMode::None; |
||||
let mut crosshair_visibility = crosshair.into_inner(); |
||||
*crosshair_visibility = (!is_grabbed || cursor.visible) |
||||
.then_some(Visibility::Hidden) |
||||
.unwrap_or_default(); |
||||
} |
||||
@ -1,41 +0,0 @@ |
||||
use bevy::prelude::*; |
||||
use lightyear::prelude::input::client::*; |
||||
use lightyear::prelude::input::native::*; |
||||
|
||||
use bevy::window::{CursorGrabMode, CursorOptions}; |
||||
|
||||
use crate::camera::CameraFreeLook; |
||||
use common::network::Inputs; |
||||
|
||||
pub fn plugin(app: &mut App) { |
||||
app.add_systems( |
||||
FixedPreUpdate, |
||||
buffer_input.in_set(InputSystems::WriteClientInputs), |
||||
); |
||||
} |
||||
|
||||
fn buffer_input( |
||||
key_input: Res<ButtonInput<KeyCode>>, |
||||
mut action_state: Single<&mut ActionState<Inputs>, With<InputMarker<Inputs>>>, |
||||
cursor: Single<&CursorOptions>, |
||||
camera: Single<&CameraFreeLook>, |
||||
) { |
||||
action_state.0 = if cursor.grab_mode == CursorGrabMode::None { |
||||
// If cursor is not grabbed, reset movement input.
|
||||
Default::default() |
||||
} else { |
||||
Inputs { |
||||
#[rustfmt::skip] |
||||
movement: { |
||||
let mut movement = Vec2::ZERO; |
||||
if key_input.pressed(KeyCode::KeyD) { movement.x += 1.0; } |
||||
if key_input.pressed(KeyCode::KeyA) { movement.x -= 1.0; } |
||||
if key_input.pressed(KeyCode::KeyS) { movement.y += 1.0; } |
||||
if key_input.pressed(KeyCode::KeyW) { movement.y -= 1.0; } |
||||
Rot2::radians(-camera.yaw) * movement.clamp_length_max(1.0) |
||||
}, |
||||
up: key_input.pressed(KeyCode::Space), |
||||
down: key_input.pressed(KeyCode::ShiftLeft), |
||||
} |
||||
}; |
||||
} |
||||
@ -0,0 +1,45 @@ |
||||
//! Handles client-side input using [`lightyear`], updating the
|
||||
//! [`ActionState`] of the affected entities, such as the `Player`.
|
||||
|
||||
use bevy::prelude::*; |
||||
use common::prelude::*; |
||||
use lightyear::prelude::input::client::*; |
||||
use lightyear::prelude::input::native::*; |
||||
|
||||
use bevy::window::{CursorGrabMode, CursorOptions}; |
||||
|
||||
pub fn plugin(app: &mut App) { |
||||
app.add_systems( |
||||
FixedPreUpdate, |
||||
buffer_input.in_set(InputSystems::WriteClientInputs), |
||||
); |
||||
} |
||||
|
||||
fn buffer_input( |
||||
keys: Res<ButtonInput<KeyCode>>, |
||||
player: Single<(&mut ActionState<Inputs>, &HeadOrientation), With<InputMarker<Inputs>>>, |
||||
cursor: Single<&CursorOptions>, |
||||
) { |
||||
let (mut inputs, orientation) = player.into_inner(); |
||||
inputs.0 = if cursor.grab_mode == CursorGrabMode::None { |
||||
Inputs { |
||||
look: *orientation, |
||||
..default() |
||||
} |
||||
} else { |
||||
Inputs { |
||||
look: *orientation, |
||||
#[rustfmt::skip] |
||||
movement: { |
||||
let mut movement = Vec2::ZERO; |
||||
if keys.pressed(KeyCode::KeyD) { movement.x += 1.0; } |
||||
if keys.pressed(KeyCode::KeyA) { movement.x -= 1.0; } |
||||
if keys.pressed(KeyCode::KeyS) { movement.y += 1.0; } |
||||
if keys.pressed(KeyCode::KeyW) { movement.y -= 1.0; } |
||||
movement.clamp_length_max(1.0) |
||||
}, |
||||
up: keys.pressed(KeyCode::Space), |
||||
down: keys.pressed(KeyCode::ShiftLeft), |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,63 @@ |
||||
use bevy::prelude::*; |
||||
|
||||
use bevy::window::{CursorGrabMode, CursorOptions}; |
||||
|
||||
use crate::Screen; |
||||
|
||||
pub fn plugin(app: &mut App) { |
||||
app.add_systems( |
||||
PreUpdate, |
||||
update_cursor_grab.run_if(in_state(Screen::Gameplay)), |
||||
); |
||||
} |
||||
|
||||
fn update_cursor_grab( |
||||
mut mouse_button_input: ResMut<ButtonInput<MouseButton>>, |
||||
key_input: Res<ButtonInput<KeyCode>>, |
||||
window: Single<(&mut Window, &mut CursorOptions)>, |
||||
) { |
||||
let (mut window, mut cursor) = window.into_inner(); |
||||
|
||||
let is_grabbed = cursor.grab_mode != CursorGrabMode::None; |
||||
let request_grab = mouse_button_input.any_just_pressed([MouseButton::Left, MouseButton::Right]); |
||||
let request_ungrab = !window.focused || key_input.just_pressed(KeyCode::Escape); |
||||
|
||||
if !is_grabbed && request_grab && !request_ungrab { |
||||
cursor.grab_mode = CursorGrabMode::Locked; |
||||
|
||||
// To prevent other systems (such as `place_break_blocks`)
|
||||
// from seeing the mouse button inputs, clear the state here.
|
||||
mouse_button_input.clear(); |
||||
} |
||||
|
||||
if is_grabbed && request_ungrab && !request_grab { |
||||
cursor.grab_mode = CursorGrabMode::None; |
||||
} |
||||
|
||||
if is_grabbed && !request_ungrab { |
||||
// Set the cursor position to the center of the window, so that when
|
||||
// it is ungrabbed, it will reappear there. Because `is_grabbed` is
|
||||
// not updated on grab, this block is delayed by one frame.
|
||||
//
|
||||
// On Wayland, since the cursor is locked into place, this only needs
|
||||
// to be done once. Unfortunately, for some reason this doesn't work
|
||||
// in the same frame as setting `grab_mode`, and would log an error.
|
||||
//
|
||||
// On X11, the cursor can't be locked into place, only confined to the
|
||||
// window bounds, so we repeatedly move the cursor back to the center
|
||||
// while it's grabbed.
|
||||
//
|
||||
// On the web, the cursor can be locked, but setting its position is
|
||||
// not supported at all, so this would instead log a bunch of errors.
|
||||
let center = window.resolution.size() / 2.; |
||||
#[cfg(not(target_family = "wasm"))] // skip on web
|
||||
window.set_cursor_position(Some(center)); |
||||
} |
||||
|
||||
// Keep cursor visbility in sync with `grab_mode`.
|
||||
cursor.visible = cursor.grab_mode == CursorGrabMode::None; |
||||
} |
||||
|
||||
pub fn is_cursor_grabbed(cursor: Single<&CursorOptions>) -> bool { |
||||
cursor.grab_mode != CursorGrabMode::None |
||||
} |
||||
@ -0,0 +1,30 @@ |
||||
use bevy::prelude::*; |
||||
use common::prelude::*; |
||||
use lightyear::prelude::input::native::*; |
||||
|
||||
use bevy::input::mouse::AccumulatedMouseMotion; |
||||
|
||||
use crate::input::is_cursor_grabbed; |
||||
|
||||
#[derive(Resource, Deref, DerefMut, Debug)] |
||||
pub struct MouseSensitivity(Vec2); |
||||
|
||||
impl Default for MouseSensitivity { |
||||
fn default() -> Self { |
||||
Self(Vec2::splat(0.2).map(f32::to_radians)) |
||||
} |
||||
} |
||||
|
||||
pub fn plugin(app: &mut App) { |
||||
app.init_resource::<MouseSensitivity>(); |
||||
app.add_systems(Update, update_head_orientation.run_if(is_cursor_grabbed)); |
||||
} |
||||
|
||||
fn update_head_orientation( |
||||
accumulated_mouse_motion: Res<AccumulatedMouseMotion>, |
||||
mouse_sensitivity: Res<MouseSensitivity>, |
||||
mut orientation: Single<&mut HeadOrientation, With<InputMarker<Inputs>>>, |
||||
) { |
||||
let delta = accumulated_mouse_motion.delta * **mouse_sensitivity; |
||||
**orientation = orientation.update(delta); |
||||
} |
||||
@ -0,0 +1,15 @@ |
||||
use bevy::prelude::*; |
||||
|
||||
mod client_inputs; |
||||
mod cursor_grab; |
||||
mod head_orientation; |
||||
|
||||
pub use cursor_grab::is_cursor_grabbed; |
||||
|
||||
pub fn plugin(app: &mut App) { |
||||
app.add_plugins(( |
||||
client_inputs::plugin, |
||||
cursor_grab::plugin, |
||||
head_orientation::plugin, |
||||
)); |
||||
} |
||||
@ -0,0 +1,68 @@ |
||||
use bevy::prelude::*; |
||||
use common::prelude::*; |
||||
|
||||
use bevy::window::{CursorGrabMode, CursorOptions}; |
||||
|
||||
use crate::Screen; |
||||
|
||||
pub fn plugin(app: &mut App) { |
||||
app.load_resource::<CrosshairAssets>(); |
||||
app.add_systems(OnEnter(Screen::Gameplay), setup_crosshair); |
||||
app.add_systems(Update, update_crosshair_visibility); |
||||
} |
||||
|
||||
#[derive(Resource, Asset, Reflect, Clone)] |
||||
#[reflect(Resource)] |
||||
pub struct CrosshairAssets { |
||||
#[dependency] |
||||
image: Handle<Image>, |
||||
} |
||||
|
||||
impl FromWorld for CrosshairAssets { |
||||
fn from_world(world: &mut World) -> Self { |
||||
let assets = world.resource::<AssetServer>(); |
||||
Self { |
||||
image: assets.load("crosshair.png"), |
||||
} |
||||
} |
||||
} |
||||
|
||||
#[derive(Component)] |
||||
pub struct Crosshair; |
||||
|
||||
fn setup_crosshair(mut commands: Commands, assets: Res<CrosshairAssets>) { |
||||
commands.spawn(( |
||||
Node { |
||||
width: percent(100), |
||||
height: percent(100), |
||||
align_items: AlignItems::Center, |
||||
justify_content: JustifyContent::Center, |
||||
..default() |
||||
}, |
||||
children![( |
||||
Crosshair, |
||||
Node { |
||||
width: px(64), |
||||
height: px(64), |
||||
..default() |
||||
}, |
||||
ImageNode { |
||||
image: assets.image.clone(), |
||||
..default() |
||||
}, |
||||
// Hidden by default, because cursor shouldn't be grabbed at startup either.
|
||||
Visibility::Hidden, |
||||
)], |
||||
)); |
||||
} |
||||
|
||||
fn update_crosshair_visibility( |
||||
cursor: Single<&CursorOptions, Changed<CursorOptions>>, |
||||
crosshair: Single<&mut Visibility, With<Crosshair>>, |
||||
) { |
||||
let is_grabbed = cursor.grab_mode != CursorGrabMode::None; |
||||
let mut crosshair_visibility = crosshair.into_inner(); |
||||
*crosshair_visibility = (!is_grabbed || cursor.visible) |
||||
.then_some(Visibility::Hidden) |
||||
.unwrap_or_default(); |
||||
} |
||||
@ -0,0 +1,8 @@ |
||||
use bevy::prelude::*; |
||||
|
||||
mod crosshair; |
||||
mod loading_screen; |
||||
|
||||
pub fn plugin(app: &mut App) { |
||||
app.add_plugins((crosshair::plugin, loading_screen::plugin)); |
||||
} |
||||
@ -1,12 +1,19 @@ |
||||
mod client; |
||||
mod protocol; |
||||
mod server; |
||||
use bevy::prelude::*; |
||||
|
||||
pub(crate) mod protocol; |
||||
|
||||
mod client; |
||||
mod client_webtransport; |
||||
mod server; |
||||
mod server_webtransport; |
||||
|
||||
pub use client::*; |
||||
pub use protocol::*; |
||||
pub use server::*; |
||||
pub use crate::network::client_webtransport::ConnectWebTransportCommand; |
||||
pub use crate::network::server::StartLocalServerCommand; |
||||
#[cfg(not(target_family = "wasm"))] |
||||
pub use crate::network::server_webtransport::StartWebTransportServerCommand; |
||||
|
||||
pub const DEFAULT_PORT: u16 = 13580; |
||||
|
||||
pub fn plugin(app: &mut App) { |
||||
app.add_plugins((protocol::plugin, client::plugin, server::plugin)); |
||||
} |
||||
|
||||
@ -1,40 +1,95 @@ |
||||
use std::f32::consts::{PI, TAU}; |
||||
|
||||
use bevy::prelude::*; |
||||
use lightyear::prelude::*; |
||||
|
||||
use lightyear::prelude::input::native::ActionState; |
||||
use serde::{Deserialize, Serialize}; |
||||
|
||||
use crate::network::Inputs; |
||||
use crate::network::protocol::Inputs; |
||||
|
||||
const MOVEMENT_SPEED: f32 = 5.0; |
||||
const PITCH_LIMIT: f32 = TAU / 4.0; |
||||
|
||||
#[derive(Component, PartialEq, Deserialize, Serialize)] |
||||
#[require(Transform)] |
||||
#[derive(Component, Deserialize, Serialize, PartialEq)] |
||||
#[require(Transform, HeadOrientation)] |
||||
pub struct Player; |
||||
|
||||
#[derive(Component, Deserialize, Serialize, Reflect, Default, Clone, Copy, PartialEq, Debug)] |
||||
#[serde(from = "HeadOrientationUnchecked")] |
||||
pub struct HeadOrientation { |
||||
yaw: f32, |
||||
pitch: f32, |
||||
} |
||||
|
||||
impl HeadOrientation { |
||||
pub fn new(yaw: f32, pitch: f32) -> Self { |
||||
Self { |
||||
yaw: (yaw + PI).rem_euclid(TAU) - PI, |
||||
pitch: pitch.clamp(-PITCH_LIMIT, PITCH_LIMIT), |
||||
} |
||||
} |
||||
|
||||
pub fn yaw(&self) -> f32 { |
||||
self.yaw |
||||
} |
||||
|
||||
pub fn pitch(&self) -> f32 { |
||||
self.pitch |
||||
} |
||||
|
||||
pub fn update(&self, motion: Vec2) -> Self { |
||||
Self::new(self.yaw - motion.x, self.pitch - motion.y) |
||||
} |
||||
|
||||
pub fn to_quat(&self) -> Quat { |
||||
Quat::from_euler(EulerRot::ZYX, 0.0, self.yaw, self.pitch) |
||||
} |
||||
} |
||||
|
||||
#[derive(Deserialize)] |
||||
#[serde(rename = "HeadOrientation")] |
||||
struct HeadOrientationUnchecked { |
||||
yaw: f32, |
||||
pitch: f32, |
||||
} |
||||
|
||||
impl From<HeadOrientationUnchecked> for HeadOrientation { |
||||
fn from(value: HeadOrientationUnchecked) -> Self { |
||||
Self::new(value.yaw, value.pitch) |
||||
} |
||||
} |
||||
|
||||
pub fn movement( |
||||
time: Res<Time<Fixed>>, |
||||
mut players: Query< |
||||
(&mut Transform, &ActionState<Inputs>), |
||||
// Must be a `Player` which is either be `ControlledBy` a remote
|
||||
// client (server-side) or its movement `Predicted` on the client.
|
||||
(&mut Transform, &mut HeadOrientation, &ActionState<Inputs>), |
||||
// Must be a `Player` which is either be `ControlledBy` a
|
||||
// remote client (server-side) or `Predicted` on the client.
|
||||
(With<Player>, Or<(With<ControlledBy>, With<Predicted>)>), |
||||
>, |
||||
) { |
||||
for (mut transform, input) in players.iter_mut() { |
||||
for (mut transform, mut orientation, inputs) in players.iter_mut() { |
||||
*orientation = inputs.look; |
||||
|
||||
let dt = time.delta_secs(); |
||||
let mut translation = Vec3::ZERO; |
||||
if input.movement != Vec2::ZERO { |
||||
let movement = input.movement.clamp_length_max(1.0); |
||||
if inputs.movement != Vec2::ZERO { |
||||
let rotation = Rot2::radians(-orientation.yaw()); |
||||
let movement = rotation * inputs.movement.clamp_length_max(1.0); |
||||
translation.x += movement.x; |
||||
translation.z += movement.y; |
||||
} |
||||
if input.up { |
||||
if inputs.up { |
||||
translation.y += 1.0; |
||||
} |
||||
if input.down { |
||||
if inputs.down { |
||||
translation.y -= 1.0; |
||||
} |
||||
|
||||
transform.translation += translation * MOVEMENT_SPEED * dt; |
||||
|
||||
// TODO: Should affect camera/head rotation only, not the entire player.
|
||||
transform.rotation = orientation.to_quat(); |
||||
} |
||||
} |
||||
|
||||
Loading…
Reference in new issue