2 Commits

Author SHA1 Message Date
809810b8ce First parts of a collision event handler
All checks were successful
Basic checks / Basic build-and-test supertask (push) Successful in 7m15s
I've added a new `event` module to contain all the game's events (which
it does not. I'll collect them all later).

Using these events, the CollisionEvent listening system can watch for
any two colliders intersecting, check on what role they play (asteroid,
ship, bullet, etc), and re-emit an event for that specific thing. Each
component can then handle it's own destruction process.
2025-08-09 12:30:37 -05:00
29735e7426 Move ship parts into it's own submodule 2025-08-08 22:03:34 -05:00
3 changed files with 103 additions and 31 deletions

14
src/event.rs Normal file
View File

@@ -0,0 +1,14 @@
use bevy::prelude::*;
/// Signals that the player's ship has been destroyed.
/// Used when the player collides with an asteroid.
#[derive(Event)]
pub(crate) struct ShipDestroy;
/// Signals that a particular asteroid has been destroyed.
/// Used to split (or vanish) an asteroid when a bullet strikes it.
#[derive(Event)]
pub(crate) struct AsteroidDestroy(pub Entity);
// TODO: BulletDestroy
// Which depends on the still-pending Bullet component creation.

View File

@@ -1,11 +1,13 @@
mod asteroids;
pub mod config;
mod event;
mod preparation_widget;
mod ship;
mod title_screen;
use crate::config::{BACKGROUND_COLOR, PLAYER_SHIP_COLOR, SHIP_ROTATION, SHIP_THRUST, WINDOW_SIZE};
use asteroids::AsteroidSpawner;
use asteroids::{Asteroid, AsteroidSpawner};
use bevy::prelude::*;
use bevy_inspector_egui::InspectorOptions;
use bevy_inspector_egui::prelude::ReflectInspectorOptions;
@@ -16,6 +18,7 @@ use bevy_rapier2d::{
render::RapierDebugRenderPlugin,
};
use config::{ASTEROID_SMALL_COLOR, SHIP_THRUSTER_COLOR_ACTIVE, SHIP_THRUSTER_COLOR_INACTIVE};
use ship::Ship;
pub struct AsteroidPlugin;
@@ -38,7 +41,7 @@ impl Plugin for AsteroidPlugin {
.insert_resource(AsteroidSpawner::new())
.init_resource::<GameAssets>()
.add_systems(Startup, spawn_camera)
.add_systems(OnEnter(GameState::Playing), (spawn_player, spawn_ui))
.add_systems(OnEnter(GameState::Playing), (ship::spawn_player, spawn_ui))
.add_systems(
FixedUpdate,
(
@@ -47,6 +50,7 @@ impl Plugin for AsteroidPlugin {
wrap_entities,
asteroids::tick_asteroid_manager,
asteroids::spawn_asteroid.after(asteroids::tick_asteroid_manager),
collision_listener,
// TODO: Remove debug printing
debug_collision_event_printer,
)
@@ -57,7 +61,9 @@ impl Plugin for AsteroidPlugin {
(integrate_velocity, update_positions, apply_rotation_to_mesh)
.run_if(in_state(GameState::Playing)),
)
.add_event::<asteroids::SpawnAsteroid>();
.add_event::<asteroids::SpawnAsteroid>()
.add_event::<event::AsteroidDestroy>()
.add_event::<event::ShipDestroy>();
app.insert_state(GameState::TitleScreen);
}
}
@@ -68,6 +74,54 @@ fn debug_collision_event_printer(mut collision_events: EventReader<CollisionEven
}
}
/// The collision event routing system.
///
/// When a `CollisionEvent` occurrs, this system checks which things collided
/// and emits secondary events accordignly.
///
/// | Objects | Response |
/// |-|-|
/// | Ship & Asteroid | emits event [`ShipDestroy`](`crate::event::ShipDestroy`) |
/// | Asteroid & Bullet | emits event [`AsteroidDestroy`](`crate::event::AsteroidDestroy`) |
/// | Asteroid & Asteroid | Nothing. Asteroids won't collide with each other |
/// | Bullet & Bullet | Nothing. Bullets won't collide with each other (and probably can't under normal gameplay conditions) |
/// | Bullet & Ship | Nothing. The player shouldn't be able to shoot themselves (and the Flying Saucer hasn't been impl.'d, so it's bullets don't count) |
fn collision_listener(
mut collisions: EventReader<CollisionEvent>,
mut ship_writer: EventWriter<event::ShipDestroy>,
mut asteroid_writer: EventWriter<event::AsteroidDestroy>,
player: Single<Entity, With<Ship>>,
rocks: Query<&Asteroid>,
) {
for event in collisions.read() {
if let CollisionEvent::Started(one, two, _flags) = event {
// Valid collisions are:
//
// - Ship & Asteroid
// - Bullet & Asteroid
//
// Asteroids don't collide with each other, bullets don't collide
// with each other, and bullets don't collide with the player ship.
// Option 1: Ship & Asteroid
if *one == *player {
if rocks.contains(*two) {
// player-asteroid collision
dbg!("Writing ShipDestroy event");
ship_writer.write(event::ShipDestroy);
} // else, we don't care
} else if *two == *player {
if rocks.contains(*one) {
dbg!("Writing ShipDestroy event");
ship_writer.write(event::ShipDestroy);
}
}
// TODO: Bullet-asteroid collisions
}
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq, States)]
pub enum GameState {
TitleScreen, // Program is started. Present title screen and await user start
@@ -85,9 +139,6 @@ struct Velocity(bevy::math::Vec2);
#[derive(Component)]
struct Rotation(f32);
#[derive(Component)]
struct Ship;
/// Marker for any entity that should wrap on screen edges
#[derive(Component)]
struct Wrapping;
@@ -188,31 +239,6 @@ fn spawn_camera(mut commands: Commands) {
commands.spawn(Camera2d);
}
fn spawn_player(mut commands: Commands, game_assets: Res<GameAssets>) {
commands
.spawn((
Collider::ball(0.7),
Sensor,
ActiveEvents::COLLISION_EVENTS,
ActiveCollisionTypes::STATIC_STATIC,
Ship,
Wrapping,
Position(Vec2::default()),
Velocity(Vec2::ZERO),
Rotation(0.0),
Mesh2d(game_assets.ship().0),
MeshMaterial2d(game_assets.ship().1),
Transform::default().with_scale(Vec3::new(20.0, 20.0, 20.0)),
))
.with_child((
Mesh2d(game_assets.thruster_mesh()),
MeshMaterial2d(game_assets.thruster_mat_inactive()),
Transform::default()
.with_scale(Vec3::splat(0.5))
.with_translation(Vec3::new(-0.5, 0.0, -0.1)),
));
}
/*
Checks if "W" is pressed and increases velocity accordingly.
*/

32
src/ship.rs Normal file
View File

@@ -0,0 +1,32 @@
use bevy::prelude::*;
use bevy_rapier2d::prelude::*;
use crate::{GameAssets, Position, Rotation, Velocity, Wrapping};
#[derive(Component)]
pub struct Ship;
pub fn spawn_player(mut commands: Commands, game_assets: Res<GameAssets>) {
commands
.spawn((
Collider::ball(0.7),
Sensor,
ActiveEvents::COLLISION_EVENTS,
ActiveCollisionTypes::STATIC_STATIC,
Ship,
Wrapping,
Position(Vec2::default()),
Velocity(Vec2::ZERO),
Rotation(0.0),
Mesh2d(game_assets.ship().0),
MeshMaterial2d(game_assets.ship().1),
Transform::default().with_scale(Vec3::new(20.0, 20.0, 20.0)),
))
.with_child((
Mesh2d(game_assets.thruster_mesh()),
MeshMaterial2d(game_assets.thruster_mat_inactive()),
Transform::default()
.with_scale(Vec3::splat(0.5))
.with_translation(Vec3::new(-0.5, 0.0, -0.1)),
));
}