use std::collections::HashMap; use bevy::prelude::*; use avian3d::prelude::*; use crate::physics::Bullet; use crate::util::physics::CollisionQuery; const WALL_SIZE: Vec3 = Vec3::new(20.0, 10.0, 0.1); const WALL_FACE_Z: f32 = -25.0; const WALL_COLOR: Color = Color::srgb(0.000, 0.412, 0.580); const TARGET_COUNT: usize = 10; const TARGET_RADIUS: f32 = 1.0; const TARGET_HEIGHT: f32 = 0.1; const TARGET_COLOR: Color = Color::srgb(0.7, 0.1, 0.1); pub struct TargetWallPlugin; impl Plugin for TargetWallPlugin { fn build(&self, app: &mut App) { app.insert_resource(TargetResources { target_separation: 0.1, target_handle_counter: 0, targets: HashMap::new(), }); app.add_systems(Startup, setup_target_wall); app.add_systems(PostProcessCollisions, on_targets_shot.run_if(on_event::)); } } #[derive(Component, Copy, Clone, PartialEq, Eq, Hash)] struct TargetHandle(i32); // todo: would be nice to use the Entity handle here #[derive(Resource)] struct TargetResources { target_separation: f32, target_handle_counter: i32, targets: HashMap, } impl TargetResources { fn new_handle(&mut self) -> TargetHandle { self.target_handle_counter += 1; TargetHandle(self.target_handle_counter) } fn random_transform(&mut self, handle: &TargetHandle) -> Transform { self.targets.remove(&handle); let (transform, cache_entry) = loop { let radius = rand::random::() * 0.4 + 0.3; let x = rand::random::() * (WALL_SIZE.x - radius) - WALL_SIZE.x * 0.5; let y = rand::random::() * (WALL_SIZE.y - radius) - WALL_SIZE.y * 0.5; let position = Vec2::new(x, y); let circle = Circle::new(radius); let has_overlap = self.targets.values().any(|&(position_other, circle_other)| { let distance_squared = Vec2::distance_squared(position, position_other); let max_distance = circle.radius + circle_other.radius + self.target_separation; distance_squared < max_distance * max_distance }); if has_overlap { continue; } let transform = Transform::IDENTITY .with_translation(Vec3::new(x, y, TARGET_HEIGHT * 0.5)) .with_rotation(Quat::from_rotation_x(std::f32::consts::PI * 0.5)) .with_scale(Vec3::new(radius, 1.0, radius)) ; break (transform, (position, circle)); }; self.targets.insert(*handle, cache_entry); return transform; } } #[derive(Component)] pub struct TargetWall; #[derive(Component)] pub struct Target; fn setup_target_wall( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, mut target_resources: ResMut, ) { // spawn the wall // let mut wall_commands = commands.spawn(( TargetWall, Mesh3d(meshes.add(Cuboid::from_corners( Vec3::ZERO, WALL_SIZE ))), MeshMaterial3d(materials.add(StandardMaterial { base_color: WALL_COLOR, ..default() })), Transform::from_xyz(0.0, WALL_SIZE.y * 0.5, WALL_FACE_Z), Collider::cuboid(WALL_SIZE.x, WALL_SIZE.y, WALL_SIZE.z * 0.5), RigidBody::Static, )); let target_mesh = meshes.add(Cylinder { radius: TARGET_RADIUS, half_height: TARGET_HEIGHT * 0.5, }); let target_material = materials.add(StandardMaterial { base_color: TARGET_COLOR, ..default() }); let target_collider = Collider::cylinder( TARGET_RADIUS, TARGET_HEIGHT ); // spawn the targets as children of the wall // (0..TARGET_COUNT).for_each(|_| { let handle = target_resources.new_handle(); let transform = target_resources.random_transform(&handle); wall_commands.with_child(( Target, handle, Mesh3d(target_mesh.clone()), MeshMaterial3d(target_material.clone()), transform, target_collider.clone(), RigidBody::Static, )); }); } fn on_targets_shot( mut collision_events: EventReader, mut query_set: ParamSet<( Query>, Query<(&TargetHandle, &mut Transform), With>, )>, mut target_resources: ResMut, ) { for collision in collision_events.read() { // resolve the query data from the collision, if the collision is // between a bullet and a target // let bullet_query = &mut query_set.p0(); let Ok(_) = collision.query_unique(bullet_query) else { continue; // the collision doesn't involve a bullet }; // let target_query = &mut query_set.p1(); let Ok((target_handle, mut target_transform)) = collision.query_unique(target_query) else { continue; // the collision doesn't involve a target }; // move the target *target_transform = target_resources.random_transform(target_handle); } }