diff --git a/src/game/level/target_wall.rs b/src/game/level/target_wall.rs index ecd31a7..a4f3285 100644 --- a/src/game/level/target_wall.rs +++ b/src/game/level/target_wall.rs @@ -1,4 +1,6 @@ +use std::collections::HashMap; + use bevy::prelude::*; use avian3d::prelude::*; @@ -20,35 +22,90 @@ 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)] -struct Target; +#[derive(Component, Copy, Clone, PartialEq, Eq, Hash)] +struct TargetHandle(i32); // todo: would be nice to use the Entity handle here -impl Target +#[derive(Resource)] +struct TargetResources { - fn random_transform() -> Transform - { - 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); + target_separation: f32, + target_handle_counter: i32, + targets: HashMap, +} - Transform::IDENTITY - .with_translation(Vec3::new(x, y, WALL_FACE_Z + TARGET_HEIGHT * 0.5)) - .with_rotation(Quat::from_rotation_x(std::f32::consts::PI * 0.5)) - .with_scale(Vec3::new(radius, 1.0, radius)) +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, ) { - commands.spawn(( + // spawn the wall + // + let mut wall_commands = commands.spawn(( + TargetWall, Mesh3d(meshes.add(Cuboid::from_corners( Vec3::ZERO, WALL_SIZE ))), @@ -56,7 +113,7 @@ fn setup_target_wall( base_color: WALL_COLOR, ..default() })), - Transform::from_xyz(0.0, WALL_SIZE.y * 0.5, WALL_FACE_Z - WALL_SIZE.z * 0.5), + 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, )); @@ -73,35 +130,49 @@ fn setup_target_wall( TARGET_RADIUS, TARGET_HEIGHT ); - let targets: Box<[_;TARGET_COUNT]> = Box::new(std::array::from_fn(|_| ( - Target, - Mesh3d(target_mesh.clone()), - MeshMaterial3d(target_material.clone()), - Target::random_transform(), - target_collider.clone(), - RigidBody::Static, - ))); - commands.spawn_batch(targets.into_iter()); + // 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 bullet_query: Query>, - mut target_query: Query<&mut Transform, With>, + 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 Ok(_) = collision.query_unique(&mut bullet_query) else { - continue; + let bullet_query = &mut query_set.p0(); + let Ok(_) = collision.query_unique(bullet_query) else { + continue; // the collision doesn't involve a bullet }; - let Ok(mut target_transform) = collision.query_unique(&mut target_query) else { - continue; + // + 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::random_transform(); + *target_transform = target_resources.random_transform(target_handle); } }