simple hitpoint mechanic

This commit is contained in:
2025-03-12 23:25:13 +01:00
parent 709c1762bd
commit 8946289ac2
8 changed files with 140 additions and 29 deletions

Binary file not shown.

View File

@@ -5,11 +5,12 @@ use crate::{
}; };
use bevy::prelude::*; use bevy::prelude::*;
use bevy_sprite3d::{Sprite3dBuilder, Sprite3dParams}; use bevy_sprite3d::{Sprite3dBuilder, Sprite3dParams};
use ops::sin;
use std::f32::consts::PI; use std::f32::consts::PI;
#[derive(Resource, Reflect)] #[derive(Resource, Reflect)]
#[reflect(Resource)] #[reflect(Resource)]
struct AimState { pub struct AimState {
pub target: Option<Entity>, pub target: Option<Entity>,
pub range: f32, pub range: f32,
pub angle: f32, pub angle: f32,
@@ -37,7 +38,7 @@ enum MarkerEvent {
pub fn plugin(app: &mut App) { pub fn plugin(app: &mut App) {
app.init_resource::<AimState>(); app.init_resource::<AimState>();
app.add_systems(Update, update); app.add_systems(Update, (update, move_marker));
app.add_observer(marker_event); app.add_observer(marker_event);
} }
@@ -63,7 +64,7 @@ fn marker_event(
Name::new("aim-marker"), Name::new("aim-marker"),
Billboard, Billboard,
Marker, Marker,
Transform::from_translation(Vec3::new(0., 3., 0.)), Transform::default(),
Sprite3dBuilder { Sprite3dBuilder {
image: selector, image: selector,
pixels_per_metre: 30., pixels_per_metre: 30.,
@@ -129,3 +130,9 @@ fn update(
state.target = new_target; state.target = new_target;
} }
} }
fn move_marker(mut query: Query<&mut Transform, With<Marker>>, time: Res<Time>) {
for mut transform in query.iter_mut() {
transform.translation = Vec3::new(0., 3. + (sin(time.elapsed_secs() * 6.) * 0.2), 0.);
}
}

View File

@@ -1,4 +1,4 @@
use crate::movables::TriggerMovableEvent; use crate::{movables::TriggerMovableEvent, npc::KeyCollected};
use bevy::{prelude::*, utils::hashbrown::HashSet}; use bevy::{prelude::*, utils::hashbrown::HashSet};
#[derive(Resource)] #[derive(Resource)]
@@ -9,36 +9,34 @@ enum GatesState {
pub fn plugin(app: &mut App) { pub fn plugin(app: &mut App) {
app.insert_resource(GatesState::Init); app.insert_resource(GatesState::Init);
app.add_systems(Update, update); app.add_observer(on_key);
} }
fn update( fn on_key(
_trigger: Trigger<KeyCollected>,
mut commands: Commands, mut commands: Commands,
keyboard: Res<ButtonInput<KeyCode>>,
mut state: ResMut<GatesState>, mut state: ResMut<GatesState>,
asset_server: Res<AssetServer>, asset_server: Res<AssetServer>,
) { ) {
if keyboard.just_pressed(KeyCode::Digit1) { if matches!(*state, GatesState::Init) {
if matches!(*state, GatesState::Init) { *state = GatesState::GateOpen1;
*state = GatesState::GateOpen1;
//TODO: put into a sound effects system //TODO: put into a sound effects system
commands.spawn(( commands.spawn((
AudioPlayer::new(asset_server.load("sfx/effects/gate.ogg")), AudioPlayer::new(asset_server.load("sfx/effects/gate.ogg")),
PlaybackSettings::DESPAWN, PlaybackSettings::DESPAWN,
)); ));
//TODO: put into actual key collect once we have that //TODO: put into actual key collect once we have that
commands.spawn(( commands.spawn((
AudioPlayer::new(asset_server.load("sfx/effects/key_collect.ogg")), AudioPlayer::new(asset_server.load("sfx/effects/key_collect.ogg")),
PlaybackSettings::DESPAWN, PlaybackSettings::DESPAWN,
)); ));
let entities: HashSet<_> = vec!["fence_01", "fence_02"] let entities: HashSet<_> = vec!["fence_01", "fence_02"]
.into_iter() .into_iter()
.map(|s| String::from(s)) .map(|s| String::from(s))
.collect(); .collect();
commands.trigger(TriggerMovableEvent(entities)); commands.trigger(TriggerMovableEvent(entities));
}
} }
} }

View File

@@ -6,8 +6,10 @@ mod cash;
mod gates; mod gates;
mod heads_ui; mod heads_ui;
mod movables; mod movables;
mod npc;
mod platforms; mod platforms;
mod player; mod player;
mod shooting;
mod tb_entities; mod tb_entities;
use avian3d::PhysicsPlugins; use avian3d::PhysicsPlugins;
@@ -76,6 +78,8 @@ fn main() {
app.add_plugins(movables::plugin); app.add_plugins(movables::plugin);
app.add_plugins(billboards::plugin); app.add_plugins(billboards::plugin);
app.add_plugins(aim::plugin); app.add_plugins(aim::plugin);
app.add_plugins(shooting::plugin);
app.add_plugins(npc::plugin);
app.insert_resource(AmbientLight { app.insert_resource(AmbientLight {
color: Color::WHITE, color: Color::WHITE,

View File

@@ -0,0 +1,41 @@
use crate::tb_entities::EnemySpawn;
use bevy::prelude::*;
#[derive(Event, Reflect)]
pub struct Hit {
pub damage: u32,
}
// TODO: add actual keys
#[derive(Event, Reflect)]
pub struct KeyCollected;
#[derive(Component, Reflect)]
struct Hp(i32);
pub fn plugin(app: &mut App) {
app.add_systems(Update, init);
}
fn init(mut commands: Commands, query: Query<Entity, (With<EnemySpawn>, Without<Hp>)>) {
for e in query.iter() {
commands.entity(e).insert(Hp(100)).observe(on_hit);
}
}
fn on_hit(trigger: Trigger<Hit>, mut commands: Commands, mut query: Query<&mut Hp>) {
let Hit { damage } = trigger.event();
let Ok(mut hp) = query.get_mut(trigger.entity()) else {
return;
};
hp.0 = hp.0.saturating_sub(*damage as i32);
info!("npc hp changed: {} [{}]", hp.0, trigger.entity());
if hp.0 <= 0 {
commands.entity(trigger.entity()).despawn_recursive();
commands.trigger(KeyCollected);
}
}

View File

@@ -6,11 +6,15 @@ use crate::{
camera::GameCameraRig, camera::GameCameraRig,
cash::{Cash, CashCollectEvent}, cash::{Cash, CashCollectEvent},
heads_ui::HeadChanged, heads_ui::HeadChanged,
shooting::TriggerState,
tb_entities::SpawnPoint, tb_entities::SpawnPoint,
}; };
use avian3d::prelude::*; use avian3d::prelude::*;
use bevy::{ use bevy::{
input::mouse::MouseMotion, input::{
ButtonState,
mouse::{MouseButtonInput, MouseMotion},
},
prelude::*, prelude::*,
window::{CursorGrabMode, PrimaryWindow}, window::{CursorGrabMode, PrimaryWindow},
}; };
@@ -49,7 +53,6 @@ pub fn plugin(app: &mut App) {
( (
spawn, spawn,
update_camera, update_camera,
mouse_rotate,
collect_cash, collect_cash,
toggle_animation, toggle_animation,
setup_animations_marker_for_player, setup_animations_marker_for_player,
@@ -60,6 +63,9 @@ pub fn plugin(app: &mut App) {
apply_controls.in_set(TnuaUserControlsSystemSet), apply_controls.in_set(TnuaUserControlsSystemSet),
); );
app.add_systems(Update, mouse_rotate.run_if(on_event::<MouseMotion>));
app.add_systems(Update, mouse_click.run_if(on_event::<MouseButtonInput>));
app.add_observer(updaate_head); app.add_observer(updaate_head);
} }
@@ -131,6 +137,28 @@ fn mouse_rotate(
} }
} }
fn mouse_click(mut events: EventReader<MouseButtonInput>, mut commands: Commands) {
for ev in events.read() {
match ev {
MouseButtonInput {
button: MouseButton::Left,
state: ButtonState::Pressed,
..
} => {
commands.trigger(TriggerState::Active);
}
MouseButtonInput {
button: MouseButton::Left,
state: ButtonState::Released,
..
} => {
commands.trigger(TriggerState::Inactive);
}
_ => {}
}
}
}
fn toggle_grab_cursor(window: &mut Window) { fn toggle_grab_cursor(window: &mut Window) {
match window.cursor_options.grab_mode { match window.cursor_options.grab_mode {
CursorGrabMode::None => { CursorGrabMode::None => {

31
src/shooting.rs Normal file
View File

@@ -0,0 +1,31 @@
use bevy::prelude::*;
use crate::{aim::AimState, npc::Hit};
#[derive(Event, Reflect)]
pub enum TriggerState {
Active,
Inactive,
}
pub fn plugin(app: &mut App) {
app.add_observer(on_trigger_state);
}
fn on_trigger_state(
trigger: Trigger<TriggerState>,
mut commands: Commands,
asset_server: Res<AssetServer>,
aim: Res<AimState>,
) {
if matches!(trigger.event(), TriggerState::Active) {
commands.spawn((
AudioPlayer::new(asset_server.load("sfx/abilities/gun.ogg")),
PlaybackSettings::DESPAWN,
));
if let Some(target) = aim.target {
commands.entity(target).trigger(Hit { damage: 20 });
}
}
}

View File

@@ -108,12 +108,14 @@ impl EnemySpawn {
let head_mesh = asset_server let head_mesh = asset_server
.load(GltfAssetLabel::Scene(0).from_asset(format!("models/heads/{}.glb", this.head))); .load(GltfAssetLabel::Scene(0).from_asset(format!("models/heads/{}.glb", this.head)));
let head = this.head.clone();
world world
.commands() .commands()
.entity(entity) .entity(entity)
.insert(( .insert((
this_transform, this_transform,
Name::from("Enemy"), Name::from(format!("enemy [{}]", head)),
Visibility::default(), Visibility::default(),
RigidBody::Dynamic, RigidBody::Dynamic,
Collider::capsule(0.4, 2.), Collider::capsule(0.4, 2.),