Added drawing debug shapes in immediate mode

feature/debug_draw
hheik 2025-02-24 22:02:06 +02:00
parent 782a568eb6
commit 9c76992905
4 changed files with 180 additions and 19 deletions

View File

@ -1,4 +1,5 @@
use bevy::prelude::*; use bevy::prelude::*;
use bevy_prototype_lyon::prelude::*;
pub struct DebugPlugin; pub struct DebugPlugin;
@ -11,12 +12,18 @@ impl Plugin for DebugPlugin {
.configure_sets(PostUpdate, DebugSet.run_if(is_debug_enabled)); .configure_sets(PostUpdate, DebugSet.run_if(is_debug_enabled));
app.insert_resource(DebugMode::off()) app.insert_resource(DebugMode::off())
.add_plugins(( .insert_resource(DebugDraw::default());
bevy_inspector_egui::quick::WorldInspectorPlugin::new().run_if(is_debug_enabled),
bevy_rapier2d::prelude::RapierDebugRenderPlugin::default(), app.add_plugins((
)) bevy_inspector_egui::quick::WorldInspectorPlugin::new().run_if(is_debug_enabled),
bevy_rapier2d::prelude::RapierDebugRenderPlugin::default(),
));
app.register_type::<DebugCanvas>()
.add_systems(First, clear_shapes)
.add_systems(Update, debug_toggle) .add_systems(Update, debug_toggle)
.add_systems(PostUpdate, draw_debug.in_set(DebugSet)); // TODO: Check if this could be scheduled just before render instead
.add_systems(PostUpdate, draw_shapes);
} }
} }
@ -36,6 +43,43 @@ impl DebugMode {
} }
} }
/// TODO: Rename to something smarter
#[derive(Clone, Copy, Debug, Reflect)]
pub struct Draw {
pub shape: Shape,
pub color: Srgba,
}
#[derive(Clone, Copy, Debug, Reflect)]
pub enum Shape {
Line {
from: Vec2,
to: Vec2,
},
Polygon {
center: Vec2,
sides: usize,
radius: f32,
},
}
#[derive(Clone, Debug, Default, Component, Reflect)]
#[reflect(Component)]
#[require(Transform, Visibility)]
pub struct DebugCanvas;
#[derive(Debug, Default, Resource, Reflect)]
#[reflect(Resource)]
pub struct DebugDraw {
pub draw_queue: Vec<Draw>,
}
impl DebugDraw {
pub fn shape(&mut self, draw: Draw) {
self.draw_queue.push(draw);
}
}
pub fn is_debug_enabled(debug_mode: Res<DebugMode>) -> bool { pub fn is_debug_enabled(debug_mode: Res<DebugMode>) -> bool {
debug_mode.enabled debug_mode.enabled
} }
@ -46,6 +90,44 @@ fn debug_toggle(input: Res<ButtonInput<KeyCode>>, mut debug_mode: ResMut<DebugMo
} }
} }
fn draw_debug() { fn clear_shapes(mut commands: Commands, canvas_query: Query<Entity, With<DebugCanvas>>) {
// TODO for parent in canvas_query.iter() {
commands.entity(parent).despawn_descendants();
}
}
fn draw_shapes(
mut commands: Commands,
canvas: Option<Single<Entity, With<DebugCanvas>>>,
mut debug_draw: ResMut<DebugDraw>,
) {
let canvas = match canvas {
Some(canvas) => *canvas,
None => commands
.spawn((Name::new("Debug Canvas"), DebugCanvas))
.id(),
};
commands.entity(canvas).with_children(|builder| {
for draw in debug_draw.draw_queue.drain(..) {
// TODO
let path = match draw.shape {
Shape::Line { from, to } => GeometryBuilder::build_as(&shapes::Line(from, to)),
Shape::Polygon {
center,
sides,
radius,
} => GeometryBuilder::build_as(&shapes::RegularPolygon {
center,
sides,
feature: RegularPolygonFeature::Radius(radius),
}),
};
builder.spawn((
ShapeBundle { path, ..default() },
Fill::color(Srgba::NONE),
Stroke::color(draw.color),
));
}
});
} }

View File

@ -1,5 +1,6 @@
use crate::{debug, game_setup, util}; use crate::{debug, game_setup, util};
use bevy::prelude::*; use bevy::prelude::*;
use bevy_prototype_lyon::prelude::*;
use bevy_rapier2d::prelude::*; use bevy_rapier2d::prelude::*;
mod item; mod item;
@ -10,6 +11,7 @@ mod work;
pub fn init(app: &mut App) { pub fn init(app: &mut App) {
let app = app.add_plugins(( let app = app.add_plugins((
game_setup::GameSetupPlugin, game_setup::GameSetupPlugin,
ShapePlugin,
RapierPhysicsPlugin::<NoUserData>::default(), RapierPhysicsPlugin::<NoUserData>::default(),
util::UtilPlugin, util::UtilPlugin,
debug::DebugPlugin, debug::DebugPlugin,
@ -24,5 +26,6 @@ pub fn init(app: &mut App) {
.register_type::<prefab::Tree>() .register_type::<prefab::Tree>()
.add_systems(Startup, systems::setup_2d) .add_systems(Startup, systems::setup_2d)
.add_systems(Update, (systems::demo_2d, systems::work_select)) .add_systems(Update, (systems::demo_2d, systems::work_select))
.add_systems(PostUpdate, item::update_item_sprite); .add_systems(PostUpdate, item::update_item_sprite)
.add_systems(Update, (systems::draw_job_targets).in_set(debug::DebugSet));
} }

View File

@ -1,6 +1,9 @@
use bevy::prelude::*; use bevy::prelude::*;
use crate::game::{item::Item, prefab}; use crate::game::{
item::{Inventory, Item, ItemStack},
prefab,
};
pub fn setup_2d(mut commands: Commands) { pub fn setup_2d(mut commands: Commands) {
commands.spawn(( commands.spawn((
@ -9,13 +12,23 @@ pub fn setup_2d(mut commands: Commands) {
Transform::from_xyz(0.0, 0.0, 10.0), Transform::from_xyz(0.0, 0.0, 10.0),
)); ));
commands.spawn((Transform::from_xyz(-200.0, 0.0, 0.0), prefab::Glorb)); commands.spawn((
Transform::from_xyz(-300.0, 0.0, 0.0),
prefab::Glorb,
Inventory {
items: vec![ItemStack {
item: Item::Wood,
count: 1,
}],
capacity: Some(1),
},
));
commands.spawn((Transform::from_xyz(-200.0, 100.0, 0.0), prefab::Glorb)); commands.spawn((Transform::from_xyz(-200.0, 100.0, 0.0), prefab::Glorb));
commands.spawn((Transform::from_xyz(-200.0, -100.0, 0.0), prefab::Glorb)); commands.spawn((Transform::from_xyz(-200.0, -100.0, 0.0), prefab::Glorb));
commands.spawn((Transform::from_xyz(200.0, 0.0, 0.0), prefab::Tree)); commands.spawn((Transform::from_xyz(200.0, 0.0, 0.0), prefab::Tree));
commands.spawn((Name::from("Wood"), Item::Wood)); // commands.spawn((Name::from("Wood"), Item::Wood));
commands.spawn((Transform::from_xyz(-200.0, -150.0, 0.0), prefab::Chest)); commands.spawn((Transform::from_xyz(-200.0, -150.0, 0.0), prefab::Chest));
} }

View File

@ -1,8 +1,11 @@
use bevy::prelude::*; use bevy::{color::palettes::css, prelude::*};
use crate::game::{ use crate::{
item::{Inventory, ItemSource, Stockpile}, debug::{DebugDraw, Draw, Shape},
work::{Task, WorkType, Worker}, game::{
item::{Inventory, ItemSource, Stockpile},
work::{Task, WorkType, Worker},
},
}; };
pub fn work_select( pub fn work_select(
@ -26,7 +29,7 @@ pub fn work_select(
let stockpile_dist_squared = worker_transform let stockpile_dist_squared = worker_transform
.translation() .translation()
.distance_squared(stockpile_transform.translation()); .distance_squared(stockpile_transform.translation());
if task_by_distance.map_or(true, |(_, closest_task_dist)| { if task_by_distance.is_none_or(|(_, closest_task_dist)| {
stockpile_dist_squared < closest_task_dist stockpile_dist_squared < closest_task_dist
}) { }) {
task_by_distance = Some(( task_by_distance = Some((
@ -52,9 +55,9 @@ pub fn work_select(
let source_dist_squared = worker_transform let source_dist_squared = worker_transform
.translation() .translation()
.distance_squared(item_source_transform.translation()); .distance_squared(item_source_transform.translation());
if task_by_distance.map_or(true, |(_, closest_task_dist)| { if task_by_distance
source_dist_squared < closest_task_dist .is_none_or(|(_, closest_task_dist)| source_dist_squared < closest_task_dist)
}) { {
task_by_distance = Some(( task_by_distance = Some((
Task { Task {
target: item_source_entity, target: item_source_entity,
@ -70,3 +73,63 @@ pub fn work_select(
worker.0 = task_by_distance.map(|(task, _)| task); worker.0 = task_by_distance.map(|(task, _)| task);
} }
} }
/// Example of DebugDraw usage
pub fn draw_job_targets(
mut debug_draw: ResMut<DebugDraw>,
worker_query: Query<(Entity, &Worker)>,
global_query: Query<&GlobalTransform>,
) {
for (worker_entity, worker) in worker_query.iter() {
let worker_global = global_query.get(worker_entity).unwrap();
let draws = match worker.0 {
Some(task) => match task.work_type {
WorkType::Gather => vec![
Draw {
shape: Shape::Polygon {
center: worker_global.translation().xy(),
sides: 3,
radius: 16.,
},
color: css::GREEN,
},
Draw {
shape: Shape::Line {
from: worker_global.translation().xy(),
to: global_query.get(task.target).unwrap().translation().xy(),
},
color: css::GREEN,
},
],
WorkType::Store(_) => vec![
Draw {
shape: Shape::Polygon {
center: worker_global.translation().xy(),
sides: 3,
radius: 16.,
},
color: css::YELLOW,
},
Draw {
shape: Shape::Line {
from: worker_global.translation().xy(),
to: global_query.get(task.target).unwrap().translation().xy(),
},
color: css::YELLOW,
},
],
},
None => vec![Draw {
shape: Shape::Polygon {
center: worker_global.translation().xy(),
sides: 3,
radius: 16.,
},
color: css::RED,
}],
};
for draw in draws {
debug_draw.shape(draw);
}
}
}