diff --git a/protobufs/atc/v1/event.proto b/protobufs/atc/v1/event.proto index 3940a5a..48aea9f 100644 --- a/protobufs/atc/v1/event.proto +++ b/protobufs/atc/v1/event.proto @@ -6,6 +6,11 @@ import "atc/v1/airplane.proto"; import "atc/v1/location.proto"; import "atc/v1/node.proto"; +message AirplaneCollided { + string id1 = 1; + string id2 = 2; +} + message AirplaneDetected { Airplane airplane = 1; } @@ -32,12 +37,13 @@ message StreamRequest {} message StreamResponse { oneof event { - AirplaneDetected airplane_detected = 1; - AirplaneLanded airplane_landed = 2; - AirplaneMoved airplane_moved = 3; - FlightPlanUpdated flight_plan_updated = 4; - GameStarted game_started = 5; - GameStopped game_stopped = 6; + AirplaneCollided airplane_collided = 1; + AirplaneDetected airplane_detected = 2; + AirplaneLanded airplane_landed = 3; + AirplaneMoved airplane_moved = 4; + FlightPlanUpdated flight_plan_updated = 5; + GameStarted game_started = 6; + GameStopped game_stopped = 7; } } diff --git a/src/atc-game/src/components/airplane.rs b/src/atc-game/src/components/airplane.rs index 8154429..5f03650 100644 --- a/src/atc-game/src/components/airplane.rs +++ b/src/atc-game/src/components/airplane.rs @@ -1,5 +1,7 @@ use bevy::prelude::*; +pub const AIRPLANE_SIZE: f32 = 24.0; + #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default, Component)] pub struct Airplane; diff --git a/src/atc-game/src/components/collider.rs b/src/atc-game/src/components/collider.rs new file mode 100644 index 0000000..8d8f576 --- /dev/null +++ b/src/atc-game/src/components/collider.rs @@ -0,0 +1,21 @@ +use bevy::prelude::*; + +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default, Component)] +pub struct Collider; + +#[cfg(test)] +mod tests { + use super::Collider; + + #[test] + fn trait_send() { + fn assert_send() {} + assert_send::(); + } + + #[test] + fn trait_sync() { + fn assert_sync() {} + assert_sync::(); + } +} diff --git a/src/atc-game/src/components/mod.rs b/src/atc-game/src/components/mod.rs index 2370f08..24bdfa3 100644 --- a/src/atc-game/src/components/mod.rs +++ b/src/atc-game/src/components/mod.rs @@ -1,11 +1,13 @@ pub use self::airplane::*; pub use self::airplane_id::*; +pub use self::collider::*; pub use self::flight_plan::*; pub use self::location::*; pub use self::speed::*; mod airplane; mod airplane_id; +mod collider; mod flight_plan; mod location; mod speed; diff --git a/src/atc-game/src/event/mod.rs b/src/atc-game/src/event/mod.rs index ecd24f4..e302424 100644 --- a/src/atc-game/src/event/mod.rs +++ b/src/atc-game/src/event/mod.rs @@ -1,7 +1,7 @@ use atc::v1::stream_response::Event as ApiEvent; use atc::v1::{ - Airplane, AirplaneDetected, AirplaneLanded, AirplaneMoved, FlightPlanUpdated, GameStarted, - GameStopped, + Airplane, AirplaneCollided, AirplaneDetected, AirplaneLanded, AirplaneMoved, FlightPlanUpdated, + GameStarted, GameStopped, }; use crate::api::IntoApi; @@ -13,6 +13,7 @@ mod bus; #[derive(Clone, Eq, PartialEq, Hash, Debug)] pub enum Event { + AirplaneCollided(AirplaneId, AirplaneId), AirplaneDetected(AirplaneId, Location, FlightPlan), AirplaneLanded(AirplaneId), AirplaneMoved(AirplaneId, Location), @@ -26,6 +27,12 @@ impl IntoApi for Event { fn into_api(self) -> Self::ApiType { match self { + Event::AirplaneCollided(airplane_id1, airplane_id2) => { + ApiEvent::AirplaneCollided(AirplaneCollided { + id1: airplane_id1.into_api(), + id2: airplane_id2.into_api(), + }) + } Event::AirplaneDetected(id, location, flight_plan) => { ApiEvent::AirplaneDetected(AirplaneDetected { airplane: Some(Airplane { diff --git a/src/atc-game/src/state/running.rs b/src/atc-game/src/state/running.rs index d40436f..788f65d 100644 --- a/src/atc-game/src/state/running.rs +++ b/src/atc-game/src/state/running.rs @@ -4,8 +4,8 @@ use atc::v1::get_game_state_response::GameState; use crate::event::{Event, EventBus}; use crate::systems::{ - despawn_airplane, follow_flight_plan, setup_airport, setup_grid, spawn_airplane, - update_flight_plan, SpawnTimer, + despawn_airplane, detect_collision, follow_flight_plan, setup_airport, setup_grid, + spawn_airplane, update_flight_plan, SpawnTimer, }; pub struct GameStateRunningPlugin; @@ -22,11 +22,12 @@ impl Plugin for GameStateRunningPlugin { .add_system_set( SystemSet::on_update(GameState::Running) .with_system(despawn_airplane) - .with_system(follow_flight_plan) + .with_system(follow_flight_plan.label("movement")) .with_system(spawn_airplane) - .with_system(update_flight_plan), + .with_system(update_flight_plan) + .with_system(detect_collision.after("movement")), ) - .add_system_set(SystemSet::on_exit(GameState::Running)); + .add_system_set(SystemSet::on_exit(GameState::Running).with_system(despawn_entities)); } } @@ -36,3 +37,9 @@ fn send_event(event_bus: Local) { .send(Event::GameStarted) .expect("failed to send event"); // TODO: Handle error } + +fn despawn_entities(mut commands: Commands, query: Query>) { + for entity in query.iter() { + commands.entity(entity).despawn_recursive(); + } +} diff --git a/src/atc-game/src/store/watcher.rs b/src/atc-game/src/store/watcher.rs index 3d21a2f..c5223bc 100644 --- a/src/atc-game/src/store/watcher.rs +++ b/src/atc-game/src/store/watcher.rs @@ -29,6 +29,7 @@ impl StoreWatcher { Event::FlightPlanUpdated(id, flight_plan) => { self.update_flight_plan(id, flight_plan); } + Event::GameStopped => self.reset(), _ => {} } } @@ -60,4 +61,8 @@ impl StoreWatcher { airplane.flight_plan = flight_plan.into_api(); } } + + fn reset(&self) { + self.store.clear(); + } } diff --git a/src/atc-game/src/systems/detect_collisions.rs b/src/atc-game/src/systems/detect_collisions.rs new file mode 100644 index 0000000..72c71ed --- /dev/null +++ b/src/atc-game/src/systems/detect_collisions.rs @@ -0,0 +1,55 @@ +use bevy::prelude::*; +use bevy::sprite::collide_aabb::collide; + +use atc::v1::get_game_state_response::GameState; + +use crate::components::{AirplaneId, Collider, AIRPLANE_SIZE}; +use crate::event::{Event, EventBus}; + +pub struct Size { + airplane: Vec2, +} + +impl Default for Size { + fn default() -> Self { + Self { + airplane: Vec2::new(AIRPLANE_SIZE, AIRPLANE_SIZE), + } + } +} + +pub fn detect_collision( + mut app_state: ResMut>, + query: Query<(Entity, &AirplaneId, &Collider, &Transform)>, + event_bus: Local, + size: Local, +) { + 'outer: for (entity1, airplane_id1, _, transform1) in query.iter() { + for (entity2, airplane_id2, _, transform2) in query.iter() { + if entity1 == entity2 { + continue; + } + + if collide( + transform1.translation, + size.airplane, + transform2.translation, + size.airplane, + ) + .is_some() + { + event_bus + .sender() + .send(Event::AirplaneCollided( + airplane_id1.clone(), + airplane_id2.clone(), + )) + .expect("failed to send event"); // TODO: Handle error + + app_state.set(GameState::Ready).unwrap(); + + break 'outer; + } + } + } +} diff --git a/src/atc-game/src/systems/mod.rs b/src/atc-game/src/systems/mod.rs index 41e82e5..197501a 100644 --- a/src/atc-game/src/systems/mod.rs +++ b/src/atc-game/src/systems/mod.rs @@ -1,5 +1,6 @@ pub use self::change_app_state::*; pub use self::despawn_airplane::*; +pub use self::detect_collisions::*; pub use self::follow_flight_plan::*; pub use self::setup_airport::*; pub use self::setup_cameras::*; @@ -9,6 +10,7 @@ pub use self::update_flight_plan::*; mod change_app_state; mod despawn_airplane; +mod detect_collisions; mod follow_flight_plan; mod setup_airport; mod setup_cameras; diff --git a/src/atc-game/src/systems/spawn_airplane.rs b/src/atc-game/src/systems/spawn_airplane.rs index 1ed8bf3..4d965a7 100644 --- a/src/atc-game/src/systems/spawn_airplane.rs +++ b/src/atc-game/src/systems/spawn_airplane.rs @@ -1,7 +1,9 @@ use bevy::prelude::*; use rand::Rng; -use crate::components::{Airplane, AirplaneIdGenerator, FlightPlan, Location, Speed}; +use crate::components::{ + Airplane, AirplaneIdGenerator, Collider, FlightPlan, Location, Speed, AIRPLANE_SIZE, +}; use crate::map::{route_between, Tile, MAP_HEIGHT_RANGE, MAP_WIDTH_RANGE}; use crate::{Event, EventBus}; @@ -32,7 +34,7 @@ pub fn spawn_airplane( .spawn_bundle(SpriteBundle { transform: Transform { translation: Vec3::new(spawn_point.x(), spawn_point.y(), 2.0), - scale: Vec3::new(8.0, 8.0, 0.0), + scale: Vec3::new(AIRPLANE_SIZE, AIRPLANE_SIZE, 0.0), ..Default::default() }, sprite: Sprite { @@ -43,6 +45,7 @@ pub fn spawn_airplane( }) .insert(Airplane) .insert(airplane_id.clone()) + .insert(Collider) .insert(flight_plan.clone()) .insert(Speed::new(32.0)); diff --git a/src/print-client/src/main.rs b/src/print-client/src/main.rs index 6f0ee1e..9fcd9d2 100644 --- a/src/print-client/src/main.rs +++ b/src/print-client/src/main.rs @@ -6,6 +6,7 @@ use atc::v1::StreamRequest; fn should_print(event: &Event) -> bool { match event { + Event::AirplaneCollided(_) => true, Event::AirplaneDetected(_) => true, Event::AirplaneLanded(_) => true, Event::AirplaneMoved(_) => false, @@ -29,6 +30,12 @@ async fn main() -> Result<(), Box> { } match event { + Event::AirplaneCollided(collision) => { + println!( + "Airplane {} collided with airplane {}", + collision.id1, collision.id2 + ); + } Event::AirplaneDetected(airplane_detected) => { let airplane = airplane_detected.airplane.unwrap(); let location = airplane.location.unwrap();