Select refactors for 2d, sync select/deselects

bevy0.12
Elijah C. Voigt 2 years ago
parent fdd3ccaa84
commit 729c74e932

BIN
assets/models/Martian Chess.glb (Stored with Git LFS)

Binary file not shown.

@ -1,15 +1,10 @@
use bevy::{
input::{mouse::MouseButtonInput, ButtonState},
window::{PrimaryWindow, WindowResized},
};
///
/// TODO: Pick up and move pieces! /// TODO: Pick up and move pieces!
/// TODO: Custom Asset: SpriteSheetAtlas Mapper /// TODO: Custom Asset: SpriteSheetAtlas Mapper
/// TODO: Handle Cursor! /// TODO: Handle Cursor!
/// use bevy::window::{PrimaryWindow, WindowResized};
use crate::{ use crate::{
game::{ActiveTile, Board, BoardIndex, Piece, Side, Tile}, game::{Board, BoardIndex, Piece, Side, Tile},
prelude::*, prelude::*,
}; };
@ -26,18 +21,15 @@ impl Plugin for Display2dPlugin {
.add_systems( .add_systems(
Update, Update,
( (
active_tile.run_if(in_state(GameState::Display2d)),
menu::exit_to_menu.run_if(in_state(GameState::Display2d)), menu::exit_to_menu.run_if(in_state(GameState::Display2d)),
select_piece.run_if(in_state(GameState::Display2d)),
move_piece move_piece
.run_if(in_state(GameState::Display2d)) .run_if(in_state(GameState::Display2d))
.run_if(any_with_component::<game::Selected>()), .run_if(any_with_component::<game::Selected>()),
place_piece select.run_if(in_state(GameState::Display2d)).run_if(
.run_if(in_state(GameState::Display2d)) |buttons: Res<Input<MouseButton>>| -> bool {
.run_if(any_with_component::<game::Selected>()), buttons.just_pressed(MouseButton::Left)
cancel_place },
.run_if(in_state(GameState::Display2d)) ),
.run_if(on_event::<MouseButtonInput>()),
snap_back_cancel snap_back_cancel
.run_if(in_state(GameState::Display2d)) .run_if(in_state(GameState::Display2d))
.run_if(any_component_removed::<game::Selected>()), .run_if(any_component_removed::<game::Selected>()),
@ -157,7 +149,13 @@ fn initialize_board(board: Option<Res<Board>>, mut commands: Commands) {
.with_children(|parent| { .with_children(|parent| {
// Spawn tiles // Spawn tiles
game::tiles().for_each(|(index, tile)| { game::tiles().for_each(|(index, tile)| {
parent.spawn((tile, index, Display2d, SpriteSheetBundle { ..default() })); parent.spawn((
tile,
index,
Display2d,
SpriteSheetBundle { ..default() },
game::Selectable,
));
}); });
// Spawn pieces // Spawn pieces
@ -170,6 +168,7 @@ fn initialize_board(board: Option<Res<Board>>, mut commands: Commands) {
index.clone(), index.clone(),
side, side,
SpriteSheetBundle { ..default() }, SpriteSheetBundle { ..default() },
game::Selectable,
)); ));
}); });
}); });
@ -249,74 +248,67 @@ fn set_transform(
}); });
} }
fn active_tile( /// Select pieces and tiles in 2d
mut events: EventReader<CursorMoved>, fn select(
sprite_q: Query<( candidates: Query<
(
Entity,
&TextureAtlasSprite, &TextureAtlasSprite,
&Handle<TextureAtlas>, &Handle<TextureAtlas>,
&GlobalTransform, &GlobalTransform,
&BoardIndex, ),
)>, (
camera_q: Query<(&Camera, &GlobalTransform), With<Display2d>>, With<game::Selectable>,
Without<game::Selected>,
With<Display2d>,
),
>,
windows: Query<&Window, With<PrimaryWindow>>,
cameras: Query<(&Camera, &GlobalTransform), With<Display2d>>,
atlases: Res<Assets<TextureAtlas>>, atlases: Res<Assets<TextureAtlas>>,
mut active: ResMut<ActiveTile>, mut commands: Commands,
) { ) {
events.iter().for_each(|CursorMoved { position, .. }| { // For each window (there is only one)
if let Some(position) = camera_q windows
.iter() .iter()
.find_map(|(camera, transform)| camera.viewport_to_world_2d(transform, *position)) .filter_map(|window| window.cursor_position())
{ .for_each(|position| {
let idx = sprite_q.iter().find_map( // For each 2d Camera (there is only one)
|(TextureAtlasSprite { index, anchor, .. }, handle, transform, board_index)| { cameras
// Implementation credit goes to the sprite bevy_mod_picking backend .iter()
// TODO: Upstream changes .filter_map(|(camera, transform)| camera.viewport_to_world_2d(transform, position))
let pos = transform.translation(); .for_each(|pos| {
// For each selectable sprite (there are many)
let size = { // Filter down to the list of hit objects
candidates
.iter()
.filter_map(
|(
entity,
TextureAtlasSprite { index, anchor, .. },
handle,
transform,
)| {
let sprite_size = atlases let sprite_size = atlases
.get(handle) .get(handle)
.map(|atlas| atlas.textures.get(*index).expect("Get this rect texture")) .map(|atlas| {
.expect("get this rect") atlas.textures.get(*index).expect("Atlas Sprite Texture")
})
.expect("Atlas Sprite Rectangle")
.size(); .size();
let (transform_scale, _, _) = transform.to_scale_rotation_translation(); hit::intersects2d(sprite_size, anchor, transform, pos)
sprite_size * transform_scale.truncate() .then_some((entity, transform))
};
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() { .max_by(|(_, a), (_, b)| {
active.idx = idx.cloned(); a.translation().z.partial_cmp(&b.translation().z).unwrap()
}
}
});
}
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))
})
})
})
.filter_map(|(entity, button, state)| {
((*button, *state) == (MouseButton::Left, ButtonState::Pressed)).then_some(entity)
}) })
.for_each(|entity| { .iter()
commands.entity(entity).insert(game::Selected); .for_each(|(entity, _)| {
writer.send(game::GameEvent::SelectPiece); info!("Selected entity {:?}", entity);
commands.entity(*entity).insert(game::Selected);
});
});
}); });
} }
@ -338,75 +330,6 @@ fn move_piece(
}) })
} }
///
/// TODO: Only place piece if spot is valid move for the selected entity
///
fn place_piece(
mut events: EventReader<MouseButtonInput>,
current: Query<
(Entity, &BoardIndex),
(With<game::Selected>, With<game::Piece>, With<Display2d>),
>,
pieces: Query<&BoardIndex, (Without<game::Selected>, With<game::Piece>, With<Display2d>)>,
active: Res<ActiveTile>,
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(|idx| {
if !current.is_empty() {
(!pieces.iter().any(|board_index| board_index == idx)).then_some((
current.single().0,
current.single().1,
idx,
))
} else {
None
}
})
.for_each(|(entity, from, to)| match board.move_piece(from, to) {
Ok(moves) => {
commands.entity(entity).remove::<game::Selected>();
moves.iter().for_each(|m| {
move_events.send(m.clone());
});
game_events.send(game::GameEvent::PlacePiece);
}
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( fn snap_back_cancel(
mut events: RemovedComponents<game::Selected>, mut events: RemovedComponents<game::Selected>,
query: Query<&game::BoardIndex, (With<game::Piece>, With<Display2d>)>, query: Query<&game::BoardIndex, (With<game::Piece>, With<Display2d>)>,
@ -415,9 +338,9 @@ fn snap_back_cancel(
events.iter().for_each(|entity| { events.iter().for_each(|entity| {
if let Ok(idx) = query.get(entity) { if let Ok(idx) = query.get(entity) {
move_events.send(game::Move { move_events.send(game::Move {
epoch: 0,
from: idx.clone(), from: idx.clone(),
to: Some(idx.clone()), to: Some(idx.clone()),
..default()
}); });
} }
}) })

@ -1,15 +1,23 @@
// TODO: Camera Animations
// Intro animation
// Turn changing animations
// TODO: Pickup/put-down sound in 3d
// TODO: Unify the selection logic // TODO: Unify the selection logic
// If you select in 2d, it should "be" selected in 3d. // If you select in 2d, it should "be" selected in 3d.
// And if you select in 3d, 2d is selected. // And if you select in 3d, 2d is selected.
// TODO: De-select pieces
use crate::{ use crate::{
game::{Board, BoardIndex, Piece, Side}, game::{Board, BoardIndex, Piece, Side},
prelude::*, prelude::*,
}; };
use bevy::{ use bevy::{
core_pipeline::Skybox, core_pipeline::{tonemapping::DebandDither, Skybox},
ecs::removal_detection::RemovedComponentReader, input::mouse::{MouseButtonInput, MouseMotion, MouseScrollUnit, MouseWheel},
input::mouse::{MouseButtonInput, MouseMotion, MouseWheel}, render::{
render::render_resource::{TextureViewDescriptor, TextureViewDimension}, render_resource::{TextureViewDescriptor, TextureViewDimension},
view::ColorGrading,
},
window::PrimaryWindow, window::PrimaryWindow,
}; };
@ -25,13 +33,14 @@ impl Plugin for Display3dPlugin {
.add_systems( .add_systems(
Update, Update,
( (
hydrate_camera, // TODO: add run_if...
menu::exit_to_menu.run_if(in_state(GameState::Display3d)), menu::exit_to_menu.run_if(in_state(GameState::Display3d)),
set_piece_model.run_if(any_component_added::<Piece>), set_piece_model.run_if(any_component_added::<Piece>),
set_board_model.run_if(any_component_added::<game::BoardComponent>), set_board_model.run_if(any_component_added::<game::BoardComponent>),
set_tile_hitbox.run_if(any_component_added::<game::Tile>), set_tile_hitbox.run_if(any_component_added::<game::Tile>),
set_piece_position.run_if(any_component_changed::<BoardIndex>), set_piece_position.run_if(any_component_changed::<BoardIndex>),
set_piece_texture.run_if(any_component_changed::<Side>), set_piece_texture.run_if(any_component_changed::<Side>),
select_3d select
.run_if(in_state(GameState::Display3d)) .run_if(in_state(GameState::Display3d))
.run_if(on_event::<MouseButtonInput>()), .run_if(on_event::<MouseButtonInput>()),
pick_up.run_if(any_component_added::<game::Selected>), pick_up.run_if(any_component_added::<game::Selected>),
@ -42,15 +51,18 @@ impl Plugin for Display3dPlugin {
Update, Update,
( (
move_camera move_camera
.run_if(resource_exists::<debug::DebugEnabled>())
.run_if(in_state(GameState::Display3d)) .run_if(in_state(GameState::Display3d))
.run_if(on_event::<MouseMotion>()), .run_if(on_event::<MouseMotion>()),
gizmo_system.run_if(in_state(GameState::Display3d)), gizmo_system
.run_if(resource_exists::<debug::DebugEnabled>())
.run_if(in_state(GameState::Display3d)),
mouse_zoom mouse_zoom
.run_if(resource_exists::<debug::DebugEnabled>())
.run_if(in_state(GameState::Display3d)) .run_if(in_state(GameState::Display3d))
.run_if(on_event::<MouseWheel>()), .run_if(on_event::<MouseWheel>()),
selected_gizmo, selected_gizmo.run_if(resource_exists::<debug::DebugEnabled>()),
) ),
.run_if(resource_exists::<debug::DebugEnabled>()),
) )
.add_systems( .add_systems(
OnEnter(GameState::Display3d), OnEnter(GameState::Display3d),
@ -98,27 +110,6 @@ fn load_assets(
/// Initialize the 3d board /// Initialize the 3d board
fn initialize(mut commands: Commands, board: Res<game::Board>, assets: Res<AssetsMap>) { fn initialize(mut commands: Commands, board: Res<game::Board>, assets: Res<AssetsMap>) {
info!("Initialize 3d camera");
// let handle = server.load("images/mars.hdr");
commands.spawn((
Display3d,
Camera3dBundle {
camera: Camera {
is_active: false,
hdr: true,
..default()
},
transform: Transform::from_xyz(0.0, 20.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y),
..default()
},
Skybox(assets.skybox.clone()),
EnvironmentMapLight {
diffuse_map: assets.skybox.clone(),
specular_map: assets.skybox.clone(),
},
UiCameraConfig { show_ui: true },
));
info!("Initializing root"); info!("Initializing root");
commands commands
.spawn(( .spawn((
@ -159,6 +150,7 @@ fn initialize(mut commands: Commands, board: Res<game::Board>, assets: Res<Asset
visibility: Visibility::Hidden, visibility: Visibility::Hidden,
..default() ..default()
}, },
game::Selectable,
)); ));
}); });
@ -172,12 +164,55 @@ fn initialize(mut commands: Commands, board: Res<game::Board>, assets: Res<Asset
piece.clone(), piece.clone(),
index.clone(), index.clone(),
SceneBundle { ..default() }, SceneBundle { ..default() },
game::Selectable,
)); ));
}); });
}); });
}); });
} }
fn hydrate_camera(
events: Query<(&Name, Entity), Added<Camera>>,
assets: Res<AssetsMap>,
mut commands: Commands,
) {
events
.iter()
.filter(|(name, _)| name.as_str() == "GameCam")
.for_each(|(_, entity)| {
info!("Initialize 3d camera");
commands.entity(entity).insert((
Display3d,
Camera3dBundle {
camera: Camera {
is_active: false,
hdr: true,
..default()
},
transform: Transform::from_xyz(0.0, 20.0, 10.0).looking_at(Vec3::ZERO, Vec3::Y),
dither: DebandDither::Enabled,
color_grading: ColorGrading { ..default() },
..default()
},
Skybox(assets.skybox.clone()),
EnvironmentMapLight {
diffuse_map: assets.skybox.clone(),
specular_map: assets.skybox.clone(),
},
UiCameraConfig { show_ui: true },
FogSettings {
color: Color::WHITE,
falloff: FogFalloff::from_visibility_colors(
100.0,
Color::ORANGE_RED,
Color::NONE,
),
..default()
},
));
});
}
fn fix_skybox(mut images: ResMut<Assets<Image>>, assets: Res<AssetsMap>) { fn fix_skybox(mut images: ResMut<Assets<Image>>, assets: Res<AssetsMap>) {
let image = images.get_mut(&assets.skybox).unwrap(); let image = images.get_mut(&assets.skybox).unwrap();
info!("Loaded skybox image"); info!("Loaded skybox image");
@ -290,9 +325,16 @@ fn mouse_zoom(
mut events: EventReader<MouseWheel>, mut events: EventReader<MouseWheel>,
mut camera: Query<&mut Transform, (With<Display3d>, With<Camera>)>, mut camera: Query<&mut Transform, (With<Display3d>, With<Camera>)>,
) { ) {
events.iter().for_each(|MouseWheel { y, .. }| { events.iter().for_each(|MouseWheel { unit, y, .. }| {
camera.iter_mut().for_each(|mut t| { camera.iter_mut().for_each(|mut t| {
match unit {
MouseScrollUnit::Line => {
t.translation *= 1.0 - (*y / 4.0); t.translation *= 1.0 - (*y / 4.0);
}
MouseScrollUnit::Pixel => {
t.translation *= 1.0 - (*y / 64.0);
}
}
t.look_at(Vec3::ZERO, Vec3::Y); t.look_at(Vec3::ZERO, Vec3::Y);
}); });
}); });
@ -413,18 +455,26 @@ fn set_piece_texture(
}) })
} }
/// Function for selecting entities based on ray intersection /// Select tiles and pieces in 3d
/// There is a bug where we are selecting multiple entities... /// There is a bug where we are selecting multiple entities...
fn select_3d( /// TODO: Selectable generalize picking pieces **and** hitboxes
fn select(
mut events: EventReader<MouseButtonInput>, mut events: EventReader<MouseButtonInput>,
query: Query<(Entity, &Handle<Mesh>, &GlobalTransform)>, query: Query<(Entity, &Handle<Mesh>, &GlobalTransform)>,
meshes: Res<Assets<Mesh>>, meshes: Res<Assets<Mesh>>,
cameras: Query<(&Camera, &GlobalTransform)>, cameras: Query<(&Camera, &GlobalTransform)>,
windows: Query<&Window, With<PrimaryWindow>>, windows: Query<&Window, With<PrimaryWindow>>,
parents: Query<Entity, (With<game::Piece>, With<Display3d>)>, selectable: Query<Entity, (With<game::Selectable>, With<Display3d>)>,
children: Query<&Children>, children: Query<&Children>,
mut commands: Commands, mut commands: Commands,
selected: Query<Entity, (With<game::Selected>, With<game::Piece>, With<Display3d>)>, selected: Query<
Entity,
(
With<game::Selected>,
With<game::Selectable>,
With<Display3d>,
),
>,
) { ) {
events events
.iter() .iter()
@ -440,8 +490,8 @@ fn select_3d(
meshes.get(handle).map(|mesh| (entity, mesh, gt)) meshes.get(handle).map(|mesh| (entity, mesh, gt))
}) })
.for_each(|(entity, mesh, gt)| { .for_each(|(entity, mesh, gt)| {
hit3d::intersects(&ray, mesh, &gt).and_then(|_hit| { hit::intersects3d(&ray, mesh, &gt).and_then(|_hit| {
parents selectable
.iter() .iter()
.find(|&parent| { .find(|&parent| {
children children
@ -472,7 +522,14 @@ fn select_3d(
} }
fn selected_gizmo( fn selected_gizmo(
selected: Query<&Transform, (With<game::Selected>, With<game::Piece>, With<Display3d>)>, selected: Query<
&Transform,
(
With<game::Selected>,
With<game::Selectable>,
With<Display3d>,
),
>,
mut gizmos: Gizmos, mut gizmos: Gizmos,
) { ) {
selected.iter().for_each(|transform| { selected.iter().for_each(|transform| {
@ -504,15 +561,16 @@ fn pick_up(
game::Piece::Drone => gltf.named_animations.get("DroneIdle"), game::Piece::Drone => gltf.named_animations.get("DroneIdle"),
game::Piece::Pawn => gltf.named_animations.get("PawnIdle"), game::Piece::Pawn => gltf.named_animations.get("PawnIdle"),
}; };
player.play_with_transition( player
.start_with_transition(
animation.expect("Pickup Animation").clone(), animation.expect("Pickup Animation").clone(),
Duration::from_secs_f32(0.75), Duration::from_secs_f32(0.75),
); )
player.play_with_transition( .play_with_transition(
idle.expect("Idle animation").clone(), idle.expect("Idle animation").clone(),
Duration::from_secs_f32(1.5), Duration::from_secs_f32(1.5),
); )
player.repeat(); .repeat();
} }
}) })
}); });
@ -520,7 +578,7 @@ fn pick_up(
fn put_down( fn put_down(
mut events: RemovedComponents<game::Selected>, mut events: RemovedComponents<game::Selected>,
mut query: Query<&game::Piece, (With<game::Piece>, With<Display3d>)>, mut query: Query<&game::Piece, (With<game::Piece>, With<game::Selectable>, With<Display3d>)>,
assets_map: Res<AssetsMap>, assets_map: Res<AssetsMap>,
gltfs: Res<Assets<Gltf>>, gltfs: Res<Assets<Gltf>>,
children: Query<&Children>, children: Query<&Children>,
@ -536,11 +594,12 @@ fn put_down(
game::Piece::Drone => gltf.named_animations.get("DronePutDown"), game::Piece::Drone => gltf.named_animations.get("DronePutDown"),
game::Piece::Pawn => gltf.named_animations.get("PawnPutDown"), game::Piece::Pawn => gltf.named_animations.get("PawnPutDown"),
}; };
player.stop_repeating(); player
player.play_with_transition( .play_with_transition(
animation.expect("PutDown Animation").clone(), animation.expect("PutDown Animation").clone(),
Duration::from_secs_f32(0.75), Duration::from_secs_f32(0.75),
); )
.stop_repeating();
} }
}) })
} }

@ -6,7 +6,6 @@ impl Plugin for GamePlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_event::<GameEvent>() app.add_event::<GameEvent>()
.add_event::<Move>() .add_event::<Move>()
.init_resource::<ActiveTile>()
.add_systems(Startup, setup_board) .add_systems(Startup, setup_board)
.add_systems( .add_systems(
Update, Update,
@ -14,14 +13,16 @@ impl Plugin for GamePlugin {
update_board::<display2d::Display2d>.run_if(on_event::<Move>()), update_board::<display2d::Display2d>.run_if(on_event::<Move>()),
update_board::<display3d::Display3d>.run_if(on_event::<Move>()), update_board::<display3d::Display3d>.run_if(on_event::<Move>()),
set_side.run_if(on_event::<Move>()), // TODO: correct run_if? set_side.run_if(on_event::<Move>()), // TODO: correct run_if?
cancel_place.run_if(|buttons: Res<Input<MouseButton>>| -> bool {
buttons.just_pressed(MouseButton::Right)
}),
select_sync.run_if(any_component_added::<Selected>),
deselect_sync.run_if(any_component_removed::<Selected>()),
), ),
) )
.add_systems( .add_systems(
PostUpdate, PostUpdate,
( (debug_board.run_if(resource_exists::<debug::DebugEnabled>()),),
debug_hovering.run_if(resource_exists::<debug::DebugEnabled>()),
debug_board.run_if(resource_exists::<debug::DebugEnabled>()),
),
); );
} }
} }
@ -202,14 +203,14 @@ impl std::fmt::Display for Board {
} }
} }
#[derive(Debug, Default, Resource)] /// Marker component for currently selected entities
pub(crate) struct ActiveTile {
pub idx: Option<BoardIndex>,
}
#[derive(Debug, Default, Component)] #[derive(Debug, Default, Component)]
pub(crate) struct Selected; pub(crate) struct Selected;
/// Marker component for selectable entities
#[derive(Debug, Default, Component)]
pub(crate) struct Selectable;
fn setup_board(mut commands: Commands) { fn setup_board(mut commands: Commands) {
use Piece::*; use Piece::*;
commands.insert_resource(Board { commands.insert_resource(Board {
@ -259,21 +260,6 @@ fn setup_board(mut commands: Commands) {
}); });
} }
/// TODO: only run_if debug enabled
fn debug_hovering(
selected: Res<ActiveTile>,
board: Res<Board>,
mut debug_info: ResMut<debug::DebugInfo>,
) {
match &selected.idx {
Some(idx) => debug_info.set(
"hovering".into(),
format!("{:?}@({},{})", board.at(&idx), idx.x, idx.y,),
),
None => debug_info.set("selected".into(), format!("N/A")),
};
}
fn debug_board(board: Res<Board>, mut debug_info: ResMut<debug::DebugInfo>) { fn debug_board(board: Res<Board>, mut debug_info: ResMut<debug::DebugInfo>) {
debug_info.set("board".into(), format!("\n{}", *board)); debug_info.set("board".into(), format!("\n{}", *board));
} }
@ -312,3 +298,47 @@ pub(crate) fn set_side(mut events: Query<(&mut Side, &BoardIndex), Changed<Board
Err(e) => warn!("{:?}", e), Err(e) => warn!("{:?}", e),
}); });
} }
fn select_sync(
events: Query<Entity, Added<Selected>>,
pieces: Query<(Entity, &BoardIndex), (With<Selectable>, With<Piece>)>,
mut commands: Commands,
) {
events.iter().for_each(|entity| {
if let Ok((this_e, this_idx)) = pieces.get(entity) {
if let Some((entangled, _)) = pieces
.iter()
.find(|(e, idx)| *idx == this_idx && *e != this_e)
{
commands.entity(entangled).insert(Selected);
}
}
})
}
fn deselect_sync(
mut events: RemovedComponents<Selected>,
pieces: Query<(Entity, &BoardIndex), (With<Selectable>, With<Piece>)>,
mut commands: Commands,
) {
events.iter().for_each(|entity| {
if let Ok((this_e, this_idx)) = pieces.get(entity) {
if let Some((entangled, _)) = pieces
.iter()
.find(|(e, idx)| *idx == this_idx && *e != this_e)
{
commands.entity(entangled).remove::<Selected>();
}
}
})
}
fn cancel_place(
current: Query<Entity, (With<game::Selected>, With<game::Piece>)>,
mut commands: Commands,
) {
current.iter().for_each(|entity| {
info!("De-selecting {:?}", entity);
commands.entity(entity).remove::<game::Selected>();
});
}

@ -1,14 +1,22 @@
use bevy::render::{ use bevy::{
render::{
mesh::{MeshVertexAttribute, VertexAttributeValues}, mesh::{MeshVertexAttribute, VertexAttributeValues},
render_resource::VertexFormat, render_resource::VertexFormat,
},
sprite::Anchor,
}; };
use crate::prelude::*; use crate::prelude::*;
/// Struct containing hit data /// Hit data for 2d sprites
/// The point in Global (not Local) space and the distance from the Camera
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct Hit { pub(crate) struct Hit2d {
point: Vec2,
}
/// Hit data for 3d objects in Global (not Local) space and the distance from the Camera
#[derive(Debug)]
pub(crate) struct Hit3d {
distance: f32, distance: f32,
point: Vec3, point: Vec3,
} }
@ -51,7 +59,7 @@ impl Triangle {
/// Heavily synthesized from these two resources: /// Heavily synthesized from these two resources:
/// * Textbook: https://www.scratchapixel.com/lessons/3d-basic-rendering/ray-tracing-rendering-a-triangle/ray-triangle-intersection-geometric-solution.html /// * Textbook: https://www.scratchapixel.com/lessons/3d-basic-rendering/ray-tracing-rendering-a-triangle/ray-triangle-intersection-geometric-solution.html
/// * Example: https://github.com/aevyrie/bevy_mod_raycast/blob/435d8ef100738797161ac3a9b910ea346a4ed6e6/src/raycast.rs#L43 /// * Example: https://github.com/aevyrie/bevy_mod_raycast/blob/435d8ef100738797161ac3a9b910ea346a4ed6e6/src/raycast.rs#L43
pub(crate) fn intersects(ray: &Ray, mesh: &Mesh, gt: &GlobalTransform) -> Option<Hit> { pub(crate) fn intersects3d(ray: &Ray, mesh: &Mesh, gt: &GlobalTransform) -> Option<Hit3d> {
let attr = MeshVertexAttribute::new("Vertex_Position", 0, VertexFormat::Float32x3); let attr = MeshVertexAttribute::new("Vertex_Position", 0, VertexFormat::Float32x3);
if let Some(verts) = mesh.attribute(attr) { if let Some(verts) = mesh.attribute(attr) {
if let Some(idxs) = mesh.indices() { if let Some(idxs) = mesh.indices() {
@ -96,7 +104,7 @@ pub(crate) fn intersects(ray: &Ray, mesh: &Mesh, gt: &GlobalTransform) -> Option
&& triangle.normal().dot(c1) > 0.0 && triangle.normal().dot(c1) > 0.0
&& triangle.normal().dot(c2) > 0.0 && triangle.normal().dot(c2) > 0.0
}; };
hit.then_some(Hit { hit.then_some(Hit3d {
distance: d, distance: d,
point: p, point: p,
}) })
@ -115,3 +123,24 @@ pub(crate) fn intersects(ray: &Ray, mesh: &Mesh, gt: &GlobalTransform) -> Option
None None
} }
} }
pub(crate) fn intersects2d(
sprite_size: Vec2,
anchor: &Anchor,
transform: &GlobalTransform,
pos: Vec2,
) -> bool {
// Implementation credit goes to the sprite bevy_mod_picking backend
// TODO: Upstream changes
let p = transform.translation();
let 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)
}

@ -8,7 +8,7 @@ mod debug;
mod display2d; mod display2d;
mod display3d; mod display3d;
mod game; mod game;
mod hit3d; mod hit;
mod loading; mod loading;
mod menu; mod menu;
mod prelude; mod prelude;

@ -66,7 +66,7 @@ fn init_menu_ui(mut commands: Commands) {
.with_children(|parent| { .with_children(|parent| {
parent parent
.spawn(( .spawn((
GameState::Display2d, GameState::Display3d,
ButtonBundle { ButtonBundle {
style: Style { style: Style {
padding: UiRect::all(Val::Px(5.0)), padding: UiRect::all(Val::Px(5.0)),
@ -79,7 +79,7 @@ fn init_menu_ui(mut commands: Commands) {
)) ))
.with_children(|parent| { .with_children(|parent| {
parent.spawn(( parent.spawn((
GameState::Display2d, GameState::Display3d,
TextBundle::from_section( TextBundle::from_section(
"Start", "Start",
TextStyle { TextStyle {

Loading…
Cancel
Save