From 946bcfb2460c84c0440658184e1394c5a859acf9 Mon Sep 17 00:00:00 2001 From: hheik <4469778+hheik@users.noreply.github.com> Date: Wed, 20 Aug 2025 13:24:13 +0300 Subject: [PATCH] Piece spawning and game area --- Cargo.lock | 1 + Cargo.toml | 3 +- assets/sprites/block_base.png | Bin 0 -> 99 bytes assets/sprites/block_highlight.png | Bin 0 -> 95 bytes src/debug.rs | 17 ++- src/debug/systems.rs | 3 + src/debug/systems/game_scene.rs | 19 ++++ src/game.rs | 26 ++++- src/game/grid.rs | 34 ++++++ src/game/prefab.rs | 145 +++++++++++++++++++++--- src/game/systems.rs | 4 + src/game/systems/demo.rs | 104 ----------------- src/game/systems/game_scene.rs | 176 +++++++++++++++++++++++++++++ src/game/systems/grid.rs | 19 ++++ src/game/tetris.rs | 133 ++++++++++++++++++++++ src/game_setup.rs | 6 +- src/util/vector2.rs | 16 ++- src/util/vector2_i32.rs | 9 ++ 18 files changed, 578 insertions(+), 137 deletions(-) create mode 100644 assets/sprites/block_base.png create mode 100644 assets/sprites/block_highlight.png create mode 100644 src/debug/systems.rs create mode 100644 src/debug/systems/game_scene.rs create mode 100644 src/game/grid.rs create mode 100644 src/game/systems/game_scene.rs create mode 100644 src/game/systems/grid.rs create mode 100644 src/game/tetris.rs diff --git a/Cargo.lock b/Cargo.lock index 6158930..643a0d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -437,6 +437,7 @@ dependencies = [ "bevy_egui", "bevy_mod_debugdump", "bevy_prototype_lyon", + "fastrand", "num-traits", ] diff --git a/Cargo.toml b/Cargo.toml index 3c30be6..9789161 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bevy-template" version = "0.1.0" -edition = "2021" +edition = "2024" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -11,6 +11,7 @@ bevy-inspector-egui = "0.32.0" bevy_egui = "0.35.1" bevy_mod_debugdump = "0.13.0" bevy_prototype_lyon = "0.13.0" +fastrand = "2.3.0" num-traits = "0.2.19" # Enable a small amount of optimization in debug mode diff --git a/assets/sprites/block_base.png b/assets/sprites/block_base.png new file mode 100644 index 0000000000000000000000000000000000000000..abd07f29e7779810f5a159e9f7412dbcd5b29a31 GIT binary patch literal 99 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqjKx9jP7LeL$-D$|WIbIRLnNjq t|M~y_e?6N_!$ct9oUX#d!^z6R$RKx#g>Ox+s^e?6Pi1ACDNp$-?O9OPm6eVG5_53B93fr=PBUHx3vIVCg!08F+WOaK4? literal 0 HcmV?d00001 diff --git a/src/debug.rs b/src/debug.rs index bfe0c49..8eeb9bb 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -1,5 +1,7 @@ use bevy::prelude::*; +mod systems; + pub struct DebugPlugin; #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] @@ -7,6 +9,8 @@ pub struct DebugSet; impl Plugin for DebugPlugin { fn build(&self, app: &mut App) { + app.configure_sets(Update, 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.insert_resource(DebugMode::on()) @@ -15,7 +19,8 @@ impl Plugin for DebugPlugin { bevy_egui::EguiPlugin::default(), bevy_inspector_egui::quick::WorldInspectorPlugin::new().run_if(is_debug_enabled), )) - .add_systems(Update, debug_toggle); + .add_systems(Update, debug_toggle) + .add_systems(Update, systems::game_area_gizmos); } } @@ -39,8 +44,16 @@ pub fn is_debug_enabled(debug_mode: Res) -> bool { debug_mode.enabled } -fn debug_toggle(input: Res>, mut debug_mode: ResMut) { +fn debug_toggle( + input: Res>, + mut debug_mode: ResMut, + mut config_store: ResMut, +) { if input.just_pressed(KeyCode::KeyP) { debug_mode.enabled = !debug_mode.enabled } + if input.just_pressed(KeyCode::KeyO) { + let (config, _) = config_store.config_mut::(); + config.enabled ^= true; + } } diff --git a/src/debug/systems.rs b/src/debug/systems.rs new file mode 100644 index 0000000..e8b7e37 --- /dev/null +++ b/src/debug/systems.rs @@ -0,0 +1,3 @@ +mod game_scene; + +pub use game_scene::*; diff --git a/src/debug/systems/game_scene.rs b/src/debug/systems/game_scene.rs new file mode 100644 index 0000000..3efa92c --- /dev/null +++ b/src/debug/systems/game_scene.rs @@ -0,0 +1,19 @@ +use bevy::prelude::*; + +use crate::game::{grid::*, tetris::*}; + +pub fn game_area_gizmos(mut gizmos: Gizmos, game_area_query: Query<(&GameArea, &Grid)>) { + for (game_area, grid) in game_area_query.iter() { + gizmos + .grid_2d( + 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, + ), + grid.tile_size, + Color::srgba(0.4, 0.1, 0.1, 0.6), + ) + .outer_edges(); + } +} diff --git a/src/game.rs b/src/game.rs index af6ff24..0f4e911 100644 --- a/src/game.rs +++ b/src/game.rs @@ -1,8 +1,12 @@ use crate::{debug, game_setup, util}; use bevy::prelude::*; -mod prefab; +pub mod grid; +pub mod prefab; mod systems; +pub mod tetris; + +pub const PIXEL_SCALE: f32 = 3.0; pub fn init(app: &mut App) { let app = app.add_plugins(( @@ -11,9 +15,19 @@ pub fn init(app: &mut App) { debug::DebugPlugin, )); - // app.add_systems(Startup, systems::setup_demo_2d) - // .add_systems(Update, systems::demo_2d); - - app.add_systems(Startup, systems::setup_demo_3d) - .add_systems(Update, systems::demo_3d); + app.add_event::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .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(), + ); } diff --git a/src/game/grid.rs b/src/game/grid.rs new file mode 100644 index 0000000..a625ad6 --- /dev/null +++ b/src/game/grid.rs @@ -0,0 +1,34 @@ +use bevy::prelude::*; + +use crate::util::Vector2I; + +#[derive(Component, Clone, Copy, Debug, Reflect)] +#[reflect(Component)] +#[require(Transform)] +#[require(Visibility)] +pub struct Grid { + pub tile_size: Vec2, +} + +impl Default for Grid { + fn default() -> Self { + Self { + tile_size: Vec2::splat(8.0), + } + } +} + +#[derive(Component, Clone, Copy, Debug, Default, Reflect)] +#[reflect(Component)] +#[require(Transform)] +pub struct GridTransform { + pub translation: Vector2I, +} + +impl GridTransform { + pub fn from_xy(x: i32, y: i32) -> Self { + Self { + translation: Vector2I::new(x, y), + } + } +} diff --git a/src/game/prefab.rs b/src/game/prefab.rs index c78b405..f228d9f 100644 --- a/src/game/prefab.rs +++ b/src/game/prefab.rs @@ -1,30 +1,143 @@ -use bevy::pbr::Atmosphere; use bevy::prelude::*; +use crate::util::{SpriteLoader, Vector2I}; + +use super::{PIXEL_SCALE, grid::*, tetris::*}; + #[derive(Clone, Debug, Default, Component, Reflect)] #[reflect(Component)] #[require( Name = Name::from("2D Camera"), Camera2d, + Projection = Projection::Orthographic(OrthographicProjection { + scale: 1.0 / PIXEL_SCALE, + ..OrthographicProjection::default_2d() + }), Transform = Transform::from_xyz(0., 0., 10.), )] pub struct DemoCamera2d; -#[derive(Clone, Debug, Default, Component, Reflect)] -#[reflect(Component)] -#[require( - Name = Name::from("3D Camera"), - // Atmosphere causes a panic if hdr is not true: https://github.com/bevyengine/bevy/issues/18959 - Camera = Camera { - hdr: true, - ..default() - }, - Camera3d, - Atmosphere = Atmosphere::EARTH, - Transform = Transform::from_xyz(1., 2., 10.).looking_at(Vec3::ZERO, Dir3::Y), - PointLight, -)] -pub struct DemoCamera3d; +#[derive(Clone, Copy, Debug, Reflect)] +pub enum PieceType { + /// ...... + /// .XXXX. + I, + /// ..X.. + /// .XXX. + T, + /// ...X. + /// .XXX. + L, + /// .X... + /// .XXX. + J, + /// ..XX. + /// .XX.. + S, + /// .XX.. + /// ..XX. + Z, + /// .XX. + /// .XX. + Square, +} +impl PieceType { + pub fn color(&self) -> Color { + match self { + Self::I => Color::srgb(1.0, 0.0, 1.0), + Self::T => Color::srgb(0.0, 1.0, 1.0), + Self::L => Color::srgb(0.0, 0.0, 1.0), + Self::J => Color::srgb(1.0, 0.5, 0.0), + Self::S => Color::srgb(1.0, 0.0, 0.0), + Self::Z => Color::srgb(0.0, 0.75, 0.0), + Self::Square => Color::srgb(0.5, 0.5, 1.0), + } + } + pub fn block_positions(&self) -> [Vector2I; 4] { + match self { + Self::I => [(0, 0), (1, 0), (2, 0), (3, 0)], + Self::T => [(0, 0), (1, 0), (1, 1), (2, 0)], + Self::L => [(0, 0), (1, 0), (2, 0), (2, 1)], + Self::J => [(0, 0), (0, 1), (1, 0), (2, 0)], + Self::S => [(0, 0), (1, 0), (1, 1), (2, 1)], + Self::Z => [(1, 0), (1, 1), (0, 1), (2, 0)], + Self::Square => [(0, 0), (0, 1), (1, 0), (1, 1)], + } + .map(|pos| pos.into()) + } + // pub fn width(&self) -> i32 { + // let x_positions: Vec<_> = self.block_positions().iter().map(|pos| pos.x).collect(); + // if x_positions.is_empty() { + // return 0; + // } + // let min = x_positions.iter().min().unwrap(); + // let max = x_positions.iter().max().unwrap(); + // max - min + 1 + // } + + pub fn name(&self) -> String { + match self { + Self::I => "I-piece", + Self::T => "T-piece", + Self::L => "L-piece", + Self::J => "J-piece", + Self::S => "S-piece", + Self::Z => "Z-piece", + Self::Square => "Square-piece", + } + .to_string() + } + + pub fn iter_all() -> impl ExactSizeIterator { + vec![ + Self::I, + Self::T, + Self::L, + Self::J, + Self::S, + Self::Z, + Self::Square, + ] + .into_iter() + } +} + +pub fn create_block(pos: Vector2I, color: Color) -> impl Bundle { + ( + GridTransform::from_xy(pos.x, pos.y), + Block, + create_block_sprite(color), + ) +} + +pub fn create_block_sprite(color: Color) -> impl Bundle { + ( + Name::from("Block"), + Sprite { color, ..default() }, + SpriteLoader("sprites/block_base.png".into()), + children![( + Name::from("Block Highlight"), + Sprite::default(), + Transform::from_xyz(0.0, 0.0, 1.0), + SpriteLoader("sprites/block_highlight.png".into()), + )], + ) +} + +pub fn create_piece(piece: PieceType) -> impl Bundle { + let pos_arr = piece.block_positions(); + let mut pos_iter = pos_arr.iter(); + ( + Name::from(piece.name()), + Piece, + children![ + create_block(*pos_iter.next().unwrap(), piece.color()), + create_block(*pos_iter.next().unwrap(), piece.color()), + create_block(*pos_iter.next().unwrap(), piece.color()), + create_block(*pos_iter.next().unwrap(), piece.color()), + ], + ) +} diff --git a/src/game/systems.rs b/src/game/systems.rs index 05b00c0..09618bd 100644 --- a/src/game/systems.rs +++ b/src/game/systems.rs @@ -1,3 +1,7 @@ mod demo; +mod game_scene; +mod grid; pub use demo::*; +pub use game_scene::*; +pub use grid::*; diff --git a/src/game/systems/demo.rs b/src/game/systems/demo.rs index 53eb575..571bb9f 100644 --- a/src/game/systems/demo.rs +++ b/src/game/systems/demo.rs @@ -1,19 +1,5 @@ pub use bevy::prelude::*; -use crate::game::prefab::{DemoCamera2d, DemoCamera3d}; - -pub fn setup_demo_2d(mut commands: Commands) { - commands.spawn(( - Name::from("2D Demo Scene"), - Transform::default(), - Visibility::default(), - children![ - DemoCamera2d, - (Name::from("2D sprite"), Sprite::sized(Vec2::splat(256.0)),) - ], - )); -} - pub fn demo_2d( mut camera_query: Query<(&mut Transform, &mut Projection)>, mut mouse_events: EventReader, @@ -44,93 +30,3 @@ pub fn demo_2d( } } } - -pub fn setup_demo_3d( - mut commands: Commands, - mut meshes: ResMut>, - mut materials: ResMut>, -) { - commands.spawn(( - Name::from("3D Demo Scene"), - Transform::from_xyz(0., 300.0, 0.), - Visibility::default(), - children![ - ( - DemoCamera3d, - Transform::from_xyz(-6.225, 2.197, -10.470).looking_at(Vec3::ZERO, Dir3::Y), - ), - ( - Name::from("Cuboid"), - Transform::from_xyz(0., 0.5, 0.), - Mesh3d(meshes.add(Cuboid::from_length(1.0))), - MeshMaterial3d(materials.add(Color::from(bevy::color::palettes::css::GREY))), - ), - ( - Name::from("Floor"), - Transform::from_xyz(0., -0.25, 0.), - Mesh3d(meshes.add(Cuboid::from_size(Vec3::new(10., 0.5, 10.)))), - MeshMaterial3d(materials.add(Color::from(bevy::color::palettes::css::GREY))), - ), - ( - Name::from("Sun"), - Transform::from_rotation(Quat::from_euler( - EulerRot::default(), - f32::to_radians(160.0), - f32::to_radians(-20.0), - 0.0, - )), - DirectionalLight { - shadows_enabled: true, - ..default() - }, - ), - ], - )); -} - -pub fn demo_3d( - mut camera_query: Query<&mut Transform, With>, - mut mouse_events: EventReader, - mouse_input: Res>, - key_input: Res>, - time: Res