From 5f255275e0d4bc76639e1061132e6b2dcf34c4d0 Mon Sep 17 00:00:00 2001 From: Elijah Voigt Date: Mon, 2 Oct 2023 23:14:22 -0700 Subject: [PATCH] Drawing pieces on the board! Also states: 2d state, skeleton 3d state, menu state, loading state. Really leaning into the bevy system scheduling. --- examples/sprites-2d.rs | 14 ++-- src/debug.rs | 24 +++--- src/display2d.rs | 165 ++++++++++++++++++++++++++++++++++++++++- src/display3d.rs | 58 ++++++++++++++- src/game.rs | 140 ++++++++++++++++------------------ src/main.rs | 35 ++++++++- src/prelude.rs | 4 + 7 files changed, 341 insertions(+), 99 deletions(-) diff --git a/examples/sprites-2d.rs b/examples/sprites-2d.rs index 0015b2c..0af5fb5 100644 --- a/examples/sprites-2d.rs +++ b/examples/sprites-2d.rs @@ -96,13 +96,15 @@ fn initialize_board(sprite_sheet: Option>, mut commands: Comman let index = BoardIndex { x, y }; // Rectangle - parent.spawn(SpriteSheetBundle { - texture_atlas, - sprite, - transform, + parent.spawn(( + SpriteSheetBundle { + texture_atlas, + sprite, + transform, + ..default() + }, index, - ..default() - }); + )); } }); } diff --git a/src/debug.rs b/src/debug.rs index 88a0e70..1f5bf76 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -4,21 +4,17 @@ pub struct DebugPlugin; impl Plugin for DebugPlugin { fn build(&self, app: &mut App) { - app.add_systems(PostStartup, display_board); + app.add_systems( + Update, + display_board.run_if(resource_exists::()), + ); } } -fn display_board(board: Res, pieces: Query<&crate::game::Piece>) { - board.inner.iter().for_each(|row| { - print!("+--+--+--+--+--+--+--+--+\n"); - print!("|"); - row.iter() - .map(|piece| piece.and_then(|p| pieces.get(p).ok())) - .for_each(|piece| match piece { - Some(p) => print!("{} |", p), - None => print!(" |"), - }); - print!("\n"); - }); - print!("+--+--+--+--+--+--+--+--+\n"); +/// Marker resource used to enable Debug mode when present +#[derive(Debug, Resource)] +struct DebugEnabled; + +fn display_board(board: Res) { + info!("{}", *board); } diff --git a/src/display2d.rs b/src/display2d.rs index 7a4feac..aa64324 100644 --- a/src/display2d.rs +++ b/src/display2d.rs @@ -1,9 +1,170 @@ -use crate::prelude::*; +use crate::{ + game::{Board, BoardIndex, Piece}, + prelude::*, +}; + +const SCALE: f32 = 80.0; pub struct Display2dPlugin; impl Plugin for Display2dPlugin { fn build(&self, app: &mut App) { - todo!() + app.add_systems( + Startup, + (initialize_camera, load_spritesheet).run_if(in_state(GameState::Loading)), + ) + .add_systems( + Update, + ( + initialize_board.run_if(resource_added::()), + draw_board + .run_if(resource_exists::()) + .run_if(resource_changed::()) // TODO: run_if(in_state(Display2d)) + .run_if(any_with_component::()), + ), + ) + .add_systems(OnEnter(GameState::Display2d), (activate, draw_board)); + } +} + +/// Sprite sheet Resource for later reference +#[derive(Debug, Resource)] +struct SpriteSheet { + handle: Handle, +} + +/// Marker component for the 2d board entity +#[derive(Debug, Component)] +struct Board2d; + +/// Marker for 2d piece entities +#[derive(Debug, Component)] +struct Piece2d; + +/// STARTUP: Initialize 2d gameplay Camera +fn initialize_camera(mut commands: Commands) { + commands.spawn(Camera2dBundle { + camera: Camera { + is_active: false, + ..default() + }, + ..default() + }); +} + +/// STARTUP: Load sprite sheet and insert texture atlas +fn load_spritesheet( + mut texture_atlases: ResMut>, + server: Res, + mut commands: Commands, +) { + let atlas = TextureAtlas::from_grid( + server.load("sprites.png"), + Vec2::new(16.0, 16.0), + 5, + 1, + None, + None, + ); + commands.insert_resource(SpriteSheet { + handle: texture_atlases.add(atlas), + }); +} + +/// STARTUP: Initialize the board for representation +fn initialize_board(sprite_sheet: Option>, mut commands: Commands) { + if let Some(sprite_sheet) = sprite_sheet { + commands + .spawn(( + SpatialBundle { + transform: Transform::from_xyz(-SCALE * 3.5, -SCALE * 1.5, 0.0), + ..default() + }, + Board2d, + )) + .with_children(|parent| { + for i in 0..32 { + let x = i % 8; + let y = i / 8; + let s = (x % 2) ^ (y % 2); + + let transform = Transform::from_scale(Vec3::splat(5.0)) + .with_translation(Vec3::new(SCALE * x as f32, SCALE * y as f32, 0.0)); + + let sprite = TextureAtlasSprite::new(s); + + let texture_atlas = sprite_sheet.handle.clone(); + + let index = BoardIndex { x, y }; + + // Rectangle + parent.spawn(( + SpriteSheetBundle { + texture_atlas, + sprite, + transform, + ..default() + }, + index, + )); + } + }); } } + +fn draw_board( + board: Option>, + mut commands: Commands, + tiles: Query<(&Transform, &BoardIndex)>, + pieces: Query>, + root: Query>, + sprite_sheet: Option>, +) { + if let (Some(board), Some(sprite_sheet)) = (board, sprite_sheet) { + commands.entity(root.single()).with_children(|parent| { + info!("Board and sprite sheet ready, drawing board"); + board + .pieces() + .iter() + .filter_map(|(board_index, piece)| { + tiles.iter().find_map(|(transform, this_index)| { + (*this_index == *board_index).then(|| (piece, transform)) + }) + }) + .for_each(|(piece, transform)| { + let texture_atlas = sprite_sheet.handle.clone(); + let s = match piece { + Piece::Queen => 2, + Piece::Drone => 3, + Piece::Pawn => 4, + }; + let sprite = TextureAtlasSprite::new(s); + // TODO: transform is slightly different, set sprite + parent.spawn(( + piece.clone(), + SpriteSheetBundle { + texture_atlas, + sprite, + transform: Transform { + ..transform.clone() + }, + ..default() + }, + Piece2d, + )); + }); + }); + } +} + +fn activate( + mut cameras: Query<&mut Camera, With>, + mut boards: Query<&mut Visibility, With>, +) { + cameras.iter_mut().for_each(|mut camera| { + camera.is_active = true; + }); + boards.iter_mut().for_each(|mut visibility| { + *visibility = Visibility::Visible; + }); +} diff --git a/src/display3d.rs b/src/display3d.rs index a2d32ad..8c9ce1a 100644 --- a/src/display3d.rs +++ b/src/display3d.rs @@ -4,6 +4,62 @@ pub struct Display3dPlugin; impl Plugin for Display3dPlugin { fn build(&self, app: &mut App) { - todo!() + app.add_systems( + Startup, + (initialize_camera, load_models).run_if(in_state(GameState::Loading)), + ) + .add_systems( + Update, + initialize_board.run_if(resource_added::()), + ) + .add_systems(OnEnter(GameState::Display3d), activate); } } + +#[derive(Debug, Component)] +struct Board3d; + +#[derive(Debug, Resource)] +struct ModelMap; + +fn initialize_camera(mut commands: Commands) { + commands.spawn(Camera3dBundle { + camera: Camera { + is_active: false, + ..default() + }, + ..default() + }); +} + +fn load_models(server: Res, mut commands: Commands) { + warn!("TODO: Load models"); + + commands.insert_resource(ModelMap); +} + +fn initialize_board(mut commands: Commands, model_map: Option>) { + if let Some(models) = model_map { + warn!("TODO: Intialize 3D Board!"); + + commands.spawn(( + SpatialBundle { + visibility: Visibility::Hidden, + ..default() + }, + Board3d, + )); + } +} + +fn activate( + mut cameras: Query<&mut Camera, With>, + mut boards: Query<&mut Visibility, With>, +) { + cameras.iter_mut().for_each(|mut camera| { + camera.is_active = true; + }); + boards.iter_mut().for_each(|mut visibility| { + *visibility = Visibility::Visible; + }); +} diff --git a/src/game.rs b/src/game.rs index 1b1f93a..3faf1a7 100644 --- a/src/game.rs +++ b/src/game.rs @@ -8,7 +8,7 @@ impl Plugin for GamePlugin { } } -#[derive(Debug, Component)] +#[derive(Debug, Component, Clone)] pub(crate) enum Piece { Pawn, Drone, @@ -41,65 +41,55 @@ impl std::fmt::Display for Piece { /// ```` #[derive(Debug, Resource)] pub(crate) struct Board { - pub inner: Vec>>, + inner: Vec>>, } -fn setup_board(mut commands: Commands) { - let a5d = commands - .spawn((SpatialBundle { ..default() }, Piece::Drone)) - .id(); - let a6q = commands - .spawn((SpatialBundle { ..default() }, Piece::Queen)) - .id(); - let a7q = commands - .spawn((SpatialBundle { ..default() }, Piece::Queen)) - .id(); - let b0d = commands - .spawn((SpatialBundle { ..default() }, Piece::Drone)) - .id(); - let b1p = commands - .spawn((SpatialBundle { ..default() }, Piece::Pawn)) - .id(); - let b2p = commands - .spawn((SpatialBundle { ..default() }, Piece::Pawn)) - .id(); - let c5p = commands - .spawn((SpatialBundle { ..default() }, Piece::Pawn)) - .id(); - let b5p = commands - .spawn((SpatialBundle { ..default() }, Piece::Pawn)) - .id(); - let b6d = commands - .spawn((SpatialBundle { ..default() }, Piece::Drone)) - .id(); - let b7q = commands - .spawn((SpatialBundle { ..default() }, Piece::Queen)) - .id(); - let c0q = commands - .spawn((SpatialBundle { ..default() }, Piece::Queen)) - .id(); - let c1d = commands - .spawn((SpatialBundle { ..default() }, Piece::Drone)) - .id(); - let c2p = commands - .spawn((SpatialBundle { ..default() }, Piece::Pawn)) - .id(); - let c6p = commands - .spawn((SpatialBundle { ..default() }, Piece::Pawn)) - .id(); - let c7d = commands - .spawn((SpatialBundle { ..default() }, Piece::Drone)) - .id(); - let d0q = commands - .spawn((SpatialBundle { ..default() }, Piece::Queen)) - .id(); - let d1q = commands - .spawn((SpatialBundle { ..default() }, Piece::Queen)) - .id(); - let d2d = commands - .spawn((SpatialBundle { ..default() }, Piece::Drone)) - .id(); +#[derive(Debug, Component, PartialEq)] +pub(crate) struct BoardIndex { + pub x: usize, + pub y: usize, +} + +impl Board { + /// Returns the piece at the given location + pub(crate) fn _at(&self, (_x, _y): (usize, usize)) -> Option { + todo!("return piece at location") + } + + /// Returns a list of all pieces on the board with their location + pub(crate) fn pieces(&self) -> Vec<(BoardIndex, Piece)> { + self.inner + .iter() + .enumerate() + .flat_map(|(y, nested)| { + nested.iter().enumerate().filter_map(move |(x, p)| { + p.as_ref().map(|val| (BoardIndex { x, y }, val.clone())) + }) + }) + .collect() + } +} + +impl std::fmt::Display for Board { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let _ = write!(f, "\n"); + self.inner.iter().for_each(|row| { + let _ = write!(f, "+--+--+--+--+--+--+--+--+\n"); + let _ = write!(f, "|"); + row.iter().for_each(|piece| { + let _ = match piece { + Some(p) => write!(f, "{} |", p), + None => write!(f, " |"), + }; + }); + let _ = write!(f, "\n"); + }); + let _ = write!(f, "+--+--+--+--+--+--+--+--+\n"); + Ok(()) + } +} +fn setup_board(mut commands: Commands) { commands.insert_resource(Board { inner: vec![ vec![ @@ -108,34 +98,34 @@ fn setup_board(mut commands: Commands) { None, None, None, - Some(a5d), - Some(a6q), - Some(a7q), + Some(Piece::Drone), + Some(Piece::Queen), + Some(Piece::Queen), ], vec![ - Some(b0d), - Some(b1p), - Some(b2p), + Some(Piece::Drone), + Some(Piece::Pawn), + Some(Piece::Pawn), None, None, - Some(b5p), - Some(b6d), - Some(b7q), + Some(Piece::Pawn), + Some(Piece::Drone), + Some(Piece::Queen), ], vec![ - Some(c0q), - Some(c1d), - Some(c2p), + Some(Piece::Queen), + Some(Piece::Drone), + Some(Piece::Pawn), None, None, - Some(c5p), - Some(c6p), - Some(c7d), + Some(Piece::Pawn), + Some(Piece::Pawn), + Some(Piece::Drone), ], vec![ - Some(d0q), - Some(d1q), - Some(d2d), + Some(Piece::Queen), + Some(Piece::Queen), + Some(Piece::Drone), None, None, None, diff --git a/src/main.rs b/src/main.rs index 67a999f..300fbeb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,8 +9,11 @@ use crate::prelude::*; fn main() { App::new() + .add_state::() + .add_systems(Update, state) + .add_systems(Update, loading.run_if(in_state(GameState::Loading))) .add_plugins(( - DefaultPlugins, + DefaultPlugins.set(ImagePlugin::default_nearest()), audio::AudioPlugin, debug::DebugPlugin, display2d::Display2dPlugin, @@ -19,3 +22,33 @@ fn main() { )) .run(); } + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default, States)] +pub enum GameState { + #[default] + Loading, + Menu, + Display2d, + Display3d, +} + +fn loading( + server: Res, + sprites: Res>, + mut next_state: ResMut>, +) { + let items = { sprites.ids() }; + let states = server.get_group_load_state(items); + match states { + LoadState::Loaded | LoadState::NotLoaded => next_state.set(GameState::Display2d), + _ => (), + } +} + +fn state(state: Option>>) { + state.map(|s| { + if s.is_added() || s.is_changed() { + info!("Updated state is {:?}", s); + } + }); +} diff --git a/src/prelude.rs b/src/prelude.rs index 5df1da5..8795546 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,2 +1,6 @@ +pub use crate::*; +pub use bevy::asset::LoadState; +pub use bevy::gltf::Gltf; pub use bevy::prelude::*; +pub use bevy_fmod::prelude::AudioSource; pub use bevy_fmod::prelude::*;