From 76733e20d81901da7c3b027a2d242fcd498a4a44 Mon Sep 17 00:00:00 2001 From: Elijah Voigt Date: Sun, 2 Nov 2025 20:50:08 -0800 Subject: [PATCH] Assigning and drawing health --- src/bin/tetris/main.rs | 146 ++++++++++++++++++++++++++++------------- 1 file changed, 102 insertions(+), 44 deletions(-) diff --git a/src/bin/tetris/main.rs b/src/bin/tetris/main.rs index f0f1dcd..262c29c 100644 --- a/src/bin/tetris/main.rs +++ b/src/bin/tetris/main.rs @@ -4,8 +4,9 @@ use bevy::{ asset::RenderAssetUsages, - math::FloatOrd, + math::{FloatOrd}, render::{ + mesh::MeshAabb, camera::{ImageRenderTarget, RenderTarget}, render_resource::{Extent3d, TextureDimension, TextureFormat, TextureUsages}, view::RenderLayers, @@ -79,6 +80,8 @@ fn main() { .run_if(any_component_changed::) .after(clear_line), assert_grid_position_uniqueness.run_if(any_component_changed::), + sync_health + .run_if(any_component_changed::.or(any_component_added::)), ), ) // UI systems @@ -245,6 +248,9 @@ impl Display for Score { } } +#[derive(Component, Debug)] +struct Health(f32); + /// ShapesBuffer resource stores non-active shapes #[derive(Resource, Debug, Default)] struct ShapesBuffer { @@ -314,6 +320,12 @@ fn init_tetris( }); } +#[derive(Component, Debug)] +struct Protagonist; + +#[derive(Component, Debug)] +struct Enemy; + fn init_battler( mut commands: Commands, mut meshes: ResMut>, @@ -331,7 +343,9 @@ fn init_battler( MeshMaterial2d(mat), t, BATTLER, + Health(100.0), Name::new("Protagonist"), + Protagonist, )); } @@ -347,12 +361,13 @@ fn init_battler( MeshMaterial2d(mat), t, BATTLER, + Health(100.0), Name::new("ENEMY"), + Enemy, )); } } - #[derive(Resource, Default)] struct OutputImages { tetris: Handle, @@ -482,53 +497,57 @@ fn init_ui(mut commands: Commands, output_images: Res, images: Res }); }); - commands.spawn(( - Node { - align_self: AlignSelf::End, - justify_self: JustifySelf::Center, - border: UiRect::all(Val::Px(5.0)), - ..default() - }, - BorderColor(WHITE.into()), - )).with_children(|parent| { - let img = images.get(&output_images.tetris).unwrap(); - parent.spawn(( + commands + .spawn(( Node { - width: Val::Px(img.size_f32().x * 0.75), - height: Val::Px(img.size_f32().y * 0.75), - ..default() - }, - ImageNode { - image: output_images.tetris.clone(), - image_mode: NodeImageMode::Stretch, + align_self: AlignSelf::End, + justify_self: JustifySelf::Center, + border: UiRect::all(Val::Px(5.0)), ..default() }, - )); - }); + BorderColor(WHITE.into()), + )) + .with_children(|parent| { + let img = images.get(&output_images.tetris).unwrap(); + parent.spawn(( + Node { + width: Val::Px(img.size_f32().x * 0.75), + height: Val::Px(img.size_f32().y * 0.75), + ..default() + }, + ImageNode { + image: output_images.tetris.clone(), + image_mode: NodeImageMode::Stretch, + ..default() + }, + )); + }); - commands.spawn(( - Node { - align_self: AlignSelf::Start, - justify_self: JustifySelf::Center, - border: UiRect::all(Val::Px(5.0)), - ..default() - }, - BorderColor(WHITE.into()), - )).with_children(|parent| { - let img = images.get(&output_images.battler).unwrap(); - parent.spawn(( + commands + .spawn(( Node { - width: Val::Px(img.size_f32().x * 0.75), - height: Val::Px(img.size_f32().y * 0.75), - ..default() - }, - ImageNode { - image: output_images.battler.clone(), - image_mode: NodeImageMode::Stretch, + align_self: AlignSelf::Start, + justify_self: JustifySelf::Center, + border: UiRect::all(Val::Px(5.0)), ..default() }, - )); - }); + BorderColor(WHITE.into()), + )) + .with_children(|parent| { + let img = images.get(&output_images.battler).unwrap(); + parent.spawn(( + Node { + width: Val::Px(img.size_f32().x * 0.75), + height: Val::Px(img.size_f32().y * 0.75), + ..default() + }, + ImageNode { + image: output_images.battler.clone(), + image_mode: NodeImageMode::Stretch, + ..default() + }, + )); + }); commands .spawn(( @@ -995,7 +1014,10 @@ enum Movement { // TODO: When out of bounds left/right, try to move piece away from wall fn movement( trigger: Trigger, - mut grid_positions: Query<&mut GridPosition, Or<(With, With, Without)>>, + mut grid_positions: Query< + &mut GridPosition, + Or<(With, With, Without)>, + >, mut shape: Query<&mut Shape>, inactive: Query<&GridPosition, (Without, Without, With)>, mut commands: Commands, @@ -1106,6 +1128,7 @@ fn deactivate_shape( parent: Query<&ShapeBlocks>, lines: Query<(Entity, &Line), With>, mut commands: Commands, + mut enemy_health: Single<&mut Health, With> ) { events.read().for_each(|target| { parent.iter_descendants(target).for_each(|block| { @@ -1122,6 +1145,9 @@ fn deactivate_shape( } }); commands.entity(target).despawn(); + + // TODO: Turn this into an event + enemy_health.0 -= 1.0; }); } @@ -1143,9 +1169,41 @@ fn update_next_shapes(mut buffer: ResMut) { } fn assert_grid_position_uniqueness( - grid_positions: Query<&GridPosition, (Without, Without)> + grid_positions: Query<&GridPosition, (Without, Without)>, ) { grid_positions.iter_combinations().for_each(|[a, b]| { assert_ne!(a, b, "Two entities are in the same grid position!"); }); } + +fn sync_health( + query: Query<(Entity, &Health, &Mesh2d), Or<(Changed, Added)>>, + parent: Query<&Children>, + meshes: Res>, + mut texts: Query<&mut Text2d>, + mut commands: Commands, +) { + query.iter().for_each(|(e, h, m)| { + if let Some(child) = parent + .iter_descendants(e) + .find(|child| texts.contains(*child)) + { + info!("Updating health"); + let mut t = texts.get_mut(child).unwrap(); + t.0 = format!("{}", h.0); + } else { + info!("Creating health display"); + commands.entity(e).with_children(|parent| { + let mesh = meshes.get(&m.0).unwrap(); + let aabb = mesh.compute_aabb().unwrap(); + let offset = Vec3::new(0.0, aabb.half_extents.y + 10.0, 0.0); + parent.spawn(( + Text2d(format!("{}", h.0)), + TextColor(BLACK.into()), + Transform::from_translation(offset), + BATTLER, + )); + }); + } + }) +}