Piece spawning and game area

master
hheik 2025-08-20 13:24:13 +03:00
parent 020187c55a
commit 946bcfb246
18 changed files with 578 additions and 137 deletions

1
Cargo.lock generated
View File

@ -437,6 +437,7 @@ dependencies = [
"bevy_egui", "bevy_egui",
"bevy_mod_debugdump", "bevy_mod_debugdump",
"bevy_prototype_lyon", "bevy_prototype_lyon",
"fastrand",
"num-traits", "num-traits",
] ]

View File

@ -1,7 +1,7 @@
[package] [package]
name = "bevy-template" name = "bevy-template"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2024"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # 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_egui = "0.35.1"
bevy_mod_debugdump = "0.13.0" bevy_mod_debugdump = "0.13.0"
bevy_prototype_lyon = "0.13.0" bevy_prototype_lyon = "0.13.0"
fastrand = "2.3.0"
num-traits = "0.2.19" num-traits = "0.2.19"
# Enable a small amount of optimization in debug mode # Enable a small amount of optimization in debug mode

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 B

View File

@ -1,5 +1,7 @@
use bevy::prelude::*; use bevy::prelude::*;
mod systems;
pub struct DebugPlugin; pub struct DebugPlugin;
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
@ -7,6 +9,8 @@ pub struct DebugSet;
impl Plugin for DebugPlugin { impl Plugin for DebugPlugin {
fn build(&self, app: &mut App) { 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.configure_sets(Last, DebugSet.run_if(is_debug_enabled));
app.insert_resource(DebugMode::on()) app.insert_resource(DebugMode::on())
@ -15,7 +19,8 @@ impl Plugin for DebugPlugin {
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),
)) ))
.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<DebugMode>) -> bool {
debug_mode.enabled debug_mode.enabled
} }
fn debug_toggle(input: Res<ButtonInput<KeyCode>>, mut debug_mode: ResMut<DebugMode>) { fn debug_toggle(
input: Res<ButtonInput<KeyCode>>,
mut debug_mode: ResMut<DebugMode>,
mut config_store: ResMut<GizmoConfigStore>,
) {
if input.just_pressed(KeyCode::KeyP) { if input.just_pressed(KeyCode::KeyP) {
debug_mode.enabled = !debug_mode.enabled debug_mode.enabled = !debug_mode.enabled
} }
if input.just_pressed(KeyCode::KeyO) {
let (config, _) = config_store.config_mut::<DefaultGizmoConfigGroup>();
config.enabled ^= true;
}
} }

3
src/debug/systems.rs Normal file
View File

@ -0,0 +1,3 @@
mod game_scene;
pub use game_scene::*;

View File

@ -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();
}
}

View File

@ -1,8 +1,12 @@
use crate::{debug, game_setup, util}; use crate::{debug, game_setup, util};
use bevy::prelude::*; use bevy::prelude::*;
mod prefab; pub mod grid;
pub mod prefab;
mod systems; mod systems;
pub mod tetris;
pub const PIXEL_SCALE: f32 = 3.0;
pub fn init(app: &mut App) { pub fn init(app: &mut App) {
let app = app.add_plugins(( let app = app.add_plugins((
@ -11,9 +15,19 @@ pub fn init(app: &mut App) {
debug::DebugPlugin, debug::DebugPlugin,
)); ));
// app.add_systems(Startup, systems::setup_demo_2d) app.add_event::<tetris::OnPiecePlaced>()
// .add_systems(Update, systems::demo_2d); .register_type::<tetris::GameArea>()
.register_type::<tetris::NextPiece>()
app.add_systems(Startup, systems::setup_demo_3d) .register_type::<tetris::Piece>()
.add_systems(Update, systems::demo_3d); .register_type::<tetris::PieceControls>()
.register_type::<tetris::ControllablePiece>()
.register_type::<grid::Grid>()
.register_type::<grid::GridTransform>()
.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(),
);
} }

34
src/game/grid.rs Normal file
View File

@ -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),
}
}
}

View File

@ -1,30 +1,143 @@
use bevy::pbr::Atmosphere;
use bevy::prelude::*; use bevy::prelude::*;
use crate::util::{SpriteLoader, Vector2I};
use super::{PIXEL_SCALE, grid::*, tetris::*};
#[derive(Clone, Debug, Default, Component, Reflect)] #[derive(Clone, Debug, Default, Component, Reflect)]
#[reflect(Component)] #[reflect(Component)]
#[require( #[require(
Name = Name::from("2D Camera"), Name = Name::from("2D Camera"),
Camera2d, Camera2d,
Projection = Projection::Orthographic(OrthographicProjection {
scale: 1.0 / PIXEL_SCALE,
..OrthographicProjection::default_2d()
}),
Transform = Transform::from_xyz(0., 0., 10.), Transform = Transform::from_xyz(0., 0., 10.),
)] )]
pub struct DemoCamera2d; pub struct DemoCamera2d;
#[derive(Clone, Debug, Default, Component, Reflect)] #[derive(Clone, Copy, Debug, Reflect)]
#[reflect(Component)] pub enum PieceType {
#[require( /// ......
Name = Name::from("3D Camera"), /// .XXXX.
// Atmosphere causes a panic if hdr is not true: https://github.com/bevyengine/bevy/issues/18959 I,
Camera = Camera { /// ..X..
hdr: true, /// .XXX.
..default() T,
}, /// ...X.
Camera3d, /// .XXX.
Atmosphere = Atmosphere::EARTH, L,
Transform = Transform::from_xyz(1., 2., 10.).looking_at(Vec3::ZERO, Dir3::Y), /// .X...
PointLight, /// .XXX.
)] J,
pub struct DemoCamera3d; /// ..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<Item = Self> {
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()),
],
)
}

View File

@ -1,3 +1,7 @@
mod demo; mod demo;
mod game_scene;
mod grid;
pub use demo::*; pub use demo::*;
pub use game_scene::*;
pub use grid::*;

View File

@ -1,19 +1,5 @@
pub use bevy::prelude::*; 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( pub fn demo_2d(
mut camera_query: Query<(&mut Transform, &mut Projection)>, mut camera_query: Query<(&mut Transform, &mut Projection)>,
mut mouse_events: EventReader<bevy::input::mouse::MouseMotion>, mut mouse_events: EventReader<bevy::input::mouse::MouseMotion>,
@ -44,93 +30,3 @@ pub fn demo_2d(
} }
} }
} }
pub fn setup_demo_3d(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
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<Camera3d>>,
mut mouse_events: EventReader<bevy::input::mouse::MouseMotion>,
mouse_input: Res<ButtonInput<MouseButton>>,
key_input: Res<ButtonInput<KeyCode>>,
time: Res<Time>,
) {
let raw_mouse_motion: Vec2 = mouse_events.read().map(|e| e.delta).sum();
if mouse_input.pressed(MouseButton::Right) {
let move_forward = key_input.pressed(KeyCode::KeyW);
let move_back = key_input.pressed(KeyCode::KeyS);
let move_left = key_input.pressed(KeyCode::KeyA);
let move_right = key_input.pressed(KeyCode::KeyD);
let move_up = key_input.pressed(KeyCode::Space) || key_input.pressed(KeyCode::KeyE);
let move_down = key_input.pressed(KeyCode::ControlLeft) || key_input.pressed(KeyCode::KeyQ);
let raw_movement = Vec3 {
x: match (move_right, move_left) {
(true, false) => 1.0,
(false, true) => -1.0,
_ => 0.0,
},
y: match (move_up, move_down) {
(true, false) => 1.0,
(false, true) => -1.0,
_ => 0.0,
},
z: match (move_back, move_forward) {
(true, false) => 1.0,
(false, true) => -1.0,
_ => 0.0,
},
};
for mut transform in camera_query.iter_mut() {
let mouse_motion = raw_mouse_motion * Vec2::new(-1.0, -1.0) * 0.002;
transform.rotate_axis(Dir3::Y, mouse_motion.x);
transform.rotate_local_x(mouse_motion.y);
let local_movement = raw_movement * time.delta_secs() * 10.0;
let movement = transform.rotation * local_movement;
transform.translation += movement;
}
}
}

View File

@ -0,0 +1,176 @@
use bevy::prelude::*;
use crate::game::{
grid::*,
prefab::*,
tetris::{piece_input, *},
};
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),
),
],
));
world.flush();
}
pub fn block_control(
time: Res<Time>,
key_input: Res<ButtonInput<KeyCode>>,
mut piece_query: Query<&mut PieceControls, With<ControllablePiece>>,
) {
const SOFT_DROP_SPEED: f32 = 10.0;
for mut piece in piece_query.iter_mut() {
piece.reset_inputs();
if key_input.pressed(KeyCode::ArrowDown) {
piece.cumulated_gravity += SOFT_DROP_SPEED * time.delta_secs();
}
if key_input.just_pressed(KeyCode::Space) {
piece.instant_drop = true;
}
if key_input.just_pressed(KeyCode::ArrowUp) {
piece.rotation = Some(piece_input::Rotation::Clockwise);
}
if key_input.just_pressed(KeyCode::ShiftLeft) {
piece.rotation = Some(piece_input::Rotation::CounterClockwise);
}
match (
key_input.pressed(KeyCode::ArrowLeft),
key_input.pressed(KeyCode::ArrowRight),
) {
(true, false) => piece.movement = Some(piece_input::Move::Left),
(false, true) => piece.movement = Some(piece_input::Move::Right),
_ => (),
}
}
}
pub fn apply_gravity(
mut piece_query: Query<(&mut PieceControls, &ChildOf), With<ControllablePiece>>,
time: Res<Time>,
gravity_query: Query<&GameGravity>,
) {
for (mut piece, parent) in piece_query.iter_mut() {
if let Ok(gravity) = gravity_query.get(parent.0) {
piece.cumulated_gravity += gravity.current * time.delta_secs();
}
}
}
pub struct InputRepeatState {
pub input_type: piece_input::Move,
pub cumulated_time: f32,
pub next_interval: f32,
pub interval_reduction: f32,
pub min_interval: f32,
}
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>,
time: Res<Time>,
) {
// TODO: Handle piece collision
for (mut piece_controls, entity, parent, children) in piece_query.iter_mut() {
let (mut grid_transform, _) = transform_query.get_mut(entity).unwrap();
// TODO: replace with actual logic
if piece_controls.instant_drop {
grid_transform.translation.y = 0;
on_piece_placed.write(OnPiecePlaced { entity });
*input_repeat = None;
continue;
}
let can_move_down = grid_transform.translation.y > 0;
if can_move_down && piece_controls.cumulated_gravity >= 1.0 {
piece_controls.cumulated_gravity -= 1.0;
grid_transform.translation.y -= 1;
}
if !can_move_down && piece_controls.cumulated_gravity >= 3.0 {
// Force piece placement
on_piece_placed.write(OnPiecePlaced { entity });
*input_repeat = None;
continue;
}
if let Some(movement) = piece_controls.movement {
let mut move_piece = false;
if input_repeat
.as_ref()
.is_some_and(|state| state.input_type == movement)
{
if let Some(input_repeat) = input_repeat.as_mut() {
input_repeat.cumulated_time += time.delta_secs();
if input_repeat.cumulated_time >= input_repeat.next_interval {
input_repeat.cumulated_time = 0.0;
input_repeat.next_interval = (input_repeat.next_interval
* input_repeat.interval_reduction)
.max(input_repeat.min_interval);
move_piece = true;
}
}
} else {
*input_repeat = Some(InputRepeatState {
input_type: movement,
cumulated_time: 0.0,
next_interval: 0.1,
interval_reduction: 0.5,
min_interval: 0.03,
});
move_piece = true;
}
if move_piece {
grid_transform.translation.x += match movement {
piece_input::Move::Left => -1,
piece_input::Move::Right => 1,
}
}
} else {
*input_repeat = None;
}
}
}
pub fn handle_piece_placed(
mut on_piece_placed: EventReader<OnPiecePlaced>,
mut commands: Commands,
parent_query: Query<&ChildOf>,
mut next_piece_query: Query<(&mut NextPiece, &GameArea)>,
) {
for event in on_piece_placed.read() {
info!("Placed piece {:#}", event.entity);
let mut entity_cmds = commands
.get_entity(event.entity)
.unwrap()
.remove::<(PieceControls, ControllablePiece)>();
let parent = parent_query.get(event.entity).unwrap().0;
let (mut next_piece, game_area) = next_piece_query.get_mut(parent).unwrap();
let new_piece = next_piece.take_and_generate();
commands.entity(parent).with_child((
GridTransform {
translation: game_area.block_spawn_point(),
},
ControllablePiece,
create_piece(new_piece),
));
// TODO: Finish up clearing the piece / lines
// TODO: Lose condition
}
}

19
src/game/systems/grid.rs Normal file
View File

@ -0,0 +1,19 @@
use bevy::prelude::*;
use crate::game::grid::*;
pub fn grid_positioning(
grid_query: Query<(&Grid, &Children)>,
mut transform_query: Query<(&mut Transform, &GridTransform)>,
) {
for (grid, children) in grid_query.iter() {
for child in children.iter() {
if let Ok((mut transform, grid_transform)) = transform_query.get_mut(child) {
let new_pos = Vec2::from(grid_transform.translation) * grid.tile_size;
// Don't touch Z in case of layering
transform.translation.x = new_pos.x;
transform.translation.y = new_pos.y;
}
}
}
}

133
src/game/tetris.rs Normal file
View File

@ -0,0 +1,133 @@
use bevy::prelude::*;
use crate::util::Vector2I;
use super::{grid::*, prefab::*};
#[derive(Component, Clone, Debug, Reflect)]
#[reflect(Component)]
#[require(Grid, NextPiece)]
pub struct GameArea {
pub bottom_boundary: i32,
pub top_boundary: i32,
pub left_boundary: i32,
pub right_boundary: i32,
}
impl Default for GameArea {
fn default() -> Self {
Self {
bottom_boundary: 0,
top_boundary: 20,
left_boundary: 0,
right_boundary: 9,
}
}
}
impl GameArea {
pub fn block_spawn_point(&self) -> Vector2I {
Vector2I::new(
(self.left_boundary + self.right_boundary) / 2,
self.top_boundary + 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,
}
}
}
#[derive(Component, Debug, Reflect)]
#[reflect(Component)]
pub struct GameGravity {
pub current: f32,
}
impl Default for GameGravity {
fn default() -> Self {
Self { current: 1.5 }
}
}
pub mod piece_input {
use bevy::prelude::*;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Reflect)]
pub enum Rotation {
Clockwise,
CounterClockwise,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Reflect)]
pub enum Move {
Left,
Right,
}
}
#[derive(Component, Debug, Default, Reflect)]
#[reflect(Component)]
#[require(Grid, GridTransform)]
pub struct Piece;
#[derive(Component, Debug, Default, Reflect)]
#[reflect(Component)]
#[require(Piece)]
pub struct PieceControls {
pub cumulated_gravity: f32,
pub instant_drop: bool,
pub movement: Option<piece_input::Move>,
pub rotation: Option<piece_input::Rotation>,
}
impl PieceControls {
pub fn reset_inputs(&mut self) {
self.instant_drop = false;
self.movement = None;
self.rotation = None;
}
}
#[derive(Component, Debug, Reflect)]
#[reflect(Component)]
#[require(PieceControls)]
pub struct ControllablePiece;
#[derive(Component, Clone, Debug, Reflect)]
#[reflect(Component)]
pub struct NextPiece {
pub piece: PieceType,
}
impl Default for NextPiece {
fn default() -> Self {
Self {
piece: fastrand::choice(PieceType::iter_all()).unwrap(),
}
}
}
impl NextPiece {
pub fn generate() -> PieceType {
fastrand::choice(PieceType::iter_all()).unwrap()
}
pub fn take_and_generate(&mut self) -> PieceType {
let result = self.piece;
self.piece = Self::generate();
result
}
}
#[derive(Component, Clone, Debug, Reflect)]
#[reflect(Component)]
pub struct Block;
#[derive(Event, Clone, Debug)]
pub struct OnPiecePlaced {
pub entity: Entity,
}

View File

@ -1,5 +1,7 @@
use bevy::{prelude::*, window::WindowResolution}; use bevy::{prelude::*, window::WindowResolution};
use crate::game::PIXEL_SCALE;
pub struct GameSetupPlugin; pub struct GameSetupPlugin;
impl Plugin for GameSetupPlugin { impl Plugin for GameSetupPlugin {
@ -8,8 +10,8 @@ impl Plugin for GameSetupPlugin {
DefaultPlugins DefaultPlugins
.set(WindowPlugin { .set(WindowPlugin {
primary_window: Some(Window { primary_window: Some(Window {
resolution: WindowResolution::new(512.0 * 2.0, 320.0 * 2.0), resolution: WindowResolution::new(256.0 * PIXEL_SCALE, 240.0 * PIXEL_SCALE),
title: "Bevy template <press P to toggle debug mode>".to_string(), // NOTE: Replace this title: "Tetris <P: debug mode, O: gizmos>".to_string(), // NOTE: Replace this
resizable: false, resizable: false,
..default() ..default()
}), }),

View File

@ -59,19 +59,23 @@ impl<T: VectorComponent> Vector2<T> {
} }
} }
pub fn new(x: T, y: T) -> Vector2<T> { pub fn new(x: T, y: T) -> Self {
Vector2 { x, y } Self { x, y }
} }
pub fn min(&self, other: &Vector2<T>) -> Vector2<T> { pub fn splat(value: T) -> Self {
Vector2 { Self { x: value, y: value }
}
pub fn min(&self, other: &Self) -> Self {
Self {
x: VectorComponent::min(self.x, other.x), x: VectorComponent::min(self.x, other.x),
y: VectorComponent::min(self.y, other.y), y: VectorComponent::min(self.y, other.y),
} }
} }
pub fn max(&self, other: &Vector2<T>) -> Vector2<T> { pub fn max(&self, other: &Self) -> Self {
Vector2 { Self {
x: VectorComponent::max(self.x, other.x), x: VectorComponent::max(self.x, other.x),
y: VectorComponent::max(self.y, other.y), y: VectorComponent::max(self.y, other.y),
} }

View File

@ -44,3 +44,12 @@ impl From<Vector2I> for Vec3 {
} }
} }
} }
impl From<(i32, i32)> for Vector2I {
fn from(value: (i32, i32)) -> Self {
Self {
x: value.0,
y: value.1,
}
}
}