selection refactor; not working

selection-refactor
Elijah Voigt 2 years ago
parent f32d462df6
commit 3473738edc

@ -56,17 +56,22 @@ fn audio_events(
mut reader: EventReader<game::GameEvent>,
studio: Res<FmodStudio>,
mut commands: Commands,
pieces: Query<&game::Piece>,
) {
reader.iter().for_each(|event| match event {
game::GameEvent::SelectPiece => {
game::GameEvent::Select(entity, _) => {
if pieces.contains(*entity) {
commands.spawn(AudioSource::new(
studio.0.get_event("event:/SFX/PickUpPiece").unwrap(),
));
}
game::GameEvent::PlacePiece => {
}
game::GameEvent::Place(entity, _) => {
if pieces.contains(*entity) {
commands.spawn(AudioSource::new(
studio.0.get_event("event:/SFX/PutDownPiece").unwrap(),
));
}
}
})
}

@ -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::<MouseButtonInput>()),
move_piece
.run_if(in_state(GameState::Display2d))
.run_if(any_with_component::<game::Selected>()),
place_piece
.run_if(in_state(GameState::Display2d))
.run_if(any_with_component::<game::Selected>()),
cancel_place
.run_if(in_state(GameState::Display2d))
.run_if(on_event::<MouseButtonInput>()),
snap_back_cancel
.run_if(in_state(GameState::Display2d))
.run_if(any_component_removed::<game::Selected>()),
update_background.run_if(on_event::<WindowResized>()),
set_transform
.after(game::update_board::<Display2d>)
.run_if(any_component_changed::<BoardIndex>),
set_2d_transform
.run_if(any_component_changed::<BoardIndex>)
.before(game::update_board::<Display2d>)
.after(game::manage_piece),
set_piece_sprite.run_if(any_component_changed::<Side>),
set_tile_sprite.run_if(any_component_added::<game::Tile>),
),
@ -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<Display2d>,
Or<(Changed<BoardIndex>, Added<BoardIndex>)>,
),
>,
pieces: Query<Entity, (With<game::Piece>, With<Display2d>)>,
) {
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<CursorMoved>,
sprite_q: Query<(
fn select_2d(
mut events: EventReader<MouseButtonInput>,
sprite_q: Query<
(
Entity,
&TextureAtlasSprite,
&Handle<TextureAtlas>,
&GlobalTransform,
&BoardIndex,
)>,
camera_q: Query<(&Camera, &GlobalTransform), With<Display2d>>,
),
(Without<game::Selected>, With<Display2d>),
>,
cameras: Query<(&Camera, &GlobalTransform), With<Display2d>>,
atlases: Res<Assets<TextureAtlas>>,
mut active: ResMut<ActiveTile>,
windows: Query<&Window, With<PrimaryWindow>>,
mut writer: EventWriter<game::GameEvent>,
) {
events.iter().for_each(|CursorMoved { position, .. }| {
if let Some(position) = camera_q
events
.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)| {
.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
let pos = transform.translation();
// 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"))
.map(|atlas| {
atlas
.textures
.get(*index)
.expect("Get this rect texture")
})
.expect("get this rect")
.size();
let (transform_scale, _, _) = transform.to_scale_rotation_translation();
let (transform_scale, _, _) =
transform.to_scale_rotation_translation();
sprite_size * transform_scale.truncate()
};
let center = pos.truncate() - (anchor.as_vec() * size);
let center = p.truncate() - (anchor.as_vec() * size);
let rect = Rect::from_center_half_size(center, size / 2.0);
rect.contains(position).then_some(board_index)
rect.contains(pos).then_some((entity, board_index, p.z))
},
);
if active.idx != idx.cloned() {
active.idx = idx.cloned();
}
}
});
}
fn select_piece(
mut events: EventReader<MouseButtonInput>,
pieces: Query<(Entity, &BoardIndex), (With<game::Piece>, With<Display2d>)>,
active: Res<ActiveTile>,
mut commands: Commands,
mut writer: EventWriter<game::GameEvent>,
) {
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))
})
})
)
.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<MouseButtonInput>,
current: Query<
active_piece: Query<
(Entity, &BoardIndex),
(With<game::Selected>, With<game::Piece>, With<Display2d>),
(With<game::Piece>, With<game::Selected>, With<Display2d>),
>,
pieces: Query<&BoardIndex, (Without<game::Selected>, With<game::Piece>, With<Display2d>)>,
active: Res<ActiveTile>,
active_tile: Query<&BoardIndex, (With<game::Tile>, With<game::Selected>, With<Display2d>)>,
mut board: ResMut<Board>,
mut commands: Commands,
mut game_events: EventWriter<game::GameEvent>,
mut move_events: EventWriter<game::Move>,
) {
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::<game::Selected>();
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<MouseButtonInput>,
current: Query<Entity, (With<game::Selected>, With<game::Piece>, With<Display2d>)>,
mut commands: Commands,
mut writer: EventWriter<game::GameEvent>,
) {
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::<game::Selected>();
writer.send(game::GameEvent::PlacePiece);
}
})
}
fn snap_back_cancel(
mut events: RemovedComponents<game::Selected>,
query: Query<&game::BoardIndex, (With<game::Piece>, With<Display2d>)>,
mut move_events: EventWriter<game::Move>,
) {
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()),
});
}
})
}

@ -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::<MouseButtonInput>()),
pick_up.run_if(any_component_added::<game::Selected>),
put_down.run_if(any_component_removed::<game::Selected>()),
place_piece.run_if(any_component_changed::<game::BoardIndex>),
),
)
.add_systems(
@ -153,6 +153,7 @@ fn initialize(mut commands: Commands, board: Res<game::Board>, assets: Res<Asset
Display3d,
index,
tile,
game::Selecting,
PbrBundle {
mesh: assets.hitbox_shape.clone(),
material: assets.hitbox_material.clone(),
@ -164,13 +165,12 @@ fn initialize(mut commands: Commands, board: Res<game::Board>, assets: Res<Asset
// Pieces
board.pieces().iter().for_each(|(index, piece)| {
let side = Board::side(index).expect("Spawn valid side");
parent.spawn((
side,
Board::side(index).expect("Spawn valid side"),
Display3d,
piece.clone(),
index.clone(),
game::Selecting,
SceneBundle { ..default() },
));
});
@ -415,71 +415,73 @@ fn set_piece_texture(
/// Function for selecting entities based on ray intersection
/// There is a bug where we are selecting multiple entities...
/// Refactor:
/// * Emit event that an object was selected by finding the parent (or self) with the Selecting component.
/// * Downstream systems can listen for this event and act accoringly (piece, tiles, etc)
fn select_3d(
mut events: EventReader<MouseButtonInput>,
mut writer: EventWriter<game::GameEvent>,
query: Query<(Entity, &Handle<Mesh>, &GlobalTransform)>,
meshes: Res<Assets<Mesh>>,
cameras: Query<(&Camera, &GlobalTransform)>,
windows: Query<&Window, With<PrimaryWindow>>,
parents: Query<Entity, (With<game::Piece>, With<Display3d>)>,
children: Query<&Children>,
mut commands: Commands,
selected: Query<Entity, (With<game::Selected>, With<game::Piece>, With<Display3d>)>,
candidates: Query<(Entity, &game::BoardIndex), With<Display3d>>,
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, &gt).and_then(|_hit| {
parents
.iter()
.find(|&parent| {
if let Some(_hit) = hit3d::intersects(&ray, mesh, &gt) {
// 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_descendants(parent)
.any(|child| child == entity)
})
.iter_ancestors(entity)
.find_map(|a| candidates.get(a).ok())
.iter()
.for_each(|&parent| {
selected.iter().for_each(|s| {
if s != parent {
commands
.entity(s)
.remove::<game::Selected>();
}
});
commands.entity(parent).insert(game::Selected);
});
Some(())
});
});
Some(())
.for_each(|(e, &ref i)| {
writer.send(game::GameEvent::Select(
*e,
i.clone(),
));
});
}
}
});
Some(())
});
});
});
}
fn selected_gizmo(
selected: Query<&Transform, (With<game::Selected>, With<game::Piece>, With<Display3d>)>,
selected: Query<&GlobalTransform, (With<game::Selected>, With<Display3d>)>,
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<game::Selected>,
mut query: Query<&game::Piece, (With<game::Piece>, With<Display3d>)>,
@ -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<Display3d>, Added<game::Tile>)>,
) {
@ -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<Display3d>,
With<game::Piece>,
Changed<game::BoardIndex>,
),
>,
) {
events
.iter_mut()
.for_each(|(entity, idx, mut transform)| todo!("Place 3d piece in new location"))
}

@ -13,6 +13,8 @@ impl Plugin for GamePlugin {
(
update_board::<display2d::Display2d>.run_if(on_event::<Move>()),
update_board::<display3d::Display3d>.run_if(on_event::<Move>()),
select_tile.run_if(on_event::<game::GameEvent>()),
manage_piece.run_if(on_event::<game::GameEvent>()),
set_side.run_if(on_event::<Move>()), // 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<Board
Err(e) => warn!("{:?}", e),
});
}
/// Manage selection of 3d entities
pub(crate) fn manage_piece(
mut events: EventReader<game::GameEvent>,
mut pieces: Query<(Entity, &mut BoardIndex), With<Piece>>,
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::Selected>();
}
});
}
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::<game::Selected>();
}
}
});
}
/// Manage selection of tiles
fn select_tile(
mut events: EventReader<game::GameEvent>,
tiles: Query<Entity, With<game::Tile>>,
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::Selected>();
}
}),
game::GameEvent::Place(_, _) => error!("Cannot place tiles!"),
})
}

Loading…
Cancel
Save