moving pieces, background image, pickup/putdown audio

selection-refactor
Elijah Voigt 2 years ago
parent 14307a710b
commit 4f54cece67

BIN
assets/images/mars-daybreak.png (Stored with Git LFS)

Binary file not shown.

@ -19,8 +19,9 @@ impl Plugin for AudioPlugin {
.add_systems( .add_systems(
Update, Update,
( (
play_audio.run_if(any_with_component::<Button>()), play_audio.run_if(any_with_component::<AudioSource>()),
button_audio.run_if(any_with_component::<Button>()), button_audio.run_if(any_with_component::<Button>()),
audio_events.run_if(on_event::<game::GameEvent>()),
), ),
); );
} }
@ -50,3 +51,22 @@ fn button_audio(
)); ));
}); });
} }
fn audio_events(
mut reader: EventReader<game::GameEvent>,
studio: Res<FmodStudio>,
mut commands: Commands,
) {
reader.iter().for_each(|event| match event {
game::GameEvent::SelectPiece => {
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(),
));
}
})
}

@ -5,6 +5,7 @@ use bevy::{
SystemInformationDiagnosticsPlugin, SystemInformationDiagnosticsPlugin,
}, },
input::{keyboard::KeyboardInput, ButtonState}, input::{keyboard::KeyboardInput, ButtonState},
utils::{hashbrown::hash_map::Iter, HashMap},
}; };
use crate::prelude::*; use crate::prelude::*;
@ -22,6 +23,7 @@ impl Plugin for DebugPlugin {
AssetCountDiagnosticsPlugin::<Font>::default(), AssetCountDiagnosticsPlugin::<Font>::default(),
SystemInformationDiagnosticsPlugin::default(), SystemInformationDiagnosticsPlugin::default(),
)) ))
.init_resource::<DebugInfo>()
.add_systems(Startup, init_debug_ui) .add_systems(Startup, init_debug_ui)
.add_systems( .add_systems(
Update, Update,
@ -34,9 +36,26 @@ impl Plugin for DebugPlugin {
} }
} }
#[derive(Debug, Default, Resource)]
pub(crate) struct DebugInfo(HashMap<String, String>);
impl DebugInfo {
pub fn set(&mut self, key: String, val: String) -> Option<String> {
self.0.insert(key, val)
}
pub fn get(&self, key: &String) -> Option<&String> {
self.0.get(key)
}
pub fn iter(&self) -> Iter<'_, String, String> {
self.0.iter()
}
}
/// Marker resource used to enable Debug mode when present /// Marker resource used to enable Debug mode when present
#[derive(Debug, Resource, Default)] #[derive(Debug, Resource, Default)]
struct DebugEnabled; pub(crate) struct DebugEnabled;
#[derive(Debug, Component)] #[derive(Debug, Component)]
struct DebugRoot; struct DebugRoot;
@ -94,20 +113,14 @@ fn init_debug_ui(mut commands: Commands) {
fn display_diagnostics( fn display_diagnostics(
mut root: Query<&mut Text, With<DebugRoot>>, mut root: Query<&mut Text, With<DebugRoot>>,
diagnostics: Res<DiagnosticsStore>, diagnostics: Res<DiagnosticsStore>,
debug_infos: Res<DebugInfo>,
) { ) {
root.iter_mut().for_each(|mut text| { root.iter_mut().for_each(|mut text| {
text.sections = diagnostics text.sections = diagnostics
.iter() .iter()
.map(|diagnostic| { .map(|d| format!("{}: {:.0}\n", d.name, d.smoothed().unwrap_or(0.0),))
TextSection::new( .chain(debug_infos.iter().map(|(k, v)| format!("{}: {}\n", k, v)))
format!( .map(|s| TextSection::new(s, TextStyle { ..default() }))
"{}: {:.0}\n",
diagnostic.name,
diagnostic.smoothed().unwrap_or(0.0),
),
TextStyle { ..default() },
)
})
.collect(); .collect();
text.sections.sort_unstable_by(|a, b| a.value.cmp(&b.value)); text.sections.sort_unstable_by(|a, b| a.value.cmp(&b.value));
}); });

@ -1,9 +1,18 @@
use bevy::{
input::{
mouse::{MouseButtonInput, MouseMotion},
ButtonState,
},
window::{PrimaryWindow, WindowResized},
};
/// ///
/// TODO: Pick up and move pieces!
/// TODO: Custom Asset: SpriteSheetAtlas Mapper /// TODO: Custom Asset: SpriteSheetAtlas Mapper
/// TODO: Handle Cursor! /// TODO: Handle Cursor!
/// ///
use crate::{ use crate::{
game::{Board, BoardIndex, Piece, SelectedTile}, game::{ActiveTile, Board, BoardIndex, Piece},
prelude::*, prelude::*,
}; };
@ -16,19 +25,32 @@ impl Plugin for Display2dPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems( app.add_systems(
Startup, Startup,
(initialize_camera, load_spritesheet).run_if(in_state(GameState::Loading)), (initialize_camera, load_spritesheet, set_background)
.run_if(in_state(GameState::Loading)),
) )
.add_systems( .add_systems(
Update, Update,
( (
initialize_board.run_if(resource_added::<SpriteSheet>()), initialize_board.run_if(resource_added::<SpriteSheet>()),
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))
.run_if(not(any_with_component::<game::Selected>())),
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(any_with_component::<game::Selected>()),
update_background.run_if(on_event::<WindowResized>()),
draw_board draw_board
.run_if(resource_exists::<SpriteSheet>()) .run_if(resource_exists::<SpriteSheet>())
.run_if(resource_changed::<Board>()) // TODO: run_if(in_state(Display2d)) .run_if(any_component_removed::<game::Selected>()) // trigger if item was de-selected
.run_if(any_with_component::<BoardIndex>()), .run_if(any_with_component::<BoardIndex>()),
cursor_position.run_if(in_state(GameState::Display2d)),
selected.run_if(resource_changed::<SelectedTile>()),
menu::exit_to_menu.run_if(in_state(GameState::Display2d)),
), ),
) )
.add_systems(OnEnter(GameState::Display2d), (activate, draw_board)) .add_systems(OnEnter(GameState::Display2d), (activate, draw_board))
@ -53,6 +75,9 @@ struct Piece2d;
#[derive(Debug, Component)] #[derive(Debug, Component)]
struct Display2dCamera; struct Display2dCamera;
#[derive(Debug, Component)]
struct BackgroundImage;
/// STARTUP: Initialize 2d gameplay Camera /// STARTUP: Initialize 2d gameplay Camera
fn initialize_camera(mut commands: Commands) { fn initialize_camera(mut commands: Commands) {
commands.spawn(( commands.spawn((
@ -87,6 +112,43 @@ fn load_spritesheet(
}); });
} }
fn set_background(
server: Res<AssetServer>,
mut commands: Commands,
window: Query<&Window, With<PrimaryWindow>>,
) {
commands.spawn((
BackgroundImage,
SpriteBundle {
texture: server.load("mars-daybreak.png"),
sprite: Sprite {
custom_size: Some(Vec2 {
x: window.single().width(),
y: window.single().height(),
}),
..default()
},
..default()
},
));
}
fn update_background(
mut sprites: Query<&mut Sprite, With<BackgroundImage>>,
mut events: EventReader<WindowResized>,
) {
events
.iter()
.for_each(|&WindowResized { width, height, .. }| {
sprites.iter_mut().for_each(|mut sprite| {
sprite.custom_size = Some(Vec2 {
x: width,
y: height,
});
});
});
}
/// STARTUP: Initialize the board for representation /// STARTUP: Initialize the board for representation
fn initialize_board(sprite_sheet: Option<Res<SpriteSheet>>, mut commands: Commands) { fn initialize_board(sprite_sheet: Option<Res<SpriteSheet>>, mut commands: Commands) {
if let Some(sprite_sheet) = sprite_sheet { if let Some(sprite_sheet) = sprite_sheet {
@ -136,24 +198,26 @@ fn initialize_board(sprite_sheet: Option<Res<SpriteSheet>>, mut commands: Comman
fn draw_board( fn draw_board(
board: Option<Res<Board>>, board: Option<Res<Board>>,
mut commands: Commands, sprite_sheet: Option<Res<SpriteSheet>>,
root: Query<Entity, With<Board2d>>,
tiles: Query<(&Transform, &BoardIndex)>, tiles: Query<(&Transform, &BoardIndex)>,
pieces: Query<Entity, With<Piece2d>>, pieces: Query<Entity, With<Piece2d>>,
root: Query<Entity, With<Board2d>>, mut commands: Commands,
sprite_sheet: Option<Res<SpriteSheet>>,
) { ) {
if let (Some(board), Some(sprite_sheet)) = (board, sprite_sheet) { if let (Some(board), Some(sprite_sheet)) = (board, sprite_sheet) {
pieces
.iter()
.for_each(|entity| commands.entity(entity).despawn_recursive());
commands.entity(root.single()).with_children(|parent| { commands.entity(root.single()).with_children(|parent| {
info!("Board and sprite sheet ready, drawing board");
board board
.pieces() .pieces()
.iter() .iter()
.filter_map(|(board_index, piece)| { .filter_map(|(board_index, piece)| {
tiles.iter().find_map(|(transform, this_index)| { tiles.iter().find_map(|(transform, this_index)| {
(*this_index == *board_index).then(|| (piece, transform)) (*this_index == *board_index).then(|| (piece, transform, this_index))
}) })
}) })
.for_each(|(piece, transform)| { .for_each(|(piece, transform, index)| {
let texture_atlas = sprite_sheet.handle.clone(); let texture_atlas = sprite_sheet.handle.clone();
let s = match piece { let s = match piece {
Piece::Queen => 2, Piece::Queen => 2,
@ -173,6 +237,7 @@ fn draw_board(
..default() ..default()
}, },
Piece2d, Piece2d,
index.clone(),
)); ));
}); });
}); });
@ -203,7 +268,7 @@ fn deactivate(
}); });
} }
fn cursor_position( fn active_tile(
mut events: EventReader<CursorMoved>, mut events: EventReader<CursorMoved>,
sprite_q: Query<( sprite_q: Query<(
&TextureAtlasSprite, &TextureAtlasSprite,
@ -213,7 +278,7 @@ fn cursor_position(
)>, )>,
camera_q: Query<(&Camera, &GlobalTransform), With<Camera2d>>, camera_q: Query<(&Camera, &GlobalTransform), With<Camera2d>>,
atlases: Res<Assets<TextureAtlas>>, atlases: Res<Assets<TextureAtlas>>,
mut selected: ResMut<SelectedTile>, mut active: ResMut<ActiveTile>,
) { ) {
events.iter().for_each(|CursorMoved { position, .. }| { events.iter().for_each(|CursorMoved { position, .. }| {
if let Some(position) = camera_q if let Some(position) = camera_q
@ -242,15 +307,111 @@ fn cursor_position(
rect.contains(position).then_some(board_index) rect.contains(position).then_some(board_index)
}, },
); );
if selected.idx != idx.cloned() { if active.idx != idx.cloned() {
selected.idx = idx.cloned(); active.idx = idx.cloned();
} }
} }
}); });
} }
fn selected(selected: Res<SelectedTile>, board: Res<Board>) { fn select_piece(
selected.idx.iter().for_each(|idx| { mut events: EventReader<MouseButtonInput>,
info!("Selected Tile: {:?} contains {:?}", idx, board.at(idx)); pieces: Query<(Entity, &BoardIndex), With<Piece2d>>,
}); 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| {
commands
.entity(entity)
.insert(game::Selected)
.remove_parent();
writer.send(game::GameEvent::SelectPiece);
});
}
fn move_piece(
window: Query<&Window, With<PrimaryWindow>>,
mut query: Query<&mut Transform, (With<game::Selected>, With<Piece2d>)>,
camera_query: Query<(&Camera, &GlobalTransform), With<Display2dCamera>>,
) {
query.iter_mut().for_each(|mut t| {
let (camera, camera_t) = camera_query.single();
if let Some(pos) = window
.single()
.cursor_position()
.and_then(|cursor| camera.viewport_to_world_2d(camera_t, cursor))
{
t.translation.x = pos.x;
t.translation.y = pos.y;
}
})
}
///
/// 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<Piece2d>)>,
pieces: Query<&BoardIndex, (Without<game::Selected>, With<Piece2d>)>,
active: Res<ActiveTile>,
mut board: ResMut<Board>,
mut commands: Commands,
mut writer: EventWriter<game::GameEvent>,
) {
events
.iter()
.filter_map(|MouseButtonInput { button, state, .. }| {
if (*button, *state) == (MouseButton::Left, ButtonState::Pressed) {
active.idx.as_ref()
} else {
None
}
})
.filter_map(|idx| {
(!pieces.iter().any(|board_index| board_index == idx)).then_some((
current.single().0,
current.single().1,
idx,
))
})
.for_each(|(entity, from, to)| match board.move_piece(from, to) {
Ok(()) => {
commands.entity(entity).remove::<game::Selected>();
writer.send(game::GameEvent::PlacePiece);
}
Err(game::GameError::NullMove) => writer.send(game::GameEvent::PlacePiece),
Err(game::GameError::InvalidMove) => warn!("Invalid move!"),
})
}
fn cancel_place(
mut events: EventReader<MouseButtonInput>,
current: Query<Entity, (With<game::Selected>, With<Piece2d>)>,
mut commands: Commands,
mut writer: EventWriter<game::GameEvent>,
) {
events
.iter()
.filter(|MouseButtonInput { button, state, .. }| {
(*button, *state) == (MouseButton::Right, ButtonState::Pressed)
})
.for_each(|_| {
let entity = current.single();
commands.entity(entity).remove::<game::Selected>();
writer.send(game::GameEvent::PlacePiece);
})
} }

@ -4,8 +4,18 @@ pub(crate) struct GamePlugin;
impl Plugin for GamePlugin { impl Plugin for GamePlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.init_resource::<SelectedTile>() app.add_event::<GameEvent>()
.add_systems(Startup, setup_board); .init_resource::<ActiveTile>()
.add_systems(Startup, setup_board)
.add_systems(
PostUpdate,
(
debug_hovering
.run_if(resource_exists::<debug::DebugEnabled>())
.run_if(resource_changed::<ActiveTile>()),
debug_board.run_if(resource_exists::<debug::DebugEnabled>()),
),
);
} }
} }
@ -27,6 +37,18 @@ impl std::fmt::Display for Piece {
} }
} }
#[derive(Debug)]
pub(crate) enum GameError {
NullMove,
InvalidMove,
}
#[derive(Debug, Event)]
pub(crate) enum GameEvent {
SelectPiece,
PlacePiece,
}
/// The board is setup like this: /// The board is setup like this:
/// ```text /// ```text
/// 0 1 2 3 4 5 6 7 /// 0 1 2 3 4 5 6 7
@ -69,12 +91,31 @@ impl Board {
}) })
.collect() .collect()
} }
pub(crate) fn move_piece(
&mut self,
from: &BoardIndex,
to: &BoardIndex,
) -> Result<(), GameError> {
if from == to {
Err(GameError::NullMove)
} else if self.at(to).is_none() {
self.at(from).map_or(Err(GameError::NullMove), |from_val| {
// TODO: We can self.inner.swap(to, from) if board is single vec
self.inner[to.y][to.x] = Some(from_val);
self.inner[from.y][from.x] = None;
Ok(())
})
} else {
Err(GameError::InvalidMove)
}
}
} }
impl std::fmt::Display for Board { impl std::fmt::Display for Board {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let _ = write!(f, "\n"); let _ = write!(f, "\n");
self.inner.iter().for_each(|row| { self.inner.iter().rev().for_each(|row| {
let _ = write!(f, "+--+--+--+--+--+--+--+--+\n"); let _ = write!(f, "+--+--+--+--+--+--+--+--+\n");
let _ = write!(f, "|"); let _ = write!(f, "|");
row.iter().for_each(|piece| { row.iter().for_each(|piece| {
@ -91,10 +132,13 @@ impl std::fmt::Display for Board {
} }
#[derive(Debug, Default, Resource)] #[derive(Debug, Default, Resource)]
pub(crate) struct SelectedTile { pub(crate) struct ActiveTile {
pub idx: Option<BoardIndex>, pub idx: Option<BoardIndex>,
} }
#[derive(Debug, Default, Component)]
pub(crate) struct Selected;
fn setup_board(mut commands: Commands) { fn setup_board(mut commands: Commands) {
use Piece::*; use Piece::*;
commands.insert_resource(Board { commands.insert_resource(Board {
@ -142,3 +186,22 @@ 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>) {
debug_info.set("board".into(), format!("\n{}", *board));
}

Loading…
Cancel
Save