Make nametages etc fixed size and only display within limited range

This commit is contained in:
Imbris 2020-01-24 22:06:41 -05:00
parent f175947a91
commit 166aba61f0
7 changed files with 113 additions and 195 deletions

View File

@ -81,6 +81,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Significantly reduced the use of warp during world generation.
- Parallelized and otherwise sped up significant parts of world generation.
- Various performance improvements to world generation.
- Nametags now a fixed size and shown in a limited range
### Removed

View File

@ -23,9 +23,6 @@ void main() {
f_color = v_color;
if (w_pos.w == 1.0) {
// In-game element
gl_Position = proj_mat * (view_mat * w_pos + vec4(v_pos, 0.0, 0.0));
} else if (w_pos.w == -1.0 ) {
// Fixed scale In-game element
vec4 projected_pos = proj_mat * view_mat * vec4(w_pos.xyz, 1.0);
gl_Position = vec4(projected_pos.xy / projected_pos.w + v_pos, 0.0, 1.0);

View File

@ -18,6 +18,9 @@ pub struct HpFloaterList {
// Keep from spawning more floaters from same hp change
// Note: this can't detect a change if equivalent healing and damage take place simultaneously
pub last_hp: u32,
// The time since you last damaged this entity
// Used to display nametags outside normal range if this time is below a certain value
pub time_since_last_dmg_by_me: Option<f32>,
}
impl Component for HpFloaterList {
type Storage = IDVStorage<Self>;

View File

@ -43,6 +43,7 @@ impl<'a> System<'a> for Sys {
HpFloaterList {
floaters: Vec::new(),
last_hp,
time_since_last_dmg_by_me: None,
},
);
}
@ -53,6 +54,12 @@ impl<'a> System<'a> for Sys {
.join()
.map(|(e, s, fl)| (e, s.health, fl))
{
// Increment timer for time since last damaged by me
hp_floater_list
.time_since_last_dmg_by_me
.as_mut()
.map(|t| *t += dt.0);
// Check if health has changed (won't work if damaged and then healed with
// equivalently in the same frame)
if hp_floater_list.last_hp != health.current() {
@ -65,7 +72,12 @@ impl<'a> System<'a> for Sys {
// health changes could be sent to the client as a list of events)
if match health.last_change.1.cause {
HealthSource::Attack { by } => {
my_entity.0 == entity || my_uid.map_or(false, |&uid| by == uid)
let by_me = my_uid.map_or(false, |&uid| by == uid);
// If the attack was by me also reset this timer
if by_me {
hp_floater_list.time_since_last_dmg_by_me = Some(0.0);
}
my_entity.0 == entity || by_me
}
HealthSource::Suicide => my_entity.0 == entity,
HealthSource::World => my_entity.0 == entity,
@ -106,6 +118,7 @@ impl<'a> System<'a> for Sys {
HpFloaterList {
ref mut floaters,
ref last_hp,
..
},
) in (&entities, &mut hp_floater_lists).join()
{

View File

@ -79,6 +79,13 @@ const GROUP_COLOR: Color = Color::Rgba(0.47, 0.84, 1.0, 1.0);
const FACTION_COLOR: Color = Color::Rgba(0.24, 1.0, 0.48, 1.0);
const KILL_COLOR: Color = Color::Rgba(1.0, 0.17, 0.17, 1.0);
/// Distance at which nametags are visible
const NAMETAG_RANGE: f32 = 40.0;
/// Time nametags stay visible after doing damage even if they are out of range in seconds
const NAMETAG_DMG_TIME: f32 = 60.0;
/// Range damaged triggered nametags can be seen
const NAMETAG_DMG_RANGE: f32 = 120.0;
widget_ids! {
struct Ids {
// Crosshair
@ -545,7 +552,6 @@ impl Hud {
let scales = ecs.read_storage::<comp::Scale>();
let entities = ecs.entities();
let me = client.entity();
let view_distance = client.view_distance().unwrap_or(1);
let own_level = stats
.get(client.entity())
.map_or(0, |stats| stats.level.level());
@ -632,21 +638,25 @@ impl Hud {
&stats,
&energy,
scales.maybe(),
hp_floater_lists.maybe(), // Potentially move this to its own loop
&hp_floater_lists,
)
.join()
.filter(|(entity, _, _, stats, _, _, _)| {
*entity != me && !stats.is_dead
//&& stats.health.current() != stats.health.maximum()
})
// Don't process health bars outside the vd (visibility further limited by ui backend)
.filter(|(_, pos, _, _, _, _, _)| {
Vec2::from(pos.0 - player_pos)
.map2(TerrainChunk::RECT_SIZE, |d: f32, sz| {
d.abs() as f32 / sz as f32
// Don't show outside a certain range
.filter(|(_, pos, _, _, _, _, hpfl)| {
pos.0.distance_squared(player_pos)
< (if hpfl
.time_since_last_dmg_by_me
.map_or(false, |t| t < NAMETAG_DMG_TIME)
{
NAMETAG_DMG_RANGE
} else {
NAMETAG_RANGE
})
.magnitude()
< view_distance as f32
.powi(2)
})
.map(|(_, pos, interpolated, stats, energy, scale, f)| {
(
@ -684,7 +694,6 @@ impl Hud {
Rectangle::fill_with([82.0, 8.0], Color::Rgba(0.3, 0.3, 0.3, 0.5))
.x_y(0.0, -25.0)
.position_ingame(pos + Vec3::new(0.0, 0.0, 1.5 * scale + 1.5))
.resolution(100.0)
.set(back_id, ui_widgets);
// % HP Filling
@ -699,7 +708,6 @@ impl Hud {
HP_COLOR
}))
.position_ingame(pos + Vec3::new(0.0, 0.0, 1.5 * scale + 1.5))
.resolution(100.0)
.set(health_bar_id, ui_widgets);
// % Mana Filling
Rectangle::fill_with(
@ -711,7 +719,6 @@ impl Hud {
)
.x_y(4.5 + (energy_percentage / 100.0 * 36.5) - 36.45, -28.0)
.position_ingame(pos + Vec3::new(0.0, 0.0, 1.5 * scale + 1.5))
.resolution(100.0)
.set(mana_bar_id, ui_widgets);
// Foreground
@ -720,12 +727,12 @@ impl Hud {
.x_y(0.0, -25.0)
.color(Some(Color::Rgba(1.0, 1.0, 1.0, 0.99)))
.position_ingame(pos + Vec3::new(0.0, 0.0, 1.5 * scale + 1.5))
.resolution(100.0)
.set(front_id, ui_widgets);
// Enemy SCT
if let Some(HpFloaterList { floaters, .. }) = hp_floater_list
if let Some(floaters) = Some(hp_floater_list)
.filter(|fl| !fl.floaters.is_empty() && global_state.settings.gameplay.sct)
.map(|l| &l.floaters)
{
// Colors
const WHITE: Rgb<f32> = Rgb::new(1.0, 0.9, 0.8);
@ -790,8 +797,6 @@ impl Hud {
.color(Color::Rgba(0.0, 0.0, 0.0, fade))
.x_y(0.0, y - 3.0)
.position_ingame(pos + Vec3::new(0.0, 0.0, 1.5 * scale as f32 + 1.8))
.fixed_scale()
.resolution(100.0)
.set(sct_bg_id, ui_widgets);
Text::new(&format!("{}", hp_damage.abs()))
.font_size(font_size)
@ -802,8 +807,6 @@ impl Hud {
Color::Rgba(0.1, 1.0, 0.1, fade)
})
.position_ingame(pos + Vec3::new(0.0, 0.0, 1.5 * scale as f32 + 1.8))
.fixed_scale()
.resolution(100.0)
.set(sct_id, ui_widgets);
} else {
for floater in floaters {
@ -846,8 +849,6 @@ impl Hud {
.position_ingame(
pos + Vec3::new(0.0, 0.0, 1.5 * scale as f32 + 1.8),
)
.fixed_scale()
.resolution(100.0)
.set(sct_bg_id, ui_widgets);
Text::new(&format!("{}", (floater.hp_change).abs()))
.font_size(font_size)
@ -860,8 +861,6 @@ impl Hud {
.position_ingame(
pos + Vec3::new(0.0, 0.0, 1.5 * scale as f32 + 1.8),
)
.fixed_scale()
.resolution(100.0)
.set(sct_id, ui_widgets);
}
}
@ -1107,19 +1106,24 @@ impl Hud {
&stats,
players.maybe(),
scales.maybe(),
&hp_floater_lists,
)
.join()
.filter(|(entity, _, _, stats, _, _)| *entity != me && !stats.is_dead)
// Don't process nametags outside the vd (visibility further limited by ui backend)
.filter(|(_, pos, _, _, _, _)| {
Vec2::from(pos.0 - player_pos)
.map2(TerrainChunk::RECT_SIZE, |d: f32, sz| {
d.abs() as f32 / sz as f32
.filter(|(entity, _, _, stats, _, _, _)| *entity != me && !stats.is_dead)
// Don't show outside a certain range
.filter(|(_, pos, _, _, _, _, hpfl)| {
pos.0.distance_squared(player_pos)
< (if hpfl
.time_since_last_dmg_by_me
.map_or(false, |t| t < NAMETAG_DMG_TIME)
{
NAMETAG_DMG_RANGE
} else {
NAMETAG_RANGE
})
.magnitude()
< view_distance as f32
.powi(2)
})
.map(|(_, pos, interpolated, stats, player, scale)| {
.map(|(_, pos, interpolated, stats, player, scale, _)| {
// TODO: This is temporary
// If the player used the default character name display their name instead
let name = if stats.name == "Character Name" {
@ -1156,14 +1160,12 @@ impl Hud {
.color(Color::Rgba(0.0, 0.0, 0.0, 1.0))
.x_y(-1.0, -1.0)
.position_ingame(pos + Vec3::new(0.0, 0.0, 1.5 * scale + 1.5))
.resolution(100.0)
.set(name_bg_id, ui_widgets);
Text::new(&name)
.font_size(20)
.color(Color::Rgba(0.61, 0.61, 0.89, 1.0))
.x_y(0.0, 0.0)
.position_ingame(pos + Vec3::new(0.0, 0.0, 1.5 * scale + 1.5))
.resolution(100.0)
.set(name_id, ui_widgets);
// Level
@ -1193,7 +1195,6 @@ impl Hud {
})
.x_y(-37.0, -24.0)
.position_ingame(pos + Vec3::new(0.0, 0.0, 1.5 * scale + 1.5))
.resolution(100.0)
.set(level_id, ui_widgets);
if level_comp > 9 {
let skull_ani = ((self.pulse * 0.7/*speed factor*/).cos() * 0.5 + 0.5) * 10.0; //Animation timer
@ -1206,7 +1207,6 @@ impl Hud {
.x_y(-39.0, -25.0)
.color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0)))
.position_ingame(pos + Vec3::new(0.0, 0.0, 1.5 * scale + 1.5))
.resolution(100.0)
.set(level_skull_id, ui_widgets);
}
}
@ -2036,12 +2036,9 @@ impl Hud {
self.ui.focus_widget(maybe_id);
}
let events = self.update_layout(client, global_state, debug_info, dt);
let (view_mat, _, _) = camera.compute_dependents(client);
let fov = camera.get_fov();
self.ui.maintain(
&mut global_state.window.renderer_mut(),
Some((view_mat, fov)),
);
let (v_mat, p_mat, _) = camera.compute_dependents(client);
self.ui
.maintain(&mut global_state.window.renderer_mut(), Some(p_mat * v_mat));
// Check if item images need to be reloaded
self.item_imgs.reload_if_changed(&mut self.ui);

View File

@ -14,7 +14,7 @@ pub use scale::{Scale, ScaleMode};
pub use widgets::{
image_frame::ImageFrame,
image_slider::ImageSlider,
ingame::{Ingame, IngameAnchor, Ingameable},
ingame::{Ingame, Ingameable},
radio_list::RadioList,
toggle_button::ToggleButton,
tooltip::{Tooltip, TooltipManager, Tooltipable},
@ -257,7 +257,7 @@ impl Ui {
self.ui.widget_input(id)
}
pub fn maintain(&mut self, renderer: &mut Renderer, cam_params: Option<(Mat4<f32>, f32)>) {
pub fn maintain(&mut self, renderer: &mut Renderer, view_projection_mat: Option<Mat4<f32>>) {
// Maintain tooltip manager
self.tooltip_manager
.maintain(self.ui.global_input(), self.scale.scale_factor_logical());
@ -304,13 +304,12 @@ impl Ui {
enum Placement {
Interface,
// Number of primitives left to render ingame and relative scaling/resolution
InWorld(usize, Option<(f64, f64)>),
// Number of primitives left to render ingame and visibility
InWorld(usize, bool),
};
let mut placement = Placement::Interface;
// TODO: maybe mutate an ingame scale factor instead of this, depends on if we want them to scale with other ui scaling or not
let mut p_scale_factor = self.scale.scale_factor_physical();
let p_scale_factor = self.scale.scale_factor_physical();
// Switches to the `Plain` state and completes the previous `Command` if not already in the
// `Plain` state.
@ -377,7 +376,6 @@ impl Ui {
// No primitives left to place in the world at the current position, go back to drawing the interface
Placement::InWorld(0, _) => {
placement = Placement::Interface;
p_scale_factor = self.scale.scale_factor_physical();
// Finish current state
self.draw_commands.push(match current_state {
State::Plain => DrawCommand::plain(start..mesh.vertices().len()),
@ -388,22 +386,23 @@ impl Ui {
self.draw_commands.push(DrawCommand::WorldPos(None));
}
// Primitives still left to draw ingame
Placement::InWorld(num_prims, res) => match kind {
Placement::InWorld(num_prims, visible) => match kind {
// Other types aren't drawn & shouldn't decrement the number of primitives left to draw ingame
PrimitiveKind::Other(_) => {}
// Decrement the number of primitives left
_ => placement = Placement::InWorld(num_prims - 1, res),
_ => {
placement = Placement::InWorld(num_prims - 1, visible);
// Behind the camera
if !visible {
continue;
}
}
},
Placement::Interface => {}
}
// Functions for converting for conrod scalar coords to GL vertex coords (-1.0 to 1.0).
let (ui_win_w, ui_win_h) = match placement {
Placement::InWorld(_, Some(res)) => res,
// Behind the camera or far away
Placement::InWorld(_, None) => continue,
Placement::Interface => (self.ui.win_w, self.ui.win_h),
};
let (ui_win_w, ui_win_h) = (self.ui.win_w, self.ui.win_h);
let vx = |x: f64| (x / ui_win_w * 2.0) as f32;
let vy = |y: f64| (y / ui_win_h * 2.0) as f32;
let gl_aabr = |rect: Rect| {
@ -615,7 +614,7 @@ impl Ui {
PrimitiveKind::Other(container) => {
if container.type_id == std::any::TypeId::of::<widgets::ingame::State>() {
// Calculate the scale factor to pixels at this 3d point using the camera.
if let Some((view_mat, fov)) = cam_params {
if let Some(view_projection_mat) = view_projection_mat {
// Retrieve world position
let parameters = container
.state_and_style::<widgets::ingame::State, widgets::ingame::Style>()
@ -623,16 +622,21 @@ impl Ui {
.state
.parameters;
let pos_in_view = view_mat * Vec4::from_point(parameters.pos);
let scale_factor = self.ui.win_w as f64
/ (-2.0
* pos_in_view.z as f64
* (0.5 * fov as f64).tan()
// TODO: make this have no effect for fixed scale
* parameters.res as f64);
// Don't process ingame elements behind the camera or very far away
placement = if scale_factor > 0.2 {
let pos_on_screen = (view_projection_mat
* Vec4::from_point(parameters.pos))
.homogenized();
let visible = if pos_on_screen.z > -1.0 && pos_on_screen.z < 1.0 {
let x = pos_on_screen.x;
let y = pos_on_screen.y;
let (w, h) = parameters.dims.into_tuple();
let (half_w, half_h) = (w / ui_win_w as f32, h / ui_win_h as f32);
(x - half_w < 1.0 && x + half_w > -1.0)
&& (y - half_h < 1.0 && y + half_h > -1.0)
} else {
false
};
// Don't process ingame elements outside the frustum
placement = if visible {
// Finish current state
self.draw_commands.push(match current_state {
State::Plain => {
@ -643,12 +647,9 @@ impl Ui {
}
});
start = mesh.vertices().len();
// Push new position command
let mut world_pos = Vec4::from_point(parameters.pos);
if parameters.fixed_scale {
world_pos.w = -1.0
};
// Push new position command
let world_pos = Vec4::from_point(parameters.pos);
if self.ingame_locals.len() > ingame_local_index {
renderer
.update_consts(
@ -664,28 +665,9 @@ impl Ui {
.push(DrawCommand::WorldPos(Some(ingame_local_index)));
ingame_local_index += 1;
p_scale_factor = if parameters.fixed_scale {
self.scale.scale_factor_physical()
} else {
((scale_factor * 10.0).log2().round().powi(2) / 10.0)
.min(1.6)
.max(0.2)
};
// Scale down ingame elements that are close to the camera
let res = if parameters.fixed_scale {
(self.ui.win_w, self.ui.win_h)
} else if scale_factor > 3.2 {
let res = parameters.res * scale_factor as f32 / 3.2;
(res as f64, res as f64)
} else {
let res = parameters.res;
(res as f64, res as f64)
};
Placement::InWorld(parameters.num, Some(res))
Placement::InWorld(parameters.num, true)
} else {
Placement::InWorld(parameters.num, None)
Placement::InWorld(parameters.num, false)
};
}
}

View File

@ -1,6 +1,4 @@
use conrod_core::{
builder_methods, position::Dimension, widget, Position, Ui, UiCell, Widget, WidgetCommon,
};
use conrod_core::{widget, Position, Sizeable, Ui, UiCell, Widget, WidgetCommon};
use vek::*;
#[derive(Clone, WidgetCommon)]
@ -8,7 +6,8 @@ pub struct Ingame<W> {
#[conrod(common_builder)]
common: widget::CommonBuilder,
widget: W,
parameters: IngameParameters,
prim_num: usize,
pos: Vec3<f32>,
}
pub trait Ingameable: Widget + Sized {
@ -47,19 +46,14 @@ where
}
}
// All ingame widgets are now fixed scale
#[derive(Copy, Clone, PartialEq)]
pub struct IngameParameters {
// Number of primitive widgets to position in the game at the specified position
// Note this could be more than the number of widgets in the widgets field since widgets can contain widgets
pub num: usize,
pub pos: Vec3<f32>,
// Number of pixels per 1 unit in world coordinates (ie a voxel)
// Used for widgets that are rasterized before being sent to the gpu (text & images)
// Potentially make this automatic based on distance to camera?
pub res: f32,
// Whether the widgets should be scaled based on distance to the camera or if they should be a
// fixed size (res is ignored in that case)
pub fixed_scale: bool,
pub dims: Vec2<f32>,
}
pub struct State {
@ -73,22 +67,11 @@ impl<W: Ingameable> Ingame<W> {
pub fn new(pos: Vec3<f32>, widget: W) -> Self {
Self {
common: widget::CommonBuilder::default(),
parameters: IngameParameters {
num: widget.prim_count(),
pos,
res: 1.0,
fixed_scale: false,
},
prim_num: widget.prim_count(),
pos,
widget,
}
}
pub fn fixed_scale(mut self) -> Self {
self.parameters.fixed_scale = true;
self
}
builder_methods! {
pub resolution { parameters.res = f32 }
}
}
impl<W: Ingameable> Widget for Ingame<W> {
@ -99,7 +82,11 @@ impl<W: Ingameable> Widget for Ingame<W> {
fn init_state(&self, mut id_gen: widget::id::Generator) -> Self::State {
State {
id: Some(id_gen.next()),
parameters: self.parameters,
parameters: IngameParameters {
num: self.prim_num,
pos: self.pos,
dims: Vec2::zero(),
},
}
}
@ -110,10 +97,19 @@ impl<W: Ingameable> Widget for Ingame<W> {
fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
let widget::UpdateArgs { state, ui, .. } = args;
let Ingame {
widget, parameters, ..
widget,
prim_num,
pos,
..
} = self;
// Update pos if it has changed
let parameters = IngameParameters {
num: prim_num,
pos,
dims: Vec2::<f64>::from(widget.get_wh(ui).unwrap_or([1.0, 1.0])).map(|e| e as f32),
};
// Update parameters if it has changed
if state.parameters != parameters {
state.update(|s| {
s.parameters = parameters;
@ -129,75 +125,4 @@ impl<W: Ingameable> Widget for Ingame<W> {
fn default_y_position(&self, _: &Ui) -> Position {
Position::Absolute(0.0)
}
fn default_x_dimension(&self, _: &Ui) -> Dimension {
Dimension::Absolute(1.0)
}
fn default_y_dimension(&self, _: &Ui) -> Dimension {
Dimension::Absolute(1.0)
}
}
// Use this if you have multiple widgets that you want to place at the same spot in-game
// but don't want to create a new custom widget to contain them both
// Note: widgets must be set immediately after settings this
// Note: remove this if it ends up unused
#[derive(Clone, WidgetCommon)]
pub struct IngameAnchor {
#[conrod(common_builder)]
common: widget::CommonBuilder,
parameters: IngameParameters,
}
impl IngameAnchor {
pub fn new(pos: Vec3<f32>) -> Self {
IngameAnchor {
common: widget::CommonBuilder::default(),
parameters: IngameParameters {
num: 0,
pos,
res: 1.0,
fixed_scale: false,
},
}
}
pub fn for_widget(mut self, widget: impl Ingameable) -> Self {
self.parameters.num += widget.prim_count();
self
}
pub fn for_widgets(mut self, widget: impl Ingameable, n: usize) -> Self {
self.parameters.num += n * widget.prim_count();
self
}
pub fn for_prims(mut self, num: usize) -> Self {
self.parameters.num += num;
self
}
}
impl Widget for IngameAnchor {
type State = State;
type Style = Style;
type Event = ();
fn init_state(&self, _: widget::id::Generator) -> Self::State {
State {
id: None,
parameters: self.parameters,
}
}
fn style(&self) -> Self::Style {
()
}
fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
let widget::UpdateArgs { id: _, state, .. } = args;
let IngameAnchor { parameters, .. } = self;
// Update pos if it has changed
if state.parameters != parameters {
state.update(|s| {
s.parameters = parameters;
});
}
}
}