generated from hheik/bevy-template
Added piece collision
parent
8d091fec47
commit
db238c3248
|
|
@ -17,16 +17,19 @@ pub fn init(app: &mut App) {
|
|||
|
||||
app.add_event::<tetris::OnPiecePlaced>()
|
||||
.register_type::<tetris::GameArea>()
|
||||
.register_type::<tetris::GameGravity>()
|
||||
.register_type::<tetris::BlockSet>()
|
||||
.register_type::<tetris::NextPiece>()
|
||||
.register_type::<tetris::Piece>()
|
||||
.register_type::<tetris::PieceControls>()
|
||||
.register_type::<tetris::ControllablePiece>()
|
||||
.register_type::<tetris::Gravity>()
|
||||
.register_type::<tetris::Block>()
|
||||
.register_type::<prefab::PieceType>()
|
||||
.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(PreUpdate, (systems::repeat_inputs, systems::apply_gravity))
|
||||
.add_systems(Update, (systems::demo_2d, systems::apply_piece_movement))
|
||||
.add_systems(PostUpdate, systems::grid_positioning);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
mod demo;
|
||||
mod game_scene;
|
||||
mod grid;
|
||||
mod input;
|
||||
mod movement;
|
||||
|
||||
pub use demo::*;
|
||||
pub use game_scene::*;
|
||||
pub use grid::*;
|
||||
pub use input::*;
|
||||
pub use movement::*;
|
||||
|
|
|
|||
|
|
@ -2,11 +2,7 @@ use std::collections::{HashMap, HashSet};
|
|||
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::game::{
|
||||
grid::*,
|
||||
prefab::*,
|
||||
tetris::{piece_input, *},
|
||||
};
|
||||
use crate::game::{grid::*, prefab::*, tetris::*};
|
||||
|
||||
pub fn setup_game_scene(world: &mut World) {
|
||||
let game_area = GameArea::default();
|
||||
|
|
@ -35,127 +31,6 @@ pub fn setup_game_scene(world: &mut World) {
|
|||
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>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
// 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 { piece: entity });
|
||||
commands.trigger_targets(OnPiecePlaced { piece: entity }, parent.parent());
|
||||
*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 { piece: entity });
|
||||
commands.trigger_targets(OnPiecePlaced { piece: entity }, parent.parent());
|
||||
*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.2,
|
||||
interval_reduction: 0.0,
|
||||
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_placed_piece(trigger: Trigger<OnPiecePlaced>, world: &mut World) {
|
||||
let game_area = trigger.target();
|
||||
let event = *trigger.event();
|
||||
|
|
@ -178,6 +53,10 @@ pub fn handle_placed_piece(trigger: Trigger<OnPiecePlaced>, world: &mut World) {
|
|||
world.flush();
|
||||
}
|
||||
|
||||
world
|
||||
.run_system_cached_with(rebuild_collisions, game_area)
|
||||
.expect("Rebuilding collisions");
|
||||
|
||||
world
|
||||
.run_system_cached_with(check_lose_condition, game_area)
|
||||
.expect("Checking lose condition");
|
||||
|
|
@ -187,13 +66,28 @@ pub fn handle_placed_piece(trigger: Trigger<OnPiecePlaced>, world: &mut World) {
|
|||
.expect("Creating new piece");
|
||||
world.flush();
|
||||
|
||||
// TODO: Finish up clearing the piece / lines
|
||||
// TODO: Lose condition
|
||||
}
|
||||
|
||||
fn rebuild_collisions(
|
||||
In(game_entity): In<Entity>,
|
||||
game_query: Query<&Children>,
|
||||
block_query: Query<&GridTransform, With<Block>>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
if let Ok(children) = game_query.get(game_entity) {
|
||||
let mut blocks = HashSet::new();
|
||||
for child in children.iter() {
|
||||
if let Ok(transform) = block_query.get(child) {
|
||||
blocks.insert(transform.translation);
|
||||
}
|
||||
}
|
||||
commands.entity(game_entity).insert(BlockSet::new(blocks));
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a vector of affected game area entities
|
||||
fn place_blocks(
|
||||
// mut piece_placed_events: EventReader<OnPiecePlaced>,
|
||||
In((event, game_area)): In<(OnPiecePlaced, Entity)>,
|
||||
mut commands: Commands,
|
||||
child_query: Query<&Children>,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
use bevy::prelude::*;
|
||||
|
||||
use crate::game::tetris::*;
|
||||
|
||||
pub fn repeat_inputs(
|
||||
key_input: Res<ButtonInput<KeyCode>>,
|
||||
mut repeat_move: Local<input::InputRepeat>,
|
||||
mut repeat_rotate: Local<input::InputRepeat>,
|
||||
time: Res<Time>,
|
||||
mut controllable_query: Query<&mut PieceControls, With<ControllablePiece>>,
|
||||
) {
|
||||
for mut controls in controllable_query.iter_mut() {
|
||||
*controls = PieceControls::default();
|
||||
let delta_secs = time.delta_secs();
|
||||
|
||||
let move_dir = match (
|
||||
key_input.pressed(KeyCode::ArrowLeft),
|
||||
key_input.pressed(KeyCode::ArrowRight),
|
||||
) {
|
||||
(true, false) => Some(input::Move::Left),
|
||||
(false, true) => Some(input::Move::Right),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
controls.movement = if repeat_move.should_repeat(move_dir.is_some(), delta_secs) {
|
||||
move_dir
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let rotate_dir = match (
|
||||
key_input.pressed(KeyCode::ArrowUp),
|
||||
key_input.pressed(KeyCode::ShiftLeft),
|
||||
) {
|
||||
(true, false) => Some(input::Rotation::Clockwise),
|
||||
(false, true) => Some(input::Rotation::CounterClockwise),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
controls.rotation = if repeat_rotate.should_repeat(rotate_dir.is_some(), delta_secs) {
|
||||
rotate_dir
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if key_input.pressed(KeyCode::ArrowDown) {
|
||||
controls.fast_drop = true;
|
||||
}
|
||||
if key_input.just_pressed(KeyCode::Space) {
|
||||
controls.instant_drop = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
use bevy::prelude::*;
|
||||
|
||||
use crate::{
|
||||
game::{grid::*, tetris::*},
|
||||
util::Vector2I,
|
||||
};
|
||||
|
||||
pub fn apply_gravity(
|
||||
mut piece_query: Query<(&mut Gravity, &PieceControls, &ChildOf)>,
|
||||
time: Res<Time>,
|
||||
game_gravity_query: Query<&GameGravity>,
|
||||
) {
|
||||
const SOFT_DROP_SPEED: f32 = 10.0;
|
||||
for (mut piece_gravity, controls, parent) in piece_query.iter_mut() {
|
||||
if let Ok(game_gravity) = game_gravity_query.get(parent.0) {
|
||||
piece_gravity.cumulated += game_gravity.current * time.delta_secs();
|
||||
}
|
||||
if controls.fast_drop {
|
||||
piece_gravity.cumulated += SOFT_DROP_SPEED * time.delta_secs();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_piece_movement(
|
||||
mut piece_query: Query<(
|
||||
&PieceControls,
|
||||
Option<&mut Gravity>,
|
||||
Entity,
|
||||
&ChildOf,
|
||||
&Children,
|
||||
)>,
|
||||
game_query: Query<(&BlockSet, &GameArea)>,
|
||||
mut transform_query: Query<(&mut GridTransform, Has<Block>)>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
for (controls, maybe_gravity, entity, parent, children) in piece_query.iter_mut() {
|
||||
let game_entity = parent.parent();
|
||||
let (collisions, game_area) = game_query.get(game_entity).unwrap();
|
||||
|
||||
let blocks: Vec<Vector2I> = {
|
||||
let mut blocks = vec![];
|
||||
for child in children.iter() {
|
||||
let block = transform_query
|
||||
.get(child)
|
||||
.iter()
|
||||
.filter_map(|(transform, has_block)| {
|
||||
if *has_block {
|
||||
Some(transform.translation)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.next();
|
||||
if let Some(pos) = block {
|
||||
blocks.push(pos);
|
||||
}
|
||||
}
|
||||
blocks
|
||||
};
|
||||
|
||||
let cast_shape =
|
||||
|pos| collisions.cast_shape(pos, &blocks) || !game_area.is_shape_inside(pos, &blocks);
|
||||
|
||||
let piece_pos = &mut transform_query.get_mut(entity).unwrap().0.translation;
|
||||
|
||||
if controls.instant_drop {
|
||||
let mut drop_pos = *piece_pos;
|
||||
while !cast_shape(drop_pos + Vector2I::DOWN) {
|
||||
drop_pos = drop_pos + Vector2I::DOWN;
|
||||
}
|
||||
*piece_pos = drop_pos;
|
||||
commands.trigger_targets(OnPiecePlaced { piece: entity }, game_entity);
|
||||
continue;
|
||||
}
|
||||
|
||||
let can_move_down = !cast_shape(*piece_pos + Vector2I::DOWN);
|
||||
|
||||
if let Some(mut gravity) = maybe_gravity {
|
||||
if gravity.cumulated >= 1.0 && can_move_down {
|
||||
gravity.cumulated -= 1.0;
|
||||
*piece_pos = *piece_pos + Vector2I::DOWN;
|
||||
}
|
||||
if gravity.cumulated >= 3.0 && !can_move_down {
|
||||
commands.trigger_targets(OnPiecePlaced { piece: entity }, game_entity);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(movement) = controls.movement {
|
||||
let dir = match movement {
|
||||
input::Move::Left => Vector2I::LEFT,
|
||||
input::Move::Right => Vector2I::RIGHT,
|
||||
};
|
||||
if !cast_shape(*piece_pos + dir) {
|
||||
*piece_pos = *piece_pos + dir;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +1,42 @@
|
|||
use std::collections::HashSet;
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
use crate::util::Vector2I;
|
||||
|
||||
use super::{grid::*, prefab::*};
|
||||
|
||||
pub mod input;
|
||||
|
||||
pub type YPos = 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)]
|
||||
#[reflect(Component)]
|
||||
#[require(Grid, NextPiece)]
|
||||
#[require(Grid, NextPiece, BlockSet)]
|
||||
pub struct GameArea {
|
||||
pub bottom_boundary: i32,
|
||||
pub kill_height: i32,
|
||||
|
|
@ -54,6 +81,13 @@ impl GameArea {
|
|||
&& point.x <= self.right_boundary
|
||||
&& point.y >= self.bottom_boundary
|
||||
}
|
||||
|
||||
pub fn is_shape_inside(&self, shape_pos: Vector2I, local_blocks: &[Vector2I]) -> bool {
|
||||
local_blocks
|
||||
.iter()
|
||||
.map(|local_pos| shape_pos + *local_pos)
|
||||
.all(|global_pos| self.is_point_inside(global_pos))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Debug, Reflect)]
|
||||
|
|
@ -68,22 +102,6 @@ impl Default for GameGravity {
|
|||
}
|
||||
}
|
||||
|
||||
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)]
|
||||
|
|
@ -91,20 +109,19 @@ 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>,
|
||||
pub struct Gravity {
|
||||
pub cumulated: f32,
|
||||
}
|
||||
|
||||
impl PieceControls {
|
||||
pub fn reset_inputs(&mut self) {
|
||||
self.instant_drop = false;
|
||||
self.movement = None;
|
||||
self.rotation = None;
|
||||
}
|
||||
/// Dictates the intention of piece's movement
|
||||
#[derive(Component, Debug, Default, Reflect)]
|
||||
#[reflect(Component)]
|
||||
#[require(Piece, Gravity)]
|
||||
pub struct PieceControls {
|
||||
pub instant_drop: bool,
|
||||
pub fast_drop: bool,
|
||||
pub movement: Option<input::Move>,
|
||||
pub rotation: Option<input::Rotation>,
|
||||
}
|
||||
|
||||
#[derive(Component, Debug, Reflect)]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
use bevy::prelude::*;
|
||||
|
||||
#[derive(Debug, Clone, Reflect)]
|
||||
pub struct InputRepeat {
|
||||
pub currently_pressed: bool,
|
||||
pub cumulated_time: f32,
|
||||
pub next_interval: f32,
|
||||
pub interval_mult: f32,
|
||||
pub min_interval: f32,
|
||||
}
|
||||
|
||||
impl Default for InputRepeat {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
currently_pressed: false,
|
||||
cumulated_time: 0.0,
|
||||
next_interval: 0.2,
|
||||
interval_mult: 0.0,
|
||||
min_interval: 0.03,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InputRepeat {
|
||||
/// Returns if the input should be repeated this frame.
|
||||
pub fn should_repeat(&mut self, pressed: bool, delta_seconds: f32) -> bool {
|
||||
if !pressed {
|
||||
*self = Self::default();
|
||||
return false;
|
||||
}
|
||||
if !self.currently_pressed {
|
||||
self.currently_pressed = true;
|
||||
true
|
||||
} else {
|
||||
self.cumulated_time += delta_seconds;
|
||||
if self.cumulated_time >= self.next_interval {
|
||||
self.cumulated_time = 0.0;
|
||||
self.next_interval =
|
||||
(self.next_interval * self.interval_mult).max(self.min_interval);
|
||||
return true;
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Reflect)]
|
||||
pub enum Rotation {
|
||||
Clockwise,
|
||||
CounterClockwise,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Reflect)]
|
||||
pub enum Move {
|
||||
Left,
|
||||
Right,
|
||||
}
|
||||
Loading…
Reference in New Issue