From c585045fa5a7e0bc036f739774761b17fcd0a3dc Mon Sep 17 00:00:00 2001 From: eri Date: Fri, 19 Jul 2024 22:34:07 +0200 Subject: [PATCH] feat: enemies --- Cargo.toml | 1 - src/camera.rs | 31 ++++++++++------ src/enemy.rs | 95 +++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 6 ++++ src/misc.rs | 89 ++++++++++++++++++++++++++++++++++++++++++++++ src/player.rs | 98 +++++++++++++++------------------------------------ 6 files changed, 239 insertions(+), 81 deletions(-) create mode 100644 src/enemy.rs create mode 100644 src/misc.rs diff --git a/Cargo.toml b/Cargo.toml index f434767..5343383 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,6 @@ release = [ # Only in release (build with --release --no-default-features --feat ] common = ["input", "navigation", "persist", "pixel_perfect", "ui"] -3d_camera = [] input = ["leafwing-input-manager"] loading = ["ui"] menu = ["input", "navigation", "ui"] diff --git a/src/camera.rs b/src/camera.rs index 187f4ac..b8f6c28 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -4,6 +4,8 @@ use bevy::prelude::*; use crate::{ data::{init_data, GameOptions, Persistent}, + misc::move_to, + player::Player, GameState, }; @@ -24,6 +26,12 @@ impl Plugin for CameraPlugin { app.add_systems( OnEnter(GameState::Startup), init.after(init_data), + ) + .add_systems( + Update, + update_camera + .after(move_to) + .run_if(in_state(GameState::Play)), ); } } @@ -51,7 +59,6 @@ fn init(mut cmd: Commands, options: Res>) { let clear_color = ClearColorConfig::Custom(options.base_color.with_luminance(BACKGROUND_LUMINANCE)); - #[cfg(not(feature = "3d_camera"))] let camera_bundle = Camera2dBundle { camera: Camera { clear_color, @@ -60,15 +67,17 @@ fn init(mut cmd: Commands, options: Res>) { ..default() }; - #[cfg(feature = "3d_camera")] - let camera_bundle = Camera3dBundle { - camera: Camera { - clear_color, - ..default() - }, - transform: Transform::from_xyz(0.0, 0.0, 10.0), - ..default() - }; - cmd.spawn((camera_bundle, GameCamera, FinalCamera)); } + +fn update_camera( + player: Query<&Transform, (With, Without)>, + mut cam: Query<&mut Transform, With>, +) { + let Ok(player) = player.get_single() else { return }; + let Ok(mut trans) = cam.get_single_mut() else { return }; + + let target_pos = player.translation.truncate(); + let pos = trans.translation.truncate().lerp(target_pos, 0.1); + trans.translation = pos.extend(trans.translation.z); +} diff --git a/src/enemy.rs b/src/enemy.rs new file mode 100644 index 0000000..8c30652 --- /dev/null +++ b/src/enemy.rs @@ -0,0 +1,95 @@ +use bevy::prelude::*; + +use crate::{ + assets::{SoundAssets, SpriteAssets}, + tilemap::tile_to_pos, + GameState, SCALE, +}; + +// ······ +// Plugin +// ······ + +pub struct EnemyPlugin; + +impl Plugin for EnemyPlugin { + fn build(&self, app: &mut App) { + app.add_event::() + .add_systems( + OnEnter(GameState::Play), + init.run_if(run_once()), + ) + .add_systems( + Update, + on_damage.run_if(in_state(GameState::Play)), + ); + } +} + +// ·········· +// Components +// ·········· + +#[derive(Component)] +pub struct Enemy { + pub pos: UVec2, + pub health: u32, +} + +// ······ +// Events +// ······ + +#[derive(Event)] +pub struct DamageEvent(pub Entity); + +// ······· +// Systems +// ······· + +fn init(mut cmd: Commands, sprite_assets: Res) { + for _ in 0..3 { + let (x, y) = ( + rand::random::() % 11, + rand::random::() % 7, + ); + let pos = tile_to_pos(x, y); + cmd.spawn(( + SpriteBundle { + transform: Transform::from_translation(pos.extend(5.)) + .with_scale(Vec3::splat(SCALE)), + texture: sprite_assets.one_bit.clone(), + ..default() + }, + TextureAtlas { + layout: sprite_assets.one_bit_atlas.clone(), + index: 29 + 7 * 48, + }, + Enemy { + pos: UVec2::new(x, y), + health: 2, + }, + )); + } +} + +fn on_damage( + mut cmd: Commands, + mut enemies: Query<&mut Enemy>, + sound_assets: Res, + mut damage_reader: EventReader, +) { + for DamageEvent(entity) in damage_reader.read() { + if let Ok(mut enemy) = enemies.get_mut(*entity) { + enemy.health -= 1; + if enemy.health == 0 { + cmd.entity(*entity).despawn(); + } + } + + cmd.spawn(AudioBundle { + source: sound_assets.boing.clone(), + settings: PlaybackSettings::DESPAWN, + }); + } +} diff --git a/src/lib.rs b/src/lib.rs index e7422da..f0fe550 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,8 +9,10 @@ pub mod assets; pub mod audio; pub mod camera; pub mod data; +pub mod enemy; #[cfg(feature = "input")] pub mod input; +pub mod misc; pub mod player; pub mod tilemap; #[cfg(feature = "ui")] @@ -43,6 +45,8 @@ pub enum GameState { End, } +// TODO: Create custom schedules inside update + /// Static configuration /// Allows to pass options to the game plugin such as the title and resolution. /// Must be added before the plugin @@ -161,6 +165,8 @@ impl Plugin for GamePlugin { audio::AudioPlugin, camera::CameraPlugin, data::DataPlugin, + enemy::EnemyPlugin, + misc::MiscPlugin, player::PlayerPlugin, tilemap::TilemapPlugin, )); diff --git a/src/misc.rs b/src/misc.rs new file mode 100644 index 0000000..08753e9 --- /dev/null +++ b/src/misc.rs @@ -0,0 +1,89 @@ +use std::f32::consts::PI; + +use bevy::prelude::*; + +use crate::{tilemap::TILE_SEP, GameState}; + +// ······ +// Plugin +// ······ + +pub struct MiscPlugin; + +impl Plugin for MiscPlugin { + fn build(&self, app: &mut App) { + app.add_systems( + Update, + move_to.run_if(in_state(GameState::Play)), + ); + } +} + +// ·········· +// Components +// ·········· + +pub enum Direction { + North, + South, + East, + West, +} + +#[derive(Component)] +pub struct MoveTo { + start: Vec2, + target: Vec2, + bump_dir: Option, + timer: Timer, +} + +impl MoveTo { + pub fn new(start: Vec2, target: Vec2, bump_dir: Option) -> Self { + Self { + start, + target, + bump_dir, + timer: Timer::from_seconds(0.15, TimerMode::Once), + } + } +} + +// ······· +// Systems +// ······· + +pub fn move_to( + mut cmd: Commands, + time: Res