relaod mechanic

This commit is contained in:
2025-04-02 02:15:43 +08:00
parent 9aaa015bd6
commit 01f8128e5f
7 changed files with 82 additions and 16 deletions

View File

@@ -82,6 +82,7 @@ fn on_trigger_state(
player_rot: Query<&Transform, With<PlayerRig>>,
player_transform: Query<&Transform, With<Player>>,
mut active_heads: ResMut<ActiveHeads>,
time: Res<Time>,
) {
if matches!(trigger.event(), TriggerState::Active) {
let Some(state) = active_heads.current() else {
@@ -107,7 +108,7 @@ fn on_trigger_state(
pos: transform.translation,
};
active_heads.use_ammo();
active_heads.use_ammo(time.elapsed_secs());
match state.ability {
HeadAbility::Thrown => commands.trigger(TriggerThrow(trigger_state)),

View File

@@ -154,6 +154,7 @@ fn sync(
head: head.0,
health: hp.health(),
ammo: 1.,
reloading: None,
});
}
}

View File

@@ -262,6 +262,7 @@ fn swap_head_inputs(
backpack: Res<Backpack>,
mut commands: Commands,
mut state: ResMut<BackpackUiState>,
time: Res<Time>,
) {
if state.count == 0 {
return;
@@ -292,17 +293,17 @@ fn swap_head_inputs(
if changed {
commands.trigger(PlaySound::Selection);
sync(&backpack, &mut state);
sync(&backpack, &mut state, time.elapsed_secs());
}
}
fn sync_on_change(backpack: Res<Backpack>, mut state: ResMut<BackpackUiState>) {
if backpack.is_changed() {
sync(&backpack, &mut state);
fn sync_on_change(backpack: Res<Backpack>, mut state: ResMut<BackpackUiState>, time: Res<Time>) {
if backpack.is_changed() || backpack.reloading() {
sync(&backpack, &mut state, time.elapsed_secs());
}
}
fn sync(backpack: &Res<Backpack>, state: &mut ResMut<BackpackUiState>) {
fn sync(backpack: &Res<Backpack>, state: &mut ResMut<BackpackUiState>, time: f32) {
state.count = backpack.heads.len();
state.scroll = state.scroll.min(state.count.saturating_sub(HEAD_SLOTS));
@@ -316,7 +317,7 @@ fn sync(backpack: &Res<Backpack>, state: &mut ResMut<BackpackUiState>) {
for i in 0..HEAD_SLOTS {
if let Some(head) = backpack.heads.get(i + state.scroll) {
state.heads[i] = Some(UiHeadState::from(*head));
state.heads[i] = Some(UiHeadState::new(*head, time));
} else {
state.heads[i] = None;
}

View File

@@ -15,6 +15,18 @@ pub struct Backpack {
pub heads: Vec<HeadState>,
}
impl Backpack {
pub fn reloading(&self) -> bool {
for head in &self.heads {
if !head.has_ammo() {
return true;
}
}
false
}
}
#[derive(Event)]
pub struct BackbackSwapEvent(pub usize);

View File

@@ -6,6 +6,7 @@ pub struct UiHeadState {
pub head: usize,
pub health: f32,
pub ammo: f32,
pub reloading: Option<f32>,
}
impl UiHeadState {
@@ -16,14 +17,23 @@ impl UiHeadState {
pub fn ammo_used(&self) -> f32 {
1. - self.ammo
}
}
impl From<HeadState> for UiHeadState {
fn from(value: HeadState) -> Self {
pub fn reloading(&self) -> Option<f32> {
self.reloading
}
pub(crate) fn new(value: HeadState, time: f32) -> Self {
let reloading = if value.has_ammo() {
None
} else {
Some((time - value.last_use) / value.reload_duration)
};
Self {
head: value.head,
ammo: value.ammo as f32 / value.ammo_max as f32,
health: value.health as f32 / value.health_max as f32,
reloading,
}
}
}

View File

@@ -182,11 +182,18 @@ fn update_ammo(
if res.is_changed() {
for (mut gradient, HeadImage(head)) in gradients.iter_mut() {
if let Some(head) = res.heads[*head] {
let ammo_used = head.ammo_used();
let Gradient::Conic(gradient) = &mut gradient.0[0] else {
continue;
};
let angle = PI * 2. * ammo_used;
let progress = if let Some(reloading) = head.reloading() {
1. - reloading
} else {
head.ammo_used()
};
let angle = progress * PI * 2.;
gradient.stops[1].angle = Some(angle);
gradient.stops[2].angle = Some(angle);
}
@@ -203,11 +210,13 @@ fn update_health(res: Res<UiActiveHeads>, mut query: Query<(&mut Node, &HeadDama
}
}
fn sync(active: Res<ActiveHeads>, mut state: ResMut<UiActiveHeads>) {
if active.is_changed() {
fn sync(active: Res<ActiveHeads>, mut state: ResMut<UiActiveHeads>, time: Res<Time>) {
if active.is_changed() || active.reloading() {
state.current_slot = active.slot();
for i in 0..HEAD_SLOTS {
state.heads[i] = active.head(i).map(UiHeadState::from);
state.heads[i] = active
.head(i)
.map(|state| UiHeadState::new(state, time.elapsed_secs()));
}
}
}

View File

@@ -25,6 +25,8 @@ pub struct HeadState {
pub health_max: u32,
pub ammo: u32,
pub ammo_max: u32,
pub reload_duration: f32,
pub last_use: f32,
}
impl HeadState {
@@ -36,6 +38,8 @@ impl HeadState {
ammo,
ammo_max: ammo,
ability: HeadAbility::None,
reload_duration: 5.,
last_use: 0.,
}
}
@@ -61,12 +65,13 @@ impl ActiveHeads {
self.heads[self.current_slot]
}
pub fn use_ammo(&mut self) {
pub fn use_ammo(&mut self, time: f32) {
let Some(head) = &mut self.heads[self.current_slot] else {
error!("cannot use ammo of empty head");
return;
};
head.last_use = time;
head.ammo = head.ammo.saturating_sub(1);
}
@@ -77,6 +82,16 @@ impl ActiveHeads {
pub fn head(&self, slot: usize) -> Option<HeadState> {
self.heads[slot]
}
pub fn reloading(&self) -> bool {
for head in self.heads {
if head.map(|head| head.ammo == 0).unwrap_or(false) {
return true;
}
}
false
}
}
#[derive(Event, Reflect)]
@@ -103,6 +118,7 @@ pub fn plugin(app: &mut App) {
});
app.add_systems(OnEnter(GameState::Playing), setup);
app.add_systems(Update, reload.run_if(in_state(GameState::Playing)));
app.add_observer(on_select_active_head);
app.add_observer(on_swap_backpack);
@@ -117,6 +133,22 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.insert_resource(HeadsImages { heads });
}
fn reload(mut active: ResMut<ActiveHeads>, time: Res<Time>) {
if !active.reloading() {
return;
}
for head in active.heads.iter_mut() {
let Some(head) = head else {
continue;
};
if !head.has_ammo() && (head.last_use + head.reload_duration <= time.elapsed_secs()) {
head.ammo = head.ammo_max;
}
}
}
fn on_select_active_head(
trigger: Trigger<SelectActiveHead>,
mut commands: Commands,