Skip to content

Commit 753563c

Browse files
committed
add third-person view camera & player box
1 parent d6e7846 commit 753563c

File tree

4 files changed

+174
-71
lines changed

4 files changed

+174
-71
lines changed

src/main.rs

+60-11
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66

77
use std::f32::consts::PI;
88

9-
use bevy::{core_pipeline::fxaa::Fxaa, prelude::*};
9+
use bevy::{
10+
core_pipeline::fxaa::Fxaa,
11+
prelude::*,
12+
};
13+
use voxel::player::{self, CameraMode};
1014

1115
mod debug;
1216
mod voxel;
@@ -20,17 +24,62 @@ fn main() {
2024
.run();
2125
}
2226

23-
fn setup(mut cmds: Commands) {
24-
cmds.spawn(Camera3dBundle {
25-
projection: bevy::render::camera::Projection::Perspective(PerspectiveProjection {
26-
fov: PI / 2.,
27-
far: 2048.0,
28-
..Default::default()
29-
}),
30-
transform: Transform::from_xyz(2.0, 160.0, 2.0).looking_at(Vec3::ZERO, Vec3::Y),
31-
..Default::default()
27+
fn setup(
28+
mut cmds: Commands,
29+
mut meshes: ResMut<Assets<Mesh>>,
30+
mut materials: ResMut<Assets<StandardMaterial>>,
31+
) {
32+
cmds.spawn((
33+
player::Player,
34+
VisibilityBundle {
35+
visibility: Visibility::Visible,
36+
..default()
37+
},
38+
TransformBundle {
39+
local: Transform::from_xyz(2.0, 170.0, 2.0).looking_to(Vec3::Z, Vec3::Y),
40+
..default()
41+
},
42+
))
43+
.with_children(|player| {
44+
player
45+
.spawn(player::Body)
46+
.insert(MaterialMeshBundle {
47+
mesh: meshes.add(Mesh::from(shape::Box::new(0.5, 1.8, 0.5))),
48+
material: materials.add(StandardMaterial {
49+
base_color: Color::WHITE,
50+
..default()
51+
}),
52+
transform: Transform::IDENTITY.looking_to(Vec3::Z, Vec3::Y),
53+
..default()
54+
});
55+
56+
player
57+
.spawn((
58+
player::Head,
59+
TransformBundle {
60+
// head is 1.8m above feet
61+
local: Transform::from_translation(Vec3::new(0.0, 0.9, 0.0))
62+
.looking_to(Vec3::Z, Vec3::Y),
63+
..default()
64+
},
65+
))
66+
.with_children(|head| {
67+
// spawn camera as a child of head
68+
head.spawn(Camera3dBundle {
69+
projection: bevy::render::camera::Projection::Perspective(
70+
PerspectiveProjection {
71+
fov: PI / 2.,
72+
far: 2048.0,
73+
..Default::default()
74+
},
75+
),
76+
transform: Transform::from_translation(Vec3::new(0.0, 0.0, -5.0))
77+
.looking_to(Vec3::Z, Vec3::Y),
78+
..Default::default()
79+
})
80+
.insert(CameraMode::ThirdPersonForward);
81+
});
3282
})
33-
.insert(voxel::player::PlayerController::default())
3483
.insert(Fxaa::default())
3584
.insert(bevy_atmosphere::plugin::AtmosphereCamera::default());
3685

src/voxel/world/chunks.rs

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
use bevy::{
22
math::IVec3,
33
prelude::{
4-
Changed, Commands, CoreSet, Entity, GlobalTransform, IntoSystemConfig, IntoSystemConfigs,
5-
IntoSystemSetConfig, Plugin, Query, Res, ResMut, Resource, SystemSet, With,
4+
Changed, Commands, CoreSet, Entity, GlobalTransform, IntoSystemConfig, IntoSystemSetConfig,
5+
Plugin, Query, Res, ResMut, Resource, SystemSet, With, IntoSystemConfigs,
66
},
77
utils::{HashMap, HashSet},
88
};
99
use float_ord::FloatOrd;
1010

11-
use super::{player::PlayerController, Chunk, ChunkShape, CHUNK_LENGTH};
11+
use super::{player, Chunk, ChunkShape, CHUNK_LENGTH};
1212
use crate::voxel::storage::ChunkMap;
1313
use crate::voxel::Voxel;
1414

1515
/// Updates the current chunk position for the current player.
1616
fn update_player_pos(
17-
player: Query<&GlobalTransform, (With<PlayerController>, Changed<GlobalTransform>)>,
17+
player: Query<&GlobalTransform, (With<player::Body>, Changed<GlobalTransform>)>,
1818
mut chunk_pos: ResMut<CurrentLocalPlayerChunk>,
1919
) {
2020
if let Ok(ply) = player.get_single() {

src/voxel/world/player.rs

+108-45
Original file line numberDiff line numberDiff line change
@@ -4,78 +4,108 @@ use std::f32::consts::FRAC_PI_2;
44

55
use crate::debug::DebugUISet;
66

7-
// Reusing the player controller impl for now.
7+
const BODY_ROTATION_SLERP: f32 = 0.5;
88

9-
pub const DEFAULT_CAMERA_SENS: f32 = 0.005;
9+
#[derive(Component)]
10+
pub struct Player;
11+
12+
/// Marker component for player body.
13+
#[derive(Component)]
14+
pub struct Body;
1015

11-
#[derive(Default, Component)]
12-
pub struct PlayerController {
13-
yaw: f32,
14-
pitch: f32,
15-
cursor_locked: bool,
16+
#[derive(Component)]
17+
pub struct Head;
18+
19+
#[derive(Component, Debug, Clone, Copy)]
20+
pub enum CameraMode {
21+
FirstPerson,
22+
ThirdPersonForward,
1623
}
1724

18-
pub fn handle_player_mouse_move(
19-
mut query: Query<(&mut PlayerController, &mut Transform)>,
20-
mut mouse_motion_event_reader: EventReader<MouseMotion>,
21-
mut window: Query<&mut Window>,
22-
) {
23-
let (mut controller, mut transform) = query.single_mut();
24-
let mut delta = Vec2::ZERO;
25+
impl CameraMode {
26+
fn next(self) -> Self {
27+
match self {
28+
Self::FirstPerson => Self::ThirdPersonForward,
29+
Self::ThirdPersonForward => Self::FirstPerson,
30+
}
31+
}
2532

26-
if controller.cursor_locked {
27-
for mouse_move in mouse_motion_event_reader.iter() {
28-
delta += mouse_move.delta;
33+
fn translation(self) -> Vec3 {
34+
match self {
35+
Self::FirstPerson => Vec3::ZERO,
36+
Self::ThirdPersonForward => Vec3::Z * -5.0,
2937
}
3038
}
39+
}
3140

32-
let mut first_win = window.single_mut();
33-
first_win.cursor.visible = !controller.cursor_locked;
34-
first_win.cursor.grab_mode = if controller.cursor_locked {
35-
CursorGrabMode::Locked
36-
} else {
37-
CursorGrabMode::None
38-
};
41+
// Reusing the player controller impl for now.
3942

40-
if delta == Vec2::ZERO {
41-
return;
42-
}
43+
pub const DEFAULT_CAMERA_SENS: f32 = 0.005;
4344

44-
let mut new_pitch = delta.y.mul_add(DEFAULT_CAMERA_SENS, controller.pitch);
45-
let new_yaw = delta.x.mul_add(-DEFAULT_CAMERA_SENS, controller.yaw);
45+
fn handle_player_mouse_move(
46+
mut head: Query<&mut Transform, With<Head>>,
47+
mut mouse_motion_event_reader: EventReader<MouseMotion>,
48+
windows: Query<&Window>,
49+
) {
50+
let window = windows.single();
51+
let mut head_transform = head.single_mut();
52+
let mut delta = Vec2::ZERO;
4653

47-
new_pitch = new_pitch.clamp(-FRAC_PI_2, FRAC_PI_2);
54+
for mouse_move in mouse_motion_event_reader.iter() {
55+
delta -= mouse_move.delta;
56+
}
4857

49-
controller.yaw = new_yaw;
50-
controller.pitch = new_pitch;
58+
if !matches!(window.cursor.grab_mode, CursorGrabMode::Locked) {
59+
return;
60+
}
5161

52-
transform.rotation =
53-
Quat::from_axis_angle(Vec3::Y, new_yaw) * Quat::from_axis_angle(-Vec3::X, new_pitch);
62+
let (yaw, pitch, _roll) = head_transform.rotation.to_euler(EulerRot::YXZ);
63+
let yaw = delta.x.mul_add(DEFAULT_CAMERA_SENS, yaw);
64+
let pitch = delta
65+
.y
66+
.mul_add(-DEFAULT_CAMERA_SENS, pitch)
67+
// ensure that the look direction always has a component in the xz plane:
68+
.clamp(-FRAC_PI_2 + 1e-5, FRAC_PI_2 - 1e-5);
69+
head_transform.rotation = Quat::from_euler(EulerRot::YXZ, yaw, pitch, 0.);
5470
}
5571

56-
pub fn handle_player_input(
72+
fn handle_player_keyboard_input(
5773
mut egui: EguiContexts,
58-
mut query: Query<(&mut PlayerController, &mut Transform)>,
74+
// mut queries: ParamSet<Query<&mut Transform, With<Body>>>,
75+
mut queries: ParamSet<(
76+
Query<&mut Transform, With<Player>>,
77+
Query<&Transform, With<Body>>,
78+
)>,
5979
keys: Res<Input<KeyCode>>,
6080
btns: Res<Input<MouseButton>>,
81+
mut windows: Query<&mut Window>,
6182
) {
62-
let (mut controller, mut transform) = query.single_mut();
83+
let mut window = windows.single_mut();
6384

6485
// cursor grabbing
65-
// @todo: this should prevent cursor grabbing when the user is interacting with a debug UI. Why doesn't this work?
6686
if btns.just_pressed(MouseButton::Left) && !egui.ctx_mut().wants_pointer_input() {
67-
controller.cursor_locked = true;
87+
window.cursor.grab_mode = CursorGrabMode::Locked;
88+
window.cursor.visible = false;
6889
}
6990

7091
// cursor ungrabbing
7192
if keys.just_pressed(KeyCode::Escape) {
72-
controller.cursor_locked = false;
93+
window.cursor.grab_mode = CursorGrabMode::None;
94+
window.cursor.visible = true;
7395
}
74-
let mut direction = Vec3::ZERO;
7596

76-
let forward = transform.rotation.mul_vec3(Vec3::Z).normalize() * Vec3::new(1.0, 0., 1.0);
77-
let right = transform.rotation.mul_vec3(Vec3::X).normalize();
97+
let (forward, right) = {
98+
let body = queries.p1();
99+
let body_transform = body.single();
100+
let forward = body_transform.rotation.mul_vec3(Vec3::Z).normalize();
101+
let right = Vec3::Y.cross(forward); // @todo(meyerzinn): not sure why this is the correct orientation
102+
(forward, right)
103+
};
104+
105+
let mut body = queries.p0();
106+
let mut body_transform = body.single_mut();
78107

108+
let mut direction = Vec3::ZERO;
79109
let mut acceleration = 1.0f32;
80110

81111
if keys.pressed(KeyCode::W) {
@@ -111,11 +141,39 @@ pub fn handle_player_input(
111141
}
112142

113143
// hardcoding 0.10 as a factor for now to not go zoomin across the world.
114-
transform.translation += direction.x * right * acceleration
144+
body_transform.translation += direction.x * right * acceleration
115145
+ direction.z * forward * acceleration
116146
+ direction.y * Vec3::Y * acceleration;
117147
}
118148

149+
fn handle_player_change_camera_mode(
150+
keys: Res<Input<KeyCode>>,
151+
mut cameras: Query<(&mut CameraMode, &mut Transform)>,
152+
) {
153+
if keys.just_pressed(KeyCode::F5) {
154+
let (mut mode, mut transform) = cameras.single_mut();
155+
*mode = mode.next();
156+
transform.translation = mode.translation();
157+
}
158+
}
159+
160+
fn update_player_body_rotation(
161+
mut queries: ParamSet<(
162+
Query<&mut Transform, With<Body>>,
163+
Query<&Transform, With<Head>>,
164+
)>,
165+
) {
166+
let yaw = {
167+
let head = queries.p1();
168+
let (yaw, _pitch, _roll) = head.single().rotation.to_euler(EulerRot::YXZ);
169+
yaw
170+
};
171+
let mut body = queries.p0();
172+
let mut body_transform = body.single_mut();
173+
let desired = Quat::from_euler(EulerRot::YXZ, yaw, 0., 0.);
174+
body_transform.rotation = body_transform.rotation.slerp(desired, BODY_ROTATION_SLERP);
175+
}
176+
119177
#[derive(Hash, Copy, Clone, PartialEq, Eq, Debug, SystemSet)]
120178
/// Systems related to player controls.
121179
pub struct PlayerControllerSet;
@@ -125,7 +183,12 @@ pub struct VoxelWorldPlayerControllerPlugin;
125183
impl Plugin for VoxelWorldPlayerControllerPlugin {
126184
fn build(&self, app: &mut App) {
127185
app.add_systems(
128-
(handle_player_input, handle_player_mouse_move)
186+
(
187+
handle_player_mouse_move,
188+
update_player_body_rotation,
189+
handle_player_keyboard_input,
190+
handle_player_change_camera_mode,
191+
)
129192
.chain()
130193
.in_base_set(CoreSet::Update)
131194
.after(DebugUISet::Display),

src/voxel/world/sky.rs

+2-11
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use bevy::prelude::{
33
Query, Res, Resource, Transform, Vec3, With,
44
};
55

6-
use super::player::PlayerController;
6+
use super::player::CameraMode;
77

88
#[derive(Resource, Deref)]
99
struct SkyLightEntity(Entity);
@@ -17,15 +17,6 @@ fn setup_sky_lighting(mut cmds: Commands) {
1717
directional_light: DirectionalLight {
1818
color: Color::WHITE,
1919
shadows_enabled: true,
20-
// shadow_projection: OrthographicProjection {
21-
// // left: -SIZE,
22-
// // right: SIZE,
23-
// // bottom: -SIZE,
24-
// // top: SIZE,
25-
// near: -SIZE,
26-
// far: SIZE,
27-
// ..Default::default()
28-
// },
2920
..Default::default()
3021
},
3122
..Default::default()
@@ -39,7 +30,7 @@ fn update_light_position(
3930
sky_light_entity: Res<SkyLightEntity>,
4031
mut queries: ParamSet<(
4132
Query<&mut Transform>,
42-
Query<&Transform, With<PlayerController>>,
33+
Query<&Transform, With<CameraMode>>,
4334
)>,
4435
) {
4536
let sky_light_entity = **sky_light_entity;

0 commit comments

Comments
 (0)