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
#![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 itertools::Itertools;
#[cfg(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: Make falling based on a timer resource ticking
// This allows us to tune the falling rate over time
// TODO: Preview next batch of pieces that will drop
fn main() {
App::new()
@ -26,7 +36,16 @@ fn main() {
.init_resource::<ShapesBuffer>()
.init_resource::<ShapeStore>()
.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
.add_systems(
Update,
@ -48,8 +67,11 @@ fn main() {
.run_if(clock_cycle(1.0)),
update_position.run_if(any_component_changed::<GridPosition>),
update_shape_blocks
.run_if(any_component_added::<Shape>.or(any_component_changed::<Shape>)).after(update_position),
deactivate_shape.run_if(any_component_removed::<Shape>).after(update_shape_blocks),
.run_if(any_component_added::<Shape>.or(any_component_changed::<Shape>))
.after(update_position),
deactivate_shape
.run_if(any_component_removed::<Shape>)
.after(update_shape_blocks),
// Clearing lines systems
clear_line.run_if(any_component_changed::<LineBlocks>),
adjust_block_lines
@ -246,7 +268,7 @@ impl Display for ShapeStore {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.0 {
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| {
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
.spawn((
Node {
@ -279,7 +386,6 @@ fn init_ui(mut commands: Commands) {
flex_direction: FlexDirection::Column,
..default()
},
BackgroundColor(BLACK.into()),
))
.with_children(|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
.spawn((
Node {
@ -341,9 +491,7 @@ fn init_debug_ui(mut commands: Commands) {
.with_children(|parent| {
parent.spawn((
Node::default(),
children![
(Text::new("SHAPE"), SyncSingleton::<Shape>::default()),
],
children![(Text::new("SHAPE"), SyncSingleton::<Shape>::default()),],
));
});
}
@ -555,7 +703,15 @@ fn update_position(
// TODO: Move to trigger
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 commands: Commands,
visuals: Res<Visuals>,
@ -571,7 +727,7 @@ fn update_shape_blocks(
.with_related_entities::<ShapeBlock>(|parent| {
s.coordinates(center).for_each(|gp| {
parent
.spawn((mesh.clone(), mat.clone(), gp.unwrap(), Block))
.spawn((mesh.clone(), mat.clone(), gp.unwrap(), Block, TETRIS))
.observe(movement);
});
});
@ -673,8 +829,12 @@ fn add_piece(mut commands: Commands, mut shapes: ResMut<ShapesBuffer>) {
let this_shape = shapes.next.pop_front().unwrap();
commands
.spawn((
GridPosition { y: Y_MAX - this_shape.height(), ..default() },
GridPosition {
y: Y_MAX - this_shape.height(),
..default()
},
this_shape,
TETRIS,
))
.observe(movement)
.observe(swap);
@ -690,10 +850,13 @@ fn clear_line(
) {
let mut cleared_lines: Vec<usize> = changed_lines
.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))| {
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;
info!("New score: {:?}", score.0);
Some(*i)
@ -711,9 +874,14 @@ fn clear_line(
{
debug_assert_eq!(lines.iter().count(), 20, "There should be 20 lines");
// Check that all line numbers are present
lines.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");
});
lines
.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();
@ -785,7 +953,9 @@ fn movement(
Movement::Left => vec![center.with_offset(-1, 0)],
Movement::Right => vec![center.with_offset(1, 0)],
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() {
Movement::Down | Movement::Left | Movement::Right | Movement::Skip => *this_shape,
@ -860,7 +1030,10 @@ fn swap(
// Copy current shape into store
// De-activate entity
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) => {
// Copy current shape into store
@ -883,7 +1056,8 @@ fn deactivate_shape(
let GridPosition { y, .. } = grid_positions.get(block).unwrap();
if let Some(parent_line) = lines
.iter()
.find_map(|(e, Line(i))| (*y == *i).then_some(e)) {
.find_map(|(e, Line(i))| (*y == *i).then_some(e))
{
commands
.entity(parent_line)
.add_one_related::<LineBlock>(block);

Loading…
Cancel
Save