From 3473738edcf6c3fdc81cf21e5040cd655a7287c4 Mon Sep 17 00:00:00 2001 From: Elijah Voigt Date: Tue, 31 Oct 2023 22:33:49 -0700 Subject: [PATCH] selection refactor; not working --- src/audio.rs | 21 ++-- src/display2d.rs | 243 ++++++++++++++++++++--------------------------- src/display3d.rs | 98 +++++++++++-------- src/game.rs | 62 +++++++++++- 4 files changed, 234 insertions(+), 190 deletions(-) diff --git a/src/audio.rs b/src/audio.rs index e658718..71c0b1f 100644 --- a/src/audio.rs +++ b/src/audio.rs @@ -56,17 +56,22 @@ fn audio_events( mut reader: EventReader, studio: Res, mut commands: Commands, + pieces: Query<&game::Piece>, ) { reader.iter().for_each(|event| match event { - game::GameEvent::SelectPiece => { - commands.spawn(AudioSource::new( - studio.0.get_event("event:/SFX/PickUpPiece").unwrap(), - )); + game::GameEvent::Select(entity, _) => { + if pieces.contains(*entity) { + commands.spawn(AudioSource::new( + studio.0.get_event("event:/SFX/PickUpPiece").unwrap(), + )); + } } - game::GameEvent::PlacePiece => { - commands.spawn(AudioSource::new( - studio.0.get_event("event:/SFX/PutDownPiece").unwrap(), - )); + game::GameEvent::Place(entity, _) => { + if pieces.contains(*entity) { + commands.spawn(AudioSource::new( + studio.0.get_event("event:/SFX/PutDownPiece").unwrap(), + )); + } } }) } diff --git a/src/display2d.rs b/src/display2d.rs index 13493ae..a478fd7 100644 --- a/src/display2d.rs +++ b/src/display2d.rs @@ -8,10 +8,7 @@ use bevy::{ /// TODO: Custom Asset: SpriteSheetAtlas Mapper /// TODO: Handle Cursor! /// -use crate::{ - game::{ActiveTile, Board, BoardIndex, Piece, Side, Tile}, - prelude::*, -}; +use crate::{game::*, prelude::*}; const SCALE: f32 = 4.0; const TILE_SIZE: f32 = 16.0; @@ -26,25 +23,19 @@ impl Plugin for Display2dPlugin { .add_systems( Update, ( - active_tile.run_if(in_state(GameState::Display2d)), menu::exit_to_menu.run_if(in_state(GameState::Display2d)), - select_piece.run_if(in_state(GameState::Display2d)), + select_2d.run_if(on_event::()), move_piece .run_if(in_state(GameState::Display2d)) .run_if(any_with_component::()), place_piece .run_if(in_state(GameState::Display2d)) .run_if(any_with_component::()), - cancel_place - .run_if(in_state(GameState::Display2d)) - .run_if(on_event::()), - snap_back_cancel - .run_if(in_state(GameState::Display2d)) - .run_if(any_component_removed::()), update_background.run_if(on_event::()), - set_transform - .after(game::update_board::) - .run_if(any_component_changed::), + set_2d_transform + .run_if(any_component_changed::) + .before(game::update_board::) + .after(game::manage_piece), set_piece_sprite.run_if(any_component_changed::), set_tile_sprite.run_if(any_component_added::), ), @@ -232,91 +223,99 @@ fn set_tile_sprite( } /// Sets a piece location given it's board index -fn set_transform( +fn set_2d_transform( mut events: Query< - (&mut Transform, &BoardIndex), + (Entity, &mut Transform, &BoardIndex), ( With, Or<(Changed, Added)>, ), >, + pieces: Query, With)>, ) { - events.iter_mut().for_each(|(mut t, i)| { + events.iter_mut().for_each(|(e, mut t, i)| { let x = SCALE * 16.0 * ((i.x as f32) - 3.5); let y = SCALE * 16.0 * ((i.y as f32) - 1.5); - *t = Transform::from_scale(Vec3::splat(SCALE)).with_translation(Vec3::new(x, y, 0.0)); + let z = if pieces.contains(e) { 1.0 } else { 0.0 }; + *t = Transform::from_scale(Vec3::splat(SCALE)).with_translation(Vec3::new(x, y, z)); debug!("setting position of {:?} to {:?}", i, t); }); } -fn active_tile( - mut events: EventReader, - sprite_q: Query<( - &TextureAtlasSprite, - &Handle, - &GlobalTransform, - &BoardIndex, - )>, - camera_q: Query<(&Camera, &GlobalTransform), With>, - atlases: Res>, - mut active: ResMut, -) { - events.iter().for_each(|CursorMoved { position, .. }| { - if let Some(position) = camera_q - .iter() - .find_map(|(camera, transform)| camera.viewport_to_world_2d(transform, *position)) - { - let idx = sprite_q.iter().find_map( - |(TextureAtlasSprite { index, anchor, .. }, handle, transform, board_index)| { - // Implementation credit goes to the sprite bevy_mod_picking backend - // TODO: Upstream changes - let pos = transform.translation(); - - let size = { - let sprite_size = atlases - .get(handle) - .map(|atlas| atlas.textures.get(*index).expect("Get this rect texture")) - .expect("get this rect") - .size(); - let (transform_scale, _, _) = transform.to_scale_rotation_translation(); - sprite_size * transform_scale.truncate() - }; - - let center = pos.truncate() - (anchor.as_vec() * size); - let rect = Rect::from_center_half_size(center, size / 2.0); - - rect.contains(position).then_some(board_index) - }, - ); - if active.idx != idx.cloned() { - active.idx = idx.cloned(); - } - } - }); -} - -fn select_piece( +fn select_2d( mut events: EventReader, - pieces: Query<(Entity, &BoardIndex), (With, With)>, - active: Res, - mut commands: Commands, + sprite_q: Query< + ( + Entity, + &TextureAtlasSprite, + &Handle, + &GlobalTransform, + &BoardIndex, + ), + (Without, With), + >, + cameras: Query<(&Camera, &GlobalTransform), With>, + atlases: Res>, + windows: Query<&Window, With>, mut writer: EventWriter, ) { events .iter() - .filter_map(|MouseButtonInput { button, state, .. }| { - active.idx.as_ref().and_then(|index| { - pieces.iter().find_map(|(entity, board_idx)| { - (board_idx == index).then_some((entity, button, state)) + .filter(|ev| ev.state == ButtonState::Pressed) + .for_each(|_| { + windows + .iter() + .filter_map(|window| window.cursor_position()) + .find_map(|position| { + cameras + .iter() + .filter_map(|(camera, gt)| camera.viewport_to_world_2d(gt, position)) + .find_map(|pos| { + // TODO: Sort by Z-Index + sprite_q + .iter() + .filter_map( + |( + entity, + TextureAtlasSprite { index, anchor, .. }, + handle, + transform, + board_index, + )| { + // Implementation credit goes to the sprite bevy_mod_picking backend + // TODO: Upstream changes (related to sprite atlas correct behavior) + let p = transform.translation(); + + let size = { + let sprite_size = atlases + .get(handle) + .map(|atlas| { + atlas + .textures + .get(*index) + .expect("Get this rect texture") + }) + .expect("get this rect") + .size(); + let (transform_scale, _, _) = + transform.to_scale_rotation_translation(); + sprite_size * transform_scale.truncate() + }; + + let center = p.truncate() - (anchor.as_vec() * size); + let rect = Rect::from_center_half_size(center, size / 2.0); + + rect.contains(pos).then_some((entity, board_index, p.z)) + }, + ) + .max_by_key(|(_, _, z)| z.round() as usize) + }) }) - }) - }) - .filter_map(|(entity, button, state)| { - ((*button, *state) == (MouseButton::Left, ButtonState::Pressed)).then_some(entity) - }) - .for_each(|entity| { - commands.entity(entity).insert(game::Selected); - writer.send(game::GameEvent::SelectPiece); + .iter() + .for_each(|(e, &ref i, _)| { + info!("Select piece in 2d {:?} {:?}", e, i); + writer.send(game::GameEvent::Select(*e, i.clone())); + }); }); } @@ -339,86 +338,48 @@ fn move_piece( } /// -/// TODO: Only place piece if spot is valid move for the selected entity +/// 2D Placement Logic /// fn place_piece( mut events: EventReader, - current: Query< + active_piece: Query< (Entity, &BoardIndex), - (With, With, With), + (With, With, With), >, - pieces: Query<&BoardIndex, (Without, With, With)>, - active: Res, + active_tile: Query<&BoardIndex, (With, With, With)>, mut board: ResMut, - mut commands: Commands, mut game_events: EventWriter, mut move_events: EventWriter, ) { events .iter() - .filter_map(|MouseButtonInput { button, state, .. }| { - if (*button, *state) == (MouseButton::Left, ButtonState::Pressed) { - active.idx.as_ref() - } else { - None - } + .filter_map(|event| match event { + MouseButtonInput { + button: MouseButton::Left, + state: ButtonState::Pressed, + .. + } => active_tile.get_single().ok(), + _ => None, }) - .filter_map(|idx| { - if !current.is_empty() { - (!pieces.iter().any(|board_index| board_index == idx)).then_some(( - current.single().0, - current.single().1, - idx, - )) - } else { - None - } + .filter_map(|to| { + active_piece + .get_single() + .map(|(entity, from)| (entity, from, to)) + .ok() }) .for_each(|(entity, from, to)| match board.move_piece(from, to) { Ok(moves) => { - commands.entity(entity).remove::(); + info!("Moving piece {:?} {:?} -> {:?}", entity, from, to); moves.iter().for_each(|m| { move_events.send(m.clone()); }); - game_events.send(game::GameEvent::PlacePiece); + game_events.send(game::GameEvent::Place(entity, to.clone())); + } + Err(game::GameError::NullMove) => { + warn!("Null move!"); + game_events.send(game::GameEvent::Place(entity, from.clone())) } - Err(game::GameError::NullMove) => game_events.send(game::GameEvent::PlacePiece), Err(game::GameError::InvalidMove) => warn!("Invalid move!"), Err(game::GameError::InvalidIndex) => warn!("Invalid index!"), }) } - -fn cancel_place( - mut events: EventReader, - current: Query, With, With)>, - mut commands: Commands, - mut writer: EventWriter, -) { - events - .iter() - .filter(|MouseButtonInput { button, state, .. }| { - (*button, *state) == (MouseButton::Right, ButtonState::Pressed) - }) - .for_each(|_| { - if let Ok(entity) = current.get_single() { - commands.entity(entity).remove::(); - writer.send(game::GameEvent::PlacePiece); - } - }) -} - -fn snap_back_cancel( - mut events: RemovedComponents, - query: Query<&game::BoardIndex, (With, With)>, - mut move_events: EventWriter, -) { - events.iter().for_each(|entity| { - if let Ok(idx) = query.get(entity) { - move_events.send(game::Move { - epoch: 0, - from: idx.clone(), - to: Some(idx.clone()), - }); - } - }) -} diff --git a/src/display3d.rs b/src/display3d.rs index aae1a63..3bbc435 100644 --- a/src/display3d.rs +++ b/src/display3d.rs @@ -7,7 +7,6 @@ use crate::{ }; use bevy::{ core_pipeline::Skybox, - ecs::removal_detection::RemovedComponentReader, input::mouse::{MouseButtonInput, MouseMotion, MouseWheel}, render::render_resource::{TextureViewDescriptor, TextureViewDimension}, window::PrimaryWindow, @@ -36,6 +35,7 @@ impl Plugin for Display3dPlugin { .run_if(on_event::()), pick_up.run_if(any_component_added::), put_down.run_if(any_component_removed::()), + place_piece.run_if(any_component_changed::), ), ) .add_systems( @@ -153,6 +153,7 @@ fn initialize(mut commands: Commands, board: Res, assets: Res, assets: Res, + mut writer: EventWriter, query: Query<(Entity, &Handle, &GlobalTransform)>, meshes: Res>, cameras: Query<(&Camera, &GlobalTransform)>, windows: Query<&Window, With>, - parents: Query, With)>, - children: Query<&Children>, - mut commands: Commands, - selected: Query, With, With)>, + candidates: Query<(Entity, &game::BoardIndex), With>, + children: Query<&Parent>, ) { events .iter() .filter(|ev| ev.state == ButtonState::Pressed) .for_each(|_| { - windows.iter().for_each(|window| { - window.cursor_position().and_then(|pos| { - cameras.iter().for_each(|(camera, gt)| { - camera.viewport_to_world(gt, pos).and_then(|ray| { + windows + .iter() + .filter_map(|window| window.cursor_position()) + .for_each(|pos| { + cameras + .iter() + .filter_map(|(camera, gt)| camera.viewport_to_world(gt, pos)) + .for_each(|ray| { query .iter() .filter_map(|(entity, handle, gt)| { meshes.get(handle).map(|mesh| (entity, mesh, gt)) }) .for_each(|(entity, mesh, gt)| { - hit3d::intersects(&ray, mesh, >).and_then(|_hit| { - parents - .iter() - .find(|&parent| { - children - .iter_descendants(parent) - .any(|child| child == entity) - }) - .iter() - .for_each(|&parent| { - selected.iter().for_each(|s| { - if s != parent { - commands - .entity(s) - .remove::(); - } + if let Some(_hit) = hit3d::intersects(&ray, mesh, >) { + // The hit was on an entity directly + if let Ok((e, i)) = candidates.get(entity) { + writer.send(game::GameEvent::Select(e, i.clone())); + // The hit was on an entity which may have a parent which + // may be selectable + } else { + children + .iter_ancestors(entity) + .find_map(|a| candidates.get(a).ok()) + .iter() + .for_each(|(e, &ref i)| { + writer.send(game::GameEvent::Select( + *e, + i.clone(), + )); }); - commands.entity(parent).insert(game::Selected); - }); - Some(()) - }); + } + } }); - Some(()) }); - }); - Some(()) }); - }); }); } fn selected_gizmo( - selected: Query<&Transform, (With, With, With)>, + selected: Query<&GlobalTransform, (With, With)>, mut gizmos: Gizmos, ) { - selected.iter().for_each(|transform| { - gizmos.cuboid(transform.clone(), Color::GREEN); + selected.iter().for_each(|gt| { + info!("Adding selected gizmo at {:?}", gt); + gizmos.cuboid(gt.clone(), Color::GREEN); }) } +/// Pick up a piece when it is selected fn pick_up( mut events: Query< (Entity, &game::Piece), @@ -518,6 +520,7 @@ fn pick_up( }); } +/// Put down a piece when it is de-selected fn put_down( mut events: RemovedComponents, mut query: Query<&game::Piece, (With, With)>, @@ -547,6 +550,7 @@ fn put_down( }) } +/// Set the transformation of a tile hitbox fn set_tile_hitbox( mut events: Query<(&mut Transform, &BoardIndex), (With, Added)>, ) { @@ -554,3 +558,19 @@ fn set_tile_hitbox( *transform = Transform::from_translation(board_translation(index)); }); } + +// Place a piece at a newly selected tile +fn place_piece( + mut events: Query< + (Entity, &BoardIndex, &mut Transform), + ( + With, + With, + Changed, + ), + >, +) { + events + .iter_mut() + .for_each(|(entity, idx, mut transform)| todo!("Place 3d piece in new location")) +} diff --git a/src/game.rs b/src/game.rs index 8afa5c6..f1590af 100644 --- a/src/game.rs +++ b/src/game.rs @@ -13,6 +13,8 @@ impl Plugin for GamePlugin { ( update_board::.run_if(on_event::()), update_board::.run_if(on_event::()), + select_tile.run_if(on_event::()), + manage_piece.run_if(on_event::()), set_side.run_if(on_event::()), // TODO: correct run_if? ), ) @@ -73,10 +75,12 @@ pub(crate) enum GameError { InvalidIndex, } +/// Generic event for selecting an entity with a BoardIndex. +/// This can be a Piece or a Tile/Hitbox. #[derive(Debug, Event)] pub(crate) enum GameEvent { - SelectPiece, - PlacePiece, + Select(Entity, BoardIndex), + Place(Entity, BoardIndex), } /// The board is setup like this: @@ -210,6 +214,9 @@ pub(crate) struct ActiveTile { #[derive(Debug, Default, Component)] pub(crate) struct Selected; +#[derive(Debug, Default, Component)] +pub(crate) struct Selecting; + fn setup_board(mut commands: Commands) { use Piece::*; commands.insert_resource(Board { @@ -312,3 +319,54 @@ pub(crate) fn set_side(mut events: Query<(&mut Side, &BoardIndex), Changed warn!("{:?}", e), }); } + +/// Manage selection of 3d entities +pub(crate) fn manage_piece( + mut events: EventReader, + mut pieces: Query<(Entity, &mut BoardIndex), With>, + mut commands: Commands, +) { + events.iter().for_each(|event| match event { + game::GameEvent::Select(entity, _) => { + pieces.iter().for_each(|(e, _)| { + if e == *entity { + info!("Marking Piece {:?} as selected", e); + commands.entity(e).insert(game::Selected); + } else { + commands.entity(e).remove::(); + } + }); + } + game::GameEvent::Place(entity, index) => { + if let Ok((_, mut idx)) = pieces.get_mut(*entity) { + info!("Placing Piece {:?} at {:?}", entity, idx); + *idx = index.clone(); + + debug!("Removing selected marker"); + commands + .entity(*entity) + .insert(index.clone()) + .remove::(); + } + } + }); +} + +/// Manage selection of tiles +fn select_tile( + mut events: EventReader, + tiles: Query>, + mut commands: Commands, +) { + events.iter().for_each(|event| match event { + game::GameEvent::Select(entity, _) => tiles.iter().for_each(|e| { + if e == *entity { + info!("Marking Tile {:?} as selected", e); + commands.entity(*entity).insert(game::Selected); + } else { + commands.entity(*entity).remove::(); + } + }), + game::GameEvent::Place(_, _) => error!("Cannot place tiles!"), + }) +}