|
|
|
|
@ -1,11 +1,13 @@
|
|
|
|
|
#![feature(try_blocks)]
|
|
|
|
|
|
|
|
|
|
// Bevy basically forces "complex types" with Querys
|
|
|
|
|
#![allow(clippy::type_complexity)]
|
|
|
|
|
|
|
|
|
|
use bevy::{
|
|
|
|
|
asset::RenderAssetUsages,
|
|
|
|
|
math::FloatOrd,
|
|
|
|
|
math::{FloatOrd},
|
|
|
|
|
render::{
|
|
|
|
|
mesh::MeshAabb,
|
|
|
|
|
camera::{ImageRenderTarget, RenderTarget},
|
|
|
|
|
render_resource::{Extent3d, TextureDimension, TextureFormat, TextureUsages},
|
|
|
|
|
view::RenderLayers,
|
|
|
|
|
@ -40,10 +42,11 @@ fn main() {
|
|
|
|
|
.add_systems(
|
|
|
|
|
Startup,
|
|
|
|
|
(
|
|
|
|
|
init_world,
|
|
|
|
|
init_tetris,
|
|
|
|
|
init_debug_ui,
|
|
|
|
|
init_cameras,
|
|
|
|
|
init_ui.after(init_cameras),
|
|
|
|
|
init_battler,
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
// Input and basic systems
|
|
|
|
|
@ -78,6 +81,11 @@ fn main() {
|
|
|
|
|
.run_if(any_component_changed::<Line>)
|
|
|
|
|
.after(clear_line),
|
|
|
|
|
assert_grid_position_uniqueness.run_if(any_component_changed::<GridPosition>),
|
|
|
|
|
sync_health
|
|
|
|
|
.run_if(any_component_changed::<Health>.or(any_component_added::<Health>)),
|
|
|
|
|
damage_on_place_shape.run_if(any_component_removed::<Shape>),
|
|
|
|
|
damage_on_clear_line.run_if(any_component_removed::<LineBlock>),
|
|
|
|
|
damage_over_time.run_if(clock_cycle(5.0))
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
// UI systems
|
|
|
|
|
@ -90,6 +98,7 @@ fn main() {
|
|
|
|
|
sync_singleton_to_ui::<Shape>.run_if(any_component_changed::<Shape>),
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
.add_observer(deal_damage)
|
|
|
|
|
.run();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -244,6 +253,9 @@ impl Display for Score {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Component, Debug)]
|
|
|
|
|
struct Health(f32);
|
|
|
|
|
|
|
|
|
|
/// ShapesBuffer resource stores non-active shapes
|
|
|
|
|
#[derive(Resource, Debug, Default)]
|
|
|
|
|
struct ShapesBuffer {
|
|
|
|
|
@ -276,7 +288,7 @@ impl Display for ShapeStore {
|
|
|
|
|
#[derive(Component)]
|
|
|
|
|
struct GridBackground;
|
|
|
|
|
|
|
|
|
|
fn init_world(
|
|
|
|
|
fn init_tetris(
|
|
|
|
|
mut commands: Commands,
|
|
|
|
|
mut meshes: ResMut<Assets<Mesh>>,
|
|
|
|
|
mut materials: ResMut<Assets<ColorMaterial>>,
|
|
|
|
|
@ -313,6 +325,54 @@ fn init_world(
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Component, Debug)]
|
|
|
|
|
struct Protagonist;
|
|
|
|
|
|
|
|
|
|
#[derive(Component, Debug)]
|
|
|
|
|
struct Enemy;
|
|
|
|
|
|
|
|
|
|
fn init_battler(
|
|
|
|
|
mut commands: Commands,
|
|
|
|
|
mut meshes: ResMut<Assets<Mesh>>,
|
|
|
|
|
mut materials: ResMut<Assets<ColorMaterial>>,
|
|
|
|
|
) {
|
|
|
|
|
{
|
|
|
|
|
let mat = materials.add(ColorMaterial {
|
|
|
|
|
color: BLUE.into(),
|
|
|
|
|
..default()
|
|
|
|
|
});
|
|
|
|
|
let mesh = meshes.add(Ellipse::new(SCALE, SCALE * 2.0));
|
|
|
|
|
let t = Transform::from_xyz(-3.0 * SCALE, 0.0, 0.0);
|
|
|
|
|
commands.spawn((
|
|
|
|
|
Mesh2d(mesh),
|
|
|
|
|
MeshMaterial2d(mat),
|
|
|
|
|
t,
|
|
|
|
|
BATTLER,
|
|
|
|
|
Health(100.0),
|
|
|
|
|
Name::new("Protagonist"),
|
|
|
|
|
Protagonist,
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
let mat = materials.add(ColorMaterial {
|
|
|
|
|
color: RED.into(),
|
|
|
|
|
..default()
|
|
|
|
|
});
|
|
|
|
|
let mesh = meshes.add(Ellipse::new(SCALE, SCALE));
|
|
|
|
|
let t = Transform::from_xyz(3.0 * SCALE, 0.0, 0.0);
|
|
|
|
|
commands.spawn((
|
|
|
|
|
Mesh2d(mesh),
|
|
|
|
|
MeshMaterial2d(mat),
|
|
|
|
|
t,
|
|
|
|
|
BATTLER,
|
|
|
|
|
Health(100.0),
|
|
|
|
|
Name::new("ENEMY"),
|
|
|
|
|
Enemy,
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Resource, Default)]
|
|
|
|
|
struct OutputImages {
|
|
|
|
|
tetris: Handle<Image>,
|
|
|
|
|
@ -366,6 +426,7 @@ fn init_cameras(
|
|
|
|
|
Camera {
|
|
|
|
|
order: 1,
|
|
|
|
|
target,
|
|
|
|
|
clear_color: ClearColorConfig::Custom(WHITE.into()),
|
|
|
|
|
..default()
|
|
|
|
|
},
|
|
|
|
|
TETRIS,
|
|
|
|
|
@ -374,7 +435,8 @@ fn init_cameras(
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
let img = render_target_image((512, 512));
|
|
|
|
|
let (width, height) = (SCALE as u32 * Y_MAX as u32, SCALE as u32 * X_MAX as u32);
|
|
|
|
|
let img = render_target_image((width, height));
|
|
|
|
|
let target = {
|
|
|
|
|
let handle = images.add(img);
|
|
|
|
|
output_images.battler = handle.clone();
|
|
|
|
|
@ -390,6 +452,7 @@ fn init_cameras(
|
|
|
|
|
Camera {
|
|
|
|
|
order: 2,
|
|
|
|
|
target,
|
|
|
|
|
clear_color: ClearColorConfig::Custom(WHITE.into()),
|
|
|
|
|
..default()
|
|
|
|
|
},
|
|
|
|
|
BATTLER,
|
|
|
|
|
@ -439,53 +502,57 @@ fn init_ui(mut commands: Commands, output_images: Res<OutputImages>, 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.5),
|
|
|
|
|
height: Val::Px(img.size_f32().y * 0.5),
|
|
|
|
|
..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((
|
|
|
|
|
@ -910,11 +977,11 @@ fn clear_line(
|
|
|
|
|
if cleared_lines.contains(&l.0) {
|
|
|
|
|
// Move to the N-offset line number (top, top-1, etc)
|
|
|
|
|
let offset = original_cleared_lines_len - cleared_lines.len();
|
|
|
|
|
info!("Moving line {:?}->{:?}", l.0, Y_MAX - 1 - offset);
|
|
|
|
|
debug!("Moving line {:?}->{:?}", l.0, Y_MAX - 1 - offset);
|
|
|
|
|
l.0 = Y_MAX - 1 - offset;
|
|
|
|
|
cleared_lines.pop();
|
|
|
|
|
} else {
|
|
|
|
|
info!("Moving line {:?}->{:?}", l.0, l.0 - cleared_lines.len());
|
|
|
|
|
debug!("Moving line {:?}->{:?}", l.0, l.0 - cleared_lines.len());
|
|
|
|
|
l.0 -= cleared_lines.len();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
@ -952,7 +1019,10 @@ enum Movement {
|
|
|
|
|
// TODO: When out of bounds left/right, try to move piece away from wall
|
|
|
|
|
fn movement(
|
|
|
|
|
trigger: Trigger<Movement>,
|
|
|
|
|
mut grid_positions: Query<&mut GridPosition, Or<(With<ShapeBlock>, With<ShapeBlocks>, Without<LineBlock>)>>,
|
|
|
|
|
mut grid_positions: Query<
|
|
|
|
|
&mut GridPosition,
|
|
|
|
|
Or<(With<ShapeBlock>, With<ShapeBlocks>, Without<LineBlock>)>,
|
|
|
|
|
>,
|
|
|
|
|
mut shape: Query<&mut Shape>,
|
|
|
|
|
inactive: Query<&GridPosition, (Without<ShapeBlock>, Without<ShapeBlocks>, With<LineBlock>)>,
|
|
|
|
|
mut commands: Commands,
|
|
|
|
|
@ -1100,9 +1170,84 @@ fn update_next_shapes(mut buffer: ResMut<ShapesBuffer>) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn assert_grid_position_uniqueness(
|
|
|
|
|
grid_positions: Query<&GridPosition, (Without<GridBackground>, Without<Shape>)>
|
|
|
|
|
grid_positions: Query<&GridPosition, (Without<GridBackground>, Without<Shape>)>,
|
|
|
|
|
) {
|
|
|
|
|
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<Health>, Added<Health>)>>,
|
|
|
|
|
parent: Query<&Children>,
|
|
|
|
|
meshes: Res<Assets<Mesh>>,
|
|
|
|
|
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,
|
|
|
|
|
));
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Event)]
|
|
|
|
|
struct Damage {
|
|
|
|
|
quantity: f32
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn deal_damage(
|
|
|
|
|
trigger: Trigger<Damage>,
|
|
|
|
|
mut healths: Query<&mut Health>
|
|
|
|
|
) {
|
|
|
|
|
healths.get_mut(trigger.target()).unwrap().0 -= trigger.event().quantity
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn damage_on_place_shape(
|
|
|
|
|
mut events: RemovedComponents<Shape>,
|
|
|
|
|
enemies: Query<Entity, With<Enemy>>,
|
|
|
|
|
mut commands: Commands,
|
|
|
|
|
) {
|
|
|
|
|
events.read().for_each(|_| {
|
|
|
|
|
enemies.iter().for_each(|e| {
|
|
|
|
|
commands.entity(e).trigger(Damage { quantity: 1.0 });
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn damage_on_clear_line(
|
|
|
|
|
mut events: RemovedComponents<LineBlock>,
|
|
|
|
|
enemies: Query<Entity, With<Enemy>>,
|
|
|
|
|
mut commands: Commands,
|
|
|
|
|
) {
|
|
|
|
|
events.read().for_each(|_| {
|
|
|
|
|
enemies.iter().for_each(|e| {
|
|
|
|
|
commands.entity(e).trigger(Damage { quantity: 1.0 });
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn damage_over_time(
|
|
|
|
|
protagonist: Single<Entity, With<Protagonist>>,
|
|
|
|
|
mut commands: Commands,
|
|
|
|
|
) {
|
|
|
|
|
commands.entity(*protagonist).trigger(Damage { quantity: 1.0 });
|
|
|
|
|
}
|
|
|
|
|
|