diff --git a/.gitignore b/.gitignore index ea8c4bf..45e0eab 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +*.DS_Store diff --git a/assets/fonts/PressStart2P-Regular.ttf b/assets/fonts/PressStart2P-Regular.ttf new file mode 100644 index 0000000..2442aff Binary files /dev/null and b/assets/fonts/PressStart2P-Regular.ttf differ diff --git a/src/main.rs b/src/main.rs index 59cfbbc..836e661 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,11 +4,9 @@ use bevy::core::{FixedTimestep, Time, Timer}; use bevy::input::system::exit_on_esc_system; use bevy::input::Input; use bevy::math::Vec3; -use bevy::prelude::{ - App, AssetServer, Commands, Component, Entity, KeyCode, OrthographicCameraBundle, Query, Res, - SystemSet, Transform, Without, -}; +use bevy::prelude::*; use bevy::sprite::{Sprite, SpriteBundle}; +use bevy::text::Text; use bevy::window::Windows; use bevy::DefaultPlugins; use rand::Rng; @@ -16,6 +14,7 @@ use rand::Rng; fn main() { App::new() .add_plugins(DefaultPlugins) + .insert_resource(ArnoldStats::new()) .add_startup_system(setup) .add_system_set( SystemSet::new() @@ -26,7 +25,8 @@ fn main() { .with_system(linear_movement_system) .with_system(expiration_date_system) .with_system(follow_arnold_system) - .with_system(enemy_spawner_system), + .with_system(enemy_spawner_system) + .with_system(update_scoreboard_system), ) .add_system(exit_on_esc_system) .run(); @@ -35,60 +35,162 @@ fn main() { fn setup(mut commands: Commands, assets: Res) { let arnold_sh = assets.load("arno.png"); + // cameras commands.spawn_bundle(OrthographicCameraBundle::new_2d()); + commands.spawn_bundle(UiCameraBundle::default()); + // arnold himself commands .spawn_bundle(SpriteBundle { texture: arnold_sh, ..Default::default() }) - .insert(EnemySpawner::new(Timer::from_seconds(3.0, true))) .insert(Arnold::new()) .insert(Transform { translation: Vec3::new(0.0, 0.0, 0.0), ..Default::default() }); + // enemy spawner + commands + .spawn() + .insert(EnemySpawner::new(Timer::from_seconds(3.0, true))); + // scoreboard + commands + .spawn_bundle(TextBundle { + text: Text { + sections: vec![ + TextSection { + style: TextStyle { + font: assets.load("fonts/PressStart2P-Regular.ttf"), + font_size: 15.0, + color: Color::WHITE, + }, + value: "kills: ".to_string(), + }, + TextSection { + style: TextStyle { + font: assets.load("fonts/PressStart2P-Regular.ttf"), + font_size: 15.0, + color: Color::RED, + }, + value: "0".to_string(), + }, + ], + ..Default::default() + }, + style: Style { + position_type: PositionType::Absolute, + position: Rect { + top: Val::Px(5.0), + left: Val::Px(5.0), + ..Default::default() + }, + ..Default::default() + }, + ..Default::default() + }) + .insert(ScoreBoard); +} + +// Unit struct for tracking the ScoreBoard Text UI +#[derive(Component)] +struct ScoreBoard; + +fn update_scoreboard_system( + stats: Res, + mut query: Query<&mut Text, With>, +) { + query.single_mut().sections[1].value = format!("{}", stats.kills); +} + +struct ArnoldStats { + damage_dealt: i32, + kills: i32, +} + +impl ArnoldStats { + fn new() -> Self { + Self { + damage_dealt: 0, + kills: 0, + } + } } #[derive(Component)] struct Projectile { + damage: i32, } impl Projectile { - fn new() -> Self { - Self {} + fn new(damage: i32) -> Self { + Self { damage } } } #[derive(Component)] struct Hitpoints { + hp: i32, } impl Hitpoints { - fn new() -> Self { - Self {} + fn new(hp: i32) -> Self { + Self { hp } } } fn projectile_collide_system( mut commands: Commands, + mut stats: ResMut, + assets: Res, projectile_query: Query<(&Projectile, &Transform)>, - target_query: Query<(Entity, &Hitpoints, &Transform)> + mut target_query: Query<(Entity, &mut Hitpoints, &Transform)>, ) { - for (entity, _hp, target_transform) in target_query.iter() { - let mut has_been_hit = false; - for (_projectile, projectile_transform) in projectile_query.iter() { - if target_transform.translation.distance(projectile_transform.translation) < 32.0 { - has_been_hit = true; + for (entity, mut hp, target_transform) in target_query.iter_mut() { + for (projectile, projectile_transform) in projectile_query.iter() { + if target_transform + .translation + .distance(projectile_transform.translation) + < 32.0 + { + hp.hp -= projectile.damage; + stats.damage_dealt += projectile.damage; + + let font = assets.load("fonts/PressStart2P-Regular.ttf"); + let text_style = TextStyle { + font, + font_size: 10.0, + color: Color::YELLOW, + }; + let text_alignment = TextAlignment { + vertical: VerticalAlign::Center, + horizontal: HorizontalAlign::Center, + }; + commands + .spawn_bundle(Text2dBundle { + text: Text::with_section( + &format!("{}", projectile.damage), + text_style, + text_alignment, + ), + transform: Transform { + translation: target_transform.translation + Vec3::new(0.0, 10.0, 10.0), + ..Default::default() + }, + ..Default::default() + }) + .insert(ExpirationDate::new(3.0)) + .insert(LinearMovement::new(Vec3::new(0.0, 0.5, 0.0))); } } - if has_been_hit { + if hp.hp <= 0 { + stats.kills += 1; commands.entity(entity).despawn(); } } } -#[derive(PartialEq)] +#[derive(Clone, Copy, PartialEq)] enum Facing { Left, Right, @@ -141,8 +243,10 @@ struct ExpirationDate { } impl ExpirationDate { - fn new(countdown: Timer) -> Self { - Self { countdown } + fn new(duration: f32) -> Self { + Self { + countdown: Timer::from_seconds(duration, false), + } } } @@ -200,30 +304,14 @@ fn arnold_attack_system( let (mut arnold, arnold_transform) = query.single_mut(); if arnold.attack_timer.tick(time.delta()).just_finished() { - let knife_velocity = match arnold.facing { - Facing::Left => Vec3::new(-1.0, 0.0, 0.0), - Facing::Right => Vec3::new(1.0, 0.0, 0.0), - } * 4.0; - - commands - .spawn_bundle(SpriteBundle { - texture: assets.load("knife.png"), - sprite: Sprite { - flip_x: arnold.facing == Facing::Left, - ..Default::default() - }, - ..Default::default() - }) - .insert(Knife::new()) - .insert(Transform { - translation: arnold_transform.translation - + Vec3::new(0.0, 0.0, 10.0) - + knife_velocity, - ..Default::default() - }) - .insert(LinearMovement::new(knife_velocity)) - .insert(ExpirationDate::new(Timer::from_seconds(5.0, false))) - .insert(Projectile::new()); + commands.spawn_bundle(KnifeBundle::new( + assets.load("knife.png"), + arnold_transform.translation + Vec3::new(0.0, 0.0, 10.0), + arnold.facing, + 4.0, + 5.0, + 10, + )); } } @@ -267,7 +355,53 @@ fn enemy_spawner_system( ..Default::default() }) .insert(FollowArnold::new(1.0)) - .insert(Hitpoints::new()); + .insert(Hitpoints::new(8)); +} + +#[derive(Bundle)] +struct KnifeBundle { + _knife: Knife, + #[bundle] + sprite: SpriteBundle, + lin_movement: LinearMovement, + exp_date: ExpirationDate, + projectile: Projectile, +} + +impl KnifeBundle { + fn new( + texture: Handle, + translation: Vec3, + facing: Facing, + speed: f32, + lifetime: f32, + damage: i32, + ) -> Self { + let velocity = match facing { + Facing::Left => Vec3::new(-1.0, 0.0, 0.0), + Facing::Right => Vec3::new(1.0, 0.0, 0.0), + } * speed; + let flip_x = velocity.x < 0.0; + + KnifeBundle { + _knife: Knife::new(), + sprite: SpriteBundle { + texture, + sprite: Sprite { + flip_x, + ..Default::default() + }, + transform: Transform { + translation, + ..Default::default() + }, + ..Default::default() + }, + lin_movement: LinearMovement::new(velocity), + exp_date: ExpirationDate::new(lifetime), + projectile: Projectile::new(damage), + } + } } #[derive(Component)]