From 68ea17a93a72ef71d7ee190614ea6b676cd19f1b Mon Sep 17 00:00:00 2001 From: extrawurst <776816+extrawurst@users.noreply.github.com> Date: Fri, 18 Apr 2025 18:04:53 +0200 Subject: [PATCH] arrow ability (#29) --- assets/sfx/abilities/crossbow.ogg | Bin 0 -> 8059 bytes src/abilities/arrow.rs | 111 ++++++++++++++++++++++++++++++ src/abilities/mod.rs | 7 +- src/loading_assets.rs | 2 + src/sounds.rs | 2 + 5 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 assets/sfx/abilities/crossbow.ogg create mode 100644 src/abilities/arrow.rs diff --git a/assets/sfx/abilities/crossbow.ogg b/assets/sfx/abilities/crossbow.ogg new file mode 100644 index 0000000000000000000000000000000000000000..f9e9fb59df9b5006b0390a814cc73883b5f3ab9e GIT binary patch literal 8059 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 literal 0 HcmV?d00001 diff --git a/src/abilities/arrow.rs b/src/abilities/arrow.rs new file mode 100644 index 0000000..d8e48c0 --- /dev/null +++ b/src/abilities/arrow.rs @@ -0,0 +1,111 @@ +use super::TriggerArrow; +use crate::{ + GameState, billboards::Billboard, global_observer, hitpoints::Hit, loading_assets::GameAssets, + physics_layers::GameLayer, sounds::PlaySound, utils::sprite_3d_animation::AnimationTimer, +}; +use avian3d::prelude::*; +use bevy::{pbr::NotShadowCaster, prelude::*}; +use bevy_sprite3d::{Sprite3dBuilder, Sprite3dParams}; +use std::f32::consts::PI; + +#[derive(Component)] +struct ArrowProjectile; + +#[derive(Resource)] +struct ShotAssets { + image: Handle, + layout: Handle, +} + +pub fn plugin(app: &mut App) { + app.add_systems(OnEnter(GameState::Playing), setup); + app.add_systems(Update, update.run_if(in_state(GameState::Playing))); + + global_observer!(app, on_trigger_arrow); +} + +fn setup(mut commands: Commands, assets: Res, mut sprite_params: Sprite3dParams) { + let layout = TextureAtlasLayout::from_grid(UVec2::splat(256), 7, 6, None, None); + let texture_atlas_layout = sprite_params.atlas_layouts.add(layout); + + commands.insert_resource(ShotAssets { + image: assets.impact_atlas.clone(), + layout: texture_atlas_layout, + }); +} + +fn on_trigger_arrow( + trigger: Trigger, + mut commands: Commands, + query_transform: Query<&Transform>, +) { + let state = trigger.0; + + commands.trigger(PlaySound::Crossbow); + + let rotation = if let Some(target) = state.target { + let t = query_transform + .get(target) + .expect("target must have transform"); + Transform::from_translation(state.pos) + .looking_at(t.translation, Vec3::Y) + .rotation + } else { + state.rot.mul_quat(Quat::from_rotation_y(PI)) + }; + + let mut t = Transform::from_translation(state.pos).with_rotation(rotation); + t.translation += (t.forward().as_vec3() * 2.) + (Vec3::Y * 0.6); + + commands.spawn((Name::new("projectile-arrow"), ArrowProjectile, t)); +} + +fn update( + mut cmds: Commands, + query: Query<(Entity, &Transform), With>, + spatial_query: SpatialQuery, + assets: Res, + mut sprite_params: Sprite3dParams, +) { + for (e, t) in query.iter() { + let filter = SpatialQueryFilter::from_mask(LayerMask( + GameLayer::Level.to_bits() | GameLayer::Npc.to_bits(), + )); + + if let Some(first_hit) = spatial_query.cast_shape( + &Collider::sphere(0.5), + t.translation, + t.rotation, + t.forward(), + &ShapeCastConfig::from_max_distance(80.), + &filter, + ) { + cmds.entity(first_hit.entity).trigger(Hit { damage: 50 }); + + cmds.spawn( + Sprite3dBuilder { + image: assets.image.clone(), + pixels_per_metre: 128., + alpha_mode: AlphaMode::Blend, + unlit: true, + ..default() + } + .bundle_with_atlas( + &mut sprite_params, + TextureAtlas { + layout: assets.layout.clone(), + index: 0, + }, + ), + ) + .insert(( + Billboard, + Transform::from_translation(first_hit.point1), + NotShadowCaster, + AnimationTimer::new(Timer::from_seconds(0.005, TimerMode::Repeating)), + )); + } + + cmds.entity(e).despawn(); + } +} diff --git a/src/abilities/mod.rs b/src/abilities/mod.rs index 6a79a3d..44107dc 100644 --- a/src/abilities/mod.rs +++ b/src/abilities/mod.rs @@ -1,3 +1,4 @@ +mod arrow; mod gun; mod thrown; @@ -73,11 +74,14 @@ impl TriggerData { #[derive(Event, Reflect)] pub struct TriggerGun(pub TriggerData); #[derive(Event, Reflect)] +pub struct TriggerArrow(pub TriggerData); +#[derive(Event, Reflect)] pub struct TriggerThrow(pub TriggerData); pub fn plugin(app: &mut App) { app.add_plugins(gun::plugin); app.add_plugins(thrown::plugin); + app.add_plugins(arrow::plugin); app.add_systems(Update, enemy_hit.run_if(in_state(GameState::Playing))); @@ -155,7 +159,8 @@ fn on_trigger_state( match ability { HeadAbility::Thrown => commands.trigger(TriggerThrow(trigger_state)), HeadAbility::Gun => commands.trigger(TriggerGun(trigger_state)), - _ => (), + HeadAbility::Arrow => commands.trigger(TriggerArrow(trigger_state)), + HeadAbility::None => (), }; } } diff --git a/src/loading_assets.rs b/src/loading_assets.rs index e312741..abd7043 100644 --- a/src/loading_assets.rs +++ b/src/loading_assets.rs @@ -15,6 +15,8 @@ pub struct AudioAssets { pub key_collect: Handle, #[asset(path = "sfx/abilities/gun.ogg")] pub gun: Handle, + #[asset(path = "sfx/abilities/crossbow.ogg")] + pub crossbow: Handle, #[asset(path = "sfx/effects/gate.ogg")] pub gate: Handle, #[asset(path = "sfx/effects/cash.ogg")] diff --git a/src/sounds.rs b/src/sounds.rs index 9db5c71..31e85b8 100644 --- a/src/sounds.rs +++ b/src/sounds.rs @@ -14,6 +14,7 @@ pub enum PlaySound { Invalid, Reloaded, CashHeal, + Crossbow, Backpack { open: bool }, Head(String), } @@ -42,6 +43,7 @@ fn on_spawn_sounds( } PlaySound::KeyCollect => assets.key_collect.clone(), PlaySound::Gun => assets.gun.clone(), + PlaySound::Crossbow => assets.crossbow.clone(), PlaySound::Gate => assets.gate.clone(), PlaySound::CashCollect => assets.cash.clone(), PlaySound::Selection => assets.selection.clone(),