generated from hheik/bevy-template
Piece spawning and game area
parent
020187c55a
commit
946bcfb246
|
|
@ -437,6 +437,7 @@ dependencies = [
|
|||
"bevy_egui",
|
||||
"bevy_mod_debugdump",
|
||||
"bevy_prototype_lyon",
|
||||
"fastrand",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 99 B |
Binary file not shown.
|
After Width: | Height: | Size: 95 B |
17
src/debug.rs
17
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<DebugMode>) -> bool {
|
|||
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) {
|
||||
debug_mode.enabled = !debug_mode.enabled
|
||||
}
|
||||
if input.just_pressed(KeyCode::KeyO) {
|
||||
let (config, _) = config_store.config_mut::<DefaultGizmoConfigGroup>();
|
||||
config.enabled ^= true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
mod game_scene;
|
||||
|
||||
pub use game_scene::*;
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
26
src/game.rs
26
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::<tetris::OnPiecePlaced>()
|
||||
.register_type::<tetris::GameArea>()
|
||||
.register_type::<tetris::NextPiece>()
|
||||
.register_type::<tetris::Piece>()
|
||||
.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(),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<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()),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
mod demo;
|
||||
mod game_scene;
|
||||
mod grid;
|
||||
|
||||
pub use demo::*;
|
||||
pub use game_scene::*;
|
||||
pub use grid::*;
|
||||
|
|
|
|||
|
|
@ -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<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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
use bevy::{prelude::*, window::WindowResolution};
|
||||
|
||||
use crate::game::PIXEL_SCALE;
|
||||
|
||||
pub struct GameSetupPlugin;
|
||||
|
||||
impl Plugin for GameSetupPlugin {
|
||||
|
|
@ -8,8 +10,8 @@ impl Plugin for GameSetupPlugin {
|
|||
DefaultPlugins
|
||||
.set(WindowPlugin {
|
||||
primary_window: Some(Window {
|
||||
resolution: WindowResolution::new(512.0 * 2.0, 320.0 * 2.0),
|
||||
title: "Bevy template <press P to toggle debug mode>".to_string(), // NOTE: Replace this
|
||||
resolution: WindowResolution::new(256.0 * PIXEL_SCALE, 240.0 * PIXEL_SCALE),
|
||||
title: "Tetris <P: debug mode, O: gizmos>".to_string(), // NOTE: Replace this
|
||||
resizable: false,
|
||||
..default()
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -59,19 +59,23 @@ impl<T: VectorComponent> Vector2<T> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn new(x: T, y: T) -> Vector2<T> {
|
||||
Vector2 { x, y }
|
||||
pub fn new(x: T, y: T) -> Self {
|
||||
Self { x, y }
|
||||
}
|
||||
|
||||
pub fn min(&self, other: &Vector2<T>) -> Vector2<T> {
|
||||
Vector2 {
|
||||
pub fn splat(value: T) -> Self {
|
||||
Self { x: value, y: value }
|
||||
}
|
||||
|
||||
pub fn min(&self, other: &Self) -> Self {
|
||||
Self {
|
||||
x: VectorComponent::min(self.x, other.x),
|
||||
y: VectorComponent::min(self.y, other.y),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn max(&self, other: &Vector2<T>) -> Vector2<T> {
|
||||
Vector2 {
|
||||
pub fn max(&self, other: &Self) -> Self {
|
||||
Self {
|
||||
x: VectorComponent::max(self.x, other.x),
|
||||
y: VectorComponent::max(self.y, other.y),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue