generated from hheik/bevy-template
Implemented line clearing
parent
381609c261
commit
c8fa0bf4ed
|
|
@ -9,7 +9,7 @@ pub fn game_area_gizmos(mut gizmos: Gizmos, game_area_query: Query<(&GameArea, &
|
||||||
Isometry2d::new(game_area.center() * grid.tile_size, Rot2::IDENTITY),
|
Isometry2d::new(game_area.center() * grid.tile_size, Rot2::IDENTITY),
|
||||||
UVec2::new(
|
UVec2::new(
|
||||||
(game_area.right_boundary - game_area.left_boundary) as u32 + 1,
|
(game_area.right_boundary - game_area.left_boundary) as u32 + 1,
|
||||||
(game_area.top_boundary - game_area.bottom_boundary) as u32 + 1,
|
(game_area.kill_height - game_area.bottom_boundary) as u32 + 1,
|
||||||
),
|
),
|
||||||
grid.tile_size,
|
grid.tile_size,
|
||||||
Color::srgba(0.4, 0.1, 0.1, 0.6),
|
Color::srgba(0.4, 0.1, 0.1, 0.6),
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,5 @@ pub fn init(app: &mut App) {
|
||||||
.add_systems(Startup, systems::setup_game_scene)
|
.add_systems(Startup, systems::setup_game_scene)
|
||||||
.add_systems(PreUpdate, (systems::block_control, systems::apply_gravity))
|
.add_systems(PreUpdate, (systems::block_control, systems::apply_gravity))
|
||||||
.add_systems(Update, (systems::demo_2d, systems::apply_piece_movement))
|
.add_systems(Update, (systems::demo_2d, systems::apply_piece_movement))
|
||||||
.add_systems(
|
.add_systems(PostUpdate, systems::grid_positioning);
|
||||||
PostUpdate,
|
|
||||||
(systems::handle_piece_placed, systems::grid_positioning).chain(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
use crate::game::{
|
use crate::game::{
|
||||||
|
|
@ -10,23 +12,26 @@ pub fn setup_game_scene(world: &mut World) {
|
||||||
let game_area = GameArea::default();
|
let game_area = GameArea::default();
|
||||||
let mut next_piece = NextPiece::default();
|
let mut next_piece = NextPiece::default();
|
||||||
let starting_piece = next_piece.take_and_generate();
|
let starting_piece = next_piece.take_and_generate();
|
||||||
world.spawn((
|
world
|
||||||
Name::from("Game scene"),
|
.spawn((
|
||||||
GameGravity::default(),
|
Name::from("Game scene"),
|
||||||
game_area.clone(),
|
GameGravity::default(),
|
||||||
next_piece,
|
game_area.clone(),
|
||||||
children![
|
next_piece,
|
||||||
(Transform::from_xyz(-16.0, 88.0, 10.0), DemoCamera2d,),
|
children![
|
||||||
// Create first piece
|
(Transform::from_xyz(-16.0, 88.0, 10.0), DemoCamera2d,),
|
||||||
(
|
// Create first piece
|
||||||
GridTransform {
|
(
|
||||||
translation: game_area.block_spawn_point()
|
GridTransform {
|
||||||
},
|
translation: game_area.block_spawn_point()
|
||||||
ControllablePiece,
|
},
|
||||||
create_piece(starting_piece),
|
ControllablePiece,
|
||||||
),
|
create_piece(starting_piece),
|
||||||
],
|
),
|
||||||
));
|
],
|
||||||
|
))
|
||||||
|
.observe(handle_placed_piece)
|
||||||
|
.observe(handle_line_clear);
|
||||||
world.flush();
|
world.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,8 +90,9 @@ pub fn apply_piece_movement(
|
||||||
mut piece_query: Query<(&mut PieceControls, Entity, &ChildOf, &Children)>,
|
mut piece_query: Query<(&mut PieceControls, Entity, &ChildOf, &Children)>,
|
||||||
mut transform_query: Query<(&mut GridTransform, Has<Block>)>,
|
mut transform_query: Query<(&mut GridTransform, Has<Block>)>,
|
||||||
mut input_repeat: Local<Option<InputRepeatState>>,
|
mut input_repeat: Local<Option<InputRepeatState>>,
|
||||||
mut on_piece_placed: EventWriter<OnPiecePlaced>,
|
// mut on_piece_placed: EventWriter<OnPiecePlaced>,
|
||||||
time: Res<Time>,
|
time: Res<Time>,
|
||||||
|
mut commands: Commands,
|
||||||
) {
|
) {
|
||||||
// TODO: Handle piece collision
|
// TODO: Handle piece collision
|
||||||
for (mut piece_controls, entity, parent, children) in piece_query.iter_mut() {
|
for (mut piece_controls, entity, parent, children) in piece_query.iter_mut() {
|
||||||
|
|
@ -94,7 +100,8 @@ pub fn apply_piece_movement(
|
||||||
// TODO: replace with actual logic
|
// TODO: replace with actual logic
|
||||||
if piece_controls.instant_drop {
|
if piece_controls.instant_drop {
|
||||||
grid_transform.translation.y = 0;
|
grid_transform.translation.y = 0;
|
||||||
on_piece_placed.write(OnPiecePlaced { piece: entity });
|
// on_piece_placed.write(OnPiecePlaced { piece: entity });
|
||||||
|
commands.trigger_targets(OnPiecePlaced { piece: entity }, parent.parent());
|
||||||
*input_repeat = None;
|
*input_repeat = None;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -105,7 +112,8 @@ pub fn apply_piece_movement(
|
||||||
}
|
}
|
||||||
if !can_move_down && piece_controls.cumulated_gravity >= 3.0 {
|
if !can_move_down && piece_controls.cumulated_gravity >= 3.0 {
|
||||||
// Force piece placement
|
// Force piece placement
|
||||||
on_piece_placed.write(OnPiecePlaced { piece: entity });
|
// on_piece_placed.write(OnPiecePlaced { piece: entity });
|
||||||
|
commands.trigger_targets(OnPiecePlaced { piece: entity }, parent.parent());
|
||||||
*input_repeat = None;
|
*input_repeat = None;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -148,58 +156,184 @@ pub fn apply_piece_movement(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_piece_placed(
|
pub fn handle_placed_piece(trigger: Trigger<OnPiecePlaced>, world: &mut World) {
|
||||||
mut on_piece_placed: EventReader<OnPiecePlaced>,
|
let game_area = trigger.target();
|
||||||
|
let event = *trigger.event();
|
||||||
|
// for game_area in world
|
||||||
|
// .run_system_cached(place_blocks)
|
||||||
|
// .iter()
|
||||||
|
// .flatten()
|
||||||
|
// .cloned()
|
||||||
|
// {
|
||||||
|
world
|
||||||
|
.run_system_cached_with(place_blocks, (event, game_area))
|
||||||
|
.expect("Placing piece");
|
||||||
|
world.flush();
|
||||||
|
|
||||||
|
let cleared_rows = world
|
||||||
|
.run_system_cached_with(check_full_rows, game_area)
|
||||||
|
.expect("Checking for completed lines");
|
||||||
|
if !cleared_rows.is_empty() {
|
||||||
|
world.trigger_targets(
|
||||||
|
OnLinesCleared {
|
||||||
|
lines: cleared_rows,
|
||||||
|
},
|
||||||
|
game_area,
|
||||||
|
);
|
||||||
|
// world
|
||||||
|
// .run_system_cached_with(clear_rows, cleared_rows)
|
||||||
|
// .expect("Clearing full rows");
|
||||||
|
world.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
world
|
||||||
|
.run_system_cached_with(check_lose_condition, game_area)
|
||||||
|
.expect("Checking lose condition");
|
||||||
|
|
||||||
|
world
|
||||||
|
.run_system_cached_with(create_next_piece, game_area)
|
||||||
|
.expect("Creating new piece");
|
||||||
|
world.flush();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// TODO: Finish up clearing the piece / lines
|
||||||
|
// TODO: Lose condition
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a vector of affected game area entities
|
||||||
|
fn place_blocks(
|
||||||
|
// mut piece_placed_events: EventReader<OnPiecePlaced>,
|
||||||
|
In((event, game_area)): In<(OnPiecePlaced, Entity)>,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
parent_query: Query<&ChildOf>,
|
|
||||||
child_query: Query<&Children>,
|
child_query: Query<&Children>,
|
||||||
grid_transform_query: Query<&GridTransform>,
|
grid_transform_query: Query<&GridTransform>,
|
||||||
block_query: Query<&GridTransform, With<Block>>,
|
block_query: Query<&GridTransform, With<Block>>,
|
||||||
mut next_piece_query: Query<(&mut NextPiece, &GameArea)>,
|
|
||||||
) {
|
) {
|
||||||
for OnPiecePlaced { piece } in on_piece_placed.read() {
|
let piece = event.piece;
|
||||||
let piece = *piece;
|
|
||||||
|
|
||||||
let parent = parent_query.get(piece).unwrap().0;
|
// Move blocks from old piece to under game area
|
||||||
let (mut next_piece, game_area) = next_piece_query.get_mut(parent).unwrap();
|
let piece_position = grid_transform_query.get(piece).cloned().unwrap_or_default();
|
||||||
let new_piece = next_piece.take_and_generate();
|
for child in child_query
|
||||||
|
.get(piece)
|
||||||
|
.iter()
|
||||||
|
.flat_map(|children| children.iter())
|
||||||
|
{
|
||||||
|
if let Ok(block_pos) = block_query.get(child) {
|
||||||
|
let global_pos = piece_position.translation + block_pos.translation;
|
||||||
|
commands.entity(child).insert((
|
||||||
|
GridTransform {
|
||||||
|
translation: global_pos,
|
||||||
|
},
|
||||||
|
ChildOf(game_area),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create new piece
|
// Despawn old piece
|
||||||
commands.entity(parent).with_child((
|
commands.entity(piece).despawn();
|
||||||
GridTransform {
|
}
|
||||||
translation: game_area.block_spawn_point(),
|
|
||||||
},
|
|
||||||
ControllablePiece,
|
|
||||||
create_piece(new_piece),
|
|
||||||
));
|
|
||||||
|
|
||||||
// Move blocks from old piece to under game area
|
fn check_full_rows(
|
||||||
let piece_position = grid_transform_query.get(piece).cloned().unwrap_or_default();
|
In(game_area_entity): In<Entity>,
|
||||||
for child in child_query
|
game_area_query: Query<(&GameArea, &Children)>,
|
||||||
.get(piece)
|
block_query: Query<&GridTransform, With<Block>>,
|
||||||
.iter()
|
) -> Vec<YPos> {
|
||||||
.flat_map(|children| children.iter())
|
let mut row_map: HashMap<YPos, HashSet<XPos>> = HashMap::new();
|
||||||
{
|
let (game_area, children) = game_area_query
|
||||||
if let Ok(block_pos) = block_query.get(child) {
|
.get(game_area_entity)
|
||||||
let global_pos = piece_position.translation + block_pos.translation;
|
.expect("Getting GameArea component to check for cleared rows");
|
||||||
commands.entity(child).insert((
|
|
||||||
GridTransform {
|
for child in children.iter() {
|
||||||
translation: global_pos,
|
let pos = match block_query.get(child) {
|
||||||
},
|
Ok(transform) => transform.translation,
|
||||||
ChildOf(parent),
|
_ => continue,
|
||||||
));
|
};
|
||||||
|
if !game_area.is_point_inside(pos) {
|
||||||
|
warn!("Block outside of game bounds! {pos} ({child})");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if !row_map.entry(pos.y).or_default().insert(pos.x) {
|
||||||
|
warn!("Duplicate block at position {pos} ({child})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Y-coordinates of cleared rows
|
||||||
|
let mut cleared_rows = vec![];
|
||||||
|
for (y, row_set) in row_map.iter() {
|
||||||
|
// Detect if the row is full
|
||||||
|
if row_set.len() as i32 == game_area.right_boundary - game_area.left_boundary + 1 {
|
||||||
|
cleared_rows.push(*y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Sort rows from up to down
|
||||||
|
cleared_rows.sort_unstable_by(|a, b| b.cmp(a));
|
||||||
|
cleared_rows
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_lose_condition(In(game_area_entity): In<Entity>) {}
|
||||||
|
|
||||||
|
fn create_next_piece(
|
||||||
|
In(game_area_entity): In<Entity>,
|
||||||
|
mut game_query: Query<(&mut NextPiece, &GameArea)>,
|
||||||
|
mut commands: Commands,
|
||||||
|
) {
|
||||||
|
let (mut next_piece, game_area) = game_query.get_mut(game_area_entity).unwrap();
|
||||||
|
commands.entity(game_area_entity).with_child((
|
||||||
|
GridTransform {
|
||||||
|
translation: game_area.block_spawn_point(),
|
||||||
|
},
|
||||||
|
ControllablePiece,
|
||||||
|
create_piece(next_piece.take_and_generate()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_line_clear(
|
||||||
|
trigger: Trigger<OnLinesCleared>,
|
||||||
|
game_area_query: Query<&Children>,
|
||||||
|
mut block_query: Query<(Entity, &mut GridTransform), With<Block>>,
|
||||||
|
mut commands: Commands,
|
||||||
|
) {
|
||||||
|
let lines = trigger.event().lines.clone();
|
||||||
|
let children = game_area_query
|
||||||
|
.get(trigger.target())
|
||||||
|
.expect("Getting GameArea component to clear rows");
|
||||||
|
|
||||||
|
enum BlockOperation {
|
||||||
|
Nothing,
|
||||||
|
Move(YPos),
|
||||||
|
Remove,
|
||||||
|
}
|
||||||
|
|
||||||
|
let choose_block_operation = |y: YPos| {
|
||||||
|
let mut move_down = 0;
|
||||||
|
for cleared_y in lines.iter() {
|
||||||
|
if y == *cleared_y {
|
||||||
|
return BlockOperation::Remove;
|
||||||
|
} else if y > *cleared_y {
|
||||||
|
move_down += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if move_down == 0 {
|
||||||
|
BlockOperation::Nothing
|
||||||
|
} else {
|
||||||
|
BlockOperation::Move(y - move_down)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Despawn old piece
|
let mut memoized: HashMap<YPos, BlockOperation> = HashMap::new();
|
||||||
commands.entity(piece).despawn();
|
|
||||||
|
|
||||||
// commands
|
for child in children.iter() {
|
||||||
// .get_entity(event.entity)
|
let (block_entity, mut transform) = match block_query.get_mut(child) {
|
||||||
// .unwrap()
|
Ok(transform) => transform,
|
||||||
// .remove::<(PieceControls, ControllablePiece)>();
|
_ => continue,
|
||||||
|
};
|
||||||
// TODO: Finish up clearing the piece / lines
|
let operation = memoized
|
||||||
// TODO: Lose condition
|
.entry(transform.translation.y)
|
||||||
|
.or_insert(choose_block_operation(transform.translation.y));
|
||||||
|
match operation {
|
||||||
|
BlockOperation::Move(new_y) => transform.translation.y = *new_y,
|
||||||
|
BlockOperation::Remove => commands.entity(block_entity).despawn(),
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,15 @@ use crate::util::Vector2I;
|
||||||
|
|
||||||
use super::{grid::*, prefab::*};
|
use super::{grid::*, prefab::*};
|
||||||
|
|
||||||
|
pub type YPos = i32;
|
||||||
|
pub type XPos = i32;
|
||||||
|
|
||||||
#[derive(Component, Clone, Debug, Reflect)]
|
#[derive(Component, Clone, Debug, Reflect)]
|
||||||
#[reflect(Component)]
|
#[reflect(Component)]
|
||||||
#[require(Grid, NextPiece)]
|
#[require(Grid, NextPiece)]
|
||||||
pub struct GameArea {
|
pub struct GameArea {
|
||||||
pub bottom_boundary: i32,
|
pub bottom_boundary: i32,
|
||||||
pub top_boundary: i32,
|
pub kill_height: i32,
|
||||||
pub left_boundary: i32,
|
pub left_boundary: i32,
|
||||||
pub right_boundary: i32,
|
pub right_boundary: i32,
|
||||||
}
|
}
|
||||||
|
|
@ -18,7 +21,7 @@ impl Default for GameArea {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
bottom_boundary: 0,
|
bottom_boundary: 0,
|
||||||
top_boundary: 20,
|
kill_height: 19,
|
||||||
left_boundary: 0,
|
left_boundary: 0,
|
||||||
right_boundary: 9,
|
right_boundary: 9,
|
||||||
}
|
}
|
||||||
|
|
@ -29,16 +32,28 @@ impl GameArea {
|
||||||
pub fn block_spawn_point(&self) -> Vector2I {
|
pub fn block_spawn_point(&self) -> Vector2I {
|
||||||
Vector2I::new(
|
Vector2I::new(
|
||||||
(self.left_boundary + self.right_boundary) / 2,
|
(self.left_boundary + self.right_boundary) / 2,
|
||||||
self.top_boundary + 2,
|
self.kill_height + 2,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn center(&self) -> Vec2 {
|
pub fn center(&self) -> Vec2 {
|
||||||
Vec2 {
|
Vec2 {
|
||||||
x: (self.left_boundary + self.right_boundary) as f32 / 2.0,
|
x: (self.left_boundary + self.right_boundary) as f32 / 2.0,
|
||||||
y: (self.bottom_boundary + self.top_boundary) as f32 / 2.0,
|
y: (self.bottom_boundary + self.kill_height) as f32 / 2.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks if the point is considered inside the game area.
|
||||||
|
/// This basically means the the point is not below the `bottom_boundary` or outside of the
|
||||||
|
/// horizontal boundaries.
|
||||||
|
///
|
||||||
|
/// The game area has no upper boundary, so points above the kill height are still considered
|
||||||
|
/// inside, unless it's beyond any of the other boundaries.
|
||||||
|
pub fn is_point_inside(&self, point: Vector2I) -> bool {
|
||||||
|
point.x >= self.left_boundary
|
||||||
|
&& point.x <= self.right_boundary
|
||||||
|
&& point.y >= self.bottom_boundary
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Debug, Reflect)]
|
#[derive(Component, Debug, Reflect)]
|
||||||
|
|
@ -127,7 +142,12 @@ impl NextPiece {
|
||||||
#[reflect(Component)]
|
#[reflect(Component)]
|
||||||
pub struct Block;
|
pub struct Block;
|
||||||
|
|
||||||
#[derive(Event, Clone, Debug)]
|
#[derive(Event, Clone, Copy, Debug)]
|
||||||
pub struct OnPiecePlaced {
|
pub struct OnPiecePlaced {
|
||||||
pub piece: Entity,
|
pub piece: Entity,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Event, Clone, Debug)]
|
||||||
|
pub struct OnLinesCleared {
|
||||||
|
pub lines: Vec<YPos>,
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue