]> code.bitgloo.com Git - clyne/entityx.git/commitdiff
Add example.
authorAlec Thomas <alec@swapoff.org>
Thu, 9 Oct 2014 10:14:21 +0000 (21:14 +1100)
committerAlec Thomas <alec@swapoff.org>
Thu, 9 Oct 2014 10:14:21 +0000 (21:14 +1100)
README.md
examples/arial.ttf [new file with mode: 0644]
examples/example.cc [new file with mode: 0644]

index 6223b8dbf78db27caa3ec17f73b905aa35870916..29f53460e8b7f66b63335496b60f397605454b86 100644 (file)
--- a/README.md
+++ b/README.md
@@ -46,6 +46,20 @@ See the [ChangeLog](https://github.com/alecthomas/entityx/blob/master/CHANGES.md
 - [Python bindings](https://github.com/alecthomas/entityx_python) allowing entity logic to be extended through Python scripts.
 - [Rodrigo Setti](https://github.com/rodrigosetti) has written an OpenGL [Asteroids clone](https://github.com/rodrigosetti/azteroids) which uses EntityX.
 
+## Example
+
+An SFML2 example application is [available](/examples/example.cc) that shows most of EntityX's concepts. It spawns random circles on a 2D plane moving in random directions. If two circles collide they will explode and emit particles. All circles and particles are entities.
+
+It illustrates:
+
+- Separation of data via components.
+- Separation of logic via systems.
+- Use of events (colliding bodies trigger a CollisionEvent).
+
+Compile with:
+
+    c++ -O3 -std=c++11 -Wall -lsfml-system -lsfml-window -lsfml-graphics -lentityx example.cc -o example
+
 ## Overview
 
 In EntityX data associated with an entity is called a `entityx::Component`. `Systems` encapsulate logic and can use as many component types as necessary. An `entityx::EventManager` allows systems to interact without being tightly coupled. Finally, a `Manager` object ties all of the systems together for convenience.
diff --git a/examples/arial.ttf b/examples/arial.ttf
new file mode 100644 (file)
index 0000000..ab68fb1
Binary files /dev/null and b/examples/arial.ttf differ
diff --git a/examples/example.cc b/examples/example.cc
new file mode 100644 (file)
index 0000000..0caa952
--- /dev/null
@@ -0,0 +1,342 @@
+/**
+ * This is an example of using EntityX.
+ *
+ * It is an SFML2 application that spawns 100 random circles on a 2D plane
+ * moving in random directions. If two circles collide they will explode and
+ * emit particles.
+ *
+ * This illustrates a bunch of EC/EntityX concepts:
+ *
+ * - Separation of data via components.
+ * - Separation of logic via systems.
+ * - Use of events (colliding bodies trigger a CollisionEvent).
+ *
+ * Compile with:
+ *
+ *    c++ -O3 -std=c++11 -Wall -lsfml-system -lsfml-window -lsfml-graphics -lentityx readme.cc -o readme
+ */
+#include <unordered_set>
+#include <cstdlib>
+#include <strstream>
+#include <memory>
+#include <string>
+#include <vector>
+#include <iostream>
+#include <SFML/Window.hpp>
+#include <SFML/Graphics.hpp>
+#include <entityx/entityx.h>
+
+using std::cerr;
+using std::cout;
+using std::endl;
+
+namespace ex = entityx;
+
+namespace std {
+  template <>
+  struct hash<ex::Entity> {
+    std::size_t operator()(const ex::Entity& k) const { return k.id().id(); }
+  };
+}
+
+
+float r(int a, float b = 0) {
+  return static_cast<float>(std::rand() % (a * 1000) + b * 1000) / 1000.0;
+}
+
+
+struct Body : ex::Component<Body> {
+  Body(const sf::Vector2f &position, const sf::Vector2f &direction, float rotationd = 0.0)
+    : position(position), direction(direction), rotationd(rotationd) {}
+
+  sf::Vector2f position;
+  sf::Vector2f direction;
+  float rotation = 0.0, rotationd;
+};
+
+
+struct Renderable : ex::Component<Renderable> {
+  explicit Renderable(std::unique_ptr<sf::Shape> shape) : shape(std::move(shape)) {}
+
+  std::unique_ptr<sf::Shape> shape;
+};
+
+
+struct Fadeable : ex::Component<Fadeable> {
+  explicit Fadeable(sf::Uint8 alpha, float duration) : alpha(alpha), d(alpha / duration) {}
+
+  float alpha, d;
+};
+
+
+struct Collideable : ex::Component<Collideable> {
+  explicit Collideable(float radius) : radius(radius) {}
+
+  float radius;
+};
+
+
+// Emitted when two entities collide.
+struct CollisionEvent : public ex::Event<CollisionEvent> {
+  CollisionEvent(ex::Entity left, ex::Entity right) : left(left), right(right) {}
+
+  ex::Entity left, right;
+};
+
+
+// Updates a body's position and rotation.
+struct BodySystem : public ex::System<BodySystem> {
+  void update(ex::EntityManager &es, ex::EventManager &events, double dt) override {
+    Body::Handle body;
+    for (ex::Entity entity : es.entities_with_components(body)) {
+      body->position += body->direction * static_cast<float>(dt);
+      body->rotation += body->rotationd * dt;
+    }
+  };
+};
+
+
+// Fades out the alpha value of any Renderable and Fadeable entity. Once the
+// object has completely faded out it is destroyed.
+struct FadeOutSystem : public ex::System<FadeOutSystem> {
+  void update(ex::EntityManager &es, ex::EventManager &events, double dt) override {
+    Fadeable::Handle fade;
+    Renderable::Handle renderable;
+    for (ex::Entity entity : es.entities_with_components(fade, renderable)) {
+      fade->alpha -= fade->d * dt;
+      if (fade->alpha <= 0)
+        entity.destroy();
+      else {
+        sf::Color color = renderable->shape->getFillColor();
+        color.a = fade->alpha;
+        renderable->shape->setFillColor(color);
+      }
+    }
+  }
+};
+
+
+// Bounce bodies off the edge of the screen.
+class BounceSystem : public ex::System<BounceSystem> {
+public:
+  explicit BounceSystem(sf::RenderTarget &target) : size(target.getSize()) {}
+
+  void update(ex::EntityManager &es, ex::EventManager &events, double dt) override {
+    Body::Handle body;
+    for (ex::Entity entity : es.entities_with_components(body)) {
+      if (body->position.x + body->direction.x < 0 ||
+          body->position.x + body->direction.x >= size.x)
+        body->direction.x = -body->direction.x;
+      if (body->position.y + body->direction.y < 0 ||
+          body->position.y + body->direction.y >= size.y)
+        body->direction.y = -body->direction.y;
+    }
+  }
+
+private:
+  sf::Vector2u size;
+};
+
+
+// Determines if two Collideable bodies have collided. If they have it emits a
+// CollisionEvent. This is used by ExplosionSystem to create explosion
+// particles, but it could be used by a SoundSystem to play an explosion
+// sound, etc..
+class CollisionSystem : public ex::System<CollisionSystem> {
+ public:
+  void update(ex::EntityManager &es, ex::EventManager &events, double dt) override {
+    Body::Handle left_body;
+    Collideable::Handle left_collideable;
+    for (ex::Entity left_entity : es.entities_with_components(left_body, left_collideable)) {
+
+      for (ex::Entity right_entity : es.entities_with_components<Body, Collideable>()) {
+        if (left_entity == right_entity) continue;
+
+        Body::Handle right_body = right_entity.component<Body>();
+        Collideable::Handle right_collideable = right_entity.component<Collideable>();
+        if (collided(left_body, left_collideable, right_body, right_collideable))
+          events.emit<CollisionEvent>(left_entity, right_entity);
+      }
+    }
+  };
+
+private:
+  float length(const sf::Vector2f &v) {
+    return std::sqrt(v.x * v.x + v.y * v.y);
+  }
+
+
+  bool collided(Body::Handle r1, Collideable::Handle c1, Body::Handle r2, Collideable::Handle c2) {
+    return length(r1->position - r2->position) < c1->radius + c2->radius;
+  }
+};
+
+
+// For any two colliding bodies, destroys the bodies and emits a bunch of bodgy explosion particles.
+class ExplosionSystem : public ex::System<ExplosionSystem>, public ex::Receiver<ExplosionSystem> {
+public:
+  void configure(ex::EventManager &events) override {
+    events.subscribe<CollisionEvent>(*this);
+  }
+
+  void update(ex::EntityManager &es, ex::EventManager &events, double dt) override {
+    for (ex::Entity entity : collided) {
+      emit_particles(es, entity);
+      entity.destroy();
+    }
+    collided.clear();
+  }
+
+  void emit_particles(ex::EntityManager &es, ex::Entity entity) {
+    Body::Handle body = entity.component<Body>();
+    Renderable::Handle renderable = entity.component<Renderable>();
+    Collideable::Handle collideable = entity.component<Collideable>();
+    sf::Color colour = renderable->shape->getFillColor();
+    colour.a = 200;
+
+    float area = (M_PI * collideable->radius * collideable->radius) / 3.0;
+    for (int i = 0; i < area; i++) {
+      ex::Entity particle = es.create();
+
+      float rotationd = r(720, 180);
+      if (std::rand() % 2 == 0) rotationd = -rotationd;
+
+      particle.assign<Body>(
+        body->position + sf::Vector2f(r(collideable->radius * 2, -collideable->radius), r(collideable->radius * 2, -collideable->radius)),
+        body->direction + sf::Vector2f(r(50, -25), r(100, -50)),
+        rotationd);
+
+      float radius = r(3, 1);
+      std::unique_ptr<sf::Shape> shape(new sf::RectangleShape(sf::Vector2f(radius * 2, radius * 2)));
+      shape->setFillColor(colour);
+      shape->setOrigin(radius, radius);
+      particle.assign<Renderable>(std::move(shape));
+
+      particle.assign<Fadeable>(colour.a, radius / 2);
+    }
+  }
+
+  void receive(const CollisionEvent &collision) {
+    // Events are immutable, so we can't destroy the entities here. We defer
+    // the work until the update loop.
+    collided.insert(collision.left);
+    collided.insert(collision.right);
+  }
+
+private:
+  std::unordered_set<ex::Entity> collided;
+};
+
+
+// Render all Renderable entities and draw some informational text.
+class RenderSystem  :public ex::System<RenderSystem> {
+public:
+  explicit RenderSystem(sf::RenderTarget &target, sf::Font &font) : target(target) {
+    text.setFont(font);
+    text.setPosition(sf::Vector2f(2, 2));
+    text.setCharacterSize(18);
+    text.setColor(sf::Color::White);
+  }
+
+  void update(ex::EntityManager &es, ex::EventManager &events, double dt) override {
+    Body::Handle body;
+    Renderable::Handle renderable;
+    for (ex::Entity entity : es.entities_with_components(body, renderable)) {
+      renderable->shape->setPosition(body->position);
+      renderable->shape->setRotation(body->rotation);
+      target.draw(*renderable->shape.get());
+    }
+    last_update += dt;
+    if (last_update >= 1.0) {
+      std::strstream out;
+      out << es.size() << " entities (" << static_cast<int>(1.0 / dt) << " fps)";
+      text.setString(out.str());
+      last_update = 0.0;
+      out.freeze(false);
+    }
+    target.draw(text);
+  }
+
+private:
+  float last_update = 0.0;
+  sf::RenderTarget &target;
+  sf::Text text;
+};
+
+
+class Application : public ex::EntityX {
+public:
+  explicit Application(sf::RenderTarget &target, sf::Font &font) {
+    systems.add<BodySystem>();
+    systems.add<FadeOutSystem>();
+    systems.add<BounceSystem>(target);
+    systems.add<CollisionSystem>();
+    systems.add<ExplosionSystem>();
+    systems.add<RenderSystem>(target, font);
+    systems.configure();
+
+    sf::Vector2u size = target.getSize();
+    for (int i = 0; i < 500; i++) {
+      ex::Entity entity = entities.create();
+
+      // Mark as collideable (explosion particles will not be collideable).
+      Collideable::Handle collideable = entity.assign<Collideable>(r(8, 2));
+
+      // "Physical" attributes.
+      entity.assign<Body>(
+        sf::Vector2f(r(size.x), r(size.y)),
+        sf::Vector2f(r(100, -50), r(100, -50)));
+
+      // Shape to apply to entity.
+      std::unique_ptr<sf::Shape> shape(new sf::CircleShape(collideable->radius));
+      shape->setFillColor(sf::Color(r(128, 127), r(128, 127), r(128, 127)));
+      shape->setOrigin(collideable->radius, collideable->radius);
+      entity.assign<Renderable>(std::move(shape));
+    }
+  }
+
+  void update(double dt) {
+    systems.update<BodySystem>(dt);
+    systems.update<FadeOutSystem>(dt);
+    systems.update<BounceSystem>(dt);
+    systems.update<CollisionSystem>(dt);
+    systems.update<ExplosionSystem>(dt);
+    systems.update<RenderSystem>(dt);
+  }
+};
+
+
+
+int main() {
+  std::srand(std::time(nullptr));
+
+  sf::RenderWindow window(sf::VideoMode::getDesktopMode(), "EntityX Example", sf::Style::Fullscreen);
+  sf::Font font;
+  if (!font.loadFromFile("arial.ttf")) {
+    cerr << "error: failed to load arial.ttf" << endl;
+    return 1;
+  }
+
+  Application app(window, font);
+
+  sf::Clock clock;
+  while (window.isOpen()) {
+    sf::Event event;
+    while (window.pollEvent(event)) {
+      switch (event.type) {
+        case sf::Event::Closed:
+        case sf::Event::KeyPressed:
+          window.close();
+          break;
+
+        default:
+          break;
+      }
+    }
+
+    window.clear();
+    sf::Time elapsed = clock.restart();
+    app.update(elapsed.asSeconds());
+    window.display();
+  }
+}