simple hitpoint mechanic
This commit is contained in:
BIN
assets/sfx/abilities/gun.ogg
Normal file
BIN
assets/sfx/abilities/gun.ogg
Normal file
Binary file not shown.
13
src/aim.rs
13
src/aim.rs
@@ -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.);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
44
src/gates.rs
44
src/gates.rs
@@ -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));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
41
src/npc.rs
41
src/npc.rs
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
31
src/shooting.rs
Normal 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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.),
|
||||||
|
|||||||
Reference in New Issue
Block a user