From 28a645ef925e58cb9007487a72571e247efff7c2 Mon Sep 17 00:00:00 2001 From: Jan David Date: Wed, 16 Mar 2022 13:25:56 +0100 Subject: [PATCH] Add collisions (#20) The game now ends when two airplanes collide. When the game ends, it transitions back to the `ready` state so that a new game can be started. Collision detection is implemented using simple rectangular bounding boxes. For this game, this method is reliable enough since airplanes cannot move faster than their own size in a frame. Meaning airplanes cannot glitch through other objects. --- protobufs/atc/v1/event.proto | 18 ++++-- src/atc-game/src/components/airplane.rs | 2 + src/atc-game/src/components/collider.rs | 21 +++++++ src/atc-game/src/components/mod.rs | 2 + src/atc-game/src/event/mod.rs | 11 +++- src/atc-game/src/state/running.rs | 17 ++++-- src/atc-game/src/store/watcher.rs | 5 ++ src/atc-game/src/systems/detect_collisions.rs | 55 +++++++++++++++++++ src/atc-game/src/systems/mod.rs | 2 + src/atc-game/src/systems/spawn_airplane.rs | 7 ++- src/print-client/src/main.rs | 7 +++ 11 files changed, 132 insertions(+), 15 deletions(-) create mode 100644 src/atc-game/src/components/collider.rs create mode 100644 src/atc-game/src/systems/detect_collisions.rs 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();