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.
349 lines
11 KiB
Rust
349 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>)
|
|
.after(game::set_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>)
|
|
.add_systems(
|
|
OnEnter(GameState::Play),
|
|
activate::<Display2d>.run_if(in_state(DisplayState::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,
|
|
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<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;
|
|
}
|
|
})
|
|
}
|