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
|
**Down arrow:** soft drop
|
||||||
|
|
||||||
**Space bar:** hard 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(PostUpdate, DebugSet.run_if(is_debug_enabled));
|
||||||
app.configure_sets(Last, 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((
|
.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_egui::EguiPlugin::default(),
|
||||||
bevy_inspector_egui::quick::WorldInspectorPlugin::new().run_if(is_debug_enabled),
|
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>()
|
app.add_event::<tetris::OnPiecePlaced>()
|
||||||
|
.add_event::<tetris::OnLinesCleared>()
|
||||||
|
.add_event::<tetris::OnStoredPieceChanged>()
|
||||||
|
.add_event::<tetris::OnNextPieceChanged>()
|
||||||
.register_type::<tetris::GameArea>()
|
.register_type::<tetris::GameArea>()
|
||||||
|
.register_type::<tetris::GameStats>()
|
||||||
.register_type::<tetris::GameGravity>()
|
.register_type::<tetris::GameGravity>()
|
||||||
.register_type::<tetris::BlockSet>()
|
.register_type::<tetris::BlockSet>()
|
||||||
.register_type::<tetris::NextPiece>()
|
.register_type::<tetris::NextPiece>()
|
||||||
.register_type::<tetris::Piece>()
|
.register_type::<tetris::Piece>()
|
||||||
.register_type::<tetris::PieceControls>()
|
.register_type::<tetris::PieceControls>()
|
||||||
.register_type::<tetris::ControllablePiece>()
|
.register_type::<tetris::ControllablePiece>()
|
||||||
|
.register_type::<tetris::SwappedPiece>()
|
||||||
.register_type::<tetris::Gravity>()
|
.register_type::<tetris::Gravity>()
|
||||||
.register_type::<tetris::Block>()
|
.register_type::<tetris::Block>()
|
||||||
.register_type::<prefab::PieceType>()
|
.register_type::<prefab::PieceType>()
|
||||||
|
|
@ -30,6 +35,15 @@ pub fn init(app: &mut App) {
|
||||||
.register_type::<grid::GridTransform>()
|
.register_type::<grid::GridTransform>()
|
||||||
.add_systems(Startup, systems::setup_game_scene)
|
.add_systems(Startup, systems::setup_game_scene)
|
||||||
.add_systems(PreUpdate, (systems::repeat_inputs, systems::apply_gravity))
|
.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);
|
.add_systems(PostUpdate, systems::grid_positioning);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ impl PieceType {
|
||||||
Self::J => [(-1, 0), (-1, 1), (0, 0), (1, 0)],
|
Self::J => [(-1, 0), (-1, 1), (0, 0), (1, 0)],
|
||||||
Self::S => [(-1, 0), (0, 0), (0, 1), (1, 1)],
|
Self::S => [(-1, 0), (0, 0), (0, 1), (1, 1)],
|
||||||
Self::Z => [(-1, 1), (0, 0), (0, 1), (1, 0)],
|
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())
|
.map(|pos| pos.into())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,11 @@ mod game_scene;
|
||||||
mod grid;
|
mod grid;
|
||||||
mod input;
|
mod input;
|
||||||
mod movement;
|
mod movement;
|
||||||
|
mod ui;
|
||||||
|
|
||||||
pub use demo::*;
|
pub use demo::*;
|
||||||
pub use game_scene::*;
|
pub use game_scene::*;
|
||||||
pub use grid::*;
|
pub use grid::*;
|
||||||
pub use input::*;
|
pub use input::*;
|
||||||
pub use movement::*;
|
pub use movement::*;
|
||||||
|
pub use ui::*;
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,15 @@ 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((
|
let mut game_entity = world.spawn((
|
||||||
Name::from("Game scene"),
|
Name::from("Game scene"),
|
||||||
GameGravity::default(),
|
GameGravity::default(),
|
||||||
|
GameStats::default(),
|
||||||
game_area.clone(),
|
game_area.clone(),
|
||||||
next_piece,
|
next_piece,
|
||||||
children![
|
children![
|
||||||
(Transform::from_xyz(-16.0, 88.0, 10.0), DemoCamera2d,),
|
(Transform::from_xyz(36.0, 88.0, 10.0), DemoCamera2d,),
|
||||||
// Create first piece
|
// Create first piece
|
||||||
(
|
(
|
||||||
GridTransform {
|
GridTransform {
|
||||||
|
|
@ -25,13 +26,113 @@ pub fn setup_game_scene(world: &mut World) {
|
||||||
create_piece(starting_piece),
|
create_piece(starting_piece),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
))
|
));
|
||||||
|
|
||||||
|
game_entity
|
||||||
.observe(handle_placed_piece)
|
.observe(handle_placed_piece)
|
||||||
.observe(handle_line_clear);
|
.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();
|
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 game_area = trigger.target();
|
||||||
let event = *trigger.event();
|
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)
|
.run_system_cached_with(create_next_piece, game_area)
|
||||||
.expect("Creating new piece");
|
.expect("Creating new piece");
|
||||||
world.flush();
|
world.flush();
|
||||||
|
|
||||||
// TODO: Lose condition
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rebuild_collisions(
|
fn rebuild_collisions(
|
||||||
|
|
@ -155,7 +254,7 @@ fn check_full_rows(
|
||||||
cleared_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(
|
fn create_next_piece(
|
||||||
In(game_area_entity): In<Entity>,
|
In(game_area_entity): In<Entity>,
|
||||||
|
|
@ -174,13 +273,13 @@ fn create_next_piece(
|
||||||
|
|
||||||
fn handle_line_clear(
|
fn handle_line_clear(
|
||||||
trigger: Trigger<OnLinesCleared>,
|
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 block_query: Query<(Entity, &mut GridTransform), With<Block>>,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
) {
|
) {
|
||||||
let lines = trigger.event().lines.clone();
|
let lines = trigger.event().lines.clone();
|
||||||
let children = game_area_query
|
let (children, maybe_stats) = game_area_query
|
||||||
.get(trigger.target())
|
.get_mut(trigger.target())
|
||||||
.expect("Getting GameArea component to clear rows");
|
.expect("Getting GameArea component to clear rows");
|
||||||
|
|
||||||
enum BlockOperation {
|
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) {
|
if key_input.just_pressed(KeyCode::Space) {
|
||||||
controls.instant_drop = true;
|
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 YPos = i32;
|
||||||
pub type XPos = 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)]
|
#[derive(Component, Clone, Debug, Reflect)]
|
||||||
#[reflect(Component)]
|
#[reflect(Component)]
|
||||||
#[require(Grid, NextPiece, BlockSet)]
|
#[require(Grid, NextPiece, BlockSet, GameStats)]
|
||||||
pub struct GameArea {
|
pub struct GameArea {
|
||||||
pub bottom_boundary: i32,
|
pub bottom_boundary: i32,
|
||||||
pub kill_height: 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)]
|
#[derive(Component, Debug, Reflect)]
|
||||||
#[reflect(Component)]
|
#[reflect(Component)]
|
||||||
pub struct GameGravity {
|
pub struct GameGravity {
|
||||||
|
|
@ -122,6 +128,7 @@ pub struct PieceControls {
|
||||||
pub fast_drop: bool,
|
pub fast_drop: bool,
|
||||||
pub movement: Option<input::Move>,
|
pub movement: Option<input::Move>,
|
||||||
pub rotation: Option<input::Rotation>,
|
pub rotation: Option<input::Rotation>,
|
||||||
|
pub store: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Debug, Reflect)]
|
#[derive(Component, Debug, Reflect)]
|
||||||
|
|
@ -129,12 +136,23 @@ pub struct PieceControls {
|
||||||
#[require(PieceControls)]
|
#[require(PieceControls)]
|
||||||
pub struct ControllablePiece;
|
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)]
|
#[derive(Component, Clone, Debug, Reflect)]
|
||||||
#[reflect(Component)]
|
#[reflect(Component)]
|
||||||
pub struct NextPiece {
|
pub struct NextPiece {
|
||||||
pub piece: PieceType,
|
pub piece: PieceType,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Clone, Debug, Reflect)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub struct StoredPiece {
|
||||||
|
pub piece: PieceType,
|
||||||
|
}
|
||||||
|
|
||||||
impl Default for NextPiece {
|
impl Default for NextPiece {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|
@ -168,3 +186,13 @@ pub struct OnPiecePlaced {
|
||||||
pub struct OnLinesCleared {
|
pub struct OnLinesCleared {
|
||||||
pub lines: Vec<YPos>,
|
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