generated from hheik/bevy-template
Added piece swapping and an indicator for the next piece
parent
c4275f90be
commit
56b99d9c9a
|
|
@ -25,3 +25,5 @@ cargo build
|
|||
**Down arrow:** soft drop
|
||||
|
||||
**Space bar:** hard drop
|
||||
|
||||
**C:** swap piece
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
# TODO
|
||||
|
||||
- Lose condition
|
||||
- Speedup
|
||||
- GUI
|
||||
- Background
|
||||
- Indicators for next/stored piece
|
||||
- Cleared lines counter
|
||||
|
|
@ -13,9 +13,9 @@ impl Plugin for DebugPlugin {
|
|||
app.configure_sets(PostUpdate, DebugSet.run_if(is_debug_enabled));
|
||||
app.configure_sets(Last, DebugSet.run_if(is_debug_enabled));
|
||||
|
||||
app.insert_resource(DebugMode::on())
|
||||
app.insert_resource(DebugMode::off())
|
||||
.add_plugins((
|
||||
// // WorldInspector requires EguiPlugin plugin to be added before it
|
||||
// WorldInspector requires EguiPlugin plugin to be added before it
|
||||
bevy_egui::EguiPlugin::default(),
|
||||
bevy_inspector_egui::quick::WorldInspectorPlugin::new().run_if(is_debug_enabled),
|
||||
))
|
||||
|
|
|
|||
16
src/game.rs
16
src/game.rs
|
|
@ -16,13 +16,18 @@ pub fn init(app: &mut App) {
|
|||
));
|
||||
|
||||
app.add_event::<tetris::OnPiecePlaced>()
|
||||
.add_event::<tetris::OnLinesCleared>()
|
||||
.add_event::<tetris::OnStoredPieceChanged>()
|
||||
.add_event::<tetris::OnNextPieceChanged>()
|
||||
.register_type::<tetris::GameArea>()
|
||||
.register_type::<tetris::GameStats>()
|
||||
.register_type::<tetris::GameGravity>()
|
||||
.register_type::<tetris::BlockSet>()
|
||||
.register_type::<tetris::NextPiece>()
|
||||
.register_type::<tetris::Piece>()
|
||||
.register_type::<tetris::PieceControls>()
|
||||
.register_type::<tetris::ControllablePiece>()
|
||||
.register_type::<tetris::SwappedPiece>()
|
||||
.register_type::<tetris::Gravity>()
|
||||
.register_type::<tetris::Block>()
|
||||
.register_type::<prefab::PieceType>()
|
||||
|
|
@ -30,6 +35,15 @@ pub fn init(app: &mut App) {
|
|||
.register_type::<grid::GridTransform>()
|
||||
.add_systems(Startup, systems::setup_game_scene)
|
||||
.add_systems(PreUpdate, (systems::repeat_inputs, systems::apply_gravity))
|
||||
.add_systems(Update, (systems::demo_2d, systems::apply_piece_movement))
|
||||
.add_systems(
|
||||
Update,
|
||||
(
|
||||
systems::demo_2d,
|
||||
systems::store_piece,
|
||||
systems::apply_piece_movement,
|
||||
systems::trigger_on_next_piece_changed,
|
||||
systems::trigger_on_stored_piece_changed,
|
||||
),
|
||||
)
|
||||
.add_systems(PostUpdate, systems::grid_positioning);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ impl PieceType {
|
|||
Self::J => [(-1, 0), (-1, 1), (0, 0), (1, 0)],
|
||||
Self::S => [(-1, 0), (0, 0), (0, 1), (1, 1)],
|
||||
Self::Z => [(-1, 1), (0, 0), (0, 1), (1, 0)],
|
||||
Self::Square => [(0, 0), (0, 1), (1, 0), (1, 1)],
|
||||
Self::Square => [(-1, 0), (-1, 1), (0, 0), (0, 1)],
|
||||
}
|
||||
.map(|pos| pos.into())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,11 @@ mod game_scene;
|
|||
mod grid;
|
||||
mod input;
|
||||
mod movement;
|
||||
mod ui;
|
||||
|
||||
pub use demo::*;
|
||||
pub use game_scene::*;
|
||||
pub use grid::*;
|
||||
pub use input::*;
|
||||
pub use movement::*;
|
||||
pub use ui::*;
|
||||
|
|
|
|||
|
|
@ -8,30 +8,131 @@ 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((
|
||||
Name::from("Game scene"),
|
||||
GameGravity::default(),
|
||||
game_area.clone(),
|
||||
next_piece,
|
||||
children![
|
||||
(Transform::from_xyz(-16.0, 88.0, 10.0), DemoCamera2d,),
|
||||
// Create first piece
|
||||
(
|
||||
GridTransform {
|
||||
translation: game_area.block_spawn_point()
|
||||
},
|
||||
ControllablePiece,
|
||||
create_piece(starting_piece),
|
||||
),
|
||||
],
|
||||
))
|
||||
|
||||
let mut game_entity = world.spawn((
|
||||
Name::from("Game scene"),
|
||||
GameGravity::default(),
|
||||
GameStats::default(),
|
||||
game_area.clone(),
|
||||
next_piece,
|
||||
children![
|
||||
(Transform::from_xyz(36.0, 88.0, 10.0), DemoCamera2d,),
|
||||
// Create first piece
|
||||
(
|
||||
GridTransform {
|
||||
translation: game_area.block_spawn_point()
|
||||
},
|
||||
ControllablePiece,
|
||||
create_piece(starting_piece),
|
||||
),
|
||||
],
|
||||
));
|
||||
|
||||
game_entity
|
||||
.observe(handle_placed_piece)
|
||||
.observe(handle_line_clear);
|
||||
|
||||
// Next piece indicator
|
||||
game_entity.with_child((
|
||||
Name::from("Next piece indicator"),
|
||||
Grid::default(),
|
||||
GridTransform::from_xy(13, 18),
|
||||
Observer::new(update_next_piece_ui).with_entity(game_entity.id()),
|
||||
));
|
||||
|
||||
// Stored piece indicator
|
||||
game_entity.with_child((
|
||||
Name::from("Stored piece indicator"),
|
||||
Grid::default(),
|
||||
GridTransform::from_xy(-4, 18),
|
||||
Observer::new(update_stored_piece_ui).with_entity(game_entity.id()),
|
||||
));
|
||||
|
||||
world.flush();
|
||||
}
|
||||
|
||||
pub fn handle_placed_piece(trigger: Trigger<OnPiecePlaced>, world: &mut World) {
|
||||
pub fn store_piece(
|
||||
mut commands: Commands,
|
||||
piece_query: Query<(Entity, &PieceControls, &PieceType, &ChildOf), Without<SwappedPiece>>,
|
||||
mut store_query: Query<(Option<&mut StoredPiece>, &GameArea)>,
|
||||
mut next_piece_query: Query<&mut NextPiece>,
|
||||
) {
|
||||
for (old_entity, controls, piece, parent) in piece_query.iter() {
|
||||
if controls.store
|
||||
&& let Ok((maybes_stored_piece, game_area)) = store_query.get_mut(parent.parent())
|
||||
{
|
||||
let mut game = commands.entity(parent.parent());
|
||||
match maybes_stored_piece {
|
||||
Some(mut stored_piece) => {
|
||||
game.with_child((
|
||||
GridTransform {
|
||||
translation: game_area.block_spawn_point(),
|
||||
},
|
||||
ControllablePiece,
|
||||
SwappedPiece,
|
||||
create_piece(stored_piece.piece),
|
||||
));
|
||||
stored_piece.piece = *piece;
|
||||
commands.entity(old_entity).despawn();
|
||||
}
|
||||
None => {
|
||||
if let Ok(mut next_piece) = next_piece_query.get_mut(parent.parent()) {
|
||||
game.insert(StoredPiece { piece: *piece });
|
||||
game.with_child((
|
||||
GridTransform {
|
||||
translation: game_area.block_spawn_point(),
|
||||
},
|
||||
ControllablePiece,
|
||||
SwappedPiece,
|
||||
create_piece(next_piece.take_and_generate()),
|
||||
));
|
||||
commands.entity(old_entity).despawn();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_next_piece_ui(
|
||||
trigger: Trigger<OnNextPieceChanged>,
|
||||
mut commands: Commands,
|
||||
parent_query: Query<&ChildOf>,
|
||||
next_piece_query: Query<&NextPiece>,
|
||||
) {
|
||||
if let Ok(mut indicator) = commands.get_entity(trigger.observer()) {
|
||||
// Despawn children recursively
|
||||
indicator.despawn_related::<Children>();
|
||||
|
||||
if let Ok(next_piece) = parent_query
|
||||
.get(indicator.id())
|
||||
.and_then(|p| next_piece_query.get(p.parent()))
|
||||
{
|
||||
indicator.with_child((Grid::default(), create_piece(next_piece.piece)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_stored_piece_ui(
|
||||
trigger: Trigger<OnStoredPieceChanged>,
|
||||
mut commands: Commands,
|
||||
parent_query: Query<&ChildOf>,
|
||||
stored_piece_query: Query<&StoredPiece>,
|
||||
) {
|
||||
if let Ok(mut indicator) = commands.get_entity(trigger.observer()) {
|
||||
// Despawn children recursively
|
||||
indicator.despawn_related::<Children>();
|
||||
|
||||
if let Ok(stored_piece) = parent_query
|
||||
.get(indicator.id())
|
||||
.and_then(|p| stored_piece_query.get(p.parent()))
|
||||
{
|
||||
indicator.with_child((Grid::default(), create_piece(stored_piece.piece)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_placed_piece(trigger: Trigger<OnPiecePlaced>, world: &mut World) {
|
||||
let game_area = trigger.target();
|
||||
let event = *trigger.event();
|
||||
|
||||
|
|
@ -65,8 +166,6 @@ pub fn handle_placed_piece(trigger: Trigger<OnPiecePlaced>, world: &mut World) {
|
|||
.run_system_cached_with(create_next_piece, game_area)
|
||||
.expect("Creating new piece");
|
||||
world.flush();
|
||||
|
||||
// TODO: Lose condition
|
||||
}
|
||||
|
||||
fn rebuild_collisions(
|
||||
|
|
@ -155,7 +254,7 @@ fn check_full_rows(
|
|||
cleared_rows
|
||||
}
|
||||
|
||||
fn check_lose_condition(In(game_area_entity): In<Entity>) {}
|
||||
fn check_lose_condition(In(_game_area_entity): In<Entity>) {}
|
||||
|
||||
fn create_next_piece(
|
||||
In(game_area_entity): In<Entity>,
|
||||
|
|
@ -174,13 +273,13 @@ fn create_next_piece(
|
|||
|
||||
fn handle_line_clear(
|
||||
trigger: Trigger<OnLinesCleared>,
|
||||
game_area_query: Query<&Children>,
|
||||
mut game_area_query: Query<(&Children, Option<&mut GameStats>)>,
|
||||
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())
|
||||
let (children, maybe_stats) = game_area_query
|
||||
.get_mut(trigger.target())
|
||||
.expect("Getting GameArea component to clear rows");
|
||||
|
||||
enum BlockOperation {
|
||||
|
|
@ -221,4 +320,8 @@ fn handle_line_clear(
|
|||
_ => (),
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(mut stats) = maybe_stats {
|
||||
stats.lines_cleared += lines.len() as i32;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,5 +49,9 @@ pub fn repeat_inputs(
|
|||
if key_input.just_pressed(KeyCode::Space) {
|
||||
controls.instant_drop = true;
|
||||
}
|
||||
|
||||
if key_input.just_pressed(KeyCode::KeyC) {
|
||||
controls.store = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
use bevy::prelude::*;
|
||||
|
||||
use crate::game::tetris::*;
|
||||
|
||||
pub fn trigger_on_next_piece_changed(
|
||||
mut commands: Commands,
|
||||
next_piece_query: Query<(Entity, &NextPiece), Changed<NextPiece>>,
|
||||
) {
|
||||
for (entity, changed) in next_piece_query.iter() {
|
||||
commands.trigger_targets(
|
||||
OnNextPieceChanged {
|
||||
piece: changed.piece,
|
||||
},
|
||||
entity,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn trigger_on_stored_piece_changed(
|
||||
mut commands: Commands,
|
||||
next_piece_query: Query<(Entity, &StoredPiece), Changed<StoredPiece>>,
|
||||
) {
|
||||
for (entity, changed) in next_piece_query.iter() {
|
||||
commands.trigger_targets(
|
||||
OnStoredPieceChanged {
|
||||
piece: changed.piece,
|
||||
},
|
||||
entity,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -11,32 +11,9 @@ pub mod input;
|
|||
pub type YPos = i32;
|
||||
pub type XPos = i32;
|
||||
|
||||
#[derive(Component, Debug, Default, Reflect)]
|
||||
#[reflect(Component)]
|
||||
pub struct BlockSet {
|
||||
blocks: HashSet<Vector2I>,
|
||||
}
|
||||
|
||||
impl BlockSet {
|
||||
pub fn new(blocks: HashSet<Vector2I>) -> Self {
|
||||
Self { blocks }
|
||||
}
|
||||
|
||||
pub fn cast_point(&self, pos: &Vector2I) -> bool {
|
||||
self.blocks.contains(pos)
|
||||
}
|
||||
|
||||
pub fn cast_shape(&self, shape_pos: Vector2I, local_blocks: &[Vector2I]) -> bool {
|
||||
local_blocks
|
||||
.iter()
|
||||
.map(|local_pos| shape_pos + *local_pos)
|
||||
.any(|global_pos| self.cast_point(&global_pos))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Clone, Debug, Reflect)]
|
||||
#[reflect(Component)]
|
||||
#[require(Grid, NextPiece, BlockSet)]
|
||||
#[require(Grid, NextPiece, BlockSet, GameStats)]
|
||||
pub struct GameArea {
|
||||
pub bottom_boundary: i32,
|
||||
pub kill_height: i32,
|
||||
|
|
@ -90,6 +67,35 @@ impl GameArea {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Debug, Default, Reflect)]
|
||||
#[reflect(Component)]
|
||||
pub struct GameStats {
|
||||
pub lines_cleared: i32,
|
||||
}
|
||||
|
||||
#[derive(Component, Debug, Default, Reflect)]
|
||||
#[reflect(Component)]
|
||||
pub struct BlockSet {
|
||||
blocks: HashSet<Vector2I>,
|
||||
}
|
||||
|
||||
impl BlockSet {
|
||||
pub fn new(blocks: HashSet<Vector2I>) -> Self {
|
||||
Self { blocks }
|
||||
}
|
||||
|
||||
pub fn cast_point(&self, pos: &Vector2I) -> bool {
|
||||
self.blocks.contains(pos)
|
||||
}
|
||||
|
||||
pub fn cast_shape(&self, shape_pos: Vector2I, local_blocks: &[Vector2I]) -> bool {
|
||||
local_blocks
|
||||
.iter()
|
||||
.map(|local_pos| shape_pos + *local_pos)
|
||||
.any(|global_pos| self.cast_point(&global_pos))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Debug, Reflect)]
|
||||
#[reflect(Component)]
|
||||
pub struct GameGravity {
|
||||
|
|
@ -122,6 +128,7 @@ pub struct PieceControls {
|
|||
pub fast_drop: bool,
|
||||
pub movement: Option<input::Move>,
|
||||
pub rotation: Option<input::Rotation>,
|
||||
pub store: bool,
|
||||
}
|
||||
|
||||
#[derive(Component, Debug, Reflect)]
|
||||
|
|
@ -129,12 +136,23 @@ pub struct PieceControls {
|
|||
#[require(PieceControls)]
|
||||
pub struct ControllablePiece;
|
||||
|
||||
/// Indicates that the piece was already swapped, so it can't be swapped again.
|
||||
#[derive(Component, Debug, Reflect)]
|
||||
#[reflect(Component)]
|
||||
pub struct SwappedPiece;
|
||||
|
||||
#[derive(Component, Clone, Debug, Reflect)]
|
||||
#[reflect(Component)]
|
||||
pub struct NextPiece {
|
||||
pub piece: PieceType,
|
||||
}
|
||||
|
||||
#[derive(Component, Clone, Debug, Reflect)]
|
||||
#[reflect(Component)]
|
||||
pub struct StoredPiece {
|
||||
pub piece: PieceType,
|
||||
}
|
||||
|
||||
impl Default for NextPiece {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
|
|
@ -168,3 +186,13 @@ pub struct OnPiecePlaced {
|
|||
pub struct OnLinesCleared {
|
||||
pub lines: Vec<YPos>,
|
||||
}
|
||||
|
||||
#[derive(Event, Clone, Debug)]
|
||||
pub struct OnNextPieceChanged {
|
||||
pub piece: PieceType,
|
||||
}
|
||||
|
||||
#[derive(Event, Clone, Debug)]
|
||||
pub struct OnStoredPieceChanged {
|
||||
pub piece: PieceType,
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue