Skip to content
This repository has been archived by the owner on Aug 2, 2023. It is now read-only.

Commit

Permalink
Add collisions (#20)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jdno committed Mar 16, 2022
1 parent e0db74a commit 28a645e
Show file tree
Hide file tree
Showing 11 changed files with 132 additions and 15 deletions.
18 changes: 12 additions & 6 deletions protobufs/atc/v1/event.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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;
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/atc-game/src/components/airplane.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down
21 changes: 21 additions & 0 deletions src/atc-game/src/components/collider.rs
Original file line number Diff line number Diff line change
@@ -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<T: Send>() {}
assert_send::<Collider>();
}

#[test]
fn trait_sync() {
fn assert_sync<T: Sync>() {}
assert_sync::<Collider>();
}
}
2 changes: 2 additions & 0 deletions src/atc-game/src/components/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
11 changes: 9 additions & 2 deletions src/atc-game/src/event/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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),
Expand All @@ -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 {
Expand Down
17 changes: 12 additions & 5 deletions src/atc-game/src/state/running.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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));
}
}

Expand All @@ -36,3 +37,9 @@ fn send_event(event_bus: Local<EventBus>) {
.send(Event::GameStarted)
.expect("failed to send event"); // TODO: Handle error
}

fn despawn_entities(mut commands: Commands, query: Query<Entity, Without<Camera>>) {
for entity in query.iter() {
commands.entity(entity).despawn_recursive();
}
}
5 changes: 5 additions & 0 deletions src/atc-game/src/store/watcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ impl StoreWatcher {
Event::FlightPlanUpdated(id, flight_plan) => {
self.update_flight_plan(id, flight_plan);
}
Event::GameStopped => self.reset(),
_ => {}
}
}
Expand Down Expand Up @@ -60,4 +61,8 @@ impl StoreWatcher {
airplane.flight_plan = flight_plan.into_api();
}
}

fn reset(&self) {
self.store.clear();
}
}
55 changes: 55 additions & 0 deletions src/atc-game/src/systems/detect_collisions.rs
Original file line number Diff line number Diff line change
@@ -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<State<GameState>>,
query: Query<(Entity, &AirplaneId, &Collider, &Transform)>,
event_bus: Local<EventBus>,
size: Local<Size>,
) {
'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;
}
}
}
}
2 changes: 2 additions & 0 deletions src/atc-game/src/systems/mod.rs
Original file line number Diff line number Diff line change
@@ -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::*;
Expand All @@ -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;
Expand Down
7 changes: 5 additions & 2 deletions src/atc-game/src/systems/spawn_airplane.rs
Original file line number Diff line number Diff line change
@@ -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};

Expand Down Expand Up @@ -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 {
Expand All @@ -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));

Expand Down
7 changes: 7 additions & 0 deletions src/print-client/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -29,6 +30,12 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
}

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();
Expand Down

0 comments on commit 28a645e

Please sign in to comment.