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