generated from hheik/bevy-template
Piece spawning and game area
parent
020187c55a
commit
946bcfb246
|
|
@ -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",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 |
17
src/debug.rs
17
src/debug.rs
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 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(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 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()),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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::*;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -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 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()
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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