diff --git a/CREDITS.md b/CREDITS.md index 8e5d609..85d05e9 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -18,6 +18,13 @@ NOTE: Only a handful of sprites were used from this. https://ansimuz.itch.io/explosion-animations-pack +#### Super Sonic Effect + +https://bdragon1727.itch.io/free-smoke-fx-pixel-2 + +NOTE: Only a handful of sprites were used from this +(and rotated to work easier with the game). + ### Audio #### Sounds diff --git a/assets/gfx/super_sonic.png b/assets/gfx/super_sonic.png new file mode 100644 index 0000000..b628842 Binary files /dev/null and b/assets/gfx/super_sonic.png differ diff --git a/assets/sounds/super_sonic.ogg b/assets/sounds/super_sonic.ogg new file mode 100644 index 0000000..f292c18 Binary files /dev/null and b/assets/sounds/super_sonic.ogg differ diff --git a/src/assets.rs b/src/assets.rs index d867e71..b20a4d2 100644 --- a/src/assets.rs +++ b/src/assets.rs @@ -28,6 +28,9 @@ pub struct GameAssets { #[asset(texture_atlas(tile_size_x = 32.0, tile_size_y = 32.0, columns = 8, rows = 1))] #[asset(path = "gfx/explosion.png")] pub explosion: Handle, + #[asset(texture_atlas(tile_size_x = 64.0, tile_size_y = 64.0, columns = 9, rows = 1))] + #[asset(path = "gfx/super_sonic.png")] + pub super_sonic: Handle, // MAP #[asset(path = "map/background.png")] @@ -75,7 +78,9 @@ pub struct GameAssets { #[asset(path = "sounds/dodge.ogg")] pub dodge_sound: Handle, #[asset(path = "sounds/dodge_refresh.ogg")] - pub dodge_refresh: Handle, + pub dodge_refresh_sound: Handle, + #[asset(path = "sounds/super_sonic.ogg")] + pub super_sonic_sound: Handle, #[asset(path = "sounds/round-start-sound.ogg")] pub round_start_sound: Handle, diff --git a/src/player/dodge.rs b/src/player/dodge.rs index 7fee434..ffb7663 100644 --- a/src/player/dodge.rs +++ b/src/player/dodge.rs @@ -108,7 +108,7 @@ pub fn tick_dodge_timers( commands .spawn(RollbackSound { - clip: assets.dodge_refresh.clone(), + clip: assets.dodge_refresh_sound.clone(), start_frame: frame.0 as usize, sub_key: player.handle, volume: 0.35, diff --git a/src/player/effect/mod.rs b/src/player/effect/mod.rs index 0d9b62b..4b37fdc 100644 --- a/src/player/effect/mod.rs +++ b/src/player/effect/mod.rs @@ -1,7 +1,9 @@ -mod bullet; pub mod damage; pub mod trail; +mod bullet; +mod super_sonic; + use bevy::prelude::*; use bevy_ggrs::GgrsSchedule; @@ -25,6 +27,16 @@ impl Plugin for EffectPlugin { .chain() .run_if(in_state(GameState::InRollbackGame)), ) + .add_systems( + Update, + ( + super_sonic::spawn_super_sonic_effects, + super_sonic::animate_super_sonic_effects, + super_sonic::despawn_super_sonic_effects, + ) + .chain() + .run_if(in_state(GameState::InRollbackGame)), + ) .add_systems( GgrsSchedule, ( diff --git a/src/player/effect/super_sonic.rs b/src/player/effect/super_sonic.rs new file mode 100644 index 0000000..86c9ae3 --- /dev/null +++ b/src/player/effect/super_sonic.rs @@ -0,0 +1,89 @@ +use std::time::Duration; + +use bevy::core::FrameCount; +use bevy::prelude::*; +use bevy_ggrs::AddRollbackCommandExtension; + +use crate::audio::RollbackSound; +use crate::misc::utils::quat_from_vec3; +use crate::network::ggrs_config::GGRS_FPS; +use crate::player::movement::ReachedMaxSpeed; +use crate::GameAssets; + +#[derive(Component)] +pub struct SuperSonicAnimationTimer { + pub timer: Timer, + disabled: bool, +} + +impl Default for SuperSonicAnimationTimer { + fn default() -> Self { + Self { + timer: Timer::from_seconds(0.075, TimerMode::Repeating), + disabled: false, + } + } +} + +pub fn spawn_super_sonic_effects( + mut commands: Commands, + assets: Res, + frame: Res, + mut ev_reached_max_speed: EventReader, +) { + for ev in &mut ev_reached_max_speed { + let transform = Transform::from_translation(ev.position) + .with_rotation(quat_from_vec3(ev.direction)) + .with_scale(Vec3::splat(1.5)); + let entity = commands + .spawn(( + SuperSonicAnimationTimer::default(), + SpriteSheetBundle { + transform, + texture_atlas: assets.super_sonic.clone(), + ..default() + }, + )) + .id(); + commands + .spawn(RollbackSound { + clip: assets.super_sonic_sound.clone(), + start_frame: frame.0 as usize, + sub_key: entity.index() as usize, + ..default() + }) + .add_rollback(); + } +} + +pub fn animate_super_sonic_effects( + mut query: Query<(&mut SuperSonicAnimationTimer, &mut TextureAtlasSprite)>, +) { + for (mut timer, mut sprite) in &mut query { + if timer.disabled { + continue; + } + + timer + .timer + .tick(Duration::from_secs_f32(1.0 / GGRS_FPS as f32)); + if timer.timer.just_finished() { + if sprite.index == 8 { + timer.disabled = true; + } else { + sprite.index += 1; + } + } + } +} + +pub fn despawn_super_sonic_effects( + mut commands: Commands, + query: Query<(Entity, &SuperSonicAnimationTimer)>, +) { + for (entity, timer) in &query { + if timer.disabled { + commands.entity(entity).despawn_recursive(); + } + } +} diff --git a/src/player/mod.rs b/src/player/mod.rs index 9635644..edc3ae7 100644 --- a/src/player/mod.rs +++ b/src/player/mod.rs @@ -140,6 +140,7 @@ impl Plugin for PlayerPlugin { ), ) .add_event::() + .add_event::() .init_resource::() .add_plugins((shooting::ShootingPlugin, effect::EffectPlugin)) .add_systems( diff --git a/src/player/movement.rs b/src/player/movement.rs index fbf341d..38e31ff 100644 --- a/src/player/movement.rs +++ b/src/player/movement.rs @@ -6,6 +6,12 @@ use crate::input; use crate::network::GgrsConfig; use crate::player::{Player, DELTA_SPEED, DELTA_STEERING, MIN_SPEED}; +#[derive(Event)] +pub struct ReachedMaxSpeed { + pub position: Vec3, + pub direction: Vec3, +} + pub fn steer_players( inputs: Res>, mut players: Query<(&mut Transform, &Player, &mut DebugTransform)>, @@ -28,8 +34,12 @@ pub fn steer_players( } } -pub fn accelerate_players(inputs: Res>, mut players: Query<&mut Player>) { - for mut player in &mut players { +pub fn accelerate_players( + inputs: Res>, + mut players: Query<(&Transform, &mut Player)>, + mut ev_reached_max_speed: EventWriter, +) { + for (transform, mut player) in &mut players { let (input, _) = inputs[player.handle]; let accelerate_direction = input::accelerate_direction(input); @@ -44,10 +54,20 @@ pub fn accelerate_players(inputs: Res>, mut players: Qu -DELTA_SPEED }; + let sub_sonic = player.current_speed != player.stats.max_speed; + player.current_speed += acceleration; player.current_speed = player .current_speed .clamp(MIN_SPEED, player.stats.max_speed); + + // We just reached max speed this frame, send event + if sub_sonic && player.current_speed == player.stats.max_speed { + ev_reached_max_speed.send(ReachedMaxSpeed { + position: transform.translation, + direction: transform.rotation.mul_vec3(Vec3::X), + }); + } } }