/// TODO: Pick up and move pieces! /// TODO: Custom Asset: SpriteSheetAtlas Mapper /// TODO: Handle Cursor! use bevy::window::{PrimaryWindow, WindowResized}; use crate::{ game::{Board, BoardIndex, Piece, Side, Tile}, prelude::*, }; const SCALE: f32 = 4.0; const TILE_SIZE: f32 = 16.0; pub(crate) struct Display2dPlugin; impl Plugin for Display2dPlugin { fn build(&self, app: &mut App) { app.add_systems(Startup, (initialize_camera, set_background)) .add_systems(OnEnter(GameState::Loading), load_spritesheet) .add_systems(OnExit(GameState::Loading), initialize_board) .add_systems( Update, ( move_piece .run_if(in_state(GameState::Play)) .run_if(in_state(DisplayState::Display2d)) .run_if(any_with_component::()), select .run_if(in_state(GameState::Play)) .run_if(in_state(DisplayState::Display2d)) .run_if(|buttons: Res>| -> bool { buttons.just_pressed(MouseButton::Left) }), update_background.run_if(on_event::()), set_transform .after(game::update_board) .run_if(any_component_changed::), set_piece_sprite .run_if(any_component_changed::) .after(game::set_side), set_tile_sprite.run_if(any_component_added::), ), ) .add_systems(OnEnter(DisplayState::Display2d), activate::) .add_systems(OnExit(DisplayState::Display2d), deactivate::) .add_systems( OnEnter(GameState::Play), activate::.run_if(in_state(DisplayState::Display2d)), ); } } /// Sprite sheet Resource for later reference #[derive(Debug, Resource)] struct SpriteSheet { handle: Handle, } /// Marker component for the 2d entitys #[derive(Debug, Component)] pub(crate) struct Display2d; #[derive(Debug, Component)] struct BackgroundImage; /// STARTUP: Initialize 2d gameplay Camera fn initialize_camera(mut commands: Commands) { commands.spawn(( Display2d, DisplayState::Display2d, Camera2dBundle { camera: Camera { is_active: false, ..default() }, ..default() }, UiCameraConfig { show_ui: true }, Name::new("2D Camera"), )); } /// 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("images/sprites.png"), Vec2::new(TILE_SIZE, TILE_SIZE), 8, 1, None, None, ); commands.insert_resource(SpriteSheet { handle: texture_atlases.add(atlas), }); } fn set_background( server: Res, mut commands: Commands, window: Query<&Window, With>, ) { commands.spawn(( BackgroundImage, Display2d, SpriteBundle { texture: server.load("images/mars-daybreak.png"), sprite: Sprite { custom_size: Some(Vec2 { x: window.single().width(), y: window.single().height(), }), ..default() }, transform: Transform { translation: Vec3::NEG_Z, ..default() }, visibility: Visibility::Hidden, ..default() }, )); } fn update_background( mut sprites: Query<&mut Sprite, With>, mut events: EventReader, ) { 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 fn initialize_board(board: Option>, mut commands: Commands) { if let Some(board) = board { commands .spawn(( Display2d, SpatialBundle { visibility: Visibility::Hidden, ..default() }, )) .with_children(|parent| { // Spawn tiles game::tiles().for_each(|(index, tile)| { parent.spawn(( tile, index, Display2d, SpriteSheetBundle { ..default() }, game::Selectable, )); }); // Spawn pieces board.pieces().iter().for_each(|(index, piece)| { let side = Board::side(*index).expect("Spawn valid side"); parent.spawn(( piece.clone(), Display2d, index.clone(), side, SpriteSheetBundle { ..default() }, game::Selectable, )); }); }); } } fn set_piece_sprite( mut events: Query< ( &mut TextureAtlasSprite, &mut Handle, &Piece, &Side, ), (With, Or<(Added, Changed)>), >, sprite_sheet: Option>, ) { if let Some(sprite_sheet) = sprite_sheet { events .iter_mut() .for_each(|(mut sprite, mut texture_atlas, piece, side)| { debug!("Updating sprite {:?} {:?}", piece, side); if *texture_atlas != sprite_sheet.handle { *texture_atlas = sprite_sheet.handle.clone(); } sprite.index = match (piece, side) { (Piece::Queen, Side::A) => 2, (Piece::Queen, Side::B) => 5, (Piece::Drone, Side::A) => 3, (Piece::Drone, Side::B) => 6, (Piece::Pawn, Side::A) => 4, (Piece::Pawn, Side::B) => 7, }; }); } } fn set_tile_sprite( mut events: Query< (&mut TextureAtlasSprite, &mut Handle, &Tile), (Added, With), >, sprite_sheet: Option>, ) { if let Some(sprite_sheet) = sprite_sheet { events .iter_mut() .for_each(|(mut sprite, mut texture_atlas, tile)| { *texture_atlas = sprite_sheet.handle.clone(); let s = match tile { Tile::Dark => 0, Tile::Light => 1, }; *sprite = TextureAtlasSprite::new(s); }); } } /// Sets a piece location given it's board index fn set_transform( mut events: Query< (&mut Transform, &BoardIndex, Option<&Piece>, Option<&Tile>), ( With, Or<(Changed, Added)>, ), >, ) { events.iter_mut().for_each(|(mut t, i, piece, tile)| { let x = SCALE * 16.0 * ((i.x as f32) - 3.5); let y = SCALE * 16.0 * ((i.y as f32) - 1.5); let z = match (piece, tile) { (None, None) | (Some(_), Some(_)) => { error!("Entity with BoardIndex is neither a Piece nor a Tile!"); 0.0 } // Piece (Some(_), None) => 2.0, // Tile (None, Some(_)) => 1.0, }; *t = Transform::from_scale(Vec3::splat(SCALE)).with_translation(Vec3::new(x, y, z)); debug!("setting position of {:?} to {:?}", i, t); }); } /// Select pieces and tiles in 2d fn select( candidates: Query< ( Entity, &TextureAtlasSprite, &Handle, &GlobalTransform, ), ( With, Without, With, ), >, windows: Query<&Window, With>, cameras: Query<(&Camera, &GlobalTransform), With>, atlases: Res>, mut commands: Commands, ) { // For each window (there is only one) windows .iter() .filter_map(|window| window.cursor_position()) .for_each(|position| { // For each 2d Camera (there is only one) cameras .iter() .filter_map(|(camera, transform)| camera.viewport_to_world_2d(transform, position)) .for_each(|pos| { // For each selectable sprite (there are many) // Filter down to the list of hit objects candidates .iter() .filter_map( |( entity, TextureAtlasSprite { index, anchor, .. }, handle, transform, )| { let sprite_size = atlases .get(handle) .map(|atlas| { atlas.textures.get(*index).expect("Atlas Sprite Texture") }) .expect("Atlas Sprite Rectangle") .size(); hit::intersects2d(sprite_size, anchor, transform, pos) .then_some((entity, transform)) }, ) .max_by(|(_, a), (_, b)| { a.translation().z.partial_cmp(&b.translation().z).unwrap() }) .iter() .for_each(|(entity, _)| { info!("Selected entity {:?}", entity); commands.entity(*entity).insert(game::Selected); }); }); }); } fn move_piece( window: Query<&Window, With>, mut query: Query<&mut Transform, (With, With, With)>, camera_query: Query<(&Camera, &GlobalTransform), With>, ) { 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; } }) }