Rendering to images works, images are ui nodes

Lots of improvements we need to make but the bones are there!
main
Elijah Voigt 2 months ago
parent e6093b3fcb
commit 1c99950e65

@ -2,17 +2,27 @@
// Bevy basically forces "complex types" with Querys // Bevy basically forces "complex types" with Querys
#![allow(clippy::type_complexity)] #![allow(clippy::type_complexity)]
use bevy::{
asset::RenderAssetUsages,
math::FloatOrd,
render::{
camera::{ImageRenderTarget, RenderTarget},
render_resource::{Extent3d, TextureDimension, TextureFormat, TextureUsages},
view::RenderLayers,
},
};
use games::*; use games::*;
use itertools::Itertools; use itertools::Itertools;
#[cfg(test)] #[cfg(test)]
mod test; mod test;
// TODO: Space key: skip to end const TETRIS: RenderLayers = RenderLayers::layer(1);
const BATTLER: RenderLayers = RenderLayers::layer(2);
// TODO: When piece is near wall and rotates, move it over if it fits // TODO: When piece is near wall and rotates, move it over if it fits
// TODO: Make falling based on a timer resource ticking // TODO: Make falling based on a timer resource ticking
// This allows us to tune the falling rate over time // This allows us to tune the falling rate over time
// TODO: Preview next batch of pieces that will drop
fn main() { fn main() {
App::new() App::new()
@ -26,7 +36,16 @@ fn main() {
.init_resource::<ShapesBuffer>() .init_resource::<ShapesBuffer>()
.init_resource::<ShapeStore>() .init_resource::<ShapeStore>()
.init_resource::<Score>() .init_resource::<Score>()
.add_systems(Startup, (init_world, init_debug_ui, init_ui)) .init_resource::<OutputImages>()
.add_systems(
Startup,
(
init_world,
init_debug_ui,
init_cameras,
init_ui.after(init_cameras),
),
)
// Input and basic systems // Input and basic systems
.add_systems( .add_systems(
Update, Update,
@ -48,8 +67,11 @@ fn main() {
.run_if(clock_cycle(1.0)), .run_if(clock_cycle(1.0)),
update_position.run_if(any_component_changed::<GridPosition>), update_position.run_if(any_component_changed::<GridPosition>),
update_shape_blocks update_shape_blocks
.run_if(any_component_added::<Shape>.or(any_component_changed::<Shape>)).after(update_position), .run_if(any_component_added::<Shape>.or(any_component_changed::<Shape>))
deactivate_shape.run_if(any_component_removed::<Shape>).after(update_shape_blocks), .after(update_position),
deactivate_shape
.run_if(any_component_removed::<Shape>)
.after(update_shape_blocks),
// Clearing lines systems // Clearing lines systems
clear_line.run_if(any_component_changed::<LineBlocks>), clear_line.run_if(any_component_changed::<LineBlocks>),
adjust_block_lines adjust_block_lines
@ -246,7 +268,7 @@ impl Display for ShapeStore {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.0 { match self.0 {
Some(inner) => write!(f, "{}", inner.as_ascii()), Some(inner) => write!(f, "{}", inner.as_ascii()),
None => write!(f, "---") None => write!(f, "---"),
} }
} }
} }
@ -266,11 +288,96 @@ fn init_world(
(0..Y_MAX).for_each(|i| { (0..Y_MAX).for_each(|i| {
info!("Spawning line {i}"); info!("Spawning line {i}");
commands.spawn((Line(i), LineBlocks::default())); commands.spawn((Line(i), LineBlocks::default(), TETRIS));
}); });
} }
fn init_ui(mut commands: Commands) { #[derive(Resource, Default)]
struct OutputImages {
tetris: Handle<Image>,
battler: Handle<Image>,
}
#[derive(Component)]
struct TetrisCamera;
#[derive(Component)]
struct BattlerCamera;
fn init_cameras(
mut commands: Commands,
mut images: ResMut<Assets<Image>>,
mut output_images: ResMut<OutputImages>,
) {
fn render_target_image() -> Image {
let (width, height) = (1028, 1028);
let size = Extent3d {
width,
height,
..default()
};
let dimension = TextureDimension::D2;
let format = TextureFormat::Bgra8UnormSrgb;
let asset_usage = RenderAssetUsages::all();
let mut image = Image::new_uninit(size, dimension, format, asset_usage);
image.texture_descriptor.usage = TextureUsages::TEXTURE_BINDING
| TextureUsages::COPY_DST
| TextureUsages::RENDER_ATTACHMENT;
image
}
{
let img = render_target_image();
let target = {
let handle = images.add(img);
output_images.tetris = handle.clone();
let scale_factor = FloatOrd(1.0);
RenderTarget::Image(ImageRenderTarget {
handle,
scale_factor,
})
};
commands.spawn((
Camera2d,
Camera {
order: 1,
target,
..default()
},
TETRIS,
TetrisCamera,
));
}
{
let img = render_target_image();
let target = {
let handle = images.add(img);
output_images.battler = handle.clone();
let scale_factor = FloatOrd(1.0);
RenderTarget::Image(ImageRenderTarget {
handle,
scale_factor,
})
};
commands.spawn((
Camera2d,
Camera {
order: 2,
target,
..default()
},
BATTLER,
BattlerCamera,
));
}
}
fn init_ui(mut commands: Commands, output_images: Res<OutputImages>) {
commands commands
.spawn(( .spawn((
Node { Node {
@ -279,7 +386,6 @@ fn init_ui(mut commands: Commands) {
flex_direction: FlexDirection::Column, flex_direction: FlexDirection::Column,
..default() ..default()
}, },
BackgroundColor(BLACK.into()),
)) ))
.with_children(|parent| { .with_children(|parent| {
parent parent
@ -314,6 +420,50 @@ fn init_ui(mut commands: Commands) {
}); });
}); });
commands.spawn((
Node {
align_self: AlignSelf::End,
justify_self: JustifySelf::Center,
border: UiRect::all(Val::Px(5.0)),
..default()
},
BorderColor(BLACK.into()),
children![(
Node {
width: Val::Px(256.0),
height: Val::Px(256.0),
..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(BLACK.into()),
children![(
Node {
width: Val::Px(256.0),
height: Val::Px(256.0),
..default()
},
ImageNode {
image: output_images.battler.clone(),
image_mode: NodeImageMode::Stretch,
..default()
},
)]
));
commands commands
.spawn(( .spawn((
Node { Node {
@ -341,9 +491,7 @@ fn init_debug_ui(mut commands: Commands) {
.with_children(|parent| { .with_children(|parent| {
parent.spawn(( parent.spawn((
Node::default(), Node::default(),
children![ children![(Text::new("SHAPE"), SyncSingleton::<Shape>::default()),],
(Text::new("SHAPE"), SyncSingleton::<Shape>::default()),
],
)); ));
}); });
} }
@ -555,7 +703,15 @@ fn update_position(
// TODO: Move to trigger // TODO: Move to trigger
fn update_shape_blocks( fn update_shape_blocks(
query: Query<(Entity, &Shape, &GridPosition), Or<(Added<Shape>, Changed<Shape>, Added<GridPosition>, Changed<GridPosition>)>>, query: Query<
(Entity, &Shape, &GridPosition),
Or<(
Added<Shape>,
Changed<Shape>,
Added<GridPosition>,
Changed<GridPosition>,
)>,
>,
mut blocks: Query<&mut GridPosition, (With<ShapeBlock>, Without<Shape>)>, mut blocks: Query<&mut GridPosition, (With<ShapeBlock>, Without<Shape>)>,
mut commands: Commands, mut commands: Commands,
visuals: Res<Visuals>, visuals: Res<Visuals>,
@ -571,7 +727,7 @@ fn update_shape_blocks(
.with_related_entities::<ShapeBlock>(|parent| { .with_related_entities::<ShapeBlock>(|parent| {
s.coordinates(center).for_each(|gp| { s.coordinates(center).for_each(|gp| {
parent parent
.spawn((mesh.clone(), mat.clone(), gp.unwrap(), Block)) .spawn((mesh.clone(), mat.clone(), gp.unwrap(), Block, TETRIS))
.observe(movement); .observe(movement);
}); });
}); });
@ -673,8 +829,12 @@ fn add_piece(mut commands: Commands, mut shapes: ResMut<ShapesBuffer>) {
let this_shape = shapes.next.pop_front().unwrap(); let this_shape = shapes.next.pop_front().unwrap();
commands commands
.spawn(( .spawn((
GridPosition { y: Y_MAX - this_shape.height(), ..default() }, GridPosition {
y: Y_MAX - this_shape.height(),
..default()
},
this_shape, this_shape,
TETRIS,
)) ))
.observe(movement) .observe(movement)
.observe(swap); .observe(swap);
@ -690,10 +850,13 @@ fn clear_line(
) { ) {
let mut cleared_lines: Vec<usize> = changed_lines let mut cleared_lines: Vec<usize> = changed_lines
.iter() .iter()
.filter_map(|e| try { (Some(e)?, line_blocks.get(e).ok()?, lines.get(e).ok()?) } ) .filter_map(|e| try { (Some(e)?, line_blocks.get(e).ok()?, lines.get(e).ok()?) })
.filter_map(|(e, lb, Line(i))| { .filter_map(|(e, lb, Line(i))| {
if lb.0.len() == 10 { if lb.0.len() == 10 {
commands.entity(e).despawn_related::<LineBlocks>().insert(LineBlocks::default()); commands
.entity(e)
.despawn_related::<LineBlocks>()
.insert(LineBlocks::default());
score.0 += 1; score.0 += 1;
info!("New score: {:?}", score.0); info!("New score: {:?}", score.0);
Some(*i) Some(*i)
@ -711,9 +874,14 @@ fn clear_line(
{ {
debug_assert_eq!(lines.iter().count(), 20, "There should be 20 lines"); debug_assert_eq!(lines.iter().count(), 20, "There should be 20 lines");
// Check that all line numbers are present // Check that all line numbers are present
lines.iter().map(|Line(i)| i).sorted().enumerate().for_each(|(i, line_num)| { lines
debug_assert_eq!(i, *line_num, "Line numbers should match their sorted index"); .iter()
}); .map(|Line(i)| i)
.sorted()
.enumerate()
.for_each(|(i, line_num)| {
debug_assert_eq!(i, *line_num, "Line numbers should match their sorted index");
});
} }
let original_cleared_lines_len = cleared_lines.len(); let original_cleared_lines_len = cleared_lines.len();
@ -785,7 +953,9 @@ fn movement(
Movement::Left => vec![center.with_offset(-1, 0)], Movement::Left => vec![center.with_offset(-1, 0)],
Movement::Right => vec![center.with_offset(1, 0)], Movement::Right => vec![center.with_offset(1, 0)],
Movement::Rotate => vec![Ok(*center)], Movement::Rotate => vec![Ok(*center)],
Movement::Skip => (1..=center.y).map(|i| center.with_offset(0, -(i as isize))).collect(), Movement::Skip => (1..=center.y)
.map(|i| center.with_offset(0, -(i as isize)))
.collect(),
}; };
let new_shape = match trigger.event() { let new_shape = match trigger.event() {
Movement::Down | Movement::Left | Movement::Right | Movement::Skip => *this_shape, Movement::Down | Movement::Left | Movement::Right | Movement::Skip => *this_shape,
@ -860,7 +1030,10 @@ fn swap(
// Copy current shape into store // Copy current shape into store
// De-activate entity // De-activate entity
store.0 = Some(*shapes.get(trigger.target()).unwrap()); store.0 = Some(*shapes.get(trigger.target()).unwrap());
commands.entity(trigger.target()).despawn_related::<ShapeBlocks>().despawn(); commands
.entity(trigger.target())
.despawn_related::<ShapeBlocks>()
.despawn();
} }
Some(inner) => { Some(inner) => {
// Copy current shape into store // Copy current shape into store
@ -883,7 +1056,8 @@ fn deactivate_shape(
let GridPosition { y, .. } = grid_positions.get(block).unwrap(); let GridPosition { y, .. } = grid_positions.get(block).unwrap();
if let Some(parent_line) = lines if let Some(parent_line) = lines
.iter() .iter()
.find_map(|(e, Line(i))| (*y == *i).then_some(e)) { .find_map(|(e, Line(i))| (*y == *i).then_some(e))
{
commands commands
.entity(parent_line) .entity(parent_line)
.add_one_related::<LineBlock>(block); .add_one_related::<LineBlock>(block);

Loading…
Cancel
Save