You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
martian-chess/src/display2d.rs

341 lines
11 KiB
Rust

/// 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::<game::Selected>()),
select
.run_if(in_state(GameState::Play))
.run_if(in_state(DisplayState::Display2d))
.run_if(|buttons: Res<Input<MouseButton>>| -> bool {
buttons.just_pressed(MouseButton::Left)
}),
update_background.run_if(on_event::<WindowResized>()),
set_transform
.after(game::update_board)
.run_if(any_component_changed::<BoardIndex>),
set_piece_sprite.run_if(any_component_changed::<Side>),
set_tile_sprite.run_if(any_component_added::<game::Tile>),
),
)
.add_systems(OnEnter(DisplayState::Display2d), activate::<Display2d>)
.add_systems(OnExit(DisplayState::Display2d), deactivate::<Display2d>);
}
}
/// Sprite sheet Resource for later reference
#[derive(Debug, Resource)]
struct SpriteSheet {
handle: Handle<TextureAtlas>,
}
/// 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,
Camera2dBundle {
camera: Camera {
is_active: false,
..default()
},
..default()
},
UiCameraConfig { show_ui: true },
));
}
/// STARTUP: Load sprite sheet and insert texture atlas
fn load_spritesheet(
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
server: Res<AssetServer>,
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<AssetServer>,
mut commands: Commands,
window: Query<&Window, With<PrimaryWindow>>,
) {
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<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
fn initialize_board(board: Option<Res<Board>>, 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<TextureAtlas>,
&Piece,
&Side,
),
(With<Display2d>, Or<(Added<Piece>, Changed<Side>)>),
>,
sprite_sheet: Option<Res<SpriteSheet>>,
) {
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<TextureAtlas>, &Tile),
(Added<game::Tile>, With<Display2d>),
>,
sprite_sheet: Option<Res<SpriteSheet>>,
) {
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<Display2d>,
Or<(Changed<BoardIndex>, Added<BoardIndex>)>,
),
>,
) {
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<TextureAtlas>,
&GlobalTransform,
),
(
With<game::Selectable>,
Without<game::Selected>,
With<Display2d>,
),
>,
windows: Query<&Window, With<PrimaryWindow>>,
cameras: Query<(&Camera, &GlobalTransform), With<Display2d>>,
atlases: Res<Assets<TextureAtlas>>,
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<PrimaryWindow>>,
mut query: Query<&mut Transform, (With<game::Selected>, With<game::Piece>, With<Display2d>)>,
camera_query: Query<(&Camera, &GlobalTransform), With<Display2d>>,
) {
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;
}
})
}