player can take damage

This commit is contained in:
2025-04-02 11:33:01 +08:00
parent f967b1b0da
commit 4652bc4563
8 changed files with 120 additions and 50 deletions

View File

@@ -4,7 +4,7 @@ mod thrown;
use crate::{
GameState,
heads::ActiveHeads,
npc::Hit,
hitpoints::Hit,
player::{Player, PlayerRig},
sounds::PlaySound,
tb_entities::EnemySpawn,

View File

@@ -3,8 +3,8 @@ use crate::{
GameState,
aim::AimState,
billboards::Billboard,
hitpoints::Hit,
loading_assets::GameAssets,
npc::Hit,
physics_layers::GameLayer,
player::{Player, PlayerRig},
sounds::PlaySound,

View File

@@ -1,10 +1,7 @@
use super::AimState;
use crate::{
GameState,
backpack::UiHeadState,
heads::HeadsImages,
loading_assets::UIAssets,
npc::{Hitpoints, NpcHead},
GameState, backpack::UiHeadState, heads::HeadsImages, hitpoints::Hitpoints,
loading_assets::UIAssets, npc::NpcHead,
};
use bevy::prelude::*;

View File

@@ -4,7 +4,8 @@ use crate::{
GameState,
abilities::HeadAbility,
backpack::{BackbackSwapEvent, Backpack},
player::head_id_to_str,
hitpoints::Hitpoints,
player::{Player, head_id_to_str},
sounds::PlaySound,
};
use bevy::prelude::*;
@@ -92,6 +93,15 @@ impl ActiveHeads {
false
}
pub fn set_hitpoint(&mut self, hp: &Hitpoints) {
let Some(head) = &mut self.heads[self.current_slot] else {
error!("cannot use ammo of empty head");
return;
};
(head.health, head.health_max) = hp.get()
}
}
#[derive(Event, Reflect)]
@@ -118,7 +128,10 @@ 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_systems(
Update,
(reload, sync_hp).run_if(in_state(GameState::Playing)),
);
app.add_observer(on_select_active_head);
app.add_observer(on_swap_backpack);
@@ -133,6 +146,14 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.insert_resource(HeadsImages { heads });
}
fn sync_hp(mut active: ResMut<ActiveHeads>, query: Query<&Hitpoints, With<Player>>) {
let Ok(hp) = query.get_single() else {
return;
};
active.set_hitpoint(hp);
}
fn reload(mut commands: Commands, mut active: ResMut<ActiveHeads>, time: Res<Time>) {
if !active.reloading() {
return;
@@ -154,6 +175,7 @@ fn on_select_active_head(
trigger: Trigger<SelectActiveHead>,
mut commands: Commands,
mut res: ResMut<ActiveHeads>,
mut query: Query<&mut Hitpoints, With<Player>>,
) {
match trigger.event() {
SelectActiveHead::Right => {
@@ -164,6 +186,12 @@ fn on_select_active_head(
}
}
// sync health back into Hitpoints
let Ok(mut hp) = query.get_single_mut() else {
return;
};
hp.set_health(res.current().unwrap().health);
commands.trigger(PlaySound::Selection);
commands.trigger(HeadChanged(res.heads[res.current_slot].unwrap().head));
}
@@ -173,6 +201,7 @@ fn on_swap_backpack(
mut commands: Commands,
mut res: ResMut<ActiveHeads>,
mut backpack: ResMut<Backpack>,
mut query: Query<&mut Hitpoints, With<Player>>,
) {
let backpack_slot = trigger.event().0;
@@ -189,5 +218,11 @@ fn on_swap_backpack(
backpack.heads.remove(backpack_slot);
}
// sync health back into Hitpoints
let Ok(mut hp) = query.get_single_mut() else {
return;
};
hp.set_health(res.current().unwrap().health);
commands.trigger(HeadChanged(res.heads[res.current_slot].unwrap().head));
}

61
src/hitpoints.rs Normal file
View File

@@ -0,0 +1,61 @@
use bevy::prelude::*;
use crate::sounds::PlaySound;
#[derive(Event, Reflect)]
pub struct Kill;
#[derive(Event, Reflect)]
pub struct Hit {
pub damage: u32,
}
#[derive(Component, Reflect, Debug)]
pub struct Hitpoints {
max: u32,
current: u32,
}
impl Hitpoints {
pub fn new(v: u32) -> Self {
Self { max: v, current: v }
}
pub fn health(&self) -> f32 {
self.current as f32 / self.max as f32
}
pub fn set_health(&mut self, v: u32) {
self.current = v;
}
pub fn get(&self) -> (u32, u32) {
(self.current, self.max)
}
}
pub fn plugin(app: &mut App) {
app.add_systems(Update, on_hp_added);
}
fn on_hp_added(mut commands: Commands, query: Query<Entity, Added<Hitpoints>>) {
for e in query.iter() {
commands.entity(e).observe(on_hit);
}
}
fn on_hit(trigger: Trigger<Hit>, mut commands: Commands, mut query: Query<&mut Hitpoints>) {
let Hit { damage } = trigger.event();
let Ok(mut hp) = query.get_mut(trigger.entity()) else {
return;
};
commands.trigger(PlaySound::Hit);
hp.current = hp.current.saturating_sub(*damage);
if hp.current == 0 {
commands.trigger_targets(Kill, trigger.entity());
}
}

View File

@@ -8,6 +8,7 @@ mod control;
mod cutscene;
mod gates;
mod heads;
mod hitpoints;
mod keys;
mod loading_assets;
mod loading_map;
@@ -116,6 +117,7 @@ fn main() {
app.add_plugins(sprite_3d_animation::plugin);
app.add_plugins(abilities::plugin);
app.add_plugins(heads::plugin);
app.add_plugins(hitpoints::plugin);
app.init_state::<GameState>();

View File

@@ -1,31 +1,14 @@
use crate::{
GameState, heads::HEAD_COUNT, keys::KeySpawn, player::head_id_to_str, sounds::PlaySound,
GameState,
heads::HEAD_COUNT,
hitpoints::{Hitpoints, Kill},
keys::KeySpawn,
player::head_id_to_str,
tb_entities::EnemySpawn,
};
use bevy::prelude::*;
use std::collections::HashMap;
#[derive(Event, Reflect)]
pub struct Hit {
pub damage: u32,
}
#[derive(Component, Reflect)]
pub struct Hitpoints {
max: i32,
current: i32,
}
impl Hitpoints {
fn new(v: i32) -> Self {
Self { max: v, current: v }
}
pub fn health(&self) -> f32 {
self.current as f32 / self.max as f32
}
}
#[derive(Component)]
pub struct NpcHead(pub usize);
@@ -33,7 +16,7 @@ pub fn plugin(app: &mut App) {
app.add_systems(OnEnter(GameState::Playing), init);
}
fn init(mut commands: Commands, query: Query<(Entity, &EnemySpawn), Without<Hitpoints>>) {
fn init(mut commands: Commands, query: Query<(Entity, &EnemySpawn)>) {
let mut names: HashMap<String, usize> = HashMap::default();
for i in 0..HEAD_COUNT {
names.insert(head_id_to_str(i).to_string(), i);
@@ -43,32 +26,22 @@ fn init(mut commands: Commands, query: Query<(Entity, &EnemySpawn), Without<Hitp
commands
.entity(e)
.insert((Hitpoints::new(100), NpcHead(id)))
.observe(on_hit);
.observe(on_kill);
}
}
fn on_hit(
trigger: Trigger<Hit>,
fn on_kill(
trigger: Trigger<Kill>,
mut commands: Commands,
mut query: Query<(&mut Hitpoints, &Transform, &EnemySpawn)>,
query: Query<(&Transform, &EnemySpawn)>,
) {
let Hit { damage } = trigger.event();
let Ok((mut hp, transform, enemy)) = query.get_mut(trigger.entity()) else {
let Ok((transform, enemy)) = query.get(trigger.entity()) else {
return;
};
commands.trigger(PlaySound::Hit);
hp.current = hp.current.saturating_sub(*damage as i32);
info!("npc hp changed: {} [{}]", hp.current, trigger.entity());
if hp.current <= 0 {
commands.entity(trigger.entity()).despawn_recursive();
if !enemy.key.is_empty() {
commands.trigger(KeySpawn(transform.translation, enemy.key.clone()));
}
}
}

View File

@@ -8,6 +8,7 @@ use crate::{
controller::{CharacterControllerBundle, MovementBundle, PlayerMovement},
},
heads::HeadChanged,
hitpoints::Hitpoints,
loading_assets::GameAssets,
physics_layers::GameLayer,
sounds::PlaySound,
@@ -95,6 +96,7 @@ fn spawn(
.spawn((
Name::from("player"),
Player(0),
Hitpoints::new(100),
CameraTarget,
transform,
Visibility::default(),