1
use bevy::prelude::*;
2
3
use crate::{
4
Health, WINDOW_HEIGHT, WINDOW_WIDTH,
5
player::{Player, PlayerId},
6
};
7
8
const UI_PLAYER_HEALTH_HEIGHT: f32 = 800.;
9
const UI_PLAYER_HEALTH_WIDTH: f32 = 15.;
10
const UI_PLAYER_ANIMATE_IN_PPS: f32 = UI_PLAYER_HEALTH_HEIGHT / 0.8;
11
12
const UI_PLAYER0_HEALTH_COLOR: Color = Color::srgb(0., 0., 0.9);
13
const UI_PLAYER0_HEALTH_MISSING_COLOR: Color = Color::srgb(0., 0., 0.3);
14
const UI_PLAYER1_HEALTH_COLOR: Color = Color::srgb(0.9, 0., 0.);
15
const UI_PLAYER1_HEALTH_MISSING_COLOR: Color = Color::srgb(0.3, 0., 0.);
16
17
#[derive(Component)]
18
pub(crate) struct PlayerHealthUi(pub(crate) PlayerId);
19
20
#[derive(Component)]
21
pub(crate) struct PlayerHealthUiAnimateIn;
22
23
pub(crate) fn setup(mut commands: Commands) {
24
commands.spawn((
25
Node {
26
left: Val::Px(0.),
27
width: Val::Px(UI_PLAYER_HEALTH_WIDTH),
28
top: Val::Px(0.),
29
height: Val::Px(UI_PLAYER_HEALTH_HEIGHT),
30
..default()
31
},
32
BackgroundColor(UI_PLAYER0_HEALTH_MISSING_COLOR),
33
children![(
34
PlayerHealthUi(PlayerId(0)),
35
PlayerHealthUiAnimateIn,
36
Node {
37
width: Val::Px(UI_PLAYER_HEALTH_WIDTH),
38
height: Val::Px(0.),
39
..default()
40
},
41
BackgroundColor(UI_PLAYER0_HEALTH_COLOR)
42
)],
43
));
44
commands.spawn((
45
Node {
46
left: Val::Px(WINDOW_WIDTH - UI_PLAYER_HEALTH_WIDTH),
47
width: Val::Px(UI_PLAYER_HEALTH_WIDTH),
48
top: Val::Px(WINDOW_HEIGHT - UI_PLAYER_HEALTH_HEIGHT),
49
height: Val::Px(UI_PLAYER_HEALTH_HEIGHT),
50
..default()
51
},
52
BackgroundColor(UI_PLAYER1_HEALTH_MISSING_COLOR),
53
children![(
54
PlayerHealthUi(PlayerId(1)),
55
PlayerHealthUiAnimateIn,
56
Node {
57
position_type: PositionType::Absolute,
58
width: Val::Px(UI_PLAYER_HEALTH_WIDTH),
59
height: Val::Px(0.),
60
bottom: Val::Px(0.),
61
..default()
62
},
63
BackgroundColor(UI_PLAYER1_HEALTH_COLOR),
64
)],
65
));
66
}
67
68
pub(crate) fn update_player_health_ui(
69
mut commands: Commands,
70
mut player_health_ui_nodes: Query<(
71
Entity,
72
&ChildOf,
73
&mut Node,
74
&PlayerHealthUi,
75
Option<&PlayerHealthUiAnimateIn>,
76
)>,
77
players: Query<(&Player, &Health)>,
78
time: Res<Time>,
79
) {
80
let players = players.iter().collect::<Vec<_>>();
81
82
for (entity, parent, mut node, PlayerHealthUi(player_id), maybe_animate_in) in
83
&mut player_health_ui_nodes
84
{
85
let Some(health) = players
86
.iter()
87
.find(|(p, _h)| p.0 == *player_id)
88
.map(|(_p, h)| h)
89
else {
90
commands.entity(parent.0).despawn();
91
continue;
92
};
93
let target_height_px =
94
UI_PLAYER_HEALTH_HEIGHT * (health.current as f32) / (health.max as f32);
95
if maybe_animate_in.is_some() {
96
let Val::Px(current_height_px) = node.height else {
97
panic!("PlayerHealthUi unexpectedly had non-Px height");
98
};
99
let new_height_px = (current_height_px + UI_PLAYER_ANIMATE_IN_PPS * time.delta_secs())
100
.min(target_height_px);
101
node.height = Val::Px(new_height_px);
102
if new_height_px == target_height_px {
103
commands.entity(entity).remove::<PlayerHealthUiAnimateIn>();
104
}
105
} else {
106
node.height = Val::Px(target_height_px);
107
}
108
}
109
}
110