Browse Source

Switch to using ncollide2d for collision detection

Getty Ritter 10 months ago
parent
commit
a44265ae5e
1 changed files with 117 additions and 92 deletions
  1. 117 92
      src/main.rs

+ 117 - 92
src/main.rs

@@ -1,5 +1,7 @@
-#[macro_use] extern crate specs_derive;
-#[macro_use] extern crate specs_system_macro;
+#[macro_use]
+extern crate specs_derive;
+#[macro_use]
+extern crate specs_system_macro;
 
 use ggez::graphics::Drawable;
 use specs::prelude::*;
@@ -44,36 +46,30 @@ impl Sprite {
     /// Convert one of the pre-known sprites to is intended visual appearance
     fn to_mesh(&self, ctx: &mut ggez::Context) -> ggez::GameResult<ggez::graphics::Mesh> {
         match self {
-            Sprite::Player => {
-                ggez::graphics::Mesh::new_circle(
-                    ctx,
-                    ggez::graphics::DrawMode::fill(),
-                    [0.0, 0.0],
-                    10.0,
-                    0.1,
-                    ggez::graphics::WHITE,
-                )
-            },
-            Sprite::Hostile => {
-                ggez::graphics::Mesh::new_circle(
-                    ctx,
-                    ggez::graphics::DrawMode::fill(),
-                    [0.0, 0.0],
-                    10.0,
-                    0.1,
-                    (1.0, 0.0, 0.0).into(),
-                )
-            },
-            Sprite::Kaboom => {
-                ggez::graphics::Mesh::new_circle(
-                    ctx,
-                    ggez::graphics::DrawMode::stroke(1.0),
-                    [0.0, 0.0],
-                    20.0,
-                    0.1,
-                    (0.0, 1.0, 0.0).into(),
-                )
-            },
+            Sprite::Player => ggez::graphics::Mesh::new_circle(
+                ctx,
+                ggez::graphics::DrawMode::fill(),
+                [0.0, 0.0],
+                10.0,
+                0.1,
+                ggez::graphics::WHITE,
+            ),
+            Sprite::Hostile => ggez::graphics::Mesh::new_circle(
+                ctx,
+                ggez::graphics::DrawMode::fill(),
+                [0.0, 0.0],
+                10.0,
+                0.1,
+                (1.0, 0.0, 0.0).into(),
+            ),
+            Sprite::Kaboom => ggez::graphics::Mesh::new_circle(
+                ctx,
+                ggez::graphics::DrawMode::stroke(1.0),
+                [0.0, 0.0],
+                20.0,
+                0.1,
+                (0.0, 1.0, 0.0).into(),
+            ),
         }
     }
 }
@@ -86,16 +82,6 @@ pub struct Position {
     y: f32,
 }
 
-impl Position {
-    /// Lazy collision detection: returns true if the two points are
-    /// within 20 units of each other
-    fn close_to(&self, other: &Position) -> bool{
-        let dx = self.x - other.x;
-        let dy = self.y - other.y;
-        (dx * dx + dy * dy).sqrt() < 20.0
-    }
-}
-
 /// A component for things which move around the screen at a specific
 /// velocity
 #[derive(Component)]
@@ -129,7 +115,6 @@ pub struct HP {
 
 // * KeyState impl
 
-
 /// A KeyState contains four bools that indicate whether particular
 /// keys are held (the only four keys we care about, the famous
 /// Gamer's Square: W, A, S, and who can forget D?)
@@ -182,7 +167,6 @@ impl KeyState {
             self.d_pressed = false;
         }
     }
-
 }
 
 // * systems
@@ -212,7 +196,11 @@ impl<'a, 't> specs::System<'a> for Draw<'t> {
                 dest: [pos.x, pos.y].into(),
                 ..ggez::graphics::DrawParam::default()
             };
-            draw.sprite.to_mesh(self.ctx).unwrap().draw(self.ctx, param).unwrap();
+            draw.sprite
+                .to_mesh(self.ctx)
+                .unwrap()
+                .draw(self.ctx, param)
+                .unwrap();
         }
         // find the HP of the player and print it on the screen
         for (hp, _) in (&hp, &controlled).join() {
@@ -229,7 +217,7 @@ impl<'a, 't> specs::System<'a> for Draw<'t> {
 // based on the current state of the keys. The longer you hold keys,
 // the more it'll accelerate, up to some fixed speed (20 units per
 // tick)
-system!{
+system! {
     Control(resource kc: KeyState, _c: Controlled, mut v: Velocity) {
         const SPEEDUP: f32 = 0.2;
         if kc.w_pressed {
@@ -249,9 +237,8 @@ system!{
     }
 }
 
-
 // This applies friction to components, currently by a global amount
-system!{
+system! {
     Slowdown(mut v: Velocity, _c: Controlled) {
         const ROUNDING: f32 = 0.01;
         const FRICTION: f32 = 0.95;
@@ -269,7 +256,7 @@ system!{
 }
 
 // This moves components around and handles wrapping them
-system!{
+system! {
     Move(mut pos: Position, v: Velocity) {
         pos.x += v.dx;
         pos.y += v.dy;
@@ -286,49 +273,39 @@ system!{
     }
 }
 
-// This naively handles collision detection in a shitty way that
-// would never scale for anything real
-system_impl!{
+system_impl! {
     Collision(
         en: Entity,
+        resource mut world: ncollide2d::world::CollisionWorld<f32, CollisionData>,
         mut pos: Position,
-        hp: HP,
+        col: Collidable,
         mut lifetime: Lifetime,
         mut drawn: Drawn,
         mut damage: AppliesDamage,
     ) {
-        let mut seen = std::collections::HashMap::new();
-        // just compare every entity to every other one
-        for (e1, p1, _) in (&en, &pos, &hp).join() {
-            for (e2, p2, _) in (&en, &pos, &hp).join() {
-                // don't collide with ourselves, that's stupid
-                if e1 == e2 {
-                    continue;
+        use nalgebra::Isometry2;
+        for (p, c) in (&mut pos, &col).join() {
+            world.get_mut(c.handle).expect("Unable to find collision object")
+                .set_position(Isometry2::translation(p.x / 10.0, p.y / 10.0));
+        }
+
+        world.update();
+        for (c1, c2, _, man) in world.contact_pairs(true) {
+            if let Some(m) = man.deepest_contact() {
+                let a = m.contact.world1;
+                let ping = en.create();
+                lifetime.insert(ping, Lifetime { lifetime: 10 }).unwrap();
+                drawn.insert(ping, Drawn { sprite: Sprite::Kaboom }).unwrap();
+                pos.insert(ping, Position {x: a.x * 10.0, y: a.y * 10.0}).unwrap();
+
+                if let Some(e1) = world.collision_object(c1).unwrap().data().for_entity {
+                    damage.insert(en.create(), AppliesDamage { target: e1 }).unwrap();
                 }
-                // if they're close and we haven't already looked at
-                // this one (which we might have!) then add it to the
-                // set of collisions
-                if p1.close_to(p2) && !seen.contains_key(&(e1, e2)) {
-                    seen.insert((e1, e2), p1.clone());
+                if let Some(e2) = world.collision_object(c2).unwrap().data().for_entity {
+                    damage.insert(en.create(), AppliesDamage { target: e2 }).unwrap();
                 }
             }
         }
-        // now, for each collision
-        for ((e1, e2), p) in seen {
-            // create the kaboom graphic! it'll only last 10 ticks,
-            // such is the nature of kabooms. (this should probably be
-            // split apart into another system, but whatevs)
-            let ping = en.create();
-            lifetime.insert(ping, Lifetime { lifetime: 10 }).unwrap();
-            drawn.insert(ping, Drawn { sprite: Sprite::Kaboom }).unwrap();
-            pos.insert(ping, p).unwrap();
-            // and create the damage events
-            let ev1 = en.create();
-            damage.insert(ev1, AppliesDamage { target: e1 }).unwrap();
-
-            let ev2 = en.create();
-            damage.insert(ev2, AppliesDamage { target: e2 }).unwrap();
-        }
     }
 }
 
@@ -355,16 +332,25 @@ system_impl! {
     ) {
         for dmg in (&dmg).join() {
             if let Some(target) = hp.get_mut(dmg.target) {
-                target.hp -= 1;
                 if target.hp == 0 {
                     en.delete(dmg.target).unwrap();
                 }
+                target.hp -= 1;
             }
         }
         dmg.clear();
     }
 }
 
+pub struct CollisionData {
+    for_entity: Option<specs::Entity>,
+}
+
+#[derive(Component)]
+pub struct Collidable {
+    handle: ncollide2d::pipeline::CollisionObjectSlabHandle,
+}
+
 // * game definition
 
 /// A game just needs a specs world! All our state is in there
@@ -374,6 +360,11 @@ struct MyGame {
 
 impl MyGame {
     fn setup() -> MyGame {
+        use nalgebra::Isometry2;
+        use ncollide2d::pipeline::{CollisionGroups, GeometricQueryType};
+        use ncollide2d::shape::{Ball, ShapeHandle};
+        use ncollide2d::world::CollisionWorld;
+
         // this is the first step in making apple pie from scratch
         let mut world = specs::World::new();
         // register some component types
@@ -384,17 +375,41 @@ impl MyGame {
         world.register::<HP>();
         world.register::<Lifetime>();
         world.register::<AppliesDamage>();
+        world.register::<Collidable>();
 
         // create our blank key state
         world.insert(KeyState::new());
+
+        // create a collision world
+        let mut collision_world = CollisionWorld::<f32, CollisionData>::new(0.02);
+        let mut group = CollisionGroups::new();
+        group.set_membership(&[0]);
+        group.set_whitelist(&[0]);
+
+        let contacts_query = GeometricQueryType::Contacts(0.0, 0.0);
+
         // create a player
-        world.create_entity()
-            .with(Drawn { sprite: Sprite::Player })
+        let (player_handle, object) = collision_world.add(
+            Isometry2::translation(20.0, 20.0),
+            ShapeHandle::new(Ball::new(1.0)),
+            group,
+            contacts_query,
+            CollisionData { for_entity: None },
+        );
+        let player = world
+            .create_entity()
+            .with(Drawn {
+                sprite: Sprite::Player,
+            })
             .with(Position { x: 200.0, y: 200.0 })
             .with(Controlled)
             .with(Velocity { dx: 0.0, dy: 0.0 })
             .with(HP { hp: MAX_HP })
+            .with(Collidable {
+                handle: player_handle,
+            })
             .build();
+        object.data_mut().for_entity = Some(player);
 
         // create ten ENEMY ORBS!!!!!!!11111one
         for _ in 0..10 {
@@ -402,18 +417,31 @@ impl MyGame {
             let y = rand::random::<f32>() * HEIGHT;
             let dx = (rand::random::<f32>() * 20.0) - 10.0;
             let dy = (rand::random::<f32>() * 20.0) - 10.0;
-            world.create_entity()
-                .with(Drawn { sprite: Sprite::Hostile })
+            let (handle, object) = collision_world.add(
+                Isometry2::translation(x / 10.0, y / 10.0),
+                ShapeHandle::new(Ball::new(1.0)),
+                group,
+                contacts_query,
+                CollisionData { for_entity: None },
+            );
+            let orb = world
+                .create_entity()
+                .with(Drawn {
+                    sprite: Sprite::Hostile,
+                })
                 .with(Position { x, y })
                 .with(Velocity { dx, dy })
                 .with(HP { hp: 50 })
+                .with(Collidable { handle })
                 .build();
+            object.data_mut().for_entity = Some(orb);
         }
 
+        world.insert(collision_world);
+
         // this is the world I have created. observe it and despair
         MyGame { world }
     }
-
 }
 
 impl ggez::event::EventHandler for MyGame {
@@ -447,8 +475,7 @@ impl ggez::event::EventHandler for MyGame {
         if keycode == winit::VirtualKeyCode::Escape {
             ggez::event::quit(ctx);
         }
-        KeyState::handle_down(&mut self.world.write_resource(),
-                              keycode);
+        KeyState::handle_down(&mut self.world.write_resource(), keycode);
     }
 
     fn key_up_event(
@@ -457,10 +484,8 @@ impl ggez::event::EventHandler for MyGame {
         keycode: winit::VirtualKeyCode,
         _keymod: ggez::event::KeyMods,
     ) {
-        KeyState::handle_up(&mut self.world.write_resource(),
-                            keycode);
+        KeyState::handle_up(&mut self.world.write_resource(), keycode);
     }
-
 }
 
 // * main