use crate::{ game::{Board, BoardIndex, Piece, SelectedTile}, prelude::*, }; const SCALE: f32 = 4.0; const TILE_SIZE: f32 = 16.0; pub struct Display2dPlugin; impl Plugin for Display2dPlugin { fn build(&self, app: &mut App) { 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::()), cursor_position.run_if(in_state(GameState::Display2d)), selected.run_if(resource_changed::()), ), ) .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(TILE_SIZE, TILE_SIZE), 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 * TILE_SIZE * 7.0 / 2.0, // TODO: WHY??? -SCALE * TILE_SIZE * 3.0 / 2.0, // Why 7 and 3?? 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(SCALE)).with_translation( Vec3::new(SCALE * 16.0 * x as f32, SCALE * 16.0 * 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; }); } fn cursor_position( mut events: EventReader, sprite_q: Query<( &TextureAtlasSprite, &Handle, &GlobalTransform, &BoardIndex, )>, camera_q: Query<(&Camera, &GlobalTransform), With>, atlases: Res>, mut selected: 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")) .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 selected.idx != idx.cloned() { selected.idx = idx.cloned(); } } }); } fn selected(selected: Res, board: Res) { selected.idx.iter().for_each(|idx| { info!("Selected Tile: {:?} contains {:?}", idx, board.at(idx)); }); }