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),
|
||||
UVec2::new(
|
||||
(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,
|
||||
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(PreUpdate, (systems::block_control, systems::apply_gravity))
|
||||
.add_systems(Update, (systems::demo_2d, systems::apply_piece_movement))
|
||||
.add_systems(
|
||||
PostUpdate,
|
||||
(systems::handle_piece_placed, systems::grid_positioning).chain(),
|
||||
);
|
||||
.add_systems(PostUpdate, systems::grid_positioning);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::game::{
|
||||
|
|
@ -10,7 +12,8 @@ pub fn setup_game_scene(world: &mut World) {
|
|||
let game_area = GameArea::default();
|
||||
let mut next_piece = NextPiece::default();
|
||||
let starting_piece = next_piece.take_and_generate();
|
||||
world.spawn((
|
||||
world
|
||||
.spawn((
|
||||
Name::from("Game scene"),
|
||||
GameGravity::default(),
|
||||
game_area.clone(),
|
||||
|
|
@ -26,7 +29,9 @@ pub fn setup_game_scene(world: &mut World) {
|
|||
create_piece(starting_piece),
|
||||
),
|
||||
],
|
||||
));
|
||||
))
|
||||
.observe(handle_placed_piece)
|
||||
.observe(handle_line_clear);
|
||||
world.flush();
|
||||
}
|
||||
|
||||
|
|
@ -85,8 +90,9 @@ pub fn apply_piece_movement(
|
|||
mut piece_query: Query<(&mut PieceControls, Entity, &ChildOf, &Children)>,
|
||||
mut transform_query: Query<(&mut GridTransform, Has<Block>)>,
|
||||
mut input_repeat: Local<Option<InputRepeatState>>,
|
||||
mut on_piece_placed: EventWriter<OnPiecePlaced>,
|
||||
// mut on_piece_placed: EventWriter<OnPiecePlaced>,
|
||||
time: Res<Time>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
// TODO: Handle piece collision
|
||||
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
|
||||
if piece_controls.instant_drop {
|
||||
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;
|
||||
continue;
|
||||
}
|
||||
|
|
@ -105,7 +112,8 @@ pub fn apply_piece_movement(
|
|||
}
|
||||
if !can_move_down && piece_controls.cumulated_gravity >= 3.0 {
|
||||
// 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;
|
||||
continue;
|
||||
}
|
||||
|
|
@ -148,30 +156,60 @@ pub fn apply_piece_movement(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn handle_piece_placed(
|
||||
mut on_piece_placed: EventReader<OnPiecePlaced>,
|
||||
pub fn handle_placed_piece(trigger: Trigger<OnPiecePlaced>, world: &mut World) {
|
||||
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,
|
||||
parent_query: Query<&ChildOf>,
|
||||
child_query: Query<&Children>,
|
||||
grid_transform_query: Query<&GridTransform>,
|
||||
block_query: Query<&GridTransform, With<Block>>,
|
||||
mut next_piece_query: Query<(&mut NextPiece, &GameArea)>,
|
||||
) {
|
||||
for OnPiecePlaced { piece } in on_piece_placed.read() {
|
||||
let piece = *piece;
|
||||
|
||||
let parent = parent_query.get(piece).unwrap().0;
|
||||
let (mut next_piece, game_area) = next_piece_query.get_mut(parent).unwrap();
|
||||
let new_piece = next_piece.take_and_generate();
|
||||
|
||||
// Create new piece
|
||||
commands.entity(parent).with_child((
|
||||
GridTransform {
|
||||
translation: game_area.block_spawn_point(),
|
||||
},
|
||||
ControllablePiece,
|
||||
create_piece(new_piece),
|
||||
));
|
||||
let piece = event.piece;
|
||||
|
||||
// Move blocks from old piece to under game area
|
||||
let piece_position = grid_transform_query.get(piece).cloned().unwrap_or_default();
|
||||
|
|
@ -186,20 +224,116 @@ pub fn handle_piece_placed(
|
|||
GridTransform {
|
||||
translation: global_pos,
|
||||
},
|
||||
ChildOf(parent),
|
||||
ChildOf(game_area),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Despawn old piece
|
||||
commands.entity(piece).despawn();
|
||||
}
|
||||
|
||||
// commands
|
||||
// .get_entity(event.entity)
|
||||
// .unwrap()
|
||||
// .remove::<(PieceControls, ControllablePiece)>();
|
||||
fn check_full_rows(
|
||||
In(game_area_entity): In<Entity>,
|
||||
game_area_query: Query<(&GameArea, &Children)>,
|
||||
block_query: Query<&GridTransform, With<Block>>,
|
||||
) -> Vec<YPos> {
|
||||
let mut row_map: HashMap<YPos, HashSet<XPos>> = HashMap::new();
|
||||
let (game_area, children) = game_area_query
|
||||
.get(game_area_entity)
|
||||
.expect("Getting GameArea component to check for cleared rows");
|
||||
|
||||
// TODO: Finish up clearing the piece / lines
|
||||
// TODO: Lose condition
|
||||
for child in children.iter() {
|
||||
let pos = match block_query.get(child) {
|
||||
Ok(transform) => transform.translation,
|
||||
_ => 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)
|
||||
}
|
||||
};
|
||||
|
||||
let mut memoized: HashMap<YPos, BlockOperation> = HashMap::new();
|
||||
|
||||
for child in children.iter() {
|
||||
let (block_entity, mut transform) = match block_query.get_mut(child) {
|
||||
Ok(transform) => transform,
|
||||
_ => continue,
|
||||
};
|
||||
let operation = memoized
|
||||
.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::*};
|
||||
|
||||
pub type YPos = i32;
|
||||
pub type XPos = i32;
|
||||
|
||||
#[derive(Component, Clone, Debug, Reflect)]
|
||||
#[reflect(Component)]
|
||||
#[require(Grid, NextPiece)]
|
||||
pub struct GameArea {
|
||||
pub bottom_boundary: i32,
|
||||
pub top_boundary: i32,
|
||||
pub kill_height: i32,
|
||||
pub left_boundary: i32,
|
||||
pub right_boundary: i32,
|
||||
}
|
||||
|
|
@ -18,7 +21,7 @@ impl Default for GameArea {
|
|||
fn default() -> Self {
|
||||
Self {
|
||||
bottom_boundary: 0,
|
||||
top_boundary: 20,
|
||||
kill_height: 19,
|
||||
left_boundary: 0,
|
||||
right_boundary: 9,
|
||||
}
|
||||
|
|
@ -29,16 +32,28 @@ impl GameArea {
|
|||
pub fn block_spawn_point(&self) -> Vector2I {
|
||||
Vector2I::new(
|
||||
(self.left_boundary + self.right_boundary) / 2,
|
||||
self.top_boundary + 2,
|
||||
self.kill_height + 2,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn center(&self) -> Vec2 {
|
||||
Vec2 {
|
||||
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)]
|
||||
|
|
@ -127,7 +142,12 @@ impl NextPiece {
|
|||
#[reflect(Component)]
|
||||
pub struct Block;
|
||||
|
||||
#[derive(Event, Clone, Debug)]
|
||||
#[derive(Event, Clone, Copy, Debug)]
|
||||
pub struct OnPiecePlaced {
|
||||
pub piece: Entity,
|
||||
}
|
||||
|
||||
#[derive(Event, Clone, Debug)]
|
||||
pub struct OnLinesCleared {
|
||||
pub lines: Vec<YPos>,
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue