Move asteroid spawning & splitting systems

They deal with events, but are directly related to how the Asteroid
object interacts with the events, so I'm putting them here.
This commit is contained in:
2025-08-11 22:54:59 -05:00
parent c1b69c412a
commit 619037dab0
3 changed files with 93 additions and 85 deletions

View File

@@ -1,19 +1,12 @@
//! This is the module containing all the rock-related things.
//! Not... not the whole game.
use bevy_rapier2d::prelude::*;
use rand::{Rng, SeedableRng};
use std::time::Duration;
use bevy::prelude::*;
use crate::{
GameAssets, Lifetime, WorldSize,
config::ASTEROID_LIFETIME,
events::{AsteroidDestroy, SpawnAsteroid},
objects::{Asteroid, AsteroidSize},
physics::Velocity,
};
use crate::{WorldSize, events::SpawnAsteroid, objects::AsteroidSize};
#[derive(Resource)]
pub struct AsteroidSpawner {
@@ -84,77 +77,3 @@ pub fn tick_asteroid_manager(
events.write(SpawnAsteroid { pos, vel, size });
}
}
/// Utility function to spawn a single asteroid of a given type
/// TODO: convert to an event listener monitoring for "spawn asteroid" events
/// from the `fn tick_asteroid_manager(...)` system.
pub fn spawn_asteroid(
mut events: EventReader<SpawnAsteroid>,
mut commands: Commands,
game_assets: Res<GameAssets>,
) {
for spawn in events.read() {
let (mesh, material) = match spawn.size {
AsteroidSize::Small => game_assets.asteroid_small(),
AsteroidSize::Medium => game_assets.asteroid_medium(),
AsteroidSize::Large => game_assets.asteroid_large(),
};
let collider_radius = match spawn.size {
AsteroidSize::Small => 10.0,
AsteroidSize::Medium => 20.0,
AsteroidSize::Large => 40.0,
};
commands.spawn((
Asteroid(spawn.size),
Collider::ball(collider_radius),
Sensor,
Transform::from_translation(spawn.pos.extend(0.0)),
Velocity(spawn.vel),
Mesh2d(mesh),
MeshMaterial2d(material),
Lifetime(Timer::from_seconds(ASTEROID_LIFETIME, TimerMode::Once)),
));
}
}
/// Event listener for asteroid destruction events. Shrinks and multiplies
/// asteroids until they vanish.
///
/// - Large -> 2x Medium
/// - Medium -> 2x Small
/// - Small -> (despawned)
///
/// The velocity of the child asteroids is scattered somewhat, as if they were
/// explosively pushed apart.
pub fn split_asteroids(
mut destroy_events: EventReader<AsteroidDestroy>,
mut respawn_events: EventWriter<SpawnAsteroid>,
mut commands: Commands,
query: Query<(&Transform, &Asteroid, &Velocity)>,
) {
for event in destroy_events.read() {
if let Ok((transform, rock, velocity)) = query.get(event.0) {
let next_size = rock.0.next();
if let Some(size) = next_size {
let pos = transform.translation.xy();
let left_offset = Vec2::from_angle(0.4);
let right_offset = Vec2::from_angle(-0.4);
respawn_events.write(SpawnAsteroid {
pos,
vel: left_offset.rotate(velocity.0),
size,
});
respawn_events.write(SpawnAsteroid {
pos,
vel: right_offset.rotate(velocity.0),
size,
});
}
// Always despawn the asteroid. New ones (may) be spawned in it's
// place, but this one is gone.
commands.entity(event.0).despawn();
}
}
}

View File

@@ -55,8 +55,8 @@ impl Plugin for AsteroidPlugin {
input_ship_shoot,
physics::wrap_entities,
asteroids::tick_asteroid_manager,
asteroids::spawn_asteroid.after(asteroids::tick_asteroid_manager),
asteroids::split_asteroids,
objects::spawn_asteroid.after(asteroids::tick_asteroid_manager),
objects::split_asteroids,
ship::bullet_impact_listener,
ship::ship_impact_listener,
collision_listener,

View File

@@ -3,8 +3,25 @@
//! Asteroids, the player's ship, and such.
use bevy::{
ecs::component::Component,
ecs::{
component::Component,
event::{EventReader, EventWriter},
system::{Commands, Query, Res},
},
math::{Vec2, Vec3Swizzles},
prelude::{Deref, DerefMut},
render::mesh::Mesh2d,
sprite::MeshMaterial2d,
time::{Timer, TimerMode},
transform::components::Transform,
};
use bevy_rapier2d::prelude::{Collider, Sensor};
use crate::{
GameAssets, Lifetime,
config::ASTEROID_LIFETIME,
events::{AsteroidDestroy, SpawnAsteroid},
physics::Velocity,
};
#[derive(Component, Deref, DerefMut)]
@@ -32,3 +49,75 @@ pub struct Ship;
#[derive(Component)]
pub struct Bullet;
/// Responds to [`SpawnAsteroid`] events, spawning as specified
pub fn spawn_asteroid(
mut events: EventReader<SpawnAsteroid>,
mut commands: Commands,
game_assets: Res<GameAssets>,
) {
for spawn in events.read() {
let (mesh, material) = match spawn.size {
AsteroidSize::Small => game_assets.asteroid_small(),
AsteroidSize::Medium => game_assets.asteroid_medium(),
AsteroidSize::Large => game_assets.asteroid_large(),
};
let collider_radius = match spawn.size {
AsteroidSize::Small => 10.0,
AsteroidSize::Medium => 20.0,
AsteroidSize::Large => 40.0,
};
commands.spawn((
Asteroid(spawn.size),
Collider::ball(collider_radius),
Sensor,
Transform::from_translation(spawn.pos.extend(0.0)),
Velocity(spawn.vel),
Mesh2d(mesh),
MeshMaterial2d(material),
Lifetime(Timer::from_seconds(ASTEROID_LIFETIME, TimerMode::Once)),
));
}
}
/// Event listener for asteroid destruction events. Shrinks and multiplies
/// asteroids until they vanish.
///
/// - Large -> 2x Medium
/// - Medium -> 2x Small
/// - Small -> (despawned)
///
/// The velocity of the child asteroids is scattered somewhat, as if they were
/// explosively pushed apart.
pub fn split_asteroids(
mut destroy_events: EventReader<AsteroidDestroy>,
mut respawn_events: EventWriter<SpawnAsteroid>,
mut commands: Commands,
query: Query<(&Transform, &Asteroid, &Velocity)>,
) {
for event in destroy_events.read() {
if let Ok((transform, rock, velocity)) = query.get(event.0) {
let next_size = rock.0.next();
if let Some(size) = next_size {
let pos = transform.translation.xy();
let left_offset = Vec2::from_angle(0.4);
let right_offset = Vec2::from_angle(-0.4);
respawn_events.write(SpawnAsteroid {
pos,
vel: left_offset.rotate(velocity.0),
size,
});
respawn_events.write(SpawnAsteroid {
pos,
vel: right_offset.rotate(velocity.0),
size,
});
}
// Always despawn the asteroid. New ones (may) be spawned in it's
// place, but this one is gone.
commands.entity(event.0).despawn();
}
}
}