|
@@ -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
|