not perfect, but gooder movement

main
Elijah Voigt 1 week ago
parent c4394f9728
commit 7ef5a73704

@ -21,7 +21,6 @@ fn main() {
Update, Update,
( (
kb_input.run_if(on_event::<KeyboardInput>), kb_input.run_if(on_event::<KeyboardInput>),
update_position,
falling falling
.run_if(in_state(Falling::On)) .run_if(in_state(Falling::On))
.run_if(clock_cycle(1.0)), .run_if(clock_cycle(1.0)),
@ -31,19 +30,16 @@ fn main() {
.or(any_component_added::<Orientation>) .or(any_component_added::<Orientation>)
.or(any_component_changed::<Orientation>), .or(any_component_changed::<Orientation>),
), ),
shape_block_movement.run_if( update_relative_position,
any_component_added::<RelativePosition> update_position.after(update_relative_position),
.or(any_component_changed::<RelativePosition>)
.or(any_component_changed::<GridPosition>),
),
sync_singleton_to_ui::<Shape>.run_if(any_component_changed::<Shape>), sync_singleton_to_ui::<Shape>.run_if(any_component_changed::<Shape>),
sync_singleton_to_ui::<Orientation>.run_if(any_component_changed::<Orientation>), sync_singleton_to_ui::<Orientation>.run_if(any_component_changed::<Orientation>),
check_collision.run_if(any_component_changed::<GridPosition>),
add_piece.run_if(not(any_with_component::<Shape>)), add_piece.run_if(not(any_with_component::<Shape>)),
clear_line.run_if(any_component_changed::<LineBlocks>), clear_line.run_if(any_component_changed::<LineBlocks>),
), ),
) )
.add_systems(Update, draw_grid) .add_systems(Update, draw_grid)
.add_observer(deactive_shape)
.run(); .run();
} }
@ -115,7 +111,7 @@ struct Line(u8);
#[derive(Component, Debug)] #[derive(Component, Debug)]
struct Block; struct Block;
#[derive(Component, Event, Debug)] #[derive(Component, Event, Debug, Clone, Copy)]
#[require(GridPosition)] #[require(GridPosition)]
struct RelativePosition { struct RelativePosition {
x: i8, x: i8,
@ -124,31 +120,19 @@ struct RelativePosition {
impl RelativePosition { impl RelativePosition {
fn up() -> Self { fn up() -> Self {
RelativePosition { RelativePosition { x: 0, y: 1 }
x: 0,
y: 1,
}
} }
fn down() -> Self { fn down() -> Self {
RelativePosition { RelativePosition { x: 0, y: -1 }
x: 0,
y: -1,
}
} }
fn left() -> Self { fn left() -> Self {
RelativePosition { RelativePosition { x: -1, y: 0 }
x: -1,
y: 0,
}
} }
fn right() -> Self { fn right() -> Self {
RelativePosition { RelativePosition { x: 1, y: 0 }
x: 1,
y: 0,
}
} }
} }
@ -206,12 +190,23 @@ impl GridPosition {
self.x == other.x && self.y.saturating_sub(1) == other.y self.x == other.x && self.y.saturating_sub(1) == other.y
} }
fn add_offset(&self, RelativePosition { x: x1, y: y1 }: &RelativePosition) -> Result<Self, GameError> { fn add_relative(
let x = self.x.checked_add_signed(*x1 as i32).ok_or(GameError::OutOfBoundsLeft)?; &self,
let y = self.y.checked_add_signed(*y1 as i32).ok_or(GameError::OutOfBoundsDown)?; RelativePosition { x: x1, y: y1 }: &RelativePosition,
if x >= X_MAX { // TODO: y > Y_MAX? ) -> Result<Self, GameError> {
let x = self
.x
.checked_add_signed(*x1 as i32)
.ok_or(GameError::OutOfBoundsLeft)?;
let y = self
.y
.checked_add_signed(*y1 as i32)
.ok_or(GameError::OutOfBoundsDown)?;
if x >= X_MAX {
// TODO: y > Y_MAX?
Err(GameError::OutOfBoundsRight) Err(GameError::OutOfBoundsRight)
} else { } else {
debug!("Moving to {x},{y}");
Ok(GridPosition { x, y }) Ok(GridPosition { x, y })
} }
} }
@ -514,7 +509,7 @@ fn set_piece(
visuals: Res<Visuals>, visuals: Res<Visuals>,
) { ) {
query.iter().for_each(|(e, s, o)| { query.iter().for_each(|(e, s, o)| {
debug!("{e:?} {s:?} {o:?}"); debug!("Setting piece: {e:?} {s:?} {o:?}");
let mesh = visuals.mesh.clone(); let mesh = visuals.mesh.clone();
let mat = visuals.material.clone(); let mat = visuals.material.clone();
@ -525,7 +520,9 @@ fn set_piece(
.entity(e) .entity(e)
.with_related_entities::<ShapeBlock>(|parent| { .with_related_entities::<ShapeBlock>(|parent| {
positions.into_iter().for_each(|rp| { positions.into_iter().for_each(|rp| {
parent.spawn((Mesh2d(mesh.clone()), MeshMaterial2d(mat.clone()), rp, Block)); parent
.spawn((Mesh2d(mesh.clone()), MeshMaterial2d(mat.clone()), rp, Block))
.observe(movement);
}); });
}); });
} else { } else {
@ -537,11 +534,42 @@ fn set_piece(
}); });
} }
fn update_position(mut query: Query<(&GridPosition, &mut Transform), Changed<GridPosition>>) { fn update_position(
query.iter_mut().for_each(|(gp, mut t)| { mut changed: Query<
(Entity, &GridPosition, &mut Transform),
Or<(Added<GridPosition>, Changed<GridPosition>)>,
>,
) {
changed.iter_mut().for_each(|(e, gp, mut t)| {
let v3: Vec3 = gp.into();
debug!(
"Updating {e} with grid position {:?} to coordinates {:?}",
gp, v3
);
debug_assert!(gp.x < X_MAX, "block x > x_max"); debug_assert!(gp.x < X_MAX, "block x > x_max");
t.translation = gp.into(); t.translation = gp.into();
debug!("Updating position {:?}", t.translation); });
}
fn update_relative_position(
shape: Single<&GridPosition, With<ShapeBlocks>>,
mut query: Query<
(Entity, &mut GridPosition, &RelativePosition),
(
Without<ShapeBlocks>,
Or<(Added<RelativePosition>, Changed<RelativePosition>)>,
),
>,
) {
query.iter_mut().for_each(|(e, mut gp, rp)| {
debug!(
"Updating {e} grid position to {:?} + {:?} = {:?}",
gp,
rp,
gp.add_relative(rp).unwrap()
);
*gp = (*shape).add_relative(rp).unwrap();
}); });
} }
@ -568,13 +596,13 @@ fn kb_input(
} }
KeyCode::ArrowDown => { KeyCode::ArrowDown => {
commands.entity(e).trigger(RelativePosition::down()); commands.entity(e).trigger(RelativePosition::down());
}, }
KeyCode::ArrowLeft => { KeyCode::ArrowLeft => {
commands.entity(e).trigger(RelativePosition::left()); commands.entity(e).trigger(RelativePosition::left());
}, }
KeyCode::ArrowRight => { KeyCode::ArrowRight => {
commands.entity(e).trigger(RelativePosition::right()); commands.entity(e).trigger(RelativePosition::right());
}, }
KeyCode::Space => next.set(match curr.get() { KeyCode::Space => next.set(match curr.get() {
Falling::On => Falling::Off, Falling::On => Falling::Off,
Falling::Off => Falling::On, Falling::Off => Falling::On,
@ -607,28 +635,11 @@ fn draw_grid(mut gizmos: Gizmos) {
fn falling(mut shape: Query<Entity, With<Shape>>, mut commands: Commands) { fn falling(mut shape: Query<Entity, With<Shape>>, mut commands: Commands) {
shape.iter_mut().for_each(|e| { shape.iter_mut().for_each(|e| {
info!("Making {:?} fall", e);
commands.entity(e).trigger(RelativePosition::down()); commands.entity(e).trigger(RelativePosition::down());
}); });
} }
// Re-implement parenting for movement, but on the grid structure
fn shape_block_movement(
shape: Query<
&GridPosition,
(
With<ShapeBlocks>,
Without<RelativePosition>,
),
>,
mut blocks: Query<(&mut GridPosition, &RelativePosition), With<ShapeBlock>>,
) {
shape.iter().for_each(|shape_gp| {
blocks.iter_mut().for_each(|(mut block_gp, block_rp)| {
*block_gp = shape_gp.add_offset(block_rp).expect("Cannot move there...");
});
});
}
// Run condition that returns `true` every `n` seconds // Run condition that returns `true` every `n` seconds
// TODO: Update a resource with the current tick // TODO: Update a resource with the current tick
fn clock_cycle(n: f32) -> impl FnMut(Res<Time>, Local<f32>) -> bool { fn clock_cycle(n: f32) -> impl FnMut(Res<Time>, Local<f32>) -> bool {
@ -643,58 +654,12 @@ fn clock_cycle(n: f32) -> impl FnMut(Res<Time>, Local<f32>) -> bool {
} }
} }
/// Check if the active piece is about to collide with the ground
fn check_collision(
active: Query<(&GridPosition, Entity), (With<Block>, With<RelativePosition>)>,
shape: Single<(&GridPosition, Entity), With<ShapeBlocks>>,
inactive: Query<&GridPosition, (With<Block>, Without<RelativePosition>)>,
lines: Query<(&Line, Entity)>,
mut commands: Commands,
) {
// TODO: Easy optimizations
let hit = active.iter().any(|(a, _e)| {
debug!("Checking: {:?}", a);
// Check if active shape is at floor
let hit_floor = a.y == 0;
// Check if active peice is near other blocks
let hit_block = inactive.iter().any(|b| {
debug!("Checking against: {:?}", b);
a.is_colliding_with(b)
});
hit_floor || hit_block
});
if hit {
info!("hit detected!");
let blocks: Vec<Entity> = active
.iter()
.map(|(gp, e)| commands.entity(e).remove::<RelativePosition>().id())
.collect();
debug_assert_eq!(blocks.len(), 4, "Shapes should have 4 blocks");
info!("De-relating blocks {:?} and shape {:?}", blocks, *shape);
let (_shape_pos, shape_e) = *shape;
commands
.entity(shape_e)
.remove_related::<ShapeBlock>(blocks.as_slice())
.despawn();
active.iter().for_each(|(GridPosition { y, .. }, block_e)| {
lines.iter().for_each(|(Line(i), line_e)| {
if *y == *i as u32 {
commands.entity(line_e).add_one_related::<LineBlock>(block_e);
}
});
});
}
}
fn add_piece(mut commands: Commands) { fn add_piece(mut commands: Commands) {
// TODO: Choose a different piece // TODO: Choose a different piece
commands.spawn((Orientation::default(), GridPosition::default(), Shape::T)).observe(movement).observe(rotation); commands
.spawn((Orientation::default(), GridPosition::default(), Shape::T))
.observe(movement)
.observe(rotation);
} }
/// When a line reaches 10 blocks, clear it /// When a line reaches 10 blocks, clear it
@ -713,51 +678,77 @@ fn clear_line(
fn movement( fn movement(
trigger: Trigger<RelativePosition>, trigger: Trigger<RelativePosition>,
mut shapes: Query<&mut GridPosition, With<Shape>>, mut grid_positions: Query<&mut GridPosition, Or<(With<RelativePosition>, With<Shape>)>>,
mut active: Query<&mut GridPosition, (With<RelativePosition>, Without<Shape>)>, parent: Query<&ShapeBlocks>,
inactive: Query<&GridPosition, (Without<RelativePosition>, Without<Shape>)>, inactive: Query<
blocks: Query<&ShapeBlocks>, &GridPosition,
(
Without<RelativePosition>,
Without<Shape>,
),
>,
mut commands: Commands, mut commands: Commands,
) { ) {
// Find any errors // Do a bunch of checks if this move is valid
for block in blocks.iter_descendants(trigger.target()) { for block in parent.iter_descendants(trigger.target()) {
// Get the child's grid position let block_gp = grid_positions.get(block).unwrap();
let gp = active.get(block).unwrap();
match gp.add_offset(trigger.event()) { match block_gp.add_relative(trigger.event()) {
// If this would go out of bounds to the left or right, prevent this move
Err(GameError::OutOfBoundsLeft) | Err(GameError::OutOfBoundsRight) => { Err(GameError::OutOfBoundsLeft) | Err(GameError::OutOfBoundsRight) => {
return // Hit the left/right wall, just ignore this move
return;
} }
// If this would go out of bounds down, or cause a collision, remove the active Err(GameError::OutOfBoundsDown) => {
// component from the piece
Err(GameError::OutOfBoundsDown) | Err(GameError::Collision) => {
// remove active component
commands.entity(trigger.target()).remove::<Shape>(); commands.entity(trigger.target()).remove::<Shape>();
return return;
}
Err(GameError::Collision) => {
// Do nothing. add_relative does not return this variant
} }
Ok(new_gp) => { Ok(new_block_gp) => {
// If any children collide with the new position, remove the active component let collision = inactive
if inactive.iter().any(|other_gp| new_gp == *other_gp) { .iter()
.any(|inactive_block_gp| new_block_gp == *inactive_block_gp);
if collision {
commands.entity(trigger.target()).remove::<Shape>(); commands.entity(trigger.target()).remove::<Shape>();
return
} }
} }
} }
} }
// If the above checks went ok, we can move // Move shape itself
let mut gp = shapes.get_mut(trigger.target()).unwrap(); if let Ok(mut shape_gp) = grid_positions.get_mut(trigger.target()) {
*gp = gp.add_offset(trigger.event()).unwrap(); *shape_gp = shape_gp.add_relative(trigger.event()).unwrap();
}
// Move the blocks of this shape
parent.iter_descendants(trigger.target()).for_each(|block| {
info!("Propogating movement {:?} to child {:?}", trigger.event(), block);
commands.entity(block).trigger(*trigger.event());
});
} }
fn rotation( fn rotation(trigger: Trigger<Orientation>, mut q: Query<&mut Orientation>) {
trigger: Trigger<Orientation>, let mut o = q.get_mut(trigger.target()).unwrap();
mut q: Query<(&mut Orientation, &mut GridPosition)>,
) {
let (mut o, mut _gp) = q.get_mut(trigger.target()).unwrap();
// If children would go out of bounds to the left // If children would go out of bounds to the left
// If children would not go out of bounds to the right // If children would not go out of bounds to the right
// If children would not go out of bounds to the down // If children would not go out of bounds to the down
*o = trigger.event().clone(); *o = trigger.event().clone();
} }
// TODO: Just despawn?
fn deactive_shape(
trigger: Trigger<OnRemove, Shape>,
parent: Query<&ShapeBlocks>,
mut commands: Commands,
) {
let v: Vec<Entity> = parent.iter_descendants(trigger.target()).collect();
parent.iter_descendants(trigger.target()).for_each(|block| {
commands.entity(block).remove::<RelativePosition>();
});
commands
.entity(trigger.target())
.remove_related::<ShapeBlock>(v.as_slice())
.despawn();
}

Loading…
Cancel
Save