Rip out 2d, merge editor and debug mode
parent
c9b2a22e68
commit
f0a64541e1
@ -1,404 +0,0 @@
|
|||||||
/// 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::*,
|
|
||||||
tweak::Tweaks,
|
|
||||||
};
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
const SCALE: f32 = 4.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(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>),
|
|
||||||
sync_sprite.run_if(any_component_changed::<Side>),
|
|
||||||
load_spritesheet.run_if(on_event::<AssetEvent<Tweaks>>()),
|
|
||||||
// Set Sprite for Pieces
|
|
||||||
set_sprite.run_if(any_component_changed::<GameSprite>),
|
|
||||||
// When tweakfile is updated
|
|
||||||
set_sprite.run_if(resource_exists_and_changed::<SpriteSheet>()),
|
|
||||||
capture_piece.run_if(any_component_added::<game::Captured>),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.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;
|
|
||||||
|
|
||||||
/// All possible sprites
|
|
||||||
/// Necessary because individual components of Piece, Side, and Light/Dark are not homogeneous
|
|
||||||
#[derive(Debug, Deserialize, Component, Clone, PartialEq)]
|
|
||||||
pub(crate) enum GameSprite {
|
|
||||||
RedQueen,
|
|
||||||
RedDrone,
|
|
||||||
RedPawn,
|
|
||||||
BlueQueen,
|
|
||||||
BlueDrone,
|
|
||||||
BluePawn,
|
|
||||||
LightTile,
|
|
||||||
DarkTile,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<(game::Piece, game::Side)> for GameSprite {
|
|
||||||
fn from((piece, side): (game::Piece, game::Side)) -> GameSprite {
|
|
||||||
match (piece, side) {
|
|
||||||
(Piece::Queen, Side::A) => GameSprite::RedQueen,
|
|
||||||
(Piece::Drone, Side::A) => GameSprite::RedDrone,
|
|
||||||
(Piece::Pawn, Side::A) => GameSprite::RedPawn,
|
|
||||||
(Piece::Queen, Side::B) => GameSprite::BlueQueen,
|
|
||||||
(Piece::Drone, Side::B) => GameSprite::BlueDrone,
|
|
||||||
(Piece::Pawn, Side::B) => GameSprite::BluePawn,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Tile> for GameSprite {
|
|
||||||
fn from(tile: game::Tile) -> GameSprite {
|
|
||||||
match tile {
|
|
||||||
Tile::Light => GameSprite::LightTile,
|
|
||||||
Tile::Dark => GameSprite::DarkTile,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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>>,
|
|
||||||
tweaks: Res<Assets<tweak::Tweaks>>,
|
|
||||||
mut commands: Commands,
|
|
||||||
tweaks_file: Res<tweak::GameTweaks>,
|
|
||||||
) {
|
|
||||||
info!("Loading spritesheet");
|
|
||||||
let tweak = tweaks
|
|
||||||
.get(&tweaks_file.handle.clone())
|
|
||||||
.expect("Load tweakfiles");
|
|
||||||
let atlas = TextureAtlas::from_grid(
|
|
||||||
tweak
|
|
||||||
.get_handle_unchecked::<Image>("display2d_sprites_file")
|
|
||||||
.unwrap(),
|
|
||||||
Vec2::new(
|
|
||||||
tweak.get::<f32>("display2d_sprites_tile_size_x").unwrap(),
|
|
||||||
tweak.get::<f32>("display2d_sprites_tile_size_y").unwrap(),
|
|
||||||
),
|
|
||||||
tweak.get::<usize>("display2d_sprites_columns").unwrap(),
|
|
||||||
tweak.get::<usize>("display2d_sprites_rows").unwrap(),
|
|
||||||
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
|
|
||||||
.read()
|
|
||||||
.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)| {
|
|
||||||
let game_sprite: GameSprite = tile.clone().into();
|
|
||||||
|
|
||||||
parent.spawn((
|
|
||||||
game_sprite,
|
|
||||||
tile.clone(),
|
|
||||||
index,
|
|
||||||
Display2d,
|
|
||||||
SpriteSheetBundle { ..default() },
|
|
||||||
game::Selectable,
|
|
||||||
));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Spawn pieces
|
|
||||||
board.pieces().iter().for_each(|(index, piece)| {
|
|
||||||
let side = Board::side(*index).expect("Spawn valid side");
|
|
||||||
|
|
||||||
let game_sprite: GameSprite = (*piece, side).into();
|
|
||||||
|
|
||||||
parent.spawn((
|
|
||||||
game_sprite,
|
|
||||||
piece.clone(),
|
|
||||||
Display2d,
|
|
||||||
index.clone(),
|
|
||||||
side.clone(),
|
|
||||||
SpriteSheetBundle { ..default() },
|
|
||||||
game::Selectable,
|
|
||||||
));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sync_sprite(mut events: Query<(&mut GameSprite, &Piece, &Side), Changed<Side>>) {
|
|
||||||
events
|
|
||||||
.iter_mut()
|
|
||||||
.for_each(|(mut game_sprite, piece, side)| {
|
|
||||||
*game_sprite = (*piece, *side).into();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_sprite(
|
|
||||||
mut entities: Query<
|
|
||||||
(
|
|
||||||
&mut TextureAtlasSprite,
|
|
||||||
&mut Handle<TextureAtlas>,
|
|
||||||
&GameSprite,
|
|
||||||
),
|
|
||||||
With<Display2d>,
|
|
||||||
>,
|
|
||||||
sprite_sheet: Option<Res<SpriteSheet>>,
|
|
||||||
tweaks: Res<Assets<Tweaks>>,
|
|
||||||
tweaks_file: Res<tweak::GameTweaks>,
|
|
||||||
) {
|
|
||||||
let tweak = tweaks
|
|
||||||
.get(tweaks_file.handle.clone())
|
|
||||||
.expect("Load tweaksfile in set piece sprite");
|
|
||||||
let sprite_sheet = sprite_sheet.expect("Sprite sheet");
|
|
||||||
entities
|
|
||||||
.iter_mut()
|
|
||||||
.for_each(|(mut sprite, mut texture_atlas, game_sprite)| {
|
|
||||||
if let Some(index) = tweak
|
|
||||||
.get::<Vec<GameSprite>>("display2d_sprites_sprite_order")
|
|
||||||
.unwrap()
|
|
||||||
.iter()
|
|
||||||
.position(|s| s == game_sprite)
|
|
||||||
{
|
|
||||||
if *texture_atlas != sprite_sheet.handle {
|
|
||||||
*texture_atlas = sprite_sheet.handle.clone();
|
|
||||||
}
|
|
||||||
sprite.index = index;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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,
|
|
||||||
&BoardIndex,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
With<game::Selectable>,
|
|
||||||
Without<game::Selected>,
|
|
||||||
With<Display2d>,
|
|
||||||
),
|
|
||||||
>,
|
|
||||||
windows: Query<&Window, With<PrimaryWindow>>,
|
|
||||||
cameras: Query<(&Camera, &GlobalTransform), With<Display2d>>,
|
|
||||||
atlases: Res<Assets<TextureAtlas>>,
|
|
||||||
mut selections: EventWriter<game::Selection>,
|
|
||||||
) {
|
|
||||||
// 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(
|
|
||||||
|(
|
|
||||||
_,
|
|
||||||
TextureAtlasSprite { index, anchor, .. },
|
|
||||||
handle,
|
|
||||||
transform,
|
|
||||||
board_index,
|
|
||||||
)| {
|
|
||||||
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((transform, board_index))
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.max_by(|(a, _), (b, _)| {
|
|
||||||
a.translation().z.partial_cmp(&b.translation().z).unwrap()
|
|
||||||
})
|
|
||||||
.map(|(_, board_index)| board_index)
|
|
||||||
.iter()
|
|
||||||
.for_each(|&board_index| {
|
|
||||||
selections.send(game::Selection(board_index.clone()));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// When a piece is captured, we make it invisible in 2D
|
|
||||||
fn capture_piece(
|
|
||||||
mut events: Query<(Entity, &mut Visibility), (With<Display2d>, Added<game::Captured>)>,
|
|
||||||
mut commands: Commands,
|
|
||||||
) {
|
|
||||||
events.iter_mut().for_each(|(entity, mut vis)| {
|
|
||||||
info!("Hiding captured piece");
|
|
||||||
*vis = Visibility::Hidden;
|
|
||||||
commands.entity(entity).remove::<game::Captured>();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@ -1,97 +0,0 @@
|
|||||||
use bevy::input::keyboard::KeyboardInput;
|
|
||||||
|
|
||||||
use crate::prelude::*;
|
|
||||||
|
|
||||||
pub struct EditorPlugin;
|
|
||||||
|
|
||||||
impl Plugin for EditorPlugin {
|
|
||||||
fn build(&self, app: &mut App) {
|
|
||||||
app.add_systems(Startup, init_editor)
|
|
||||||
.insert_resource(
|
|
||||||
GizmoConfig {
|
|
||||||
depth_bias: -0.1,
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.add_systems(Update, (
|
|
||||||
toggle_editor.run_if(on_event::<KeyboardInput>()),
|
|
||||||
aabb_gizmo,
|
|
||||||
))
|
|
||||||
// Systems that run in the editor mode
|
|
||||||
.add_systems(Update, (
|
|
||||||
selected_gizmo.run_if(any_with_component::<game::Selected>()),
|
|
||||||
selected_position.run_if(any_with_component::<game::Selected>()),
|
|
||||||
).run_if(resource_exists::<EditorActive>()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Resource)]
|
|
||||||
struct EditorActive;
|
|
||||||
|
|
||||||
fn init_editor(
|
|
||||||
mut commands: Commands,
|
|
||||||
) {
|
|
||||||
info!("Starting editor");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn toggle_editor(
|
|
||||||
mut events: EventReader<KeyboardInput>,
|
|
||||||
active: Option<Res<EditorActive>>,
|
|
||||||
mut commands: Commands,
|
|
||||||
) {
|
|
||||||
events
|
|
||||||
.read()
|
|
||||||
.filter(|KeyboardInput {
|
|
||||||
state, key_code, ..
|
|
||||||
}| *state == ButtonState::Pressed && *key_code == Some(KeyCode::F3))
|
|
||||||
.for_each(|_| match active {
|
|
||||||
Some(_) => commands.remove_resource::<EditorActive>(),
|
|
||||||
None => commands.insert_resource(EditorActive),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn aabb_gizmo(
|
|
||||||
added: Query<Entity, Added<game::Selected>>,
|
|
||||||
mut removed: RemovedComponents<game::Selected>,
|
|
||||||
selected: Query<Entity, With<game::Selected>>,
|
|
||||||
active: Option<Res<EditorActive>>,
|
|
||||||
mut commands: Commands,
|
|
||||||
) {
|
|
||||||
added.iter().for_each(|e| {
|
|
||||||
commands.entity(e).insert(AabbGizmo { color: Some(Color::RED) });
|
|
||||||
});
|
|
||||||
removed.read().for_each(|e| {
|
|
||||||
commands.entity(e).remove::<AabbGizmo>();
|
|
||||||
});
|
|
||||||
match active {
|
|
||||||
Some(_) => selected.iter().for_each(|e| {
|
|
||||||
commands.entity(e).insert(AabbGizmo { color: Some(Color::RED) });
|
|
||||||
}),
|
|
||||||
None => selected.iter().for_each(|e| {
|
|
||||||
commands.entity(e).remove::<AabbGizmo>();
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Draw a gizmo showing cardinal directions for a selected object
|
|
||||||
fn selected_gizmo(
|
|
||||||
selected: Query<(Entity, &Transform, &GlobalTransform), With<game::Selected>>,
|
|
||||||
mut gizmos: Gizmos,
|
|
||||||
) {
|
|
||||||
selected.iter().for_each(|(e, t, g)| {
|
|
||||||
let s = g.translation();
|
|
||||||
gizmos.ray(s, Vec3::X, Color::RED);
|
|
||||||
gizmos.ray(s, Vec3::Y, Color::GREEN);
|
|
||||||
gizmos.ray(s, Vec3::Z, Color::BLUE);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn selected_position(
|
|
||||||
selected: Query<(Entity, &GlobalTransform), With<game::Selected>>,
|
|
||||||
mut debug_info: ResMut<debug::DebugInfo>,
|
|
||||||
) {
|
|
||||||
let val = selected.iter().map(|(e, gt)| {
|
|
||||||
format!("\n{:?} {:?}", e, gt.translation())
|
|
||||||
}).collect::<Vec<String>>().join("");
|
|
||||||
debug_info.set("Position".into(), val);
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue