diff --git a/CHANGELOG.md b/CHANGELOG.md index 45c8a9b226..5b21edd215 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Added context-sensitive crosshair + ### Changed +- Improved camera aiming + ### Removed ## [0.6.0] - 2020-05-16 diff --git a/assets/voxygen/shaders/figure-frag.glsl b/assets/voxygen/shaders/figure-frag.glsl index ee5d6d5d47..26d85fea88 100644 --- a/assets/voxygen/shaders/figure-frag.glsl +++ b/assets/voxygen/shaders/figure-frag.glsl @@ -54,7 +54,7 @@ void main() { vec3 color = mix(mix(surf_color, fog_color, fog_level), clouds.rgb, clouds.a); if ((flags & 1) == 1 && int(cam_mode) == 1) { - float distance = distance(vec3(cam_pos), vec3(model_mat * vec4(vec3(0), 1))) - 2; + float distance = distance(vec3(cam_pos), focus_pos.xyz) - 2; float opacity = clamp(distance / distance_divider, 0, 1); diff --git a/assets/voxygen/shaders/figure-vert.glsl b/assets/voxygen/shaders/figure-vert.glsl index c84139f353..e39718670b 100644 --- a/assets/voxygen/shaders/figure-vert.glsl +++ b/assets/voxygen/shaders/figure-vert.glsl @@ -42,6 +42,8 @@ void main() { combined_mat * vec4(pos, 1)).xyz; + f_pos.z -= 25.0 * pow(distance(focus_pos.xy, f_pos.xy) / view_distance.x, 20.0); + f_col = vec3((uvec3(v_col) >> uvec3(0, 8, 16)) & uvec3(0xFFu)) / 255.0; f_ao = float(v_ao_bone & 0x3u) / 4.0; diff --git a/assets/voxygen/shaders/player-shadow-frag.glsl b/assets/voxygen/shaders/player-shadow-frag.glsl index 25bd05ed0a..1aba8bc4fa 100644 --- a/assets/voxygen/shaders/player-shadow-frag.glsl +++ b/assets/voxygen/shaders/player-shadow-frag.glsl @@ -29,8 +29,8 @@ uniform u_bones { out vec4 tgt_color; void main() { - float distance = distance(vec3(cam_pos), vec3(model_mat * vec4(vec3(0), 1))) - 2; - + float distance = distance(vec3(cam_pos), focus_pos.xyz) - 2; + float opacity = clamp(distance / distance_divider, 0, 1); if(threshold_matrix[int(gl_FragCoord.x) % 4][int(gl_FragCoord.y) % 4] > opacity) { @@ -42,4 +42,4 @@ void main() { } tgt_color = vec4(0.0,0.0,0.0, 1.0); -} \ No newline at end of file +} diff --git a/common/src/comp/character_state.rs b/common/src/comp/character_state.rs index 6cc4f90e12..4521dc1724 100644 --- a/common/src/comp/character_state.rs +++ b/common/src/comp/character_state.rs @@ -91,6 +91,17 @@ impl CharacterState { } } + pub fn is_aimed(&self) -> bool { + match self { + CharacterState::BasicMelee(_) + | CharacterState::BasicRanged(_) + | CharacterState::DashMelee(_) + | CharacterState::TripleStrike(_) + | CharacterState::BasicBlock => true, + _ => false, + } + } + pub fn is_block(&self) -> bool { match self { CharacterState::BasicBlock => true, diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 046f66238a..c5a1e2878b 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -207,6 +207,11 @@ pub struct DebugInfo { pub num_figures_visible: u32, } +pub struct HudInfo { + pub is_aiming: bool, + pub is_first_person: bool, +} + pub enum Event { SendMessage(String), AdjustMousePan(u32), @@ -455,6 +460,7 @@ pub struct Hud { slot_manager: slots::SlotManager, hotbar: hotbar::State, events: Vec, + crosshair_opacity: f32, } impl Hud { @@ -529,6 +535,7 @@ impl Hud { slot_manager, hotbar: hotbar::State::new(), events: Vec::new(), + crosshair_opacity: 0.0, } } @@ -544,6 +551,7 @@ impl Hud { global_state: &GlobalState, debug_info: DebugInfo, dt: Duration, + info: HudInfo, ) -> Vec { let mut events = std::mem::replace(&mut self.events, Vec::new()); let (ref mut ui_widgets, ref mut tooltip_manager) = self.ui.set_widgets(); @@ -602,7 +610,14 @@ impl Hud { .set(self.ids.death_bg, ui_widgets); } // Crosshair - if !self.show.help && !stats.is_dead { + let show_crosshair = (info.is_aiming || info.is_first_person) && !stats.is_dead; + self.crosshair_opacity = Lerp::lerp( + self.crosshair_opacity, + if show_crosshair { 1.0 } else { 0.0 }, + 5.0 * dt.as_secs_f32(), + ); + + if !self.show.help { Image::new( // TODO: Do we want to match on this every frame? match global_state.settings.gameplay.crosshair_type { @@ -617,7 +632,7 @@ impl Hud { 1.0, 1.0, 1.0, - global_state.settings.gameplay.crosshair_transp, + self.crosshair_opacity * global_state.settings.gameplay.crosshair_transp, ))) .set(self.ids.crosshair_outer, ui_widgets); Image::new(self.imgs.crosshair_inner) @@ -2330,6 +2345,7 @@ impl Hud { debug_info: DebugInfo, camera: &Camera, dt: Duration, + info: HudInfo, ) -> Vec { // conrod eats tabs. Un-eat a tabstop so tab completion can work if self.ui.ui.global_input().events().any(|event| { @@ -2355,7 +2371,7 @@ impl Hud { if let Some(maybe_id) = self.to_focus.take() { self.ui.focus_widget(maybe_id); } - let events = self.update_layout(client, global_state, debug_info, dt); + let events = self.update_layout(client, global_state, debug_info, dt, info); let camera::Dependents { view_mat, proj_mat, .. } = camera.dependents(); diff --git a/voxygen/src/scene/camera.rs b/voxygen/src/scene/camera.rs index 2dc24fa000..451e60c7cb 100644 --- a/voxygen/src/scene/camera.rs +++ b/voxygen/src/scene/camera.rs @@ -168,9 +168,10 @@ impl Camera { pub fn zoom_switch(&mut self, delta: f32) { if delta > 0_f32 || self.mode != CameraMode::FirstPerson { let t = self.tgt_dist + delta; + const MIN_THIRD_PERSON: f32 = 2.35; match self.mode { CameraMode::ThirdPerson => { - if t < 1_f32 { + if t < MIN_THIRD_PERSON { self.set_mode(CameraMode::FirstPerson); } else { self.tgt_dist = t; @@ -178,16 +179,16 @@ impl Camera { }, CameraMode::FirstPerson => { self.set_mode(CameraMode::ThirdPerson); - self.tgt_dist = 1_f32; + self.tgt_dist = MIN_THIRD_PERSON; }, } } } - /// Get the distance of the camera from the target - pub fn get_distance(&self) -> f32 { self.tgt_dist } + /// Get the distance of the camera from the focus + pub fn get_distance(&self) -> f32 { self.dist } - /// Set the distance of the camera from the target (i.e., zoom). + /// Set the distance of the camera from the focus (i.e., zoom). pub fn set_distance(&mut self, dist: f32) { self.tgt_dist = dist; } pub fn update(&mut self, time: f64, dt: f32, smoothing_enabled: bool) { @@ -197,16 +198,33 @@ impl Camera { self.dist = f32::lerp( self.dist, self.tgt_dist, - (delta as f32) / self.interp_time(), + 0.65 * (delta as f32) / self.interp_time(), ); } - if (self.focus - self.tgt_focus).magnitude() > 0.01 { - self.focus = Vec3::lerp( + if (self.focus - self.tgt_focus).magnitude_squared() > 0.001 { + let lerped_focus = Lerp::lerp( self.focus, self.tgt_focus, - (delta as f32) / self.interp_time(), + (delta as f32) / self.interp_time() + * if matches!(self.mode, CameraMode::FirstPerson) { + 2.0 + } else { + 1.0 + }, ); + + // Snap when close enough in x/y, but lerp otherwise + if (self.focus.xy() - self.tgt_focus.xy()).magnitude_squared() > 2.0f32.powf(2.0) { + self.focus.x = lerped_focus.x; + self.focus.y = lerped_focus.y; + } else { + self.focus.x = self.tgt_focus.x; + self.focus.y = self.tgt_focus.y; + } + + // Always lerp in z + self.focus.z = lerped_focus.z; } let lerp_angle = |a: f32, b: f32, rate: f32| { @@ -280,5 +298,13 @@ impl Camera { } /// Get the mode of the camera - pub fn get_mode(&self) -> CameraMode { self.mode } + pub fn get_mode(&self) -> CameraMode { + // Perfom a bit of a trick... don't report first-person until the camera has + // lerped close enough to the player. + match self.mode { + CameraMode::FirstPerson if self.dist < 0.5 => CameraMode::FirstPerson, + CameraMode::FirstPerson => CameraMode::ThirdPerson, + mode => mode, + } + } } diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index 369ac89678..914b5e98f0 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -78,6 +78,7 @@ pub struct SceneData<'a> { pub mouse_smoothing: bool, pub sprite_render_distance: f32, pub figure_lod_render_distance: f32, + pub is_aiming: bool, } impl Scene { @@ -198,7 +199,8 @@ impl Scene { let is_running = ecs .read_storage::() .get(scene_data.player_entity) - .map(|v| v.0.magnitude_squared() > RUNNING_THRESHOLD.powi(2)); + .map(|v| v.0.magnitude_squared() > RUNNING_THRESHOLD.powi(2)) + .unwrap_or(false); let on_ground = ecs .read_storage::() @@ -229,18 +231,18 @@ impl Scene { CameraMode::FirstPerson => { if player_rolling { player_scale * 0.8 - } else if is_running.unwrap_or(false) && on_ground.unwrap_or(false) { - player_scale * 1.6 + (scene_data.state.get_time() as f32 * 17.0).sin() * 0.05 + } else if is_running && on_ground.unwrap_or(false) { + player_scale * 1.65 + (scene_data.state.get_time() as f32 * 17.0).sin() * 0.05 } else { - player_scale * 1.6 + player_scale * 1.65 } }, - CameraMode::ThirdPerson => 1.2, + CameraMode::ThirdPerson if scene_data.is_aiming => player_scale * 2.1, + CameraMode::ThirdPerson => player_scale * 1.65, }; - self.camera.set_focus_pos( - player_pos + Vec3::unit_z() * (up + dist * 0.15 - tilt.min(0.0) * dist * 0.4), - ); + self.camera + .set_focus_pos(player_pos + Vec3::unit_z() * (up - tilt.min(0.0).sin() * dist * 0.6)); // Tick camera for interpolation. self.camera.update( diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index e462b019ff..ff67858882 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -1,6 +1,6 @@ use crate::{ ecs::MyEntity, - hud::{DebugInfo, Event as HudEvent, Hud, PressBehavior}, + hud::{DebugInfo, Event as HudEvent, Hud, HudInfo, PressBehavior}, i18n::{i18n_asset_key, VoxygenLocalization}, key_state::KeyState, menu::char_selection::CharSelectionState, @@ -166,6 +166,25 @@ impl PlayState for SessionState { _ => cam_pos, // Should never happen, but a safe fallback }; + let (is_aiming, aim_dir_offset) = { + let client = self.client.borrow(); + let is_aiming = client + .state() + .read_storage::() + .get(client.entity()) + .map(|cs| cs.is_aimed()) + .unwrap_or(false); + + ( + is_aiming, + if is_aiming { + Vec3::unit_z() * 0.025 + } else { + Vec3::zero() + }, + ) + }; + let cam_dir: Vec3 = Vec3::from(view_mat.inverted() * -Vec4::unit_z()); // Check to see whether we're aiming at anything @@ -435,7 +454,7 @@ impl PlayState for SessionState { if !free_look { ori = self.scene.camera().get_orientation(); - self.inputs.look_dir = Dir::from_unnormalized(cam_dir).unwrap(); + self.inputs.look_dir = Dir::from_unnormalized(cam_dir + aim_dir_offset).unwrap(); } // Calculate the movement input vector of the player from the current key // presses and the camera direction. @@ -511,6 +530,13 @@ impl PlayState for SessionState { }, &self.scene.camera(), clock.get_last_delta(), + HudInfo { + is_aiming, + is_first_person: matches!( + self.scene.camera().get_mode(), + camera::CameraMode::FirstPerson + ), + }, ); // Look for changes in the localization files @@ -737,6 +763,7 @@ impl PlayState for SessionState { .graphics .figure_lod_render_distance as f32, + is_aiming, }; // Runs if either in a multiplayer server or the singleplayer server is unpaused